Backwards compatibility
Scenario
How do you make your scripts backwards compatible with older versions of the JS API?Over time, the JS API changes - new functions are added, existing functions are changed or renamed, old functions are removed, and so on.
This guide looks at some ways to achieve backwards compatibility with earlier JS API versions if you don't want to use backport.js and it's associated Backport Scripts.
Solution
What follows is a list of things that your script might encounter in the wild and how to identify them and, where possible, work around them. As Warzone 3.1 Beta 1 was the first widely adopted release of warzone that made use of the JS API, that's where we'll start from.
Finding batches of changes
Sometimes it will be difficult to check for an API change - for example if one of the parameters of a function was changed, but the number of parameters for that function remains the same (someFunction.length tells you how many params a function expects if they are named params).
When scenarios like this occur, you have to look for other changes made around the same time. Commits to the Warzone C++ source code usually include a batch of changes so there's usually something else you can look for to identify what version of the JS API you're dealing with.
A good example is the pickStructLocation() offset bug. The bug was fixed within the function, so there's no way your script can see that. But in the same commit, the removeReticuleButton() function was added to the API - so you can check if that function exists and if it does it's fairly safe to assume that you're dealing with a fixed version of pickStructLocaiton() – a code sample is shown later on this page for this particular scenario.
To find batches of changes, go to the C++ source file in the git repository, and click the history button. To save you time, here's links to the history for the two source files that contain most of the JS API stuff:
- qtscriptfuncs.cpp history – this contains most of the stuff you see in the JS API
- qtscript.cpp history – this contains things like setTimer() and include()
A quick way to use different code based on API features
Often you'll improve your functions as new API features are added. You can maintain some level of backwards compatibility by keeping your old functions and using them if an old version of the API is present:
var myFunction = (assertion) // assert if the new feature exists ? function(...) { ... } // new and improved function : function(...) { ... } // older version of your function
For example, let's say you originally had a getLaserSatelliteTarget() function that picked a random target, but when the ".cost" property was added to structures/droids you decided to update it to pick the most expensive enemy target. When your script initialises, you can set up the right version of the function based on the JS API features available as follows:
function eventStartLevel() { // all games start with a construction droid // if it has a .cost property, we can use cost-based targeting function global.getLaserSatelliteTarget = ("cost" in enumDroid()[0]) ? function() { /* pick target based on cost */ } : function() { /* pick random target */ } }
By doing this, you only check for the desired API feature at the start of the game instead of checking for it every time your getLaserSatelliteTarget() function is called.
pickStructLocation() offset bug
In early January 2012, there was a bug whereby pickStructLocation() would offset positions by 1 tile for most buildings (except cyborg factories).
You can detect the presence of the bug, and work around it, as follows:
var pos = pickStructLocation(myDroid,structToBuild,x,y); if (!global["removeReticuleButton"] && structToBuild != "A0CyborgFactory") { // need to fix offset bug ++pos.x; ++pos.y; }
droidFromId() → objFromId()
In early January 2012 droidFromId() was deprecated, use objFromId() instead:
if (!objFromId && !!droidFromId) global.objFromId = droidFromId; // then use objFromId() exclusively in your code
orderDroidStatsLoc() → orderDroidBuild()
In early January 2012 orderDroidStatsLoc() was deprecated, use orderDroidBuild() instead:
if (!orderDroidBuild && !!orderDroidStatsLoc) global.orderDroidBuild = orderDroidStatsLoc; // then use orderDroidBuild() exclusively in your code
DORDER_STOP emulation
In mid-February 2012, the new orderDroid() function and DORDER_STOP constant, along with a load of other stuff, was added. If you want to tell a droid to stop it's current command prior to this change, do something like the following:
if (!!DORDER_STOP) { // do it the new way orderDroid(myDroid, DORDER_STOP); } else { // do it the old way orderDroidLoc(myDroid, DORDER_MOVE, myDroid.x, myDroid.y); }
.canHitAir emulation
In warzone 3.1 beta 2 and earlier, there was no ".canHitAir" property on droids and structures. You can create a custom canHitAir() function that masks this issue as follows:
var canHitAir = ("canHitAir" in enumDroid()[0]) ? function(obj) { return obj.canHitAir; } : function(obj) { var name = obj.name; // bail out quick for certain scenarios: if (obj.type == STRUCTURE) { if (obj.stattype != DEFENSE) return false; if (name.indexOf("Sensor")>-1) return false; } if (obj.type == DROID && obj.droidType != DROID_WEAPON && obj.droidType != DROID_CYBORG) return false; // proper AA stuff: if (name.indexOf("AA ")>-1) return true; if (name.indexOf(" SAM")>-1) return true; if (name.indexOf("Whirl")>-1) return true; // whirlwind if (name.indexOf("Aveng")>-1) return true; // avenger if (name.indexOf("Vindi")>-1) return true; // vindicator if (name.indexOf("Sunbu")>-1) return true; // sunburst // versatile weapons: if (name.indexOf("Assault Gun")>-1) return true; if (name.indexOf("Machinegun")>-1) return true; if (name.indexOf("Flashlight")>-1) return true; if (name.indexOf("Pulse")>-1) return true; // pulse laser if (name.indexOf("Rocket Pod")>-1) return true; if (name.indexOf("Hyper")>-1) return true; // HVC // assume the object can't shoot at aircraft return false; }
chat() error avoidance
The chat() function was added in v3.2. While there's no way to send messages to players using earlier versions of the API, you can create a dummy chat() function to avoid errors being generated when your script tries to use the chat system:
if (!("chat" in global)) { global.chat = function(to,message) { console(message); } chat.enabled = false; } else { chat.enabled = true; } // you can check chat.enabled in your code if you want to know whether the chat function is real or not if (chat.enabled) { // the chat function is real, we can talk to other players! }