Hero Modding Documentation

Modding Guide
8 Aug 2023, 10:05 a.m.

The heroes introduced in the Heroes & Villains DLC can be modded, changing them or adding new ones.

You can download a simple example mod here, for you to study and modify. You can also look at the already existing heroes in data/crossplay/heroes/HeroType. You can probably figure things out just from that.

The rest of this post attempts to document everything exhaustively

Don't feel that you have to have read all of it to start modding heroes! Reading "Basic Hero Structure" and "Recruit Hooks" and then whatever you think you need should be enough to start.

You can find detailed documentation on how modding works here, but here's the short version: A mod is a folder containing an info.json file, a logo.png file, and a bunch of folders containing JSON files with the same structure as the data folder in the game. You will also need a strings/en.properties file for the text in your mod, and an images folder. Here's the basic structure:

  • MyHeroMod/
    • info.json
    • logo.png
    • HeroType/
      • myHeroes.json
    • strings/
      • en.properties
    • images/
      • myHeroPortrait.jpg
      • scaled/
        • myHeroPortrait-200.jpg
        • myHeroPortrait-100.jpg
        • myHeroPortrait-50.jpg

logo.png should be a 512x512 PNG and info.json should look like this:

{
    "id": "MyHeroMod",
    "name": {
        "en": "My Hero Mod"
    },
    "description": {
        "en": "My Hero Mod is a mod that mods heroes."
    }
}

Note that for your mod, you shouldn't put the HeroType folder into a crossplay folder.

Basic Hero Structure

To get started, create a HeroType folder inside your mod folder, and add a JSON file containing the following.

[
    {
        "name": "myHero",
        "role": "CAPTAIN",
        "img": "myHeroPortrait.jpg",
        "maintenance": 10,
    },
]

A hero needs at least a name, a role ("CAPTAIN" or "GOVERNOR"), a portrait image, and a maintenance value. If you want to change an existing hero in the game, use the same name to overwrite them.

The name is used to look up their display name in en.properties. The hero also needs a description under [hero name]_desc, so:

myHero=Steve Stevenson
myHero_desc=Steve is just this guy, you know?

The portrait should be a 300x300px JPG or PNG stored in the images folder. Also add 200px, 100px, and 50px downscaled versions of it to images/scaled/, with the pixel size in the name like so: myHeroPortrait-200.jpg, myHeroPortrait-100.jpg, myHeroPortrait-50.jpg.

This will spawn a new hero in the game, but they won't do anything and can't be acquired.

Recruit Hooks

To get heroes to turn up for recruitment, you need a list of recruit hooks, which specify events that cause them to turn up. Here's an example of three hooks:

"recruitHooks": [
    { "type": "upgradeBuilt", "upgradeType": "fleetAcademy" },
    { "type": "combatVictory" },
    { "type": "techResearched", "tech": "CARTOGRAPHY" },
]

So the hero may turn up when you build a fleet academy, when you win a fight, or when you research cartography. See the full list at the end of this document, or look at the existing heroes.

Template Heroes

If you want to create a generic kind of hero, of which there can be multiple copies, use the following lines:

"isTemplate": true,
"templateSpawnPerEmpire": 0.1,
"templateFirstNameKey": "M",
"templateFirstNameNum": 26,
"templateLastNameKey": "B",
"templateLastNameNum": 26,

templateSpawnPerEmpire is multiplied by the number of starting empires and rounded up to arrive at the number of such heroes to create at the start of the game.

The first name of the hero is generated by picking a number between 0 and templateFirstNameNum - 1 and then looking up "HERO_M_[number]" in the strings, same with the last name. So M are male names (26 available), F are female names (26 available), NB are non-binary names (6 available), and B are last names (26 available). You can also make your own name lists.

Starter Heroes

At the start of the game, players are given a choice between a set of heroes to start the game with. You can turn a template hero into a starter hero by setting isStarter to true. The game will also create additional such heroes based on templateSpawnPerEmpire if it's more than 0.

Techs and Bonuses

Heroes can give the player techs when they are hired, for example:

"techs": [ "CARTOGRAPHY" ],

If a hero gives a tech, it's a good idea to also specify a hireCost value, which is an up-front payment when hiring the hero, so that players can't just get a tech for nothing.

And a hero can give a bonus to the empire they're working for, which lasts as long as they're around, using "bonus".

