Example Mod Chat Alert

From Planetary Annihilation Titans & Classic PA Wiki
Jump to navigation Jump to search

Titans-icon.png Example Mod: Chat Alert

This example will demonstrate how to make a client mod that changes how the user interface works. It shows how to add new settings to the Planetary Annihilation Settings screen and the use of the Coherent Debugger for examining JavaScript.

Gold-rank-icon.png Chat Alert

Here we will modify the inner workings of the UI, which is based on HTML and JavaScript. We are going to make a mods that plays an audible alert when somebody types a new message in-game or in the lobby. This will also demonstrate how to add settings for the mod in the PA Settings screen.

Gold-rank-icon.png Mod structure

To create a new local client mod, navigate to the Planetary Annihilation Data Directory and if it does not already exist, create a new directory called "client_mods". Inside that directory create a directory for our new mod, for example: "com.pa.you.chat-alert". And inside of that directory, create a new text file called "modinfo.json". This will contain the information about our mod. It should look like this:

{
  "context": "client",
  "identifier": "com.pa.you.chat-alert",
  "display_name": "Chat alert",
  "description": "Audible alerts when somebody type in lobby or in-game chat.",
  "author": "You",
  "version": "1.0",
  "build": "105067",
  "date": "2017-12-10",
  "signature": "not yet implemented",
  "forum": "",
  "priority": 100,
  "icon": "",
  "category": ["gameplay", "lobby", "ui"],
  "scenes":
  {
    "new_game":
    [
      "coui://ui/mods/com.pa.you.chat-alert/new_game.js"
    ],
    "live_game_chat":
    [
      "coui://ui/mods/com.pa.you.chat-alert/live_game_chat.js"
    ],
    "settings":
    [
      "coui://ui/mods/com.pa.you.chat-alert/settings.js"
    ]
  }
}

See here for more details on the meaning of these entries. However, important for this mod are the entries for `"scenes"`. Here will tell the mod manager which parts of the UI we will inject new JavaScript into.

Next, we are going to add the files we are going to add. Since we are defining new files, we should take care that we do not accidentally shadow existing PA files or of other mods. One way of doing that is adding our own subdirectory. So create a new folder "ui" and inside that folder create "mods", and inside of that folder create "com.pa.you.chat-alert". By reusing our mod identifier like this, we can be certain we are not affecting anything else. Notice that we already used this directory structure in the `"scenes"` above.

Now we should have the following structure:

Gold-rank-icon.png Adding JavaScript

Now we make three JavaScript files. Open a text editor and copy-paste the following text:

(function() {
  console.log("Hello world!");
})();

Save this text to three files called "new_game.js", "live_game_chat.js", and "settings.js" in the directory we created above. The function `console.log` on the second line will print a message to the log so that we can check if the mod is actually loaded. The structure on the first and third line are not strictly necessary, but it is good practise because it allows any variables or functions you might declare to remain "local". That means that they cannot interfere with other mods or PA in unexpected ways.

Now we should have the following structure:

Now we will test if this works and in doing so introduce the debugger tool that comes with PA which is in invaluable tool for developing mods that modify the UI. First enable the mod by starting Planetary Annihilation and going to Community Mods. Under the Installed tab, you should see the mod you just created. Click on it and click the Enable button.

Gold-rank-icon.png Coherent Debugger

Our mods modifies the Settings, Lobby, and the in-game Chat interface. So if we go to any of those, our mod should be called. But first we need a way to see the log in the first place. For that, we can use the Coherent Debugger. You will find it at the Planetary Annihilation Installation Directory/Coherent/Debugger/Debugger on Linux and probably similar locations on Windows can macOS. First start Planetary Annihilation and navigate to the Settings screen. Then start the debugger. It will start with a blank screen, but click on "Go" to connect to Planetary Annihilation. You should then see the following:

Coherent-debugger-main.jpg

Click on "Game Settings". Here it will display the HTML code of the settings screen in PA. Now click on the "Console" button in the toolbar at the top of the screen. That's where the `console.log` messages or any errors if it did not work. It should look like this:

Coherent-debugger-settings.jpg

Notice the "Hello world!" being displayed there. That means that the mod is active and working. You can also check the lobby and the live game.

Gold-rank-icon.png Investigating Planetary Annihilation User Interface files

Before we can add the scripts to modify the UI, we first need to figure out how the UI works in the first place. Planetary Annihilation uses HTML and JavaScript for its UI, but it relies heavily on a JavaScript library called Knockout.js. It is very instructive to read the documentation for that library.

The files for the UI can be found in Planetary Annihilation Installation Directory/media/ui/main/game. There are a lot of subdirectories here with one for each screen. Let's first focus on the Game Lobby, the files for which can be found in new_game. Inside that subdirectory, there are two files of interest, "new_game.html" and "new_game.js". If you open the HTML file and search for the word "chat", then you will eventually find this:

<!-- LOBBY CHAT -->
<div class="section game_chat">
  <div class="form-group" id="chat-bar">
    <div class="section_header" for="chat">
      <loc>Chat</loc>
    </div>
    <div style="height: calc(100% - 58px); position: relative;">
      <div class="chat_container">
        <!-- ko foreach: chatMessages -->

