package KondoKit import KondoKit.Constants.COLOR_BACKGROUND_DARK import KondoKit.Helpers.getSpriteId import KondoKit.SpriteToBufferedImage.getBufferedImageFromSprite import KondoKit.plugin.Companion.VIEW_BACKGROUND_COLOR import com.google.gson.Gson import plugin.api.API import rt4.Sprites import java.awt.* import java.awt.datatransfer.DataFlavor import java.awt.event.KeyAdapter import java.awt.event.KeyEvent import java.awt.event.MouseAdapter import java.awt.event.MouseEvent import java.io.BufferedReader import java.io.InputStreamReader import java.net.HttpURLConnection import java.net.URL import javax.swing.* import javax.swing.border.MatteBorder object Constants { // Sprite IDs const val COMBAT_LVL_SPRITE = 168 const val IRONMAN_SPRITE = 4 const val MAG_SPRITE = 1423 const val LVL_BAR_SPRITE = 898 // Dimensions val SEARCH_FIELD_DIMENSION = Dimension(270, 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 IMAGE_CANVAS_DIMENSION = Dimension(20, 20) val NUMBER_LABEL_DIMENSION = Dimension(30, 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_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_BOLD_12 = Font("Arial", Font.BOLD, 12) } var text: String = "" object HiscoresView { class CustomSearchField(private val hiscoresPanel: JPanel) : Canvas() { private var cursorVisible: Boolean = true private val gson = Gson() val bufferedImageSprite = getBufferedImageFromSprite(API.GetSprite(Constants.MAG_SPRITE)) val imageCanvas = bufferedImageSprite.let { ImageCanvas(it).apply { preferredSize = Constants.ICON_DIMENSION_SMALL size = preferredSize minimumSize = preferredSize maximumSize = preferredSize } } init { preferredSize = Constants.SEARCH_FIELD_DIMENSION background = Constants.COLOR_BACKGROUND_DARK foreground = Constants.COLOR_FOREGROUND_LIGHT font = Constants.FONT_ARIAL_PLAIN_14 minimumSize = preferredSize maximumSize = preferredSize addKeyListener(object : KeyAdapter() { override fun keyTyped(e: KeyEvent) { // Prevent null character from being typed on Ctrl+A & Ctrl+V if (e.isControlDown && (e.keyChar == '\u0001' || e.keyChar == '\u0016')) { e.consume() return } if (e.keyChar == '\b') { if (text.isNotEmpty()) { text = text.dropLast(1) } } else if (e.keyChar == '\n') { searchPlayer(text) } else { text += e.keyChar } repaint() } override fun keyPressed(e: KeyEvent) { if (e.isControlDown) { when (e.keyCode) { KeyEvent.VK_A -> { text = "" repaint() } KeyEvent.VK_V -> { val clipboard = Toolkit.getDefaultToolkit().systemClipboard val pasteText = clipboard.getData(DataFlavor.stringFlavor) as String text += pasteText repaint() } } } } }) addMouseListener(object : MouseAdapter() { override fun mouseClicked(e: MouseEvent) { if (e.x > width - 20 && e.y < 20) { text = "" repaint() } } }) Timer(500) { cursorVisible = !cursorVisible if(plugin.StateManager.focusedView == "HISCORE_SEARCH_VIEW") repaint() }.start() } override fun paint(g: Graphics) { super.paint(g) g.color = foreground g.font = font val fm = g.fontMetrics val cursorX = fm.stringWidth(text) + 30 imageCanvas?.let { canvas -> val imgG = g.create(5, 5, canvas.width, canvas.height) canvas.paint(imgG) imgG.dispose() } g.drawString(text, 30, 20) if (cursorVisible && hasFocus()) { g.drawLine(cursorX, 5, cursorX, 25) } if (text.isNotEmpty()) { g.color = Constants.COLOR_RED g.drawString("x", width - 20, 20) } } fun searchPlayer(username: String) { text = username val apiUrl = "http://api.2009scape.org:3000/hiscores/playerSkills/1/${username.toLowerCase()}" Thread { try { val url = URL(apiUrl) val connection = url.openConnection() as HttpURLConnection connection.requestMethod = "GET" val responseCode = connection.responseCode if (responseCode == HttpURLConnection.HTTP_OK) { val reader = BufferedReader(InputStreamReader(connection.inputStream)) val response = reader.use { it.readText() } reader.close() SwingUtilities.invokeLater { updatePlayerData(response, username) } } else { SwingUtilities.invokeLater { showError("Player not found!") } } } catch (e: Exception) { SwingUtilities.invokeLater { showError("Error fetching data!") } } }.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) { val playerNameLabel = findComponentByName(hiscoresPanel, "playerNameLabel") as? JPanel val ironMode = data.info.iron_mode playerNameLabel?.removeAll() // Clear previous components if (ironMode != "0") { val ironmanBufferedImage = getBufferedImageFromSprite(Sprites.nameIcons[Constants.IRONMAN_SPRITE + ironMode.toInt() - 1]) val imageCanvas = ironmanBufferedImage.let { ImageCanvas(it).apply { preferredSize = Constants.IMAGE_CANVAS_DIMENSION size = Constants.IMAGE_CANVAS_DIMENSION } } playerNameLabel?.add(imageCanvas) } val nameLabel = JLabel(username, JLabel.CENTER).apply { font = Constants.FONT_ARIAL_BOLD_12 foreground = Constants.COLOR_FOREGROUND_LIGHT } playerNameLabel?.add(nameLabel) playerNameLabel?.revalidate() playerNameLabel?.repaint() // Update skill labels data.skills.forEachIndexed { index, skill -> val labelName = "skillLabel_$index" val numberLabel = findComponentByName(hiscoresPanel, labelName) as? JLabel numberLabel?.text = skill.static } updateTotalAndCombatLevel(data.skills, true) hiscoresPanel.revalidate() hiscoresPanel.repaint() } private fun updateTotalAndCombatLevel(skills: List, isMemberWorld: Boolean) { val totalLevel = skills.sumBy { it.static.toInt() } val totalLevelLabel = findComponentByName(hiscoresPanel, "totalLevelLabel") as? JLabel totalLevelLabel?.text = totalLevel.toString() val attack = skills.find { it.id == "0" }?.static?.toInt() ?: 1 val defence = skills.find { it.id == "1" }?.static?.toInt() ?: 1 val strength = skills.find { it.id == "2" }?.static?.toInt() ?: 1 val hitpoints = skills.find { it.id == "3" }?.static?.toInt() ?: 1 val ranged = skills.find { it.id == "4" }?.static?.toInt() ?: 1 val prayer = skills.find { it.id == "5" }?.static?.toInt() ?: 1 val magic = skills.find { it.id == "6" }?.static?.toInt() ?: 1 val summoning = skills.find { it.id == "23" }?.static?.toInt() ?: 1 val combatLevel = calculateCombatLevel(attack, defence, strength, hitpoints, prayer, ranged, magic, summoning, true) val combatLevelLabel = findComponentByName(hiscoresPanel, "combatLevelLabel") as? JLabel combatLevelLabel?.text = combatLevel.toString() } private fun calculateCombatLevel( attack: Int, defence: Int, strength: Int, hitpoints: Int, prayer: Int, ranged: Int, magic: Int, summoning: Int, isMemberWorld: Boolean ): Double { val base = (defence + hitpoints + Math.floor(prayer.toDouble() / 2)) * 0.25 val melee = (attack + strength) * 0.325 val range = Math.floor(ranged * 1.5) * 0.325 val mage = Math.floor(magic * 1.5) * 0.325 val maxCombatType = maxOf(melee, range, mage) val summoningFactor = if (isMemberWorld) Math.floor(summoning.toDouble() / 8) else 0.0 return Math.round((base + maxCombatType + summoningFactor) * 1000.0) / 1000.0 } private fun showError(message: String) { JOptionPane.showMessageDialog(null, message, "Error", JOptionPane.ERROR_MESSAGE) } private fun findComponentByName(container: Container, name: String): Component? { for (component in container.components) { if (name == component.name) { return component } if (component is Container) { val child = findComponentByName(component, name) if (child != null) { return child } } } return null } } fun createHiscoreSearchView(): JPanel { val hiscorePanel = JPanel().apply { layout = BoxLayout(this, BoxLayout.Y_AXIS) name = "HISCORE_SEARCH_VIEW" background = Constants.COLOR_BACKGROUND_MEDIUM preferredSize = Constants.HISCORE_PANEL_DIMENSION maximumSize = preferredSize minimumSize = preferredSize } val customSearchField = CustomSearchField(hiscorePanel) val searchFieldWrapper = JPanel().apply { layout = BoxLayout(this, BoxLayout.X_AXIS) background = Constants.COLOR_BACKGROUND_MEDIUM preferredSize = Constants.SEARCH_FIELD_DIMENSION maximumSize = preferredSize minimumSize = preferredSize alignmentX = Component.CENTER_ALIGNMENT add(customSearchField) } val searchPanel = JPanel().apply { layout = BoxLayout(this, BoxLayout.Y_AXIS) background = Constants.COLOR_BACKGROUND_MEDIUM add(searchFieldWrapper) } hiscorePanel.add(Helpers.Spacer(height = 10)) hiscorePanel.add(searchPanel) hiscorePanel.add(Helpers.Spacer(height = 10)) // Adding the player name panel in place of the filterPanel val playerNamePanel = JPanel().apply { layout = FlowLayout(FlowLayout.CENTER) background = VIEW_BACKGROUND_COLOR preferredSize = Constants.FILTER_PANEL_DIMENSION maximumSize = preferredSize minimumSize = preferredSize name = "playerNameLabel" } hiscorePanel.add(playerNamePanel) hiscorePanel.add(Helpers.Spacer(height = 10)) val skillsPanel = JPanel(FlowLayout(FlowLayout.CENTER, 0, 0)).apply { background = Constants.COLOR_BACKGROUND_MEDIUM preferredSize = Constants.SKILLS_PANEL_DIMENSION maximumSize = preferredSize minimumSize = preferredSize } for (i in 0 until 24) { val skillPanel = JPanel().apply { layout = BorderLayout() background = Constants.COLOR_SKILL_PANEL preferredSize = Constants.SKILL_PANEL_DIMENSION maximumSize = preferredSize minimumSize = preferredSize border = MatteBorder(5, 0, 0, 0, COLOR_BACKGROUND_DARK) } val bufferedImageSprite = getBufferedImageFromSprite(API.GetSprite(getSpriteId(i))) val imageCanvas = bufferedImageSprite.let { ImageCanvas(it).apply { preferredSize = Constants.IMAGE_CANVAS_DIMENSION size = Constants.IMAGE_CANVAS_DIMENSION } } val numberLabel = JLabel("", JLabel.RIGHT).apply { name = "skillLabel_$i" foreground = Constants.COLOR_FOREGROUND_LIGHT font = Constants.FONT_ARIAL_PLAIN_12 preferredSize = Constants.NUMBER_LABEL_DIMENSION minimumSize = Constants.NUMBER_LABEL_DIMENSION } val imageContainer = JPanel(FlowLayout(FlowLayout.CENTER, 5, 0)).apply { background = Constants.COLOR_BACKGROUND_DARK add(imageCanvas) add(numberLabel) } skillPanel.add(imageContainer, BorderLayout.CENTER) skillsPanel.add(skillPanel) } hiscorePanel.add(skillsPanel) val totalCombatPanel = JPanel(FlowLayout(FlowLayout.CENTER, 0, 0)).apply { background = COLOR_BACKGROUND_DARK preferredSize = Constants.TOTAL_COMBAT_PANEL_DIMENSION maximumSize = preferredSize minimumSize = preferredSize } val bufferedImageSprite = getBufferedImageFromSprite(API.GetSprite(Constants.LVL_BAR_SPRITE)); val totalLevelIcon = ImageCanvas(bufferedImageSprite).apply { preferredSize = Constants.ICON_DIMENSION_LARGE size = Constants.ICON_DIMENSION_LARGE } val totalLevelLabel = JLabel("").apply { name = "totalLevelLabel" foreground = Constants.COLOR_FOREGROUND_LIGHT font = Constants.FONT_ARIAL_BOLD_12 horizontalAlignment = JLabel.LEFT iconTextGap = 10 } val totalLevelPanel = JPanel(FlowLayout(FlowLayout.LEFT)).apply { background = Constants.COLOR_BACKGROUND_DARK add(totalLevelIcon) add(totalLevelLabel) } val bufferedImageSprite2 = getBufferedImageFromSprite(API.GetSprite(Constants.COMBAT_LVL_SPRITE)) val combatLevelIcon = ImageCanvas(bufferedImageSprite2).apply { preferredSize = Constants.ICON_DIMENSION_LARGE size = Constants.ICON_DIMENSION_LARGE } val combatLevelLabel = JLabel("").apply { name = "combatLevelLabel" foreground = Constants.COLOR_FOREGROUND_LIGHT font = Constants.FONT_ARIAL_BOLD_12 horizontalAlignment = JLabel.LEFT iconTextGap = 10 } val combatLevelPanel = JPanel(FlowLayout(FlowLayout.LEFT)).apply { background = Constants.COLOR_BACKGROUND_DARK add(combatLevelIcon) add(combatLevelLabel) } totalCombatPanel.add(totalLevelPanel) totalCombatPanel.add(combatLevelPanel) hiscorePanel.add(totalCombatPanel) return hiscorePanel } data class HiscoresResponse( val info: PlayerInfo, val skills: List ) data class PlayerInfo( val exp_multiplier: String, val iron_mode: String ) data class Skill( val id: String, val dynamic: String, val experience: String, val static: String ) }