Skip to content

Plugin Development

wsrvn edited this page Mar 24, 2024 · 18 revisions

Making a Plugin

Note

This only covers the most basic of things, such as listening on events and creating commands.

It is assumed you have had at least some minor experience in making plugins for (X)ASECO, with examples from there.

Getting started

  1. Before starting, it is a good idea to know the language you will be working in, TypeScript in our case. If you do not, it is not impossible to make a plugin, it simply will be much more of a burden than it needs to be.
  2. Make sure you have all the requirements (and that everything is up to date) as specified in the README.md file.
  3. Create your plugin files in the /plugins/ directory. If there are multiple files, it is best to create a directory specifically for them not to make a mess. If your plugin spans multiple files, we suggest creating a file that will import all other files.
  4. Include your newly created files via import statements inside the Plugins.ts file. The import extension MUST be .js still.
  5. Optionally, enable watch-mode compilation with npx tsc watch so that you are not required to type npx tsc after every change.
  6. You can now write code for your very own plugin, good luck!

Things to know

It might be useful to check the available controller methods first for you to be aware of what is currently possible. Do remember however, that page is not always up-to-date.

If a certain getter, function, etc. is not in the Trakman namespace and you are 100% sure it should be there, open an issue and let us know.

Do not forget, despite this being a modern app written in a modern language, the game we are interacting with is over 15 years old with little to no support from the developers. Some things are straight up impossible to implement (e.g. using ManiaScript), which is, well, unfortunate.

Events

This is a fairly trivial task in any of the available controllers, and exactly what makes them "automatic".

Adding a callback to a certain event

(X)ASECO

First of all, we must call the static method registerEvent of the Aseco class, with the 2 arguments being the event name, and the callback name:

Aseco::registerEvent('onPlayerFinish', 'callback');

Afterwards, it only makes sense to add our callback function later in the file:

function callback (mixed $params): void {
 # ... Code to be executed ...
}

This will execute callback on the PlayerFinish event.

Trakman

Due to difference in language and overall coding style, our system may appear more complex.

Note

Before you continue on, make sure you have read the events list wiki page, otherwise you will not know the return type of certain events.

Start by invoking the addListener method in the Trakman namespace (tm):

tm.addListener(`PlayerChat`, async (info: tm.MessageInfo) => { /** ... Code to be executed ... **/ })

Note

Every element of type tm.Event must include event and callback properties, former being the event to execute the function on, and latter the function itself, be it a lambda or something else.

The code above will execute the callback function (in this case, a lambda function) on the PlayerChat event.

Commands

Commands are valuable additions to the controller, allowing players to interact with it, or the dedicated server directly.

Adding chat commands

(X)ASECO

First of all, we must call static method addChatCommand of the Aseco class, with the 3 arguments being the command name, the help message and a boolean indicating whether it is an admin command (all that does is hide it from user /help command):

Aseco::addChatCommand('ilovephp', 'Show others that you love PHP!', false);

After that, you must add the command code itself, note how every command MUST be prefixed with chat_:

function chat_ilovephp(mixed $aseco, mixed $command): void {
 # ... Code to be executed ...
}

We can add an alias to it too:

function chat_ilp(mixed $aseco, mixed $command): void {
 chat_ilovephp(mixed $aseco, mixed $command);
}

Trakman

Each object of type tm.Command has 5 total properties:

Property Type Description
aliases String[] An array of strings containing all possible aliases for your command. There is no limit
help String (optional) A string containing the command help, or generally whatever you would like. Leaving it empty makes the command invisible in /help
params Object (optional) An array of objects containing the information about argument types. See the tm.Command entry in the types list for more information. In short, this allows you to remove all the type-checking from the callback itself, as it will be done by the Trakman chat service
callback Function The function that will be executed once the chat command is typed in (unless the controller crashes)
privilege Number An integer determining the privilege requirement to be able to use the command. 0 - User, 1 = Operator, etc
disableForMuted Boolean (Optional) A boolean determining whether the command is disabled for players currently in the mutelist

Start by invoking the commands.add method in the Trakman namespace (tm):

tm.commands.add(
  {
    aliases: [`ilovets`, `ilovetypescript`],
    help: `Show others that you love TypeScript!`,
    params: [{ name: `paramNameOne`, type: `int` }, { name: `paramNameTwo`, type: `boolean`, optional: true }],
    callback: async (info: tm.MessageInfo, paramNameOne: number, paramNameTwo?: boolean) => {
      // ... Code to be executed ...
    },
    privilege: 0
  },
  {
    // You can add another one immediately:
    aliases: [`ilovejs`, `ilovejavascript`],
    help: `Show others that you love JavaScript!`,
    callback: async (info: tm.MessageInfo) => {
      // ... Code to be executed ...
    },
    privilege: 0
  }
)

Warning

If several commands share the same alias, they will be executed simultaneously.

Your command is ready to use after that, enjoy.

Chat routing

Chat routing makes every player message go through the controller first before being sent out. This way, chat messages can be altered to your liking.

Enabling

(X)ASECO

The most common way of enabling chat routing in (X)ASECO is by directly adding a call to enable it after the controller startup sequence. This can be done like this:

Aseco::registerEvent('onStartup', enable);

function enable(mixed $aseco): void {
 global $aseco;
 $aseco->client->addCall('ChatEnableManualRouting', true, true);
}

This will enable chat routing, but will not make any messages show up at all, as (X)ASECO does not support this normally (as you could probably guess by the fact that you have to enable it yourself). To make it actually output something, you must listen on the onChat event, and then send the chat message from the controller:

Aseco::registerEvent('onChat', message);

function message(mixed $aseco, mixed $player): void {
 global $aseco;
 $aseco->client->addCall('ChatSendServerMessage', '/* Whatever, I am not writing all that. */');
}

Note

This, however, is still not enough, as the method will be triggered for every server message, etc., etc.

Trakman

To enable chat routing in Trakman, go to /config/Config.js/ and set manualChatRoutingEnabled to true. Done.

Customising the chat

(X)ASECO

All customisations must go through the message method you have created. Obviously, you can generate the kind of styling you want with other functions, but it all has to go through that onChat listener.

Trakman

Trakman provides several chat routing utilities via tm.chat, as well as usage examples in /plugins/chat_routing_additions to make your life easier:

tm.chat.addMessagePrefix - Allows you to add a prefix/postfix to every player message (e.g. the finish counter)

tm.chat.setMessageStyle - Allows you to modify the look of the player's name (e.g. the custom brackets)

tm.chat.addMessageTextModifier - Allows you to modify the chat message's text directly (e.g. the url fixer)

Using these methods (and, perhaps, looking at the provided examples), it becomes fairly trivial to modify the chat to your liking.

After you are done

As the author, you certainly have all the rights to keep everything you have written to yourself, the license allows for it too!

If you think that the inclusion of your plugin would immensely help our project, submit a PR, and we will look at it. Consider opening an issue first though.