So that looks promising. If you read the Knockout documentation you may recognize what ko foreach does; it uses the observable "chatMessages" to insert HTML. So this means that if the observable "chatMessages" changes, then probably a new chat message has been added and we need to play a sound. If we look in the JS file, we see that "chatMessages" is actually a observableArray. So now we know how chat messages are handled in the Game Lobby.

We can do the same for "live_game" which is the in-game screen. If you look in that subdirectory, there are lots of files. But of course, live_game_chat.js looks the most promising. This time we'll investigate the JavaScript file directly just to show how that works. We want to react to chat messages as they come in. In the UI event are handled by "handlers", so look for those at the end of the file. There are only five and this one looks the most interesting for our purposes:

handlers.chat_message = function (payload) {
  var chat_message = new ChatMessageModel(payload);
  model.chatLog.push(chat_message);
  model.visibleChat.push(chat_message);
  $(".div_chat_feed").scrollTop($(".div_chat_feed")[0].scrollHeight);
  $(".div_chat_log_feed").scrollTop($(".div_chat_log_feed")[0].scrollHeight);
};

So we see that every time a new chat message comes in, the message is added to both "chatLog" and "visibleChat", so we need to look for changes of one of those. To figure out which one, look for both "chatLog" and "visibleChat" in the rest of the file. You'll see that "chatLog" is only defined and nothing else happens to it, whereas "visibleChat" is also involved in the function "removeOldChatMessages". So that means that chatLog only changes when new message arrive while visibleChat also changes when old messages are removed. We do not want to play a sound on message removal, so we have to use "chatLog".

Gold-rank-icon.png Modding new_game.js

In the previous section, we found that we need to monitor the observableArray "chatMessages" for changes. We can do by using the subscribe function on it which is a function that is defined by Knockout for this exact purpose as shown in the Knockout documentation.

Open the file "new_game.js" in your mod. It now just contains the "Hello World!" we put there earlier, but we will now replace that with subscribing to "chatMessages". Make it so that the file now contains:

(function() {
  model.chatMessages.subscribe(function () {
    console.log("My mod says message received!");
  });
})();

The argument for the subscribe-function defined by Knockout is a function that is called whenever "chatMessages" is changed. Here that function is just contains a console.log again to see if it actually works. Save the file and in Planetary Annihilation go to the Game Lobby (or press F5 in either Planetary Annihilation or in the debugger to reload if you already are there). Now type a chat message in the lobby and if you are looking at the console output in the Debugger, you should see "My mod says message received!" every time you type a new message in the lobby.

The next step is to figure out how to play audio. We can again look at the existing Planetary Annihilation files. The grep command on Linux and macOS is a helpful tool for that allows you to search for text in files. For Windows there is Select-String in the PowerShell. Using that you can search for strings such as "sound" or "audio" to see examples of playing sounds. In particular searching for "audio" turns up examples of api.audio.playSound. So now we know how to play a sound. Searching for "audio.playSound" shows a bunch of sound effects we can use to play. For example, /media/ui/main/shared/js/inputmap.js contains api.audio.playSound('/SE/UI/UI_camera_anchor_saved'). If want to quickly test this, we can go to the Debugger and pas that in the console and execute by pressing Enter. Planetary Annihilation will then play that sound.

That sound sounds ok to get a chat alert, so we will use that. Now we change the new_game.js file in our mod to:

(function() {
  model.chatMessages.subscribe(function () {
    api.audio.playSound('/SE/UI/UI_camera_anchor_saved');
  });
})();

And that is all we need for the Game Lobby. You can test this now.

Gold-rank-icon.png Modding live_game_chat.js

Now we need to do the same thing as for new_game.js. The only difference is that we determined we needed to use the observableArray "chatLog". So the content of live_game_chat.js is going to be almost the same as for new_game.js, we just need to use the proper observable to subscribe to. So make the content of live_game_chat.js in your mod this:

(function() {
  model.chatLog.subscribe(function () {
    api.audio.playSound('/SE/UI/UI_camera_anchor_saved');
  });
})();

And now that part is done too and we have a functioning mod that plays audio alerts on chat messages.

Gold-rank-icon.png Adding settings

We would like the sound to be configurable and make a new drop-down menu in the settings menu by modding settings.js. After that we need to change the other files in our mod to make use of these new settings.

Gold-rank-icon.png Modding settings.js

To figure out how to add settings, we need to read through the settings.js that comes with Planetary Annihilation. Here, you can see that settings are accessed through api.settings.definitions. To investigate what this contains, you can use the Coherent Debugger. Just type in:

api.settings.definitions

This will get you a list of setting groups with a small triangle in front of it, which means you can expand it. If you expand ui -> settings -> show_stat_bars, you will see what kind of structure a drop-down settings should have. We will just copy that structure to define our new setting, so copy this in the settings.js of your mod:

