Modding the Final Earth 2

This guide will give you a quick introduction on how to mod the Steam version of The Final Earth 2. Modding is a work in process feature, so note that this is not necessarily final. Modding tools and this guide will be expanded over time.

The possibilities for mods are endless. However, complex mods currently require some extensive knowledge of the game code. Simpler mods, like adding a new building, are much easier to make. Some coding knowledge is required, though, except for very simple mods such as texture packs.

To talk to other modders, check out their creations and share yours, you're welcome to join the Discord and check out the #modding channel!

I hope you have fun modding the game and I'm excited to see what you'll create!

Table of Contents

Requirements

To be able to mod The Final Earth 2, you will need to have the Steam version of The Final Earth 2 installed. Currently, only the demo is available, so you can use that for modding for now.

You will also need a text/code editor and a sprite editor with transparency support (e.g. not just Paint). I personally use Visual Studio Code and Paint.NET, which are both free and also very good.

Checking out the game files

While making mods, you may sometimes want to see how something is implemented in-game. To do this, right-click the game in your Steam libraries, choose Properties, then Local Files, then Browse. The game files are in the game directory.

Sprites are packed into a single sprite sheet. In case you need the original sprites, you can download them here. Note that some of these sprites are unused.

Creating a mod: directory and files

To start modding, open Explorer and go to %localappdata%\the-final-earth-2\User Data\Default\mods. Just add a folder here to make a new mod.

All files in your modding directory and its subdirectories will be loaded by the game. In particular:

These JSON files should contain an array with info objects about the elements you want to add. For an example of what this should look like, open the respective game file. You can also overwrite existing elements of the game by using their className.

Note that in most cases, such as for buildings and upgrades, you will need to add code in addition to the JSON object. Otherwise, your mod won't work and the game may crash.

Here's an example of a possible buildinginfo.json:

[
    {
        "className": "CorporationOfTheOwl",
        "name": "Corporation of the Owl HQ",
        "description": "They see everything.",
        "food": 50000,
        "wood": 50000,
        "stone": 0,
        "machineParts": 0,
        "refinedMetal": 0,
        "computerChips": 1250,
        "knowledge": 75000,
        "category": "Unique Buildings",
        "unlockedByDefault": false,
        "specialInfo": [
            "unique"
        ],
        "residents": 12,
        "quality": 100,
        "jobs": 12,
        "showUnlockHint": "Build The Machine to unlock!"
    },
    {
        "className": "FunctionalHouse",
        "name": "Functional House",
        "description": "Don't complain, this house is exactly what you need.",
        "food": 0,
        "wood": 0,
        "stone": 10,
        "machineParts": 0,
        "knowledge": 10000,
        "computerChips": 1,
        "category": "Houses",
        "unlockedByDefault": false,
        "specialInfo": [],
        "residents": 10,
        "quality": 35
    }
]

Furthermore:

Load Order

Currently, the order in which mods are loaded is not defined. Please keep this into account.

Coding your mod

While the game is originally written in Haxe, mods use JavaScript and can hook into the compiled JavaScript of the game. You can also use all features of PixiJS version 4.5.4, the graphics library used by the game.

To add code to your mod, just create a JavaScript file and start coding. The code will be automatically executed after the game has been loaded.

You can overwrite or expand functions in the game code. In most cases, you will want to call the parent function, plus do something extra before or after that. Here's an example of how that might look like:

(function(existingFunction) {
    gui_CityGUI.prototype.addGeneralStatistics = function () {
        existingFunction.call(this);

        //Insert new code here
    };
}(gui_CityGUI.prototype.addGeneralStatistics));

With some actual code inserted to add a button, it could look like this:

(function(existingFunction) {
    gui_CityGUI.prototype.addGeneralStatistics = function () {
        existingFunction.call(this);

        // Constants for later use
        const _gthis = this;
        const generalStatistics =
            this.cityInfo.children[this.cityInfo.children.length - 1];

        // A stat to show on the button
        function getWorkBuildingNumber() {
            return _gthis.city.workBuildings.length;
        }

        // Create a new button
        // (onclick, onhover, text, icon, parent, default width, show active)
        this.workBuildingsButton = this.createInfoButton(
            function() {
                // Hide/show a window on clicking the button
                if(_gthis.windowRelatedTo == "workBuildings") {
                    _gthis.closeWindow();
                } else {
                    _gthis.createWindow("workBuildings");
                    _gthis.windowAddInfoText(null,
                        () => `There are ${getWorkBuildingNumber()} work buildings` +
                            "in the city.");
                    _gthis.windowAddBottomButtons();
                }
            }, 
            function() {
                _gthis.tooltip.setText("Work Buildings",
                    "The number of work buildings in the city.");
            },
            function() {
                return getWorkBuildingNumber() + "";
            },
            "spr_work", generalStatistics, 20, function() {
                return _gthis.windowRelatedTo == "workBuildings";
            });
        this.workBuildingsButton.keyboardButton = Keyboard.getLetterCode("B");
        
        // Add it to the UI!
        generalStatistics.insertChild(this.workBuildingsButton, 1);
    };
}(gui_CityGUI.prototype.addGeneralStatistics));

