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