-
Notifications
You must be signed in to change notification settings - Fork 141
Adding Private Server Support
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.
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.
All games should have a base bot class. This class should inherit from Bot
or RuneLiteBot
(if it has RuneLite support).
- 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. - 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 namedzaros_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 ofRuneLiteWindow
with"Zaros"
as the argument. This allows theWindow
utility to locate the Zaros game window while it is running. If your game has a slightly customized interface, you may need to extend theRuneLiteWindow()
class to add additional functionality. We will discuss this in a later section.
Now that we have a base class, we can create a new bot for Zaros. This bot will inherit from ZarosBot
.
- 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 calledZarosWoodcutter
, the file could be namedwoodcutter.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.
- 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
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:
- In
src/runelite_settings/
, duplicate theosrs_settings.properties
file and name itzaros_settings.properties
. - Launch OSBC.
- 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.
- Go ahead and launch Zaros RSPS using the
Launch Zaros
button in the Home View.- You may need to locate the executable file first.
- 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.- Do your best to modify the plugins that may not have been configured properly.
- When you're satisfied with the settings, log out and close RuneLite. Your settings have been saved to
src/runelite_settings/temp.properties
. - Edit
src/runelite_settings/temp.properties
file, removing any lines that containrsprofile
. These lines may expose your account name, so it is best to remove them. - Delete the
zaros_settings.properties
file you created in Step 1, and renametemp.properties
tozaros_settings.properties
to replace it.
Now, when you launch Zaros RSPS using OSBC, it will use the settings you configured in zaros_settings.properties
.
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.
- 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.
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.
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
.
This image is not the same UI style as the one above, but for the sake of example, pretend it is.
If you're using Photoshop to delete pixels from your images, be sure to export them with the following settings:
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.
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 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.).