Some personal thoughts on improving scripts

RedAlert

Senior In-Game Staff
Tribal Wars Team
Senior
Team
Script Moderator
Reaction score
608
In this article, I will share my own experience writing and maintaining Tribal Wars scripts.

So I will start by linking the official script rules and approval procedure:
https://forum.tribalwars.net/index....proval-process-public-private-scripts.286651/
Every rule that is mentioned on the link above needs to be followed.

All that comes below are only recommendations based on personal experience as a scripts author, they do not represent Tribal Wars official policy regarding scripts and scripting.

You are not enforced to follow the suggestions mentioned below.

I will try to make my case to support those suggestions on each specific point as better as I can but you are not forced nor will you be denied script approval (if your script deserves to be approved) if it does not fit any of the suggestions below.

So consider these suggestions as an open and fair discussion between developers, on how to improve scripts, instead of hard rules on how to write scripts.


General script guidelines/suggestions

Do we need that script?
Whenever you start working on a script think about what the player will gain from this script. If it gains very little, like for example a script that redirects the player to HQ no matter where you are ... is this script worth it?
Digitalization of a process (a.k.a script) is valid when you want to "automate" some complex/hard/repetitive task (for example sending resources from village to village, filling unit fields on rally point, etc). If instead, you are trying to "automate" 2 clicks ... is that worth it? Do we need a script to bring us to HQ and then another script to bring us to the Barracks?


The need for universalization
When working on a new script, try to make that script as universal as possible. Not world-specific, not unit-based specific (for example a script that only works on archer worlds but does not work on non-archer worlds). If you write your script to be universal and translation-ready you will already have created a script with strong foundations. A script which is less prone to bugs and fairly more ready to be expanded.

By having created a universal script, you will automatically minimize possible issues of players running your script and the script non-working just because you worked the script on an archer world and the world the player is running the script is non-archer world or vice-versa.

Having a translation-ready script also helps with another thing, script maintainability in the long run. Different servers, use different languages and if your script depends on some string found in the game, one solution (bad solution) to port that script into the other server, could be to clone and rehost the script. Bad solution because now the new script which is rehosted is considered as a new script. Completely separated from the script you wrote and maintain and whenever you release fixes/updates the cloned script will not get them.

So which is the good solution then? Either make the string translatable or have the string be a script variable defined by the player. Both solutions achieve the goal of having one script (one single source of truth), which makes life easy for you and for the players using your script.


Going more technical
  1. Use a script loader instead of copy/pasting the script itself. For the player, it is the best and easiest way to get scripts because it minimizes stupid errors like comments. Also whenever you update the script, the update will be automatically propagated to whoever has the script added on Quick bar. For you, it's way more maintainable to simply modify the script on your code editor of choice and the updated script version will auto-propagate than having to always update the net forums thread with the new script version. Also since there will be fewer errors if you use a script loader, it means less time trying to debug issues and more time to focus on new features :)
  2. Use a script template. There is a lot to talk about here. Probably it's easier for me to share my script template, a template that I have used in almost all my scripts. It kind of looks like I’m promoting my scripting method by “promoting” my script template. I’m pretty sure there are better ways (templates) to create scripts, as there are worse ways too. I think, my method stands in the middle. It’s not hard to be followed even from beginners but even experienced developers might find some good practices there too.
  3. Regarding UI, try to keep it simple. Stick as much as possible to TW styling. If possible, use CSS classes built-in into the game. If possible use the game API (when it comes to JS game API, UI, Dialog, etc) as much as possible. This will make your script faster (since you won't have to add custom styles for everything and custom logic for example just to show a popup).
  4. The script needs to be accessible and reactive-ready. For example, you have a list of attacks to be sent generated from your attack planner script. Wouldn't it be helpful for the player, to know which attacks did he open so he does not open them again? For sure that would help, so take that into account then (this means thinking about user reactions and handling those correctly).
  5. Provide the player with feedback on what is going on. Also wouldn't it help to have something shown when a player clicks a button and for 2 seconds nothing happens? The normal player would keep clicking that button until something happened. So whenever something takes too long to show, show at least a notice. The same is valid even on script initialization. Even more, with a click of a button, you could set the button to be disabled after the first click. The same is true also whenever whatever happens for example on button click is out of the current view. The player needs to know that whatever needed to happen, happened, so no need to keep clicking the button over and over.
  6. The Script needs to be translation-ready. There are multiple ways to make a script translation-ready but most of those are based on the same technique, having a JS object which contains the strings for each language. An example of this can be found on the script template.
  7. Use comments whenever possible. Now I know as programmers you must have heard that comments are not always good. Sometimes they tell half the truth and you might have heard that good code does not need documentation. True. But again, think that someone else needs to review your code, and comments, good comments, comments which are kept up to date are always helpful to understand easily what a piece of code is doing.
  8. Whenever possible, try to make the script mobile and mobile app friendly. Sometimes that is just not doable for multiple reasons but whenever possible let's write inclusive scripts instead of writing scripts that exclude players from using them. However, being fair, this suggestion no matter how much we try to optimize for mobile we can not achieve the same user experience on mobile for scripts as in the desktop browser. Also in some mobile screens, the Quick bar is not even showing (example reports screen) so it is what it is.
  9. Limit the number of API requests your script makes. For API requests like world data (village, ally, player, etc) it is already mandatory to limit those requests (for example by saving the response in localStorage). For world setting data instead like unit_info, building_info, etc ... those settings do not change throughout the entire world duration so you could save those on localStorage with a very long expiration time (basically you only need to grab them once).
  10. Minimize basing the logic of your script on DOM elements. Keep in mind that the DOM can be changed from game updates, which means your script could break. Now I know this is very limiting because the nature of the script could be that it depends on grabbing and parsing data from the DOM and manipulating them somehow. In which case try to base your logic on something constant which very hardly will get affected from game updates. Also, in the case that the script depends on DOM elements and can't do without parsing/reading or writing the DOM, try/catch that and probably instead of having the script silently fail, show an error to the player so he knows something went wrong and he can notify the script author.
  11. Limit localStorage/sessionStorage access to 1 key per script. So basically, a script could have multiple data that need to be saved in localStorage. Save them all in one JS object then save the object on localStorage. Also, localStorage is domain-based. So there is no need for prefixing/suffixing a localStorage key with the world tag.
  12. Limit using JS methods or JS API which is based on browser permissions (browser popup). A lot of players, if not most, are not tech-savy. By making a script rely on the browser popup permission you could create false bugs of the script not working (which would put more work on you to “solve” this issue) and also would “deny” players who don’t know how to allow popups for example from using the script. So instead of using the native browser popup, try to use the JS game API (example Dialog) for that.
  13. Code in English, comment in English. The universal coding language is English. For me as a developer, no matter what project I’m working on, or the client I’m working for, the code that I write is always written in English even though, English is not my native language. Because, it only makes sense to write and comment everything in English. We as programmers always need to keep in mind that someone else has to work with the mess that is our code. So writing in english as an universal language is what makes sense the most.

Conclusions

First of all, let’s keep in mind that this article does NOT represent Tribal Wars official policy regarding scripts and scripting.

Do not consider the suggestions listed here as rules to follow, they are not. They are merely suggestions.

Feel free to implement them if you want. It will only make your scripts work better IMO, have less bugs, look better and have a better user experience offered to the player.


Other References

https://dev.opera.com/articles/javascript-best-practices/
https://developer.mozilla.org/en-US/docs/MDN/Guidelines/Code_guidelines/JavaScript


Update

The rest of the code shown on this article can be used at your own will, however it is outdated. I have been working to make a Tribal Wars scripting SDK/library call it what you want. Basically is a library with pre-defined methods which help the developers do more in less time.

The twSDK encompasses better the principles and ideas shown above, it is built following as much as possible the principles mentioned on this thread.

I talk about this here:


Script Template

JavaScript:
/*
* Script Name: Script Template
* Version: v1.0
* Last Updated: 2021-01-29
* Author: RedAlert
* Author URL: https://twscripts.ga/
* Author Contact: <author-contact-method, example discord, email, etc>
* Approved: N/A
* Approved Date: N/A
* Mod: N/A
*/

var scriptData = {
    name: 'Script Template',
    version: 'v1.0',
    author: 'RedAlert',
    authorUrl: 'https://twscripts.ga/',
    helpLink: '#',
};

// User Input
if (typeof DEBUG !== 'boolean') DEBUG = false;

// CONSTANTS
var DUMMY_CONSTANT = 0;

// Globals
var allowedGameScreens = ['overview_villages'];
var allowedGameModes = ['prod'];

// Translations
var translations = {
    en_DK: {
        'Script Template': 'Script Template',
        Help: 'Help',
        'Invalid game mode!': 'Invalid game mode!',
    },
    en_US: {
        'Script Template': 'Script Template',
        Help: 'Help',
        'Invalid game mode!': 'Invalid game mode!',
    },
};

// Init Debug
initDebug();

// Init Translations Notice
initTranslationsNotice();

// Helper: Get parameter by name
function getParameterByName(name, url = window.location.href) {
    return new URL(url).searchParams.get(name);
}

// Helper: Generates script info
function scriptInfo() {
    return `[${scriptData.name} ${scriptData.version}]`;
}

// Helper: Prints universal debug information
function initDebug() {
    console.debug(`${scriptInfo()} It works !`);
    console.debug(`${scriptInfo()} HELP:`, scriptData.helpLink);
    if (DEBUG) {
        console.debug(`${scriptInfo()} Market:`, game_data.market);
        console.debug(`${scriptInfo()} World:`, game_data.world);
        console.debug(`${scriptInfo()} Screen:`, game_data.screen);
        console.debug(`${scriptInfo()} Game Version:`, game_data.majorVersion);
        console.debug(`${scriptInfo()} Game Build:`, game_data.version);
        console.debug(`${scriptInfo()} Locale:`, game_data.locale);
        console.debug(`${scriptInfo()} Premium:`, game_data.features.Premium.active);
    }
}

// Helper: Text Translator
function tt(string) {
    var gameLocale = game_data.locale;

    if (translations[gameLocale] !== undefined) {
        return translations[gameLocale][string];
    } else {
        return translations['en_DK'][string];
    }
}

// Helper: Translations Notice
function initTranslationsNotice() {
    const gameLocale = game_data.locale;

    if (translations[gameLocale] === undefined) {
        UI.ErrorMessage(
            `No translation found for <b>${gameLocale}</b>. <a href="${scriptData.helpLink}" class="btn" target="_blank" rel="noreferrer noopener">Add Yours</a> by replying to the thread.`,
            4000
        );
    }
}

// Initialize Script
(function () {
    const gameScreen = getParameterByName('screen');
    const gameMode = getParameterByName('mode');

    if (allowedGameScreens.includes(gameScreen)) {
        if (allowedGameModes.includes(gameMode)) {
            console.log('We are on a valid game screen and mode, init script!');
            console.log('If a lot of stuff are going to be done from the script encapsulate in a function');
        } else {
            UI.ErrorMessage(`${tt('Invalid game mode!')}`);
        }
    } else {
        console.log('Show a notice or redirect to the correct place!');
    }
})();
 
Last edited:

Frying Pan Warrior

Still Going Strong
Reaction score
578
I suggest you add some code to your template as a world data / world setting API. That way most scripts can share cached world data / world settings instead of each fetching and storing their own copy. You can also therefore set the rules for how often an update is fetched.
 

Frying Pan Warrior

Still Going Strong
Reaction score
578
Another thought that came to mind is the need to use a namespace for the script. Perhaps a larger script may want to declare many variables and functions in the global scope that conflict with existing tw game identifiers, or other scripts.

It could be solved by having each script declare their globals in a namespace, thus containing the entire script to one unique global variable.

Eg, if my script was called My Script and I'd like to store the script name as a global variable and then use it in a function:

JavaScript:
{
const _fpwMs = {
     scriptName: "My Script",
     displayScriptName: function () {
         UI.InfoMessage(_fpwMs.scriptName + " created by Frying Pan Warrior");
     }
}

_fpwMs.displayScriptName();
}

Edit: Apparently this can be easier solved by wrapping the script in a block and using let statements. I'm going to see how it goes to find the best solution.

Yes that works, wrapping scripts with a block { } and using let / const statements for script global variables allows the script to run contained and no garbage global variables or functions are left behind on the page, nor does it conflict with game code. This also allows a script that declares constants to run a second time on the same page.

Solution (Wrap the code in a block and only use let and const in as global script variables):


JavaScript:
{
    const scriptName = "My Script";
   
    function displayScriptName() {
        UI.InfoMessage(scriptName + " by Frying Pan Warrior");
    }
   
    displayScriptName();
}
 
Last edited:

The Quacks

Non-stop Poster
Reaction score
76
Another thought that came to mind is the need to use a namespace for the script. Perhaps a larger script may want to declare many variables and functions in the global scope that conflict with existing tw game identifiers, or other scripts.

It could be solved by having each script declare their globals in a namespace, thus containing the entire script to one unique global variable.

Eg, if my script was called My Script and I'd like to store the script name as a global variable and then use it in a function:

JavaScript:
{
const _fpwMs = {
     scriptName: "My Script",
     displayScriptName: function () {
         UI.InfoMessage(_fpwMs.scriptName + " created by Frying Pan Warrior");
     }
}

_fpwMs.displayScriptName();
}

Edit: Apparently this can be easier solved by wrapping the script in a block and using let statements. I'm going to see how it goes to find the best solution.

Yes that works, wrapping scripts with a block { } and using let / const statements for script global variables allows the script to run contained and no garbage global variables or functions are left behind on the page, nor does it conflict with game code. This also allows a script that declares constants to run a second time on the same page.

Solution (Wrap the code in a block and only use let and const in as global script variables):


JavaScript:
{
    const scriptName = "My Script";
  
    function displayScriptName() {
        UI.InfoMessage(scriptName + " by Frying Pan Warrior");
    }
  
    displayScriptName();
}
Thanks, that is pretty cool, was looking precisely at how to rerun a script in the same page if const variables are declared. A point similar to yours that could be added is regarding classes and ids in HTML. Use some unique preidentifier before any class / id you define on the html
 

Frying Pan Warrior

Still Going Strong
Reaction score
578
Thanks, that is pretty cool, was looking precisely at how to rerun a script in the same page if const variables are declared. A point similar to yours that could be added is regarding classes and ids in HTML. Use some unique preidentifier before any class / id you define on the html

I have added a clearDisplay() function in my code that searches for created elements by id and deletes them if they exist before initializing the display new again (to delete previously created display elements if the script is re-run). Other cleanup could be added to such a function to ensure the script can run a second time.
 

RedAlert

Senior In-Game Staff
Tribal Wars Team
Senior
Team
Script Moderator
Reaction score
608
Thanks guys. Really glad this attracted some attention towards improving scripts :)

