- Reaction score
- 623
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
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
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
- 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
- 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.
- 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).
- 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).
- 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.
- 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.
- 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.
- 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.
- 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).
- 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.
- 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.
- 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.
- 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: