Custom plugins in JavaScript games

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

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

    A game


    Generals is a tactical card game. I think many are familiar with Magic: The Gathering. The main gameplay is the battlefield 5 * 3, 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 choosing the deck with which you will go into battle, “Research Tree”, “Deck Editor” and so on.



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

    About the plugin system


    Plugins are definitely useful functionality.

    First, hardcore players can use them to improve their game experience.
    Secondly, developers themselves can draw some ideas.
    Thirdly, some plugin writers already work in our team.
    Fourth, some vulnerabilities were found precisely thanks to third-party authors.

    Although we have not yet had an official release, the plug-in system has been working for a year and a half. I thought for a long time about what they should be. And below I will tell the story of how and what we came to as a result.

    Of course, you can write an API, but I did not like the limitations of such an approach as a programmer’s imagination and 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 only deals with displaying - all the logic is transferred to a server that is not affected by plugins
    - All sensitive operations are sent to third-party servers - authorization (where the password might 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 a game:

    1. Using the restricted API


    When creating a plugin, an instance of the object is transferred to it Wotg.Plugins.Simplewith basic methods that allow you to perform the simplest operations - replacing pictures, shifting elements, changing sounds, etc. Such operations are used for simple plugins:



    2. Subscribe to events


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

    3. Aggressive change


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

    plugin.refactor( Wotg.Utils.ReplayManager, {
    	save: functionmethod (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 writing new functionality, such as an in-game map generator:



    Here it is from the point of view 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 browser add-ons and official stores like chrome.google.com/webstore and addons.mozilla.org/uk/firefox . But there were problems with this. It was hard to develop such plugins until the game was launched - they cannot be added to the store for users. As a result, players had to copy-paste the pieces of code posted in the topic on the game forum. Using unsafeWindow instead of the classic window was confusing for developers. And then with unsafeWindow additional bans appeared. In general, it was dark times, and it became clear that it was necessary 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 game repository. Usability has grown significantly, but two problems have arisen - a very long update of GitHub Pages and the lack of convenient administration. But it was obvious that the direction of movement was correct. And it was already clear where our promised land was.

    Gitlab


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

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

    As a result, we get the following features:

    - The entire repository is under our administrative control and has a common Uptime with our server.
    - Name format Owner:Title:Branch. For example, Shock:MyCoolPlugin- the main plug-in, and Shock:MyCoolPlugin:Unstable- the version for development, which merges into the master upon full readiness. The path to the file is determined by the pattern.gen-git.socapp.net{author}/{title}/raw/{branch}/{title}.js. This additionally facilitates the joint work on plugins - one developer branches off from the repository of another, receives a plugin with the same name, but by a different author. He makes his changes, can even let users install his plug-in, and then creates a pool request to the main plug-in.
    - During installation, information about the plugin is written to localStorage and its main file is connected every time after loading all classes but before calling the entry point.
    - Each plug-in 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 plug-in will automatically turn on again for all users who have it installed. In this case, the next version can be prepared in advance on a super test and simply during the update release it can be frozen through the web interface with one button.
    - In the plug-in code, it is enough to write require, and the necessary plug-ins (dependencies) will automatically pull up. They will be tightened in the correct order and will be accessible from the body of the plugin as shown below.
    - You can also include additional plugin classes with include. 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'] ); // Ссылка на необходимый класс
    });
    

    - Using a command to plugin.addStyles( 'my.css' )connect your styles.
    - Each plugin has its own settings, which can be obtained by the team plugins.getConfig( 'index' ).
    - The simplest plugins can be modified through the GitLab web interface, and more complex ones thanks to the git clonedevelopment locally. To do this, it is enough to configure nginx using the template. Thanks to the settings in the game, the corresponding directory will be used as a plugin repository. And then any change in the directory of the connected plugin will be displayed without commits in the developer's game.
    - GitLab has an API. A fake user was registered and thanks to its private token, any game web client can send requests to the API. This allows you to search for plugins, validate names, 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 plugins is done through the in-game console, which opens on Ctrl ~. It’s quite convenient for everyone (although sometimes users ask where the tilde is). But in the distant plans there is the creation of a GUI - from localStorage we get a list of installed plugins, and thanks to the GitLab API we implement their search and display information about them.

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

    I specifically did not include the implementation specifically in terms of code, because it is quite simple. For me, the most interesting thing is the use of GitLabas a package manager. But if you have questions about the technical implementation or the idea, I’m waiting in the comments.

    Also popular now: