1. This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn More.
  2. Hey Guest, If you'd like to instant message with many other users, then join our
    Official Rune-Status Discord Server!
    Dismiss Notice
  3. Got your own Server? Looking for a Server to play right now??
    Make sure you check out the Public Server Directory!
    Dismiss Notice
  4. If you're new to the Rsps Scene and want to jump straight into learning how to Set-up and Customise your own RuneScape Private Sever then check out the Guides and Tutorials for RS2 here.
    Dismiss Notice
Dismiss Notice
Hey Guest,
Are you sure your posting in the Right Sections?
If your unsure which RuneTek version you are working with visit this page to find out.

Adding the 2001/6 Menu to Client with JFrame

Discussion in 'Guides & Tutorials' started by Ian, Oct 24, 2017.

  1. Hey guys,
    In this guide I'm going to explain how you can use Java Swing to add a nice Menu Bar and JFrame to your Client frame. Throughout this guide I will be using 'Majors-renamed-317' Client this is what Clients such as nshusa's Astraeus are based on so I thought I would use this as the base for the guide as It is strictly a 317 Client.

    You can grab a fresh copy of Majors 317 project from here:

    Note: Now it's worth making a note that I haven't added any features to this to take in account of features that may have been added to clients which would affect this such as Resizing of the client.

    I know this is not a perfect visualisation of what the Clients menu was back in those days when it was being used by the Official Web Client and its worth saying that back then it was done using Html. So doing this by making use of the Swing utilities has been something that i feel may have been overlooked by many of the true-to-form 317 Server developers.

    Here is an image of what we will be adding during this guide:
    [​IMG]

    As you can see I have added a slick new Themed frame which makes use of the Substance Swing Look-And-Feel Library by Insubstantial albeit prepackaged for ease of use. Using this library gives us the option of choice for 24 'Themes'.​
    [​IMG]
    Also I made use of the JMenuBar Swing component to create what I feel is pretty damn close to resembling the HTML Menu Bar of the RS2 Web client era. Here is an image I found on google to give an example of what I've tried to achieve with this:​
    This menu bar was from around 317-377:
    [​IMG]

    As you can see and I think you would agree that I almost got it exact to the original though of cause there is room for improvement; I chose this menu bar from around the 317 era to fit in with the Client a bit better though there are some other iterations of the Official menu bar that were run for lengths of time. There was also the fact that the 'Upgrade to Members' wouldn't be shown for Members, Check these other versions of the Official menu bar:​

    This is the menu bar was use during Pre-317:
    [​IMG]

    This is the menu bar which was used around the times of 474:
    [​IMG]

    The most notable difference here being the 474 era version, they had a change in the Logo and also switched out the 'Manual' button for a search bar which would of cause search the RuneScape Knowledge base instead of just linking too it for easier access to information, they also dropped the '& Security' to make space for the new search function. Adding a Search bar could be achieved doing this but I Feel would be overkill for a guide like this, though I'd love to see images of anyone who completes that.

    Now on to how to add this to our Clients:
    I've attempted to make this as straight forward and drag and drop as possible though I will explain the process of what is happening though out - first of all lets take a look at the ClientFrame.java file which holds the majority of what we will be adding/changing in our clients:
    package org.major.client;
     
    import java.awt.BorderLayout;
    import java.awt.Color;
    import java.awt.Component;
    import java.awt.Desktop;
    import java.awt.Dimension;
    import java.awt.Font;
    import java.awt.Graphics;
    import java.awt.Image;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.awt.image.BufferedImage;
    import java.lang.reflect.Method;
    import java.net.URI;
    import java.util.HashMap;
     
    import javax.imageio.ImageIO;
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JMenuBar;
    import javax.swing.JPanel;
    import javax.swing.UIManager;
     
    import com.jagex.Client;
     
    @SuppressWarnings("serial")
    public class ClientFrame extends Client implements ActionListener {
     
        public ClientFrame() {
            try {
                JFrame.setDefaultLookAndFeelDecorated(true);
                UIManager.setLookAndFeel("org.jvnet.substance.skin.SubstanceRavenLookAndFeel");
     
                JFrame jFrame = new JFrame("RuneScape - the massive online adventure game by Jagex Ltd");
     
                JPanel jPanel = new JPanel();
                jPanel.setPreferredSize(new Dimension(765, 503));
                jPanel.setLayout(new BorderLayout());
                jPanel.add(this); // Client
     
                JMenuBar jMenuBar = new JMenuBar() {
                    @Override
                    public void paintComponent(Graphics graphics) {
                        Dimension dim = this.getSize();
                        graphics.drawImage(openImage("/org/major/client/images/MENU_BAR.gif"), 0, 0, dim.width, dim.height,
                                this);
                    }
                };
                jMenuBar.setPreferredSize(new Dimension(765, 25));
     
                JButton logoImageButton = new JButton("LOGO_BUTTON") {
                    @Override
                    public void paintComponent(Graphics graphics) {
                        graphics.drawImage(openImage("/org/major/client/images/JAGEX_LOGO.gif"), 2, -4, null);
                    }
                };
     
                JButton mainMenuButton = new JButton("MAIN_MENU_BUT") {
                    @Override
                    public void paintComponent(Graphics graphics) {
                        graphics.drawImage(openImage("/org/major/client/images/MAIN_MENU_WORLD_SELECT.gif"), 12, -2, null);
                        drawUnderlinedString(graphics, "Main Menu", 39, 14);
                    }
                };
                mainMenuButton.addActionListener(this);
     
                JButton worldSelectButton = new JButton("WORLD_SELECT_B") {
                    @Override
                    public void paintComponent(Graphics graphics) {
                        graphics.drawImage(openImage("/org/major/client/images/MAIN_MENU_WORLD_SELECT.gif"), 4, -2, null);
                        drawUnderlinedString(graphics, "World Select", 30, 14);
                    }
                };
                worldSelectButton.addActionListener(this);
     
                JButton worldMapButton = new JButton("WORLD_MAP_BUT") {
                    @Override
                    public void paintComponent(Graphics graphics) {
                        graphics.drawImage(openImage("/org/major/client/images/WORLD_MAP.gif"), 6, -4, null);
                        drawUnderlinedString(graphics, "World Map", 34, 14);
                    }
                };
                worldMapButton.addActionListener(this);
     
                JButton manualButton = new JButton("MANUAL_BUTT") {
                    @Override
                    public void paintComponent(Graphics graphics) {
                        graphics.drawImage(openImage("/org/major/client/images/MANUAL.gif"), 2, -2, null);
                        drawUnderlinedString(graphics, "Manual", 42, 14);
                    }
                };
                manualButton.addActionListener(this);
     
                JButton rulesAndSecurityButton = new JButton("RULES_AND_SECURIT") {
                    @Override
                    public void paintComponent(Graphics graphics) {
                        graphics.drawImage(openImage("/org/major/client/images/RULES_AND_SECURITY.gif"), 6, -2, null);
                        drawUnderlinedString(graphics, "Rules & Security", 30, 14);
                    }
                };
                rulesAndSecurityButton.addActionListener(this);
     
                JButton upgradeToMembersButton = new JButton("UPGRADE_TO_MEMBERS") {
                    @Override
                    public void paintComponent(Graphics graphics) {
                        graphics.drawImage(openImage("/org/major/client/images/UPGRADE_TO_MEMBERS.gif"), 4, -2, null);
                        drawUnderlinedString(graphics, "Upgrade to Members", 22, 14);
                    }
                };
                upgradeToMembersButton.addActionListener(this);
     
                jMenuBar.add(logoImageButton);
                jMenuBar.add(mainMenuButton);
                jMenuBar.add(worldSelectButton);
                jMenuBar.add(worldMapButton);
                jMenuBar.add(manualButton);
                jMenuBar.add(rulesAndSecurityButton);
                jMenuBar.add(upgradeToMembersButton);
     
                jFrame.add(jPanel, "Center");
                jFrame.add(jMenuBar, "North");
                jFrame.pack();
     
                setJFrameIcon(jFrame);
     
                jFrame.setVisible(true);
                jFrame.setResizable(false);
                jFrame.setLocationRelativeTo(null);
                jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
     
                init();
     
            } catch (Exception e) {
                // ignored.
            }
     
        }
     
        @Override
        public void actionPerformed(ActionEvent event) {
            String actionCommand = event.getActionCommand();
            try {
     
                if (actionCommand.equalsIgnoreCase("MAIN_MENU_BUT")) {
                    openURI("http://rune-status.net/");
                }
     
                if (actionCommand.equalsIgnoreCase("WORLD_SELECT_B")) {
                    openURI("http://rune-status.net/servers/");
                }
     
                if (actionCommand.equalsIgnoreCase("WORLD_MAP_BUT")) {
                    openURI("http://rune-status.net/tools/map");
                }
     
                if (actionCommand.equalsIgnoreCase("MANUAL_BUTT")) {
                    openURI("http://rune-status.net/help/");
                }
     
                if (actionCommand.equalsIgnoreCase("RULES_AND_SECURITY")) {
                    openURI("https://rune-status.net/help/terms");
                }
     
                if (actionCommand.equalsIgnoreCase("UPGRADE_TO_MEMBERS_")) {
                    openURI("https://rune-status.net/index.php?account/upgrades");
                }
     
                // System.out.println(actionCommand + " clicked.");
     
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
     
        private static void openURI(String uri) {
            Desktop desktop = Desktop.getDesktop();
            try {
                desktop.browse(new URI(uri));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
     
        public static BufferedImage openImage(String imagePath) {
            final HashMap<String, BufferedImage> IMAGE_CACHE = new HashMap<>();
            if (IMAGE_CACHE.containsKey(imagePath))
                return IMAGE_CACHE.get(imagePath);
            try {
                final BufferedImage srcImage = ImageIO.read(ClientFrame.class.getResourceAsStream(imagePath));
                IMAGE_CACHE.put(imagePath, srcImage);
                return srcImage;
            } catch (Throwable t) {
                throw new RuntimeException("Error opening Image: " + t.getMessage());
            }
        }
     
        public void drawUnderlinedString(Graphics graphics, String string, int x, int y) {
            Font currentFont = graphics.getFont();
            Font newFont = currentFont.deriveFont(currentFont.getSize() * .89F);
            graphics.setFont(newFont);
            graphics.drawString(string, x, y);
            graphics.drawLine(x, y + 1, x + getFontMetrics(newFont).stringWidth(string), y + 1);
            graphics.setColor(Color.WHITE);
        }
     
        public static void setJFrameIcon(JFrame jFrame) {
            try {
                jFrame.setIconImage(openImage("/org/major/client/images/ICON.gif"));
                if (System.getProperty("os.name").contains("mac")) {
                        Class<?> util = Class.forName("com.apple.eawt.Application");
                        Object application = util.getMethod("getApplication", new Class[] {}).invoke(null);
                        Method setDockIconImage = util.getMethod("setDockIconImage", new Class[] { Image.class });
                        setDockIconImage.invoke(application, openImage("/org/major/client/images/ICON.gif"));
                }
            } catch (Exception e) {
                // ignored.
            }
        }
     
    }

    When this file is called the program will go through adding the elements to the JFrame we have added, before this though it will set the Look-and-Feel using the UIManager (we can change this to change the Theme of the JFrame but more on that later) from there we then create a JPanel inside the of which contains out actual Client -> we then add our JMenuBar with the elements needed to build that and by taking advantage of the paintComponent(Graphics) override we are able to customise our elements, This is how I have painted the menu bar with the background and icons for the JButtons which are attached to the JMenuBar. We add actionListeners to all the buttons as to be able to complete a task on the Click of the buttons which makes them (for now) open the Links to various pages on Rune-Status by making use of the openURI method which has been added.

    We finally pack all the components onto the JFrame and add a JFrame Icon (which is done using the setJFrameIcon method I have added - this includes a work arround for Mac OS's to add the icon to the Dock image - hence why our icon is 32x32 px in size.) Once done we set our JFrame as visible and make sure it is centered to the Screen by making use of the setLocationRelativeTo(null); line and ensude that the Client is closed properly by using setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); line, Though most of you will be able too see that from the code :emoji_grinning: .

    So obviously you wanna add this file to your client, I had it in the org.major.client package though you can change this if you like.

    You WILL need to add the images to the client too, the same package, i just added a folder called /images/ to the org.major.client package, this way you know that when it comes to creating an executable Jar for the client, the Images are already pre-packed, though you could go a step further and add these to the cache, though you may run into issues where by the images haven't loaded in time for the Frames visualisation.

    Download the attached Images.rar file and extract the folder where ever you see fit. If you have different package naming though you will need to go through each of the lines using the openImage method and change the source path for example:
    graphics.drawImage(openImage("/org/major/client/images/JAGEX_LOGO.gif"), 2, -4, null);

    That is pretty much it for the ClientFrame.java file - now all we have left to do is to make the changes to the Client itself to make it work within the ClientFrame instead of the GameFrame which becomes redundant after using this. GameFrame makes use of java.awt where as we are now making use of javax.swing..

    So once you are in your Client.java navigate to the main method, we need to make some changes to this to make it run through our new ClientFrame instea of the GameFrame, you will note that on a fresh copy of Majors renamed it still relies on runtime arguments to get it started, however I just removed these and gave them the default values to be able to run without any Arguments:
    Here is mine:​
    public static void main(String...args) {
    System.out.println("RS2 Client deob - revision " + 317);
    try {
    node = 10;
    portOffset = 0;
    setHighMemory();
    membersServer = true;
    SignLink.setStoreId(32);
    SignLink.startPriv(InetAddress.getByName(IP));
    new ClientFrame();
    // Client client = new Client();
    // client.initFrame(503, 765);
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    
    new ClientFrame(); being the key line added, this runs an instance of the JFrame from which the clients init() is eventually called once the frame has been setup.

    We also need to do the same for the init method, the same priniciples apply for this so here is my method:
     @Override
    public final void init() {
    try {
    node = 10;
    portOffset = 0;
    setHighMemory(); // setLowMemory();
    membersServer = true;
    SignLink.setStoreId(32);
    SignLink.startPriv(InetAddress.getByName(IP));
    this.startApplet(503, 765);
    } catch (Exception e) {
    ;
    }
    }

    Next we need to make a slight change to our getCodeBase method, which has a check to see if the frame instance is null or not, however seeing as this is part of the GameFrame system of visualising the Client its now irrelevant and we should either remove or comment this out, here is my method for reference:
    
    @Override
    public URL getCodeBase() {
    if (SignLink.getApplet() != null) {
    return SignLink.getApplet().getCodeBase();
    }
    try {
    //if (super.frame != null) {
    return new URL("http://" + IP + ":" + (80 + portOffset));
    //}
    } catch (Exception ex) {
    ex.printStackTrace();
    }
    return super.getCodeBase();
    }
    

    And last but not least, and a big shout out to @De0 for figuring this pain in the arse for me (also shout outs too Tommeh, Kris and also Oly for taking the time out of the projects to have a look into this for me :emoji_grinning:) we need to make a change to two more methods, firstly the getFrame method and secondly the getGraphics method;

    The getFrame method is pretty much doing what the getCodeBase method was doing so what we need to do is make this method simply return this - here is my method for reference:
    @Override
    public final Component getFrame() {
    // if (SignLink.getApplet() != null) {
    // return SignLink.getApplet();
    // } else if (super.frame != null) {
    // return super.frame;
    // }
    return this;
    }

    and this was the killer :emoji_grinning: the getGraphics from the GameApplet.java was overwriting the proper method and was sending nothing but null as the GameFrame file hadnt been initialised - this is what Major added for what ever reason and this was causing the Client not to show inside the jPanel from the ClientFrame method, this needed to be removed so simply comment this out or remove as you please:
    // @Override
    // public Graphics getGraphics() {
    // return graphics;
    // }

    The last thing we need to do to complete this guide is to ensure that we have added the substance-ui.jar to our Classpath, now because we are adding an extra jar to our Client we need to ensure that when we are creating an executable Jar of the Client (in Eclipse) we need to make sure we check the 'Package required libraries into generated JAR' so as to pack the extra Jar archives into our Client.jar.

    And that is pretty much it - we're done. If you followed through and read you should of been able too or should be able to add a nice new modern looking 2001/06'esk Frame and Menu to your Client.

    Thanks for reading,
    - Ian.​
     

    Attached Files:

    #1 Ian, Oct 24, 2017
    Last edited: Oct 24, 2017
  2. Gj mate, looks decent af :)
     
  3. very nice thanks for that one!
     
  4. Been messing with swing and didn't know about look N Feel, default look looks shit
     
Loading...