diff --git a/runelite-client/.gitignore b/runelite-client/.gitignore
new file mode 100644
index 0000000000..a6f89c2da7
--- /dev/null
+++ b/runelite-client/.gitignore
@@ -0,0 +1 @@
+/target/
\ No newline at end of file
diff --git a/runelite-client/pom.xml b/runelite-client/pom.xml
new file mode 100644
index 0000000000..aac069cc65
--- /dev/null
+++ b/runelite-client/pom.xml
@@ -0,0 +1,79 @@
+
+
+ 4.0.0
+
+ net.runelite
+ client
+ 1.0.0-SNAPSHOT
+
+
+ UTF-8
+ 1.8
+ 1.8
+
+ 113-SNAPSHOT
+
+ 1.7.12
+
+
+
+
+ runelite
+ ${runelite.repository.url}
+
+
+
+
+
+ runelite
+ RuneLite
+ http://repo.runelite.net
+
+
+
+
+
+ org.slf4j
+ slf4j-api
+ ${slf4j.version}
+
+
+ org.slf4j
+ slf4j-simple
+ ${slf4j.version}
+
+
+ net.sf.jopt-simple
+ jopt-simple
+ 5.0.1
+
+
+
+ net.runelite.rs
+ api
+ 1.0.0-SNAPSHOT
+
+
+ net.runelite.rs
+ client
+ ${rs.version}
+
+
+
+ junit
+ junit
+ 4.12
+ test
+
+
+
+
+
+
+ org.apache.maven.wagon
+ wagon-ssh
+ 2.10
+
+
+
+
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/ClientLoader.java b/runelite-client/src/main/java/net/runelite/client/ClientLoader.java
new file mode 100644
index 0000000000..09e1ea8b36
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/ClientLoader.java
@@ -0,0 +1,28 @@
+package net.runelite.client;
+
+import java.applet.Applet;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ClientLoader
+{
+ private static final Logger logger = LoggerFactory.getLogger(ClientLoader.class);
+
+ public Applet load() throws MalformedURLException, ClassNotFoundException, IOException, InstantiationException, IllegalAccessException
+ {
+ ConfigLoader config = new ConfigLoader();
+
+ config.fetch();
+
+ String initialClass = config.getProperty(ConfigLoader.INITIAL_CLASS).replace(".class", "");
+
+ Class> clientClass = this.getClass().getClassLoader().loadClass(initialClass);
+ Applet rs = (Applet) clientClass.newInstance();
+
+ rs.setStub(new RSStub(config, rs));
+
+ return rs;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/ConfigLoader.java b/runelite-client/src/main/java/net/runelite/client/ConfigLoader.java
new file mode 100644
index 0000000000..a9f831c3da
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/ConfigLoader.java
@@ -0,0 +1,92 @@
+package net.runelite.client;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.HashMap;
+import java.util.Map;
+
+public class ConfigLoader
+{
+ private static URL configURL;
+
+ public static final String CODEBASE = "codebase";
+ public static final String INITIAL_JAR = "initial_jar";
+ public static final String INITIAL_CLASS = "initial_class";
+ public static final String APP_MINWIDTH = "applet_minwidth";
+ public static final String APP_MINHEIGHT = "applet_minheight";
+
+ private final Map properties = new HashMap<>(),
+ appletProperties = new HashMap<>();
+
+ static
+ {
+ try
+ {
+ configURL = new URL("http://oldschool.runescape.com/jav_config.ws"); // https redirects us to rs3
+ }
+ catch (MalformedURLException ex)
+ {
+ ex.printStackTrace();
+ }
+ }
+
+ public void fetch() throws IOException
+ {
+ URLConnection conn = configURL.openConnection();
+ try (BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream())))
+ {
+ String str;
+
+ while ((str = in.readLine()) != null)
+ {
+ int idx = str.indexOf('=');
+
+ if (idx == -1)
+ continue;
+
+ String s = str.substring(0, idx);
+
+ if (s.equals("param"))
+ {
+ str = str.substring(idx + 1);
+ idx = str.indexOf('=');
+ s = str.substring(0, idx);
+
+ appletProperties.put(s, str.substring(idx + 1));
+ }
+ else if (s.equals("msg"))
+ {
+ // ignore
+ }
+ else
+ {
+ properties.put(s, str.substring(idx + 1));
+ }
+ }
+ }
+ }
+
+ public String getProperty(String name)
+ {
+ return properties.get(name);
+ }
+
+ public Map getProperties()
+ {
+ return properties;
+ }
+
+ public String getAppletProperty(String name)
+ {
+ return appletProperties.get(name);
+ }
+
+ public Map getAppletProperties()
+ {
+ return appletProperties;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/RSStub.java b/runelite-client/src/main/java/net/runelite/client/RSStub.java
new file mode 100644
index 0000000000..efc0287da4
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/RSStub.java
@@ -0,0 +1,67 @@
+package net.runelite.client;
+
+import java.applet.Applet;
+import java.applet.AppletContext;
+import java.applet.AppletStub;
+import java.awt.Dimension;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+public class RSStub implements AppletStub
+{
+ private final ConfigLoader config;
+ private final Applet app;
+
+ public RSStub(ConfigLoader config, Applet app)
+ {
+ this.config = config;
+ this.app = app;
+ }
+
+ @Override
+ public boolean isActive()
+ {
+ return true;
+ }
+
+ @Override
+ public URL getDocumentBase()
+ {
+ return getCodeBase();
+ }
+
+ @Override
+ public URL getCodeBase()
+ {
+ try
+ {
+ return new URL(config.getProperty(ConfigLoader.CODEBASE));
+ }
+ catch (MalformedURLException ex)
+ {
+ return null;
+ }
+ }
+
+ @Override
+ public String getParameter(String name)
+ {
+ return config.getAppletProperty(name);
+ }
+
+ @Override
+ public AppletContext getAppletContext()
+ {
+ return null;
+ }
+
+ @Override
+ public void appletResize(int width, int height)
+ {
+ Dimension d = new Dimension(width, height);
+
+ app.setSize(d);
+ app.setPreferredSize(d);
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/RuneLite.java b/runelite-client/src/main/java/net/runelite/client/RuneLite.java
new file mode 100644
index 0000000000..b737cb1b1f
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/RuneLite.java
@@ -0,0 +1,75 @@
+package net.runelite.client;
+
+import java.io.File;
+import joptsimple.OptionParser;
+import joptsimple.OptionSet;
+import net.runelite.api.Client;
+import net.runelite.client.plugins.PluginManager;
+import net.runelite.client.ui.ClientUI;
+import net.runelite.client.ui.overlay.OverlayRenderer;
+
+
+public class RuneLite
+{
+ public static final File RUNELITE_DIR = new File(System.getProperty("user.home"), ".runelite");
+ public static final File REPO_DIR = new File(RUNELITE_DIR, "repository");
+
+ private static OptionSet options;
+ private static Client client;
+ private static RuneLite runelite;
+
+ private ClientUI gui;
+ private PluginManager pluginManager;
+ private OverlayRenderer renderer;
+
+ public static void main(String[] args) throws Exception
+ {
+ OptionParser parser = new OptionParser();
+ parser.accepts("developer-mode");
+ options = parser.parse(args);
+
+ runelite = new RuneLite();
+ runelite.start();
+ }
+
+ public void start() throws Exception
+ {
+ gui = new ClientUI();
+ gui.setVisible(true);
+
+ pluginManager = new PluginManager();
+ pluginManager.loadAll();
+
+ renderer = new OverlayRenderer();
+ }
+
+ public static Client getClient()
+ {
+ return client;
+ }
+
+ public static void setClient(Client client)
+ {
+ RuneLite.client = client;
+ }
+
+ public static RuneLite getRunelite()
+ {
+ return runelite;
+ }
+
+ public PluginManager getPluginManager()
+ {
+ return pluginManager;
+ }
+
+ public OverlayRenderer getRenderer()
+ {
+ return renderer;
+ }
+
+ public static OptionSet getOptions()
+ {
+ return options;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/Plugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/Plugin.java
new file mode 100644
index 0000000000..6005ce6e2b
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/Plugin.java
@@ -0,0 +1,8 @@
+package net.runelite.client.plugins;
+
+import net.runelite.client.ui.overlay.Overlay;
+
+public abstract class Plugin
+{
+ public abstract Overlay getOverlay();
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java
new file mode 100644
index 0000000000..7ff8e0e614
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java
@@ -0,0 +1,23 @@
+package net.runelite.client.plugins;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import net.runelite.client.plugins.boosts.Boosts;
+import net.runelite.client.plugins.opponentinfo.OpponentInfo;
+
+public class PluginManager
+{
+ private final List plugins = new ArrayList<>();
+
+ public void loadAll()
+ {
+ plugins.add(new Boosts());
+ plugins.add(new OpponentInfo());
+ }
+
+ public Collection getPlugins()
+ {
+ return plugins;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/boosts/Boosts.java b/runelite-client/src/main/java/net/runelite/client/plugins/boosts/Boosts.java
new file mode 100644
index 0000000000..040c582a88
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/boosts/Boosts.java
@@ -0,0 +1,15 @@
+package net.runelite.client.plugins.boosts;
+
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.ui.overlay.Overlay;
+
+public class Boosts extends Plugin
+{
+ private final Overlay overlay = new BoostsOverlay();
+
+ @Override
+ public Overlay getOverlay()
+ {
+ return overlay;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/boosts/BoostsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/boosts/BoostsOverlay.java
new file mode 100644
index 0000000000..f536d7078c
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/boosts/BoostsOverlay.java
@@ -0,0 +1,86 @@
+package net.runelite.client.plugins.boosts;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.FontMetrics;
+import java.awt.Graphics2D;
+import net.runelite.api.Client;
+import net.runelite.api.GameState;
+import net.runelite.api.Skill;
+import net.runelite.client.RuneLite;
+import net.runelite.client.ui.overlay.Overlay;
+import net.runelite.client.ui.overlay.OverlayPosition;
+import net.runelite.client.ui.overlay.OverlayPriority;
+
+class BoostsOverlay extends Overlay
+{
+ private static final int WIDTH = 140;
+
+ private static final Color BACKGROUND = new Color(Color.gray.getRed(), Color.gray.getGreen(), Color.gray.getBlue(), 127);
+
+ private static final Skill[] SHOW = new Skill[] { Skill.ATTACK, Skill.STRENGTH, Skill.DEFENCE, Skill.RANGED, Skill.MAGIC };
+
+ private static final int TOP_BORDER = 2;
+ private static final int LEFT_BORDER = 2;
+ private static final int RIGHT_BORDER = 2;
+
+ private static final int SEPARATOR = 2;
+
+ BoostsOverlay()
+ {
+ super(OverlayPosition.TOP_LEFT, OverlayPriority.MED);
+ }
+
+ @Override
+ public Dimension render(Graphics2D graphics)
+ {
+ Client client = RuneLite.getClient();
+
+ if (client.getGameState() != GameState.LOGGED_IN)
+ return null;
+
+ FontMetrics metrics = graphics.getFontMetrics();
+
+ int[] boostedSkills = client.getBoostedSkillLevels(),
+ baseSkills = client.getRealSkillLevels();
+
+ int height = TOP_BORDER;
+ for (Skill skill : SHOW)
+ {
+ int boosted = boostedSkills[skill.ordinal()],
+ base = baseSkills[skill.ordinal()];
+
+ if (boosted == base)
+ continue;
+
+ height += metrics.getHeight() + SEPARATOR;
+ }
+
+ if (height == TOP_BORDER)
+ return null;
+
+ graphics.setColor(BACKGROUND);
+ graphics.fillRect(0, 0, WIDTH, height);
+
+ int y = TOP_BORDER;
+ for (Skill skill : SHOW)
+ {
+ int boosted = boostedSkills[skill.ordinal()],
+ base = baseSkills[skill.ordinal()];
+
+ if (boosted == base)
+ continue;
+
+ graphics.setColor(Color.white);
+ graphics.drawString(skill.getName(), LEFT_BORDER, y + metrics.getHeight());
+
+ String str = boosted + "/" + base;
+ graphics.drawString(str, WIDTH - RIGHT_BORDER - metrics.stringWidth(str), y + metrics.getHeight());
+
+ y += metrics.getHeight() + SEPARATOR;
+ }
+
+ return new Dimension(WIDTH, height);
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/opponentinfo/OpponentInfo.java b/runelite-client/src/main/java/net/runelite/client/plugins/opponentinfo/OpponentInfo.java
new file mode 100644
index 0000000000..23ae1a099f
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/opponentinfo/OpponentInfo.java
@@ -0,0 +1,15 @@
+package net.runelite.client.plugins.opponentinfo;
+
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.ui.overlay.Overlay;
+
+public class OpponentInfo extends Plugin
+{
+ private final Overlay overlay = new OpponentInfoOverlay();
+
+ @Override
+ public Overlay getOverlay()
+ {
+ return overlay;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/opponentinfo/OpponentInfoOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/opponentinfo/OpponentInfoOverlay.java
new file mode 100644
index 0000000000..3bc22c3f63
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/opponentinfo/OpponentInfoOverlay.java
@@ -0,0 +1,99 @@
+package net.runelite.client.plugins.opponentinfo;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.FontMetrics;
+import java.awt.Graphics2D;
+import net.runelite.api.Actor;
+import net.runelite.api.Client;
+import net.runelite.api.Player;
+import net.runelite.client.RuneLite;
+import net.runelite.client.ui.overlay.Overlay;
+import net.runelite.client.ui.overlay.OverlayPosition;
+import net.runelite.client.ui.overlay.OverlayPriority;
+
+class OpponentInfoOverlay extends Overlay
+{
+ private static final int WIDTH = 140;
+
+ private static final int TOP_BORDER = 2;
+ private static final int BOTTOM_BORDER = 2;
+
+ private static final int BAR_WIDTH = 124;
+ private static final int BAR_HEIGHT = 20;
+
+ private static final Color BACKGROUND = new Color(Color.gray.getRed(), Color.gray.getGreen(), Color.gray.getBlue(), 127);
+ private static final Color HP_GREEN = new Color(0, 146, 54, 230);
+ private static final Color HP_RED = new Color(102, 15, 16, 230);
+
+ OpponentInfoOverlay()
+ {
+ super(OverlayPosition.TOP_LEFT, OverlayPriority.HIGH);
+ }
+
+ private Actor getOpponent()
+ {
+ Client client = RuneLite.getClient();
+
+ Player player = client.getLocalPlayer();
+ if (player == null)
+ return null;
+
+ return player.getInteracting();
+ }
+
+ @Override
+ public Dimension render(Graphics2D graphics)
+ {
+ Actor opponent = getOpponent();
+
+ if (opponent == null)
+ return null;
+
+ int cur = opponent.getHealth();
+ int max = opponent.getMaxHealth();
+
+ FontMetrics fm = graphics.getFontMetrics();
+
+ int height = TOP_BORDER
+ + fm.getHeight(); // opponent name
+ if (max > 0)
+ height += 1 // between name and hp bar
+ + BAR_HEIGHT; // bar
+ height += BOTTOM_BORDER;
+
+ graphics.setColor(BACKGROUND);
+ graphics.fillRect(0, 0, WIDTH, height);
+
+ String str = opponent.getName();
+
+ int x = (WIDTH - fm.stringWidth(str)) / 2;
+ graphics.setColor(Color.white);
+ graphics.drawString(str, x, fm.getHeight() + TOP_BORDER);
+
+ // hp bar
+
+ if (max > 0)
+ {
+ float percent = (float) cur / (float) max;
+ if (percent > 100f)
+ percent = 100f;
+
+ int barWidth = (int) (percent * (float) BAR_WIDTH);
+ int barY = TOP_BORDER + fm.getHeight() + 1;
+
+ graphics.setColor(HP_GREEN);
+ graphics.fillRect((WIDTH - BAR_WIDTH) / 2, barY, barWidth, BAR_HEIGHT);
+
+ graphics.setColor(HP_RED);
+ graphics.fillRect(((WIDTH - BAR_WIDTH) / 2) + barWidth, barY, BAR_WIDTH - barWidth, BAR_HEIGHT);
+
+ str = cur + " / " + max;
+ x = (WIDTH - fm.stringWidth(str)) / 2;
+ graphics.setColor(Color.white);
+ graphics.drawString(str, x, barY + fm.getHeight());
+ }
+
+ return new Dimension(WIDTH, height);
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/ui/ClientPanel.java b/runelite-client/src/main/java/net/runelite/client/ui/ClientPanel.java
new file mode 100644
index 0000000000..5c613aece6
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/ui/ClientPanel.java
@@ -0,0 +1,60 @@
+package net.runelite.client.ui;
+
+import java.applet.Applet;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.event.ComponentEvent;
+import java.awt.event.ComponentListener;
+import javax.swing.JPanel;
+import net.runelite.api.Client;
+import net.runelite.client.ClientLoader;
+import net.runelite.client.RuneLite;
+
+final class ClientPanel extends JPanel implements ComponentListener
+{
+ public static final int PANEL_WIDTH = 765, PANEL_HEIGHT = 503;
+
+ private Applet rs;
+
+ public ClientPanel() throws Exception
+ {
+ setSize(new Dimension(PANEL_WIDTH, PANEL_HEIGHT));
+ setMinimumSize(new Dimension(PANEL_WIDTH, PANEL_HEIGHT));
+ setPreferredSize(new Dimension(PANEL_WIDTH, PANEL_HEIGHT));
+ setBackground(Color.black);
+ this.addComponentListener(this);
+
+ ClientLoader loader = new ClientLoader();
+
+ rs = loader.load();
+ rs.setSize(this.getSize());
+ rs.init();
+ rs.start();
+ this.add(rs);
+
+ RuneLite.setClient(new Client((net.runelite.rs.api.Client) rs));
+ }
+
+ @Override
+ public void componentResized(ComponentEvent e)
+ {
+ rs.setSize(this.getSize());
+ this.setPreferredSize(this.getSize());
+ rs.setPreferredSize(this.getPreferredSize());
+ }
+
+ @Override
+ public void componentMoved(ComponentEvent e)
+ {
+ }
+
+ @Override
+ public void componentShown(ComponentEvent e)
+ {
+ }
+
+ @Override
+ public void componentHidden(ComponentEvent e)
+ {
+ }
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java b/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java
new file mode 100644
index 0000000000..ee927145b3
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java
@@ -0,0 +1,85 @@
+package net.runelite.client.ui;
+
+import java.awt.event.ComponentEvent;
+import java.awt.event.ComponentListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+import javax.swing.JPopupMenu;
+import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+import javax.swing.UnsupportedLookAndFeelException;
+
+public final class ClientUI extends JFrame implements ComponentListener
+{
+ private ClientPanel panel;
+
+ public ClientUI() throws Exception
+ {
+ init();
+ pack();
+ setTitle("RuneLite");
+ setLocationRelativeTo(getOwner());
+ setMinimumSize(getSize());
+ setResizable(true);
+ this.addComponentListener(this);
+ }
+
+ private void init() throws Exception
+ {
+ setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
+
+ addWindowListener(new WindowAdapter()
+ {
+ @Override
+ public void windowClosing(WindowEvent e)
+ {
+ checkExit();
+ }
+ });
+
+ JPopupMenu.setDefaultLightWeightPopupEnabled(false);
+ try
+ {
+ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+ }
+ catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ignored)
+ {
+ }
+
+ panel = new ClientPanel();
+ add(panel);
+ }
+
+ private void checkExit()
+ {
+ int result = JOptionPane.showConfirmDialog(this, "Are you sure you want to exit?", "Exit", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
+
+ if (result == JOptionPane.OK_OPTION)
+ {
+ System.exit(0);
+ }
+ }
+
+ @Override
+ public void componentResized(ComponentEvent e)
+ {
+ SwingUtilities.invokeLater(() -> pack()); // is this right?
+ }
+
+ @Override
+ public void componentMoved(ComponentEvent e)
+ {
+ }
+
+ @Override
+ public void componentShown(ComponentEvent e)
+ {
+ }
+
+ @Override
+ public void componentHidden(ComponentEvent e)
+ {
+ }
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/Overlay.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/Overlay.java
new file mode 100644
index 0000000000..ae65f34d8f
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/Overlay.java
@@ -0,0 +1,38 @@
+package net.runelite.client.ui.overlay;
+
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+
+public abstract class Overlay
+{
+ private OverlayPosition position; // where to draw it
+ private OverlayPriority priority; // if multiple overlays exist in the same position, who wins
+
+ public Overlay(OverlayPosition position, OverlayPriority priority)
+ {
+ this.position = position;
+ this.priority = priority;
+ }
+
+ public OverlayPosition getPosition()
+ {
+ return position;
+ }
+
+ public void setPosition(OverlayPosition position)
+ {
+ this.position = position;
+ }
+
+ public OverlayPriority getPriority()
+ {
+ return priority;
+ }
+
+ public void setPriority(OverlayPriority priority)
+ {
+ this.priority = priority;
+ }
+
+ public abstract Dimension render(Graphics2D graphics);
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayPosition.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayPosition.java
new file mode 100644
index 0000000000..accd8ff3e3
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayPosition.java
@@ -0,0 +1,6 @@
+package net.runelite.client.ui.overlay;
+
+public enum OverlayPosition
+{
+ TOP_LEFT;
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayPriority.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayPriority.java
new file mode 100644
index 0000000000..cd6253e907
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayPriority.java
@@ -0,0 +1,8 @@
+package net.runelite.client.ui.overlay;
+
+public enum OverlayPriority
+{
+ LOW,
+ MED,
+ HIGH;
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java
new file mode 100644
index 0000000000..420b4c5d51
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java
@@ -0,0 +1,23 @@
+package net.runelite.client.ui.overlay;
+
+import java.awt.image.BufferedImage;
+import net.runelite.client.RuneLite;
+import net.runelite.client.plugins.Plugin;
+
+public class OverlayRenderer
+{
+ public void render(BufferedImage clientBuffer)
+ {
+ TopDownRenderer td = new TopDownRenderer();
+
+ for (Plugin plugin : RuneLite.getRunelite().getPluginManager().getPlugins())
+ {
+ Overlay overlay = plugin.getOverlay();
+
+ if (overlay.getPosition() == OverlayPosition.TOP_LEFT)
+ td.add(overlay);
+ }
+
+ td.render(clientBuffer);
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/TopDownRenderer.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/TopDownRenderer.java
new file mode 100644
index 0000000000..22ed9d2c1e
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/TopDownRenderer.java
@@ -0,0 +1,40 @@
+package net.runelite.client.ui.overlay;
+
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.util.ArrayList;
+import java.util.List;
+
+public class TopDownRenderer
+{
+ private static final int BORDER_TOP = 25;
+ private static final int BORDER_LEFT = 10;
+ private static final int PADDING = 10;
+
+ private final List overlays = new ArrayList<>();
+
+ public void add(Overlay overlay)
+ {
+ overlays.add(overlay);
+ }
+
+ public void render(BufferedImage clientBuffer)
+ {
+ overlays.sort((o1, o2) -> o2.getPriority().compareTo(o1.getPriority()));
+ int y = BORDER_TOP;
+
+ for (Overlay overlay : overlays)
+ {
+ BufferedImage image = clientBuffer.getSubimage(BORDER_LEFT, y, clientBuffer.getWidth() - BORDER_LEFT, clientBuffer.getHeight() - y);//(int) dimension.getWidth(), (int) dimension.getHeight());
+ Graphics2D graphics = image.createGraphics();
+ Dimension dimension = overlay.render(graphics);
+ graphics.dispose();
+
+ if (dimension == null)
+ continue;
+
+ y += dimension.getHeight() + PADDING;
+ }
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/inject/callbacks/RSCanvasCallback.java b/runelite-client/src/main/java/net/runelite/inject/callbacks/RSCanvasCallback.java
new file mode 100644
index 0000000000..f15a40e029
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/inject/callbacks/RSCanvasCallback.java
@@ -0,0 +1,34 @@
+package net.runelite.inject.callbacks;
+
+import java.awt.Canvas;
+import java.awt.Graphics;
+import java.awt.image.BufferedImage;
+import net.runelite.client.RuneLite;
+import net.runelite.client.ui.overlay.OverlayRenderer;
+
+public class RSCanvasCallback
+{
+ private final BufferedImage clientBuffer = new BufferedImage(765, 503, BufferedImage.TYPE_INT_RGB);
+ private final BufferedImage gameBuffer = new BufferedImage(765, 503, BufferedImage.TYPE_INT_RGB);
+
+ public Graphics getGraphics(Canvas canvas, Graphics superGraphics)
+ {
+ Graphics clientGraphics = clientBuffer.getGraphics();
+ clientGraphics.drawImage(gameBuffer, 0, 0, null);
+ clientGraphics.dispose();
+
+ RuneLite runelite = RuneLite.getRunelite();
+ if (runelite != null)
+ {
+ OverlayRenderer renderer = runelite.getRenderer();
+ if (renderer != null)
+ {
+ renderer.render(clientBuffer);
+ }
+ }
+
+ superGraphics.drawImage(clientBuffer, 0, 0, null);
+
+ return gameBuffer.getGraphics();
+ }
+}
diff --git a/runelite-client/src/test/java/net/runelite/client/ConfigLoaderTest.java b/runelite-client/src/test/java/net/runelite/client/ConfigLoaderTest.java
new file mode 100644
index 0000000000..ba34ce18b4
--- /dev/null
+++ b/runelite-client/src/test/java/net/runelite/client/ConfigLoaderTest.java
@@ -0,0 +1,27 @@
+package net.runelite.client;
+
+import java.io.IOException;
+import org.junit.Test;
+
+/**
+ *
+ * @author Adam
+ */
+public class ConfigLoaderTest
+{
+ @Test
+ public void test() throws IOException
+ {
+ ConfigLoader loader = new ConfigLoader();
+ loader.fetch();
+
+ for (String key : loader.getProperties().keySet())
+ System.out.println(key + ": " + loader.getProperty(key));
+
+ System.out.println("Applet properties:");
+
+ for (String key : loader.getAppletProperties().keySet())
+ System.out.println(key + ": " + loader.getAppletProperty(key));
+ }
+
+}