Skip to content
This repository has been archived by the owner on Jun 3, 2024. It is now read-only.

Adding Private Server Support

Kell Evoy edited this page May 7, 2023 · 10 revisions

Prerequisites

OSBC is meant for OSRS-based games. In other words, by default, it is only suitable for games that use the default OSRS interface style (with minor variations). Bots for games that have highly customized interfaces or use the 2005, 2006, or 2010 game frames will require additional modification to work with OSBC, and it cannot be guaranteed that the utilities will work correctly.

!! Important Note !!

This tutorial uses Zaros RSPS as an example. Much of this does not actually apply to Zaros RSPS; it's just the first RSPS that came to mind. If you are adding support for a game that is not Zaros, you can replace all instances of Zaros with the name of your game.

Adding Private Server Support

Part 1: The Baseclass

All games should have a base bot class. This class should inherit from Bot or RuneLiteBot (if it has RuneLite support).

  1. Create a new folder in src/model/ that will contain all bots for the RSPS. The name of the folder should be the name of the RSPS, or an abbreviation of it.
  2. Within this folder, create a new Python file. The name of the file should be the name of the RSPS, or an abbreviation of it, followed by _bot.py. For example, for the RSPS Zaros, the file would be named zaros_bot.py and look like this:
from abc import ABCMeta

from model.runelite_bot import RuneLiteBot, RuneLiteWindow

class ZarosBot(RuneLiteBot, metaclass=ABCMeta):
    win: RuneLiteWindow = None

    def __init__(self, bot_title, description) -> None:
        super().__init__("Zaros", bot_title, description, RuneLiteWindow("Zaros"))

As you can see, this class inherits from RuneLiteBot, as well as ABCMeta. This means that ZarosBot is an abstract class, and cannot be instantiated. Therefore, when we create a new bot for Zaros, we will need to create a new class that inherits from ZarosBot.

The notable features of this class are as follow:

  • The super().__init__() function is called with "Zaros" as the first argument. This is the name of the RSPS, and will be used to tell OSBC where Zaros bots should exist on the user interface, and what settings file to use when launching RuneLite (since this game is RuneLite-based).
  • The super().__init__() function is also passed an instance of RuneLiteWindow with "Zaros" as the argument. This allows the Window utility to locate the Zaros game window while it is running. If your game has a slightly customized interface, you may need to extend the RuneLiteWindow() class to add additional functionality. We will discuss this in a later section.

Part 2: The Bot

Now that we have a base class, we can create a new bot for Zaros. This bot will inherit from ZarosBot.

  1. Copy the src/model/osrs/template.py file to the folder you created in Part 1. Rename the file to the name of the bot you are creating. For example, if you are creating a bot for Zaros called ZarosWoodcutter, the file could be named woodcutter.py. Here's what the class might look like:
import time
from model.zaros.zaros_bot import ZarosBot

class ZarosWoodcutter(ZarosBot):
    def __init__(self):
        bot_title = "Woodcutter"
        description = "This bot chops logs in Zaros RSPS."
        super().__init__(bot_title=bot_title, description=description)
        # Set option variables below (initial value is only used during UI-less testing)
        self.running_time = 1

    def create_options(self):
        self.options_builder.add_slider_option("running_time", "How long to run (minutes)?", 1, 500)

    def save_options(self, options: dict):
        for option in options:
            if option == "running_time":
                self.running_time = options[option]
            else:
                self.log_msg(f"Unknown option: {option}")
                print("Developer: ensure that the option keys are correct, and that options are being unpacked correctly.")
                self.options_set = False
                return
        self.log_msg(f"Running time: {self.running_time} minutes.")
        self.log_msg("Options set successfully.")
        self.options_set = True

    def main_loop(self):
        # Main loop
        start_time = time.time()
        end_time = self.running_time * 60
        while time.time() - start_time < end_time:
            # -- Perform bot actions here --

            self.update_progress((time.time() - start_time) / end_time)

        self.update_progress(1)
        self.log_msg("Finished.")
        self.stop()

This bot is practically the same as the original template. The only real difference is that it inherits from the new game baseclass instead.

  1. Inside your src/model/zaros folder, add an __init__.py file. This file is required to tell OSBC where to find your bot classes in order to add them to the UI dynamically. The contents of this file should be as follows:
from .woodcutter import ZarosWoodcutter

Part 3: RuneLite Settings

If your game is based on RuneLite, you will need to create a new settings file for it. This file will be used to launch RuneLite with the correct settings for your game. Creating a good base settings file for your RSPS can be challenging. Using Zaros RSPS as the example again, here's the best way to start:

  1. In src/runelite_settings/, duplicate the osrs_settings.properties file and name it zaros_settings.properties.
  2. Launch OSBC.
  3. In the navigation pane, you should be able to select Zaros from the dropdown menu and see the Zaros bot you created in Part 2.
  4. Go ahead and launch Zaros RSPS using the Launch Zaros button in the Home View.
    1. You may need to locate the executable file first.
  5. Log into the game and take a look at the RuneLite settings it has. Ideally, we'd like it to be as similar to the osrs_settings.properties as possible - but due to RSPS's often having mismatched RuneLite versions, some of the settings from this duplicated file may not have applied.
    1. Do your best to modify the plugins that may not have been configured properly.
  6. When you're satisfied with the settings, log out and close RuneLite. Your settings have been saved to src/runelite_settings/temp.properties.
  7. Edit src/runelite_settings/temp.properties file, removing any lines that contain rsprofile. These lines may expose your account name, so it is best to remove them.
  8. Delete the zaros_settings.properties file you created in Step 1, and rename temp.properties to zaros_settings.properties to replace it.

