Notice: Modding tutorials assume you know at least the basics of coding or scripting. Commander Wars uses Javascript as the Scripting Language. In case you want to learn more about Javascript, you can check the tutorial here: Derek Banas' Javascript tutorial , purchase O'Reilly's excellent book on the subject JavaScript: The Definitive Guide, or check out W3 Schools Interactive Tutorials

You may download the files of this tutorial on Discord

Setup

In this tutorial a basic CO will be created, with an Advance Wars 2 like set of powers. The mod must be created in the mods folder for the Javascript to be detected properly.

To simplify things, we will create a CO that simply boosts the defense and offense of all units and heals up during powers. However notes to modify specific units, adding custom weapon values to units will be linked at the end of the tutorial.

  • Create the following folder and file structure, inside a new testco folder leave any files empty for now. The mp3 files can be any songs of your choice, so as long as they have the same file name. The png images will be added later in the tutorial.
~/mods/testco
├── images
   └── co
       ├── co_test+face.png
       ├── co_test+info.png
       ├── co_test+nrm.png
       └── res.xml
├── mod.txt
├── music
   └── cos
       ├── Power.mp3
       ├── Super.mp3
       └── Theme.mp3
└── scripts
    └── cos
        └── co_test.js


  • To the mod.txt file add the following content. This is required for the engine to detect compatibilities between mods, and to display what your mod does
name=Test CO
description=This is a test CO.
version=N/A
compatible_mods=
incompatible_mods=
required_mods=
cosmetic=false

CO Constructor

Inside co_test.js put the following code:

var Constructor = function()
{
   this.init = function(co,map)
    {
        co.setPowerStars(3);
        co.setSuperpowerStars(3);
    };

    this.getCOArmy = function()
    {
        return "OS";
    };


}

Constructor.prototype = CO;
var CO_TEST = new Constructor();

This code simply inits the constructor to create our CO, this.init sets the stars that are required for the Power and Super Power respectively. this.getCOArmy sets to which country the CO belongs to. In this case OS sets it for the Orange Star Army. The list of values are: OS for Orange Star, BM for Blue Moon, YC for Yellow Comet, GE for Green Earth and BH for Black Hole. Custom factions will have their own, specific strings.

CO Intel

Up next, we will add the Intel information of our CO.

var Constructor = function () {
    
    this.init = function (co, map) {
        co.setPowerStars(3);
        co.setSuperpowerStars(3);
    };

    this.getCOArmy = function () {
        return "OS";
    };

    this.getBio = function (co) {
        return qsTr("A test CO.");
    };

    this.getHits = function (co) {
        return qsTr("Hitting");
    };

    this.getMiss = function (co) {
        return qsTr("Missing");
    };

    this.getCODescription = function (co) {
        return qsTr("A Simple CO created for this tutorial. All units have 10% extra Firepower and Defense");
    };

    this.getPowerDescription = function (co) {
        return qsTr("Increases the Firepower and Defense of all units by 20%, heals 2HP to all units");
    };

    this.getPowerName = function (co) {
        return qsTr("Power Boost");
    };

    this.getSuperPowerDescription = function (co) {
        return qsTr("Increases the Firepower and Defense of all units by 40%, heals 5HP to all units");
    };

    this.getSuperPowerName = function (co) {
        return qsTr("Super Power Boost");
    };

    this.getPowerSentences = function (co) {
        return [qsTr("Attack!"), qsTr("Destroy the Enemy!")];
    };

    this.getVictorySentences = function (co) {
        return [qsTr("Victory!")];
    };

    this.getDefeatSentences = function (co) {
        return [qsTr("Defeat...")];
    };

    this.getName = function () {
        return qsTr("Test CO");
    };


}

Constructor.prototype = CO;
var CO_TEST = new Constructor();

The anonymous functions set the values for each of the respective values in the Engine. The qsTr() function is a Qt function which is used to generate translatable strings in the Engine. You can know more about the qsTr() function here: qsTr()

this.getVictorySentences, this.getDefeatSentences, this.getPowerSentences all take arrays which are randomized when a Victory, Defeat or Power activation occur.

this.getPowerSentences lists the strings for both the CO Power and Super Power.

Adding Offensive and Defensive stats

To add Firepower and Defense in CoW-Engine Style we can use the following functions

//Constructor code ommited for brevity
Constructor.prototype = CO;
var CO_TEST = new Constructor();

CO_TEST.getOffensiveBonus = function (co, attacker, atkPosX, atkPosY,
    defender, defPosX, defPosY, isDefender, action, luckMode, map) {

    switch (co.getPowerMode())
    {
        case GameEnums.PowerMode_Tagpower:
        case GameEnums.PowerMode_Superpower:
            return 50;
        case GameEnums.PowerMode_Power:
            return 30;
        default:
            if (co.inCORange(Qt.point(atkPosX, atkPosY), attacker))
            {
                return 10;
            }
                return 10; 
            break;
    }
    return 0;

}

CO_TEST.getDeffensiveBonus = function (co, attacker, atkPosX, atkPosY,
    defender, defPosX, defPosY, isAttacker, action, luckMode,map) {

    switch (co.getPowerMode())
    {
        case GameEnums.PowerMode_Tagpower:
        case GameEnums.PowerMode_Superpower:
            return 50;
        case GameEnums.PowerMode_Power:
            return 30;
        default:
            if (co.inCORange(Qt.point(atkPosX, atkPosY), attacker))
            {
                return 10;
            }
                return 10; 
            break;
    }
    return 0;

}

This will set the Defense and Offense of all units to 10% inside the co-zone as the day to day ability. And increase both to 30% during the Power activation, and to 50% during the Super Powers for all units. All bonuses are applied independend of the fact if it's the first or second co.


The following example does the same but as an AW-DS-Style CO.

//Constructor code ommited for brevity
Constructor.prototype = CO;
var CO_TEST = new Constructor();

CO_TEST.getOffensiveBonus = function (co, attacker, atkPosX, atkPosY,
    defender, defPosX, defPosY, isDefender, action, luckMode, map) {

    if (co.getIsCO0() === true) {
        switch (co.getPowerMode()) {

            case GameEnums.PowerMode_Superpower:
                return 50;

            case GameEnums.PowerMode_Power:
                return 30;
            
            default:
                return 10;
                break;
        }
    }
    return 0;

}

CO_TEST.getDeffensiveBonus = function (co, attacker, atkPosX, atkPosY,
    defender, defPosX, defPosY, isAttacker, action, luckMode,map) {

    if (co.getIsCO0() === true) {
        switch (co.getPowerMode()) {

            case GameEnums.PowerMode_Superpower:
                return 50;

            case GameEnums.PowerMode_Power:
                return 30;

            default:
                return 10;
                break;
        }
    }
    return 0;

}


This will set the Defense and Offense of all units to 10% as the day to day ability, increase both to 30% during the Power activation, and to 50% during the Super Power. If the CO is the currently the first one.

Power and Super Power Animations

To create the Power animation we will use the following code:

//Rest of the code ommited for brevity
var Constructor = function () {
      this.activatePower = function(co, map)
    {
        var dialogAnimation = co.createPowerSentence();
        var powerNameAnimation = co.createPowerScreen(GameEnums.PowerMode_Power);
        dialogAnimation.queueAnimation(powerNameAnimation);

        var units = co.getOwner().getUnits();
        var animations = [];
        var counter = 0;
        units.randomize();
        for (var i = 0; i < units.size(); i++)
        {
            var unit = units.at(i);

            var animation = GameAnimationFactory.createAnimation(map, unit.getX(), unit.getY());
            animation.writeDataInt32(unit.getX());
            animation.writeDataInt32(unit.getY());
            animation.writeDataInt32(2);
            animation.setEndOfAnimationCall("ANIMATION", "postAnimationHeal");
            var delay = globals.randInt(135, 265);
            if (animations.length < 5)
            {
                delay *= i;
            }
            animation.setSound("power0.wav", 1, delay);
            if (animations.length < 5)
            {
                animation.addSprite("power0", -map.getImageSize() * 1.27, -map.getImageSize() * 1.27, 0, 2, delay);
                powerNameAnimation.queueAnimation(animation);
                animations.push(animation);
            }
            else
            {
                animation.addSprite("power0", -map.getImageSize() * 1.27, -map.getImageSize() * 1.27, 0, 2, delay);
                animations[counter].queueAnimation(animation);
                animations[counter] = animation;
                counter++;
                if (counter >= animations.length)
                {
                    counter = 0;
                }
            }
        }
        units.remove();
    };

}

And for the Super Power:

