diff --git a/client/src/main/java/plugin/Plugin.java b/client/src/main/java/plugin/Plugin.java index 7a4a039..e8e3641 100644 --- a/client/src/main/java/plugin/Plugin.java +++ b/client/src/main/java/plugin/Plugin.java @@ -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 */ diff --git a/client/src/main/java/plugin/PluginRepository.java b/client/src/main/java/plugin/PluginRepository.java index 2af27f6..55faf80 100644 --- a/client/src/main/java/plugin/PluginRepository.java +++ b/client/src/main/java/plugin/PluginRepository.java @@ -160,6 +160,11 @@ public class PluginRepository { pluginsSnapshot.forEach(Plugin::_draw); } + public static void LateDraw() { + List 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)); } diff --git a/client/src/main/java/rt4/FrameBuffer.java b/client/src/main/java/rt4/FrameBuffer.java index 5999c44..f641434 100644 --- a/client/src/main/java/rt4/FrameBuffer.java +++ b/client/src/main/java/rt4/FrameBuffer.java @@ -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; diff --git a/client/src/main/java/rt4/GlRenderer.java b/client/src/main/java/rt4/GlRenderer.java index b42e0de..92e4564 100644 --- a/client/src/main/java/rt4/GlRenderer.java +++ b/client/src/main/java/rt4/GlRenderer.java @@ -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) { diff --git a/client/src/main/java/rt4/client.java b/client/src/main/java/rt4/client.java index 34adc87..7a77445 100644 --- a/client/src/main/java/rt4/client.java +++ b/client/src/main/java/rt4/client.java @@ -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") diff --git a/plugin-playground/src/main/kotlin/KondoKit/AltCanvas.kt b/plugin-playground/src/main/kotlin/KondoKit/AltCanvas.kt new file mode 100644 index 0000000..af5c01d --- /dev/null +++ b/plugin-playground/src/main/kotlin/KondoKit/AltCanvas.kt @@ -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() + } + } +} \ No newline at end of file diff --git a/plugin-playground/src/main/kotlin/KondoKit/Helpers.kt b/plugin-playground/src/main/kotlin/KondoKit/Helpers.kt index bfcac22..4f6e3c4 100644 --- a/plugin-playground/src/main/kotlin/KondoKit/Helpers.kt +++ b/plugin-playground/src/main/kotlin/KondoKit/Helpers.kt @@ -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() - 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) } diff --git a/plugin-playground/src/main/kotlin/KondoKit/HiscoresView.kt b/plugin-playground/src/main/kotlin/KondoKit/HiscoresView.kt index ecdeed0..0b48ab3 100644 --- a/plugin-playground/src/main/kotlin/KondoKit/HiscoresView.kt +++ b/plugin-playground/src/main/kotlin/KondoKit/HiscoresView.kt @@ -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,7 +110,9 @@ object HiscoresView { } else { text += e.keyChar } - repaint() + SwingUtilities.invokeLater { + repaint() + } } override fun keyPressed(e: KeyEvent) { if (e.isControlDown) { @@ -125,7 +125,9 @@ object HiscoresView { val clipboard = Toolkit.getDefaultToolkit().systemClipboard val pasteText = clipboard.getData(DataFlavor.stringFlavor) as String text += pasteText - repaint() + SwingUtilities.invokeLater { + repaint() + } } } } @@ -136,7 +138,9 @@ object HiscoresView { override fun mouseClicked(e: MouseEvent) { if (e.x > width - 20 && e.y < 20) { text = "" - repaint() + SwingUtilities.invokeLater { + repaint() + } } } }) @@ -144,7 +148,9 @@ object HiscoresView { Timer(500) { cursorVisible = !cursorVisible if(focusedView == VIEW_NAME) - repaint() + 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( diff --git a/plugin-playground/src/main/kotlin/KondoKit/LootTrackerView.kt b/plugin-playground/src/main/kotlin/KondoKit/LootTrackerView.kt index 3a0fc14..cfdb4a2 100644 --- a/plugin-playground/src/main/kotlin/KondoKit/LootTrackerView.kt +++ b/plugin-playground/src/main/kotlin/KondoKit/LootTrackerView.kt @@ -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() var gePriceMap = loadGEPrices() - const val VIEW_NAME = "LOOT_TRACKER_VIEW"; + const val VIEW_NAME = "LOOT_TRACKER_VIEW" private val lootItemPanels = mutableMapOf>() private val npcKillCounts = mutableMapOf() private var totalTrackerWidget: XPWidget? = null var lastConfirmedKillNpcId = -1 - var customToolTipWindow: JWindow? = null + private var customToolTipWindow: JWindow? = null var lootTrackerView: JPanel? = null fun loadGEPrices(): Map { @@ -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) diff --git a/plugin-playground/src/main/kotlin/KondoKit/ReflectiveEditorView.kt b/plugin-playground/src/main/kotlin/KondoKit/ReflectiveEditorView.kt index 60deccf..5b6cfd2 100644 --- a/plugin-playground/src/main/kotlin/KondoKit/ReflectiveEditorView.kt +++ b/plugin-playground/src/main/kotlin/KondoKit/ReflectiveEditorView.kt @@ -30,7 +30,7 @@ import kotlin.math.ceil object ReflectiveEditorView { var reflectiveEditorView: JPanel? = null - val loadedPlugins: MutableList = mutableListOf() + private val loadedPlugins: MutableList = 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) } } diff --git a/plugin-playground/src/main/kotlin/KondoKit/ScrollablePanel.kt b/plugin-playground/src/main/kotlin/KondoKit/ScrollablePanel.kt index cf6444c..0e772b9 100644 --- a/plugin-playground/src/main/kotlin/KondoKit/ScrollablePanel.kt +++ b/plugin-playground/src/main/kotlin/KondoKit/ScrollablePanel.kt @@ -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,21 +84,23 @@ class ScrollablePanel(private val content: JPanel) : JPanel() { } private fun handleResize() { - // Ensure the ScrollablePanel resizes with the frame - bounds = Rectangle(0, 0, 242, frame.height) + SwingUtilities.invokeLater{ + // Ensure the ScrollablePanel resizes with the frame + bounds = Rectangle(0, 0, 242, frame.height) // Dynamically update content bounds and scrollbar on frame resize with buffer lastSize = content.preferredSize.height.coerceAtLeast(frame.height + viewBuffer) content.bounds = Rectangle(0, 0, 242, lastSize) showScrollbar = content.height > frame.height - currentOffsetY = 0 + currentOffsetY = 0 - content.setLocation(0, currentOffsetY) - updateScrollbar() + content.setLocation(0, currentOffsetY) + updateScrollbar() - revalidate() - repaint() + revalidate() + repaint() + } } private fun scrollContent(deltaY: Int) { @@ -106,40 +109,41 @@ class ScrollablePanel(private val content: JPanel) : JPanel() { content.setLocation(0, currentOffsetY) return } + SwingUtilities.invokeLater { + currentOffsetY += deltaY - currentOffsetY += deltaY + // Apply buffer to maxOffset + val maxOffset = (frame.height - content.height + viewBuffer).coerceAtMost(0) + currentOffsetY = currentOffsetY.coerceAtMost(0).coerceAtLeast(maxOffset) - // Apply buffer to maxOffset - val maxOffset = (frame.height - content.height + viewBuffer).coerceAtMost(0) - currentOffsetY = currentOffsetY.coerceAtMost(0).coerceAtLeast(maxOffset) + content.setLocation(0, currentOffsetY) - content.setLocation(0, currentOffsetY) - - val contentHeight = content.height - val viewHeight = frame.height + viewBuffer - val scrollableRatio = viewHeight.toDouble() / contentHeight - scrollbarY = ((-currentOffsetY / contentHeight.toDouble()) * viewHeight).toInt() - scrollbarHeight = (viewHeight * scrollableRatio).toInt().coerceAtLeast(20) - - repaint() - } - - private fun updateScrollbar() { - showScrollbar = content.height > frame.height - - val contentHeight = content.height - val viewHeight = frame.height + viewBuffer - - if (showScrollbar) { + val contentHeight = content.height + val viewHeight = frame.height + viewBuffer val scrollableRatio = viewHeight.toDouble() / contentHeight scrollbarY = ((-currentOffsetY / contentHeight.toDouble()) * viewHeight).toInt() scrollbarHeight = (viewHeight * scrollableRatio).toInt().coerceAtLeast(20) - } else { - scrollbarY = 0 - scrollbarHeight = 0 + repaint() } + } - repaint() + private fun updateScrollbar() { + SwingUtilities.invokeLater { + showScrollbar = content.height > frame.height + + val contentHeight = content.height + val viewHeight = frame.height + viewBuffer + + if (showScrollbar) { + val scrollableRatio = viewHeight.toDouble() / contentHeight + scrollbarY = ((-currentOffsetY / contentHeight.toDouble()) * viewHeight).toInt() + scrollbarHeight = (viewHeight * scrollableRatio).toInt().coerceAtLeast(20) + } else { + scrollbarY = 0 + scrollbarHeight = 0 + } + repaint() + } } override fun paintComponent(g: Graphics) { diff --git a/plugin-playground/src/main/kotlin/KondoKit/SpriteToBufferedImage.kt b/plugin-playground/src/main/kotlin/KondoKit/SpriteToBufferedImage.kt index f955f95..0258838 100644 --- a/plugin-playground/src/main/kotlin/KondoKit/SpriteToBufferedImage.kt +++ b/plugin-playground/src/main/kotlin/KondoKit/SpriteToBufferedImage.kt @@ -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() diff --git a/plugin-playground/src/main/kotlin/KondoKit/XPTable.kt b/plugin-playground/src/main/kotlin/KondoKit/XPTable.kt index 38df208..78138fe 100644 --- a/plugin-playground/src/main/kotlin/KondoKit/XPTable.kt +++ b/plugin-playground/src/main/kotlin/KondoKit/XPTable.kt @@ -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 = mutableListOf() diff --git a/plugin-playground/src/main/kotlin/KondoKit/plugin.kt b/plugin-playground/src/main/kotlin/KondoKit/plugin.kt index 1fde673..f5073ac 100644 --- a/plugin-playground/src/main/kotlin/KondoKit/plugin.kt +++ b/plugin-playground/src/main/kotlin/KondoKit/plugin.kt @@ -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?) { if (currentEntries != null) { for ((index, entry) in currentEntries.withIndex()) { @@ -218,59 +162,43 @@ 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 } override fun OnXPUpdate(skillId: Int, xp: Int) { - if (!initialXP.containsKey(skillId)) { - initialXP[skillId] = xp - return - } + if (!initialXP.containsKey(skillId)) { + initialXP[skillId] = xp + return + } + var xpWidget = xpWidgets[skillId] + if (xpWidget != null) { + updateWidget(xpWidget, xp) + } else { + val previousXp = initialXP[skillId] ?: xp + if (xp == initialXP[skillId]) return - var xpWidget = xpWidgets[skillId] + xpWidget = createXPWidget(skillId, previousXp) + xpWidgets[skillId] = xpWidget - if (xpWidget != null) { - updateWidget(xpWidget, xp) - } else { - val previousXp = initialXP[skillId] ?: xp - if (xp == initialXP[skillId]) return + xpTrackerView?.add(wrappedWidget(xpWidget.container)) + xpTrackerView?.add(Box.createVerticalStrut(5)) - xpWidget = createXPWidget(skillId, previousXp) - xpWidgets[skillId] = xpWidget + if(focusedView == XPTrackerView.VIEW_NAME) { + xpTrackerView?.revalidate() + xpTrackerView?.repaint() + } - xpTrackerView?.add(wrappedWidget(xpWidget.container)) - xpTrackerView?.add(Box.createVerticalStrut(5)) - - xpTrackerView?.revalidate() - if(focusedView == XPTrackerView.VIEW_NAME) - xpTrackerView?.repaint() - - updateWidget(xpWidget, xp) - } + updateWidget(xpWidget, xp) + } } override fun Draw(timeDelta: Long) { @@ -290,7 +218,7 @@ class plugin : Plugin() { } accumulatedTime += timeDelta - if (accumulatedTime >= tickInterval) { + if (accumulatedTime >= TICK_INTERVAL) { lootTrackerView?.let { onPostClientTick(it) } accumulatedTime = 0L } @@ -312,82 +240,202 @@ class plugin : Plugin() { } } - private fun initKondoUI(){ - DrawText(FontType.LARGE, fromColor(Color(16777215)), TextModifier.CENTER, "KondoKit Loading Sprites...", GameShell.canvasWidth/2, GameShell.canvasHeight/2) - if(!allSpritesLoaded()) return; - val frame: Frame? = GameShell.frame + 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() + } - loadFont() - val themeIndex = (GetData("kondoTheme") as? String) ?: "RUNELITE" - theme = ThemeType.valueOf(themeIndex) - applyTheme(getTheme(theme)) - appliedTheme = theme + private fun updateDisplaySettings() { + val mode = GetWindowMode() + val currentScrollPaneWidth = if (mainContentPanel.isVisible) NAVBAR_WIDTH + MAIN_CONTENT_WIDTH else NAVBAR_WIDTH + lastUIOffset = uiOffset - try { - UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel") + when (mode) { + WindowMode.FIXED -> { + if (frame.width < FIXED_WIDTH + currentScrollPaneWidth + uiOffset) { + frame.setSize(FIXED_WIDTH + currentScrollPaneWidth + uiOffset, frame.height) + } - // Modify the UI properties to match theme - UIManager.put("control", VIEW_BACKGROUND_COLOR) - UIManager.put("info", VIEW_BACKGROUND_COLOR) - UIManager.put("nimbusBase", WIDGET_COLOR) - UIManager.put("nimbusBlueGrey", TITLE_BAR_COLOR) + val difference = frame.width - (uiOffset + currentScrollPaneWidth) - UIManager.put("nimbusDisabledText", primaryColor) - UIManager.put("nimbusSelectedText", secondaryColor) - UIManager.put("text", secondaryColor) + if (useScaledFixed) { + GameShell.leftMargin = 0 + val canvasWidth = difference + uiOffset / 2 + val canvasHeight = frame.height - canvas.y // Restricting height to frame height - UIManager.put("nimbusFocus", TITLE_BAR_COLOR) - UIManager.put("nimbusInfoBlue", POPUP_BACKGROUND) - UIManager.put("nimbusLightBackground", WIDGET_COLOR) - UIManager.put("nimbusSelectionBackground", PROGRESS_BAR_FILL) - - UIManager.put("Button.background", WIDGET_COLOR) - UIManager.put("Button.foreground", secondaryColor) - - UIManager.put("CheckBox.background", VIEW_BACKGROUND_COLOR) - UIManager.put("CheckBox.foreground", secondaryColor) - UIManager.put("CheckBox.icon", UIManager.getIcon("CheckBox.icon")) - - UIManager.put("ComboBox.background", WIDGET_COLOR) - UIManager.put("ComboBox.foreground", secondaryColor) - UIManager.put("ComboBox.selectionBackground", PROGRESS_BAR_FILL) - UIManager.put("ComboBox.selectionForeground", primaryColor) - UIManager.put("ComboBox.buttonBackground", WIDGET_COLOR) - - UIManager.put("Spinner.background", WIDGET_COLOR) - UIManager.put("Spinner.foreground", secondaryColor) - UIManager.put("Spinner.border", BorderFactory.createLineBorder(TITLE_BAR_COLOR)) - - UIManager.put("TextField.background", WIDGET_COLOR) - UIManager.put("TextField.foreground", secondaryColor) - UIManager.put("TextField.caretForeground", secondaryColor) - UIManager.put("TextField.border", BorderFactory.createLineBorder(TITLE_BAR_COLOR)) - - UIManager.put("ScrollBar.thumb", WIDGET_COLOR) - UIManager.put("ScrollBar.track", VIEW_BACKGROUND_COLOR) - UIManager.put("ScrollBar.thumbHighlight", TITLE_BAR_COLOR) - - UIManager.put("ProgressBar.foreground", PROGRESS_BAR_FILL) - UIManager.put("ProgressBar.background", WIDGET_COLOR) - UIManager.put("ProgressBar.border", BorderFactory.createLineBorder(TITLE_BAR_COLOR)) - - UIManager.put("ToolTip.background", VIEW_BACKGROUND_COLOR) - UIManager.put("ToolTip.foreground", secondaryColor) - UIManager.put("ToolTip.border", BorderFactory.createLineBorder(TITLE_BAR_COLOR)) - - // Update component tree UI to apply the new theme - SwingUtilities.updateComponentTreeUI(GameShell.frame) - } catch (e : Exception) { - e.printStackTrace() + 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 + } } - // 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 + 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 + val frame: Frame? = GameShell.frame + if (frame != null) { + restoreSettings() + theme = ThemeType.valueOf(themeName) + applyTheme(getTheme(theme)) + appliedTheme = theme + configureLookAndFeel() cardLayout = CardLayout() mainContentPanel = JPanel(cardLayout).apply { @@ -419,7 +467,7 @@ class plugin : Plugin() { navPanel.add(createNavButton(LOOT_ICON, LootTrackerView.VIEW_NAME)) navPanel.add(createNavButton(WRENCH_ICON, ReflectiveEditorView.VIEW_NAME)) - var rightPanel = Panel(BorderLayout()).apply { + val rightPanel = Panel(BorderLayout()).apply { add(mainContentPanel, BorderLayout.CENTER) add(navPanel, BorderLayout.EAST) } @@ -427,53 +475,29 @@ class plugin : Plugin() { rightPanelWrapper = JScrollPane(rightPanel).apply { preferredSize = Dimension(NAVBAR_WIDTH + MAIN_CONTENT_WIDTH, frame.height) background = VIEW_BACKGROUND_COLOR - border = BorderFactory.createEmptyBorder() // Removes the border completely + border = BorderFactory.createEmptyBorder() horizontalScrollBarPolicy = JScrollPane.HORIZONTAL_SCROLLBAR_NEVER verticalScrollBarPolicy = JScrollPane.VERTICAL_SCROLLBAR_NEVER } frame.layout = BorderLayout() - rightPanelWrapper?.let { frame.add(it, BorderLayout.EAST) } + rightPanelWrapper?.let { + frame.add(it, BorderLayout.EAST) + } - if(!launchMinimized){ - setActiveView(XPTrackerView.VIEW_NAME) - } else { + if(launchMinimized){ setActiveView(HIDDEN_VIEW) + } else { + setActiveView(XPTrackerView.VIEW_NAME) + } + if(useScaledFixed) { + initAltCanvas() } 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) { @@ -486,14 +510,15 @@ class plugin : Plugin() { } reloadInterfaces = true - UpdateDisplaySettings() + updateDisplaySettings() // Revalidate and repaint necessary panels mainContentPanel.revalidate() - mainContentPanel.repaint() rightPanelWrapper?.revalidate() - rightPanelWrapper?.repaint() frame?.revalidate() + + mainContentPanel.repaint() + rightPanelWrapper?.repaint() frame?.repaint() focusedView = viewName @@ -543,7 +568,7 @@ class plugin : Plugin() { maximumSize = buttonSize minimumSize = buttonSize background = WIDGET_COLOR - isOpaque = true // Ensure background is painted + isOpaque = true val gbc = GridBagConstraints().apply { anchor = GridBagConstraints.CENTER @@ -580,8 +605,69 @@ class plugin : Plugin() { return panelButton } + private fun configureLookAndFeel(){ + loadFont() + try { + UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel") - fun loadFont(): Font? { + // Modify the UI properties to match theme + UIManager.put("control", VIEW_BACKGROUND_COLOR) + UIManager.put("info", VIEW_BACKGROUND_COLOR) + UIManager.put("nimbusBase", WIDGET_COLOR) + UIManager.put("nimbusBlueGrey", TITLE_BAR_COLOR) + + UIManager.put("nimbusDisabledText", primaryColor) + UIManager.put("nimbusSelectedText", secondaryColor) + UIManager.put("text", secondaryColor) + + UIManager.put("nimbusFocus", TITLE_BAR_COLOR) + UIManager.put("nimbusInfoBlue", POPUP_BACKGROUND) + UIManager.put("nimbusLightBackground", WIDGET_COLOR) + UIManager.put("nimbusSelectionBackground", PROGRESS_BAR_FILL) + + UIManager.put("Button.background", WIDGET_COLOR) + UIManager.put("Button.foreground", secondaryColor) + + UIManager.put("CheckBox.background", VIEW_BACKGROUND_COLOR) + UIManager.put("CheckBox.foreground", secondaryColor) + UIManager.put("CheckBox.icon", UIManager.getIcon("CheckBox.icon")) + + UIManager.put("ComboBox.background", WIDGET_COLOR) + UIManager.put("ComboBox.foreground", secondaryColor) + UIManager.put("ComboBox.selectionBackground", PROGRESS_BAR_FILL) + UIManager.put("ComboBox.selectionForeground", primaryColor) + UIManager.put("ComboBox.buttonBackground", WIDGET_COLOR) + + UIManager.put("Spinner.background", WIDGET_COLOR) + UIManager.put("Spinner.foreground", secondaryColor) + UIManager.put("Spinner.border", BorderFactory.createLineBorder(TITLE_BAR_COLOR)) + + UIManager.put("TextField.background", WIDGET_COLOR) + UIManager.put("TextField.foreground", secondaryColor) + UIManager.put("TextField.caretForeground", secondaryColor) + UIManager.put("TextField.border", BorderFactory.createLineBorder(TITLE_BAR_COLOR)) + + UIManager.put("ScrollBar.thumb", WIDGET_COLOR) + UIManager.put("ScrollBar.track", VIEW_BACKGROUND_COLOR) + UIManager.put("ScrollBar.thumbHighlight", TITLE_BAR_COLOR) + + UIManager.put("ProgressBar.foreground", PROGRESS_BAR_FILL) + UIManager.put("ProgressBar.background", WIDGET_COLOR) + UIManager.put("ProgressBar.border", BorderFactory.createLineBorder(TITLE_BAR_COLOR)) + + UIManager.put("ToolTip.background", VIEW_BACKGROUND_COLOR) + UIManager.put("ToolTip.foreground", secondaryColor) + UIManager.put("ToolTip.border", BorderFactory.createLineBorder(TITLE_BAR_COLOR)) + + // Update component tree UI to apply the new theme + SwingUtilities.updateComponentTreeUI(frame) + frame.background = Color.BLACK + } catch (e : Exception) { + e.printStackTrace() + } + } + + 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