Now, when you launch Zaros RSPS using OSBC, it will use the settings you configured in zaros_settings.properties.

Part 4: The Window Class

If your game has a slightly customized interface, you may need to extend the RuneLiteWindow() class to add additional functionality. This is especially true if your game has a custom title bar, custom minimap frames, or if you want to add additional functionality to the Window utility.

Before you go ahead and create a new window class, you should first try to use the default RuneLiteWindow class. If it works, you don't need to create a new window class.

  1. In your game's baseclass, create an extension of RuneLiteWindow:
from abc import ABCMeta

from model.runelite_bot import RuneLiteBot, RuneLiteWindow

class ZarosWindow(RuneLiteWindow):
    def __init__(self) -> None:
        """
        In this init function, we can set the padding for the window.
        E.g., `padding_top` refers to the number of pixels between the top of the Zaros window
        to where the game view actually starts.
        """
        super().__init__("Zaros", padding_top=26, padding_left=0)

class ZarosBot(RuneLiteBot, metaclass=ABCMeta):
    win: RuneLiteWindow = None

    def __init__(self, bot_title, description) -> None:
        super().__init__("Zaros", bot_title, description, ZarosWindow())

Now, instead of creating ZarosBot with a RuneLiteWindow("Zaros") reference, we can just use ZarosWindow() instead. Since Zaros' UI is pretty much a perfect match with OSRS, we're done! All we need to do is create some bot scripts that inherit from ZarosBot, and they'll be good to go.

Part 5: Supporting Private Servers with Custom UI

NOTE: Adding support for RSPSs with custom user interface styles can be tedious. It is recommended that you be comfortable with Photoshop, and have a decent understanding of the Window utility's initialization functions.

Let's pretend that Zaros RSPS has a custom minimap that looks something like this.

zaros_map

OSBC needs to identify the UI on bot startup, and it will fail since it can't find the original OSRS-style minimap. To fix this, we can photoshop the Zaros minimap such that it only contains the frame of the minimap (i.e., the parts of the minimap that never change). This typically involves importing a screenshot of the entire game window into Photoshop, cropping the minimap, then deleting pixels.

Tip: To delete a perfect circle in Photoshop, equip the Elipse Selection tool, move your cursor to the center of the circular area to be deleted, hold shift and alt, click & drag to cover the area, release the mouse, and press delete.

zaros_map_transparent

This image is not the same UI style as the one above, but for the sake of example, pretend it is.

Photoshop Export Settings

If you're using Photoshop to delete pixels from your images, be sure to export them with the following settings:

image

Enabling Smaller File (8-bit) will make sure your image does not include any semi-transparent pixels that may have survived the deletion process. Preserve Details will prevent Photoshop from processing your image on export, which could make it appear different from the true UI in the game.

Overriding Functions to Locate UI Regions

Now, let's override the __locate_minimap() function of the Window class. This function is called at bot start to locate the minimap and its inner components. By defining our own function here, we can tell OSBC to look for the new minimap frame instead. A good course of action would be to copy the code from the Window class, then modify it accordingly.

class ZarosWindow(RuneLiteWindow):
    def __init__(self) -> None:
        """
        In this init function, we can set the padding for the window.
        E.g., `padding_top` refers to the number of pixels between the top of the Zaros window
        to where the game view actually starts.
        """
        super().__init__("Zaros", padding_top=26, padding_left=0)
    
    def __locate_minimap(self, client_rect: Rectangle) -> bool:
        """
        Locates the minimap area on the clent window and all of its internal positions.
        Args:
            client_rect: The client area to search in.
        Returns:
            True if successful, False otherwise.
        """
        # 'm' refers to minimap area
        if m := imsearch.search_img_in_rect("path to our new minimap", client_rect):
            self.client_fixed = False
            self.compass_orb = Rectangle(...)
            self.hp_orb_text = Rectangle(...)
            self.minimap = Rectangle(...)
            self.prayer_orb = Rectangle(...)
            self.prayer_orb_text = Rectangle(...)
            self.run_orb = Rectangle(...)
            self.run_orb_text = Rectangle(...)
            self.spec_orb = Rectangle(...)
            self.spec_orb_text = Rectangle(...)
            self.total_xp = Rectangle(...)
            return True
        else:
            # locate the fixed version of the minimap...
            # omitted for brevity
        ...
        print("Window.__locate_minimap(): Failed to find minimap.")
        return False

Now, this function will be used to locate the minimap instead of the default one.

Tip: The Rectangle Selection tool in Photoshop can be used within your custom template to retrieve the exact values needed to produce a Rectangle() in code.

image

# Image search your minimap template. This returns its Rectangle position relative to the whole screen.
m: Rectangle = imsearch.search_img_in_rect(...)

# Store the location of the HP text.
# We use the values from Photoshop + the found template to store the location
# relative to the entire computer screen.
self.hp_orb_text = Rectangle(left=4 + m.left, top=55 + m.top, width=20, height=13)

This is a very simplified example of how to define a custom window class. You may need to do this for any/all of the 3 core UI components (chatbox, minimap, or control panel). In 90% of cases, you'll just need to replace the minimap as we did in this guide, as some RSPSs tend to modify it. To maintain the full feature set of OSBC, you should make sure each inner property of the UI component is defined (E.g., hp_orb_text, compass_orb, etc.).