Finally, you can use "departureBonus" to specify a bonus that the hero gives the empire when they leave it, due to stat changes or being dismissed. Right now this is just used for druid (Vex) in captains.json, who curses your empire when they depart.

Passive Combat Abilities

On to making heroes actually do something! If you're creating a captain, you can give them passive combat abilities that apply to the ship or fleet they're in. The following are available:

Ship:

  • shipBonus: A Bonus applied to this ship alone.
  • fireRatePercent
  • accuracyPercent
  • crewSpeedPercent
  • flammabilityPercent
  • explosionRiskPercent
  • commandCooldownPercent
  • repairAmountPercent
  • firefightAmountPercent
  • propulsionPercent
  • liftPercent
  • armourRepairPercent
  • experiencePercent
  • lootMoneyPercentage: Percentage of total value of destroyed enemy ships earned after combat.
  • scavengeMoneyToSupply: Multiplied by total value of destroyed enemy ships to gain supply after combat. Note that 100 supply is a lot and 100 ship value is tiny, so this should be a number much smaller than 1.
  • surpriseAttack: If set to true, enemy ships start on command cooldown.

Fleet:

  • expeditionStrengthPercent: Expedition outcomes are based on total fleet strength. This is a percent bonus to fleet strength for this purpose alone.
  • fleetSpeedPercent
  • fleetFireRatePercent
  • fleetAccuracyPercent
  • fleetCrewSpeedPercent
  • fleetFlammabilityPercent
  • fleetExplosionRiskPercent
  • fleetCommandCooldownPercent
  • fleetRepairAmountPercent
  • fleetFirefightAmountPercent

If you have multiple captains in a fleet, only the strongest ability counts. They don't stack.

All percentage values should be integers.

Combat abilities

Captains can also have active combat abilities, which are specified like this:

"combatAbilities": [ "IMPROVISE_MUNITIONS", "SCAVENGE_MATERIALS" ],

Here's a list of all available abilities:

  • SMOKESCREEN
  • FLANK
  • IMPROVISE_MUNITIONS
  • SCAVENGE_MATERIALS
  • SCAVENGE_FUEL
  • ENGINEERING_MIRACLE
  • NECROMANTIC_INCANTATION
  • EXTINGUISH
  • BURST_OF_SPEED
  • SUPERCHARGE_SUSPENDIUM
  • FEAR
  • DOUBLE_TIME
  • TAUNT
  • BLINDING_GLIMMER
  • CROSSWINDS
  • CRIPPLING_SHOT
  • DISARMING_SHOT
  • PARALYSIS
  • TURNABOUT
  • GUST_OF_WIND
  • LAST_STAND
  • SINKHOLE
  • SUDDEN_STORM
  • MOMENT_OF_DOUBT
  • HYSTERICAL_BLINDNESS
  • EARTHQUAKE
  • CRASH_ZONE
  • EMERGENCY_ORDERS
  • HIGH_STORM
  • AERIAL_ACE: Also specify a CrewType with aerialAceCrewType so it knows what unit to spawn.
  • AIR_SUPPORT: Also specify a CrewType with airSupportCrewType and a number with airSupportNumCrew so it knows how many of what units to spawn.
  • PERSONAL_GUARD: Also specify a CrewType with guardCrewType and a number with numGuards so it knows how many of what units to spawn.

Passive City Abilities

If you're making a governor, they can have passive abilities that apply to the city they're assigned to:

  • unrest
  • spyDefence
  • productionPercent
  • defenceBudget
  • incomePercent
  • researchPercent
  • upgradeCostPercent
  • upgradeSpeedPercent
  • airshipSpeedPercent
  • landshipSpeedPercent
  • buildingSpeedPercent

You can also use "research" specify an amount of research generated independent of whether they're assigned anywhere.

Edicts

Governors can also enact edicts in cities they're assigned to. Edicts are temporary events, and unlike active captain abilities, new ones can be modded in. To add one or more edicts to a hero, add a line like this:

"edicts": [ "martial_law" ],

Then, create your edict by adding a JSON file to the Edicts folder in your mod, e.g:

[
    {
        "name": "martial_law",
        "duration": 145600,
        "icon": { "src": "heroes", "x": 16, "y": 32 },
        "iconBackground": { "src": "heroes", "x": 32, "y": 32 },
        "rep": -1,
        "unrest": -30,
        "comment": "h_martlaw",
        "stat": "experience",
        "statChange": 10,
        "sound": "double-time"
    },
]

