Jump to content
Age of History 3
Łukasz Jakowski

Map Editor for Age of History 3

Recommended Posts

Main map for AoH3 will be:

    BackgroundSize_X: 8,
    BackgroundSize_Y: 4,

 

It is:

17760 px width

8600px height

 

 

MAP_NAME=MapName
WIDTH=1920
HEIGHT=1080
MAXIMUM_SIZE=true
DRAW_POINTS=true
DRAW_PROVINCE_ID=false
CUT_FROM_PROVINCE_MODE=false

CONVERT_AOH2_MAP_PROVINCES_DATA_TO_AOH3=false

/** To convert a map from AoH2 to the AoH3, put the provinces points file from old mapEditor and name it "convertMapData.txt" in the same folder as mapEditor_2.jar. Open mapEditor_2.jar and click ENTER to save data. */

SPACE = Enable/Disable draw provinces
O = Enable/Disable draw provinces points
C = Enable/Disable draw cities
V = Enable/Disable draw cities names
Z = Enable/Disable background
X = Enable/Disable backgroundMapEditor
L = Rebuild provinces colors
Y = Follow the path of the province
P = Draw province ID
J = Delete a province


## Map -> Config.json

#### CODE

Quote

{
    Name: "MapName",
    Author: "AuthorName",
    
    NumOfProvinces: 1,
    DefaultMapScale: 4,
    WorldMap: true,
    
    BackgroundSize_X: 3,
    BackgroundSize_Y: 2,
    
    BackgroundZoomOut_Enable: true,
    BackgroundZoomOut_Size_X: 3,
    BackgroundZoomOut_Size_Y: 2,
    BackgroundZoomOut_Scale: 0.13,
    
    BackgroundZoomOut_AnimationDuration: 600,
    
    BackgroundColor: [13, 31, 49],
    BackgroundColor_ZoomIn: [5, 22, 40],
    BackgroundColor_ZoomOut: [11, 24, 33],
    
    WastelandColor: [200, 165, 130],
    
    DefaultScenario: "1440",
    Wiki: "Earth",
}

#### CODE END

## The width of every single image should be the same!
## The height of every single image should be the same!

## The width and height may be different.

## Example
    BackgroundSize_X: 3,
    BackgroundSize_Y: 2,
    
How many horizontal images and how many vertical images the map background is divided into.

In this example we have 3 horizontal images and two vertical images.

The naming of images:

It will be always: Y_X.png

0_0.png
0_1.png
0_2.png

Next line of images:

1_0.png
1_1.png
1_2.png

## Example 2
    BackgroundSize_X: 2,
    BackgroundSize_Y: 2,
    
It will be always: Y_X.png

0_0.png
0_1.png

Next line of images:

1_0.png
1_1.png


## Example 3
    BackgroundSize_X: 4,
    BackgroundSize_Y: 3,
    
It will be always: Y_X.png

0_0.png
0_1.png
0_2.png
0_3.png

Next line of images:

1_0.png
1_1.png
1_2.png
1_3.png

Next line of images:

2_0.png
2_1.png
2_2.png
2_3.png

Share this post


Link to post
Share on other sites

Hmm.... I have no idea
    BackgroundSize_X: 8,
    BackgroundSize_Y: 4,
How calculator size resolutions?

I make new test image map 0_0.png 1792x1024

    BackgroundSize_X: 0,
    BackgroundSize_Y: 0,

    BackgroundZoomOut_Enable: true,
    BackgroundZoomOut_Size_X: 0,
    BackgroundZoomOut_Size_Y: 0,
    BackgroundZoomOut_Scale: 0.13,

but not work won't launch error

Error: Could not find or load main class MapEditor_2.jar
Caused by: java.lang.ClassNotFoundException: MapEditor_2.jar

Maybe you make new program auto image map to 0_0 0_1 0_2 0_3 1_0 1_1 1_2 1_3? (Map Converter Full Image to 0_0 0_1.. etc)

Share this post


Link to post
Share on other sites

4 minutes ago, aunitydevteam said:

the map tiles can be any resolution but as long as tile stays the same resolution for all tiles you can have as many tiles has you need 

for the program not running do you have Java Installed?

I know already I have Java
MapName worked (by dev)
but I created Test123 map not work 0_0.png image 1792x1024
I need Full image convert to auto 0_0 0_1 0_2 .. etc

Share this post


Link to post
Share on other sites

20 minutes ago, aunitydevteam said:

I used gimp with plugins that did all the hard work easy for me I'll tell you all how to use gimp with plugins to split your maps tomorrow because I'm not at my computer 

Yea I got Photoshop with Split worked
thanks for helpful
https://imgur.com/a/Qjydsp4

Share this post


Link to post
Share on other sites

BackgroundSize_X: 0,
BackgroundSize_Y: 0,

It can't be 0. If you want to use only one image, it should be:

BackgroundSize_X: 1,
BackgroundSize_Y: 1,

 

Image name: 0_0.png

 

For two images like it was in AoH2 it would be:

BackgroundSize_X: 2,
BackgroundSize_Y: 1,

 

Image name: 0_0.png

Image name: 0_1.png

## The width of every single image should be the same!
## The height of every single image should be the same!

## The width and height may be different.

 

Read: MapEditorSettings.txt file, there is more tips!

 

 

BackgroundSize_X: 3,
BackgroundSize_Y: 2,

 

Image name: 0_0.png

Image name: 0_1.png

Image name: 0_2.png

 

Image name: 1_0.png

Image name: 1_1.png

Image name: 1_2.png

 

Share this post


Link to post
Share on other sites

19 hours ago, Łukasz Jakowski said:

Map Editor for Age of History 3

 

It works almost the same as the old one, but is more convenient.

If you want, you can now create a province map in this map editor and once the game is released, your map will be playable in the game.

 

You can save the province by clicking Enter and continue adding another new province.

You can remove a province and add another one in its place by clicking "J". Do not close the editor until all deleted provinces have been added.

 

 

CUT_FROM_PROVINCE_MODE=true

New special version of the map, provinces created in this mode will crop the images of the main provinces.

 

All provinces are stored in one file: MapName/data/ProvincePoints.json

No more strange files with numbers like in AoH 2.

 

One tip: make map backgrounds larger than in AoH2, so your map will have more details.

For example with width: 6000 px or 8000 px or even more.

The main map for AoH3 will be: 17760 px width x 8600px height

 

Read: MapEditorSettings.txt file, there is more tips!

 

The map editor will also be included in the game files.

Required: Java JDK 21: https://www.oracle.com/java/technologies/downloads/#jdk21-windows

 

mapeditor.thumb.png.675f163462edd4e445e50ac1273d063e.png

MapEditor_2.zip 27.33 MB · 119 downloads

it should be updated to java 8, because java jdk 21 doesnt support 32 bit devices

Edited by uwut

Share this post


Link to post
Share on other sites

Are these json5 format?
I'm sure you know, but json is compressed. json5 may be more optimized and take up less space.
In Json, the expression starts with "" and ends with "value:" followed by the value of the data.
String is also written inside "". If it is boolean or number, it is not written.
Then, commas are not used in the definition that continues within the same object or series.

Quote

{
    Name: "MapName",
    Author: "AuthorName",
    
    NumOfProvinces: 1,
    DefaultMapScale: 3,
    WorldMap: true,
    
    BackgroundSize_X: 3,
    BackgroundSize_Y: 2,
    
    BackgroundZoomOut_Enable: true,
    BackgroundZoomOut_Size_X: 3,
    BackgroundZoomOut_Size_Y: 2,
    BackgroundZoomOut_Scale: 0.13,
    
    BackgroundZoomOut_AnimationDuration: 600,
    
    BackgroundColor: [13, 31, 49],
    BackgroundColor_ZoomIn: [5, 22, 40],
    BackgroundColor_ZoomOut: [11, 24, 33],
    
    WastelandColor: [200, 165, 130],
    
    DefaultScenario: "1440",
    Wiki: "Earth",
}

so:

Quote

{
    "Name": "MapName",
    "Author": "AuthorName",
    "NumOfProvinces": 1,
    "DefaultMapScale": 3,
    "WorldMap": true,
    "BackgroundSize_X": 3,
    "BackgroundSize_Y": 2,
    "BackgroundZoomOut_Enable": true,
    "BackgroundZoomOut_Size_X": 3,
    "BackgroundZoomOut_Size_Y": 2,
    "BackgroundZoomOut_Scale": 0.13,
    "BackgroundZoomOut_AnimationDuration": 600,
    "BackgroundColor": [13, 31, 49],
    "BackgroundColor_ZoomIn": [5, 22, 40],
    "BackgroundColor_ZoomOut": [11 ,24, 33],
    "WastelandColor": [200, 165, 130],
    "DefaultScenario": "1440",
    "Wiki": "Earth"
}

 

compress:

Quote

{"Name":"MapName","Author":"AuthorName","NumOfProvinces":1,"DefaultMapScale":3,"WorldMap":true,"BackgroundSize_X":3,"BackgroundSize_Y":2,"BackgroundZoomOut_Enable":true,"BackgroundZoomOut_Size_X":3,"BackgroundZoomOut_Size_Y":2,"BackgroundZoomOut_Scale":0.13,"BackgroundZoomOut_AnimationDuration":600,"BackgroundColor":[13,31,49],"BackgroundColor_ZoomIn":[5,22,40],"BackgroundColor_ZoomOut":[11,24,33],"WastelandColor":[200,165,130],"DefaultScenario":"1440","Wiki":"Earth"}

 

On the other hand, this is a universal standard and using the org.json library.
You can read and write faster without the names of the variables being the same as the tag names.
According to logicbdx.

Just a suggestion.

Share this post


Link to post
Share on other sites

11 minutes ago, aunitydevteam said:

I honestly hate the compressed version its literally like editing a paragraphs in a book i prefer it neat and clean because you can see it

The real problem isn't compression. compression is just an advantage.

So even with a char type " { : [ } ] , json can be read only with quotes and unnecessary commas, it does not create a new memory in memory.
to the reagent
private int a;
It can be done in variables such as.

 

Quote

package com.badlogic.gdx.utils;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;

import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.utils.JsonValue.ValueType;

public class JsonReader implements BaseJsonReader {
    public JsonValue parse (String json) {
        char[] data = json.toCharArray();
        return parse(data, 0, data.length);
    }

    public JsonValue parse (Reader reader) {
        try {
            char[] data = new char[1024];
            int offset = 0;
            while (true) {
                int length = reader.read(data, offset, data.length - offset);
                if (length == -1) break;
                if (length == 0) {
                    char[] newData = new char[data.length * 2];
                    System.arraycopy(data, 0, newData, 0, data.length);
                    data = newData;
                } else
                    offset += length;
            }
            return parse(data, 0, offset);
        } catch (IOException ex) {
            throw new SerializationException(ex);
        } finally {
            StreamUtils.closeQuietly(reader);
        }
    }

    public JsonValue parse (InputStream input) {
        try {
            return parse(new InputStreamReader(input, "UTF-8"));
        } catch (IOException ex) {
            throw new SerializationException(ex);
        } finally {
            StreamUtils.closeQuietly(input);
        }
    }

    public JsonValue parse (FileHandle file) {
        try {
            return parse(file.reader("UTF-8"));
        } catch (Exception ex) {
            throw new SerializationException("Error parsing file: " + file, ex);
        }
    }

    public JsonValue parse (char[] data, int offset, int length) {
        int cs, p = offset, pe = length, eof = pe, top = 0;
        int[] stack = new int[4];

        int s = 0;
        Array<String> names = new Array(8);
        boolean needsUnescape = false, stringIsName = false, stringIsUnquoted = false;
        RuntimeException parseRuntimeEx = null;

        boolean debug = false;
        if (debug) System.out.println();

        try {
        %%{
            machine json;

            prepush {
                if (top == stack.length) {
                    int[] newStack = new int[stack.length * 2];
                    System.arraycopy(stack, 0, newStack, 0, stack.length);
                    stack = newStack;
                }
            }

            action name {
                stringIsName = true;
            }
            action string {
                String value = new String(data, s, p - s);
                if (needsUnescape) value = unescape(value);
                outer:
                if (stringIsName) {
                    stringIsName = false;
                    if (debug) System.out.println("name: " + value);
                    names.add(value);
                } else {
                    String name = names.size > 0 ? names.pop() : null;
                    if (stringIsUnquoted) {
                        if (value.equals("true")) {
                            if (debug) System.out.println("boolean: " + name + "=true");
                            bool(name, true);
                            break outer;
                        } else if (value.equals("false")) {
                            if (debug) System.out.println("boolean: " + name + "=false");
                            bool(name, false);
                            break outer;
                        } else if (value.equals("null")) {
                            string(name, null);
                            break outer;
                        }
                        boolean couldBeDouble = false, couldBeLong = true;
                        outer2:
                        for (int i = s; i < p; i++) {
                            switch (data) {
                            case '0':
                            case '1':
                            case '2':
                            case '3':
                            case '4':
                            case '5':
                            case '6':
                            case '7':
                            case '8':
                            case '9':
                            case '-':
                            case '+':
                                break;
                            case '.':
                            case 'e':
                            case 'E':
                                couldBeDouble = true;
                                couldBeLong = false;
                                break;
                            default:
                                couldBeDouble = false;
                                couldBeLong = false;
                                break outer2;
                            }
                        }
                        if (couldBeDouble) {
                            try {
                                if (debug) System.out.println("double: " + name + "=" + Double.parseDouble(value));
                                number(name, Double.parseDouble(value), value);
                                break outer;
                            } catch (NumberFormatException ignored) {
                            }
                        } else if (couldBeLong) {
                            if (debug) System.out.println("double: " + name + "=" + Double.parseDouble(value));
                            try {
                                number(name, Long.parseLong(value), value);
                                break outer;
                            } catch (NumberFormatException ignored) {
                            }
                        }
                    }
                    if (debug) System.out.println("string: " + name + "=" + value);
                    string(name, value);
                }
                stringIsUnquoted = false;
                s = p;
            }
            action startObject {
                String name = names.size > 0 ? names.pop() : null;
                if (debug) System.out.println("startObject: " + name);
                startObject(name);
                fcall object;
            }
            action endObject {
                if (debug) System.out.println("endObject");
                pop();
                fret;
            }
            action startArray {
                String name = names.size > 0 ? names.pop() : null;
                if (debug) System.out.println("startArray: " + name);
                startArray(name);
                fcall array;
            }
            action endArray {
                if (debug) System.out.println("endArray");
                pop();
                fret;
            }
            action comment {
                int start = p - 1;
                if (data[p++] == '/') {
                    while (p != eof && data[p] != '\n')
                        p++;
                    p--;
                } else {
                    while (p + 1 < eof && data[p] != '*' || data[p + 1] != '/')
                        p++;
                    p++;
                }
                if (debug) System.out.println("comment " + new String(data, start, p - start));
            }
            action unquotedChars {
                if (debug) System.out.println("unquotedChars");
                s = p;
                needsUnescape = false;
                stringIsUnquoted = true;
                if (stringIsName) {
                    outer:
                    while (true) {
                        switch (data[p]) {
                        case '\\':
                            needsUnescape = true;
                            break;
                        case '/':
                            if (p + 1 == eof) break;
                            char c = data[p + 1];
                            if (c == '/' || c == '*') break outer;
                            break;
                        case ':':
                        case '\r':
                        case '\n':
                            break outer;
                        }
                        if (debug) System.out.println("unquotedChar (name): '" + data[p] + "'");
                        p++;
                        if (p == eof) break;
                    }
                } else {
                    outer:
                    while (true) {
                        switch (data[p]) {
                        case '\\':
                            needsUnescape = true;
                            break;
                        case '/':
                            if (p + 1 == eof) break;
                            char c = data[p + 1];
                            if (c == '/' || c == '*') break outer;
                            break;
                        case '}':
                        case ']':
                        case ',':
                        case '\r':
                        case '\n':
                            break outer;
                        }
                        if (debug) System.out.println("unquotedChar (value): '" + data[p] + "'");
                        p++;
                        if (p == eof) break;
                    }
                }
                p--;
                while (Character.isSpace(data[p]))
                    p--;
            }
            action quotedChars {
                if (debug) System.out.println("quotedChars");
                s = ++p;
                needsUnescape = false;
                outer:
                while (true) {
                    switch (data[p]) {
                    case '\\':
                        needsUnescape = true;
                        p++;
                        break;
                    case '"':
                        break outer;
                    }
                    // if (debug) System.out.println("quotedChar: '" + data[p] + "'");
                    p++;
                    if (p == eof) break;
                }
                p--;
            }

            comment = ('//' | '/*') @comment;
            ws = [\r\n\t ] | comment;
            ws2 = [\t ] | comment;
            comma = ',' | ([\r\n] ws* ','?);
            quotedString = '"' @quotedChars %string '"';
            nameString = quotedString | ^[":,}/\r\n\t ] >unquotedChars %string;
            valueString = quotedString | ^[":,{[\]/\r\n\t ] >unquotedChars %string;
            value = '{' @startObject | '[' @startArray | valueString;
            nameValue = nameString >name ws* ':' ws* value;
            object := ws* nameValue? ws2* <: (comma ws* nameValue ws2*)** :>> (','? ws* '}' @endObject);
            array := ws* value? ws2* <: (comma ws* value ws2*)** :>> (','? ws* ']' @endArray);
            main := ws* value ws*;

            write init;
            write exec;
        }%%
        } catch (RuntimeException ex) {
            parseRuntimeEx = ex;
        }

        JsonValue root = this.root;
        this.root = null;
        current = null;
        lastChild.clear();

        if (p < pe) {
            int lineNumber = 1;
            for (int i = 0; i < p; i++)
                if (data == '\n') lineNumber++;
            int start = Math.max(0, p - 32);
            throw new SerializationException("Error parsing JSON on line " + lineNumber + " near: "
                + new String(data, start, p - start) + "*ERROR*" + new String(data, p, Math.min(64, pe - p)), parseRuntimeEx);
        } else if (elements.size != 0) {
            JsonValue element = elements.peek();
            elements.clear();
            if (element != null && element.isObject())
                throw new SerializationException("Error parsing JSON, unmatched brace.");
            else
                throw new SerializationException("Error parsing JSON, unmatched bracket.");
        } else if (parseRuntimeEx != null) {
            throw new SerializationException("Error parsing JSON: " + new String(data), parseRuntimeEx);
        }
        return root;
    }

    %% write data;

    private final Array<JsonValue> elements = new Array(8);
    private final Array<JsonValue> lastChild = new Array(8);
    private JsonValue root, current;

    private void addChild (String name, JsonValue child) {
        child.setName(name);
        if (current == null) {
            current = child;
            root = child;
        } else if (current.isArray() || current.isObject()) {
            child.parent = current;
            if (current.size == 0)
                current.child = child;
            else {
                JsonValue last = lastChild.pop();
                last.next = child;
                child.prev = last;
            }
            lastChild.add(child);
            current.size++;
        } else
            root = current;
    }

    protected void startObject (String name) {
        JsonValue value = new JsonValue(ValueType.object);
        if (current != null) addChild(name, value);
        elements.add(value);
        current = value;
    }

    protected void startArray (String name) {
        JsonValue value = new JsonValue(ValueType.array);
        if (current != null) addChild(name, value);
        elements.add(value);
        current = value;
    }

    protected void pop () {
        root = elements.pop();
        if (current.size > 0) lastChild.pop();
        current = elements.size > 0 ? elements.peek() : null;
    }

    protected void string (String name, String value) {
        addChild(name, new JsonValue(value));
    }

    protected void number (String name, double value, String stringValue) {
        addChild(name, new JsonValue(value, stringValue));
    }

    protected void number (String name, long value, String stringValue) {
        addChild(name, new JsonValue(value, stringValue));
    }

    protected void bool (String name, boolean value) {
        addChild(name, new JsonValue(value));
    }

    private String unescape (String value) {
        int length = value.length();
        StringBuilder buffer = new StringBuilder(length + 16);
        for (int i = 0; i < length;) {
            char c = value.charAt(i++);
            if (c != '\\') {
                buffer.append(c);
                continue;
            }
            if (i == length) break;
            c = value.charAt(i++);
            if (c == 'u') {
                buffer.append(Character.toChars(Integer.parseInt(value.substring(i, i + 4), 16)));
                i += 4;
                continue;
            }
            switch (c) {
            case '"':
            case '\\':
            case '/':
                break;
            case 'b':
                c = '\b';
                break;
            case 'f':
                c = '\f';
                break;
            case 'n':
                c = '\n';
                break;
            case 'r':
                c = '\r';
                break;
            case 't':
                c = '\t';
                break;
            default:
                throw new SerializationException("Illegal escaped character: \\" + c);
            }
            buffer.append(c);
        }
        return buffer.toString();
    }
}
 

So you don't need this code.

Share this post


Link to post
Share on other sites

On 2/5/2024 at 4:45 AM, Łukasz Jakowski said:

示例地图:

CUT_FROM_PROVINCE_MODE=真

 

河流、湖泊等将从主地图上裁剪

 

crop.thumb.png.66ab29327fab56cc546370bda13a3673.png

how is it works, need I add a rivers map? 

Share this post


Link to post
Share on other sites

What's the difference between the "main" and "MapEditor" backgrounds? Should they necessarily be different? Which one should be used when creating provinces? Both of these backgrounds are displayed side by side in the editor, which is somewhat confusing.

Share this post


Link to post
Share on other sites

On 06.02.2024 at 16:48, Łukasz Jakowski said:

Sadece illeri çiziyorsunuz.

Oyun çıktığında bunun daha iyi kullanılacağını düşünüyorum

  Exactly, because there's not much point in making a map right now.

Share this post


Link to post
Share on other sites

In the main folder you have the map background that will be in the game, the main map background.

 

In the Map Editor folder you have an optional map background just for the map editor, for example if you have drawn how the provinces you want to divide.

 

 

Share this post


Link to post
Share on other sites

On 2/4/2024 at 3:50 PM, PeteFromPat said:

Wow! this looks awesome, cant wait to use the new and improved map editor!

It's the same damn thing but you have to waste even more time by having to do shit with the background files, At this point I'd rather make my own map editor

Share this post


Link to post
Share on other sites

Briefly, first line: "lPointsx" second line: "lPointsY".
Not very useful when it comes to drawing provinces (I have a better idea for the modern world).

This is accompanied by some useful resources for those considering how to implement the "Monkeys are stronger together" program.

MapEditor_2.rar

Edited by MeteHun

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.




  • Our picks

    • Age of History 3 - October 23rd, 2024 - Official release date
      Age of History 3 - Official release date

       

      Steam: October 23rd, 2024

      Android: When it's ready

      iOS: When it's ready

      Epic: When it's ready

       
      • 0 replies
    • Campaign: Small Scenarios
      In this topic, share your ideas for Campaign scenarios.

      These scenarios focus on a small part of the map, with the rest designated as wasteland.

       

      For example, a scenario of the Reconquista in 1054, where gameplay takes place only on the Iberian Peninsula.

      What are your ideas for small historical scenarios?

       


       
      • 105 replies
    • Events - Common events for every civilization in the game
      Hi,
      in this topic, I am interested in your ideas for events that can happen for every Civilization in the game.
      I'm also interested in Missions for every Civilization.

      Here is some example, have more than 10k army, have more than 5000 gold, build 10 buildings, recruit an Advisor, increase tax efficiency 20 times, be largest  producer of some resource in the world, unlock 5 Civilization legacies etc.
      • 196 replies
    • First preview of the Alpha version of Age of History 3
      First preview of the Alpha version of Age of History 3, YouTube.
      Release date: When it's ready 😛 Subscribe for more!



       





       
      • 204 replies
    • Land units - Ideas AoH3
      AoH3 will have different types of land units.

      In this topic we will write ideas for new land units. 

       

      So the AoH 3 will have new battle system.


      Representation of the battlefield in the game.


      Land units will be grouped into 3 types. Each unit will have a different recruitment cost, attack, defense, movement speed and upkeep.

      Groups determine the placement of units on the battlefield.


       

      Each unit can be unlocked by researching technology and then upgraded.

       

      Here is the current list of units with upgrades:

      First line:

      Warrior -> Light Footmen -> Heavy Infantry -> Infantry -> Line Infantry -> Modern Infantry

      Hoplites -> Spearmen -> Pikeman -> Elite Pikeman -> Musketeer -> Riflemen -> Mechanized Infantry -> Modern Mechanized Infantry

      First line side:

      Horseman -> Elite Horseman -> Cavalry -> Tank -> Modern Tank

      Second line:

      Archer -> Bowmen -> Crossbowman -> Elite Crossbowman

      Canon -> Field Cannon -> Artillery -> Modern Artillery

      Early Airplane -> Airplane -> Modern Airplane

       

      This is a very early version, so maybe something should be changed?

      Or maybe an idea for a new type of unit with upgrades? I'm waiting for your suggestions.

       
      • 227 replies
×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue. Age of History Games