Help:Building Cargo Templates

From Heroes of Might and Magic: Olden Era Official Wiki
Revision as of 13:42, 20 May 2026 by Ketura (talk | contribs) (Created page with "{{DataNavBox}} As laid out in the Cargo introduction, after data has been stored in Cargo, accessing it is a two-step process: we must ''query'' the data and ''display'' the data. In the simplest cases we can let Cargo's default table rendering handle the display, but as the complexity of the application increases the less we can rely on this. Ultimately this results in a multi-layered structure: a template to ease the use of #cargo_query, the #cargo_qu...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)


Cargo and Structured Data
Documentation Data Overview • Introduction to Cargo • Game Files • Obelisk Bot

Building Cargo Templates • Building Rich Tables • Debugging Cargo • Searching Cargo Tables • Using Translations

Useful Recipes • Useful Templates

Architecture Reference Data Architecture • Data:Coverage • Cargo Definition Pages • Game Data Pages

All Cargo Tables • Artifacts • Buildings • Heroes • Factions • Laws • MapObjects • Skills • Spells • Translations • Units

External Links Official Cargo Extension Documentation • SQL References



As laid out in the Cargo introduction, after data has been stored in Cargo, accessing it is a two-step process: we must query the data and display the data. In the simplest cases we can let Cargo's default table rendering handle the display, but as the complexity of the application increases the less we can rely on this. Ultimately this results in a multi-layered structure: a template to ease the use of #cargo_query, the #cargo_query itself, an inner formatting template used by Cargo, and then as many nested sub-templates as necessary.

To illustrate the development process, we are going to examine Template:HeroInfoBox and expose its internal structure as an example of how such complex displays can be made Cargo-aware.


Layer 0: The Mock-up

When developing Cargo-aware templates it is almost always beneficial to start with a "top-down" approach that begins with the end result. Before doing any work on queries or templates, create a table or infobox by hand to establish the look and feel. It is significantly easier to fill in a static infobox and rewire it to use Cargo than it is to begin with Cargo queries and make decisions about the display on-the-fly.

Doing so will also make patterns start to emerge early in the process; if you find yourself copy-pasting an element, that's a good hint that you need a template: Don't Repeat Yourself. In some cases those repeated sub-elements may themselves need to be controlled by additional Cargo queries, so pay attention and notice them ahead of time.

The HeroInfoBox began as a mockup on a userpage using hard-coded values, which was not only a significant aid, but it was also a great way for editors to contribute without fully understanding Cargo and its inner workings.

Layer 1: Scalar Query

The outermost layer in the template tree is Template:HeroInfoBox itself, which performs an initial #cargo_query to pull all the "scalar" data. What this means is: information about a hero which is not dynamic or variable. Every hero has a Name, for instance, and a Motto, and a Class, and so on. But more importantly it has only one Class, one Name (translations aside), and this doesn't change hero to hero. There are other pieces of data which may or may not be present: not every hero starts with any Spells, for instance, while others start with 1 or 2, and so we set those values aside for now and focus on just static data.

We invoke HeroInfoBox like so:

{{HeroInfoBox|id=human_hero_13|lang=en}}

This means we are only passing in 2 parameters: the internal ID of the hero we are looking up, and the language we want to retrieve text in. These will be used in the #cargo_query itself:

{{#cargo_query:
 tables  = Hero=H, HeroClass=HC, Faction=F,
           Translation=hero_t, Translation=hero_en, Translation=motto_t
|join on = H.class_id = HC.id,
           H.faction  = F.id,
           H.id       = hero_t.target_id,
           H.id       = hero_en.target_id,
           H.id       = motto_t.target_id
|where   = H.id = '{{{id|}}}'
           AND hero_t.type    = 'hero'       AND hero_t.language    = '{{{lang|en}}}'
           AND hero_en.type   = 'hero'       AND hero_en.language   = 'en'
           AND motto_t.type   = 'hero_motto' AND motto_t.language   = '{{{lang|en}}}'
|fields  = H.id                                      = id,
           hero_t.language                           = lang,
           hero_t.name                               = name,
           CONCAT(hero_en.name,'.png')               = portrait,
           motto_t.description                       = motto,
           hero_t.description                        = biography,
           H.class_id                                = class,
           H.faction                                 = faction,
           COALESCE(H.offence, HC.offence)           = startattack,
           COALESCE(H.defence, HC.defence)           = startdefense,
           COALESCE(H.spell_power, HC.spell_power)   = startspellpower,
           COALESCE(H.intelligence, HC.intelligence) = startknowledge
|format  = template
|template= HeroInfoBox/base
|named args = yes
}}

(It is highly recommended that you format your non-trivial #cargo_query calls something like the above to make them easier to scan; templates are already error-prone and it doesn't help the code to cram it all into too small a space to read.)

We will go over the other details in a moment, but first draw your attention to the fields section halfway down. These are the results, equivalent to a SELECT statement in SQL, and after you've done all your joining and filtering this is where you tell Cargo what exactly it is you want to see at the end of it.

Besides the internal ID and language (which are parameters on the template), we extract the name, portrait filename, motto, biography, class, faction, and starting attributes of the hero. These scalar values are passed into another template as dictated by the format field: Template:HeroInfoBox/base. We will dive into that template eventually, but first let's examine the different kinds of data we are extracting.

Raw Data

First there are raw data fields pulled straight from the table data, such as H.class_id and H.faction. If we look up at the tables section at the top, we will see that H is an alias for the Hero table. Refer to that table's documentation and you will find a few dozen fields, many of which are references to other tables. H.faction for instance is directly tied to the id of an entry in the Faction table. Such raw values will be used to look up more data, but they could in theory also be displayed on their own, if they represent a stat or other simple data.

Translated Data

Second there are translated fields, text data which has been translated into various languages. For example, the name of the hero is not pulled from H, but is instead pulled from hero_t (meaning "hero translation"). If we refer to tables again, we will see that this is an alias of the Translation table.

But when we do so we will note an oddity, that Translation is given not one but three aliases. The reason for this is that each "view" on the Translation table is going to be filtered down to a particular set of data which we don't want to mix up; we don't want the Motto section getting mixed up with Name, so we isolate multiple joins on Translation and force each one to focus on a single thing.

"Joins" are a way of associating two tables with one another. For example, we have a Hero table with a faction field, which is in reality a "foreign key" to the Faction table. This means that if we search the Faction table, we will find a Faction row with an id column matching the key in the Hero table. Joins can have multiple use cases as we'll soon discover, but the most common way is for them to act as a reference to other data so we don't end up unnecessarily duplicating the same information. A hero with the Temple faction does not want to copy-paste all the things that define the Temple faction into itself, so instead it just leaves a reference to the Faction it is associated with and leaves it up to us to look up what that means.

Once properly joined and filtered, we can reference the data in that second table knowing that we have looked up the correct thing. hero_t is joined to H via the key H.id = hero_t.target_id,, and then further in the where section it is established that this alias will only have "hero" entries that further are restricted to a single language. If we've done things correctly, this will reduce all of Translation to a single row, the one containing the name and biography of our Hero in our given language.

Transformed Data

Next we can see a transformed field, portrait, which is based on but is not exactly the same as the English hero name. On this wiki, if we want to find the portrait for the hero Nor, we will find it at File:Nor.png. Thus, we call CONCAT, short for "concatenate", meaning to glue two pieces of text together, in this case the English translation of the hero name and the string ".png".

There are all sorts of transformations that might be done this way. Most will be additive using CONCAT, but there will also be times where we can subtract from a field or retrieve just a portion of it.

Fallback Data

Last we can see fallback data in the form of the four hero attributes. As it turns out, not every hero defines its own set of starting attributes; in some cases the intent is that the default attributes for its Hero Class be used instead. For this we use COALESCE, which means "use the first value if you can, but if it's absent fall back on the second value". Thus, we use the Hero's attack stat if it's there, but if it's missing we look at its Hero Class and use that default attack stat instead.

And of course, you may have noticed that it's not called "attack" in the data, and that's because the Cargo tables for the most part use the same internal names as those in the game files. Inside the game that stat is called "offence", and so it will be referred to using that term whenever necessary.


Layer 2: Base Template

This article is a stub. You can help this wiki by expanding it.