📜 ⬆️ ⬇️

Custom plugins in javascript games

Wargaming is currently developing a tactical card game WoT: Generals . The web version is written in JS, LibCanvas and AtomJS are used. I was directly involved in the development and I want to tell you about the functionality that seems interesting to me and can be useful in all web games. Namely - about the plugin system of the game, which was inspired by package managers in Linux and has the following features:

- History of plugin changes
- Automatic update of the plugin when updating the game version
- Development of plug-ins on localhost
- Unlimited number of branches, for example, for unstable versions
- Dependencies (plugin A automatically connects plugin B)
- Built-in ability to make packs (a consequence of the previous paragraph)
- Easy change of any part of the game client
- Full administrative control of the game authors over all plugins
- Search plugin database
- At the same time, easy installation by the user and convenient work for plug-ins.

A game


Generals is a tactical card game. I think many are familiar with Magic: The Gathering. The main gameplay is 5 * 3 battlefield, where the task is to destroy the enemy headquarters with cards of tanks, platoons and orders.


')
In addition to the battle screen, there are such screens as “Hangar” for selecting a deck from which you will go into battle, Research Tree, Deck Editor, and so on.



Objects with a lot of animations (like a carousel in a hangar, a research tree and, of course, a battle) are written in LibCanvas and rendered on html5 canvas. Interfaces are easier written on html.

Pro plugin system


Plugins are a uniquely useful feature.

First, hardcore players can use them to improve their own gaming experience.
Secondly, by themselves, developers can learn some ideas.
Third, some plug-in writers are already working in our team.
Fourthly, some vulnerabilities were found thanks to third-party authors.

And although we haven’t had an official release yet, the plugin system has been running for a year and a half. I have been thinking about what they should be. And below I will tell the story of how and what we came to in the end.

Of course, you can write an API, but I didn’t like the limitations of this approach by the imagination of the programmer and the increased support costs. I wanted to be able to change any part of the game client.

Fortunately, this is quite simple for two reasons:

- The client is very thin and deals only with the display - all the logic is carried out on the server, which is in no way affected by the plugins
- All sensitive operations are made to third-party servers - authorization (where the password could leak) and payment (where the user could lose money).

As a result, we only have a clean game client that can be safely changed.

There are three ways to change the behavior of the game:

1. Use of limited API


When creating a plug-in, it is given an instance of the Wotg.Plugins.Simple object with basic methods that allow you to perform the simplest operations — image substitution, element displacement, sound change, etc. Such operations are used for simple plug-ins:



2. Subscribe to events


We can also subscribe to a huge number of events. By events we mean receiving a message from the server, clicking a button, opening a new screen. This allows you to respond to relevant events and, for example, when you press the space button to attack the enemy with the entire available arsenal, as in the Katyusha plugin.

3. Aggressive change


This is the most difficult but also the deepest method (if you understand what I mean;)). It allows you to change any method of any class with the ability to call the previous version. For an example, look at the part of the plugin below that allows you to save replays on a third-party server. In this case, the ReplayManager save method changes.

 plugin.refactor( Wotg.Utils.ReplayManager, { save: function method (battle) { method.previous.call(this, battle); var replay, xhr; replay = this.getCompiledDataFrom(battle); xhr = new XMLHttpRequest(); xhr.open("POST", MY_OWN_REPLAYS_SERVER, true); xhr.send( "player=" + replay.player.name + "&opponent=" + replay.opponent.name + "&replay=" + JSON.stringify(replay) ); } }); 


This makes it possible to change any behavior, up to the writing of a new functional, such as, for example, the in-game card generator:



This is it in terms of the plugin code. But how to organize all this in terms of connectivity?

Story


Initially (a couple of years ago) it was decided to use add-ons built into the browser and official stores like chrome.google.com/webstore and addons.mozilla.org/uk/firefox . But there were problems with it. Such plugins were hard to develop until the game was released - they cannot be added to the store and users. As a result, players had to copy-paste add pieces of code posted in the thread on the forum of the game. Using unsafeWindow instead of the classic window caused confusion for developers. And then with unsafeWindow, additional bans appeared. In general - the dark times, and it became clear that you need to move somewhere.