There are multiple ways to handle a script being loaded again, while it's still running.

It depends IMO in what the script does on how to deal with it.

This is regarding what the user sees, so the script UI.

The browser has its own "problems" with scripts being loaded twice if for example you declare variables as constants using const (which I use personally a lot).

If those variables are global it will throw an error because variable is already defined and you can't redefine a constant. That is why I never declare my global variables with "const".

Now, regarding this, there is an easy solution.
Put the whole script on a IIFE block:

All variables inside that function will not be globals anymore and that should take away those problems.

Going back to the UI handling.

As I said earlier, in some cases it might make sense to actually let the script re-run ... like for exampel Incomings Overview. If you load it again when it's already loaded ... it will re-parse incomings and refresh the "stats" shown.

In some other cases this should not be allowed to work ofcurse. Like for example the script from Sophie that adds ODS, ODA, etc ... it wouldn't make sense to refetch those every time when the script is run and also to keep adding rows on the members table.

So the conclude:
- UI handling is tied up with what the script does
- programming side of the things, there is a built-in solution, encapsulate logic in an IIFE block
 

RedAlert

Senior In-Game Staff
Tribal Wars Team
Senior
Team
Script Moderator
Reaction score
608
Hello everyone,

I have been writing a Tribal Wars scripts SDK, a framework/library that could help write scripts faster, easier, and better.