There are also some helper functions. These can be used to easily add buildings, materials and more. For a full list, see Helper functions below.

Here's an example of how to add a new building in code:

ModTools.makeBuilding("IronHouse", (superClass) => { return {
    walkAround: function(citizen, stepsInBuilding) {
        if (random_Random.getInt(3) == 1) {
            citizen.changeFloor();
            return;
        }

        //Slowly move in the house
        citizen.moveAndWaitRandom(3, 17, 60, 90, null, false, true);
    },
    get_possibleUpgrades: function() {
        return [];
    }
};}, "spr_ironhouse");

Delta timing

The game uses delta timing. This means all timing is based on the length of time the last frame took. timeMod is passed to many functions so you can deal with this, i.e. by multiplying the material amounts produced per step with it.

Testing your mod

Simply run the game to test your mod. There are a few useful keyboard shortcuts you can use for testing:

Helper functions

Helper functions are part of the ModTools object. Currently, the following helper functions are available. A ? denotes that a function argument is optional.

ModTools.makeBuilding(className, fields, spriteName, ?saveFunc, ?loadFunc, ?superClass)

Define a new building.

ModTools.buildingAddEntertainmentProperties(building, getEntertainmentCapacity, beEntertained, entertainmentType, minimumNormalTimeToSpend, maximumNormalTimeToSpend, minimumEntertainmentGroupSatisfy, maximumEntertainmentGroupSatisfy, isOpenFunc)

After defining a building, if it is an entertainment building, you can add entertainment properties to it. Note that some uncommon functionality, such as get_isOpenForExistingVisitors, is not covered by this function and will have to be set manually if you need it.

ModTools.makeBuildingUpgrade(className, fields, ?onCreate, ?spriteName, ?displayLayer)

Define a new building upgrade. You should also add it to one or more buildings by adding it to a get_possibleUpgrades.

Adding City Upgrades, World Resources or City Policies

Currently, no helper functions are available for adding city upgrades, world resources or city policies. You can construct the classes manually for now.

ModTools.addBuildBasedUnlock(elementClass, unlockFunc, ?fullUnlockFunc)

Set the unlock condition of a city element (building, upgrade or policy), based on the current number of buildings per type. For buildings, you can set an unlock hint in buildinginfo.json. If this is set, the unlockFunc defines when this unlock hint is shown, and the building is only actually unlocked when fullUnlockFunc returns true. Otherwise, you only need to define the unlockFunc. Both of these functions are passed a haxe dictionary as an argument. Use its h property to get a JavaScript object containing the number of buildings per type.

Example code:

ModTools.addBuildBasedUnlock(buildingUpgrades_FancyTablets, function(blds) {
    return blds.h["buildings.School"] >= 10;
});

ModTools.addStatBasedUnlock(elementClass, unlockFunc, ?fullUnlockFunc)

Set the unlock condition of a city element (building, upgrade or policy), based on the stats of the city (e.g. happiness). For buildings, you can set an unlock hint in buildinginfo.json. If this is set, the unlockFunc defines when this unlock hint is shown, and the building is only actually unlocked when fullUnlockFunc returns true. Otherwise, you only need to define the unlockFunc. Both of these functions are passed the city as an argument.

ModTools.addMaterial(varName, displayName, description, ?tooltipExt)

Add a custom material to the game. It will then be shown in the UI and can be produced, consumed and used in costs.

ModTools.produce(city, varName, amount, ?ind)

Produce a given amount of a material. Using this function ensures that this is added to the daily production.

ModTools.consume(city, varName, amount, ?ind)

Consume a given amount of a material. See ModTools.produce above for a description of the parameters.

ModTools.onCityUpdate(func)

Adds a function that will be called on every city update.

ModTools.onModsLoaded(func)

Adds a function that will be called after all mods have been loaded.

ModTools.onCityLoadStart(func)

Adds a function that will be called before loading a save file.

ModTools.addSaveData(modIdentifier, saveFunc, loadFunc, ?version)

Add something to the game save. This can be used for anything you want to save that's relevant for the whole city, instead of just for a single building. The specified functions trigger after saving/loading everything else. Also see the section below on how to use the ResizingBytesQueue.

ModTools.addSaveDataEarly(modIdentifier, saveFunc, loadFunc, ?version)

The same as ModTools.addSaveData, except that the functions specified here trigger before loading anything else.

Other code documentation

The code of The Final Earth 2 is quite complex, and I'm definitively not able to document all of it here. Instead, this section contains documentation of some parts you may use commonly.

The ResizingBytesQueue

The ResizingBytesQueue is a automatically resizing array of bytes, which is used to store save data. It is passed to all saving related functions. While saving, use the add... functions to add data to it. While loading, use the read... functions to read the data back. There are functions for various data types. The following are common:

Citizen movement

For most buildings, you'll want to model movement of citizens inside. There are some helper functions on citizen objects to assign paths within a building. These are commonly used:

You may also need the following properties: