diff --git a/plugin-playground/src/main/kotlin/FooTextPlugin/plugin.kt b/plugin-playground/src/main/kotlin/FooTextPlugin/plugin.kt
index 19c07fa..e1c2bd8 100644
--- a/plugin-playground/src/main/kotlin/FooTextPlugin/plugin.kt
+++ b/plugin-playground/src/main/kotlin/FooTextPlugin/plugin.kt
@@ -1,6 +1,8 @@
package FooTextPlugin
+import KondoKit.Exposed
import plugin.Plugin
+import plugin.api.API
import rt4.Component
import rt4.JagString
import rt4.Player
@@ -32,8 +34,15 @@ class plugin : Plugin() {
3 to "
", // UIM
)
- private var ACCOUNT_TYPE = 0
+ // Local storage for username matches to avoid API calls
+ @Exposed(description = "Username to account type mappings (0=Normal, 1=IM, 2=HCIM, 3=UIM)")
+ private var usernameMatches = HashMap()
+
+ // Not used anymore - keeping for reference
+ // @Exposed(description = "Manual username entries (format: username:accountType, e.g., 'example:1')")
+ // private var manualEntries = listOf()
+ private var ACCOUNT_TYPE = 0
private var component: Component? = null
@@ -234,6 +243,19 @@ class plugin : Plugin() {
'\u00FF' to "-X"
)
+ override fun Init() {
+ // Load username matches from local storage
+ usernameMatches = API.GetData("foo-text-username-matches") as? HashMap ?: HashMap()
+ }
+
+ fun OnKondoValueUpdated() {
+ StoreData()
+ }
+
+ private fun StoreData() {
+ API.StoreData("foo-text-username-matches", usernameMatches)
+ }
+
override fun Draw(timeDelta: Long) {
if (component != null && component?.id == TARGET_COMPONENT_ID) {
val username = Player.usernameInput.toString().toLowerCase()
@@ -267,19 +289,31 @@ class plugin : Plugin() {
val reader = BufferedReader(InputStreamReader(connection.inputStream))
val response = reader.use { it.readText() }
reader.close()
- updatePlayerData(response)
+ updatePlayerData(response, cleanUsername)
}
} catch (_: Exception) { }
}.start()
}
- private fun updatePlayerData(jsonResponse: String) {
+ private fun updatePlayerData(jsonResponse: String, username: String) {
val hiscoresResponse = gson.fromJson(jsonResponse, HiscoresResponse::class.java)
ACCOUNT_TYPE = hiscoresResponse.info.iron_mode.toInt()
println("Resolved you are account type: $ACCOUNT_TYPE")
+
+ // Store the result in our local cache
+ usernameMatches[username] = ACCOUNT_TYPE
+ StoreData()
}
override fun OnLogin() {
+ // Check if we already have the account type for this user
+ val cleanUsername = Player.usernameInput.toString().toLowerCase().replace(" ", "_")
+ if (usernameMatches.containsKey(cleanUsername)) {
+ ACCOUNT_TYPE = usernameMatches[cleanUsername]!!
+ println("Using cached account type: $ACCOUNT_TYPE for $cleanUsername")
+ return
+ }
+
// The server doesn't tell us what account type we are.
// Requesting from API is the easier way to tell.
fetchAccountTypeFromAPI()
diff --git a/plugin-playground/src/main/kotlin/FooTextPlugin/plugin.properties b/plugin-playground/src/main/kotlin/FooTextPlugin/plugin.properties
index 8ef8645..4e30d94 100644
--- a/plugin-playground/src/main/kotlin/FooTextPlugin/plugin.properties
+++ b/plugin-playground/src/main/kotlin/FooTextPlugin/plugin.properties
@@ -1,3 +1,3 @@
AUTHOR=YourName
-DESCRIPTION=Replaces the username part of Component Index: 51 ID: 8978483 with "foo"
+DESCRIPTION=Displays account type icons next to usernames in chat. Uses local storage to cache results and avoid API calls. Supports manual entries for players not on live server.
VERSION=1.0
\ No newline at end of file
diff --git a/plugin-playground/src/main/kotlin/KondoKit/components/SettingsPanel.kt b/plugin-playground/src/main/kotlin/KondoKit/components/SettingsPanel.kt
index 9110948..1cdf9f4 100644
--- a/plugin-playground/src/main/kotlin/KondoKit/components/SettingsPanel.kt
+++ b/plugin-playground/src/main/kotlin/KondoKit/components/SettingsPanel.kt
@@ -12,6 +12,7 @@ import java.awt.event.MouseEvent
import java.util.*
import java.util.Timer
import javax.swing.*
+import javax.swing.table.DefaultTableModel
class SettingsPanel(private val plugin: Plugin) : JPanel() {
@@ -70,103 +71,131 @@ class SettingsPanel(private val plugin: Plugin) : JPanel() {
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 {
+ val inputComponent: JComponent
+ val isHashMapEditor: Boolean
+ when {
+ field.type == Boolean::class.javaPrimitiveType || field.type == java.lang.Boolean::class.java -> {
+ inputComponent = JCheckBox().apply {
isSelected = field.get(plugin) as Boolean
}
+ isHashMapEditor = false
+ }
- field.type.isEnum ->
- JComboBox((field.type.enumConstants as Array>)).apply {
+ field.type.isEnum -> {
+ val enumConstants = field.type.enumConstants
+ inputComponent = JComboBox(enumConstants as Array>).apply {
selectedItem = field.get(plugin)
}
+ isHashMapEditor = false
+ }
- 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 == Int::class.javaPrimitiveType || field.type == Integer::class.java -> {
+ inputComponent = JSpinner(SpinnerNumberModel(field.get(plugin) as Int, Int.MIN_VALUE, Int.MAX_VALUE, 1))
+ isHashMapEditor = false
+ }
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))
+ field.type == java.lang.Double::class.java -> {
+ inputComponent = JSpinner(SpinnerNumberModel((field.get(plugin) as Number).toDouble(), -Double.MAX_VALUE, Double.MAX_VALUE, 0.1))
+ isHashMapEditor = false
+ }
- 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, 8)
- }
- 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 -> Helpers.convertValue(field.type, field.genericType, inputComponent.text)
- else -> throw IllegalArgumentException("Unsupported input component type")
- }
- fieldNotifier.setFieldValue(field, newValue)
- Helpers.showToast(
- this@SettingsPanel,
- "${field.name} updated successfully!"
- )
- } catch (e: Exception) {
- Helpers.showToast(
- this@SettingsPanel,
- "Failed to update ${field.name}: ${e.message}",
- JOptionPane.ERROR_MESSAGE
- )
+ // Check if the field is a HashMap
+ field.type == HashMap::class.java || field.type.simpleName == "HashMap" -> {
+ inputComponent = createHashMapEditor(field, plugin, fieldNotifier)
+ isHashMapEditor = true
+ }
+
+ else -> {
+ inputComponent = JTextField(field.get(plugin)?.toString() ?: "")
+ isHashMapEditor = false
}
}
- fieldPanel.add(applyButton, gbc)
- add(fieldPanel)
+ // Handle HashMap editors differently - they don't use the fieldPanel layout
+ if (isHashMapEditor) {
+ // Add the HashMap editor directly to the settings panel
+ add(inputComponent)
+ } else {
+ // 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)
+
+ // Add apply button for non-HashMap editors
+ val applyButton = JButton("\u2714").apply {
+ maximumSize = Dimension(Int.MAX_VALUE, 8)
+ }
+ 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 -> Helpers.convertValue(field.type, field.genericType, inputComponent.text)
+ else -> throw IllegalArgumentException("Unsupported input component type")
+ }
+ fieldNotifier.setFieldValue(field, newValue)
+ Helpers.showToast(
+ this@SettingsPanel,
+ "${field.name} updated successfully!"
+ )
+ } catch (e: Exception) {
+ Helpers.showToast(
+ this@SettingsPanel,
+ "Failed to update ${field.name}: ${e.message}",
+ JOptionPane.ERROR_MESSAGE
+ )
+ }
+ }
+
+ fieldPanel.add(applyButton, gbc)
+ 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() ?: ""
+ // Track field changes in real-time and update UI (skip HashMap fields)
+ if (field.type != HashMap::class.java && field.type.simpleName != "HashMap") {
+ 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) // Poll every 1000 milliseconds (1 second)
+ }
}
if (exposedFields.isNotEmpty()) {
@@ -175,6 +204,165 @@ class SettingsPanel(private val plugin: Plugin) : JPanel() {
}
}
+ private fun createHashMapEditor(field: java.lang.reflect.Field, plugin: Plugin, fieldNotifier: FieldNotifier): JComponent {
+ // Create a panel to hold the table and buttons
+ val editorPanel = JPanel()
+ editorPanel.layout = BoxLayout(editorPanel, BoxLayout.Y_AXIS)
+ editorPanel.background = WIDGET_COLOR
+ editorPanel.border = BorderFactory.createEmptyBorder(5, 0, 5, 0)
+ editorPanel.maximumSize = Dimension(Int.MAX_VALUE, 250)
+
+ // Create title label
+ val titleLabel = JLabel("${field.name} (Key-Value Pairs)").apply {
+ foreground = secondaryColor
+ font = Font("RuneScape Small", Font.TRUETYPE_FONT, 16)
+ alignmentX = Component.LEFT_ALIGNMENT
+ }
+
+ // Get the current HashMap value
+ val hashMap = field.get(plugin) as? HashMap<*, *> ?: HashMap()
+
+ // Create a table model for the HashMap
+ val tableModel = object : DefaultTableModel() {
+ init {
+ val columnVector = java.util.Vector()
+ columnVector.add("Key")
+ columnVector.add("Value")
+ columnIdentifiers = columnVector
+ hashMap.forEach { (key, value) ->
+ val vector = java.util.Vector()
+ vector.add(key.toString())
+ vector.add(value.toString())
+ addRow(vector)
+ }
+ }
+
+ override fun isCellEditable(row: Int, column: Int): Boolean {
+ return true
+ }
+
+ override fun getColumnClass(columnIndex: Int): Class<*> {
+ return String::class.java
+ }
+ }
+
+ // Create the table
+ val table = JTable(tableModel).apply {
+ background = WIDGET_COLOR
+ foreground = secondaryColor
+ gridColor = primaryColor
+ tableHeader.background = WIDGET_COLOR
+ tableHeader.foreground = secondaryColor
+ preferredScrollableViewportSize = Dimension(Int.MAX_VALUE, 150)
+ setFillsViewportHeight(true)
+ }
+
+ // Create a scroll pane for the table with fixed size
+ val scrollPane = JScrollPane(table).apply {
+ preferredSize = Dimension(Int.MAX_VALUE, 150)
+ maximumSize = preferredSize
+ minimumSize = preferredSize
+ background = WIDGET_COLOR
+ verticalScrollBarPolicy = JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED
+ horizontalScrollBarPolicy = JScrollPane.HORIZONTAL_SCROLLBAR_NEVER
+ alignmentX = Component.LEFT_ALIGNMENT
+ }
+
+ // Create buttons panel
+ val buttonsPanel = JPanel(FlowLayout(FlowLayout.LEFT, 5, 5)).apply {
+ background = WIDGET_COLOR
+ alignmentX = Component.LEFT_ALIGNMENT
+ }
+
+ // Add button
+ val addButton = JButton("+").apply {
+ maximumSize = Dimension(40, 30)
+ addActionListener {
+ val vector = java.util.Vector()
+ vector.add("")
+ vector.add("")
+ tableModel.addRow(vector)
+ // Select the new row for editing
+ val newRow = tableModel.rowCount - 1
+ table.setRowSelectionInterval(newRow, newRow)
+ table.editCellAt(newRow, 0)
+ table.editorComponent?.requestFocus()
+ }
+ }
+
+ // Remove button
+ val removeButton = JButton("-").apply {
+ maximumSize = Dimension(40, 30)
+ addActionListener {
+ val selectedRow = table.selectedRow
+ if (selectedRow >= 0) {
+ tableModel.removeRow(selectedRow)
+ } else {
+ Helpers.showToast(
+ this@SettingsPanel,
+ "Please select a row to remove",
+ JOptionPane.WARNING_MESSAGE
+ )
+ }
+ }
+ }
+
+ // Apply button
+ val applyButton = JButton("Apply Changes").apply {
+ maximumSize = Dimension(220, 30)
+ addActionListener {
+ try {
+ // Create a new HashMap from the table data
+ // We need to determine the key and value types from the field's generic type
+ val newHashMap = HashMap()
+ for (i in 0 until tableModel.rowCount) {
+ val key = tableModel.getValueAt(i, 0).toString()
+ val value = tableModel.getValueAt(i, 1).toString()
+ // Only add non-empty keys
+ if (key.isNotBlank()) {
+ // Try to convert the value to the appropriate type
+ val convertedValue = try {
+ // For now, we'll keep everything as strings
+ // In the future, we could parse based on the generic type information
+ value
+ } catch (e: Exception) {
+ value // fallback to string
+ }
+ newHashMap[key] = convertedValue
+ }
+ }
+
+ // Update the field with the new HashMap
+ fieldNotifier.setFieldValue(field, newHashMap)
+ Helpers.showToast(
+ this@SettingsPanel,
+ "${field.name} updated successfully!"
+ )
+ } catch (e: Exception) {
+ Helpers.showToast(
+ this@SettingsPanel,
+ "Failed to update ${field.name}: ${e.message}",
+ JOptionPane.ERROR_MESSAGE
+ )
+ }
+ }
+ }
+
+ buttonsPanel.add(addButton)
+ buttonsPanel.add(removeButton)
+ buttonsPanel.add(Box.createHorizontalStrut(20))
+ buttonsPanel.add(applyButton)
+
+ // Add components to the editor panel in vertical stack
+ editorPanel.add(titleLabel)
+ editorPanel.add(Box.createVerticalStrut(5))
+ editorPanel.add(scrollPane)
+ editorPanel.add(Box.createVerticalStrut(5))
+ editorPanel.add(buttonsPanel)
+
+ return editorPanel
+ }
+
companion object {
var customToolTipWindow: JWindow? = null
diff --git a/plugin-playground/src/main/kotlin/KondoKit/plugin.properties b/plugin-playground/src/main/kotlin/KondoKit/plugin.properties
index 629c117..723ee11 100644
--- a/plugin-playground/src/main/kotlin/KondoKit/plugin.properties
+++ b/plugin-playground/src/main/kotlin/KondoKit/plugin.properties
@@ -1,3 +1,3 @@
AUTHOR='downthecrop'
-DESCRIPTION='A plugin that adds a right-side panel with custom widgets and navigation.'
-VERSION=2.0
\ No newline at end of file
+DESCRIPTION='A plugin that adds a right-side panel with custom widgets and navigation. Now with enhanced HashMap editor for easier configuration management.'
+VERSION=2.1
\ No newline at end of file