//Rest of the code omitted for brevity 
var Constructor = function () {
      this.activateSuperpower = function(co, powerMode, map)
    {
        var dialogAnimation = co.createPowerSentence();
        var powerNameAnimation = co.createPowerScreen(powerMode);
        powerNameAnimation.queueAnimationBefore(dialogAnimation);

        var units = co.getOwner().getUnits();
        var animations = [];
        var counter = 0;
        units.randomize();
        for (var i = 0; i < units.size(); i++)
        {
            var unit = units.at(i);

            var animation = GameAnimationFactory.createAnimation(map, unit.getX(), unit.getY());
            animation.writeDataInt32(unit.getX());
            animation.writeDataInt32(unit.getY());
            animation.writeDataInt32(5);
            animation.setEndOfAnimationCall("ANIMATION", "postAnimationHeal");
            var delay = globals.randInt(135, 265);
            if (animations.length < 7)
            {
                delay *= i;
            }
            if (i % 2 === 0)
            {
                animation.setSound("power12_1.wav", 1, delay);
            }
            else
            {
                animation.setSound("power12_2.wav", 1, delay);
            }
            if (animations.length < 7)
            {
                animation.addSprite("power12", -map.getImageSize() * 2, -map.getImageSize() * 2, 0, 2, delay);
                powerNameAnimation.queueAnimation(animation);
                animations.push(animation);
            }
            else
            {
                animation.addSprite("power12", -map.getImageSize() * 2, -map.getImageSize() * 2, 0, 2, delay);
                animations[counter].queueAnimation(animation);
                animations[counter] = animation;
                counter++;
                if (counter >= animations.length)
                {
                    counter = 0;
                }
            }
        }
        units.remove();
    };

}

CO Images

In the Setup step, we created a set of folders, "mods/testco/images/co/" is where all the images for this tutorial will go. Make sure you also created the res.xml file inside the "mods/testco/images/co/" folder.

Inside res.xml place the following:

<?xml version="1.0"?>
<resources>
  <set path = "mods/testco/images/co/" />
   <atlas linearFilter="false">
      <set scale_factor = "1.0" /> 
      <image file="co_test+nrm.png" />
      <set scale_factor = "1.0" /> 
      <image file="co_test+info.png" />
      <image file="co_test+face.png" cols = "3" />
   </atlas>
</resources>

co_test+nrm.png is the image that will be displayed when the power and superpowers are activated. It will also show on the Intel options of the CO. This image has no set standard and can be of any size you choose, such that it fits on the screen.

co_test+nrm.png

co_test+info.png is the image that shows next to the Star Meter of the CO as well as in some menus of the game. The standard size for this image is 32x12.

co_test+info.png


co_test+face.png is the portrait picture of the CO, with three different facial expressions, the order of these facial expressions is normal, happy and sad, these can be given a rows argument to have Advance Wars style animations. You can use the scale_factor parameter to resize the images in-game. The standard for this image is 144x48 (48x48 for each facial expression).

co_test+face.png

CO Music

Inside the constructor of the CO, place the following code:

var Constructor = function()
{
  //Rest of the code ommited for brevity
 this.loadCOMusic = function (co,map) {
        
        switch (co.getPowerMode()) {
            case GameEnums.PowerMode_Power:
                audio.addMusic("mods/testco/music/cos/Power.mp3", 0, 0);
                break;
            case GameEnums.PowerMode_Superpower:
                audio.addMusic("mods/testco/music/cos/Super.mp3", 0, 0);
                break;
            case GameEnums.PowerMode_Tagpower:
                audio.addMusic("mods/testco/music/cos/Super.mp3", 0, 0);
                break;
            default:
                audio.addMusic("mods/testco/music/cos/Theme.mp3", 0, 0);
                break;
        }
    };
 


}

Constructor.prototype = CO;
var CO_TEST = new Constructor();

Make sure the files are properly placed inside the path passed to the audio.addMusic() function.

Activating the CO

  • Start the Game.
  • Click 'Options'.
  • On the right hand side of the Options menu, click 'Mods'.
  • Activate the checkbox 'Test CO' and click 'Exit'.

After you do, the game will restart on its own. Once the game restarts, click on 'Style CO'. The test CO should show up under Orange Star.

Further Information

To learn more about the details of how the code works, this page breaks down Andy and the specifics of each segment of code.

Andy Breakdown