Kondo 2.0

This commit is contained in:
downthecrop 2024-10-08 01:20:42 -07:00
parent 637e5bf68a
commit b8c7a1150f
14 changed files with 1265 additions and 474 deletions

View file

@ -1,10 +1,165 @@
package KondoKit package KondoKit
import java.awt.Color import rt4.GameShell
import java.awt.Dimension import java.awt.*
import javax.swing.JPanel import java.awt.event.MouseListener
import java.lang.reflect.Field
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.util.*
import java.util.Timer
import javax.swing.*
object Helpers { object Helpers {
fun convertValue(type: Class<*>, genericType: Type?, value: String): Any {
return when {
type == Int::class.java -> value.toInt()
type == Double::class.java -> value.toDouble()
type == Boolean::class.java -> value.toBoolean()
type == Color::class.java -> convertToColor(value)
type == List::class.java && genericType is ParameterizedType -> {
val actualTypeArgument = genericType.actualTypeArguments.firstOrNull()
when {
value.isBlank() -> emptyList<Any>() // Handle empty string by returning an empty list
actualTypeArgument == Int::class.javaObjectType -> value.trim('[', ']').split(",").filter { it.isNotBlank() }.map { it.trim().toInt() }
actualTypeArgument == String::class.java -> value.trim('[', ']').split(",").filter { it.isNotBlank() }.map { it.trim() }
else -> throw IllegalArgumentException("Unsupported List type: $actualTypeArgument")
}
}
else -> value // Default to String
}
}
fun showToast(
parentComponent: Component?,
message: String,
messageType: Int = JOptionPane.INFORMATION_MESSAGE
) {
SwingUtilities.invokeLater {
val toast = JWindow()
toast.type = Window.Type.POPUP
toast.background = Color(0, 0, 0, 0)
val panel = JPanel()
panel.isOpaque = false
panel.layout = BoxLayout(panel, BoxLayout.Y_AXIS)
val label = JLabel(message)
label.foreground = Color.WHITE
label.background = when (messageType) {
JOptionPane.ERROR_MESSAGE -> Color(220, 20, 60, 230) // Crimson for errors
JOptionPane.INFORMATION_MESSAGE -> Color(0, 128, 0, 230) // Green for success
JOptionPane.WARNING_MESSAGE -> Color(255, 165, 0, 230) // Orange for warnings
else -> Color(0, 0, 0, 170) // Default semi-transparent black
}
label.isOpaque = true
label.border = BorderFactory.createEmptyBorder(10, 20, 10, 20)
label.maximumSize = Dimension(242, 50)
label.preferredSize = Dimension(242, 50)
panel.add(label)
toast.contentPane.add(panel)
toast.pack()
// Adjust for parent component location if it exists
if (parentComponent != null) {
val parentLocation = parentComponent.locationOnScreen
val x = parentLocation.x
val y = GameShell.canvas.locationOnScreen.y
toast.setLocation(x, y)
} else {
// Fallback to screen center if no parent is provided
val screenSize = Toolkit.getDefaultToolkit().screenSize
val x = (screenSize.width - toast.width) / 2
val y = screenSize.height - toast.height - 50
toast.setLocation(x, y)
}
toast.isVisible = true
Timer().schedule(object : TimerTask() {
override fun run() {
SwingUtilities.invokeLater {
toast.isVisible = false
toast.dispose()
}
}
}, 2000)
}
}
fun convertToColor(value: String): Color {
val color = Color.decode(value) // Assumes value is in format "#RRGGBB" or "0xRRGGBB"
return color
}
fun colorToHex(color: Color): String {
return "#%02x%02x%02x".format(color.red, color.green, color.blue)
}
fun colorToIntArray(color: Color): IntArray {
return intArrayOf(color.red, color.green, color.blue)
}
interface FieldObserver {
fun onFieldChange(field: Field, newValue: Any?)
}
fun addMouseListenerToAll(container: Container, listener: MouseListener) {
// Recursively go through all components within the container
for (component in container.components) {
// Add the passed MouseListener to the component
if (component is JComponent || component is Canvas) {
component.addMouseListener(listener)
}
// If the component is a container, recursively call this function
if (component is Container) {
addMouseListenerToAll(component, listener)
}
}
}
class FieldNotifier(private val plugin: Any) {
private val observers = mutableListOf<FieldObserver>()
fun addObserver(observer: FieldObserver) {
observers.add(observer)
}
fun notifyFieldChange(field: Field, newValue: Any?) {
for (observer in observers) {
observer.onFieldChange(field, newValue)
}
}
fun setFieldValue(field: Field, value: Any?) {
field.isAccessible = true
field.set(plugin, value)
notifyFieldChange(field, value)
try {
val onUpdateMethod = plugin::class.java.getMethod("OnKondoValueUpdated")
onUpdateMethod.invoke(plugin)
} catch (e: NoSuchMethodException) {
// The method doesn't exist
} catch (e: Exception) {
e.printStackTrace()
}
}
}
fun getSpriteId(skillId: Int) : Int { fun getSpriteId(skillId: Int) : Int {
return when (skillId) { return when (skillId) {
0 -> 197 0 -> 197
@ -78,13 +233,4 @@ object Helpers {
else -> Color(128, 128, 128) // Default grey for unhandled skill IDs else -> Color(128, 128, 128) // Default grey for unhandled skill IDs
} }
} }
class Spacer(width: Int = 0, height: Int = 0) : JPanel() {
init {
preferredSize = Dimension(width, height)
maximumSize = preferredSize
minimumSize = preferredSize
isOpaque = false
}
}
} }

View file

@ -1,9 +1,16 @@
package KondoKit package KondoKit
import KondoKit.Constants.COLOR_BACKGROUND_DARK import KondoKit.Constants.COLOR_BACKGROUND_DARK
import KondoKit.Constants.SKILL_DISPLAY_ORDER
import KondoKit.Constants.SKILL_SPRITE_DIMENSION
import KondoKit.Helpers.formatHtmlLabelText
import KondoKit.Helpers.getSpriteId import KondoKit.Helpers.getSpriteId
import KondoKit.Helpers.showToast
import KondoKit.SpriteToBufferedImage.getBufferedImageFromSprite import KondoKit.SpriteToBufferedImage.getBufferedImageFromSprite
import KondoKit.plugin.Companion.VIEW_BACKGROUND_COLOR import KondoKit.plugin.Companion.VIEW_BACKGROUND_COLOR
import KondoKit.plugin.Companion.WIDGET_COLOR
import KondoKit.plugin.Companion.primaryColor
import KondoKit.plugin.Companion.secondaryColor
import com.google.gson.Gson import com.google.gson.Gson
import plugin.api.API import plugin.api.API
import rt4.Sprites import rt4.Sprites
@ -16,6 +23,7 @@ import java.awt.event.MouseEvent
import java.io.BufferedReader import java.io.BufferedReader
import java.io.InputStreamReader import java.io.InputStreamReader
import java.net.HttpURLConnection import java.net.HttpURLConnection
import java.net.SocketTimeoutException
import java.net.URL import java.net.URL
import javax.swing.* import javax.swing.*
import javax.swing.border.MatteBorder import javax.swing.border.MatteBorder
@ -28,35 +36,38 @@ object Constants {
const val LVL_BAR_SPRITE = 898 const val LVL_BAR_SPRITE = 898
// Dimensions // Dimensions
val SEARCH_FIELD_DIMENSION = Dimension(270, 30) val SEARCH_FIELD_DIMENSION = Dimension(230, 30)
val ICON_DIMENSION_SMALL = Dimension(12, 12) val ICON_DIMENSION_SMALL = Dimension(12, 12)
val ICON_DIMENSION_MEDIUM = Dimension(18, 20) val ICON_DIMENSION_MEDIUM = Dimension(18, 20)
val ICON_DIMENSION_LARGE = Dimension(30, 30) val ICON_DIMENSION_LARGE = Dimension(30, 30)
val HISCORE_PANEL_DIMENSION = Dimension(270, 400) val HISCORE_PANEL_DIMENSION = Dimension(230, 500)
val FILTER_PANEL_DIMENSION = Dimension(270, 30) val FILTER_PANEL_DIMENSION = Dimension(230, 30)
val SKILLS_PANEL_DIMENSION = Dimension(300, 300) val SKILLS_PANEL_DIMENSION = Dimension(230, 290)
val TOTAL_COMBAT_PANEL_DIMENSION = Dimension(270, 30) val TOTAL_COMBAT_PANEL_DIMENSION = Dimension(230, 30)
val SKILL_PANEL_DIMENSION = Dimension(90, 35) val SKILL_PANEL_DIMENSION = Dimension(76, 35)
val IMAGE_CANVAS_DIMENSION = Dimension(20, 20) val IMAGE_CANVAS_DIMENSION = Dimension(20, 20)
val NUMBER_LABEL_DIMENSION = Dimension(30, 20) val SKILL_SPRITE_DIMENSION = Dimension(14, 14)
val NUMBER_LABEL_DIMENSION = Dimension(20, 20)
// Colors // Colors
val COLOR_BACKGROUND_DARK = Color(27, 27, 27) val COLOR_BACKGROUND_DARK = Color(27, 27, 27)
val COLOR_BACKGROUND_MEDIUM = Color(37, 37, 37) val COLOR_BACKGROUND_MEDIUM = VIEW_BACKGROUND_COLOR
val COLOR_BACKGROUND_LIGHT = Color(43, 43, 43)
val COLOR_FOREGROUND_LIGHT = Color(200, 200, 200) val COLOR_FOREGROUND_LIGHT = Color(200, 200, 200)
val COLOR_RED = Color.RED val COLOR_RED = Color.RED
val COLOR_SKILL_PANEL = Color(60, 60, 60) val COLOR_SKILL_PANEL = Color(60, 60, 60)
// Fonts // Fonts
val FONT_ARIAL_PLAIN_14 = Font("Arial", Font.PLAIN, 14) val FONT_ARIAL_PLAIN_14 = Font("Arial", Font.PLAIN, 14)
val FONT_ARIAL_PLAIN_12 = Font("Arial", Font.PLAIN, 12) val FONT_ARIAL_PLAIN_12 = Font("RuneScape Small", Font.TRUETYPE_FONT, 16)
val FONT_ARIAL_BOLD_12 = Font("Arial", Font.BOLD, 12) val FONT_ARIAL_BOLD_12 = Font("Arial", Font.BOLD, 12)
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)
} }
var text: String = "" var text: String = ""
object HiscoresView { object HiscoresView {
var hiScoreView: JPanel? = null
class CustomSearchField(private val hiscoresPanel: JPanel) : Canvas() { class CustomSearchField(private val hiscoresPanel: JPanel) : Canvas() {
private var cursorVisible: Boolean = true private var cursorVisible: Boolean = true
@ -69,6 +80,7 @@ object HiscoresView {
size = preferredSize size = preferredSize
minimumSize = preferredSize minimumSize = preferredSize
maximumSize = preferredSize maximumSize = preferredSize
fillColor = COLOR_BACKGROUND_DARK
} }
} }
@ -125,7 +137,7 @@ object HiscoresView {
} }
}) })
Timer(500) { Timer(1000) {
cursorVisible = !cursorVisible cursorVisible = !cursorVisible
if(plugin.StateManager.focusedView == "HISCORE_SEARCH_VIEW") if(plugin.StateManager.focusedView == "HISCORE_SEARCH_VIEW")
repaint() repaint()
@ -140,7 +152,7 @@ object HiscoresView {
val fm = g.fontMetrics val fm = g.fontMetrics
val cursorX = fm.stringWidth(text) + 30 val cursorX = fm.stringWidth(text) + 30
imageCanvas?.let { canvas -> imageCanvas.let { canvas ->
val imgG = g.create(5, 5, canvas.width, canvas.height) val imgG = g.create(5, 5, canvas.width, canvas.height)
canvas.paint(imgG) canvas.paint(imgG)
imgG.dispose() imgG.dispose()
@ -159,8 +171,10 @@ object HiscoresView {
} }
fun searchPlayer(username: String) { fun searchPlayer(username: String) {
text = username text = username.replace(" ", "_")
val apiUrl = "http://api.2009scape.org:3000/hiscores/playerSkills/1/${username.toLowerCase()}" val apiUrl = "http://api.2009scape.org:3000/hiscores/playerSkills/1/${text.toLowerCase()}"
updateHiscoresView(null, "Searching...")
Thread { Thread {
try { try {
@ -168,6 +182,10 @@ object HiscoresView {
val connection = url.openConnection() as HttpURLConnection val connection = url.openConnection() as HttpURLConnection
connection.requestMethod = "GET" connection.requestMethod = "GET"
// If a request take longer than 5 seconds timeout.
connection.connectTimeout = 5000
connection.readTimeout = 5000
val responseCode = connection.responseCode val responseCode = connection.responseCode
if (responseCode == HttpURLConnection.HTTP_OK) { if (responseCode == HttpURLConnection.HTTP_OK) {
val reader = BufferedReader(InputStreamReader(connection.inputStream)) val reader = BufferedReader(InputStreamReader(connection.inputStream))
@ -179,27 +197,45 @@ object HiscoresView {
} }
} else { } else {
SwingUtilities.invokeLater { SwingUtilities.invokeLater {
showError("Player not found!") showToast(hiscoresPanel, "Player not found!", JOptionPane.ERROR_MESSAGE)
} }
} }
} catch (e: Exception) { } catch (e: SocketTimeoutException) {
SwingUtilities.invokeLater { SwingUtilities.invokeLater {
showError("Error fetching data!") showToast(hiscoresPanel, "Request timed out", JOptionPane.ERROR_MESSAGE)
}
} catch (e: Exception) {
// Handle other errors
SwingUtilities.invokeLater {
showToast(hiscoresPanel, "Error fetching data!", JOptionPane.ERROR_MESSAGE)
} }
} }
}.start() }.start()
} }
private fun updatePlayerData(jsonResponse: String, username: String) { private fun updatePlayerData(jsonResponse: String, username: String) {
val hiscoresResponse = gson.fromJson(jsonResponse, HiscoresResponse::class.java) val hiscoresResponse = gson.fromJson(jsonResponse, HiscoresResponse::class.java)
updateHiscoresView(hiscoresResponse, username) updateHiscoresView(hiscoresResponse, username)
} }
private fun updateHiscoresView(data: HiscoresResponse, username: String) { private fun updateHiscoresView(data: HiscoresResponse?, username: String) {
val playerNameLabel = findComponentByName(hiscoresPanel, "playerNameLabel") as? JPanel val playerNameLabel = findComponentByName(hiscoresPanel, "playerNameLabel") as? JPanel
val ironMode = data.info.iron_mode
playerNameLabel?.removeAll() // Clear previous components playerNameLabel?.removeAll() // Clear previous components
var nameLabel = JLabel(formatHtmlLabelText(username, secondaryColor, "", primaryColor), JLabel.CENTER).apply {
font = Constants.FONT_ARIAL_BOLD_12
foreground = Constants.COLOR_FOREGROUND_LIGHT
border = BorderFactory.createEmptyBorder(0, 6, 0, 0) // Top, Left, Bottom, Right padding
}
playerNameLabel?.add(nameLabel)
playerNameLabel?.revalidate()
playerNameLabel?.repaint()
if(data == null) return;
playerNameLabel?.removeAll()
val ironMode = data.info.iron_mode
if (ironMode != "0") { if (ironMode != "0") {
val ironmanBufferedImage = getBufferedImageFromSprite(Sprites.nameIcons[Constants.IRONMAN_SPRITE + ironMode.toInt() - 1]) val ironmanBufferedImage = getBufferedImageFromSprite(Sprites.nameIcons[Constants.IRONMAN_SPRITE + ironMode.toInt() - 1])
@ -213,11 +249,14 @@ object HiscoresView {
playerNameLabel?.add(imageCanvas) playerNameLabel?.add(imageCanvas)
} }
val nameLabel = JLabel(username, JLabel.CENTER).apply { val exp_multiplier = data.info.exp_multiplier
nameLabel = JLabel(formatHtmlLabelText(username, secondaryColor, " (${exp_multiplier}x)", primaryColor), JLabel.CENTER).apply {
font = Constants.FONT_ARIAL_BOLD_12 font = Constants.FONT_ARIAL_BOLD_12
foreground = Constants.COLOR_FOREGROUND_LIGHT foreground = Constants.COLOR_FOREGROUND_LIGHT
border = BorderFactory.createEmptyBorder(0, 6, 0, 0) // Top, Left, Bottom, Right padding
} }
playerNameLabel?.add(nameLabel) playerNameLabel?.add(nameLabel)
playerNameLabel?.revalidate() playerNameLabel?.revalidate()
@ -296,7 +335,7 @@ object HiscoresView {
} }
} }
fun createHiscoreSearchView(): JPanel { fun createHiscoreSearchView() {
val hiscorePanel = JPanel().apply { val hiscorePanel = JPanel().apply {
layout = BoxLayout(this, BoxLayout.Y_AXIS) layout = BoxLayout(this, BoxLayout.Y_AXIS)
name = "HISCORE_SEARCH_VIEW" name = "HISCORE_SEARCH_VIEW"
@ -324,14 +363,13 @@ object HiscoresView {
add(searchFieldWrapper) add(searchFieldWrapper)
} }
hiscorePanel.add(Helpers.Spacer(height = 10)) hiscorePanel.add(Box.createVerticalStrut(10))
hiscorePanel.add(searchPanel) hiscorePanel.add(searchPanel)
hiscorePanel.add(Helpers.Spacer(height = 10)) hiscorePanel.add(Box.createVerticalStrut(10))
// Adding the player name panel in place of the filterPanel
val playerNamePanel = JPanel().apply { val playerNamePanel = JPanel().apply {
layout = FlowLayout(FlowLayout.CENTER) layout = GridBagLayout() // This will center the JLabel both vertically and horizontally
background = VIEW_BACKGROUND_COLOR background = WIDGET_COLOR
preferredSize = Constants.FILTER_PANEL_DIMENSION preferredSize = Constants.FILTER_PANEL_DIMENSION
maximumSize = preferredSize maximumSize = preferredSize
minimumSize = preferredSize minimumSize = preferredSize
@ -339,7 +377,7 @@ object HiscoresView {
} }
hiscorePanel.add(playerNamePanel) hiscorePanel.add(playerNamePanel)
hiscorePanel.add(Helpers.Spacer(height = 10)) hiscorePanel.add(Box.createVerticalStrut(10))
val skillsPanel = JPanel(FlowLayout(FlowLayout.CENTER, 0, 0)).apply { val skillsPanel = JPanel(FlowLayout(FlowLayout.CENTER, 0, 0)).apply {
background = Constants.COLOR_BACKGROUND_MEDIUM background = Constants.COLOR_BACKGROUND_MEDIUM
@ -348,10 +386,10 @@ object HiscoresView {
minimumSize = preferredSize minimumSize = preferredSize
} }
for (i in 0 until 24) { for (i in SKILL_DISPLAY_ORDER) {
val skillPanel = JPanel().apply { val skillPanel = JPanel().apply {
layout = BorderLayout() layout = BorderLayout()
background = Constants.COLOR_SKILL_PANEL background = COLOR_BACKGROUND_DARK
preferredSize = Constants.SKILL_PANEL_DIMENSION preferredSize = Constants.SKILL_PANEL_DIMENSION
maximumSize = preferredSize maximumSize = preferredSize
minimumSize = preferredSize minimumSize = preferredSize
@ -362,8 +400,9 @@ object HiscoresView {
val imageCanvas = bufferedImageSprite.let { val imageCanvas = bufferedImageSprite.let {
ImageCanvas(it).apply { ImageCanvas(it).apply {
preferredSize = Constants.IMAGE_CANVAS_DIMENSION preferredSize = SKILL_SPRITE_DIMENSION
size = Constants.IMAGE_CANVAS_DIMENSION size = SKILL_SPRITE_DIMENSION
fillColor = COLOR_BACKGROUND_DARK
} }
} }
@ -376,7 +415,7 @@ object HiscoresView {
} }
val imageContainer = JPanel(FlowLayout(FlowLayout.CENTER, 5, 0)).apply { val imageContainer = JPanel(FlowLayout(FlowLayout.CENTER, 5, 0)).apply {
background = Constants.COLOR_BACKGROUND_DARK background = COLOR_BACKGROUND_DARK
add(imageCanvas) add(imageCanvas)
add(numberLabel) add(numberLabel)
} }
@ -431,7 +470,7 @@ object HiscoresView {
} }
val combatLevelPanel = JPanel(FlowLayout(FlowLayout.LEFT)).apply { val combatLevelPanel = JPanel(FlowLayout(FlowLayout.LEFT)).apply {
background = Constants.COLOR_BACKGROUND_DARK background = COLOR_BACKGROUND_DARK
add(combatLevelIcon) add(combatLevelIcon)
add(combatLevelLabel) add(combatLevelLabel)
} }
@ -439,8 +478,9 @@ object HiscoresView {
totalCombatPanel.add(totalLevelPanel) totalCombatPanel.add(totalLevelPanel)
totalCombatPanel.add(combatLevelPanel) totalCombatPanel.add(combatLevelPanel)
hiscorePanel.add(totalCombatPanel) hiscorePanel.add(totalCombatPanel)
hiscorePanel.add(Box.createVerticalStrut(10))
return hiscorePanel hiScoreView = hiscorePanel;
} }
data class HiscoresResponse( data class HiscoresResponse(

View file

@ -1,5 +1,6 @@
package KondoKit package KondoKit
import KondoKit.plugin.Companion.WIDGET_COLOR
import java.awt.Canvas import java.awt.Canvas
import java.awt.Color import java.awt.Color
import java.awt.Dimension import java.awt.Dimension
@ -8,31 +9,25 @@ import java.awt.image.BufferedImage
class ImageCanvas(private val image: BufferedImage) : Canvas() { class ImageCanvas(private val image: BufferedImage) : Canvas() {
var fillColor: Color = WIDGET_COLOR
init { init {
// Manually set the alpha value to 255 (fully opaque) only for pixels that are not fully transparent
val width = image.width val width = image.width
val height = image.height val height = image.height
for (y in 0 until height) { for (y in 0 until height) {
for (x in 0 until width) { for (x in 0 until width) {
// Retrieve the current pixel color
val color = image.getRGB(x, y) val color = image.getRGB(x, y)
// Check if the pixel is not fully transparent (i.e., color is not 0)
if (color != 0) { if (color != 0) {
// Ensure the alpha is set to 255 (fully opaque)
val newColor = (color and 0x00FFFFFF) or (0xFF shl 24) val newColor = (color and 0x00FFFFFF) or (0xFF shl 24)
// Set the pixel with the updated color
image.setRGB(x, y, newColor) image.setRGB(x, y, newColor)
} }
} }
} }
} }
override fun paint(g: Graphics) { override fun paint(g: Graphics) {
super.paint(g) super.paint(g)
g.color = Color(27, 27, 27) g.color = fillColor
g.fillRect(0, 0, width, height) g.fillRect(0, 0, width, height)
g.drawImage(image, 0, 0, width, height, this) g.drawImage(image, 0, 0, width, height, this)
} }

View file

@ -1,100 +0,0 @@
package KondoKit
import java.awt.Color
import java.lang.reflect.Field
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
/*
This is used for the runtime editing of plugin variables.
To expose fields name them starting with `kondoExposed_`
When they are applied this will trigger an invoke of OnKondoValueUpdated()
if it is implemented. Check GroundItems plugin for an example.
*/
object KondoKitUtils {
const val KONDO_PREFIX = "kondoExposed_"
fun isKondoExposed(field: Field): Boolean {
return field.name.startsWith(KONDO_PREFIX)
}
fun getKondoExposedFields(instance: Any): List<Field> {
val exposedFields: MutableList<Field> = ArrayList()
for (field in instance.javaClass.declaredFields) {
if (isKondoExposed(field)) {
exposedFields.add(field)
}
}
return exposedFields
}
fun convertValue(type: Class<*>, genericType: Type?, value: String): Any {
return when {
type == Int::class.java -> value.toInt()
type == Double::class.java -> value.toDouble()
type == Boolean::class.java -> value.toBoolean()
type == Color::class.java -> convertToColor(value)
type == List::class.java && genericType is ParameterizedType -> {
val actualTypeArgument = genericType.actualTypeArguments.firstOrNull()
when {
value.isBlank() -> emptyList<Any>() // Handle empty string by returning an empty list
actualTypeArgument == Int::class.javaObjectType -> value.trim('[', ']').split(",").filter { it.isNotBlank() }.map { it.trim().toInt() }
actualTypeArgument == String::class.java -> value.trim('[', ']').split(",").filter { it.isNotBlank() }.map { it.trim() }
else -> throw IllegalArgumentException("Unsupported List type: $actualTypeArgument")
}
}
else -> value // Default to String
}
}
fun convertToColor(value: String): Color {
val color = Color.decode(value) // Assumes value is in format "#RRGGBB" or "0xRRGGBB"
return color
}
fun colorToHex(color: Color): String {
return "#%02x%02x%02x".format(color.red, color.green, color.blue)
}
fun colorToIntArray(color: Color): IntArray {
return intArrayOf(color.red, color.green, color.blue)
}
interface FieldObserver {
fun onFieldChange(field: Field, newValue: Any?)
}
class FieldNotifier(private val plugin: Any) {
private val observers = mutableListOf<FieldObserver>()
fun addObserver(observer: FieldObserver) {
observers.add(observer)
}
fun notifyFieldChange(field: Field, newValue: Any?) {
for (observer in observers) {
observer.onFieldChange(field, newValue)
}
}
fun setFieldValue(field: Field, value: Any?) {
field.isAccessible = true
field.set(plugin, value)
notifyFieldChange(field, value)
try {
val onUpdateMethod = plugin::class.java.getMethod("OnKondoValueUpdated")
onUpdateMethod.invoke(plugin)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}

View file

@ -1,5 +1,6 @@
package KondoKit package KondoKit
import KondoKit.Helpers.addMouseListenerToAll
import KondoKit.Helpers.formatHtmlLabelText import KondoKit.Helpers.formatHtmlLabelText
import KondoKit.SpriteToBufferedImage.getBufferedImageFromSprite import KondoKit.SpriteToBufferedImage.getBufferedImageFromSprite
import KondoKit.XPTrackerView.wrappedWidget import KondoKit.XPTrackerView.wrappedWidget
@ -9,11 +10,11 @@ import KondoKit.plugin.Companion.WIDGET_COLOR
import KondoKit.plugin.Companion.primaryColor import KondoKit.plugin.Companion.primaryColor
import KondoKit.plugin.Companion.secondaryColor import KondoKit.plugin.Companion.secondaryColor
import plugin.api.API import plugin.api.API
import rt4.NpcTypeList import rt4.*
import rt4.ObjStackNode
import rt4.Player
import rt4.SceneGraph
import java.awt.* import java.awt.*
import java.awt.Font
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.awt.image.BufferedImage import java.awt.image.BufferedImage
import java.io.BufferedReader import java.io.BufferedReader
import java.io.InputStreamReader import java.io.InputStreamReader
@ -22,6 +23,7 @@ import java.net.URL
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import java.text.DecimalFormat import java.text.DecimalFormat
import javax.swing.* import javax.swing.*
import kotlin.math.ceil
object LootTrackerView { object LootTrackerView {
private const val SNAPSHOT_LIFESPAN = 10 private const val SNAPSHOT_LIFESPAN = 10
@ -31,10 +33,12 @@ object LootTrackerView {
private val lootItemPanels = mutableMapOf<String, MutableMap<Int, Int>>() private val lootItemPanels = mutableMapOf<String, MutableMap<Int, Int>>()
private val npcKillCounts = mutableMapOf<String, Int>() private val npcKillCounts = mutableMapOf<String, Int>()
private var totalTrackerWidget: XPWidget? = null private var totalTrackerWidget: XPWidget? = null
var lastConfirmedKillNpcId = -1; var lastConfirmedKillNpcId = -1
var customToolTipWindow: JWindow? = null
var lootTrackerView: JPanel? = null
fun loadGEPrices(): Map<String, String> { fun loadGEPrices(): Map<String, String> {
return if (plugin.kondoExposed_useLiveGEPrices) { return if (plugin.useLiveGEPrices) {
try { try {
println("LootTracker: Loading Remote GE Prices") println("LootTracker: Loading Remote GE Prices")
val url = URL("https://cdn.2009scape.org/gedata/latest.json") val url = URL("https://cdn.2009scape.org/gedata/latest.json")
@ -69,7 +73,7 @@ object LootTrackerView {
} else { } else {
try { try {
println("LootTracker: Loading Local GE Prices") println("LootTracker: Loading Local GE Prices")
BufferedReader(InputStreamReader(plugin::class.java.getResourceAsStream("item_configs.json"), StandardCharsets.UTF_8)) BufferedReader(InputStreamReader(plugin::class.java.getResourceAsStream("res/item_configs.json"), StandardCharsets.UTF_8))
.useLines { lines -> .useLines { lines ->
val json = lines.joinToString("\n") val json = lines.joinToString("\n")
val items = json.trim().removeSurrounding("[", "]").split("},").map { it.trim() + "}" } val items = json.trim().removeSurrounding("[", "]").split("},").map { it.trim() + "}" }
@ -94,17 +98,34 @@ object LootTrackerView {
fun createLootTrackerView(): JPanel { fun createLootTrackerView() {
return JPanel().apply { lootTrackerView = JPanel().apply {
layout = FlowLayout(FlowLayout.CENTER, 0, 5) layout = BoxLayout(this, BoxLayout.Y_AXIS) // Use BoxLayout on Y axis to stack widgets vertically
background = VIEW_BACKGROUND_COLOR background = VIEW_BACKGROUND_COLOR
preferredSize = Dimension(270, 700)
maximumSize = Dimension(270, 700)
minimumSize = Dimension(270, 700)
add(Box.createVerticalStrut(5)) add(Box.createVerticalStrut(5))
totalTrackerWidget = createTotalLootWidget() totalTrackerWidget = createTotalLootWidget()
add(wrappedWidget(totalTrackerWidget!!.panel))
add(Helpers.Spacer(height = 15)) val wrapped = wrappedWidget(totalTrackerWidget!!.container)
val popupMenu = resetLootTrackerMenu()
// Create a custom MouseListener
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)
add(wrapped)
add(Box.createVerticalStrut(10))
revalidate() revalidate()
repaint() repaint()
} }
@ -122,7 +143,7 @@ object LootTrackerView {
totalTrackerWidget?.let { totalTrackerWidget?.let {
it.previousXp += newVal it.previousXp += newVal
it.xpPerHourLabel.text = formatHtmlLabelText("Total Value: ", primaryColor, formatValue(it.previousXp) + " gp", secondaryColor) it.xpPerHourLabel.text = formatHtmlLabelText("Total Value: ", primaryColor, formatValue(it.previousXp) + " gp", secondaryColor)
it.panel.repaint() it.container.repaint()
} }
} }
@ -132,7 +153,7 @@ object LootTrackerView {
val l2 = createLabel(formatHtmlLabelText("Total Count: ", primaryColor, "0", secondaryColor)) val l2 = createLabel(formatHtmlLabelText("Total Count: ", primaryColor, "0", secondaryColor))
return XPWidget( return XPWidget(
skillId = -1, skillId = -1,
panel = createWidgetPanel(bufferedImageSprite,l2,l1), container = createWidgetPanel(bufferedImageSprite,l2,l1),
xpGainedLabel = l2, xpGainedLabel = l2,
xpLeftLabel = JLabel(), xpLeftLabel = JLabel(),
actionsRemainingLabel = JLabel(), actionsRemainingLabel = JLabel(),
@ -173,7 +194,7 @@ object LootTrackerView {
private fun createLabel(text: String): JLabel { private fun createLabel(text: String): JLabel {
return JLabel(text).apply { return JLabel(text).apply {
font = Font("Arial", Font.PLAIN, 11) font = Font("RuneScape Small", Font.TRUETYPE_FONT, 16)
horizontalAlignment = JLabel.LEFT horizontalAlignment = JLabel.LEFT
} }
} }
@ -189,9 +210,15 @@ object LootTrackerView {
// Recalculate lootPanel size based on the number of unique items. // Recalculate lootPanel size based on the number of unique items.
val totalItems = lootItemPanels[npcName]?.size ?: 0 val totalItems = lootItemPanels[npcName]?.size ?: 0
val rowsNeeded = Math.ceil(totalItems / 6.0).toInt() val rowsNeeded = ceil(totalItems / 6.0).toInt()
val lootPanelHeight = rowsNeeded * 36 + (rowsNeeded - 1) val lootPanelHeight = rowsNeeded * (40)
lootPanel.preferredSize = Dimension(270, lootPanelHeight+10)
val size = Dimension(lootPanel.width,lootPanelHeight+32)
lootPanel.parent.preferredSize = size
lootPanel.parent.minimumSize = size
lootPanel.parent.maximumSize = size
lootPanel.parent.revalidate()
lootPanel.parent.repaint()
lootPanel.revalidate() lootPanel.revalidate()
lootPanel.repaint() lootPanel.repaint()
@ -205,21 +232,86 @@ object LootTrackerView {
private fun createItemPanel(itemId: Int, quantity: Int): JPanel { private fun createItemPanel(itemId: Int, quantity: Int): JPanel {
val bufferedImageSprite = getBufferedImageFromSprite(API.GetObjSprite(itemId, quantity, true, 0, 0)) val bufferedImageSprite = getBufferedImageFromSprite(API.GetObjSprite(itemId, quantity, true, 0, 0))
return FixedSizePanel(Dimension(36, 32)).apply {
// Create the panel for the item
val itemPanel = FixedSizePanel(Dimension(36, 32)).apply {
preferredSize = Dimension(36, 32) preferredSize = Dimension(36, 32)
background = WIDGET_COLOR background = WIDGET_COLOR
minimumSize = preferredSize minimumSize = preferredSize
maximumSize = preferredSize maximumSize = preferredSize
add(ImageCanvas(bufferedImageSprite).apply {
val imageCanvas = ImageCanvas(bufferedImageSprite).apply {
preferredSize = Dimension(36, 32) preferredSize = Dimension(36, 32)
background = WIDGET_COLOR background = WIDGET_COLOR
minimumSize = preferredSize minimumSize = preferredSize
maximumSize = preferredSize maximumSize = preferredSize
}, BorderLayout.CENTER) }
// Add the imageCanvas to the panel
add(imageCanvas, BorderLayout.CENTER)
// Put the itemId as a property for reference
putClientProperty("itemId", itemId) putClientProperty("itemId", itemId)
// Add mouse listener for custom hover text
imageCanvas.addMouseListener(object : MouseAdapter() {
override fun mouseEntered(e: MouseEvent) {
// Show custom tooltip when the mouse enters the component
showCustomToolTip(e.point, itemId,quantity,imageCanvas)
}
override fun mouseExited(e: MouseEvent) {
// Hide tooltip when mouse exits
hideCustomToolTip()
}
})
} }
return itemPanel
} }
// Function to show the custom tooltip
fun showCustomToolTip(location: Point, itemId: Int, quantity: Int, parentComponent: ImageCanvas) {
var itemDef = ObjTypeList.get(itemId)
val gePricePerItem = gePriceMap[itemDef.id.toString()]?.toInt() ?: 0
val totalGePrice = gePricePerItem * quantity
val totalHaPrice = itemDef.cost * quantity
val geText = if (quantity > 1) " (${gePricePerItem} ea)" else ""
val haText = if (quantity > 1) " (${itemDef.cost} ea)" else ""
val text = "<html><div style='color: white; background-color: #323232; padding: 3px;'>" +
"${itemDef.name} x $quantity<br>" +
"GE: $totalGePrice ${geText}<br>" +
"HA: $totalHaPrice ${haText}</div></html>"
val _font = Font("RuneScape Small", Font.TRUETYPE_FONT, 16)
val c = Color(50,50,50)
if (customToolTipWindow == null) {
customToolTipWindow = JWindow().apply {
contentPane = JLabel(text).apply {
border = BorderFactory.createLineBorder(Color.BLACK)
isOpaque = true
background = c
foreground = Color.WHITE
font = _font
}
pack()
}
}
// Calculate the tooltip location relative to the parent component
val screenLocation = parentComponent.locationOnScreen
customToolTipWindow!!.setLocation(screenLocation.x + location.x, screenLocation.y + location.y + 20)
customToolTipWindow!!.isVisible = true
}
// Function to hide the custom tooltip
fun hideCustomToolTip() {
customToolTipWindow?.isVisible = false
customToolTipWindow = null // Nullify the global instance
}
private fun updateItemPanelIcon(panel: JPanel, itemId: Int, quantity: Int) { private fun updateItemPanelIcon(panel: JPanel, itemId: Int, quantity: Int) {
panel.removeAll() panel.removeAll()
panel.add(createItemPanel(itemId, quantity).components[0], BorderLayout.CENTER) panel.add(createItemPanel(itemId, quantity).components[0], BorderLayout.CENTER)
@ -308,9 +400,8 @@ object LootTrackerView {
private fun handleNewDrops(npcName: String, newDrops: Set<Item>, lootTrackerView: JPanel) { private fun handleNewDrops(npcName: String, newDrops: Set<Item>, lootTrackerView: JPanel) {
findLootItemsPanel(lootTrackerView, npcName)?.let { findLootItemsPanel(lootTrackerView, npcName)?.let {
} ?: run { } ?: run {
// Panel doesn't exist, so create and add it
lootTrackerView.add(createLootFrame(npcName)) lootTrackerView.add(createLootFrame(npcName))
lootTrackerView.add(Helpers.Spacer(height = 15)) lootTrackerView.add(Box.createVerticalStrut(10))
lootTrackerView.revalidate() lootTrackerView.revalidate()
lootTrackerView.repaint() lootTrackerView.repaint()
} }
@ -330,14 +421,15 @@ object LootTrackerView {
val childFramePanel = JPanel().apply { val childFramePanel = JPanel().apply {
layout = BoxLayout(this, BoxLayout.Y_AXIS) layout = BoxLayout(this, BoxLayout.Y_AXIS)
background = WIDGET_COLOR background = WIDGET_COLOR
minimumSize = Dimension(270, 0) minimumSize = Dimension(230, 0)
maximumSize = Dimension(270, 700) maximumSize = Dimension(230, 700)
name = "HELLO_WORLD"
} }
val labelPanel = JPanel(BorderLayout()).apply { val labelPanel = JPanel(BorderLayout()).apply {
background = Color(21, 21, 21) background = Color(21, 21, 21)
border = BorderFactory.createEmptyBorder(5, 5, 5, 5) border = BorderFactory.createEmptyBorder(5, 5, 5, 5)
maximumSize = Dimension(270, 24) maximumSize = Dimension(230, 24)
minimumSize = maximumSize minimumSize = maximumSize
preferredSize = maximumSize preferredSize = maximumSize
} }
@ -345,14 +437,14 @@ object LootTrackerView {
val killCount = npcKillCounts.getOrPut(npcName) { 0 } val killCount = npcKillCounts.getOrPut(npcName) { 0 }
val countLabel = JLabel(formatHtmlLabelText(npcName, secondaryColor, " x $killCount", primaryColor)).apply { val countLabel = JLabel(formatHtmlLabelText(npcName, secondaryColor, " x $killCount", primaryColor)).apply {
foreground = Color(200, 200, 200) foreground = Color(200, 200, 200)
font = Font("Arial", Font.PLAIN, 12) font = Font("RuneScape Small", Font.TRUETYPE_FONT, 16)
horizontalAlignment = JLabel.LEFT horizontalAlignment = JLabel.LEFT
name = "killCountLabel_$npcName" name = "killCountLabel_$npcName"
} }
val valueLabel = JLabel("0 gp").apply { val valueLabel = JLabel("0 gp").apply {
foreground = Color(200, 200, 200) foreground = Color(200, 200, 200)
font = Font("Arial", Font.PLAIN, 12) font = Font("RuneScape Small", Font.TRUETYPE_FONT, 16)
horizontalAlignment = JLabel.RIGHT horizontalAlignment = JLabel.RIGHT
name = "valueLabel_$npcName" name = "valueLabel_$npcName"
} }
@ -370,19 +462,115 @@ object LootTrackerView {
lootItemPanels[npcName] = mutableMapOf() lootItemPanels[npcName] = mutableMapOf()
// Determine number of items and adjust size of lootPanel
val totalItems = lootItemPanels[npcName]?.size ?: 0
val rowsNeeded = Math.ceil(totalItems / 6.0).toInt()
val lootPanelHeight = rowsNeeded * 36 + (rowsNeeded - 1) // Height per row = 36 + spacing
lootPanel.preferredSize = Dimension(270, lootPanelHeight+10)
childFramePanel.add(labelPanel) childFramePanel.add(labelPanel)
childFramePanel.add(lootPanel) childFramePanel.add(lootPanel)
childFramePanel.add(lootPanel)
val popupMenu = removeLootFrameMenu(childFramePanel, npcName)
// Create a custom MouseListener
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)
}
}
}
labelPanel.addMouseListener(rightClickListener)
return childFramePanel return childFramePanel
} }
fun removeLootFrameMenu(toRemove: JPanel, npcName: String): JPopupMenu {
// Create a popup menu
val popupMenu = JPopupMenu()
val rFont = Font("RuneScape Small", Font.TRUETYPE_FONT, 16)
popupMenu.background = Color(45, 45, 45)
// Create menu items with custom font and colors
val menuItem1 = JMenuItem("Remove").apply {
font = rFont // Set custom font
background = Color(45, 45, 45) // Dark background for item
foreground = Color(220, 220, 220) // Light text color for item
}
popupMenu.add(menuItem1)
menuItem1.addActionListener {
lootItemPanels[npcName]?.clear()
npcKillCounts[npcName] = 0
lootTrackerView?.let { parent ->
val components = parent.components
val toRemoveIndex = components.indexOf(toRemove)
if (toRemoveIndex >= 0 && toRemoveIndex < components.size - 1) {
val nextComponent = components[toRemoveIndex + 1]
if (nextComponent is Box.Filler) {
// Nasty way to remove the Box.createVerticalStrut(10) after
// the lootpanel.
parent.remove(nextComponent)
}
}
parent.remove(toRemove)
parent.revalidate()
parent.repaint()
}
}
return popupMenu
}
fun resetLootTrackerMenu(): JPopupMenu {
// Create a popup menu
val popupMenu = JPopupMenu()
val rFont = Font("RuneScape Small", Font.TRUETYPE_FONT, 16)
popupMenu.background = Color(45, 45, 45)
// Create menu items with custom font and colors
val menuItem1 = JMenuItem("Reset Loot Tracker").apply {
font = rFont // Set custom font
background = Color(45, 45, 45) // Dark background for item
foreground = Color(220, 220, 220) // Light text color for item
}
popupMenu.add(menuItem1)
menuItem1.addActionListener {
lootTrackerView?.removeAll()
npcKillCounts.clear()
lootItemPanels.clear()
totalTrackerWidget = createTotalLootWidget()
val wrapped = wrappedWidget(totalTrackerWidget!!.container)
val _popupMenu = resetLootTrackerMenu()
// Create a custom MouseListener
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)
lootTrackerView?.add(Box.createVerticalStrut(5))
lootTrackerView?.add(wrapped)
lootTrackerView?.add(Box.createVerticalStrut(10))
lootTrackerView?.revalidate()
lootTrackerView?.repaint()
}
return popupMenu
}
class FixedSizePanel(private val fixedSize: Dimension) : JPanel() { class FixedSizePanel(private val fixedSize: Dimension) : JPanel() {
override fun getPreferredSize(): Dimension { override fun getPreferredSize(): Dimension {

View file

@ -2,18 +2,19 @@ package KondoKit
import java.awt.Canvas import java.awt.Canvas
import java.awt.Color import java.awt.Color
import java.awt.Dimension
import java.awt.Font import java.awt.Font
import java.awt.Graphics import java.awt.Graphics
class ProgressBar( class ProgressBar(
private var progress: Double, private var progress: Double,
private val barColor: Color, private val barColor: Color,
private var currentLevel: Int = 0, private var currentLevel: Int = 0,
private var nextLevel: Int = 1 private var nextLevel: Int = 1
) : Canvas() { ) : Canvas() {
init { init {
font = Font("Arial", Font.PLAIN, 12) font = Font("RuneScape Small", Font.TRUETYPE_FONT, 16)
} }
override fun paint(g: Graphics) { override fun paint(g: Graphics) {
@ -25,22 +26,32 @@ class ProgressBar(
g.fillRect(0, 0, width, this.height) g.fillRect(0, 0, width, this.height)
// Draw the unfilled part of the progress bar // Draw the unfilled part of the progress bar
g.color = Color(100, 100, 100) g.color = Color(61, 56, 49) // from Runelite
g.fillRect(width, 0, this.width - width, this.height) g.fillRect(width, 0, this.width - width, this.height)
// Variables for text position
val textY = this.height / 2 + 6
// Draw the current level on the far left // Draw the current level on the far left
g.color = Color(255, 255, 255) drawTextWithShadow(g, "Lvl. $currentLevel", 5, textY, Color(255, 255, 255))
g.drawString("Lvl. $currentLevel", 5, this.height / 2 + 4)
// Draw the percentage in the middle // Draw the percentage in the middle
val percentageText = String.format("%.2f%%", progress) val percentageText = String.format("%.2f%%", progress)
val percentageWidth = g.fontMetrics.stringWidth(percentageText) val percentageWidth = g.fontMetrics.stringWidth(percentageText)
g.drawString(percentageText, (this.width - percentageWidth) / 2, this.height / 2 + 4) drawTextWithShadow(g, percentageText, (this.width - percentageWidth) / 2, textY, Color(255, 255, 255))
// Draw the next level on the far right // Draw the next level on the far right
val nextLevelText = "Lvl. $nextLevel" val nextLevelText = "Lvl. $nextLevel"
val nextLevelWidth = g.fontMetrics.stringWidth(nextLevelText) val nextLevelWidth = g.fontMetrics.stringWidth(nextLevelText)
g.drawString(nextLevelText, this.width - nextLevelWidth - 5, this.height / 2 + 4) drawTextWithShadow(g, nextLevelText, this.width - nextLevelWidth - 5, textY, Color(255, 255, 255))
}
override fun getPreferredSize(): Dimension {
return Dimension(220, 16) // Force the height to 16px, width can be anything appropriate
}
override fun getMinimumSize(): Dimension {
return Dimension(220, 16) // Force the minimum height to 16px, width can be smaller
} }
fun updateProgress(newProgress: Double, currentLevel: Int, nextLevel: Int, isVisible : Boolean) { fun updateProgress(newProgress: Double, currentLevel: Int, nextLevel: Int, isVisible : Boolean) {
@ -50,4 +61,15 @@ class ProgressBar(
if(isVisible) if(isVisible)
repaint() repaint()
} }
// Helper function to draw text with a shadow effect
private fun drawTextWithShadow(g: Graphics, text: String, x: Int, y: Int, textColor: Color) {
// Draw shadow (black text with -1 x and -1 y offset)
g.color = Color(0, 0, 0)
g.drawString(text, x + 1, y + 1)
// Draw actual text on top
g.color = textColor
g.drawString(text, x, y)
}
} }

View file

@ -1,35 +1,50 @@
package KondoKit package KondoKit
import KondoKit.KondoKitUtils.convertValue import KondoKit.Helpers.convertValue
import KondoKit.Helpers.showToast
import KondoKit.plugin.Companion.VIEW_BACKGROUND_COLOR import KondoKit.plugin.Companion.VIEW_BACKGROUND_COLOR
import KondoKit.plugin.Companion.WIDGET_COLOR import KondoKit.plugin.Companion.WIDGET_COLOR
import KondoKit.plugin.Companion.primaryColor import KondoKit.plugin.Companion.primaryColor
import KondoKit.plugin.Companion.secondaryColor import KondoKit.plugin.Companion.secondaryColor
import plugin.Plugin import plugin.Plugin
import plugin.PluginInfo
import plugin.PluginRepository import plugin.PluginRepository
import java.awt.* import java.awt.*
import java.lang.reflect.Field import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.util.* import java.util.*
import java.util.Timer import java.util.Timer
import javax.swing.* import javax.swing.*
import kotlin.math.ceil
/*
This is used for the runtime editing of plugin variables.
To expose fields add the @Exposed annotation.
When they are applied this will trigger an invoke of OnKondoValueUpdated()
if it is implemented. Check GroundItems plugin for an example.
*/
object ReflectiveEditorView { object ReflectiveEditorView {
fun createReflectiveEditorView(): JPanel { var reflectiveEditorView: JPanel? = null
fun createReflectiveEditorView() {
val reflectiveEditorPanel = JPanel(BorderLayout()) val reflectiveEditorPanel = JPanel(BorderLayout())
reflectiveEditorPanel.background = VIEW_BACKGROUND_COLOR reflectiveEditorPanel.background = VIEW_BACKGROUND_COLOR
reflectiveEditorPanel.add(Box.createVerticalStrut(5)) reflectiveEditorPanel.add(Box.createVerticalStrut(5))
reflectiveEditorPanel.border = BorderFactory.createEmptyBorder(10, 10, 10, 10) reflectiveEditorPanel.border = BorderFactory.createEmptyBorder(10, 10, 10, 10)
return reflectiveEditorPanel reflectiveEditorView = reflectiveEditorPanel
addPlugins(reflectiveEditorView!!)
} }
fun addPlugins(reflectiveEditorView: JPanel) { fun addPlugins(reflectiveEditorView: JPanel) {
reflectiveEditorView.removeAll() // clear previous
try { try {
val loadedPluginsField = PluginRepository::class.java.getDeclaredField("loadedPlugins") val loadedPluginsField = PluginRepository::class.java.getDeclaredField("loadedPlugins")
loadedPluginsField.isAccessible = true loadedPluginsField.isAccessible = true
val loadedPlugins = loadedPluginsField.get(null) as HashMap<*, *> val loadedPlugins = loadedPluginsField.get(null) as HashMap<*, *>
for ((_, plugin) in loadedPlugins) { for ((pluginInfo, plugin) in loadedPlugins) {
addPluginToEditor(reflectiveEditorView, plugin as Plugin) addPluginToEditor(reflectiveEditorView, pluginInfo as PluginInfo, plugin as Plugin)
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
@ -38,112 +53,214 @@ object ReflectiveEditorView {
reflectiveEditorView.repaint() reflectiveEditorView.repaint()
} }
private fun addPluginToEditor(reflectiveEditorView: JPanel, plugin: Any) { private fun addPluginToEditor(reflectiveEditorView: JPanel, pluginInfo : PluginInfo, plugin: Plugin) {
reflectiveEditorView.layout = BoxLayout(reflectiveEditorView, BoxLayout.Y_AXIS) reflectiveEditorView.layout = BoxLayout(reflectiveEditorView, BoxLayout.Y_AXIS)
val fieldNotifier = KondoKitUtils.FieldNotifier(plugin) val fieldNotifier = Helpers.FieldNotifier(plugin)
val exposedFields = KondoKitUtils.getKondoExposedFields(plugin) val exposedFields = plugin.javaClass.declaredFields.filter { field ->
field.annotations.any { annotation ->
if (exposedFields.isNotEmpty()) { annotation.annotationClass.simpleName == "Exposed"
val packageName = plugin.javaClass.`package`.name }
val labelPanel = JPanel(BorderLayout())
labelPanel.maximumSize = Dimension(Int.MAX_VALUE, 30) // Adjust height to be minimal
labelPanel.background = VIEW_BACKGROUND_COLOR // Ensure it matches the overall background
labelPanel.border = BorderFactory.createEmptyBorder(5, 0, 5, 0)
val label = JLabel("$packageName", SwingConstants.CENTER)
label.foreground = primaryColor
label.font = Font("Arial", Font.BOLD, 14)
labelPanel.add(label, BorderLayout.CENTER)
reflectiveEditorView.add(labelPanel)
} }
for (field in exposedFields) { if (exposedFields.isNotEmpty()) {
field.isAccessible = true
val fieldPanel = JPanel() val packageName = plugin.javaClass.`package`.name
fieldPanel.layout = GridBagLayout() val version = pluginInfo.version
fieldPanel.background = WIDGET_COLOR // Match the background for minimal borders val labelPanel = JPanel(BorderLayout())
fieldPanel.foreground = secondaryColor labelPanel.maximumSize = Dimension(Int.MAX_VALUE, 30)
fieldPanel.border = BorderFactory.createEmptyBorder(5, 0, 5, 0) // No visible border, just spacing labelPanel.background = VIEW_BACKGROUND_COLOR
fieldPanel.maximumSize = Dimension(Int.MAX_VALUE, 40) labelPanel.border = BorderFactory.createEmptyBorder(5, 0, 0, 0)
val gbc = GridBagConstraints() val label = JLabel("$packageName v$version", SwingConstants.CENTER)
gbc.insets = Insets(0, 5, 0, 5) // Less padding, more minimal spacing label.foreground = primaryColor
label.font = Font("RuneScape Small", Font.TRUETYPE_FONT, 16)
labelPanel.add(label, BorderLayout.CENTER)
label.isOpaque = true
label.background = Color(21, 21, 21)
reflectiveEditorView.add(labelPanel)
val label = JLabel(field.name.removePrefix(KondoKitUtils.KONDO_PREFIX).capitalize()) for (field in exposedFields) {
label.foreground = secondaryColor field.isAccessible = true
gbc.gridx = 0
gbc.gridy = 0
gbc.weightx = 0.0
gbc.anchor = GridBagConstraints.WEST
fieldPanel.add(label, gbc)
val textField = JTextField(field.get(plugin)?.toString() ?: "") // Get the "Exposed" annotation specifically and retrieve its description, if available
textField.background = VIEW_BACKGROUND_COLOR val exposedAnnotation = field.annotations.firstOrNull { annotation ->
textField.foreground = secondaryColor annotation.annotationClass.simpleName == "Exposed"
textField.border = BorderFactory.createLineBorder(WIDGET_COLOR, 1) // Subtle border
gbc.gridx = 1
gbc.gridy = 0
gbc.weightx = 1.0
gbc.fill = GridBagConstraints.HORIZONTAL
fieldPanel.add(textField, gbc)
val applyButton = JButton("Apply")
applyButton.background = primaryColor
applyButton.foreground = VIEW_BACKGROUND_COLOR
applyButton.border = BorderFactory.createLineBorder(primaryColor, 1)
gbc.gridx = 2
gbc.gridy = 0
gbc.weightx = 0.0
gbc.fill = GridBagConstraints.NONE
applyButton.addActionListener {
try {
val newValue = convertValue(field.type, field.genericType, textField.text)
fieldNotifier.setFieldValue(field, newValue)
JOptionPane.showMessageDialog(
null,
"${field.name.removePrefix(KondoKitUtils.KONDO_PREFIX)} updated successfully!"
)
} catch (e: Exception) {
JOptionPane.showMessageDialog(
null,
"Failed to update ${field.name.removePrefix(KondoKitUtils.KONDO_PREFIX)}: ${e.message}",
"Error",
JOptionPane.ERROR_MESSAGE
)
} }
}
fieldPanel.add(applyButton, gbc) val description = exposedAnnotation?.let { annotation ->
reflectiveEditorView.add(fieldPanel) try {
val descriptionField = annotation.annotationClass.java.getMethod("description")
descriptionField.invoke(annotation) as String
} catch (e: NoSuchMethodException) {
"" // No description method, return empty string
}
} ?: ""
var previousValue = field.get(plugin)?.toString() val fieldPanel = JPanel()
val timer = Timer() fieldPanel.layout = GridBagLayout()
timer.schedule(object : TimerTask() { fieldPanel.background = WIDGET_COLOR
override fun run() { fieldPanel.foreground = secondaryColor
val currentValue = field.get(plugin)?.toString() fieldPanel.border = BorderFactory.createEmptyBorder(5, 0, 5, 0)
if (currentValue != previousValue) { fieldPanel.maximumSize = Dimension(Int.MAX_VALUE, 40)
previousValue = currentValue
SwingUtilities.invokeLater { val gbc = GridBagConstraints()
fieldNotifier.notifyFieldChange(field, currentValue) gbc.insets = Insets(0, 5, 0, 5)
val label = JLabel(field.name.capitalize())
label.foreground = secondaryColor
gbc.gridx = 0
gbc.gridy = 0
gbc.weightx = 0.0
label.font = Font("RuneScape Small", Font.TRUETYPE_FONT, 16)
gbc.anchor = GridBagConstraints.WEST
fieldPanel.add(label, gbc)
// Create appropriate input component based on field type
val inputComponent: JComponent = when {
field.type == Boolean::class.javaPrimitiveType || field.type == java.lang.Boolean::class.java -> JCheckBox().apply {
isSelected = field.get(plugin) as Boolean
}
field.type.isEnum -> JComboBox((field.type.enumConstants as Array<Enum<*>>)).apply {
selectedItem = field.get(plugin)
}
field.type == Int::class.javaPrimitiveType || field.type == Integer::class.java -> JSpinner(SpinnerNumberModel(field.get(plugin) as Int, Int.MIN_VALUE, Int.MAX_VALUE, 1))
field.type == Float::class.javaPrimitiveType || field.type == Double::class.javaPrimitiveType || field.type == java.lang.Float::class.java || field.type == java.lang.Double::class.java -> JSpinner(SpinnerNumberModel((field.get(plugin) as Number).toDouble(), -Double.MAX_VALUE, Double.MAX_VALUE, 0.1))
else -> JTextField(field.get(plugin)?.toString() ?: "")
}
// Add mouse listener to the label only if a description is available
if (description.isNotBlank()) {
label.cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)
label.addMouseListener(object : MouseAdapter() {
override fun mouseEntered(e: MouseEvent) {
showCustomToolTip(description, label)
}
override fun mouseExited(e: MouseEvent) {
customToolTipWindow?.isVisible = false
}
})
}
gbc.gridx = 1
gbc.gridy = 0
gbc.weightx = 1.0
gbc.fill = GridBagConstraints.HORIZONTAL
fieldPanel.add(inputComponent, gbc)
val applyButton = JButton("\u2714").apply {
maximumSize = Dimension(Int.MAX_VALUE, 10)
}
gbc.gridx = 2
gbc.gridy = 0
gbc.weightx = 0.0
gbc.fill = GridBagConstraints.NONE
applyButton.addActionListener {
try {
val newValue = when (inputComponent) {
is JCheckBox -> inputComponent.isSelected
is JComboBox<*> -> inputComponent.selectedItem
is JSpinner -> inputComponent.value
is JTextField -> convertValue(field.type, field.genericType, inputComponent.text)
else -> throw IllegalArgumentException("Unsupported input component type")
}
fieldNotifier.setFieldValue(field, newValue)
showToast(
reflectiveEditorView,
"${field.name} updated successfully!"
)
} catch (e: Exception) {
showToast(
reflectiveEditorView,
"Failed to update ${field.name}: ${e.message}",
JOptionPane.ERROR_MESSAGE
)
}
}
fieldPanel.add(applyButton, gbc)
reflectiveEditorView.add(fieldPanel)
// Track field changes in real-time and update UI
var previousValue = field.get(plugin)?.toString()
val timer = Timer()
timer.schedule(object : TimerTask() {
override fun run() {
val currentValue = field.get(plugin)?.toString()
if (currentValue != previousValue) {
previousValue = currentValue
SwingUtilities.invokeLater {
// Update the inputComponent based on the new value
when (inputComponent) {
is JCheckBox -> inputComponent.isSelected = field.get(plugin) as Boolean
is JComboBox<*> -> inputComponent.selectedItem = field.get(plugin)
is JSpinner -> inputComponent.value = field.get(plugin)
is JTextField -> inputComponent.text = field.get(plugin)?.toString() ?: ""
}
}
} }
} }
} }, 0, 1000) // Poll every 1000 milliseconds (1 second)
}, 0, 1000) }
fieldNotifier.addObserver(object : KondoKitUtils.FieldObserver { if (exposedFields.isNotEmpty()) {
override fun onFieldChange(field: Field, newValue: Any?) { reflectiveEditorView.add(Box.createVerticalStrut(5))
if (field.name.removePrefix(KondoKitUtils.KONDO_PREFIX).equals(label.text, ignoreCase = true)) { }
textField.text = newValue?.toString() ?: "" }
textField.revalidate() reflectiveEditorView.revalidate()
textField.repaint() if(KondoKit.plugin.StateManager.focusedView == "REFLECTIVE_EDITOR_VIEW")
} reflectiveEditorView.repaint()
}
var customToolTipWindow: JWindow? = null
fun showCustomToolTip(text: String, component: JComponent) {
val _font = Font("RuneScape Small", Font.PLAIN, 16)
val backgroundColor = Color(50, 50, 50)
val maxWidth = 150
val lineHeight = 16
// Create a dummy JLabel to get FontMetrics for the font used in the tooltip
val dummyLabel = JLabel()
dummyLabel.font = _font
val fontMetrics = dummyLabel.getFontMetrics(_font)
// Calculate the approximate width of the text
val textWidth = fontMetrics.stringWidth(text)
// Calculate the number of lines required based on the text width and max tooltip width
val numberOfLines = ceil(textWidth.toDouble() / maxWidth).toInt()
// Calculate the required height of the tooltip
val requiredHeight = numberOfLines * lineHeight + 6 // Adding some padding
if (customToolTipWindow == null) {
customToolTipWindow = JWindow().apply {
contentPane = JLabel("<html><div style='color: white; background-color: #323232; padding: 3px; word-break: break-all;'>$text</div></html>").apply {
border = BorderFactory.createLineBorder(Color.BLACK)
isOpaque = true
background = backgroundColor
foreground = Color.WHITE
font = _font
maximumSize = Dimension(maxWidth, Int.MAX_VALUE)
preferredSize = Dimension(maxWidth, requiredHeight)
} }
}) pack()
} }
if (exposedFields.isNotEmpty()) { } else {
reflectiveEditorView.add(Box.createVerticalStrut(10)) // Update the tooltip text
val label = customToolTipWindow!!.contentPane as JLabel
label.text = "<html><div style='color: white; background-color: #323232; padding: 3px; word-break: break-all;'>$text</div></html>"
label.preferredSize = Dimension(maxWidth, requiredHeight)
customToolTipWindow!!.pack()
} }
// Position the tooltip near the component
val locationOnScreen = component.locationOnScreen
customToolTipWindow!!.setLocation(locationOnScreen.x, locationOnScreen.y + 15)
customToolTipWindow!!.isVisible = true
} }
} }

View file

@ -0,0 +1,154 @@
package KondoKit
import KondoKit.plugin.Companion.VIEW_BACKGROUND_COLOR
import rt4.GameShell.frame
import java.awt.Color
import java.awt.Graphics
import java.awt.Graphics2D
import java.awt.Rectangle
import java.awt.event.*
import java.util.*
import javax.swing.JPanel
class ScrollablePanel(private val content: JPanel) : JPanel() {
private var lastMouseY = 0
private var currentOffsetY = 0
private var scrollbarHeight = 0
private var scrollbarY = 0
private var showScrollbar = false
private var draggingScrollPill = false
// Define a buffer for the view height (extra space for smoother scrolling)
private val viewBuffer = -30
init {
layout = null
background = VIEW_BACKGROUND_COLOR // Color.red color can be set to debug
// Initial content bounds
content.bounds = Rectangle(0, 0, 242, content.preferredSize.height.coerceAtLeast(frame.height + viewBuffer))
add(content)
// Add listeners for scrolling interactions
addMouseListener(object : MouseAdapter() {
override fun mousePressed(e: MouseEvent) {
lastMouseY = e.y
if (showScrollbar && e.x in (242 - 10)..242 && e.y in scrollbarY..(scrollbarY + scrollbarHeight)) {
draggingScrollPill = true
}
}
override fun mouseReleased(e: MouseEvent) {
draggingScrollPill = false
}
})
addMouseMotionListener(object : MouseMotionAdapter() {
override fun mouseDragged(e: MouseEvent) {
val deltaY = e.y - lastMouseY
if (draggingScrollPill && showScrollbar) {
val viewHeight = frame.height
val contentHeight = content.height
val scrollRatio = contentHeight.toDouble() / viewHeight
scrollContent((deltaY * scrollRatio).toInt())
} else if (showScrollbar) {
scrollContent(deltaY)
}
lastMouseY = e.y
}
})
addMouseWheelListener { e ->
if (showScrollbar) {
scrollContent(-e.wheelRotation * 20)
}
}
// Timer to periodically check and update scrollbar status
Timer().schedule(object : TimerTask() {
override fun run() {
updateScrollbar()
}
}, 0, 1000)
// Component listener for resizing the frame
frame.addComponentListener(object : ComponentAdapter() {
override fun componentResized(e: ComponentEvent) {
handleResize()
}
})
}
private fun handleResize() {
// Ensure the ScrollablePanel resizes with the frame
bounds = Rectangle(0, 0, 242, frame.height)
// Dynamically update content bounds and scrollbar on frame resize with buffer
content.bounds = Rectangle(0, 0, 242, content.preferredSize.height.coerceAtLeast(frame.height + viewBuffer))
showScrollbar = content.height > frame.height
currentOffsetY = 0
content.setLocation(0, currentOffsetY)
updateScrollbar()
revalidate()
repaint()
}
private fun scrollContent(deltaY: Int) {
if (!showScrollbar) {
currentOffsetY = 0
content.setLocation(0, currentOffsetY)
return
}
currentOffsetY += deltaY
// Apply buffer to maxOffset
val maxOffset = (frame.height - content.height + viewBuffer).coerceAtMost(0)
currentOffsetY = currentOffsetY.coerceAtMost(0).coerceAtLeast(maxOffset)
content.setLocation(0, currentOffsetY)
val contentHeight = content.height
val viewHeight = frame.height + viewBuffer
val scrollableRatio = viewHeight.toDouble() / contentHeight
scrollbarY = ((-currentOffsetY / contentHeight.toDouble()) * viewHeight).toInt()
scrollbarHeight = (viewHeight * scrollableRatio).toInt().coerceAtLeast(20)
repaint()
}
private fun updateScrollbar() {
showScrollbar = content.height > frame.height
val contentHeight = content.height
val viewHeight = frame.height + viewBuffer
if (showScrollbar) {
val scrollableRatio = viewHeight.toDouble() / contentHeight
scrollbarY = ((-currentOffsetY / contentHeight.toDouble()) * viewHeight).toInt()
scrollbarHeight = (viewHeight * scrollableRatio).toInt().coerceAtLeast(20)
} else {
scrollbarY = 0
scrollbarHeight = 0
}
repaint()
}
override fun paintComponent(g: Graphics) {
super.paintComponent(g)
}
override fun paintChildren(g: Graphics) {
super.paintChildren(g)
if (showScrollbar) {
val g2 = g as Graphics2D
val scrollbarX = 238
g2.color = Color(64, 64, 64)
g2.fillRect(scrollbarX, scrollbarY, 2, scrollbarHeight)
}
}
}

View file

@ -4,32 +4,36 @@ import KondoKit.Helpers.formatHtmlLabelText
import KondoKit.Helpers.formatNumber import KondoKit.Helpers.formatNumber
import KondoKit.Helpers.getProgressBarColor import KondoKit.Helpers.getProgressBarColor
import KondoKit.Helpers.getSpriteId import KondoKit.Helpers.getSpriteId
import KondoKit.Helpers.addMouseListenerToAll
import KondoKit.SpriteToBufferedImage.getBufferedImageFromSprite import KondoKit.SpriteToBufferedImage.getBufferedImageFromSprite
import KondoKit.plugin.Companion.IMAGE_SIZE import KondoKit.plugin.Companion.IMAGE_SIZE
import KondoKit.plugin.Companion.LVL_ICON
import KondoKit.plugin.Companion.TOTAL_XP_WIDGET_SIZE import KondoKit.plugin.Companion.TOTAL_XP_WIDGET_SIZE
import KondoKit.plugin.Companion.VIEW_BACKGROUND_COLOR import KondoKit.plugin.Companion.VIEW_BACKGROUND_COLOR
import KondoKit.plugin.Companion.WIDGET_COLOR import KondoKit.plugin.Companion.WIDGET_COLOR
import KondoKit.plugin.Companion.WIDGET_SIZE import KondoKit.plugin.Companion.WIDGET_SIZE
import KondoKit.plugin.Companion.kondoExposed_playerXPMultiplier import KondoKit.plugin.Companion.playerXPMultiplier
import KondoKit.plugin.Companion.primaryColor import KondoKit.plugin.Companion.primaryColor
import KondoKit.plugin.Companion.secondaryColor import KondoKit.plugin.Companion.secondaryColor
import KondoKit.plugin.StateManager.totalXPWidget
import plugin.api.API import plugin.api.API
import java.awt.* import java.awt.*
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.io.BufferedReader import java.io.BufferedReader
import java.io.InputStreamReader import java.io.InputStreamReader
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import javax.swing.Box import javax.swing.*
import javax.swing.BoxLayout
import javax.swing.JLabel
import javax.swing.JPanel
object XPTrackerView { object XPTrackerView {
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()
var totalXPWidget: XPWidget? = null
val initialXP: MutableMap<Int, Int> = HashMap()
var xpTrackerView: JPanel? = null
val npcHitpointsMap: Map<Int, Int> = try { val npcHitpointsMap: Map<Int, Int> = try {
BufferedReader(InputStreamReader(plugin::class.java.getResourceAsStream("npc_hitpoints_map.json"), StandardCharsets.UTF_8)) BufferedReader(InputStreamReader(plugin::class.java.getResourceAsStream("res/npc_hitpoints_map.json"), StandardCharsets.UTF_8))
.useLines { lines -> .useLines { lines ->
val json = lines.joinToString("\n") val json = lines.joinToString("\n")
val pairs = json.trim().removeSurrounding("{", "}").split(",") val pairs = json.trim().removeSurrounding("{", "}").split(",")
@ -74,8 +78,8 @@ object XPTrackerView {
if(LootTrackerView.lastConfirmedKillNpcId != -1 && npcHitpointsMap.isNotEmpty()) { if(LootTrackerView.lastConfirmedKillNpcId != -1 && npcHitpointsMap.isNotEmpty()) {
val npcHP = npcHitpointsMap[LootTrackerView.lastConfirmedKillNpcId] val npcHP = npcHitpointsMap[LootTrackerView.lastConfirmedKillNpcId]
val xpPerKill = when (xpWidget.skillId) { val xpPerKill = when (xpWidget.skillId) {
3 -> kondoExposed_playerXPMultiplier * (npcHP ?: 1) // Hitpoints 3 -> playerXPMultiplier * (npcHP ?: 1) // Hitpoints
else -> kondoExposed_playerXPMultiplier * (npcHP ?: 1) * 4 // Combat XP for other skills else -> playerXPMultiplier * (npcHP ?: 1) * 4 // Combat XP for other skills
} }
val remainingKills = xpLeft / xpPerKill val remainingKills = xpLeft / xpPerKill
xpWidget.actionsRemainingLabel.text = formatHtmlLabelText("Kills: ", primaryColor, remainingKills.toString(), secondaryColor) xpWidget.actionsRemainingLabel.text = formatHtmlLabelText("Kills: ", primaryColor, remainingKills.toString(), secondaryColor)
@ -96,20 +100,57 @@ object XPTrackerView {
xpWidget.previousXp = xp xpWidget.previousXp = xp
if (plugin.StateManager.focusedView == "XP_TRACKER_VIEW") if (plugin.StateManager.focusedView == "XP_TRACKER_VIEW")
xpWidget.panel.repaint() xpWidget.container.repaint()
} }
private fun updateTotalXPWidget(xpGainedSinceLastUpdate: Int) { private fun updateTotalXPWidget(xpGainedSinceLastUpdate: Int) {
val totalXPWidget = plugin.StateManager.totalXPWidget ?: return val totalXPWidget = totalXPWidget ?: return
totalXPWidget.totalXpGained += xpGainedSinceLastUpdate totalXPWidget.totalXpGained += xpGainedSinceLastUpdate
val formattedXp = formatNumber(totalXPWidget.totalXpGained) val formattedXp = formatNumber(totalXPWidget.totalXpGained)
totalXPWidget.xpGainedLabel.text = formatHtmlLabelText("Gained: ", primaryColor, formattedXp, secondaryColor) totalXPWidget.xpGainedLabel.text = formatHtmlLabelText("Gained: ", primaryColor, formattedXp, secondaryColor)
if (plugin.StateManager.focusedView == "XP_TRACKER_VIEW") if (plugin.StateManager.focusedView == "XP_TRACKER_VIEW")
totalXPWidget.panel.repaint() totalXPWidget.container.repaint()
} }
fun resetXPTracker(xpTrackerView : JPanel){
// Redo logic here
xpTrackerView.removeAll()
val popupMenu = createResetMenu()
// Create a custom MouseListener
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)
}
}
}
// Create the XP widget
totalXPWidget = createTotalXPWidget()
val wrapped = wrappedWidget(totalXPWidget!!.container)
addMouseListenerToAll(wrapped,rightClickListener)
wrapped.addMouseListener(rightClickListener)
xpTrackerView.add(Box.createVerticalStrut(5))
xpTrackerView.add(wrapped)
xpTrackerView.add(Box.createVerticalStrut(5))
initialXP.clear()
xpWidgets.clear()
xpTrackerView.revalidate()
if (plugin.StateManager.focusedView == "XP_TRACKER_VIEW")
xpTrackerView.repaint()
}
fun createTotalXPWidget(): XPWidget { fun createTotalXPWidget(): XPWidget {
val widgetPanel = Panel().apply { val widgetPanel = Panel().apply {
@ -120,11 +161,9 @@ object XPTrackerView {
minimumSize = TOTAL_XP_WIDGET_SIZE minimumSize = TOTAL_XP_WIDGET_SIZE
} }
val bufferedImageSprite = getBufferedImageFromSprite(API.GetSprite(898)) val bufferedImageSprite = getBufferedImageFromSprite(API.GetSprite(LVL_ICON))
val imageContainer = Panel(FlowLayout()).apply { val imageContainer = Panel(FlowLayout()).apply {
background = WIDGET_COLOR
preferredSize = IMAGE_SIZE preferredSize = IMAGE_SIZE
maximumSize = IMAGE_SIZE maximumSize = IMAGE_SIZE
minimumSize = IMAGE_SIZE minimumSize = IMAGE_SIZE
@ -133,15 +172,14 @@ object XPTrackerView {
bufferedImageSprite.let { image -> bufferedImageSprite.let { image ->
val imageCanvas = ImageCanvas(image).apply { val imageCanvas = ImageCanvas(image).apply {
background = WIDGET_COLOR preferredSize = Dimension(bufferedImageSprite.width, bufferedImageSprite.height)
preferredSize = Dimension(image.width, image.height) maximumSize = preferredSize
maximumSize = Dimension(image.width, image.height) minimumSize = preferredSize
minimumSize = Dimension(image.width, image.height) size = preferredSize
size = Dimension(image.width, image.height)
} }
imageContainer.add(imageCanvas) imageContainer.add(imageCanvas)
imageContainer.size = Dimension(image.width, image.height) imageContainer.size = Dimension(bufferedImageSprite.width, bufferedImageSprite.height)
imageContainer.revalidate() imageContainer.revalidate()
if(plugin.StateManager.focusedView == "XP_TRACKER_VIEW") if(plugin.StateManager.focusedView == "XP_TRACKER_VIEW")
@ -149,14 +187,13 @@ object XPTrackerView {
} }
val textPanel = Panel().apply { val textPanel = Panel().apply {
layout = GridLayout(2, 1, 5, 5) layout = GridLayout(2, 1, 5, 0)
background = WIDGET_COLOR
} }
val font = Font("Arial", Font.PLAIN, 11) val font = Font("RuneScape Small", Font.TRUETYPE_FONT, 16)
val xpGainedLabel = JLabel( val xpGainedLabel = JLabel(
formatHtmlLabelText("XP Gained: ", primaryColor, "0", secondaryColor) formatHtmlLabelText("Gained: ", primaryColor, "0", secondaryColor)
).apply { ).apply {
this.horizontalAlignment = JLabel.LEFT this.horizontalAlignment = JLabel.LEFT
this.font = font this.font = font
@ -177,7 +214,7 @@ object XPTrackerView {
return XPWidget( return XPWidget(
skillId = -1, skillId = -1,
panel = widgetPanel, container = widgetPanel,
xpGainedLabel = xpGainedLabel, xpGainedLabel = xpGainedLabel,
xpLeftLabel = JLabel(formatHtmlLabelText("XP Left: ", primaryColor, "0", secondaryColor)).apply { xpLeftLabel = JLabel(formatHtmlLabelText("XP Left: ", primaryColor, "0", secondaryColor)).apply {
this.horizontalAlignment = JLabel.LEFT this.horizontalAlignment = JLabel.LEFT
@ -193,19 +230,83 @@ object XPTrackerView {
} }
fun createXPTrackerView(): JPanel? { fun createXPTrackerView(){
val widgetViewPanel = JPanel() val widgetViewPanel = JPanel().apply {
widgetViewPanel.layout = BoxLayout(widgetViewPanel, BoxLayout.Y_AXIS) layout = BoxLayout(this, BoxLayout.Y_AXIS)
widgetViewPanel.background = VIEW_BACKGROUND_COLOR background = VIEW_BACKGROUND_COLOR
widgetViewPanel.add(Box.createVerticalStrut(5)) }
val popupMenu = createResetMenu()
// Create a custom MouseListener
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)
}
}
}
// Create the XP widget
totalXPWidget = createTotalXPWidget() totalXPWidget = createTotalXPWidget()
widgetViewPanel.add(wrappedWidget(totalXPWidget!!.panel)) val wrapped = wrappedWidget(totalXPWidget!!.container)
addMouseListenerToAll(wrapped,rightClickListener)
wrapped.addMouseListener(rightClickListener)
widgetViewPanel.add(Box.createVerticalStrut(5))
widgetViewPanel.add(wrapped)
widgetViewPanel.add(Box.createVerticalStrut(5)) widgetViewPanel.add(Box.createVerticalStrut(5))
return widgetViewPanel xpTrackerView = widgetViewPanel
} }
fun createResetMenu(): JPopupMenu {
// Create a popup menu
val popupMenu = JPopupMenu()
val rFont = Font("RuneScape Small", Font.TRUETYPE_FONT, 16)
popupMenu.background = Color(45, 45, 45)
// Create menu items with custom font and colors
val menuItem1 = JMenuItem("Reset Tracker").apply {
font = rFont // Set custom font
background = Color(45, 45, 45) // Dark background for item
foreground = Color(220, 220, 220) // Light text color for item
}
val menuItem2 = JMenuItem("Option 2").apply {
font = rFont
background = Color(45, 45, 45)
foreground = Color(220, 220, 220)
}
val menuItem3 = JMenuItem("Option 3").apply {
font = rFont
background = Color(45, 45, 45)
foreground = Color(220, 220, 220)
}
// Add menu items to the popup menu
popupMenu.add(menuItem1)
//popupMenu.add(menuItem2)
//popupMenu.add(menuItem3)
// Add action listeners to each menu item (optional)
menuItem1.addActionListener { resetXPTracker(xpTrackerView!!) }
//menuItem2.addActionListener { println("Option 2 selected") }
//menuItem3.addActionListener { println("Option 3 selected") }
return popupMenu
}
fun createXPWidget(skillId: Int, previousXp: Int): XPWidget { fun createXPWidget(skillId: Int, previousXp: Int): XPWidget {
val widgetPanel = Panel().apply { val widgetPanel = Panel().apply {
layout = BorderLayout(5, 5) layout = BorderLayout(5, 5)
@ -242,14 +343,14 @@ object XPTrackerView {
} }
val textPanel = Panel().apply { val textPanel = Panel().apply {
layout = GridLayout(2, 2, 5, 5) layout = GridLayout(2, 2, 5, 0)
background = WIDGET_COLOR background = WIDGET_COLOR
} }
val font = Font("Arial", Font.PLAIN, 11) val font = Font("RuneScape Small", Font.TRUETYPE_FONT, 16)
val xpGainedLabel = JLabel( val xpGainedLabel = JLabel(
formatHtmlLabelText("XP Gained: ", primaryColor, "0", secondaryColor) formatHtmlLabelText("XP Gained: ", primaryColor, "0", secondaryColor)
).apply { ).apply {
this.horizontalAlignment = JLabel.LEFT this.horizontalAlignment = JLabel.LEFT
this.font = font this.font = font
@ -302,7 +403,7 @@ object XPTrackerView {
return XPWidget( return XPWidget(
skillId = skillId, skillId = skillId,
panel = widgetPanel, container = widgetPanel,
xpGainedLabel = xpGainedLabel, xpGainedLabel = xpGainedLabel,
xpLeftLabel = xpLeftLabel, xpLeftLabel = xpLeftLabel,
xpPerHourLabel = xpPerHourLabel, xpPerHourLabel = xpPerHourLabel,
@ -314,18 +415,18 @@ object XPTrackerView {
) )
} }
fun wrappedWidget(component: Component, padding: Int = 7): Panel { fun wrappedWidget(component: Component, padding: Int = 7): Container {
val outerPanelSize = Dimension( val outerPanelSize = Dimension(
component.preferredSize.width + 2 * padding, component.preferredSize.width + 2 * padding,
component.preferredSize.height + 2 * padding component.preferredSize.height + 2 * padding
) )
val outerPanel = Panel(GridBagLayout()).apply { val outerPanel = JPanel(GridBagLayout()).apply {
background = WIDGET_COLOR background = WIDGET_COLOR
preferredSize = outerPanelSize preferredSize = outerPanelSize
maximumSize = outerPanelSize maximumSize = outerPanelSize
minimumSize = outerPanelSize minimumSize = outerPanelSize
} }
val innerPanel = Panel(BorderLayout()).apply { val innerPanel = JPanel(BorderLayout()).apply {
background = WIDGET_COLOR background = WIDGET_COLOR
preferredSize = component.preferredSize preferredSize = component.preferredSize
maximumSize = component.preferredSize maximumSize = component.preferredSize
@ -343,14 +444,14 @@ object XPTrackerView {
data class XPWidget( data class XPWidget(
val panel: Panel, val container: Container,
val skillId: Int, val skillId: Int,
val xpGainedLabel: JLabel, val xpGainedLabel: JLabel,
val xpLeftLabel: JLabel, val xpLeftLabel: JLabel,
val xpPerHourLabel: JLabel, val xpPerHourLabel: JLabel,
val actionsRemainingLabel: JLabel, val actionsRemainingLabel: JLabel,
val progressBar: ProgressBar, val progressBar: ProgressBar,
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
) )

View file

@ -5,22 +5,27 @@ import KondoKit.Helpers.formatHtmlLabelText
import KondoKit.Helpers.formatNumber import KondoKit.Helpers.formatNumber
import KondoKit.Helpers.getSpriteId import KondoKit.Helpers.getSpriteId
import KondoKit.HiscoresView.createHiscoreSearchView import KondoKit.HiscoresView.createHiscoreSearchView
import KondoKit.HiscoresView.hiScoreView
import KondoKit.LootTrackerView.BAG_ICON import KondoKit.LootTrackerView.BAG_ICON
import KondoKit.LootTrackerView.createLootTrackerView import KondoKit.LootTrackerView.createLootTrackerView
import KondoKit.LootTrackerView.lootTrackerView
import KondoKit.LootTrackerView.npcDeathSnapshots import KondoKit.LootTrackerView.npcDeathSnapshots
import KondoKit.LootTrackerView.onPostClientTick import KondoKit.LootTrackerView.onPostClientTick
import KondoKit.LootTrackerView.takeGroundSnapshot import KondoKit.LootTrackerView.takeGroundSnapshot
import KondoKit.ReflectiveEditorView.addPlugins import KondoKit.ReflectiveEditorView.addPlugins
import KondoKit.ReflectiveEditorView.createReflectiveEditorView import KondoKit.ReflectiveEditorView.createReflectiveEditorView
import KondoKit.ReflectiveEditorView.reflectiveEditorView
import KondoKit.SpriteToBufferedImage.getBufferedImageFromSprite import KondoKit.SpriteToBufferedImage.getBufferedImageFromSprite
import KondoKit.XPTrackerView.createTotalXPWidget
import KondoKit.XPTrackerView.createXPTrackerView import KondoKit.XPTrackerView.createXPTrackerView
import KondoKit.XPTrackerView.createXPWidget import KondoKit.XPTrackerView.createXPWidget
import KondoKit.XPTrackerView.initialXP
import KondoKit.XPTrackerView.resetXPTracker
import KondoKit.XPTrackerView.totalXPWidget
import KondoKit.XPTrackerView.updateWidget import KondoKit.XPTrackerView.updateWidget
import KondoKit.XPTrackerView.wrappedWidget import KondoKit.XPTrackerView.wrappedWidget
import KondoKit.plugin.StateManager.initialXP import KondoKit.XPTrackerView.xpTrackerView
import KondoKit.plugin.StateManager.totalXPWidget import KondoKit.XPTrackerView.xpWidgets
import KondoKit.plugin.StateManager.xpWidgets import KondoKit.plugin.StateManager.focusedView
import plugin.Plugin import plugin.Plugin
import plugin.api.* import plugin.api.*
import plugin.api.API.* import plugin.api.API.*
@ -38,37 +43,47 @@ import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent import java.awt.event.MouseEvent
import javax.swing.* import javax.swing.*
@Target(AnnotationTarget.FIELD)
@Retention(AnnotationRetention.RUNTIME)
annotation class Exposed(val description: String = "")
class plugin : Plugin() { class plugin : Plugin() {
companion object { companion object {
val WIDGET_SIZE = Dimension(270, 55) val WIDGET_SIZE = Dimension(220, 50)
val TOTAL_XP_WIDGET_SIZE = Dimension(270, 30) val TOTAL_XP_WIDGET_SIZE = Dimension(220, 30)
val IMAGE_SIZE = Dimension(20, 20) val IMAGE_SIZE = Dimension(20, 20)
val WIDGET_COLOR = Color(27, 27, 27) val WIDGET_COLOR = Color(30, 30, 30)
val VIEW_BACKGROUND_COLOR = Color(37, 37, 37) val VIEW_BACKGROUND_COLOR = Color(40, 40, 40)
val primaryColor = Color(129, 129, 129) // Color for "XP Gained:" val primaryColor = Color(165, 165, 165) // Color for "XP Gained:"
val secondaryColor = Color(226, 226, 226) // Color for "0" val secondaryColor = Color(255, 255, 255) // Color for "0"
var kondoExposed_useLiveGEPrices = true
var kondoExposed_playerXPMultiplier = 5 @Exposed(description = "Default: true, Use Local JSON or the prices from the Live/Stable server API")
const val FIXED_WIDTH = 782 var useLiveGEPrices = true
const val SCROLLPANE_WIDTH = 340
@Exposed(description = "Used to calculate Combat Actions until next level.")
var playerXPMultiplier = 5
@Exposed(description = "Start minimized/collapsed by default")
var launchMinimized = false
private const val FIXED_WIDTH = 782
private const val NAVBAR_WIDTH = 30
private const val MAIN_CONTENT_WIDTH = 242
private const val WRENCH_ICON = 907 private const val WRENCH_ICON = 907
private const val LVL_ICON = 898
private const val LOOT_ICON = 777 private const val LOOT_ICON = 777
private const val MAG_SPRITE = 1423 private const val MAG_SPRITE = 1423
const val LVL_ICON = 898
private lateinit var cardLayout: CardLayout private lateinit var cardLayout: CardLayout
private lateinit var mainContentPanel: Panel private lateinit var mainContentPanel: JPanel
private var scrollPane: JScrollPane? = null private var rightPanelWrapper: JScrollPane? = null
private var hiScoreView: JPanel? = null
private var reflectiveEditorView: JPanel? = null
private var lootTrackerView: JPanel? = null
private var xpTrackerView: JPanel? = null
private var accumulatedTime = 0L private var accumulatedTime = 0L
private var reloadInterfaces = false
private const val tickInterval = 600L private const val tickInterval = 600L
private var pluginsReloaded = false private var pluginsReloaded = false
private var loginScreen = 160; private var loginScreen = 160
private var lastLogin = "" private var lastLogin = ""
private var initialized = false; private var initialized = false;
private var lastClickTime = 0L
} }
fun allSpritesLoaded() : Boolean { fun allSpritesLoaded() : Boolean {
@ -95,43 +110,36 @@ 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?.removeAll() xpTrackerView?.let { resetXPTracker(it) }
totalXPWidget = createTotalXPWidget()
xpTrackerView?.add(Box.createVerticalStrut(5))
xpTrackerView?.add(wrappedWidget(totalXPWidget!!.panel))
xpTrackerView?.add(Box.createVerticalStrut(5))
initialXP.clear()
xpWidgets.clear()
xpTrackerView?.revalidate()
if (StateManager.focusedView == "XP_TRACKER_VIEW")
xpTrackerView?.repaint()
} }
lastLogin = Player.usernameInput.toString() lastLogin = Player.usernameInput.toString()
} }
private fun UpdateDisplaySettings() { private fun UpdateDisplaySettings() {
val mode = GetWindowMode() val mode = GetWindowMode()
val currentScrollPaneWidth = if (mainContentPanel.isVisible) NAVBAR_WIDTH + MAIN_CONTENT_WIDTH else NAVBAR_WIDTH
when (mode) { when (mode) {
WindowMode.FIXED -> { WindowMode.FIXED -> {
if (frame.width < FIXED_WIDTH + SCROLLPANE_WIDTH) { if (frame.width < FIXED_WIDTH + currentScrollPaneWidth) {
frame.setSize(FIXED_WIDTH + SCROLLPANE_WIDTH, frame.height) frame.setSize(FIXED_WIDTH + currentScrollPaneWidth, frame.height)
} }
val difference = frame.width - (FIXED_WIDTH + SCROLLPANE_WIDTH) val difference = frame.width - (FIXED_WIDTH + currentScrollPaneWidth)
GameShell.leftMargin = difference / 2 GameShell.leftMargin = difference / 2
} }
WindowMode.RESIZABLE -> { WindowMode.RESIZABLE -> {
GameShell.canvasWidth -= SCROLLPANE_WIDTH GameShell.canvasWidth = frame.width - (currentScrollPaneWidth + 16)
} }
} }
scrollPane?.revalidate() rightPanelWrapper?.preferredSize = Dimension(currentScrollPaneWidth, frame.height)
scrollPane?.repaint() rightPanelWrapper?.revalidate()
rightPanelWrapper?.repaint()
} }
fun OnKondoValueUpdated(){ fun OnKondoValueUpdated(){
StoreData("kondoUseRemoteGE", kondoExposed_useLiveGEPrices) StoreData("kondoUseRemoteGE", useLiveGEPrices)
StoreData("kondoPlayerXPMultiplier", kondoExposed_playerXPMultiplier) StoreData("kondoPlayerXPMultiplier", playerXPMultiplier)
LootTrackerView.gePriceMap = LootTrackerView.loadGEPrices() LootTrackerView.gePriceMap = LootTrackerView.loadGEPrices()
StoreData("kondoLaunchMinimized", launchMinimized)
} }
override fun OnMiniMenuCreate(currentEntries: Array<out MiniMenuEntry>?) { override fun OnMiniMenuCreate(currentEntries: Array<out MiniMenuEntry>?) {
@ -139,12 +147,16 @@ class plugin : Plugin() {
for ((index, entry) in currentEntries.withIndex()) { for ((index, entry) in currentEntries.withIndex()) {
if (entry.type == MiniMenuType.PLAYER && index == currentEntries.size - 1) { if (entry.type == MiniMenuType.PLAYER && index == currentEntries.size - 1) {
val input = entry.subject val input = entry.subject
val username = input // Trim spaces, clean up tags, and remove the level info
.replace(Regex("<col=[0-9a-fA-F]{6}>"), "") val cleanedInput = input
.replace(Regex("<img=\\d+>"), "") .trim() // Remove any leading/trailing spaces
.split(" ") // Split by spaces .replace(Regex("<col=[0-9a-fA-F]{6}>"), "") // Remove color tags
.first() // Take the first part, which is the username .replace(Regex("<img=\\d+>"), "") // Remove image tags
InsertMiniMenuEntry("Lookup", entry.subject, searchHiscore(username.replace(" ","_"))) .replace(Regex("\\(level: \\d+\\)"), "") // Remove level text e.g. (level: 44)
.trim() // Trim again to remove extra spaces after removing level text
// Proceed with the full cleaned username
InsertMiniMenuEntry("Lookup", entry.subject, searchHiscore(cleanedInput))
} }
} }
} }
@ -152,40 +164,31 @@ class plugin : Plugin() {
private fun searchHiscore(username: String): Runnable { private fun searchHiscore(username: String): Runnable {
return Runnable { return Runnable {
cardLayout.show(mainContentPanel, "HISCORE_SEARCH_VIEW") setActiveView("HISCORE_SEARCH_VIEW")
StateManager.focusedView = "HISCORE_SEARCH_VIEW"
val customSearchField = hiScoreView?.let { HiscoresView.CustomSearchField(it) } val customSearchField = 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.")
} }
hiScoreView?.repaint()
} }
} }
override fun OnPluginsReloaded(): Boolean { override fun OnPluginsReloaded(): Boolean {
if (!initialized) return true if (!initialized) return true
UpdateDisplaySettings() UpdateDisplaySettings()
frame.remove(scrollPane) frame.remove(rightPanelWrapper)
frame.layout = BorderLayout() frame.layout = BorderLayout()
frame.add(scrollPane, BorderLayout.EAST) frame.add(rightPanelWrapper, BorderLayout.EAST)
// Clear or regenerate the reflectiveEditorView
reflectiveEditorView?.removeAll()
reflectiveEditorView?.revalidate()
if(StateManager.focusedView == "REFLECTIVE_EDITOR_VIEW")
reflectiveEditorView?.repaint()
frame.revalidate() frame.revalidate()
frame.repaint() frame.repaint()
pluginsReloaded = true pluginsReloaded = true
reloadInterfaces = true
return true return true
} }
override fun OnXPUpdate(skillId: Int, xp: Int) { override fun OnXPUpdate(skillId: Int, xp: Int) {
if (!initialXP.containsKey(skillId)) { if (!initialXP.containsKey(skillId)) {
initialXP[skillId] = xp initialXP[skillId] = xp
@ -203,11 +206,11 @@ class plugin : Plugin() {
xpWidget = createXPWidget(skillId, previousXp) xpWidget = createXPWidget(skillId, previousXp)
xpWidgets[skillId] = xpWidget xpWidgets[skillId] = xpWidget
xpTrackerView?.add(wrappedWidget(xpWidget.panel)) xpTrackerView?.add(wrappedWidget(xpWidget.container))
xpTrackerView?.add(Box.createVerticalStrut(5)) xpTrackerView?.add(Box.createVerticalStrut(5))
xpTrackerView?.revalidate() xpTrackerView?.revalidate()
if(StateManager.focusedView == "XP_TRACKER_VIEW") if(focusedView == "XP_TRACKER_VIEW")
xpTrackerView?.repaint() xpTrackerView?.repaint()
updateWidget(xpWidget, xp) updateWidget(xpWidget, xp)
@ -221,23 +224,25 @@ class plugin : Plugin() {
} }
if (pluginsReloaded) { if (pluginsReloaded) {
InterfaceList.method3712(true) // Gets the resize working correctly
reflectiveEditorView?.let { addPlugins(it) } reflectiveEditorView?.let { addPlugins(it) }
pluginsReloaded = false pluginsReloaded = false
} }
if (reloadInterfaces){
InterfaceList.method3712(true) // Gets the resize working correctly
reloadInterfaces = false
}
accumulatedTime += timeDelta accumulatedTime += timeDelta
if (accumulatedTime >= tickInterval) { if (accumulatedTime >= tickInterval) {
lootTrackerView?.let { onPostClientTick(it) } lootTrackerView?.let { onPostClientTick(it) }
accumulatedTime = 0L accumulatedTime = 0L
} }
// Init in the draw call so we know we are between glBegin and glEnd for HD // Init in the draw call so we know we are between glBegin and glEnd for HD
if(!initialized && mainLoadState >= loginScreen) { if(!initialized && mainLoadState >= loginScreen) {
initKondoUI() initKondoUI()
} }
} }
private fun initKondoUI(){ private fun initKondoUI(){
@ -245,25 +250,66 @@ class plugin : Plugin() {
if(!allSpritesLoaded()) return; if(!allSpritesLoaded()) return;
val frame: Frame? = GameShell.frame val frame: Frame? = GameShell.frame
if (frame != null) { if (frame != null) {
kondoExposed_useLiveGEPrices = (GetData("kondoUseRemoteGE") as? Boolean) ?: true
kondoExposed_playerXPMultiplier = (GetData("kondoPlayerXPMultiplier") as? Int) ?: 5
cardLayout = CardLayout()
mainContentPanel = Panel(cardLayout)
mainContentPanel.background = VIEW_BACKGROUND_COLOR
xpTrackerView = createXPTrackerView() // Disable Font AA
hiScoreView = createHiscoreSearchView() System.setProperty("awt.useSystemAAFontSettings", "off")
lootTrackerView = createLootTrackerView() System.setProperty("swing.aatext", "false")
reflectiveEditorView = createReflectiveEditorView()
mainContentPanel.add(xpTrackerView, "XP_TRACKER_VIEW") loadFont()
mainContentPanel.add(hiScoreView, "HISCORE_SEARCH_VIEW")
mainContentPanel.add(lootTrackerView, "LOOT_TRACKER_VIEW") try {
mainContentPanel.add(reflectiveEditorView, "REFLECTIVE_EDITOR_VIEW") UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel")
// Modify the UI properties for a dark theme
UIManager.put("control", Color(50, 50, 50)) // Default background for most controls
UIManager.put("info", Color(50, 50, 50))
UIManager.put("nimbusBase", Color(35, 35, 35)) // Base color for Nimbus L&F
UIManager.put("nimbusAlertYellow", Color(255, 220, 35))
UIManager.put("nimbusDisabledText", Color(100, 100, 100))
UIManager.put("nimbusFocus", Color(115, 164, 209))
UIManager.put("nimbusGreen", Color(176, 179, 50))
UIManager.put("nimbusInfoBlue", Color(66, 139, 221))
UIManager.put("nimbusLightBackground", Color(35, 35, 35)) // Background of text fields, etc.
UIManager.put("nimbusOrange", Color(191, 98, 4))
UIManager.put("nimbusRed", Color(169, 46, 34))
UIManager.put("nimbusSelectedText", Color(255, 255, 255))
UIManager.put("nimbusSelectionBackground", Color(75, 110, 175)) // Selection background
UIManager.put("text", Color(230, 230, 230)) // General text color
// Update component tree UI to apply the new theme
SwingUtilities.updateComponentTreeUI(GameShell.frame)
} catch (e : Exception) {
e.printStackTrace()
}
// Restore saved values
useLiveGEPrices = (GetData("kondoUseRemoteGE") as? Boolean) ?: true
playerXPMultiplier = (GetData("kondoPlayerXPMultiplier") as? Int) ?: 5
launchMinimized = (GetData("kondoLaunchMinimized") as? Boolean) ?: false
cardLayout = CardLayout()
mainContentPanel = JPanel(cardLayout).apply {
border = BorderFactory.createEmptyBorder(0, 0, 0, 0) // Removes any default border or padding
background = VIEW_BACKGROUND_COLOR
preferredSize = Dimension(MAIN_CONTENT_WIDTH, frame.height)
isOpaque = true
}
// Register Views
createXPTrackerView()
createHiscoreSearchView()
createLootTrackerView()
createReflectiveEditorView()
mainContentPanel.add(ScrollablePanel(xpTrackerView!!), "XP_TRACKER_VIEW")
mainContentPanel.add(ScrollablePanel(hiScoreView!!), "HISCORE_SEARCH_VIEW")
mainContentPanel.add(ScrollablePanel(lootTrackerView!!), "LOOT_TRACKER_VIEW")
mainContentPanel.add(ScrollablePanel(reflectiveEditorView!!), "REFLECTIVE_EDITOR_VIEW")
val navPanel = Panel().apply { val navPanel = Panel().apply {
layout = BoxLayout(this, BoxLayout.Y_AXIS) layout = BoxLayout(this, BoxLayout.Y_AXIS)
background = WIDGET_COLOR background = WIDGET_COLOR
preferredSize = Dimension(42, frame.height) preferredSize = Dimension(NAVBAR_WIDTH, frame.height)
} }
navPanel.add(createNavButton(LVL_ICON, "XP_TRACKER_VIEW")) navPanel.add(createNavButton(LVL_ICON, "XP_TRACKER_VIEW"))
@ -271,13 +317,13 @@ class plugin : Plugin() {
navPanel.add(createNavButton(LOOT_ICON, "LOOT_TRACKER_VIEW")) navPanel.add(createNavButton(LOOT_ICON, "LOOT_TRACKER_VIEW"))
navPanel.add(createNavButton(WRENCH_ICON, "REFLECTIVE_EDITOR_VIEW")) navPanel.add(createNavButton(WRENCH_ICON, "REFLECTIVE_EDITOR_VIEW"))
val rightPanel = Panel(BorderLayout()).apply { var rightPanel = Panel(BorderLayout()).apply {
add(mainContentPanel, BorderLayout.CENTER) add(mainContentPanel, BorderLayout.CENTER)
add(navPanel, BorderLayout.EAST) add(navPanel, BorderLayout.EAST)
} }
scrollPane = JScrollPane(rightPanel).apply { rightPanelWrapper = JScrollPane(rightPanel).apply {
preferredSize = Dimension(SCROLLPANE_WIDTH, frame.height) preferredSize = Dimension(NAVBAR_WIDTH + MAIN_CONTENT_WIDTH, frame.height)
background = VIEW_BACKGROUND_COLOR background = VIEW_BACKGROUND_COLOR
border = BorderFactory.createEmptyBorder() // Removes the border completely border = BorderFactory.createEmptyBorder() // Removes the border completely
horizontalScrollBarPolicy = JScrollPane.HORIZONTAL_SCROLLBAR_NEVER horizontalScrollBarPolicy = JScrollPane.HORIZONTAL_SCROLLBAR_NEVER
@ -285,91 +331,173 @@ class plugin : Plugin() {
} }
frame.layout = BorderLayout() frame.layout = BorderLayout()
scrollPane?.let { frame.add(it, BorderLayout.EAST) } rightPanelWrapper?.let { frame.add(it, BorderLayout.EAST) }
frame.revalidate() if(!launchMinimized){
frame.repaint() setActiveView("XP_TRACKER_VIEW")
} else {
StateManager.focusedView = "XP_TRACKER_VIEW" setActiveView("HIDDEN")
}
initialized = true initialized = true
pluginsReloaded = true pluginsReloaded = true
UpdateDisplaySettings()
} }
} }
override fun Update() { override fun Update() {
xpWidgets.values.forEach { xpWidget ->
val widgets = xpWidgets.values
val totalXP = totalXPWidget
widgets.forEach { xpWidget ->
val elapsedTime = (System.currentTimeMillis() - xpWidget.startTime) / 1000.0 / 60.0 / 60.0 val elapsedTime = (System.currentTimeMillis() - xpWidget.startTime) / 1000.0 / 60.0 / 60.0
val xpPerHour = if (elapsedTime > 0) (xpWidget.totalXpGained / elapsedTime).toInt() else 0 val xpPerHour = if (elapsedTime > 0) (xpWidget.totalXpGained / elapsedTime).toInt() else 0
val formattedXpPerHour = formatNumber(xpPerHour) val formattedXpPerHour = formatNumber(xpPerHour)
xpWidget.xpPerHourLabel.text = xpWidget.xpPerHourLabel.text =
formatHtmlLabelText("XP /hr: ", primaryColor, formattedXpPerHour, secondaryColor) formatHtmlLabelText("XP /hr: ", primaryColor, formattedXpPerHour, secondaryColor)
xpWidget.panel.repaint() xpWidget.container.repaint()
} }
totalXPWidget?.let { totalXPWidget -> totalXP?.let { totalXPWidget ->
val elapsedTime = (System.currentTimeMillis() - totalXPWidget.startTime) / 1000.0 / 60.0 / 60.0 val elapsedTime = (System.currentTimeMillis() - totalXPWidget.startTime) / 1000.0 / 60.0 / 60.0
val totalXPPerHour = if (elapsedTime > 0) (totalXPWidget.totalXpGained / elapsedTime).toInt() else 0 val totalXPPerHour = if (elapsedTime > 0) (totalXPWidget.totalXpGained / elapsedTime).toInt() else 0
val formattedTotalXpPerHour = formatNumber(totalXPPerHour) val formattedTotalXpPerHour = formatNumber(totalXPPerHour)
totalXPWidget.xpPerHourLabel.text = totalXPWidget.xpPerHourLabel.text =
formatHtmlLabelText("XP /hr: ", primaryColor, formattedTotalXpPerHour, secondaryColor) formatHtmlLabelText("XP /hr: ", primaryColor, formattedTotalXpPerHour, secondaryColor)
totalXPWidget.panel.repaint() 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)) val preDeathSnapshot = takeGroundSnapshot(Pair(x,z))
npcDeathSnapshots[npcID] = LootTrackerView.GroundSnapshot(preDeathSnapshot, Pair(x, z), 0) npcDeathSnapshots[npcID] = LootTrackerView.GroundSnapshot(preDeathSnapshot, Pair(x, z), 0)
} }
private fun setActiveView(viewName: String) {
private fun createNavButton(spriteId: Int, viewName: String): JButton { // Handle the visibility of the main content panel
val bufferedImageSprite = getBufferedImageFromSprite(GetSprite(spriteId)) if (viewName == "HIDDEN") {
val buttonSize = Dimension(42, 42) mainContentPanel.isVisible = false
val imageSize = Dimension(bufferedImageSprite.width, bufferedImageSprite.height) } else {
if (!mainContentPanel.isVisible) {
val actionListener = ActionListener { mainContentPanel.isVisible = true
}
cardLayout.show(mainContentPanel, viewName) cardLayout.show(mainContentPanel, viewName)
StateManager.focusedView = viewName
} }
reloadInterfaces = true
UpdateDisplaySettings()
// Revalidate and repaint necessary panels
mainContentPanel.revalidate()
mainContentPanel.repaint()
rightPanelWrapper?.revalidate()
rightPanelWrapper?.repaint()
frame?.revalidate()
frame?.repaint()
focusedView = viewName
}
private fun createNavButton(spriteId: Int, viewName: String): JPanel {
val bufferedImageSprite = getBufferedImageFromSprite(GetSprite(spriteId))
val buttonSize = Dimension(NAVBAR_WIDTH, 32)
val imageSize = Dimension((bufferedImageSprite.width / 1.2f).toInt(), (bufferedImageSprite.height / 1.2f).toInt())
val cooldownDuration = 100L
val actionListener = ActionListener {
val currentTime = System.currentTimeMillis()
if (currentTime - lastClickTime < cooldownDuration) {
return@ActionListener
}
lastClickTime = currentTime
if (focusedView == viewName) {
setActiveView("HIDDEN")
} else {
setActiveView(viewName)
}
}
// ImageCanvas with forced size
val imageCanvas = ImageCanvas(bufferedImageSprite).apply { val imageCanvas = ImageCanvas(bufferedImageSprite).apply {
background = WIDGET_COLOR background = WIDGET_COLOR
preferredSize = imageSize preferredSize = imageSize
maximumSize = imageSize maximumSize = imageSize
minimumSize = imageSize minimumSize = imageSize
addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent?) {
actionListener.actionPerformed(null)
}
})
} }
val button = JButton().apply { // Wrapping the ImageCanvas in another JPanel to prevent stretching
val imageCanvasWrapper = JPanel().apply {
layout = GridBagLayout() // Keeps the layout of the wrapped panel minimal
preferredSize = imageSize
maximumSize = imageSize
minimumSize = imageSize
isOpaque = false // No background for the wrapper
add(imageCanvas) // Adding ImageCanvas directly, layout won't stretch it
}
val panelButton = JPanel().apply {
layout = GridBagLayout() layout = GridBagLayout()
preferredSize = buttonSize preferredSize = buttonSize
maximumSize = buttonSize maximumSize = buttonSize
minimumSize = buttonSize minimumSize = buttonSize
background = WIDGET_COLOR background = WIDGET_COLOR
isFocusPainted = false isOpaque = true // Ensure background is painted
isBorderPainted = false
val gbc = GridBagConstraints().apply { val gbc = GridBagConstraints().apply {
anchor = GridBagConstraints.CENTER anchor = GridBagConstraints.CENTER
fill = GridBagConstraints.NONE // Prevents stretching
} }
add(imageCanvas, gbc) add(imageCanvasWrapper, gbc)
addActionListener(actionListener)
// Hover and click behavior
val hoverListener = object : MouseAdapter() {
override fun mouseEntered(e: MouseEvent?) {
background = WIDGET_COLOR.darker()
imageCanvas.fillColor = WIDGET_COLOR.darker()
imageCanvas.repaint()
repaint()
}
override fun mouseExited(e: MouseEvent?) {
background = WIDGET_COLOR
imageCanvas.fillColor = WIDGET_COLOR
imageCanvas.repaint()
repaint()
}
override fun mouseClicked(e: MouseEvent?) {
actionListener.actionPerformed(null)
}
}
addMouseListener(hoverListener)
imageCanvas.addMouseListener(hoverListener)
} }
return button return panelButton
}
fun loadFont(): Font? {
val fontStream = plugin::class.java.getResourceAsStream("res/runescape_small.ttf")
return if (fontStream != null) {
try {
val font = Font.createFont(Font.TRUETYPE_FONT, fontStream)
val ge = GraphicsEnvironment.getLocalGraphicsEnvironment()
ge.registerFont(font) // Register the font in the graphics environment
font
} catch (e: Exception) {
e.printStackTrace()
null
}
} else {
println("Font not found!")
null
}
} }
object StateManager { object StateManager {
val initialXP: MutableMap<Int, Int> = HashMap()
val xpWidgets: MutableMap<Int, XPWidget> = HashMap()
var totalXPWidget: XPWidget? = null
var focusedView: String = "" var focusedView: String = ""
} }
} }

View file

@ -1,3 +1,3 @@
AUTHOR='downthecrop' AUTHOR='downthecrop'
DESCRIPTION='A plugin that adds a right-side panel with custom widgets and navigation.' DESCRIPTION='A plugin that adds a right-side panel with custom widgets and navigation.'
VERSION=1.1 VERSION=2.0