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/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/plugin.kt b/plugin-playground/src/main/kotlin/KondoKit/plugin.kt index bc0f6b3..f4a604f 100644 --- a/plugin-playground/src/main/kotlin/KondoKit/plugin.kt +++ b/plugin-playground/src/main/kotlin/KondoKit/plugin.kt @@ -35,16 +35,17 @@ import plugin.api.* import plugin.api.API.* import plugin.api.FontColor.fromColor import rt4.GameShell +import rt4.GameShell.canvas import rt4.GameShell.frame import rt4.GlRenderer import rt4.InterfaceList import rt4.Player +import rt4.SoftwareRaster import rt4.client.js5Archive8 import rt4.client.mainLoadState import java.awt.* -import java.awt.event.ActionListener -import java.awt.event.MouseAdapter -import java.awt.event.MouseEvent +import java.awt.event.* +import java.awt.image.BufferedImage import javax.swing.* import javax.swing.plaf.nimbus.AbstractRegionPainter @@ -149,12 +150,157 @@ class plugin : Plugin() { } lastLogin = Player.usernameInput.toString() } + class AltCanvas(private val mainCanvas: Canvas) : JPanel() { + private var gameImage: BufferedImage? = null + private var scaleX = 1.0 + private var scaleY = 1.0 + private var offsetX = 0 + private var offsetY = 0 + init { + gameImage = BufferedImage(765, 503, BufferedImage.TYPE_INT_ARGB) + val g = gameImage!!.createGraphics() + g.color = Color.RED + g.fillRect(0, 0, gameImage!!.width, gameImage!!.height) + g.color = Color.BLACK + g.drawString("Game Frame", 20, 20) + g.dispose() + + 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) + } + }) + + // Register a KeyAdapter for handling key events + addKeyListener(object : KeyAdapter() { + override fun keyPressed(e: KeyEvent) { + for(listener in canvas.keyListeners){ + listener.keyPressed(e) + } + } + + override fun keyReleased(e: KeyEvent) { + for(listener in canvas.keyListeners){ + listener.keyReleased(e) + } + } + + override fun keyTyped(e: KeyEvent) { + for(listener in canvas.keyListeners){ + listener.keyTyped(e) + } + } + }) + + isFocusable = true + requestFocusInWindow() + } + + override fun paintComponent(g: Graphics) { + super.paintComponent(g) + val g2d = g as Graphics2D + + // Set the desired background fill color here + g2d.color = Color(30, 30, 30) // Replace with your preferred fill color + g2d.fillRect(0, 0, width, height) + gameImage?.let { image -> + g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR) + + // Calculate aspect-ratio-preserving scale + val imageAspect = image.width.toDouble() / image.height.toDouble() + val panelAspect = width.toDouble() / height.toDouble() + + val (drawWidth, drawHeight) = if (imageAspect > panelAspect) { + val newWidth = width + val newHeight = (width / imageAspect).toInt() + newWidth to newHeight + } else { + val newHeight = height + val newWidth = (height * imageAspect).toInt() + newWidth to newHeight + } + + // Store scale factors and offsets for event adjustment + scaleX = drawWidth.toDouble() / image.width + scaleY = drawHeight.toDouble() / image.height + offsetX = (width - drawWidth) / 2 + offsetY = (height - drawHeight) / 2 + + // Draw the scaled image centered in the panel + val x = offsetX + val y = offsetY + g2d.drawImage(image, x, y, drawWidth, drawHeight, null) + } + } + + private fun relayMouseEvent(e: MouseEvent) { + val adjustedX = (e.x - offsetX) / scaleX + val adjustedY = (e.y - offsetY) / scaleY + + val originalX = adjustedX.toInt().coerceIn(0, gameImage!!.width - 1) + val originalY = adjustedY.toInt().coerceIn(0, gameImage!!.height - 1) + + val newEvent = MouseEvent( + mainCanvas, e.id, e.`when`, e.modifiersEx, + originalX, originalY, e.clickCount, e.isPopupTrigger, e.button + ) + + mainCanvas.dispatchEvent(newEvent) + } + + fun updateGameImage(newImage: BufferedImage) { + gameImage = newImage + repaint() + } + } + + + + fun createAltCanvas(mainCanvas: Canvas): AltCanvas { + return AltCanvas(mainCanvas).apply { + preferredSize = Dimension(FIXED_WIDTH, 503) + } + } + + private var altCanvas: AltCanvas? = null override fun Init() { // Disable Font AA System.setProperty("sun.java2d.opengl", "false") System.setProperty("awt.useSystemAAFontSettings", "off") System.setProperty("swing.aatext", "false") + val frame: Frame? = GameShell.frame + if (frame != null) { + // Create the AltCanvas and add it to the main frame + altCanvas = createAltCanvas(canvas) + + // Use BorderLayout for better layout control + frame.layout = BorderLayout() + + // Add the AltCanvas in the center to ensure it scales properly with the window size + frame.add(altCanvas, BorderLayout.NORTH) + frame.remove(canvas) + //frame.defaultCloseOperation = JFrame.EXIT_ON_CLOSE + frame.isVisible = true + } } private fun UpdateDisplaySettings() { @@ -166,9 +312,8 @@ class plugin : Plugin() { 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 + val difference = frame.width - (uiOffset + currentScrollPaneWidth) + altCanvas?.size = Dimension(difference, frame.height - 30) } WindowMode.RESIZABLE -> { GameShell.canvasWidth = frame.width - (currentScrollPaneWidth + uiOffset) @@ -274,7 +419,7 @@ class plugin : Plugin() { } } - override fun Draw(timeDelta: Long) { + override fun LateDraw(timeDelta: Long) { if (GlRenderer.enabled && GlRenderer.canvasWidth != GameShell.canvasWidth) { GlRenderer.canvasWidth = GameShell.canvasWidth GlRenderer.setViewportBounds(0, 0, GameShell.canvasWidth, GameShell.canvasHeight) @@ -296,6 +441,10 @@ class plugin : Plugin() { accumulatedTime = 0L } + // Update game image here + val rasterImage = getRasterImageFromGameShell() // Replace this with the actual method to fetch the BufferedImage from GameShell.canvas. + altCanvas?.updateGameImage(rasterImage) + // Draw synced actions (that require to be done between glBegin and glEnd) if (drawActions.isNotEmpty()) { synchronized(drawActions) { @@ -313,6 +462,18 @@ class plugin : Plugin() { } } + // Placeholder method to get the game image from GameShell + fun getRasterImageFromGameShell(): BufferedImage { + // Assuming SoftwareRaster.pixels is an IntArray containing ARGB values of the game image. + val gameImage = BufferedImage(765, 503, BufferedImage.TYPE_INT_ARGB) + + val g = gameImage.createGraphics() + + SoftwareRaster.frameBuffer.draw(g) + return gameImage + } + + private fun initKondoUI(){ DrawText(FontType.LARGE, fromColor(Color(16777215)), TextModifier.CENTER, "KondoKit Loading Sprites...", GameShell.canvasWidth/2, GameShell.canvasHeight/2) if(!allSpritesLoaded()) return; @@ -379,6 +540,7 @@ class plugin : Plugin() { // Update component tree UI to apply the new theme SwingUtilities.updateComponentTreeUI(GameShell.frame) + GameShell.frame.background = Color.BLACK } catch (e : Exception) { e.printStackTrace() }