Introduction to Modifications

Extensibility lies at the heart of the HeroWO engine and is one of its strongest points. JavaScript was specifically chosen as an alternative to C++/Java used in other projects (VCMI, fheroes2). The framework on which HeroWO is based (Sqimitive.js) allows to modify literally everything – for example, “methods” in the traditional sense are actually events that can be subscribed to or overridden, and there are no private class members either.

However, many modifications are done with no programming at all. Below are some examples.

The effect system

The engine defines over 150 type of values influencing the game mechanics, such as these (with example usage in SoD in brackets):

  • canCombat – ability to start a combat in a certain location (Sanctuary)
  • creature_attackAround – number of cells around an attacking creature in combat that take damage (Hydra)
  • creature_spellEvade – chance to avoid a targeted spell (Dwarf)
  • hero_actionCost – cost of hero movement over the adventure map (different types of terrain and roads)
  • hero_experienceGain – multiplier for XP gained by a hero (the Learning skill)
  • hero_walkTerrain – types of terrain that a hero may pass over (horse, ship)
  • hireFree – creatures that join the hero who visits a dwelling for free (first-level dwellings)
  • randomRumors – list of rumors for a tavern
  • town_spellCount – number of spells in a town (Mage Guild, Library)
  • combatCasts – number of times a hero is permitted to cast spells during a combat round
  • creature_canControl – ability to issue orders for a certain creature in combat (war machines, towers)
  • bonus_artifacts – list of artifacts obtained as a result of an encounter (timed events, movement on map)
  • ownable_shroud – number of cells revealed around mines and other owned objects except towns and heroes
  • quest_garrison – creatures opposing the hero when he visits an adventure map object (banks and dwellings)
  • worldBonusChances – chances for various world-wide bonuses (Week/Month of, Plague)

Effect is a set of selectors and modifiers. Selectors (including the value type from above) specify conditions for applying the modifiers. There are currently about 70 selectors, such as these:

  • ifPlayer – number of the player affected by the effect (Armageddon’s Blade)
  • isEnemy – alters the meaning of ifPlayer: number of the player, enemies of whom must be affected
  • ifZ – coordinate of the event taking place on adventure map (1 if underground)
  • ifDateMonth – in-game date’s month number (timed event)
  • maxCombats – removes the effect when a hero has participated in this many combats (Foutain of Fortune)
  • whileOwnedPlayer – removes the effect if a certain player ceases to be the owner of an object (income from mines)
  • ifGarrisoned – applies only if a hero is garrisoned in a city (prevents him from fleeing)
  • ifSpellLevel – checks level of a spell being cast (dragons’ immunities)
  • ifRoad – checks type of road on which the event is happening (movement cost)
  • ifCreatureUndead – checks if a combat creature is Undead or not (Holy Word)
  • ifGrantedMin – number of times a certain adventure map object has been visited by heroes (Warrior’s Tomb)

