Compare commits

...

28 commits

Author SHA1 Message Date
downthecrop
7594e98f51 Merge branch 'KondoKit-2.0' into 'master'
KondoKit 2.0

See merge request 2009scape/rt4-client!28
2024-10-28 23:43:44 +00:00
downthecrop
1de6638a12 Merge branch 'FIXED-RESIZE' into 'KondoKit-2.0'
Fixed Streched

See merge request downthecrop/rt4-client!4
2024-10-28 23:41:55 +00:00
downthecrop
4ebba194dc Merge branch 'KondoKit-2.0' into 'FIXED-RESIZE'
# Conflicts:
#   plugin-playground/src/main/kotlin/KondoKit/ScrollablePanel.kt
2024-10-28 23:41:42 +00:00
downthecrop
e05f5829d7 Restore original Kondo Fixed mode handling 2024-10-28 16:38:33 -07:00
downthecrop
cf5ccbf1ac undo some testing changes 2024-10-28 16:32:51 -07:00
downthecrop
cb9758f7d5 Organize overrides to be together in the source 2024-10-28 16:22:40 -07:00
downthecrop
1cca6611ee general cleanup 2024-10-28 12:43:29 -07:00
downthecrop
52d07d5795 add mousewheel relay 2024-10-28 11:42:37 -07:00
downthecrop
f2d4c17569 Correct offsets 2024-10-28 10:53:23 -07:00
downthecrop
c24df5ab46 cleanup files 2024-10-28 07:18:08 -07:00
downthecrop
07f4dc9349 mostly working alt canvas 2024-10-28 00:03:43 -07:00
downthecrop
c307db1e11 double buffering 2024-10-27 21:38:12 -07:00
downthecrop
8b89a2bb8c Faster transforms 2024-10-27 20:22:16 -07:00
downthecrop
c5ba4ecc94 true nn upscale 2024-10-27 20:11:25 -07:00
downthecrop
341d6758c1 Simplify altcanvas 2024-10-27 19:40:24 -07:00
downthecrop
1f1718d917 Keep the canvas in the frame but change the Z order 2024-10-27 08:10:38 -07:00
downthecrop
8180e20281 Removing debug from client + add print for unparented altcanvas 2024-10-26 22:33:54 -07:00
downthecrop
e7f46f1006 Constant for FIXED_HEIGHT + transfercomponent helper 2024-10-26 22:31:43 -07:00
downthecrop
83fe4805ac GC issues + some late invokes 2024-10-26 09:59:25 -07:00
downthecrop
71afb2b385 greatly reduce flickering 2024-10-26 09:06:23 -07:00
downthecrop
15deb6216f skip unrequired movement 2024-10-25 21:26:43 -07:00
downthecrop
4f8bf464c5 HD Resize redraw 2024-10-25 21:22:16 -07:00
downthecrop
b3c2adc51f support scaled fixed HD 2024-10-25 15:54:39 -07:00
downthecrop
838acc57ff handing for altcanvas in alerts 2024-10-24 23:21:55 -07:00
downthecrop
1eb7483d35 Regain focus on click 2024-10-24 20:58:45 -07:00
downthecrop
af4dafee89 Convert to volatile and centralize logic to the altcanvas. 2024-10-24 20:50:56 -07:00
downthecrop
8e554b573c gather functions together for easier debugging 2024-10-24 20:24:40 -07:00
downthecrop
668345b81e FIXED streched mode 2024-10-24 12:03:16 -07:00
14 changed files with 602 additions and 299 deletions

View file

