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 Feb 3, 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 gameframes 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.

Custom UI Elements

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).

zaros_map_transparent

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.

This is a very simplified example of how to define a custom window class.