The ideology behind this is based on a principle similar to "convention over configuration". What this means is that through this library I try to take away decisions from the user and integrate those decisions into the library.

The more decisions like this are taken away from the developer the faster she/he can focus the attention on the core business logic of the script instead of having to think about how to solve trivial tasks like how to fetch world data safely, check if the current world has support for archers or not, etc. All of this (and more) has been baked into the library.

Where is the library you say?
Code:
https://dl.dropboxusercontent.com/s/pg92mk1wjayrehl/twSDK.js
For the moment, the library lives here. Surely in the future, that will change.

What problems does the library solve?
- unified UI (wherever can the UI be unified)
- unified logic to fetch world config data, unit config data, buildings config data
- unified logic to fetch villages, players, ally, etc data
- unified logic to do consecutive get requests with delay
- unified logic to check if the world supports paladins, archers, watchtowers, church, etc
- lots of helper methods pre-prepared for you
- unified logic for script debug notices
- unified logic for script hit counter
- unified logic for translations

What is the future of this library?
I am using the library myself on all new scripts I am writing to test its limits and see what can be improved or added. I would love to hear feedback (preferably on Discord) and if I see that other developers want to join this project, I might create a git repo where people can do pull requests so we all can contribute and improve the code.

Should you use the library right now?
I have been using the library on many scripts I have written. It has helped me write scripts faster and with less code since the shared logic is now part of the library and with a unified codebase. However, the development phase of the library is in the Beta version and the library is still changed almost daily by me. So, as it is also mentioned in the library use the library at your own risk.

How to use the library?
You can check my latest created scripts for this and see how I have used the library. I would recommend checking like 3-4 scripts so you could get a better understanding of what the library offers and how to use it.

What else?
I would like to thank Sophie and Sass because I have included some methods I took from their scripts in this library, with their acknowledgment and consent. If you are a developer and have ideas on improving the library (pretty sure it can be improved), feel free to reach out to me on Discord. The library is a personal project of mine and it is not related to my responsibilities as a Tribal Wars scripts moderator.

Regards,
Red.
 

RedAlert

Senior In-Game Staff
Tribal Wars Team
Senior
Team
Script Moderator
Reaction score
608
SDK Library has been rehosted to:

This was done because basically I'm trying to minimize the usage on my Dropbox account.

All of my own scripts built using the SDK are already update to fetch the SDK from the new host so you should not experience any downtime at all from this switch. This notice is mostly dedicated to developers who want to use the SDK.
 
Top