@ -13,6 +13,7 @@ import rt4.Tile;
*/
public abstract class Plugin {
long timeOfLastDraw;
long timeOfLastLateDraw;
void _init() {
Init();
@ -24,6 +25,12 @@ public abstract class Plugin {
timeOfLastDraw = nowTime;
}
void _lateDraw() {
long nowTime = System.currentTimeMillis();
LateDraw(nowTime - timeOfLastLateDraw);
timeOfLastLateDraw = nowTime;
}
/**
* Draw() is called by the client rendering loop so that plugins can draw information onto the screen.
* This will be called once per frame, meaning it is framerate bound.
@ -31,6 +38,14 @@ public abstract class Plugin {
*/
public void Draw(long timeDelta) {}
/**
* LateDraw() is called at the end of a finalized frame
* This will be called once per frame, meaning it is framerate bound.
* @param timeDelta the time (ms) elapsed since the last draw call.
*/
public void LateDraw(long timeDelta) {}
/**
* Init() is called when the plugin is first loaded
*/

View file

@ -160,6 +160,11 @@ public class PluginRepository {
pluginsSnapshot.forEach(Plugin::_draw);
}
public static void LateDraw() {
List<Plugin> pluginsSnapshot = new ArrayList<>(loadedPlugins.values());
pluginsSnapshot.forEach(Plugin::_lateDraw);
}
public static void NPCOverheadDraw(Npc npc, int screenX, int screenY) {
loadedPlugins.values().forEach((plugin) -> plugin.NPCOverheadDraw(npc, screenX, screenY));
}

View file

@ -12,7 +12,7 @@ import java.awt.*;
public abstract class FrameBuffer {
@OriginalMember(owner = "client!vk", name = "e", descriptor = "[I")
protected int[] pixels;
public int[] pixels;
@OriginalMember(owner = "client!vk", name = "g", descriptor = "Ljava/awt/Image;")
protected Image image;

View file

@ -9,6 +9,7 @@ import org.openrs2.deob.annotation.OriginalMember;
import org.openrs2.deob.annotation.Pc;
import java.awt.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.nio.charset.StandardCharsets;
@ -24,6 +25,11 @@ public final class GlRenderer {
public static float hFOV = 0;
private static ByteBuffer pixelByteBuffer;
private static IntBuffer pixelIntBuffer;
public static int[] pixelData;
@OriginalMember(owner = "client!tf", name = "c", descriptor = "F")
private static float aFloat30;
@ -200,10 +206,34 @@ public final class GlRenderer {
public static void swapBuffers() {
try {
drawable.swapBuffers();
readPixels();
} catch (@Pc(3) Exception local3) {
}
}
public static void initializePixelBuffer(int width, int height) {
// Allocate ByteBuffer for BGRA pixels (4 bytes per pixel)
pixelByteBuffer = ByteBuffer.allocateDirect(width * height * 4).order(ByteOrder.nativeOrder());
pixelIntBuffer = pixelByteBuffer.asIntBuffer();
pixelData = new int[width * height];
}
public static void readPixels() {
// Ensure the pixel buffer is initialized with the correct size
if (pixelByteBuffer == null || pixelIntBuffer.capacity() != canvasWidth * canvasHeight) {
initializePixelBuffer(canvasWidth, canvasHeight);
}
// Read pixels into the direct ByteBuffer
gl.glReadPixels(0, 0, canvasWidth, canvasHeight, GL2.GL_BGRA,
GlRenderer.bigEndian ? GL2.GL_UNSIGNED_INT_8_8_8_8_REV : GL2.GL_UNSIGNED_BYTE,
pixelByteBuffer);
// Convert to int array if needed
pixelIntBuffer.rewind(); // Prepare the IntBuffer for reading
pixelIntBuffer.get(pixelData, 0, pixelData.length); // Transfer to pixelData array if necessary
}
@OriginalMember(owner = "client!tf", name = "a", descriptor = "(Z)V")
public static void setFogEnabled(@OriginalArg(0) boolean enabled) {
if (enabled == fogEnabled) {

View file

@ -921,6 +921,7 @@ public final class client extends GameShell {
Preferences.safeMode = false;
Preferences.write(GameShell.signLink);
}
PluginRepository.LateDraw();
}
@OriginalMember(owner = "client!client", name = "c", descriptor = "(B)V")

View file

@ -0,0 +1,166 @@
package KondoKit
import KondoKit.plugin.Companion.FIXED_HEIGHT
import KondoKit.plugin.Companion.FIXED_WIDTH
import plugin.api.API.IsHD
import rt4.GameShell.canvas
import rt4.GlRenderer
import rt4.SoftwareRaster
import java.awt.*
import java.awt.event.*
import java.awt.geom.AffineTransform
import java.awt.image.AffineTransformOp
import java.awt.image.BufferedImage
import java.awt.image.VolatileImage
class AltCanvas : Canvas() {
private var gameImage: VolatileImage? = null
private var op: AffineTransformOp? = null
private var transform: AffineTransform? = null
private var flippedImage: BufferedImage? = null
private var bufferImage = BufferedImage(FIXED_WIDTH, FIXED_HEIGHT, BufferedImage.TYPE_INT_BGR)
private var lastImageWidth = -1
private var lastImageHeight = -1
init {
isFocusable = true
requestFocusInWindow()
addMouseListener(object : MouseAdapter() {
override fun mousePressed(e: MouseEvent) = relayMouseEvent(e)
override fun mouseReleased(e: MouseEvent) = relayMouseEvent(e)
override fun mouseClicked(e: MouseEvent) = relayMouseEvent(e)
})
addMouseMotionListener(object : MouseMotionAdapter() {
override fun mouseMoved(e: MouseEvent) = relayMouseEvent(e)
override fun mouseDragged(e: MouseEvent) = relayMouseEvent(e)
})
addKeyListener(object : KeyAdapter() {
override fun keyPressed(e: KeyEvent) = relayKeyEvent { it.keyPressed(e) }
override fun keyReleased(e: KeyEvent) = relayKeyEvent { it.keyReleased(e) }
override fun keyTyped(e: KeyEvent) = relayKeyEvent { it.keyTyped(e) }
})
addMouseWheelListener(MouseWheelListener { e -> relayMouseWheelEvent(e) })
}
override fun update(g: Graphics) = paint(g)
override fun addNotify() {
super.addNotify()
createBufferStrategy(2) // Double-buffering
validateGameImage()
}
private fun validateGameImage() {
val gc = GraphicsEnvironment.getLocalGraphicsEnvironment().defaultScreenDevice.defaultConfiguration
gameImage?.let {
when (it.validate(gc)) {
VolatileImage.IMAGE_INCOMPATIBLE -> createGameImage(gc)
VolatileImage.IMAGE_RESTORED -> renderGameImage()
}
} ?: createGameImage(gc)
}
private fun createGameImage(gc: GraphicsConfiguration) {
gameImage = gc.createCompatibleVolatileImage(FIXED_WIDTH, FIXED_HEIGHT, Transparency.OPAQUE)
renderGameImage()
}
private fun renderGameImage() {
gameImage?.createGraphics()?.apply {
color = Color.DARK_GRAY
fillRect(0, 0, gameImage!!.width, gameImage!!.height)
color = Color.BLACK
drawString("KondoKit Scaled Fixed Canvas", 20, 20)
dispose()
}
}
override fun paint(g: Graphics) {
bufferStrategy?.let { strategy ->
val g2d = strategy.drawGraphics as? Graphics2D ?: return
g2d.color = Color.BLACK
g2d.fillRect(0, 0, width, height)
gameImage?.let { image ->
val scale = minOf(width.toDouble() / image.width, height.toDouble() / image.height)
val x = ((width - image.width * scale) / 2).toInt()
val y = ((height - image.height * scale) / 2).toInt()
g2d.drawImage(image, x, y, (image.width * scale).toInt(), (image.height * scale).toInt(), null)
}
g2d.dispose() // Release the graphics context
strategy.show() // Display the buffer
}
}
private fun relayMouseEvent(e: MouseEvent) {
requestFocusInWindow()
val scale = minOf(width.toDouble() / gameImage!!.width, height.toDouble() / gameImage!!.height)
val xOffset = ((width - gameImage!!.width * scale) / 2)
val yOffset = ((height - gameImage!!.height * scale) / 2)
val adjustedX = ((e.x - xOffset) / scale).toInt().coerceIn(0, gameImage!!.width - 1)
val adjustedY = ((e.y - yOffset) / scale).toInt().coerceIn(0, gameImage!!.height - 1)
canvas.dispatchEvent(MouseEvent(this, e.id, e.`when`, e.modifiersEx, adjustedX, adjustedY, e.clickCount, e.isPopupTrigger, e.button))
}
private fun relayKeyEvent(action: (KeyListener) -> Unit) {
for (listener in canvas.keyListeners) action(listener)
}
private fun relayMouseWheelEvent(e: MouseWheelEvent) {
val scale = minOf(width.toDouble() / gameImage!!.width, height.toDouble() / gameImage!!.height)
val xOffset = ((width - gameImage!!.width * scale) / 2)
val yOffset = ((height - gameImage!!.height * scale) / 2)
val adjustedX = ((e.x - xOffset) / scale).toInt().coerceIn(0, gameImage!!.width - 1)
val adjustedY = ((e.y - yOffset) / scale).toInt().coerceIn(0, gameImage!!.height - 1)
canvas.dispatchEvent(MouseWheelEvent(this, e.id, e.`when`, e.modifiersEx, adjustedX, adjustedY, e.clickCount, e.isPopupTrigger, e.scrollType, e.scrollAmount, e.wheelRotation))
}
fun updateGameImage() {
if (IsHD()) renderGlRaster() else renderSoftwareRaster()
repaint()
}
private fun renderGlRaster() {
val width = gameImage!!.width
val height = gameImage!!.height
bufferImage.setRGB(0, 0, width, height, GlRenderer.pixelData, 0, width)
if (width != lastImageWidth || height != lastImageHeight) {
transform = AffineTransform.getScaleInstance(1.0, -1.0).apply {
translate(0.0, -height.toDouble())
}
op = AffineTransformOp(transform, AffineTransformOp.TYPE_NEAREST_NEIGHBOR)
flippedImage = BufferedImage(width, height, bufferImage.type)
lastImageWidth = width
lastImageHeight = height
}
op!!.filter(bufferImage, flippedImage)
gameImage?.createGraphics()?.apply {
drawImage(flippedImage, 0, 0, null)
dispose()
}
}
private fun renderSoftwareRaster() {
gameImage?.createGraphics()?.apply {
SoftwareRaster.frameBuffer.draw(this)
dispose()
}
}
}

View file

@ -68,7 +68,7 @@ object Helpers {
// Adjust for parent component location if it exists
if (parentComponent != null) {
if (parentComponent != null && GameShell.canvas.isShowing) {
val parentLocation = parentComponent.locationOnScreen
val x = parentLocation.x
val y = GameShell.canvas.locationOnScreen.y
@ -95,10 +95,8 @@ object Helpers {
}
fun convertToColor(value: String): Color {
val color = Color.decode(value) // Assumes value is in format "#RRGGBB" or "0xRRGGBB"
return color
private fun convertToColor(value: String): Color {
return Color.decode(value)
}
fun colorToHex(color: Color): String {
@ -134,11 +132,7 @@ object Helpers {
class FieldNotifier(private val plugin: Any) {
private val observers = mutableListOf<FieldObserver>()
fun addObserver(observer: FieldObserver) {
observers.add(observer)
}
fun notifyFieldChange(field: Field, newValue: Any?) {
private fun notifyFieldChange(field: Field, newValue: Any?) {
for (observer in observers) {
observer.onFieldChange(field, newValue)
}

View file

@ -7,9 +7,7 @@ import KondoKit.Helpers.formatHtmlLabelText
import KondoKit.Helpers.getSpriteId
import KondoKit.Helpers.showToast
import KondoKit.SpriteToBufferedImage.getBufferedImageFromSprite
import KondoKit.plugin.Companion.POPUP_BACKGROUND
import KondoKit.plugin.Companion.POPUP_FOREGROUND
import KondoKit.plugin.Companion.TITLE_BAR_COLOR
import KondoKit.plugin.Companion.TOOLTIP_BACKGROUND
import KondoKit.plugin.Companion.VIEW_BACKGROUND_COLOR
import KondoKit.plugin.Companion.WIDGET_COLOR
@ -77,8 +75,8 @@ object HiscoresView {
private var cursorVisible: Boolean = true
private val gson = Gson()
val bufferedImageSprite = getBufferedImageFromSprite(API.GetSprite(Constants.MAG_SPRITE))
val imageCanvas = bufferedImageSprite.let {
private val bufferedImageSprite = getBufferedImageFromSprite(API.GetSprite(Constants.MAG_SPRITE))
private val imageCanvas = bufferedImageSprite.let {
ImageCanvas(it).apply {
preferredSize = Constants.ICON_DIMENSION_SMALL
size = preferredSize
@ -112,8 +110,10 @@ object HiscoresView {
} else {
text += e.keyChar
}
SwingUtilities.invokeLater {
repaint()
}
}
override fun keyPressed(e: KeyEvent) {
if (e.isControlDown) {
when (e.keyCode) {
@ -125,26 +125,32 @@ object HiscoresView {
val clipboard = Toolkit.getDefaultToolkit().systemClipboard
val pasteText = clipboard.getData(DataFlavor.stringFlavor) as String
text += pasteText
SwingUtilities.invokeLater {
repaint()
}
}
}
}
}
})
addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
if (e.x > width - 20 && e.y < 20) {
text = ""
SwingUtilities.invokeLater {
repaint()
}
}
}
})
Timer(500) {
cursorVisible = !cursorVisible
if(focusedView == VIEW_NAME)
SwingUtilities.invokeLater {
repaint()
}
}.start()
}
@ -235,7 +241,7 @@ object HiscoresView {
playerNameLabel?.revalidate()
playerNameLabel?.repaint()
if(data == null) return;
if(data == null) return
playerNameLabel?.removeAll()
@ -319,10 +325,6 @@ object HiscoresView {
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) {
@ -437,7 +439,7 @@ object HiscoresView {
minimumSize = preferredSize
}
val bufferedImageSprite = getBufferedImageFromSprite(API.GetSprite(Constants.LVL_BAR_SPRITE));
val bufferedImageSprite = getBufferedImageFromSprite(API.GetSprite(Constants.LVL_BAR_SPRITE))
val totalLevelIcon = ImageCanvas(bufferedImageSprite).apply {
fillColor = COLOR_BACKGROUND_DARK
@ -486,7 +488,7 @@ object HiscoresView {
hiscorePanel.add(totalCombatPanel)
hiscorePanel.add(Box.createVerticalStrut(10))
hiScoreView = hiscorePanel;
hiScoreView = hiscorePanel
}
data class HiscoresResponse(

View file

@ -4,7 +4,6 @@ import KondoKit.Helpers.addMouseListenerToAll
import KondoKit.Helpers.formatHtmlLabelText
import KondoKit.SpriteToBufferedImage.getBufferedImageFromSprite
import KondoKit.XPTrackerView.wrappedWidget
import KondoKit.plugin.Companion.IMAGE_SIZE
import KondoKit.plugin.Companion.POPUP_BACKGROUND
import KondoKit.plugin.Companion.POPUP_FOREGROUND
import KondoKit.plugin.Companion.TITLE_BAR_COLOR
@ -33,15 +32,15 @@ import kotlin.math.ceil
object LootTrackerView {
private const val SNAPSHOT_LIFESPAN = 10
const val BAG_ICON = 900;
const val BAG_ICON = 900
val npcDeathSnapshots = mutableMapOf<Int, GroundSnapshot>()
var gePriceMap = loadGEPrices()
const val VIEW_NAME = "LOOT_TRACKER_VIEW";
const val VIEW_NAME = "LOOT_TRACKER_VIEW"
private val lootItemPanels = mutableMapOf<String, MutableMap<Int, Int>>()
private val npcKillCounts = mutableMapOf<String, Int>()
private var totalTrackerWidget: XPWidget? = null
var lastConfirmedKillNpcId = -1
var customToolTipWindow: JWindow? = null
private var customToolTipWindow: JWindow? = null
var lootTrackerView: JPanel? = null
fun loadGEPrices(): Map<String, String> {
@ -284,7 +283,7 @@ object LootTrackerView {
// Function to show the custom tooltip
fun showCustomToolTip(location: Point, itemId: Int, quantity: Int, parentComponent: ImageCanvas) {
var itemDef = ObjTypeList.get(itemId)
val itemDef = ObjTypeList.get(itemId)
val gePricePerItem = gePriceMap[itemDef.id.toString()]?.toInt() ?: 0
val totalGePrice = gePricePerItem * quantity
val totalHaPrice = itemDef.cost * quantity
@ -351,6 +350,7 @@ object LootTrackerView {
?.apply {
val newValue = (getClientProperty("val") as? Int ?: 0) + valueOfNewDrops.toInt()
text = "${formatValue(newValue)} gp"
foreground = primaryColor
putClientProperty("val", newValue)
revalidate()
if(focusedView == VIEW_NAME)
@ -382,7 +382,7 @@ object LootTrackerView {
if (newDrops.isNotEmpty()) {
val npcName = NpcTypeList.get(npcId).name
lastConfirmedKillNpcId = npcId;
lastConfirmedKillNpcId = npcId
handleNewDrops(npcName.toString(), newDrops, lootTrackerView)
toRemove.add(npcId)
} else if (snapshot.age >= SNAPSHOT_LIFESPAN) {
@ -501,7 +501,7 @@ object LootTrackerView {
return childFramePanel
}
fun removeLootFrameMenu(toRemove: JPanel, npcName: String): JPopupMenu {
private fun removeLootFrameMenu(toRemove: JPanel, npcName: String): JPopupMenu {
// Create a popup menu
val popupMenu = JPopupMenu()
val rFont = Font("RuneScape Small", Font.TRUETYPE_FONT, 16)
@ -538,7 +538,7 @@ object LootTrackerView {
}
fun resetLootTrackerMenu(): JPopupMenu {
private fun resetLootTrackerMenu(): JPopupMenu {
// Create a popup menu
val popupMenu = JPopupMenu()
val rFont = Font("RuneScape Small", Font.TRUETYPE_FONT, 16)

View file

@ -30,7 +30,7 @@ import kotlin.math.ceil
object ReflectiveEditorView {
var reflectiveEditorView: JPanel? = null
val loadedPlugins: MutableList<String> = mutableListOf()
private val loadedPlugins: MutableList<String> = mutableListOf()
const val VIEW_NAME = "REFLECTIVE_EDITOR_VIEW"
fun createReflectiveEditorView() {
val reflectiveEditorPanel = JPanel(BorderLayout())
@ -266,7 +266,7 @@ object ReflectiveEditorView {
}
}
else {
loadedPlugins.add(plugin.javaClass.`package`.name);
loadedPlugins.add(plugin.javaClass.`package`.name)
}
}

View file

@ -9,6 +9,7 @@ import java.awt.Rectangle
import java.awt.event.*
import java.util.*
import javax.swing.JPanel
import javax.swing.SwingUtilities
class ScrollablePanel(private val content: JPanel) : JPanel() {
private var lastMouseY = 0
@ -83,6 +84,7 @@ class ScrollablePanel(private val content: JPanel) : JPanel() {
}
private fun handleResize() {
SwingUtilities.invokeLater{
// Ensure the ScrollablePanel resizes with the frame
bounds = Rectangle(0, 0, 242, frame.height)
@ -99,6 +101,7 @@ class ScrollablePanel(private val content: JPanel) : JPanel() {
revalidate()
repaint()
}
}
private fun scrollContent(deltaY: Int) {
if (!showScrollbar) {
@ -106,7 +109,7 @@ class ScrollablePanel(private val content: JPanel) : JPanel() {
content.setLocation(0, currentOffsetY)
return
}
SwingUtilities.invokeLater {
currentOffsetY += deltaY
// Apply buffer to maxOffset
@ -120,11 +123,12 @@ class ScrollablePanel(private val content: JPanel) : JPanel() {
val scrollableRatio = viewHeight.toDouble() / contentHeight
scrollbarY = ((-currentOffsetY / contentHeight.toDouble()) * viewHeight).toInt()
scrollbarHeight = (viewHeight * scrollableRatio).toInt().coerceAtLeast(20)
repaint()
}
}
private fun updateScrollbar() {
SwingUtilities.invokeLater {
showScrollbar = content.height > frame.height
val contentHeight = content.height
@ -138,9 +142,9 @@ class ScrollablePanel(private val content: JPanel) : JPanel() {
scrollbarY = 0
scrollbarHeight = 0
}
repaint()
}
}
override fun paintComponent(g: Graphics) {
super.paintComponent(g)

View file

@ -69,7 +69,7 @@ object SpriteToBufferedImage {
* @param brightnessBoost A multiplier to boost the brightness of the image.
* @return The BufferedImage created from the sprite.
*/
fun convertToBufferedImage(
private fun convertToBufferedImage(
sprite: BaseSprite,
tint: Color? = null,
grayscale: Boolean = false,
@ -88,7 +88,7 @@ object SpriteToBufferedImage {
for (y in 0 until height) {
for (x in 0 until width) {
val index = pixels[y * width + x].toInt() and 0xFF
var color = palette[index]
val color = palette[index]
// Apply grayscale or tint if provided
val finalColor = if (grayscale) {
@ -109,7 +109,7 @@ object SpriteToBufferedImage {
// Manually set pixels directly
for (y in 0 until height) {
for (x in 0 until width) {
var color = pixels[y * width + x]
val color = pixels[y * width + x]
// Apply grayscale or tint if provided
val finalColor = if (grayscale) {
@ -137,7 +137,7 @@ object SpriteToBufferedImage {
* @param brightnessBoost A multiplier to boost the brightness of the image.
* @return The tinted color.
*/
fun applyTint(original: Color, tint: Color, brightnessBoost: Float): Color {
private fun applyTint(original: Color, tint: Color, brightnessBoost: Float): Color {
val boostedColor = applyBrightness(original, brightnessBoost)
val r = (boostedColor.red * tint.red / 255).coerceIn(0, 255)
val g = (boostedColor.green * tint.green / 255).coerceIn(0, 255)
@ -152,7 +152,7 @@ object SpriteToBufferedImage {
* @param factor The multiplier to boost the brightness.
* @return The color with boosted brightness.
*/
fun applyBrightness(original: Color, factor: Float): Color {
private fun applyBrightness(original: Color, factor: Float): Color {
val r = (original.red * factor).coerceIn(0.0f, 255.0f).toInt()
val g = (original.green * factor).coerceIn(0.0f, 255.0f).toInt()
val b = (original.blue * factor).coerceIn(0.0f, 255.0f).toInt()
@ -166,7 +166,7 @@ object SpriteToBufferedImage {
* @param brightnessBoost A multiplier to boost the brightness.
* @return The grayscale version of the color with boosted brightness.
*/
fun applyGrayscale(original: Color, brightnessBoost: Float): Color {
private fun applyGrayscale(original: Color, brightnessBoost: Float): Color {
// Calculate the grayscale value using the luminosity method
val grayValue = (0.3 * original.red + 0.59 * original.green + 0.11 * original.blue).toInt()
val boostedGray = (grayValue * brightnessBoost).coerceIn(0.0f, 255.0f).toInt()

View file

@ -6,9 +6,9 @@ import rt4.Node
object XPTable {
const val MAX_LEVEL = 99
const val INVALID_LEVEL = -1
const val SKILLS_XP_TABLE = 716
private const val MAX_LEVEL = 99
private const val INVALID_LEVEL = -1
private const val SKILLS_XP_TABLE = 716
private var xpTable: MutableList<Int> = mutableListOf()

View file

@ -34,14 +34,14 @@ import plugin.Plugin
import plugin.api.*
import plugin.api.API.*
import plugin.api.FontColor.fromColor
import rt4.GameShell
import rt4.*
import rt4.DisplayMode
import rt4.GameShell.canvas
import rt4.GameShell.frame
import rt4.GlRenderer
import rt4.InterfaceList
import rt4.Player
import rt4.client.js5Archive8
import rt4.client.mainLoadState
import java.awt.*
import java.awt.Font
import java.awt.event.ActionListener
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
@ -91,7 +91,11 @@ class plugin : Plugin() {
"perfectly snapped to the edge of the game due to window chrome you can update this to fix it")
var uiOffset = 0
private const val FIXED_WIDTH = 765
@Exposed("Stretched/Scaled Fixed Mode Support")
var useScaledFixed = false
const val FIXED_WIDTH = 765
const val FIXED_HEIGHT = 503
private const val NAVBAR_WIDTH = 30
private const val MAIN_CONTENT_WIDTH = 242
private const val WRENCH_ICON = 907
@ -103,14 +107,16 @@ class plugin : Plugin() {
private var rightPanelWrapper: JScrollPane? = null
private var accumulatedTime = 0L
private var reloadInterfaces = false
private const val tickInterval = 600L
private const val TICK_INTERVAL = 600L
private var pluginsReloaded = false
private var loginScreen = 160
private var lastLogin = ""
private var initialized = false
private var lastClickTime = 0L
private var lastUIOffset = 0
private var themeName = "RUNELITE"
private const val HIDDEN_VIEW = "HIDDEN"
private var altCanvas: AltCanvas? = null
private val drawActions = mutableListOf<() -> Unit>()
fun registerDrawAction(action: () -> Unit) {
@ -120,24 +126,11 @@ class plugin : Plugin() {
}
}
fun allSpritesLoaded() : Boolean {
// Check all skill sprites
try{
for (i in 0 until 24) {
if(!js5Archive8.isFileReady(getSpriteId(i))){
return false
}
}
val otherIcons = arrayOf(LVL_ICON, MAG_SPRITE, LOOT_ICON, WRENCH_ICON, COMBAT_LVL_SPRITE, BAG_ICON)
for (icon in otherIcons) {
if(!js5Archive8.isFileReady(icon)){
return false
}
}
} catch (e : Exception){
return false
}
return true
override fun Init() {
// Disable Font AA
System.setProperty("sun.java2d.opengl", "false")
System.setProperty("awt.useSystemAAFontSettings", "off")
System.setProperty("swing.aatext", "false")
}
override fun OnLogin() {
@ -149,55 +142,6 @@ class plugin : Plugin() {
lastLogin = Player.usernameInput.toString()
}
override fun Init() {
// Disable Font AA
System.setProperty("sun.java2d.opengl", "false")
System.setProperty("awt.useSystemAAFontSettings", "off")
System.setProperty("swing.aatext", "false")
}
private fun UpdateDisplaySettings() {
val mode = GetWindowMode()
val currentScrollPaneWidth = if (mainContentPanel.isVisible) NAVBAR_WIDTH + MAIN_CONTENT_WIDTH else NAVBAR_WIDTH
lastUIOffset = uiOffset
when (mode) {
WindowMode.FIXED -> {
if (frame.width < FIXED_WIDTH + currentScrollPaneWidth + uiOffset) {
frame.setSize(FIXED_WIDTH + currentScrollPaneWidth + uiOffset, frame.height)
}
val difference = frame.width - (FIXED_WIDTH + uiOffset + currentScrollPaneWidth)
GameShell.leftMargin = difference / 2
}
WindowMode.RESIZABLE -> {
GameShell.canvasWidth = frame.width - (currentScrollPaneWidth + uiOffset)
}
}
rightPanelWrapper?.preferredSize = Dimension(currentScrollPaneWidth, frame.height)
rightPanelWrapper?.revalidate()
rightPanelWrapper?.repaint()
}
fun OnKondoValueUpdated(){
StoreData("kondoUseRemoteGE", useLiveGEPrices)
StoreData("kondoTheme", theme.toString())
if(appliedTheme != theme) {
showAlert(
"KondoKit Theme changes require a relaunch.",
"KondoKit",
JOptionPane.INFORMATION_MESSAGE
)
}
StoreData("kondoPlayerXPMultiplier", playerXPMultiplier)
LootTrackerView.gePriceMap = LootTrackerView.loadGEPrices()
StoreData("kondoLaunchMinimized", launchMinimized)
StoreData("kondoUIOffset", uiOffset)
if(lastUIOffset != uiOffset){
UpdateDisplaySettings()
reloadInterfaces = true
}
}
override fun OnMiniMenuCreate(currentEntries: Array<out MiniMenuEntry>?) {
if (currentEntries != null) {
for ((index, entry) in currentEntries.withIndex()) {
@ -218,28 +162,13 @@ class plugin : Plugin() {
}
}
private fun searchHiscore(username: String): Runnable {
return Runnable {
setActiveView(HiscoresView.VIEW_NAME)
val customSearchField = hiScoreView?.let { HiscoresView.CustomSearchField(it) }
customSearchField?.searchPlayer(username) ?: run {
println("searchView is null or CustomSearchField creation failed.")
}
}
}
override fun OnPluginsReloaded(): Boolean {
if (!initialized) return true
UpdateDisplaySettings()
updateDisplaySettings()
frame.remove(rightPanelWrapper)
frame.layout = BorderLayout()
frame.add(rightPanelWrapper, BorderLayout.EAST)
rightPanelWrapper?.let { frame.add(it, BorderLayout.EAST) }
frame.revalidate()
frame.repaint()
pluginsReloaded = true
reloadInterfaces = true
return true
@ -250,9 +179,7 @@ class plugin : Plugin() {
initialXP[skillId] = xp
return
}
var xpWidget = xpWidgets[skillId]
if (xpWidget != null) {
updateWidget(xpWidget, xp)
} else {
@ -265,9 +192,10 @@ class plugin : Plugin() {
xpTrackerView?.add(wrappedWidget(xpWidget.container))
xpTrackerView?.add(Box.createVerticalStrut(5))
if(focusedView == XPTrackerView.VIEW_NAME) {
xpTrackerView?.revalidate()
if(focusedView == XPTrackerView.VIEW_NAME)
xpTrackerView?.repaint()
}
updateWidget(xpWidget, xp)
}
@ -290,7 +218,7 @@ class plugin : Plugin() {
}
accumulatedTime += timeDelta
if (accumulatedTime >= tickInterval) {
if (accumulatedTime >= TICK_INTERVAL) {
lootTrackerView?.let { onPostClientTick(it) }
accumulatedTime = 0L
}
@ -312,18 +240,373 @@ class plugin : Plugin() {
}
}
override fun LateDraw(timeDelta: Long) {
if (!initialized) return
if(GameShell.fullScreenFrame != null) {
DisplayMode.setWindowMode(true, 0, FIXED_WIDTH, FIXED_HEIGHT)
showAlert("Fullscreen is not supported by KondoKit. Disable the plugin first.",
"Error",
JOptionPane.INFORMATION_MESSAGE
)
return
}
if(!useScaledFixed) return
if(GetWindowMode() == WindowMode.FIXED){
moveAltCanvasToFront()
} else {
moveCanvasToFront()
}
altCanvas?.updateGameImage() // Update the game image as needed
}
override fun Update() {
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.container.repaint()
}
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.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 allSpritesLoaded() : Boolean {
// Check all skill sprites
try{
for (i in 0 until 24) {
if(!js5Archive8.isFileReady(getSpriteId(i))){
return false
}
}
val otherIcons = arrayOf(LVL_ICON, MAG_SPRITE, LOOT_ICON, WRENCH_ICON, COMBAT_LVL_SPRITE, BAG_ICON)
for (icon in otherIcons) {
if(!js5Archive8.isFileReady(icon)){
return false
}
}
} catch (e : Exception){
return false
}
return true
}
private fun initAltCanvas(){
if (frame != null) {
altCanvas = AltCanvas().apply {
preferredSize = Dimension(FIXED_WIDTH, FIXED_HEIGHT)
}
altCanvas?.let { frame.add(it) }
moveAltCanvasToFront()
frame.setComponentZOrder(rightPanelWrapper, 2)
}
updateDisplaySettings()
}
private fun updateDisplaySettings() {
val mode = GetWindowMode()
val currentScrollPaneWidth = if (mainContentPanel.isVisible) NAVBAR_WIDTH + MAIN_CONTENT_WIDTH else NAVBAR_WIDTH
lastUIOffset = uiOffset
when (mode) {
WindowMode.FIXED -> {
if (frame.width < FIXED_WIDTH + currentScrollPaneWidth + uiOffset) {
frame.setSize(FIXED_WIDTH + currentScrollPaneWidth + uiOffset, frame.height)
}
val difference = frame.width - (uiOffset + currentScrollPaneWidth)
if (useScaledFixed) {
GameShell.leftMargin = 0
val canvasWidth = difference + uiOffset / 2
val canvasHeight = frame.height - canvas.y // Restricting height to frame height
altCanvas?.size = Dimension(canvasWidth, canvasHeight)
altCanvas?.setLocation(0, canvas.y)
canvas.setLocation(0, canvas.y)
} else {
val difference = frame.width - (FIXED_WIDTH + uiOffset + currentScrollPaneWidth)
GameShell.leftMargin = difference / 2
}
}
WindowMode.RESIZABLE -> {
GameShell.canvasWidth = frame.width - (currentScrollPaneWidth + uiOffset)
}
}
rightPanelWrapper?.preferredSize = Dimension(currentScrollPaneWidth, frame.height)
rightPanelWrapper?.isDoubleBuffered = true
rightPanelWrapper?.revalidate()
rightPanelWrapper?.repaint()
}
fun OnKondoValueUpdated(){
StoreData("kondoUseRemoteGE", useLiveGEPrices)
StoreData("kondoTheme", theme.toString())
if(appliedTheme != theme) {
showAlert(
"KondoKit Theme changes require a relaunch.",
"KondoKit",
JOptionPane.INFORMATION_MESSAGE
)
}
StoreData("kondoPlayerXPMultiplier", playerXPMultiplier)
LootTrackerView.gePriceMap = LootTrackerView.loadGEPrices()
StoreData("kondoLaunchMinimized", launchMinimized)
StoreData("kondoUIOffset", uiOffset)
StoreData("kondoScaledFixed", useScaledFixed)
if(altCanvas == null && useScaledFixed){
initAltCanvas()
} else if(altCanvas != null && !useScaledFixed){
destroyAltCanvas()
}
if(lastUIOffset != uiOffset){
updateDisplaySettings()
reloadInterfaces = true
}
}
private fun destroyAltCanvas(){
moveCanvasToFront()
frame.remove(altCanvas)
altCanvas = null
updateDisplaySettings()
}
private fun searchHiscore(username: String): Runnable {
return Runnable {
setActiveView(HiscoresView.VIEW_NAME)
val customSearchField = hiScoreView?.let { HiscoresView.CustomSearchField(it) }
customSearchField?.searchPlayer(username) ?: run {
println("searchView is null or CustomSearchField creation failed.")
}
}
}
private fun moveAltCanvasToFront(){
if(altCanvas == null) return
frame.setComponentZOrder(canvas, 2)
frame.setComponentZOrder(altCanvas, 1)
frame.setComponentZOrder(rightPanelWrapper, 0)
}
private fun moveCanvasToFront(){
if(altCanvas == null) return
frame.setComponentZOrder(altCanvas, 2)
frame.setComponentZOrder(canvas, 1)
frame.setComponentZOrder(rightPanelWrapper, 0)
}
private fun restoreSettings(){
themeName = (GetData("kondoTheme") as? String) ?: "RUNELITE"
useLiveGEPrices = (GetData("kondoUseRemoteGE") as? Boolean) ?: true
playerXPMultiplier = (GetData("kondoPlayerXPMultiplier") as? Int) ?: 5
val osName = System.getProperty("os.name").toLowerCase()
uiOffset = (GetData("kondoUIOffset") as? Int) ?: if (osName.contains("win")) 16 else 0
launchMinimized = (GetData("kondoLaunchMinimized") as? Boolean) ?: false
useScaledFixed = (GetData("kondoScaledFixed") as? Boolean) ?: false
}
private fun initKondoUI(){
DrawText(FontType.LARGE, fromColor(Color(16777215)), TextModifier.CENTER, "KondoKit Loading Sprites...", GameShell.canvasWidth/2, GameShell.canvasHeight/2)
if(!allSpritesLoaded()) return;
if(!allSpritesLoaded()) return
val frame: Frame? = GameShell.frame
if (frame != null) {
loadFont()
val themeIndex = (GetData("kondoTheme") as? String) ?: "RUNELITE"
theme = ThemeType.valueOf(themeIndex)
restoreSettings()
theme = ThemeType.valueOf(themeName)
applyTheme(getTheme(theme))
appliedTheme = theme
configureLookAndFeel()
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!!), XPTrackerView.VIEW_NAME)
mainContentPanel.add(ScrollablePanel(hiScoreView!!), HiscoresView.VIEW_NAME)
mainContentPanel.add(ScrollablePanel(lootTrackerView!!), LootTrackerView.VIEW_NAME)
mainContentPanel.add(ScrollablePanel(reflectiveEditorView!!), ReflectiveEditorView.VIEW_NAME)
val navPanel = Panel().apply {
layout = BoxLayout(this, BoxLayout.Y_AXIS)
background = WIDGET_COLOR
preferredSize = Dimension(NAVBAR_WIDTH, frame.height)
}
navPanel.add(createNavButton(LVL_ICON, XPTrackerView.VIEW_NAME))
navPanel.add(createNavButton(MAG_SPRITE, HiscoresView.VIEW_NAME))
navPanel.add(createNavButton(LOOT_ICON, LootTrackerView.VIEW_NAME))
navPanel.add(createNavButton(WRENCH_ICON, ReflectiveEditorView.VIEW_NAME))
val rightPanel = Panel(BorderLayout()).apply {
add(mainContentPanel, BorderLayout.CENTER)
add(navPanel, BorderLayout.EAST)
}
rightPanelWrapper = JScrollPane(rightPanel).apply {
preferredSize = Dimension(NAVBAR_WIDTH + MAIN_CONTENT_WIDTH, frame.height)
background = VIEW_BACKGROUND_COLOR
border = BorderFactory.createEmptyBorder()
horizontalScrollBarPolicy = JScrollPane.HORIZONTAL_SCROLLBAR_NEVER
verticalScrollBarPolicy = JScrollPane.VERTICAL_SCROLLBAR_NEVER
}
frame.layout = BorderLayout()
rightPanelWrapper?.let {
frame.add(it, BorderLayout.EAST)
}
if(launchMinimized){
setActiveView(HIDDEN_VIEW)
} else {
setActiveView(XPTrackerView.VIEW_NAME)
}
if(useScaledFixed) {
initAltCanvas()
}
initialized = true
pluginsReloaded = true
}
}
private fun setActiveView(viewName: String) {
// Handle the visibility of the main content panel
if (viewName == HIDDEN_VIEW) {
mainContentPanel.isVisible = false
} else {
if (!mainContentPanel.isVisible) {
mainContentPanel.isVisible = true
}
cardLayout.show(mainContentPanel, viewName)
}
reloadInterfaces = true
updateDisplaySettings()
// Revalidate and repaint necessary panels
mainContentPanel.revalidate()
rightPanelWrapper?.revalidate()
frame?.revalidate()
mainContentPanel.repaint()
rightPanelWrapper?.repaint()
frame?.repaint()
focusedView = viewName
}
private fun createNavButton(spriteId: Int, viewName: String): JPanel {
val bufferedImageSprite = getBufferedImageFromSprite(GetSprite(spriteId), NAV_TINT, NAV_GREYSCALE, BOOST)
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
}
// 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
isOpaque = true
val gbc = GridBagConstraints().apply {
anchor = GridBagConstraints.CENTER
fill = GridBagConstraints.NONE // Prevents stretching
}
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 panelButton
}
private fun configureLookAndFeel(){
loadFont()
try {
UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel")
@ -377,211 +660,14 @@ class plugin : Plugin() {
UIManager.put("ToolTip.border", BorderFactory.createLineBorder(TITLE_BAR_COLOR))
// Update component tree UI to apply the new theme
SwingUtilities.updateComponentTreeUI(GameShell.frame)
SwingUtilities.updateComponentTreeUI(frame)
frame.background = Color.BLACK
} catch (e : Exception) {
e.printStackTrace()
}
// Restore saved values
useLiveGEPrices = (GetData("kondoUseRemoteGE") as? Boolean) ?: true
playerXPMultiplier = (GetData("kondoPlayerXPMultiplier") as? Int) ?: 5
val osName = System.getProperty("os.name").toLowerCase()
uiOffset = (GetData("kondoUIOffset") as? Int) ?: if (osName.contains("win")) 16 else 0
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!!), XPTrackerView.VIEW_NAME)
mainContentPanel.add(ScrollablePanel(hiScoreView!!), HiscoresView.VIEW_NAME)
mainContentPanel.add(ScrollablePanel(lootTrackerView!!), LootTrackerView.VIEW_NAME)
mainContentPanel.add(ScrollablePanel(reflectiveEditorView!!), ReflectiveEditorView.VIEW_NAME)
val navPanel = Panel().apply {
layout = BoxLayout(this, BoxLayout.Y_AXIS)
background = WIDGET_COLOR
preferredSize = Dimension(NAVBAR_WIDTH, frame.height)
}
navPanel.add(createNavButton(LVL_ICON, XPTrackerView.VIEW_NAME))
navPanel.add(createNavButton(MAG_SPRITE, HiscoresView.VIEW_NAME))
navPanel.add(createNavButton(LOOT_ICON, LootTrackerView.VIEW_NAME))
navPanel.add(createNavButton(WRENCH_ICON, ReflectiveEditorView.VIEW_NAME))
var rightPanel = Panel(BorderLayout()).apply {
add(mainContentPanel, BorderLayout.CENTER)
add(navPanel, BorderLayout.EAST)
}
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
verticalScrollBarPolicy = JScrollPane.VERTICAL_SCROLLBAR_NEVER
}
frame.layout = BorderLayout()
rightPanelWrapper?.let { frame.add(it, BorderLayout.EAST) }
if(!launchMinimized){
setActiveView(XPTrackerView.VIEW_NAME)
} else {
setActiveView(HIDDEN_VIEW)
}
initialized = true
pluginsReloaded = true
}
}
override fun Update() {
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.container.repaint()
}
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.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 setActiveView(viewName: String) {
// Handle the visibility of the main content panel
if (viewName == HIDDEN_VIEW) {
mainContentPanel.isVisible = false
} else {
if (!mainContentPanel.isVisible) {
mainContentPanel.isVisible = true
}
cardLayout.show(mainContentPanel, 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), NAV_TINT, NAV_GREYSCALE, BOOST)
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
}
// 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
isOpaque = true // Ensure background is painted
val gbc = GridBagConstraints().apply {
anchor = GridBagConstraints.CENTER
fill = GridBagConstraints.NONE // Prevents stretching
}
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 panelButton
}
fun loadFont(): Font? {
private fun loadFont(): Font? {
val fontStream = plugin::class.java.getResourceAsStream("res/runescape_small.ttf")
return if (fontStream != null) {
try {
@ -603,7 +689,7 @@ class plugin : Plugin() {
var focusedView: String = ""
}
fun applyTheme(theme: Theme) {
private fun applyTheme(theme: Theme) {
WIDGET_COLOR = theme.widgetColor
TITLE_BAR_COLOR = theme.titleBarColor
VIEW_BACKGROUND_COLOR = theme.viewBackgroundColor