api.settings.definitions.ui.settings["com_pa_you_chat_alert"] = {"default": "CHIME", "options": ["", "/SE/UI/UI_ping", "/SE/UI/UI_camera_anchor_saved"], "title": "CHAT ALERT SOUND", "type": "select"};
model.settingDefinitions(api.settings.definitions);

The first line extends the definition of the UI settings, adding a drop-down menu with three options. That value for options are the sound files we want to play. The second line forces a reload so that all ko.computedFunction are aware of the change.

Now we have defined our setting, but we still need it to show up in the setting screen. That is, we need to add our drop-down menu to the UI settings. If you look in settings.html in the Planetary Annihilation files, you can find this line:

<div class="option-list ui" data-bind="visible: ($root.activeSettingsGroupIndex() === 3), deferBindingsUntilVisible: true">

which is the UI part of the settings screen. It is followed by statements such as

<div class="sub-group">
 <div class="option" data-bind="template: { name: 'setting-template', data: $root.settingsItemMap()['ui.show_stat_bars'] }"></div>
</div>

We will need to add one of our own like that. Luckily Planetary Annihilation uses jQuery, which allows for easy manipulation of HTML. Technically it is also possible to include a modified copy of settings.html with our mod, but that will conflict with any other mod and break with every update to Planetary Annihilation where new settings are introduced.

Again the Debugger will be helpful for this. Note that the UI part of the settings screen is a <div> with the class "ui", so to access that with jQuery, we can type this in the Debugger:

$('.ui')

The output of Debugger shows that part of the HTML containing the UI settings. We want to add our setting the the end, so we need to find the last element with the class "sub-group". A bit of jQuery magic for that is:

$('.ui').find('.sub-group').last()

which returns the settings element for the orbital shell, which is the last setting in the UI screen of the Planetary Annihilation Settings screen. Now we want to add something after that element, but to check if we at the right place, type this in the Debugger:

$('.ui').find('.sub-group').last().after("<div>Hello world!</div>")

If we look in the Settings screen in Planetary Annihilation now we see "Hello world!" appear after the last UI setting, so we are in the right place. Now we know that it works, we can add it to our mod. We will copy the HTML code for ui.show_stat_bars and modify it for our own setting. So add this to our mod:

$('.ui').find('.sub-group').last().after(
  "                            <div class=\"sub-group\">\n" +
  "                                <div class=\"option\" data-bind=\"template: { name: 'setting-template', data: $root.settingsItemMap()['ui.com_pa_you_chat_alert'] }\"></div>\n" +
  "                            </div>\n"
);

We only changed the text "ui.show_stat_bars" here and added backslashes to all of the quotation marks. Now our the settings.js in our mod should look like this:

(function() {
  api.settings.definitions.ui.settings["com_pa_you_chat_alert"] = {"default": "PING", "options": ["", "/SE/UI/UI_ping", "/SE/UI/UI_camera_anchor_saved"], "optionsText": ["OFF", "PING", "CHIME"], "title": "CHAT ALERT SOUND", "type": "select"};
  model.settingDefinitions(api.settings.definitions);

  $('.ui').find('.sub-group').last().after(
    "                            <div class=\"sub-group\">\n" +
    "                                <div class=\"option\" data-bind=\"template: { name: 'setting-template', data: $root.settingsItemMap()['ui.com_pa_you_chat_alert'] }\"></div>\n" +
    "                            </div>\n"
  );
})();

We can now save settings in the Planetary Annihilations Settings screen. Try it out!

Gold-rank-icon.png Using settings in new_game.js and live_game_chat.js

Now we need to make use of these new settings in our other JavaScript files. Studying the api JavaScript file for api.settings shows that we can use the function value to obtain saved settings. You can try using the Debugger to type

var sound = api.settings.value("ui", "com_pa_you_chat_alert");

It should return whatever you set the checkbox in the settings screen to. Of course, if you did not ever go to the Planetary Annihilation Settings screen, that this value will not be set and we have to use a default. Above we set the default to "CHIME", so if the new variable sound is undefined then we should set sound to "/SE/UI/UI_camera_anchor_saved". Finally, we need to modify the new_game.js file in our mod to use the new variable sound.

The new content of new_game.js in our mod should thus be changed to

(function() {
  var sound = api.settings.value("ui", "com_pa_deathbydenim_chat_alert");
  if(_.isUndefined(sound)) {
    sound = "/SE/UI/UI_camera_anchor_saved";
  }

  model.chatMessages.subscribe(function () {
    api.audio.playSound(sound);
  });
})();

and similarly for live_game_chat.js:

(function() {
  var sound = api.settings.value("ui", "com_pa_deathbydenim_chat_alert");
  if(_.isUndefined(sound)) {
    sound = "/SE/UI/UI_camera_anchor_saved";
  }

  model.chatLog.subscribe(function () {
    api.audio.playSound(sound);
  });
})();

And now we need to test if it works, so try changing the settings and start typing chat messages. And if that works, then we are done!

As a side note, note that we used the function _.isUndefined here to check if sound actually is defined. The _ is part of the Lodash library that is also available in Planetary Annihilation.

Gold-rank-icon.png Publishing

See Submitting Your Released Mod for details on how to publish your mod.