There’s currently no easy documentation on effects so see databank/core.php (after the class Effect { line and also the preceding comment) and databank/databank.php (after the class H3Effect line; describes selectors and existing target).

To better see how this works, visit herowo.io – the place for testing modifications. There’s an area with the Copy effects button under the game screen. It shows effects that were recently created in response to activities in the game.

For example, paste this line into the input field and click Create:

{"priority": relative, "target": hero_actionCost, "ifWorldBonus": plague, "modifier": [relative, 10.0]}
  • ifWorldBonus: plague applies the effect only if the current week special is Plague.
  • modifier: [relative, 10.0] reads as such: whenever the selectors match (target and ifWorldBonus), modify the value using the multiplication operator by 10.0 (i.e. by 1000%).
  • priority sets the order of applying the effect’s modifiers to the final value. It’s usually the same as modifier.

As a result, when such an effect is present in the world, all heroes’ movement will be slowed down 10 times during Plague.

We can add other selectors, like to exclude AI players from the effect’s scope:

{"priority": relative, "target": hero_actionCost, "ifWorldBonus": plague, "modifier": [relative, 10.0], "ifPlayerController": "human"}

Or we can increase the likelihood and harmfulness of Plague:

{"target": worldBonusChances, "modifier": [override, {"3,PLAGUE,0.1": 100}], "priority": override}

…although HeroWO applies world bonuses daily rather than weekly so if we don’t want Plague being effective 6 days out of 7, we need to add another selector (day 1 = Monday, week 1 = first week of a month):

{"target": worldBonusChances, "modifier": [override, {"3,PLAGUE,0.5": 100}], "priority": override, "ifDateDay": 1, "ifDateWeek": 1}


Here’s a more interesting example, let’s call it the Hand of Midas:

{"target": quest_choices, "modifier": [diff, "exp500", "exp1000", "exp1500"], "ifBonusObjectClass": 944, "priority": diff}
  • The value for target: quest_choices is requested when encountering most adventure map objects. It’s a list of labels from which the player can choose a desired action.
  • modifier with the diff operator excludes the listed items from the final value.
  • ifBonusObjectClass – map object’s type (similar to class+subclass in OBJECTS.TXT). 944 = Treasure Chest. The number is obtained from the classes.txt file in the databank folder (databanks/sod/classes.txt inside Workbench).

Treasure Chest has these effects by default:

array_fill_keys($c_treasureChest, [
  ['quest_chances', $chances('ge500/32 ge1000/32 ge1500/31 chArtT/5')],
]),

'ge500'   => [['quest_choices', [$append, 'gold1000', 'exp500']]],
'ge1000'  => [['quest_choices', [$append, 'gold1500', 'exp1000']]],
'ge1500'  => [['quest_choices', [$append, 'gold2000', 'exp1500']]],

This means that when an object of class treasureChest is encountered, the engine must roll a die with these chances: per 32% for both ge500 and ge1000 outcomes, 31% for ge1500 and 5% for chArtT. The first three allow the player choose effects from labels gold1000/1500/2000 and exp500/1000/1500. The definitions of these labels as well as of chArtT are omitted here for brevity (follow the link above for that).

In simpler terms, our effect applies after the standard quest_choices so the final value will never contain exp500/1000/1500. The player will always receive either gold (without an ability to choose experience) or artifact.

Alternatively, we could override quest_chances instead of quest_choices to receive artifacts from all chests (or something completely different, like creatures – as it’s done for Pandora’s Box).

The databank

HeroWO’s databank stores data independent of a particular map’s data. The data normally comes from SoD’s TXT files (possibly modified) but a map may alter them at will (in contrast to SoD where only certain things may be altered – like hero’s stats but not building’s cost).

For example, we can add a new building called City Lights in Castle. When built, it would reveal 1 extra tile around mines and other owned objects. Let’s start with the basic effect producing the desired, uh, effect:

{"target": ownable_shroud, "modifier": 1}

1 is a short form of writing modifier: [delta, +1], i.e. arithmetic addition.

Such an effect would work for all players since it only has one selector (target) but we’ll fix this later.

Let’s add the building to the databank. Start by creating the databank folder in the map’s folder (the one with some JSON files including map.json), and the buildings.txt inside it with this content:

{
  "0": {
    "0": {
      "-1": {
        "name": "City Lights",
        "description": "Increases scouting radius around mines and dwellings that you own.",
        "cost_wood": 5,
        "cost_mercury": 0,
        "cost_ore": 5,
        "cost_sulfur": 0,
        "cost_crystal": 0,
        "cost_gems": 0,
        "cost_gold": 1000,
        "require": [buildingsID.cityHall],
        "town": [townsID.castle],
        "image": {
          "0": {
            "0": {
              $"{townsID.castle}": {
                "hallImage": ["HALLTOWR", 0, 26],
                "scapeImage": "TBTWHOLY",
                "scapeOutline": "TOTHOLYA",
                "icon": "BOTGRAIL"
              }
            }
          }
        },
        "effects": {
          "0": {
            "0": {
              "0": {
                "target": effect.target.ownable_shroud,
                "priority": effect.priority.of.delta,
                "modifier": 1,
                "ifPlayer": true
              }
            }
          }
        }
      }
    }
  }
}
  • “-1” – internal number of the object being added; starts with -1 and grows down. If 0 or above, replaces a standard object rather than adding a new one.
  • require sets mandatory buildings to exist before player is allowed to erect this one. In our case City Lights can be erected only when the town has last but one magistrate upgrade.
  • town limits the building to certain town types.
  • image – a complex field defining images for the town screen, Hall and other windows. As we don’t have a fitting picture, we use the one for Skyship in Tower.
  • effects – effects applied to the player owning the building. This is the most important field; it’s alike to what we’ve seen earlier except for the unusual value for ifPlayer: true.


The engine replaces true with the number of the player who has erected the building. This trick works with many selectors (for example, ifObject = certain adventure map object, ifX = certain map coordinate) so that our Hand of Midas could become part of an artifact’s effects by setting ifObject to true – “for the hero owning the artifact”.

This effect reveals 1 extra cell per each town with City Lights. If it’s undesirable, the special stack field can be used to restrict applying multiple occurrences of the same effect to a given value (among other things, it’s used in garrison’s morale bonuses).

Programming modules

Effects and databank patches are powerful enough to produce some very impressive results. The bulk of all adventure map objects’ mechanics (chest, resource, artifact, monster, dwelling, border guard, well, mill, fountain, etc. etc.) is implemented with effects rather than code. This is way simpler – the engine decides when to apply the effect and takes care of proper clean-up once it’s disabled.

However, HeroWO also allows loading custom modules (plugins) written in JavaScript. Apart from the effect panel, herowo.io has one for controlling external scripts. Let’s click the Add script button and paste the following URL:

https://herowo.game/pub/mod-ex-1.js

The script sitting on this URL is as follows:

define([], function () {
  var coords = ''

  return {
    start: function (cx) {
      $('<button id=mybtn>')
        .text('Go')
        .insertBefore('#controls .map-size')
        .click(function () {
          coords = prompt('Enter X-Y coordinates to center on:', coords) || ''
          var pos = coords.match(/(\d+)\D+(\d+)/)
          if (pos) {
            cx.screens().forEach(function (sc) {
              sc.set('mapPosition', [pos[1], pos[2]])
            })
          }
        })

      $('<style id=mystyle>')
        .text(
          `
            #mybtn {
              display: block;
              background: red;
            }
          `
        )
        .appendTo('body')
    },

    stop: function () {
      $('#mybtn,#mystyle').remove()
    },
  }
})
  • define – part of the Require.js API. First parameter is an array of module’s dependencies, second is module’s code. Our module has no dependencies (to be precise, we do depend on the global variable $ – jQuery; this is not pretty but will do for simplicity’s sake).
  • The module function must return an object with two methods: start and stop. Both accept the cx object – instance of Context, the entry point to a running engine.
  • In start, we add a new Go button to the top debug bar. When clicked, we ask user for coordinates and center the adventure map.
  • We also add CSS styles to the document to make our button stand out.

Here’s a more complex example of hooking into the engine as a ScreenModule, drawing dashed lines around dwellings with creatures available for hire:

https://herowo.game/pub/mod-ex-2.js

define(['DOM.Common'], function (Common) {
  var Mod = Common.Sqimitive.extend({
    mixIns: [Common.ScreenModule],
    _map: null,

    events: {
      render: function () {
        this._map = this.sc.modules.nested('HeroWO.DOM.Map')
        var dwelling = this.map.constants.object.type.dwelling

        this.autoOff(this.map.objects, [
          'ochange_p_' + this.map.objects.propertyIndex('available'),
          function (n) {
            var id = this.map.objects.fromContiguous(n).x

            if (this.map.objects.atCoords(id, 0, 0, 'type', 0) == dwelling) {
              this._updateObject(id)
            }
          },
        ])

        this.map.byType.findAtCoords(
          dwelling, 0, 0,
          0,
          this._updateObject,
          this
        )
      },
    },

    _updateObject: function (id) {
      this._map.objectEl(id).classList.toggle('mymod-available',
        this.map.objects.readSubAtCoords(id, 0, 0, 'available', 0)
          .find(0, count => count > 0 || null) != null)
    }
  })

  var style = $('<style>')
    .text(
      `
        .mymod-available {
          outline: .1em dashed lime;
        }
      `
    )

  return {
    start: function (cx) {
      style.appendTo('body')
      cx.autoAddModule('mymod', Mod)
    },

    stop: function (cx) {
      $('.mymod-available').removeClass('mymod-available')
      cx.screens().forEach(sc => sc.unlist('mymod'))
      style.remove()
    },
  }
})
  • define declares dependency on a class with useful stuff, common to all screen modules.
  • Common.Sqimitive.extend() – a way to declare new classes in the Sqimitive framework. New class’ fields are provided inside an object; for example, _map is a new property defaulting to null, and the underscore prefix signifies that it should not be accessed from code outside of the class (protected).
  • render – an event to which we subscribe. It occurs when UI is being painted, just once for any given module. Other events exist, such as attach occurring while loading, prior to render.
  • this.sc – refers to the screen containing our module. Multiple screens may exist simultaneously (for example, during a hot seat game). Screen gives us instance of the module rendering the adventure map.
  • this.map – refers to the logical map representation (data only). Holds info about players, objects, effects, shroud and the rest.
  • this.autoOff() – hooks events in another object, unhooking automatically when our module is removed.
  • ochange_p_N – event occurring in the store of adventure map’s object data (terrains, roads, chests, mills, towns, heroes – and dwellings too). More accurately, it happens when an "o"bject sees a “change” on its "p"roperty “N” (in our case N stands for available).
  • function (n) receives number (n) of the changed object; obtains its ID, checks the type (we don’t work with towns, for example) and updates the object.
  • After setting up the listener, we walk existing map objects to apply initial outlines. map.byType is an index on map.objects that provides us with the list of objects grouped by their types; using it is faster than scanning all objects manually.
  • All game objects in the current version are represented by DOM nodes (which is the cause for high memory usage and low performance). This means our _updateObject can simply add the mymod-available CSS class whenever the available array of a particular object happens to have any number above 0. (available holds separate numbers per creature kind.)
  • Finally, in start we add our CSS styles to the document and our module to each (autoAddModule) screen existing in the engine context (cx).