I wanted something progressive and convenient. And so it was decided to use GitHub. One repository for commits and pool requests to the repository of the game. Convenience of work has grown significantly, but two problems have arisen - a very long GitHub Pages update and the lack of convenient administration. But it was obvious that the direction of movement is correct. And it was already clear where our promised land was.

Gitlab


We raised GitLab on our server and allocated it completely under the plugins. And it was divine. The scheme is as follows:

- There are users of GitLab - our plugins writers
- Each user has repositories - one for each plugin
- Repositories can have multiple brunches. For example, master and unstable. Master is enabled by default.

As a result, we get the following features:

- The entire repository is under our administrative control and has a common with our server Uptime.
- Name format Owner:Title:Branch . For example, Shock:MyCoolPlugin is the main plugin, and Shock:MyCoolPlugin:Unstable is the development version, which merjitsya into the master upon full readiness. The file path is determined by the pattern. gen-git.socapp.net{author}/{title}/raw/{branch}/{title}.js gen-git.socapp.net{author}/{title}/raw/{branch}/{title}.js . This further facilitates collaboration on plugins — one developer branches off another's repository, gets a plug-in with the same name, but a different author. That one makes its own changes, it can even let users install its plugin, and then creates a pull request to the main plugin.
- When installing, information about the plugin is recorded in localStorage and its main file is connected every time after loading all the classes, but before calling the entry point.
- Each plugin has a version of the game for which it is intended, and when the update is released, it automatically turns off until the author changes the version in the corresponding branch, and then the plugin will automatically turn on again for all users who have it installed. In this case, the next version can be prepared in advance at the supertest and just during the release of the update, via the web interface with one button.
- In the code of the plugin, it is enough to write require, and the necessary plugins (dependencies) will automatically be pulled up. They will pull up in the correct order and will be accessible from the body of the plugin as shown below.
- You can also include include additional plug-in classes. Something like this:

 new Wotg.Plugins.Simple({ version: '0.6.0', require: [ 'Another:Plugin' ], include: [ 'AnotherClass' ], }, function (plugin, events, required) { console.log( required['Another:Plugin'] ); //     console.log( plugin.included['AnotherClass'] ); //     }); 

- Use the plugin.addStyles( 'my.css' ) connect your styles.
- Each plugin has its own settings, which can be obtained with the plugins.getConfig( 'index' ) command.
- The simplest plug-ins can be changed via the GitLab web interface, and more complex thanks to git clone developed locally. To do this, it is enough to configure nginx using a template. Thanks to the settings in the game, the corresponding directory will be used as a repository of plugins. And then any change in the directory of the connected plug-in will be displayed without commits in the game developer.
- GitLab has an API. A fake user was registered and, thanks to his private token, any web client of the game can send requests to the API. This allows you to search for plugins, name validation, etc.

 # plugins add Test:CardCreate No such plugin. Did you mean: - Shock:CardCreate # plugins add Test:Ca Min plugin title length is 4: Test:Ca # plugins add Test:Card No such plugin. Did you mean: - Shock:CardCreate # plugins add Test:Exa Min plugin title length is 4: Test:Exa # plugins add Test:Exam No such plugin. Did you mean: - Shock:Example - Isk1n:Example # plugins add Shock:Unknown No such plugin, I dont know, what you want to install # plugins add Shock:Example:Test213 No such plugin. Did you mean: - Shock:Example:master - Shock:Example:new-test 


Console vs GUI


At the moment, all management and installation of plug-ins is done through the in-game console, which opens with Ctrl ~ . Everyone is comfortable enough (although users sometimes ask where the tilde is). But in distant plans there is a GUI creation - from localStorage we get a list of installed plugins, and thanks to the GitLab API we implement their search and display of information about them.

If anyone is interested, you can read the documentation for developers , see the plug-in section , or see how it all looks in WoT: Generals .

I specifically did not include the implementation here in terms of code, since it is quite simple. For me, the most interesting thing is to use GitLab as a package manager. But if there are questions to the technical implementation or to the idea - I wait in the comments.

Source: https://habr.com/ru/post/271357/


All Articles