Help:Cargo: Difference between revisions

From Heroes of Might and Magic: Olden Era Official Wiki
Adding a comprehensive overview of Cargo and how to use it.
 
No edit summary
Line 1: Line 1:
{{DataNavBox}}
'''Cargo''' is an extension that lets us configure database tables and store data within them.  [https://www.mediawiki.org/wiki/Extension:Cargo See the extension page here for documentation].  Here on the HOMMOE Wiki, there are hundreds of Data pages which define and populate Cargo tables relating to all sorts of HOMMOE game concepts, including Units, Heroes, Spells, Factions, and more.   
'''Cargo''' is an extension that lets us configure database tables and store data within them.  [https://www.mediawiki.org/wiki/Extension:Cargo See the extension page here for documentation].  Here on the HOMMOE Wiki, there are hundreds of Data pages which define and populate Cargo tables relating to all sorts of HOMMOE game concepts, including Units, Heroes, Spells, Factions, and more.   



Revision as of 01:22, 20 May 2026


Cargo and Structured Data
Documentation Data OverviewIntroduction to CargoGame FilesObelisk Bot

Building Cargo TemplatesBuilding Rich TablesDebugging CargoSearching Cargo TablesUsing Translations

Useful RecipesUseful Templates

Architecture Reference Data ArchitectureData:CoverageCargo Definition PagesGame Data Pages

All Cargo TablesArtifactsBuildingsHeroesFactionsLawsMapObjectsSkillsSpellsTranslationsUnits

External Links Official Cargo Extension DocumentationSQL References




Cargo is an extension that lets us configure database tables and store data within them. See the extension page here for documentation. Here on the HOMMOE Wiki, there are hundreds of Data pages which define and populate Cargo tables relating to all sorts of HOMMOE game concepts, including Units, Heroes, Spells, Factions, and more.

Each of the pages on the wiki are engineered to reference the Data pages for their procedural game content. This has two main purposes:

  • Alternate-language versions of the wiki do not need to redo the busy work of managing stats and traits of shared game objects.
  • When a patch is released, the internal Data can be altered and all pages which reference that data will automatically update.

The per-patch updating is handled by use of the Obelisk Bot; when a new patch is released, wiki maintainers will run the bot and ingest the new data into the wiki, after which altered concepts can be patrolled and updated by hand to fix obsolete articles.

But all of this is only possible due to the use of Cargo.

Cargo Tables

A database is a fancy word for a spreadsheet: a "table" is when someone decides that certain data should have "columns" and begins to add "rows" of data which fit those columns. There can be many tables, all with different kinds of data and with all sorts of relationships from table to table. One of the more famous database engines is SQL, which is at the heart of the software this wiki runs on, and is also what Cargo piggybacks on to store additional data.

There are four things that must happen for a Cargo table to be useful on this wiki:

  • We must define a table using #cargo_declare, in which we will specify the purpose of that table by deciding what columns exist in that table and what shape each one is.
  • We must populate a table using #cargo_store, in which we will fill the table with relevant data.
  • We must retrieve data from a table using #cargo_query, in which we extract useful information.
  • We must transform that data, usually using templates, so that it can be easily displayed for the user.

The specifics of how each of those Cargo commands are performed can be complex, but once established it is a powerful tool that can produce all sorts of interesting displays and visual aides without needing to maintain them by hand as the game changes. Refer to the extensive official documentation if you want to learn more than the basics.


Step 1: Declare

When a Cargo table is defined, each column will be given a name which it is referenced by and a type which will restrict it to only containing certain kinds of data: if we define a column as an Integer, then if later we attempt to put something other than a number into it, the operation will fail.

Choosing what columns to include (and what to omit) is an art more than a science, but the system is flexible: if we later decide that columns need to be added or altered, we can do so without fear, as all of the data definitions on the wiki are stored permanently in articles which will automatically re-fill the database once it is recreated. (This process can be slow, but it is reliable.)

Let us look at the Faction table for reference:

{{#cargo_declare:_table=Faction
| id = String (allowed values=human,undead,dungeon,nature,demon,unfrozen)
| name = String
| desc = Wikitext

| icon = String
| icon_faction_laws = String

| biome = String
| resource = String

| name_sid = String
| desc_sid = String

| source_path = String
}}

Here there are several details to pay attention to; first, the very first thing we do in this Cargo command is decide the name of the table we are referencing, in this case Faction. Unlike most MediaWiki concepts, these names cannot include spaces; indeed spaces should be avoided for most if not all Cargo operations.

This is a relatively simple table, with a handful of open-ended columns defining various traits relating to a faction: for the purposes of the end user, nearly all that's relevant is what it's called, a brief official description, what biome it calls home, and what primary resource it has. The other columns are for internal purposes: names of the icon used to represent it, and the Law icon used, the original source file from inside the game which defined it, and the SID of its name and description.

SIDs are internal names used to label a particular concept which will be translated into multiple languages, it possibly means "String IDentifier". SIDs will show up in many of our tables, because paired with most tables will either be a row in the Translation table or a dedicated XTranslation table. In either case, the SID of a particular concept (such as a Faction) will have entries in those other rows with the same SID, which is what other language wikis can use to easily retrieve the localized term for a concept without needing to rewrite the entire thing themselves.

In all of our tables, we include the English in-game name and description of the widget by default so that there is a fallback available.

You may notice that the ID uses terms that are not quite the same as those in the published game, and that's because the internal working names are usually not worth potentially breaking the game by updating them when the names change on the surface; as they say, there's nothing more permanent than a temporary term. Thus the faction is internally called "unfrozen" instead of Schism, "human" instead of Temple, and so on. The data we use in these Cargo tables will largely use those internal terms, because this makes changes between patches much easier to detect if we are not changing terms at every step of the process.

After we have determined what columns go in the table, we include the #cargo_declare definition in a convenient article and it will, upon being processed, produce a table which we can interact with.

Step 2: Store

Once we have ourselves a working table, it's time to populate it with data. Each cluster of data we enter is called a "row", with each field lining up with a column in that table. Not all columns are required, but we must still ensure our data matches what the table expects.

Table insertions are performed using the #cargo_store command:

{{#cargo_store:
_table = Faction
| id = human
| name = Temple
| desc = The Church of the Sun strives to forge the best version of oneself. Its well‑rounded troops gain extra benefits from buffs.
| icon = fraction_human
| icon_faction_laws = Scroll_Faction_Human
| biome = Grass
| resource = gemstones
| name_sid = human_name
| desc_sid = human_desc
| source_path = DB/fractions/1_human.json
}}

Here is a #cargo_store which corresponds to the Faction table we defined in the previous section. Here we provide all the different columns which the table expects, all of which was pulled automatically from the game files using Obelisk Bot.

But uh-oh, we can see a small typo! The "icon" field is called "fraction_human"! Surely that must be a mistake. But while it might seem like a quick and easy win to fix this typo, we must be clear:

DO NOT FIX TYPOS IN DATA DECLARATIONS

This is not just a typo on the wiki, it is a typo in the game files. And it is a very prolific typo! Lots and lots of systems and subsystems are expecting that typo to be in place, and if you fix it here, you will cause unintended problems down the line. For example, this field tells us to elsewhere look for an icon entitled "fraction_human". If we fix the typo here, then later when we come in to query it, we might try to find "faction_human.png" and fail to find the file as a result.

You may think that, okay, you'll just fix the typo and fix the filename, and while that would work for a while, the next time a patch drops and we re-import the data using Obelisk bot, all your hard work will blow up.

So don't do it.

When Obelisk runs, it uploads hundreds upon hundreds of Data articles, all of which eventually terminate in a #cargo_store call (or several!) to populate our internal tables. For the most part we do not need to interfere with this process (and indeed, it is better to fix bugs in Obelisk than to meddle with the Data articles manually!).


Step 3: Querying

Now we get to the real power of Cargo. After data has been defined and stored, we finally get to the reason why we have gone through all the trouble: now we query the data, retrieving it back out.

Querying is done using #cargo_query:

{{#cargo_query:
tables=table1=tableAlias1, table2=tablesAlias2, etc.
|join on=table1.fieldA = table2.fieldB,table2.fieldC=table3.fieldD, etc.
|fields=field1=fieldAlias1,field2=Alias2, etc.
|where=table1.fieldE='some value' AND/OR etc.
|group by=table1.fieldG
|having=table1.fieldG='some value', etc.
|order by=table2.fieldF, etc.
|limit=some number
|offset=some number
|intro=some text
|outro=some text
|default=some text
|more results text=some text
|no html
|max display chars=some number
|format=format
...additional format-based parameters
}}

If this looks like a lot of options, you're right! This is encoding many different SQL operations into a template we can use on the wiki. For a simple example, if we wanted to get a list of all Factions which exist, we would normally in SQL do so like this:

SELECT name
FROM Faction

Which is to say, out of the Faction table, retrieve everything in the "name" column. That's good for a lot of things, but where it really shines is when we restrict what data is being returned:

SELECT description
FROM Faction
WHERE id = 'human'

Now we instead retrieve only the Description of every Faction which matches the conditions in the WHERE clause, in this case only getting factions whose id is 'human'; no Hive or Schism or anything else.

We can write Cargo queries which do the same thing as these two SQL queries:

{{#cargo_query:
tables=Faction=F
|fields=name
|format=list
}}

Here we define the table (Faction) and which field we want (name), and we tell Cargo we want those names in the form of a bullet-point list. Now let's do the other one:

{{#cargo_query:
tables=Faction=F
|fields=desc
|where=F.id='human'
|format=list
}}

In this case, WHERE translates simply to the 'where' field, and it's otherwise straightforward. We gave the Faction table a convenient alias of "F" so that we could use it later as "F.id" without typing out the entire table name again. It didn't save us much space here, but if you're pulling 20 columns it can get old fast.

cargo_query includes a number of means of helping us display the information; if you look at the example from before you'll find fields referencing "intro" and "outro", which are often used to set up and end tables. You can also in the "format" field indicate that the results should be passed in to another Template for formatting. The extension documentation can help you understand what tools are available. This can get complicated, but fortunately we can solve the problem of "juggling lots of templates" with, you guessed it, more templates!


Step 4: Transform

As it turns out, it can be very precise and time-consuming to write a #cargo_query statement in a way that is robust. So like all good wiki maintainers will do, we will take that difficult task and sand it down, producing a Template which does much of the work for us. We might, for instance, write a Faction template which can be called as follows:

{{Faction|lang=EN|id=human}}

This template would then take its two parameters (the language and the ID to look up) and know how to manipulate them and plug them in to a more complicated #cargo_query, which might do much more than just language lookup--it might look up the Icon to use automatically, for instance, so that it produces output like "Schism" for us automatically.

There is no bottom to where this might lead. With time, we'll have templates calling templates calling templates, and while that sounds scary, it really just means that we are leveraging the software to do oh so much work for us.

A common pattern which you will find in many table-esque Cargo templates is as follows:

  • An article decides it wants to show a list of all the units that start with "D"
  • That article invokes a top-level conceptually simple Template, such as UnitTable
  • UnitTable defines its own #cargo_query, which filters out units who don't start with "D", but the output is piped into a UnitTableRow template
  • UnitTableRow knows how to take all of the data produced by #cargo_query's "fields" and turns them into a well-formatted single row of a table
  • UnitTable sandwiches all of the UnitTableRow entries between the "intro" and "outro" of the #cargo_query
  • The original article renders a nice, well-formatted list that shows only all the units that start with "D"

But of course the presentation of data pulled from Cargo is not restricted to a table. Article infoboxes also query for their data, pulling in dozens of fields and arranging them just so. The sky's the limit.