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

View file

@ -1,5 +1,6 @@
package KondoKit
import KondoKit.plugin.Companion.WIDGET_COLOR
import java.awt.Canvas
import java.awt.Color
import java.awt.Dimension
@ -8,31 +9,25 @@ import java.awt.image.BufferedImage
class ImageCanvas(private val image: BufferedImage) : Canvas() {
var fillColor: Color = WIDGET_COLOR
init {
// Manually set the alpha value to 255 (fully opaque) only for pixels that are not fully transparent
val width = image.width
val height = image.height
for (y in 0 until height) {
for (x in 0 until width) {
// Retrieve the current pixel color
val color = image.getRGB(x, y)
// Check if the pixel is not fully transparent (i.e., color is not 0)
if (color != 0) {
// Ensure the alpha is set to 255 (fully opaque)
val newColor = (color and 0x00FFFFFF) or (0xFF shl 24)
// Set the pixel with the updated color
image.setRGB(x, y, newColor)
}
}
}
}
override fun paint(g: Graphics) {
super.paint(g)
g.color = Color(27, 27, 27)
g.color = fillColor
g.fillRect(0, 0, width, height)
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
import KondoKit.Helpers.addMouseListenerToAll
import KondoKit.Helpers.formatHtmlLabelText
import KondoKit.SpriteToBufferedImage.getBufferedImageFromSprite
import KondoKit.XPTrackerView.wrappedWidget
@ -9,11 +10,11 @@ import KondoKit.plugin.Companion.WIDGET_COLOR
import KondoKit.plugin.Companion.primaryColor
import KondoKit.plugin.Companion.secondaryColor
import plugin.api.API
import rt4.NpcTypeList
import rt4.ObjStackNode
import rt4.Player
import rt4.SceneGraph
import rt4.*
import java.awt.*
import java.awt.Font
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.awt.image.BufferedImage
import java.io.BufferedReader
import java.io.InputStreamReader
@ -22,6 +23,7 @@ import java.net.URL
import java.nio.charset.StandardCharsets
import java.text.DecimalFormat
import javax.swing.*
import kotlin.math.ceil
object LootTrackerView {
private const val SNAPSHOT_LIFESPAN = 10
@ -31,10 +33,12 @@ object LootTrackerView {
private val lootItemPanels = mutableMapOf<String, MutableMap<Int, Int>>()
private val npcKillCounts = mutableMapOf<String, Int>()
private var totalTrackerWidget: XPWidget? = null
var lastConfirmedKillNpcId = -1;
var lastConfirmedKillNpcId = -1
var customToolTipWindow: JWindow? = null
var lootTrackerView: JPanel? = null
fun loadGEPrices(): Map<String, String> {
return if (plugin.kondoExposed_useLiveGEPrices) {
return if (plugin.useLiveGEPrices) {
try {
println("LootTracker: Loading Remote GE Prices")
val url = URL("https://cdn.2009scape.org/gedata/latest.json")
@ -69,7 +73,7 @@ object LootTrackerView {
} else {
try {
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 ->
val json = lines.joinToString("\n")
val items = json.trim().removeSurrounding("[", "]").split("},").map { it.trim() + "}" }
@ -94,17 +98,34 @@ object LootTrackerView {
fun createLootTrackerView(): JPanel {
return JPanel().apply {
layout = FlowLayout(FlowLayout.CENTER, 0, 5)
fun createLootTrackerView() {
lootTrackerView = JPanel().apply {
layout = BoxLayout(this, BoxLayout.Y_AXIS) // Use BoxLayout on Y axis to stack widgets vertically
background = VIEW_BACKGROUND_COLOR
preferredSize = Dimension(270, 700)
maximumSize = Dimension(270, 700)
minimumSize = Dimension(270, 700)
add(Box.createVerticalStrut(5))
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()
repaint()
}
@ -122,7 +143,7 @@ object LootTrackerView {
totalTrackerWidget?.let {
it.previousXp += newVal
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))
return XPWidget(
skillId = -1,
panel = createWidgetPanel(bufferedImageSprite,l2,l1),
container = createWidgetPanel(bufferedImageSprite,l2,l1),
xpGainedLabel = l2,
xpLeftLabel = JLabel(),
actionsRemainingLabel = JLabel(),
@ -173,7 +194,7 @@ object LootTrackerView {
private fun createLabel(text: String): JLabel {
return JLabel(text).apply {
font = Font("Arial", Font.PLAIN, 11)
font = Font("RuneScape Small", Font.TRUETYPE_FONT, 16)
horizontalAlignment = JLabel.LEFT
}
}
@ -189,9 +210,15 @@ object LootTrackerView {
// Recalculate lootPanel size based on the number of unique items.
val totalItems = lootItemPanels[npcName]?.size ?: 0
val rowsNeeded = Math.ceil(totalItems / 6.0).toInt()
val lootPanelHeight = rowsNeeded * 36 + (rowsNeeded - 1)
lootPanel.preferredSize = Dimension(270, lootPanelHeight+10)
val rowsNeeded = ceil(totalItems / 6.0).toInt()
val lootPanelHeight = rowsNeeded * (40)
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.repaint()
@ -205,21 +232,86 @@ object LootTrackerView {
private fun createItemPanel(itemId: Int, quantity: Int): JPanel {
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)
background = WIDGET_COLOR
minimumSize = preferredSize
maximumSize = preferredSize
add(ImageCanvas(bufferedImageSprite).apply {
val imageCanvas = ImageCanvas(bufferedImageSprite).apply {
preferredSize = Dimension(36, 32)
background = WIDGET_COLOR
minimumSize = 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)
// 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) {
panel.removeAll()
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) {
findLootItemsPanel(lootTrackerView, npcName)?.let {
} ?: run {
// Panel doesn't exist, so create and add it
lootTrackerView.add(createLootFrame(npcName))
lootTrackerView.add(Helpers.Spacer(height = 15))
lootTrackerView.add(Box.createVerticalStrut(10))
lootTrackerView.revalidate()
lootTrackerView.repaint()
}
@ -330,14 +421,15 @@ object LootTrackerView {
val childFramePanel = JPanel().apply {
layout = BoxLayout(this, BoxLayout.Y_AXIS)
background = WIDGET_COLOR
minimumSize = Dimension(270, 0)
maximumSize = Dimension(270, 700)
minimumSize = Dimension(230, 0)
maximumSize = Dimension(230, 700)
name = "HELLO_WORLD"
}
val labelPanel = JPanel(BorderLayout()).apply {
background = Color(21, 21, 21)
border = BorderFactory.createEmptyBorder(5, 5, 5, 5)
maximumSize = Dimension(270, 24)
maximumSize = Dimension(230, 24)
minimumSize = maximumSize
preferredSize = maximumSize
}
@ -345,14 +437,14 @@ object LootTrackerView {
val killCount = npcKillCounts.getOrPut(npcName) { 0 }
val countLabel = JLabel(formatHtmlLabelText(npcName, secondaryColor, " x $killCount", primaryColor)).apply {
foreground = Color(200, 200, 200)
font = Font("Arial", Font.PLAIN, 12)
font = Font("RuneScape Small", Font.TRUETYPE_FONT, 16)
horizontalAlignment = JLabel.LEFT
name = "killCountLabel_$npcName"
}
val valueLabel = JLabel("0 gp").apply {
foreground = Color(200, 200, 200)
font = Font("Arial", Font.PLAIN, 12)
font = Font("RuneScape Small", Font.TRUETYPE_FONT, 16)
horizontalAlignment = JLabel.RIGHT
name = "valueLabel_$npcName"
}
@ -370,19 +462,115 @@ object LootTrackerView {
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(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
}
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() {
override fun getPreferredSize(): Dimension {

View file

@ -2,18 +2,19 @@ package KondoKit
import java.awt.Canvas
import java.awt.Color
import java.awt.Dimension
import java.awt.Font
import java.awt.Graphics
class ProgressBar(
private var progress: Double,
private val barColor: Color,
private var currentLevel: Int = 0,
private var nextLevel: Int = 1
private var progress: Double,
private val barColor: Color,
private var currentLevel: Int = 0,
private var nextLevel: Int = 1
) : Canvas() {
init {
font = Font("Arial", Font.PLAIN, 12)
font = Font("RuneScape Small", Font.TRUETYPE_FONT, 16)
}
override fun paint(g: Graphics) {
@ -25,22 +26,32 @@ class ProgressBar(
g.fillRect(0, 0, width, this.height)
// 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)
// Variables for text position
val textY = this.height / 2 + 6
// Draw the current level on the far left
g.color = Color(255, 255, 255)
g.drawString("Lvl. $currentLevel", 5, this.height / 2 + 4)
drawTextWithShadow(g, "Lvl. $currentLevel", 5, textY, Color(255, 255, 255))
// Draw the percentage in the middle
val percentageText = String.format("%.2f%%", progress)
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
val nextLevelText = "Lvl. $nextLevel"
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) {
@ -50,4 +61,15 @@ class ProgressBar(
if(isVisible)
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
import KondoKit.KondoKitUtils.convertValue
import KondoKit.Helpers.convertValue
import KondoKit.Helpers.showToast
import KondoKit.plugin.Companion.VIEW_BACKGROUND_COLOR
import KondoKit.plugin.Companion.WIDGET_COLOR
import KondoKit.plugin.Companion.primaryColor
import KondoKit.plugin.Companion.secondaryColor
import plugin.Plugin
import plugin.PluginInfo
import plugin.PluginRepository
import java.awt.*
import java.lang.reflect.Field
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.util.*
import java.util.Timer
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 {
fun createReflectiveEditorView(): JPanel {
var reflectiveEditorView: JPanel? = null
fun createReflectiveEditorView() {
val reflectiveEditorPanel = JPanel(BorderLayout())
reflectiveEditorPanel.background = VIEW_BACKGROUND_COLOR
reflectiveEditorPanel.add(Box.createVerticalStrut(5))
reflectiveEditorPanel.border = BorderFactory.createEmptyBorder(10, 10, 10, 10)
return reflectiveEditorPanel
reflectiveEditorView = reflectiveEditorPanel
addPlugins(reflectiveEditorView!!)
}
fun addPlugins(reflectiveEditorView: JPanel) {
reflectiveEditorView.removeAll() // clear previous
try {
val loadedPluginsField = PluginRepository::class.java.getDeclaredField("loadedPlugins")
loadedPluginsField.isAccessible = true
val loadedPlugins = loadedPluginsField.get(null) as HashMap<*, *>
for ((_, plugin) in loadedPlugins) {
addPluginToEditor(reflectiveEditorView, plugin as Plugin)
for ((pluginInfo, plugin) in loadedPlugins) {
addPluginToEditor(reflectiveEditorView, pluginInfo as PluginInfo, plugin as Plugin)
}
} catch (e: Exception) {
e.printStackTrace()
@ -38,112 +53,214 @@ object ReflectiveEditorView {
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)
val fieldNotifier = KondoKitUtils.FieldNotifier(plugin)
val exposedFields = KondoKitUtils.getKondoExposedFields(plugin)
if (exposedFields.isNotEmpty()) {
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)
val fieldNotifier = Helpers.FieldNotifier(plugin)
val exposedFields = plugin.javaClass.declaredFields.filter { field ->
field.annotations.any { annotation ->
annotation.annotationClass.simpleName == "Exposed"
}
}
for (field in exposedFields) {
field.isAccessible = true
if (exposedFields.isNotEmpty()) {
val fieldPanel = JPanel()
fieldPanel.layout = GridBagLayout()
fieldPanel.background = WIDGET_COLOR // Match the background for minimal borders
fieldPanel.foreground = secondaryColor
fieldPanel.border = BorderFactory.createEmptyBorder(5, 0, 5, 0) // No visible border, just spacing
fieldPanel.maximumSize = Dimension(Int.MAX_VALUE, 40)
val packageName = plugin.javaClass.`package`.name
val version = pluginInfo.version
val labelPanel = JPanel(BorderLayout())
labelPanel.maximumSize = Dimension(Int.MAX_VALUE, 30)
labelPanel.background = VIEW_BACKGROUND_COLOR
labelPanel.border = BorderFactory.createEmptyBorder(5, 0, 0, 0)
val gbc = GridBagConstraints()
gbc.insets = Insets(0, 5, 0, 5) // Less padding, more minimal spacing
val label = JLabel("$packageName v$version", SwingConstants.CENTER)
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())
label.foreground = secondaryColor
gbc.gridx = 0
gbc.gridy = 0
gbc.weightx = 0.0
gbc.anchor = GridBagConstraints.WEST
fieldPanel.add(label, gbc)
for (field in exposedFields) {
field.isAccessible = true
val textField = JTextField(field.get(plugin)?.toString() ?: "")
textField.background = VIEW_BACKGROUND_COLOR
textField.foreground = secondaryColor
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
)
// Get the "Exposed" annotation specifically and retrieve its description, if available
val exposedAnnotation = field.annotations.firstOrNull { annotation ->
annotation.annotationClass.simpleName == "Exposed"
}
}
fieldPanel.add(applyButton, gbc)
reflectiveEditorView.add(fieldPanel)
val description = exposedAnnotation?.let { annotation ->
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 timer = Timer()
timer.schedule(object : TimerTask() {
override fun run() {
val currentValue = field.get(plugin)?.toString()
if (currentValue != previousValue) {
previousValue = currentValue
SwingUtilities.invokeLater {
fieldNotifier.notifyFieldChange(field, currentValue)
val fieldPanel = JPanel()
fieldPanel.layout = GridBagLayout()
fieldPanel.background = WIDGET_COLOR
fieldPanel.foreground = secondaryColor
fieldPanel.border = BorderFactory.createEmptyBorder(5, 0, 5, 0)
fieldPanel.maximumSize = Dimension(Int.MAX_VALUE, 40)
val gbc = GridBagConstraints()
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)
}, 0, 1000) // Poll every 1000 milliseconds (1 second)
}
fieldNotifier.addObserver(object : KondoKitUtils.FieldObserver {
override fun onFieldChange(field: Field, newValue: Any?) {
if (field.name.removePrefix(KondoKitUtils.KONDO_PREFIX).equals(label.text, ignoreCase = true)) {
textField.text = newValue?.toString() ?: ""
textField.revalidate()
textField.repaint()
}
if (exposedFields.isNotEmpty()) {
reflectiveEditorView.add(Box.createVerticalStrut(5))
}
}
reflectiveEditorView.revalidate()
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)
}
})
}
if (exposedFields.isNotEmpty()) {
reflectiveEditorView.add(Box.createVerticalStrut(10))
pack()
}
} else {
// 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.getProgressBarColor
import KondoKit.Helpers.getSpriteId
import KondoKit.Helpers.addMouseListenerToAll
import KondoKit.SpriteToBufferedImage.getBufferedImageFromSprite
import KondoKit.plugin.Companion.IMAGE_SIZE
import KondoKit.plugin.Companion.LVL_ICON
import KondoKit.plugin.Companion.TOTAL_XP_WIDGET_SIZE
import KondoKit.plugin.Companion.VIEW_BACKGROUND_COLOR
import KondoKit.plugin.Companion.WIDGET_COLOR
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.secondaryColor
import KondoKit.plugin.StateManager.totalXPWidget
import plugin.api.API
import java.awt.*
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.io.BufferedReader
import java.io.InputStreamReader
import java.nio.charset.StandardCharsets
import javax.swing.Box
import javax.swing.BoxLayout
import javax.swing.JLabel
import javax.swing.JPanel
import javax.swing.*
object XPTrackerView {
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 {
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 ->
val json = lines.joinToString("\n")
val pairs = json.trim().removeSurrounding("{", "}").split(",")
@ -74,8 +78,8 @@ object XPTrackerView {
if(LootTrackerView.lastConfirmedKillNpcId != -1 && npcHitpointsMap.isNotEmpty()) {
val npcHP = npcHitpointsMap[LootTrackerView.lastConfirmedKillNpcId]
val xpPerKill = when (xpWidget.skillId) {
3 -> kondoExposed_playerXPMultiplier * (npcHP ?: 1) // Hitpoints
else -> kondoExposed_playerXPMultiplier * (npcHP ?: 1) * 4 // Combat XP for other skills
3 -> playerXPMultiplier * (npcHP ?: 1) // Hitpoints
else -> playerXPMultiplier * (npcHP ?: 1) * 4 // Combat XP for other skills
}
val remainingKills = xpLeft / xpPerKill
xpWidget.actionsRemainingLabel.text = formatHtmlLabelText("Kills: ", primaryColor, remainingKills.toString(), secondaryColor)
@ -96,20 +100,57 @@ object XPTrackerView {
xpWidget.previousXp = xp
if (plugin.StateManager.focusedView == "XP_TRACKER_VIEW")
xpWidget.panel.repaint()
xpWidget.container.repaint()
}
private fun updateTotalXPWidget(xpGainedSinceLastUpdate: Int) {
val totalXPWidget = plugin.StateManager.totalXPWidget ?: return
val totalXPWidget = totalXPWidget ?: return
totalXPWidget.totalXpGained += xpGainedSinceLastUpdate
val formattedXp = formatNumber(totalXPWidget.totalXpGained)
totalXPWidget.xpGainedLabel.text = formatHtmlLabelText("Gained: ", primaryColor, formattedXp, secondaryColor)
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 {
val widgetPanel = Panel().apply {
@ -120,11 +161,9 @@ object XPTrackerView {
minimumSize = TOTAL_XP_WIDGET_SIZE
}
val bufferedImageSprite = getBufferedImageFromSprite(API.GetSprite(898))
val bufferedImageSprite = getBufferedImageFromSprite(API.GetSprite(LVL_ICON))
val imageContainer = Panel(FlowLayout()).apply {
background = WIDGET_COLOR
preferredSize = IMAGE_SIZE
maximumSize = IMAGE_SIZE
minimumSize = IMAGE_SIZE
@ -133,15 +172,14 @@ object XPTrackerView {
bufferedImageSprite.let { image ->
val imageCanvas = ImageCanvas(image).apply {
background = WIDGET_COLOR
preferredSize = Dimension(image.width, image.height)
maximumSize = Dimension(image.width, image.height)
minimumSize = Dimension(image.width, image.height)
size = Dimension(image.width, image.height)
preferredSize = Dimension(bufferedImageSprite.width, bufferedImageSprite.height)
maximumSize = preferredSize
minimumSize = preferredSize
size = preferredSize
}
imageContainer.add(imageCanvas)
imageContainer.size = Dimension(image.width, image.height)
imageContainer.size = Dimension(bufferedImageSprite.width, bufferedImageSprite.height)
imageContainer.revalidate()
if(plugin.StateManager.focusedView == "XP_TRACKER_VIEW")
@ -149,14 +187,13 @@ object XPTrackerView {
}
val textPanel = Panel().apply {
layout = GridLayout(2, 1, 5, 5)
background = WIDGET_COLOR
layout = GridLayout(2, 1, 5, 0)
}
val font = Font("Arial", Font.PLAIN, 11)
val font = Font("RuneScape Small", Font.TRUETYPE_FONT, 16)
val xpGainedLabel = JLabel(
formatHtmlLabelText("XP Gained: ", primaryColor, "0", secondaryColor)
formatHtmlLabelText("Gained: ", primaryColor, "0", secondaryColor)
).apply {
this.horizontalAlignment = JLabel.LEFT
this.font = font
@ -177,7 +214,7 @@ object XPTrackerView {
return XPWidget(
skillId = -1,
panel = widgetPanel,
container = widgetPanel,
xpGainedLabel = xpGainedLabel,
xpLeftLabel = JLabel(formatHtmlLabelText("XP Left: ", primaryColor, "0", secondaryColor)).apply {
this.horizontalAlignment = JLabel.LEFT
@ -193,19 +230,83 @@ object XPTrackerView {
}
fun createXPTrackerView(): JPanel? {
val widgetViewPanel = JPanel()
widgetViewPanel.layout = BoxLayout(widgetViewPanel, BoxLayout.Y_AXIS)
widgetViewPanel.background = VIEW_BACKGROUND_COLOR
widgetViewPanel.add(Box.createVerticalStrut(5))
fun createXPTrackerView(){
val widgetViewPanel = JPanel().apply {
layout = BoxLayout(this, BoxLayout.Y_AXIS)
background = VIEW_BACKGROUND_COLOR
}
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()
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))
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 {
val widgetPanel = Panel().apply {
layout = BorderLayout(5, 5)
@ -242,14 +343,14 @@ object XPTrackerView {
}
val textPanel = Panel().apply {
layout = GridLayout(2, 2, 5, 5)
layout = GridLayout(2, 2, 5, 0)
background = WIDGET_COLOR
}
val font = Font("Arial", Font.PLAIN, 11)
val font = Font("RuneScape Small", Font.TRUETYPE_FONT, 16)
val xpGainedLabel = JLabel(
formatHtmlLabelText("XP Gained: ", primaryColor, "0", secondaryColor)
formatHtmlLabelText("XP Gained: ", primaryColor, "0", secondaryColor)
).apply {
this.horizontalAlignment = JLabel.LEFT
this.font = font
@ -302,7 +403,7 @@ object XPTrackerView {
return XPWidget(
skillId = skillId,
panel = widgetPanel,
container = widgetPanel,
xpGainedLabel = xpGainedLabel,
xpLeftLabel = xpLeftLabel,
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(
component.preferredSize.width + 2 * padding,
component.preferredSize.height + 2 * padding
component.preferredSize.width + 2 * padding,
component.preferredSize.height + 2 * padding
)
val outerPanel = Panel(GridBagLayout()).apply {
val outerPanel = JPanel(GridBagLayout()).apply {
background = WIDGET_COLOR
preferredSize = outerPanelSize
maximumSize = outerPanelSize
minimumSize = outerPanelSize
}
val innerPanel = Panel(BorderLayout()).apply {
val innerPanel = JPanel(BorderLayout()).apply {
background = WIDGET_COLOR
preferredSize = component.preferredSize
maximumSize = component.preferredSize
@ -343,14 +444,14 @@ object XPTrackerView {
data class XPWidget(
val panel: Panel,
val skillId: Int,
val xpGainedLabel: JLabel,
val xpLeftLabel: JLabel,
val xpPerHourLabel: JLabel,
val actionsRemainingLabel: JLabel,
val progressBar: ProgressBar,
var totalXpGained: Int = 0,
var startTime: Long = System.currentTimeMillis(),
var previousXp: Int = 0
val container: Container,
val skillId: Int,
val xpGainedLabel: JLabel,
val xpLeftLabel: JLabel,
val xpPerHourLabel: JLabel,
val actionsRemainingLabel: JLabel,
val progressBar: ProgressBar,
var totalXpGained: Int = 0,
var startTime: Long = System.currentTimeMillis(),
var previousXp: Int = 0
)

View file

@ -5,22 +5,27 @@ import KondoKit.Helpers.formatHtmlLabelText
import KondoKit.Helpers.formatNumber
import KondoKit.Helpers.getSpriteId
import KondoKit.HiscoresView.createHiscoreSearchView
import KondoKit.HiscoresView.hiScoreView
import KondoKit.LootTrackerView.BAG_ICON
import KondoKit.LootTrackerView.createLootTrackerView
import KondoKit.LootTrackerView.lootTrackerView
import KondoKit.LootTrackerView.npcDeathSnapshots
import KondoKit.LootTrackerView.onPostClientTick
import KondoKit.LootTrackerView.takeGroundSnapshot
import KondoKit.ReflectiveEditorView.addPlugins
import KondoKit.ReflectiveEditorView.createReflectiveEditorView
import KondoKit.ReflectiveEditorView.reflectiveEditorView
import KondoKit.SpriteToBufferedImage.getBufferedImageFromSprite
import KondoKit.XPTrackerView.createTotalXPWidget
import KondoKit.XPTrackerView.createXPTrackerView
import KondoKit.XPTrackerView.createXPWidget
import KondoKit.XPTrackerView.initialXP
import KondoKit.XPTrackerView.resetXPTracker
import KondoKit.XPTrackerView.totalXPWidget
import KondoKit.XPTrackerView.updateWidget
import KondoKit.XPTrackerView.wrappedWidget
import KondoKit.plugin.StateManager.initialXP
import KondoKit.plugin.StateManager.totalXPWidget
import KondoKit.plugin.StateManager.xpWidgets
import KondoKit.XPTrackerView.xpTrackerView
import KondoKit.XPTrackerView.xpWidgets
import KondoKit.plugin.StateManager.focusedView
import plugin.Plugin
import plugin.api.*
import plugin.api.API.*
@ -38,37 +43,47 @@ import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import javax.swing.*
@Target(AnnotationTarget.FIELD)
@Retention(AnnotationRetention.RUNTIME)
annotation class Exposed(val description: String = "")
class plugin : Plugin() {
companion object {
val WIDGET_SIZE = Dimension(270, 55)
val TOTAL_XP_WIDGET_SIZE = Dimension(270, 30)
val WIDGET_SIZE = Dimension(220, 50)
val TOTAL_XP_WIDGET_SIZE = Dimension(220, 30)
val IMAGE_SIZE = Dimension(20, 20)
val WIDGET_COLOR = Color(27, 27, 27)
val VIEW_BACKGROUND_COLOR = Color(37, 37, 37)
val primaryColor = Color(129, 129, 129) // Color for "XP Gained:"
val secondaryColor = Color(226, 226, 226) // Color for "0"
var kondoExposed_useLiveGEPrices = true
var kondoExposed_playerXPMultiplier = 5
const val FIXED_WIDTH = 782
const val SCROLLPANE_WIDTH = 340
val WIDGET_COLOR = Color(30, 30, 30)
val VIEW_BACKGROUND_COLOR = Color(40, 40, 40)
val primaryColor = Color(165, 165, 165) // Color for "XP Gained:"
val secondaryColor = Color(255, 255, 255) // Color for "0"
@Exposed(description = "Default: true, Use Local JSON or the prices from the Live/Stable server API")
var useLiveGEPrices = true
@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 LVL_ICON = 898
private const val LOOT_ICON = 777
private const val MAG_SPRITE = 1423
const val LVL_ICON = 898
private lateinit var cardLayout: CardLayout
private lateinit var mainContentPanel: Panel
private var scrollPane: JScrollPane? = null
private var hiScoreView: JPanel? = null
private var reflectiveEditorView: JPanel? = null
private var lootTrackerView: JPanel? = null
private var xpTrackerView: JPanel? = null
private lateinit var mainContentPanel: JPanel
private var rightPanelWrapper: JScrollPane? = null
private var accumulatedTime = 0L
private var reloadInterfaces = false
private const val tickInterval = 600L
private var pluginsReloaded = false
private var loginScreen = 160;
private var loginScreen = 160
private var lastLogin = ""
private var initialized = false;
private var lastClickTime = 0L
}
fun allSpritesLoaded() : Boolean {
@ -95,43 +110,36 @@ class plugin : Plugin() {
if (lastLogin != "" && lastLogin != Player.usernameInput.toString()) {
// if we logged in with a new character
// we need to reset the trackers
xpTrackerView?.removeAll()
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()
xpTrackerView?.let { resetXPTracker(it) }
}
lastLogin = Player.usernameInput.toString()
}
private fun UpdateDisplaySettings() {
val mode = GetWindowMode()
val currentScrollPaneWidth = if (mainContentPanel.isVisible) NAVBAR_WIDTH + MAIN_CONTENT_WIDTH else NAVBAR_WIDTH
when (mode) {
WindowMode.FIXED -> {
if (frame.width < FIXED_WIDTH + SCROLLPANE_WIDTH) {
frame.setSize(FIXED_WIDTH + SCROLLPANE_WIDTH, frame.height)
if (frame.width < FIXED_WIDTH + currentScrollPaneWidth) {
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
}
WindowMode.RESIZABLE -> {
GameShell.canvasWidth -= SCROLLPANE_WIDTH
GameShell.canvasWidth = frame.width - (currentScrollPaneWidth + 16)
}
}
scrollPane?.revalidate()
scrollPane?.repaint()
rightPanelWrapper?.preferredSize = Dimension(currentScrollPaneWidth, frame.height)
rightPanelWrapper?.revalidate()
rightPanelWrapper?.repaint()
}
fun OnKondoValueUpdated(){
StoreData("kondoUseRemoteGE", kondoExposed_useLiveGEPrices)
StoreData("kondoPlayerXPMultiplier", kondoExposed_playerXPMultiplier)
StoreData("kondoUseRemoteGE", useLiveGEPrices)
StoreData("kondoPlayerXPMultiplier", playerXPMultiplier)
LootTrackerView.gePriceMap = LootTrackerView.loadGEPrices()
StoreData("kondoLaunchMinimized", launchMinimized)
}
override fun OnMiniMenuCreate(currentEntries: Array<out MiniMenuEntry>?) {
@ -139,12 +147,16 @@ class plugin : Plugin() {
for ((index, entry) in currentEntries.withIndex()) {
if (entry.type == MiniMenuType.PLAYER && index == currentEntries.size - 1) {
val input = entry.subject
val username = input
.replace(Regex("<col=[0-9a-fA-F]{6}>"), "")
.replace(Regex("<img=\\d+>"), "")
.split(" ") // Split by spaces
.first() // Take the first part, which is the username
InsertMiniMenuEntry("Lookup", entry.subject, searchHiscore(username.replace(" ","_")))
// Trim spaces, clean up tags, and remove the level info
val cleanedInput = input
.trim() // Remove any leading/trailing spaces
.replace(Regex("<col=[0-9a-fA-F]{6}>"), "") // Remove color tags
.replace(Regex("<img=\\d+>"), "") // Remove image tags
.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 {
return Runnable {
cardLayout.show(mainContentPanel, "HISCORE_SEARCH_VIEW")
StateManager.focusedView = "HISCORE_SEARCH_VIEW"
setActiveView("HISCORE_SEARCH_VIEW")
val customSearchField = hiScoreView?.let { HiscoresView.CustomSearchField(it) }
customSearchField?.searchPlayer(username) ?: run {
println("searchView is null or CustomSearchField creation failed.")
}
hiScoreView?.repaint()
}
}
override fun OnPluginsReloaded(): Boolean {
if (!initialized) return true
UpdateDisplaySettings()
frame.remove(scrollPane)
frame.remove(rightPanelWrapper)
frame.layout = BorderLayout()
frame.add(scrollPane, BorderLayout.EAST)
// Clear or regenerate the reflectiveEditorView
reflectiveEditorView?.removeAll()
reflectiveEditorView?.revalidate()
if(StateManager.focusedView == "REFLECTIVE_EDITOR_VIEW")
reflectiveEditorView?.repaint()
frame.add(rightPanelWrapper, BorderLayout.EAST)
frame.revalidate()
frame.repaint()
pluginsReloaded = true
reloadInterfaces = true
return true
}
override fun OnXPUpdate(skillId: Int, xp: Int) {
if (!initialXP.containsKey(skillId)) {
initialXP[skillId] = xp
@ -203,11 +206,11 @@ class plugin : Plugin() {
xpWidget = createXPWidget(skillId, previousXp)
xpWidgets[skillId] = xpWidget
xpTrackerView?.add(wrappedWidget(xpWidget.panel))
xpTrackerView?.add(wrappedWidget(xpWidget.container))
xpTrackerView?.add(Box.createVerticalStrut(5))
xpTrackerView?.revalidate()
if(StateManager.focusedView == "XP_TRACKER_VIEW")
if(focusedView == "XP_TRACKER_VIEW")
xpTrackerView?.repaint()
updateWidget(xpWidget, xp)
@ -221,23 +224,25 @@ class plugin : Plugin() {
}
if (pluginsReloaded) {
InterfaceList.method3712(true) // Gets the resize working correctly
reflectiveEditorView?.let { addPlugins(it) }
pluginsReloaded = false
}
if (reloadInterfaces){
InterfaceList.method3712(true) // Gets the resize working correctly
reloadInterfaces = false
}
accumulatedTime += timeDelta
if (accumulatedTime >= tickInterval) {
lootTrackerView?.let { onPostClientTick(it) }
accumulatedTime = 0L
}
// Init in the draw call so we know we are between glBegin and glEnd for HD
if(!initialized && mainLoadState >= loginScreen) {
initKondoUI()
}
}
private fun initKondoUI(){
@ -245,25 +250,66 @@ class plugin : Plugin() {
if(!allSpritesLoaded()) return;
val frame: Frame? = GameShell.frame
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()
hiScoreView = createHiscoreSearchView()
lootTrackerView = createLootTrackerView()
reflectiveEditorView = createReflectiveEditorView()
mainContentPanel.add(xpTrackerView, "XP_TRACKER_VIEW")
mainContentPanel.add(hiScoreView, "HISCORE_SEARCH_VIEW")
mainContentPanel.add(lootTrackerView, "LOOT_TRACKER_VIEW")
mainContentPanel.add(reflectiveEditorView, "REFLECTIVE_EDITOR_VIEW")
// Disable Font AA
System.setProperty("awt.useSystemAAFontSettings", "off")
System.setProperty("swing.aatext", "false")
loadFont()
try {
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 {
layout = BoxLayout(this, BoxLayout.Y_AXIS)
background = WIDGET_COLOR
preferredSize = Dimension(42, frame.height)
preferredSize = Dimension(NAVBAR_WIDTH, frame.height)
}
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(WRENCH_ICON, "REFLECTIVE_EDITOR_VIEW"))
val rightPanel = Panel(BorderLayout()).apply {
var rightPanel = Panel(BorderLayout()).apply {
add(mainContentPanel, BorderLayout.CENTER)
add(navPanel, BorderLayout.EAST)
}
scrollPane = JScrollPane(rightPanel).apply {
preferredSize = Dimension(SCROLLPANE_WIDTH, frame.height)
rightPanelWrapper = JScrollPane(rightPanel).apply {
preferredSize = Dimension(NAVBAR_WIDTH + MAIN_CONTENT_WIDTH, frame.height)
background = VIEW_BACKGROUND_COLOR
border = BorderFactory.createEmptyBorder() // Removes the border completely
horizontalScrollBarPolicy = JScrollPane.HORIZONTAL_SCROLLBAR_NEVER
@ -285,91 +331,173 @@ class plugin : Plugin() {
}
frame.layout = BorderLayout()
scrollPane?.let { frame.add(it, BorderLayout.EAST) }
rightPanelWrapper?.let { frame.add(it, BorderLayout.EAST) }
frame.revalidate()
frame.repaint()
StateManager.focusedView = "XP_TRACKER_VIEW"
if(!launchMinimized){
setActiveView("XP_TRACKER_VIEW")
} else {
setActiveView("HIDDEN")
}
initialized = true
pluginsReloaded = true
UpdateDisplaySettings()
}
}
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 xpPerHour = if (elapsedTime > 0) (xpWidget.totalXpGained / elapsedTime).toInt() else 0
val formattedXpPerHour = formatNumber(xpPerHour)
xpWidget.xpPerHourLabel.text =
formatHtmlLabelText("XP /hr: ", primaryColor, formattedXpPerHour, secondaryColor)
xpWidget.panel.repaint()
formatHtmlLabelText("XP /hr: ", primaryColor, formattedXpPerHour, secondaryColor)
xpWidget.container.repaint()
}
totalXPWidget?.let { totalXPWidget ->
totalXP?.let { totalXPWidget ->
val elapsedTime = (System.currentTimeMillis() - totalXPWidget.startTime) / 1000.0 / 60.0 / 60.0
val totalXPPerHour = if (elapsedTime > 0) (totalXPWidget.totalXpGained / elapsedTime).toInt() else 0
val formattedTotalXpPerHour = formatNumber(totalXPPerHour)
totalXPWidget.xpPerHourLabel.text =
formatHtmlLabelText("XP /hr: ", primaryColor, formattedTotalXpPerHour, secondaryColor)
totalXPWidget.panel.repaint()
formatHtmlLabelText("XP /hr: ", primaryColor, formattedTotalXpPerHour, secondaryColor)
totalXPWidget.container.repaint()
}
}
override fun OnKillingBlowNPC(npcID: Int, x: Int, z: Int) {
val preDeathSnapshot = takeGroundSnapshot(Pair(x,z))
npcDeathSnapshots[npcID] = LootTrackerView.GroundSnapshot(preDeathSnapshot, Pair(x, z), 0)
}
private fun createNavButton(spriteId: Int, viewName: String): JButton {
val bufferedImageSprite = getBufferedImageFromSprite(GetSprite(spriteId))
val buttonSize = Dimension(42, 42)
val imageSize = Dimension(bufferedImageSprite.width, bufferedImageSprite.height)
val actionListener = ActionListener {
private fun setActiveView(viewName: String) {
// Handle the visibility of the main content panel
if (viewName == "HIDDEN") {
mainContentPanel.isVisible = false
} else {
if (!mainContentPanel.isVisible) {
mainContentPanel.isVisible = true
}
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 {
background = WIDGET_COLOR
preferredSize = imageSize
maximumSize = 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()
preferredSize = buttonSize
maximumSize = buttonSize
minimumSize = buttonSize
background = WIDGET_COLOR
isFocusPainted = false
isBorderPainted = false
isOpaque = true // Ensure background is painted
val gbc = GridBagConstraints().apply {
anchor = GridBagConstraints.CENTER
fill = GridBagConstraints.NONE // Prevents stretching
}
add(imageCanvas, gbc)
addActionListener(actionListener)
add(imageCanvasWrapper, gbc)
// 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 {
val initialXP: MutableMap<Int, Int> = HashMap()
val xpWidgets: MutableMap<Int, XPWidget> = HashMap()
var totalXPWidget: XPWidget? = null
var focusedView: String = ""
}
}

View file

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