Merge old and new search panels

This commit is contained in:
downthecrop 2025-10-12 19:33:25 -07:00
parent 2d1e2806ec
commit 8ac1bad46f
9 changed files with 352 additions and 336 deletions

View file

@ -1,54 +1,47 @@
package KondoKit.components.ReflectiveEditorComponents
package KondoKit.components
import KondoKit.ImageCanvas
import KondoKit.plugin
import KondoKit.SpriteToBufferedImage
import KondoKit.plugin.Companion.WIDGET_COLOR
import KondoKit.plugin.Companion.secondaryColor
import KondoKit.plugin.StateManager.focusedView
import plugin.api.API
import java.awt.*
import java.awt.datatransfer.DataFlavor
import java.awt.event.ActionListener
import java.awt.event.KeyAdapter
import java.awt.event.KeyEvent
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import KondoKit.SpriteToBufferedImage.getBufferedImageFromSprite
import java.awt.image.BufferedImage
import java.awt.event.*
import javax.swing.*
class CustomSearchField(private val parentPanel: JPanel, private val onSearch: (String) -> Unit) : Canvas() {
class CustomSearchField(
private val parentPanel: JPanel,
private val onSearch: (String) -> Unit,
private val placeholderText: String = "Search plugins...",
private val fieldWidth: Int = 230,
private val fieldHeight: Int = 30,
private val viewName: String? = "REFLECTIVE_EDITOR_VIEW" // Default to the original behavior
) : Canvas() {
private var cursorVisible: Boolean = true
private var text: String = ""
private val bufferedImageSprite = getBufferedImageFromSpriteWithScale(API.GetSprite(1423)) // MAG_SPRITE
private val bufferedImageSprite = SpriteToBufferedImage.getBufferedImageFromSprite(API.GetSprite(1423)) // MAG_SPRITE
private val imageCanvas = bufferedImageSprite.let {
ImageCanvas(it).apply {
preferredSize = Dimension(12, 12) // ICON_DIMENSION_SMALL
preferredSize = Dimension(12, 12)
size = preferredSize
minimumSize = preferredSize
maximumSize = preferredSize
fillColor = WIDGET_COLOR
}
}
fun setText(newText: String) {
text = newText
SwingUtilities.invokeLater {
repaint()
}
}
fun getText(): String {
return text
}
init {
preferredSize = Dimension(230, 30) // SEARCH_FIELD_DIMENSION
background = WIDGET_COLOR // COLOR_BACKGROUND_DARK
foreground = secondaryColor // COLOR_FOREGROUND_LIGHT
font = Font("Arial", Font.PLAIN, 14) // FONT_ARIAL_PLAIN_14
minimumSize = preferredSize
maximumSize = preferredSize
val dimension = Dimension(fieldWidth, fieldHeight)
preferredSize = dimension
background = WIDGET_COLOR
foreground = secondaryColor
font = Font("Arial", Font.PLAIN, 14)
minimumSize = dimension
maximumSize = dimension
addKeyListener(object : KeyAdapter() {
override fun keyTyped(e: KeyEvent) {
@ -62,7 +55,7 @@ class CustomSearchField(private val parentPanel: JPanel, private val onSearch: (
text = text.dropLast(1)
}
} else if (e.keyChar == '\n') {
onSearch(text)
triggerSearch()
} else {
text += e.keyChar
}
@ -70,21 +63,27 @@ class CustomSearchField(private val parentPanel: JPanel, private val onSearch: (
repaint()
}
}
override fun keyPressed(e: KeyEvent) {
if (e.isControlDown) {
when (e.keyCode) {
KeyEvent.VK_A -> {
// They probably want to clear the search box
text = ""
SwingUtilities.invokeLater {
repaint()
}
}
KeyEvent.VK_V -> {
val clipboard = Toolkit.getDefaultToolkit().systemClipboard
val pasteText = clipboard.getData(DataFlavor.stringFlavor) as String
text += pasteText
SwingUtilities.invokeLater {
repaint()
try {
val clipboard = Toolkit.getDefaultToolkit().systemClipboard
val pasteText = clipboard.getData(DataFlavor.stringFlavor) as String
text += pasteText
SwingUtilities.invokeLater {
repaint()
}
} catch (ex: Exception) {
// Ignore clipboard errors
}
}
}
@ -96,7 +95,6 @@ class CustomSearchField(private val parentPanel: JPanel, private val onSearch: (
override fun mouseClicked(e: MouseEvent) {
if (e.x > width - 20 && e.y < 20) {
text = ""
onSearch(text)
SwingUtilities.invokeLater {
repaint()
}
@ -104,13 +102,15 @@ class CustomSearchField(private val parentPanel: JPanel, private val onSearch: (
}
})
Timer(500, ActionListener { _ ->
Timer(500) { _ ->
cursorVisible = !cursorVisible
if (plugin.StateManager.focusedView == "REFLECTIVE_EDITOR_VIEW")
// Only repaint if the view is active or if viewName is not specified
if (viewName == null || focusedView == viewName) {
SwingUtilities.invokeLater {
repaint()
}
}).start()
}
}.start()
}
override fun paint(g: Graphics) {
@ -132,9 +132,9 @@ class CustomSearchField(private val parentPanel: JPanel, private val onSearch: (
val currentText = text
// Draw placeholder text if field is empty, otherwise draw actual text
if (currentText == "") {
if (currentText.isEmpty()) {
g.color = Color.GRAY // Use a lighter color for placeholder text
g.drawString("Search plugins...", 30, 20)
g.drawString(placeholderText, 30, 20)
} else {
g.color = foreground // Use normal color for actual text
g.drawString(currentText, 30, 20)
@ -146,19 +146,27 @@ class CustomSearchField(private val parentPanel: JPanel, private val onSearch: (
}
// Only draw the "x" button if there's text
if (currentText != "") {
if (currentText.isNotEmpty()) {
g.color = Color.RED
g.drawString("x", width - 20, 20)
}
}
private fun getBufferedImageFromSpriteWithScale(sprite: Any?): BufferedImage {
val image = BufferedImage(12, 12, BufferedImage.TYPE_INT_ARGB)
val g2d = image.createGraphics()
g2d.color = Color.GRAY
g2d.fillOval(2, 2, 8, 8)
g2d.drawLine(8, 8, 11, 11)
g2d.dispose()
return image
fun setText(newText: String) {
text = newText
repaint()
}
fun getText(): String = text
private fun triggerSearch() {
val query = text.trim()
if (query.isNotEmpty()) {
text = query
repaint()
onSearch(query)
} else {
onSearch(query) // Call with empty string for clearing filters
}
}
}

View file

@ -1,14 +0,0 @@
package KondoKit.components.ReflectiveEditorComponents
data class GitLabPlugin(
val id: String,
val path: String,
val pluginProperties: PluginProperties?,
val pluginError: String?
)
data class PluginProperties(
val author: String,
val version: String,
val description: String
)

View file

@ -406,10 +406,10 @@ class plugin : Plugin() {
private fun searchHiscore(username: String): Runnable {
return Runnable {
setActiveView(HiscoresView.VIEW_NAME)
val customSearchField = HiscoresView.hiScoreView?.let { HiscoresView.CustomSearchField(it) }
customSearchField?.searchPlayer(username) ?: run {
println("searchView is null or CustomSearchField creation failed.")
HiscoresView.hiScoreView?.let { hiscoresPanel ->
HiscoresView.searchPlayerForHiscores(username, hiscoresPanel)
} ?: run {
println("hiscoresPanel is null")
}
}
}

View file

@ -0,0 +1,37 @@
package KondoKit.pluginmanager
/**
* Configuration object for GitLab API settings that can be shared between
* GitLabPluginFetcher and PluginDownloadManager
*/
object GitLabConfig {
// GitLab project information - using the ID from PluginFetcher
const val GITLAB_PROJECT_ID = "38297322"
const val GITLAB_PROJECT_PATH = "2009scape/tools/client-plugins" // Using path from DownloadManager
const val GITLAB_BRANCH = "master"
// HTTP headers and client settings
const val CHROME_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
// Debug settings
const val DEBUG = true // Set to false to disable debug logging
// API endpoints and URL patterns
fun getGitLabApiBaseUrl(): String = "https://gitlab.com/api/v4/projects/$GITLAB_PROJECT_ID"
fun getRepositoryTreeUrl(): String =
"${getGitLabApiBaseUrl()}/repository/tree?ref=${GITLAB_BRANCH}&per_page=100"
fun getRawFileUrl(filePath: String): String =
"${getGitLabApiBaseUrl()}/repository/files/${filePath.replace("/", "%2F")}/raw?ref=${GITLAB_BRANCH}"
// Archive URL helper
fun getArchiveBaseUrl(): String =
"https://gitlab.com/$GITLAB_PROJECT_PATH/-/archive/$GITLAB_BRANCH/${GITLAB_PROJECT_PATH.replace("/", "-")}-$GITLAB_BRANCH.zip"
fun getArchiveUrl(pluginPath: String): String =
"${getArchiveBaseUrl()}?path=$pluginPath"
fun getArchiveUrlWithRefType(pluginPath: String): String =
"${getArchiveBaseUrl()}?path=$pluginPath&ref_type=heads"
}

View file

@ -1,9 +1,22 @@
package KondoKit.components.ReflectiveEditorComponents
package KondoKit.pluginmanager
data class GitLabPlugin(
val id: String,
val path: String,
val pluginProperties: PluginProperties?,
val pluginError: String?
)
data class PluginProperties(
val author: String,
val version: String,
val description: String
)
/**
* Data class representing a plugin with its installation status and update information
*/
data class PluginStatus(
data class PluginInfoWithStatus(
val name: String,
val installedVersion: String?,
val remoteVersion: String?,

View file

@ -1,4 +1,4 @@
package KondoKit.components.ReflectiveEditorComponents
package KondoKit.pluginmanager
import KondoKit.views.ReflectiveEditorView
import com.google.gson.Gson
@ -10,19 +10,16 @@ import java.net.HttpURLConnection
import java.net.URL
import java.util.concurrent.*
import javax.swing.SwingUtilities
import KondoKit.pluginmanager.GitLabConfig
object GitLabPluginFetcher {
private const val GITLAB_PROJECT_ID = "38297322"
private const val GITLAB_BRANCH = "master"
private const val CHROME_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
private const val DEBUG = true // Set to false to disable debug logging
// Thread pool for concurrent plugin property fetching
private val executorService = Executors.newFixedThreadPool(5)
// Debug logging function
private fun debugLog(message: String) {
if (DEBUG) {
if (GitLabConfig.DEBUG) {
println("[GitLabPluginFetcher] $message")
}
}
@ -33,15 +30,15 @@ object GitLabPluginFetcher {
try {
debugLog("Starting to fetch GitLab plugins...")
val plugins = mutableListOf<GitLabPlugin>()
val apiUrl = "https://gitlab.com/api/v4/projects/${GITLAB_PROJECT_ID}/repository/tree?ref=${GITLAB_BRANCH}&per_page=100"
val apiUrl = GitLabConfig.getRepositoryTreeUrl()
debugLog("API URL: $apiUrl")
// Create URL connection
val url = URL(apiUrl)
val connection = url.openConnection() as HttpURLConnection
connection.requestMethod = "GET"
connection.setRequestProperty("User-Agent", CHROME_USER_AGENT)
debugLog("Set User-Agent header to: $CHROME_USER_AGENT")
connection.setRequestProperty("User-Agent", GitLabConfig.CHROME_USER_AGENT)
debugLog("Set User-Agent header to: ${GitLabConfig.CHROME_USER_AGENT}")
// Read response
val responseCode = connection.responseCode
@ -119,14 +116,14 @@ object GitLabPluginFetcher {
// Function to fetch plugin.properties from a specific folder
private fun fetchPluginProperties(folderPath: String): PluginProperties {
val pluginFilePath = "$folderPath/plugin.properties"
val pluginUrl = "https://gitlab.com/api/v4/projects/${GITLAB_PROJECT_ID}/repository/files/${pluginFilePath.replace("/", "%2F")}/raw?ref=${GITLAB_BRANCH}"
val pluginUrl = GitLabConfig.getRawFileUrl(pluginFilePath)
debugLog("Fetching plugin.properties from: $pluginUrl")
// Create URL connection
val url = URL(pluginUrl)
val connection = url.openConnection() as HttpURLConnection
connection.requestMethod = "GET"
connection.setRequestProperty("User-Agent", CHROME_USER_AGENT)
connection.setRequestProperty("User-Agent", GitLabConfig.CHROME_USER_AGENT)
// Read response
val responseCode = connection.responseCode

View file

@ -1,4 +1,4 @@
package KondoKit.components.ReflectiveEditorComponents
package KondoKit.pluginmanager
import KondoKit.views.ReflectiveEditorView
import plugin.PluginRepository
@ -10,16 +10,13 @@ import java.util.concurrent.*
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import javax.swing.SwingUtilities
import KondoKit.pluginmanager.GitLabConfig
/**
* Manages downloading and installing plugins from GitLab with concurrent support
*/
object PluginDownloadManager {
private const val GITLAB_PROJECT_PATH = "2009scape/tools/client-plugins"
private const val GITLAB_BRANCH = "master"
private const val CHROME_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
private const val MAX_CONCURRENT_DOWNLOADS = 3
private const val DEBUG = true // Set to false to disable debug logging
// Thread pool for concurrent downloads
private val downloadExecutor = Executors.newFixedThreadPool(MAX_CONCURRENT_DOWNLOADS)
@ -32,54 +29,14 @@ object PluginDownloadManager {
// Debug logging function
private fun debugLog(message: String) {
if (DEBUG) {
if (GitLabConfig.DEBUG) {
println("[PluginDownloadManager] $message")
}
}
// Test URL method for debugging
fun testDownloadUrl(plugin: GitLabPlugin): String {
return "https://gitlab.com/$GITLAB_PROJECT_PATH/-/archive/$GITLAB_BRANCH/${GITLAB_PROJECT_PATH.replace("/", "-")}-$GITLAB_BRANCH.zip?path=${plugin.path}"
}
/**
* Download a single plugin
*/
fun downloadPlugin(plugin: GitLabPlugin, callback: (String, Boolean, String?) -> Unit) {
downloadExecutor.submit {
try {
debugLog("Starting download for plugin: ${plugin.path}")
// Download the plugin as a ZIP archive
val success = downloadAndExtractPlugin(plugin, object : DownloadProgressCallback {
override fun onProgress(pluginName: String, progress: Int) {
// We don't need to do anything here for the simple callback
}
override fun onComplete(pluginName: String, success: Boolean, errorMessage: String?) {
callback(pluginName, success, errorMessage)
}
})
if (success) {
debugLog("Successfully downloaded and extracted plugin: ${plugin.path}")
SwingUtilities.invokeLater {
callback(plugin.path, true, null)
}
} else {
debugLog("Failed to download plugin: ${plugin.path}")
SwingUtilities.invokeLater {
callback(plugin.path, false, "Failed to download plugin")
}
}
} catch (e: Exception) {
debugLog("Exception during download of ${plugin.path}: ${e.message}")
e.printStackTrace()
SwingUtilities.invokeLater {
callback(plugin.path, false, e.message)
}
}
}
// Get download URL for debugging/logging purposes
fun getDownloadUrlForLogging(plugin: GitLabPlugin): String {
return GitLabConfig.getArchiveUrl(plugin.path)
}
/**
@ -151,11 +108,11 @@ object PluginDownloadManager {
// Try multiple URL formats since GitLab has changed their API
val downloadUrls = listOf(
// Format 1: Using ref_type parameter
"https://gitlab.com/$GITLAB_PROJECT_PATH/-/archive/$GITLAB_BRANCH/${GITLAB_PROJECT_PATH.replace("/", "-")}-$GITLAB_BRANCH.zip?path=${plugin.path}&ref_type=heads",
GitLabConfig.getArchiveUrlWithRefType(plugin.path),
// Format 2: Using ref parameter
"https://gitlab.com/$GITLAB_PROJECT_PATH/-/archive/$GITLAB_BRANCH/${GITLAB_PROJECT_PATH.replace("/", "-")}-$GITLAB_BRANCH.zip?path=${plugin.path}",
GitLabConfig.getArchiveUrl(plugin.path),
// Format 3: Direct archive URL without path parameter (we'll filter later)
"https://gitlab.com/$GITLAB_PROJECT_PATH/-/archive/$GITLAB_BRANCH/${GITLAB_PROJECT_PATH.replace("/", "-")}-$GITLAB_BRANCH.zip"
GitLabConfig.getArchiveBaseUrl()
)
for (downloadUrl in downloadUrls) {
@ -172,7 +129,7 @@ object PluginDownloadManager {
// Create URL connection
val connection = url.openConnection() as HttpURLConnection
connection.requestMethod = "GET"
connection.setRequestProperty("User-Agent", CHROME_USER_AGENT)
connection.setRequestProperty("User-Agent", GitLabConfig.CHROME_USER_AGENT)
// Add Accept header to avoid 406 errors
connection.setRequestProperty("Accept", "*/*")

View file

@ -4,6 +4,7 @@ import KondoKit.Helpers.getSpriteId
import KondoKit.Helpers.showToast
import KondoKit.ImageCanvas
import KondoKit.SpriteToBufferedImage.getBufferedImageFromSprite
import KondoKit.components.CustomSearchField
import KondoKit.components.LabelComponent
import KondoKit.components.SearchField
import KondoKit.plugin.Companion.WIDGET_COLOR
@ -12,6 +13,8 @@ import KondoKit.plugin.Companion.POPUP_FOREGROUND
import KondoKit.plugin.Companion.secondaryColor
import KondoKit.plugin.Companion.primaryColor
import KondoKit.plugin.Companion.TOOLTIP_BACKGROUND
import KondoKit.views.HiscoresView.VIEW_NAME
import KondoKit.views.HiscoresView.hiScoreView
import com.google.gson.Gson
import plugin.api.API
import rt4.Sprites
@ -63,7 +66,7 @@ object HiscoresView : View {
var hiScoreView: JPanel? = null
override val name: String = VIEW_NAME
override val iconSpriteId: Int = Constants.MAG_SPRITE
override val panel: JPanel
get() = hiScoreView ?: JPanel()
@ -75,191 +78,22 @@ object HiscoresView : View {
// Hiscores functions are handled within the view itself
}
class CustomSearchField(private val hiscoresPanel: JPanel) : SearchField(
onSearch = { _ -> }, // Placeholder, will be replaced
viewName = VIEW_NAME
) {
private val gson = Gson()
init {
// This is a workaround to set the onSearch callback after the class is fully initialized
val onSearchField = javaClass.superclass.getDeclaredField("onSearch")
onSearchField.isAccessible = true
onSearchField.set(this, { username: String -> searchPlayer(username) })
}
fun searchPlayer(username: String) {
val cleanUsername = username.replace(" ", "_")
setText(cleanUsername)
val apiUrl = "http://api.2009scape.org:3000/hiscores/playerSkills/1/${cleanUsername.toLowerCase()}"
updateHiscoresView(null, "Searching...")
Thread {
try {
val url = URL(apiUrl)
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))
val response = reader.use { it.readText() }
reader.close()
SwingUtilities.invokeLater {
updatePlayerData(response, username)
}
} else {
SwingUtilities.invokeLater {
showToast(hiscoresPanel, "Player not found!", JOptionPane.ERROR_MESSAGE)
}
}
} catch (e: SocketTimeoutException) {
SwingUtilities.invokeLater {
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) {
val playerNameLabel = findComponentByName(hiscoresPanel, "playerNameLabel") as? JPanel
playerNameLabel?.removeAll() // Clear previous components
var nameLabel = LabelComponent().apply {
updateHtmlText(username, secondaryColor, "", primaryColor)
font = Constants.FONT_ARIAL_BOLD_12
foreground = Constants.COLOR_FOREGROUND_LIGHT
border = BorderFactory.createEmptyBorder(0, 6, 0, 0) // Top, Left, Bottom, Right padding
horizontalAlignment = JLabel.CENTER
}
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])
val imageCanvas = ironmanBufferedImage.let {
ImageCanvas(it).apply {
preferredSize = Constants.IMAGE_CANVAS_DIMENSION
size = Constants.IMAGE_CANVAS_DIMENSION
}
}
playerNameLabel?.add(imageCanvas)
}
val exp_multiplier = data.info.exp_multiplier
nameLabel = LabelComponent().apply {
updateHtmlText(username, secondaryColor, " (${exp_multiplier}x)", primaryColor)
font = Constants.FONT_ARIAL_BOLD_12
foreground = Constants.COLOR_FOREGROUND_LIGHT
border = BorderFactory.createEmptyBorder(0, 6, 0, 0) // Top, Left, Bottom, Right padding
horizontalAlignment = JLabel.CENTER
}
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<Skill>, 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 + floor(prayer.toDouble() / 2)) * 0.25
val melee = (attack + strength) * 0.325
val range = floor(ranged * 1.5) * 0.325
val mage = floor(magic * 1.5) * 0.325
val maxCombatType = maxOf(melee, range, mage)
val summoningFactor = if (isMemberWorld) floor(summoning.toDouble() / 8) else 0.0
return Math.round((base + maxCombatType + summoningFactor) * 1000.0) / 1000.0
}
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() {
val hiscorePanel = BaseView(VIEW_NAME, Constants.HISCORE_PANEL_DIMENSION.width).apply {
background = Constants.COLOR_BACKGROUND_MEDIUM
setViewSize(Constants.HISCORE_PANEL_DIMENSION.height)
}
val customSearchField = CustomSearchField(hiscorePanel)
val customSearchField = CustomSearchField(
hiscorePanel,
{ username ->
searchPlayerForHiscores(username, hiscorePanel)
},
"Search player...",
230,
30,
VIEW_NAME
)
val searchFieldWrapper = JPanel().apply {
layout = BoxLayout(this, BoxLayout.X_AXIS)
@ -415,4 +249,181 @@ object HiscoresView : View {
val experience: String,
val static: String
)
}
// Function to search for players in hiscores, extracted from the old inner class
fun searchPlayerForHiscores(username: String, hiscoresPanel: JPanel) {
val cleanUsername = username.replace(" ", "_")
// Note: We can't call setText on the CustomSearchField because it now doesn't have that method
// The text is already set when the user presses enter in the search field
val apiUrl = "http://api.2009scape.org:3000/hiscores/playerSkills/1/${cleanUsername.toLowerCase()}"
// Find the updateHiscoresView function or update the display
// For now, we'll just call the main updateHiscoresView method if it's accessible
updateHiscoresViewStandalone(hiscoresPanel, null, "Searching...")
Thread {
try {
val url = URL(apiUrl)
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))
val response = reader.use { it.readText() }
reader.close()
SwingUtilities.invokeLater {
updatePlayerDataStandalone(response, username, hiscoresPanel)
}
} else {
SwingUtilities.invokeLater {
showToast(hiscoresPanel, "Player not found!", JOptionPane.ERROR_MESSAGE)
}
}
} catch (e: SocketTimeoutException) {
SwingUtilities.invokeLater {
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 updatePlayerDataStandalone(jsonResponse: String, username: String, hiscoresPanel: JPanel) {
val gson = Gson()
val hiscoresResponse = gson.fromJson(jsonResponse, HiscoresResponse::class.java)
updateHiscoresViewStandalone(hiscoresPanel, hiscoresResponse, username)
}
private fun updateHiscoresViewStandalone(hiscoresPanel: JPanel, data: HiscoresResponse?, username: String) {
val playerNameLabel = findComponentByNameStandalone(hiscoresPanel, "playerNameLabel") as? JPanel
playerNameLabel?.removeAll() // Clear previous components
var nameLabel = LabelComponent().apply {
updateHtmlText(username, secondaryColor, "", primaryColor)
font = Constants.FONT_ARIAL_BOLD_12
foreground = Constants.COLOR_FOREGROUND_LIGHT
border = BorderFactory.createEmptyBorder(0, 6, 0, 0) // Top, Left, Bottom, Right padding
horizontalAlignment = JLabel.CENTER
}
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])
val imageCanvas = ironmanBufferedImage.let {
ImageCanvas(it).apply {
preferredSize = Constants.IMAGE_CANVAS_DIMENSION
size = Constants.IMAGE_CANVAS_DIMENSION
}
}
playerNameLabel?.add(imageCanvas)
}
val exp_multiplier = data.info.exp_multiplier
nameLabel = LabelComponent().apply {
updateHtmlText(username, secondaryColor, " (${exp_multiplier}x)", primaryColor)
font = Constants.FONT_ARIAL_BOLD_12
foreground = Constants.COLOR_FOREGROUND_LIGHT
border = BorderFactory.createEmptyBorder(0, 6, 0, 0) // Top, Left, Bottom, Right padding
horizontalAlignment = JLabel.CENTER
}
playerNameLabel?.add(nameLabel)
playerNameLabel?.revalidate()
playerNameLabel?.repaint()
// Update skill labels
data.skills.forEachIndexed { index, skill ->
val labelName = "skillLabel_$index"
val numberLabel = findComponentByNameStandalone(hiscoresPanel, labelName) as? JLabel
numberLabel?.text = skill.static.toInt().toString()
}
updateTotalAndCombatLevelStandalone(data.skills, hiscoresPanel, true)
hiscoresPanel.revalidate()
hiscoresPanel.repaint()
}
private fun updateTotalAndCombatLevelStandalone(
skills: List<Skill>,
hiscoresPanel: JPanel,
isMemberWorld: Boolean
) {
val totalLevel = skills.sumBy { it.static.toInt() }
val totalLevelLabel = findComponentByNameStandalone(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 = findComponentByNameStandalone(hiscoresPanel, "combatLevelLabel") as? JLabel
combatLevelLabel?.text = combatLevel.toString()
}
private fun findComponentByNameStandalone(container: Container, name: String): Component? {
for (component in container.components) {
if (name == component.name) {
return component
}
if (component is Container) {
val child = findComponentByNameStandalone(component, name)
if (child != null) {
return child
}
}
}
return null
}
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 + floor(prayer.toDouble() / 2)) * 0.25
val melee = (attack + strength) * 0.325
val range = floor(ranged * 1.5) * 0.325
val mage = floor(magic * 1.5) * 0.325
val maxCombatType = maxOf(melee, range, mage)
val summoningFactor = if (isMemberWorld) floor(summoning.toDouble() / 8) else 0.0
return Math.round((base + maxCombatType + summoningFactor) * 1000.0) / 1000.0
}
}

View file

@ -2,12 +2,12 @@ package KondoKit.views
import KondoKit.Helpers
import KondoKit.components.*
import KondoKit.components.ReflectiveEditorComponents.CustomSearchField
import KondoKit.components.ReflectiveEditorComponents.GitLabPlugin
import KondoKit.components.ReflectiveEditorComponents.GitLabPluginFetcher
import KondoKit.components.ReflectiveEditorComponents.PluginDownloadManager
import KondoKit.components.ReflectiveEditorComponents.PluginProperties
import KondoKit.components.ReflectiveEditorComponents.PluginStatus
import KondoKit.components.CustomSearchField
import KondoKit.pluginmanager.GitLabPlugin
import KondoKit.pluginmanager.GitLabPluginFetcher
import KondoKit.pluginmanager.PluginDownloadManager
import KondoKit.pluginmanager.PluginProperties
import KondoKit.pluginmanager.PluginInfoWithStatus
import KondoKit.Helpers.showToast
import KondoKit.plugin.Companion.TITLE_BAR_COLOR
import KondoKit.plugin.Companion.TOOLTIP_BACKGROUND
@ -47,7 +47,7 @@ object ReflectiveEditorView : View {
private var gitLabPlugins: List<GitLabPlugin> = listOf()
private var pluginStatuses: List<PluginStatus> = listOf()
private var pluginStatuses: List<PluginInfoWithStatus> = listOf()
private var cogIcon: Icon? = null
@ -133,12 +133,19 @@ object ReflectiveEditorView : View {
panel.layout = BoxLayout(panel, BoxLayout.Y_AXIS)
panel.background = VIEW_BACKGROUND_COLOR
val searchField = CustomSearchField(panel) { searchText ->
pluginSearchText = searchText
SwingUtilities.invokeLater {
addPlugins(reflectiveEditorView!!)
}
}
val searchField = CustomSearchField(
parentPanel = panel,
onSearch = { searchText ->
pluginSearchText = searchText
SwingUtilities.invokeLater {
addPlugins(reflectiveEditorView!!)
}
},
placeholderText = "Search plugins...",
fieldWidth = 230,
fieldHeight = 30,
viewName = "REFLECTIVE_EDITOR_VIEW"
)
this.searchField = searchField
if (pluginSearchText.isNotBlank()) {
searchField.setText(pluginSearchText)
@ -433,7 +440,7 @@ object ReflectiveEditorView : View {
return panel
}
private fun createPluginStatusItemPanel(pluginStatus: PluginStatus): JPanel {
private fun createPluginStatusItemPanel(pluginStatus: PluginInfoWithStatus): JPanel {
val panel = JPanel(BorderLayout())
panel.background = WIDGET_COLOR
panel.border = BorderFactory.createEmptyBorder(10, 10, 10, 10)
@ -948,7 +955,7 @@ object ReflectiveEditorView : View {
}
// Method to start downloading a plugin
private fun startPluginDownload(pluginStatus: PluginStatus) {
private fun startPluginDownload(pluginStatus: PluginInfoWithStatus) {
val gitLabPlugin = pluginStatus.gitLabPlugin
if (gitLabPlugin == null) {
showToast(mainPanel, "Plugin information not available", JOptionPane.ERROR_MESSAGE)
@ -956,7 +963,7 @@ object ReflectiveEditorView : View {
}
// Log the download URL for debugging
val downloadUrl = PluginDownloadManager.testDownloadUrl(gitLabPlugin)
val downloadUrl = PluginDownloadManager.getDownloadUrlForLogging(gitLabPlugin)
System.out.println("Download URL for plugin ${gitLabPlugin.path}: $downloadUrl")
// Update plugin status to show downloading
@ -1022,7 +1029,7 @@ object ReflectiveEditorView : View {
}
// Method to start downloading multiple plugins
private fun startMultiplePluginDownloads(pluginStatuses: List<PluginStatus>) {
private fun startMultiplePluginDownloads(pluginStatuses: List<PluginInfoWithStatus>) {
val gitLabPlugins = pluginStatuses.mapNotNull { it.gitLabPlugin }
if (gitLabPlugins.isEmpty()) {
@ -1092,7 +1099,7 @@ object ReflectiveEditorView : View {
// Update plugin statuses by comparing installed and remote plugins
private fun updatePluginStatuses() {
val loadedPluginNames = getLoadedPluginNames()
val statuses = mutableListOf<PluginStatus>()
val statuses = mutableListOf<PluginInfoWithStatus>()
System.out.println("Updating plugin statuses. Loaded plugins: ${loadedPluginNames.joinToString(", ")}")
@ -1126,7 +1133,7 @@ object ReflectiveEditorView : View {
if (isLoaded) {
// Plugin is currently loaded
statuses.add(PluginStatus(
statuses.add(PluginInfoWithStatus(
name = pluginName,
installedVersion = remoteVersion, // We don't have the actual installed version, but we know it's loaded
remoteVersion = remoteVersion,
@ -1143,7 +1150,7 @@ object ReflectiveEditorView : View {
val versionsMatch = disabledVersion != null && disabledVersion == remoteVersion
if (!versionsMatch) {
// Versions don't match, show update option
statuses.add(PluginStatus(
statuses.add(PluginInfoWithStatus(
name = pluginName,
installedVersion = disabledVersion,
remoteVersion = remoteVersion,
@ -1159,7 +1166,7 @@ object ReflectiveEditorView : View {
// If versions match, we don't add it to the list since there's no point showing it
} else {
// Plugin is not installed at all
statuses.add(PluginStatus(
statuses.add(PluginInfoWithStatus(
name = pluginName,
installedVersion = null,
remoteVersion = remoteVersion,