Edicts need a name, a duration, an icon, and an iconBackground. The icon should be a 16x16px white on transparent icon with a 1px margin, and the iconBackground should be the icon plus a 1px border, fitting into those 16x16 by using the margin. (See the example mod for what that looks like.)

Edicts can make a sound. They can change the stats of the hero that enacts them (see about stats below). You can also have the hero make a comment on the edict.

Edicts can have two kinds of effects: immediate effects that happen when they're enacted, and ongoing effects that last until the end of the edict.

Immediate effects:

  • money
  • instantResearch
  • rep
  • pillaging

Ongoing effects:

  • unrest
  • spyDefence
  • production
  • defenceBudget
  • incomePercent
  • research: Misnamed, also a percentage.
  • upgradeCostPercent
  • upgradeSpeedPercent
  • airshipSpeedPercent
  • landshipSpeedPercent
  • buildingSpeedPercent

Moving and Clearing Monster Nests

Governors can have the ability to clear or move specific monster nests in the territory of their city. To specify clearable nests, add a list to "clearableNests" like so:

"clearableNests": [ "pirates", "brigands", "cultists" ],

You can use nestClearRep, nestClearMoney and nestClearResearch to add rep/money/research effects to clearing a nest. Use nestClearStat to specify the name of a stat you want to change when clearing a nest, and nestClearStatChange to specify by how much. Finally, use nestClearComment to have the hero make a comment when clearing the nest.

Moving nests (which means relocating it to an empty nest location outside your territory) has all the same fields, so moveableNests, nestMoveRep, nestMoveMoney, nestMoveResearch, nestMoveStat, nestMoveStatChange, and nestMoveComment.

Stats

Finally, both captains and governors can have stats, which are values between 0 and 100 that can be affected by the same kind of events as recruit hooks. You can make up any kind of stat, like "Sliminess" or "Desire for Cheese".

Here's an example stats block from Commander Bertelli:

"stats": [
    {
        "name": "experience",
        "startingValue": 0,
        "evolveOn100": "heroic_officer",
        "changers": [
            { "type": "combatVictory", "change": 5 },
            { "type": "combatDefeat", "change": 5 },
        ],
    },
    {
        "name": "pride",
        "startingValue": 30,
        "evolveOn100": "proud_officer",
        "changers": [
            { "type": "combatVictory", "change": 10 },
            { "type": "receiveTribute", "change": 30, "comment": "fresh_officer_superiority" },
            { "type": "receiveSubmission", "change": 40, "comment": "fresh_officer_superiority" },
            { "type": "everyMonth", "change": -1 },
            { "type": "combatDefeat", "change": -10 },
        ],
    },
],

Each stat needs a name and a starting value, and one or several changers, which specify when the stat should change. See the list of hooks below to see what changers are possible. Heroes can also make a comment when the changer is triggered.

You can also base the amount by which a stat changes on the size of the map, by adding a changeDivByCities and optionally a changeMax value. Here's an example:

{ "type": "cityGained", "change": 1, "changeDivByCities": 100, "changeMax": 10 },

This means that if you gain a town or city, the stat changes by min(changeMax, change + changeDivByCities / numberOfCitiesOnMap). So 1 plus 100 divided by the map size, but no more than 10. This is especially useful for stats with powerful effects, where you want them to happen more slowly on large maps.

When a stat reaches 0 or 100, it can affect the hero by making them leave, killing them, changing them into another hero, enabling coronation victory without having the required rep, or winning the game altogether.

Here's the values to set for these effects:

  • leaveOn0: true/false
  • leaveOn100: true/false
  • dieOn0: true/false
  • dieOn100: true/false
  • evolveOn0: Name of hero to change into
  • evolveOn100: Name of hero to change into
  • winOn100: true/false
  • coronationOn100: true/false

If you want your hero to evolve into another, set up another hero entry with spawnAtStart set to false, so that their evolved form doesn't turn up beforehand.

Note that Loyalty is a bit of a special case with stats, as a hero with a loyalty stat below 25 is corruptable by spy actions. The stat that lets you have a coronation is usually Fame, and the one that lets you win outright is usually Power, but that's not hard-coded.

Testing

Once you've put together your hero, you can use the cheats (enabled in the game settings) to acquire them for testing.

Additional Features

Using Captains in Single Combats

Captains can also be used in single combats outside of the campaign by setting the singleCombatCost value.

Nemesis Empire

