how to write plugins for OpenKore

From OpenKore Wiki
Revision as of 00:19, 27 April 2021 by 4epT (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

A plugin contains a file with Perl code. It is not part of OpenKore, and can be loaded at runtime. Plugins can add new features to OpenKore or modify existing behavior (using hooks).

Plugins depend on OpenKore's internals, so to be able to write plugins, one must be familiar with OpenKore's internals or to be able to inspect them when needed (not so hard due to OpenKore being generally written with the same Perl as your plugins and with full source code provided).

Plugins in OpenKore's GitHub page can be used as examples.

Plugins compared with patches (code edits):

  • Easier to update OpenKore, as your modifications are independent.
  • Easier to manage and share.
  • Takes some effort to understand how plugins interact with OpenKore at first.

Plugins compared with macros:

  • All advantages of real, modern, universal programming language, if you're doing anything beyond simple sequences of bot actions. Macro plugin's language and interpreter have poor design with a lot of drawbacks.
  • No bugs (including possible security risks) of macro plugin itself.
  • Hooks support in macro plugin isn't the best.
  • Perl (or, provided there's examples in the form of other plugins - just programming) skills are needed.
  • Takes some effort to understand how plugins interact with OpenKore at first. However, good understanding of how to properly do complicated things in macro may take a lot of effort, too.
  • Macro plugin's @eval() or subs for anything beyond arithmetic then it's Perl anyway.

Your first plugin: Hello World

Here's an example Hello World plugin. Save it as helloworld.pl in your plugins folder.

package HelloWorld;

use strict;

print "Hello World!\n";

1;

When you start OpenKore, you will see this in the console:

Loading plugin helloworld.pl...
Hello World!

Analysis

As you can see, the Perl code in your plugin is directly executed.

A plugin should always start with the package keyword, followed by a package name. This is not required, but is a good idea, because this way you can prevent function name clashes with the rest of OpenKore. If you're not familiar with the package keyword, read the Perl documentation.

It must also end with 1; or return 1;. Otherwise, an error message will be displayed when the plugin is loaded.


Moving on: using hooks

The above plugin is not really useful. Here's a slightly more complex plugin. This plugin will print "Hello!" every time OpenKore's AI() function has been called.

package ExamplePlugin;

use strict;
use Plugins;
use Log qw(message);

Plugins::register("example", "Example Plugin", \&on_unload, \&on_reload);
my $aiHook = Plugins::addHook("AI_pre", \&on_AI);

sub on_unload {
	# This plugin is about to be unloaded; remove hooks
	Plugins::delHook("AI_pre", $aiHook);
}

sub on_reload {
	&on_unload;
}

sub on_AI {
	message "Hello!\n";
}

1;

Registration

Plugins must register themselves with Plugins::register(). This will add the plugin's name to the plugin list. The user can use that name to unload this plugin. "example" is a short plugin name. "Example Plugin" is a short and slightly more detailed human-readable description of the current plugin.

Hooks

The above example adds a hook for "AI_pre". Whenever the function AI() in OpenKore is called, this plugin's on_AI() function is called too. Within this function, you can do whatever you want. This is how you extend OpenKore's functionality.

Tip: If you want to add many hooks, you should use use Plugins::addHooks() (notice the 's'). This way you don't have to manually delete every single hook you added.

You can find more useful hooks in hooks documentation or in the source code. If there's a certain hook that would be helpful, but it isn't in the source code, you can add it yourself. In that case, please submit a patch, and it might be incorporated into the official codebase.

Unloading

Plugins::register() allows you to specify an unload callback. Whenever the user tells OpenKore to unload or reload the plugin, or when OpenKore is about to exit, this unload callback will be called. The point of this callback is to remove any hooks you added, and to shutdown whatever you need to shutdown (such as closing sockets, deleting temporary files, or whatever).

You don't have to provide an unload callback if your plugin doesn't add any hooks, and doesn't need to shutdown or cleanup anything.

Reloading

The reload callback is very similar to the unload callback. When the user tells OpenKore to reload the plugin, the reload callback callback is called: the unload callback is not called.

The point of the reload callback is that you can choose to only shutdown a few things, but not everything, because you know you will need some things again (because the plugin is about to be reloaded). For example, your plugin opens a socket connection with a certain server. You can close the socket in the unload callback, but not in the reload callback, so you can immediately reuse the socket when the plugin is reloaded.

Usually you don't need to special-case reloading. If you do not pass a reload callback to Plugins::register(), then the unload callback will be automatically called when your plugin is being reloaded.

Adding Console Commands

Console Commands

Network packet handler hooks

OpenKore 1.9 and up has a new network packet parser. In the intialization phase of this network packet parser, a list of packet switches and their associated handler functions are defined. There is a hook for every packet that OpenKore supports.

The list of supported packets (the packet handler definition list) is initialized in the method new() in src/Network/Receive.pm. Here's a fragment of the code:

# Defines a list of Packet Handlers and decoding information
# 'packetSwitch' => ['handler function','unpack string',[qw(argument names)]]

$self{packet_list} = {
        '0069' => ['account_server_info', 'x2 a4 a4 a4 x30 C1 a*', [qw(sessionID accountID sessionID2 accountSex serverInfo)]],
        '006A' => ['login_error', 'C1', [qw(type)]],
        ...

Each packet handler has two hooks: a pre and a post hook.

  • The pre hook is called before the packet handling function is called. This allows you to change the parsed packet's arguments. The hook's name is packet_pre/$handlerFunctionName. For instance, the pre hook for 'account_server_info' is "packet_pre/account_server_info"
  • The post hook is called after the packet handling function is called. The name is packet/$handlerFunctionName. For instance, the post hook for "login_error" is "packet/login_error"

Beware that these hooks only exist in OpenKore 1.9 and up!


Where to put plugins to?

Plugins are stored in the plugins folder (if it doesn't exist, create it). Files with the .pl extension in the plugins folder are automatically loaded by OpenKore.

OpenKore also loads .pl files inside the first subfolder of the plugins folder. So if your plugin depends on external data files, you can put them in the same subfolder inside the plugins folder.

Example directory structure:

openkore.pl
plugins/helloworld.pl (this file will be loaded)
plugins/MyPluginPack/blabla.pl (this file will be loaded too)
plugins/MyPluginPack/supericons.txt
plugins/MyPluginPack/subfolder/abc.pl (this will NOT be loaded)

Keep in mind that the plugins folder doesn't have to be plugins. The user can choose to use a different folder as plugins folder, by passing the --plugins=(folder name) argument to OpenKore.

How to locate data files for your plugin

Let's say your plugin depends on a data file called supericons.txt. You can ask the user to copy supericons.txt to the controls folder manually, but a much better way would be to put supericons.txt in the same folder as your plugin. This way, the user only has to extract all files to the plugins folder, and the plugin will Just Work(tm), without any additional configuration.

But how do you locate where supericons.txt is? You can't just hardcode "plugins/MyPluginPack/supericons.txt", because the user might have specified a different folder as plugins folder, or renamed "MyPluginPack" to something else. How do you locate where your .pl file is?

The answer is in the variable $Plugins::current_plugin_folder. Whenever your plugin is being loaded, that variable's value is set to the name of the plugin's folder. You must immediately save the value in your own variable! Because when the next plugin is being loaded, the variable is set to a different value.

Example:

package BlaBla;

use strict;
use Plugins;
use Log qw(message);

Plugins::register("blabla", "Example plugin which demonstrates how to locate data files", \&on_unload);
my $aiHook = Plugins::addHook("AI_pre", \&on_AI);
my $datadir = $Plugins::current_plugin_folder;

sub on_unload {
	Plugins::delHook("AI_pre", $aiHook);
}

sub on_AI {
	message "supericons.txt is located in this folder: $datadir\n";
}

1;