By setting hasNemesisEmpire to true, a hero can have a nemesis empire, which is a random empire that they hate. The game picks a random empire at the start of the game when creating the hero. Typically, the hero's loyalty changes based on how you interact with their nemesis, but the details of that are up to you.

Because empires can be destroyed, you should also create a version of the hero without the nemesis stat effects and specify it in turnIntoIfNemesisIsGone.

See beautiful_and_determined (Captain Bui) in captains.json for an example.

Of course, a hero will never turn up for recruitment for their nemesis.

Hometown

Conversely, by setting hasHomeCity to true, a hero can have a random hometown. You can then use hooks specific to that hometown to change their stats. See science_admin (Sa'd Khayyam) in governors.json for an example.

Since towns and cities cannot be destroyed, you don't need a version without a hometown.

Hire Comments

Heroes can comment on other heroes being hired without having a stat change, for additional storytelling purposes. You don't need to add anything to the HeroType for this. Simply add a line called [hero1]_hire_[hero2] to en.properties.

For example:

painted_sorceress_hire_secret_heretic=Oh, Kamina is so eager to please! We all know why, of course.

This has painted_sorceress (Izegbe) comment on you hiring secret_heretic (Kamina Ver).

Combat Comments

Heroes can also make comments upon things happening in combat. This uses the PortraitMessageType system already used for generic comments on combat.

To create a comment when a hero uses an ability, add a PortraitMessageType that looks like this:

{
    "name": "health_and_safety_DOUBLE_TIME",
    "eventInfoPrefix": "cast DOUBLE_TIME health_and_safety",
    "messageImages": ["scaled/X2-Health-and-safety3-200.jpg"],
},

The eventInfoPrefix is what the system uses to match to combat events. Here, it says that DOUBLE_TIME has been cast by health_and_safety. The message has as many variations as there are message images - so just one in that case, which is:

health_and_safety_DOUBLE_TIME0=Double time, men! Like we trained!

To create a comment when a hero observes an enemy hero's ability use, add one like this:

{
    "name": "HYSTERICAL_BLINDNESS_druid",
    "heroType": "druid",
    "sort": -1,
    "eventInfoPrefix": "received HYSTERICAL_BLINDNESS",
    "messageImages": ["scaled/X-34-Druid-200.jpg"]
},

Interesting?

Heroes can have "interesting" set to true. If a player has spent two in-game years without having a hero marked as "interesting" turn up for hire, the game tries really hard to get one to turn up as soon as possible.

List of Hooks

Finally finally, here's the list of event hooks and their parameter that you can use for recruitHooks and stat changers. Note that you can find plenty of examples of these being used in captains.json and governors.json.

  • anyNestDestroyed: You've destroyed a nest of any type.
  • bioNestDestroyed: You've destroyed a nest marked as biological.
  • nonBioNestDestroyed: You've destroyed a nest not marked as biological.
  • nestDestroyed(nestType: MonsterNestType): You've destroyed a nest of this specific type.
  • multiNestDestroyed(nestTypes: list of MonsterNestType): You've destroyed a nest from this list of types. The list will be referred to by the first nest on the list.
  • anyNestAppeared: A nest of any type has appeared in your territory.
  • bioNestAppeared: A nest marked as biological has appeared in your territory.
  • nonBioNestAppeared: A nest not marked as biological has appeared in your territory.
  • nestAppeared(nestType: MonsterNestType): A nest of this specific type has appeared in your territory.
  • multiNestAppeared(nestTypes: list of MonsterNestType): A nest from this list of types has appeared in your territory. The list will be referred to by the first nest on the list.
  • anyUpgradeBuilt: You've built a town or city upgrade.
  • upgradeBuilt(upgradeType: CityUpgradeType): You've built a town or city upgrade of this type.
  • takeover(takeoverType: TakeoverMethod): You've started taking over a town or city using this method.
  • anySpyAction: You've done any spy action.
  • spyAction(spyActionType: see list of CitySpyActions below): You've done a spy action of this type.
  • anySpyActionAgainstNemesis: You've done any spy action against this hero's nemesis.
  • spyActionAgainstNemesis(spyActionType: see list of CitySpyActions below): You've done a spy action of this type against this hero's nemesis.
  • everyMonth: Exactly once every month.
  • randomly: Triggers about every 6 months.
  • rarely: Triggers about every 19 months.
  • relationshipLevelUpgrade(newLevel: see list of RelationshipLevels below): Your relationship level with another empire has increased to this level.
  • relationshipLevelUpgradeWithBonusEmpire(newLevel: see list of RelationshipLevels below, bonus: Bonus): Your relationship level with another empire that has this bonus has increased to this level. Used e.g to have Father Tesseract complain when you're nice to cultist empires.
  • nemesisRelationshipLevelUpgrade(newLevel: see list of RelationshipLevels below): Your relationship level with this hero's nemesis has increased to this level.
  • relationshipLevelDowngrade(newLevel: see list of RelationshipLevels below): Your relationship level with another empire has decreased to this level.
  • relationshipLevelDowngradeWithBonusEmpire(newLevel: see list of RelationshipLevels below, bonus: Bonus): Your relationship level with another empire that has this bonus has decreased to this level.
  • nemesisRelationshipLevelDowngrade(newLevel: see list of RelationshipLevels below): Your relationship level with this hero's nemesis has decreased to this level.
  • tradeTreaty: You've made a trade treaty with another empire.
  • tradeTreatyEnded: You've broken or dissolved a trade treaty with another empire.
  • researchTreaty: You've made a research treaty with another empire.
  • researchTreatyEnded: You've broken or dissolved a research treaty with another empire.
  • sendTribute: You've started sending tribute to another empire.
  • sendTributeToNemesis: You've started sending tribute to this hero's nemesis.
  • sendTributeEnded: You've stopped sending tribute to another empire.
  • receiveTribute:You've started receiving tribute from another empire.
  • receiveTributeFromNemesis: You've started receiving tribute from this hero's nemesis.
  • receiveTributeEnded: You've stopped receiving tribute from another empire.
  • demonstrateSubmission: You've demonstrated submission to another empire.
  • demonstrateSubmissionToNemesis: You've demonstrated submission to this hero's nemesis.
  • receiveSubmission: You've received submission from another empire.
  • receiveSubmissionFromNemesis: You've received submission from this hero's nemesis.
  • cityGained: You've gained control of a town or city.
  • homeCityGained: You've gained control of this hero's hometown.
  • cityLost: You've lost control of a town or city.
  • homeCityLost: You've lost control of this hero's hometown.
  • combatVictory: You've won a battle.
  • combatVictoryAgainstNemesis: You've won a battle against this hero's nemesis.
  • combatDefeat: You've lost a battle.
  • combatDefeatAgainstNemesis: You've lost a battle against this hero's nemesis.
  • nemesisDestroyed: This hero's nemesis empire has stopped existing. (By your actions or otherwise.)
  • techResearched: You've researched any technology.
  • techResearched(tech: Tech): You've researched this specific technology.
  • heroHired(hero: HeroType): You've hired this hero.
  • heroLeft(hero: HeroType): This hero has left your employ, by being fired, or leaving, or dying.
  • repLevelUpgrade: Your reputation level has increased.
  • repLevelUpgrade(level: see list of RepLevels below): Your reputation level has increased to this level.
  • repLevelDowngrade: Your reputation level has decreased.
  • repLevelDowngrade(level: see list of RepLevels below): Your reputation level has decreased to this level.
  • incident(tag: text, see list of Incident Tags below): You had a diplomatic incident outcome with this tag.

CitySpyActions:

  • SABOTAGE_RISE_TO_POWER
  • BRIBE_GOVERNOR
  • CONVERT_GOVERNOR
  • INTRIGUE
  • UNEARTH_SCANDALS
  • ORGANISE_STRIKES
  • SABOTAGE_PRODUCTION
  • STEAL_RESEARCH
  • STEAL_SUPPLIES
  • STEAL_FUNDS
  • BUILD_NETWORK
  • FOMENT_UNREST
  • SABOTAGE_CORONATION
  • SABOTAGE_FINAL_RITUAL
  • INCITE_RIOT
  • INCITE_REVOLT

RelationshipLevels:

  • WAR
  • TRUCE
  • PEACE
  • NON_AGGRESSION_PACT
  • DEFENSIVE_PACT
  • ALLIANCE

RepLevels:

  • LOVED (80-100)
  • LIKED (60-79)
  • TOLERATED (40-59)
  • DISLIKED (20-39)
  • HATED (0-19)

Incident Tags currently being used in incidents:

  • coop: You cooperate with another empire.
  • betrayed: You were betrayed by another empire.
  • betrayal: You betrayed another empire.
  • kindness: You showed kindness to another empire.
  • kindnessReceived: You received kindness from another empire.