W3 / DevelopersSDK 4.6.0Source & examples ↗

Warcraft catalog: turn game observations into useful information

A recorder tells your app which units exist and what is happening. The catalog adds what those Warcraft objects mean: their costs, base values, ability levels, production relationships and artwork. Join the two by typeId to build army panels, mana indicators, research views and richer tooltips without maintaining your own rawcode tables and icon collection.

The catalog is available through @w3booster/sdk/game-data. It is a separate, optional entry point, with data and images hosted outside your app bundle. Each match names its exact catalog revision in match.gameDataId, so a replay can use the data for its recorded Warcraft build rather than today's balance values.

What you can build

App feature Catalog data Live observation
Army composition and cost ordering Unit gold/lumber cost, supply and icon Observed units and their typeId
Ability mana indicator Mana cost for the learned ability level Current hero mana and observed recovery rate
Research panel Upgrade category, level names, costs, research duration and artwork Active upgrade type, level and progress
Building tier transition Destination building name and icon building.upgrade destination and progress
Inventory tooltip Item name, cost, icon and linked ability IDs Slot contents and directly observed slot cooldowns
Unit reference or tech browser Base stats, abilities, train/build relationships Optional selection or observed ownership

Catalog access does not expand an app's permission to observe live units or players. Keep the normal SDK access rules when combining public definitions with match state.

Four typed collections and generated artwork

Collection Available definitions
data.units Gold/lumber cost; supply used/provided; base health, mana, armor, movement and attributes; build time; abilities, upgrades, trained and built units.
data.abilities Maximum level, required hero level and per-level mana cost, cooldown, cast time and range.
data.items Name, gold/lumber cost and linked abilities.
data.upgrades Category, maximum level and ordered levels with name, cost and research time.

Each collection offers get(typeId), has(typeId) and values(). Values can be absent; an unknown cost or stat is not zero. Use data.assets.unitIcon, abilityIcon, itemIcon or upgradeIcon for hosted, content-hashed artwork. Choose classic or reforged to match the game; ability and upgrade artwork can also use a one-based level and the icon, research or inactive role. Objects without configured art can legitimately have no image.

Start with the match's revision

The following checked example builds an army row and an ability presentation. It uses actual typeId values, retains observed health and mana, and only estimates mana recovery time when a positive rate is available.

import type { MatchState, Unit, Hero, HeroAbility } from '@w3booster/sdk';
import { loadGameData, abilityCooldown } from '@w3booster/sdk/game-data';

export async function catalogDetails(
    state: MatchState, unit: Unit, hero: Hero, ability: HeroAbility,
    signal?: AbortSignal
) {
    const revision = state.match.gameDataId;
    if (!revision) return undefined;
    const data = await loadGameData(revision, { signal });
    const graphics = state.match.isReforged ? 'reforged' : 'classic';
    const definition = data.units.get(unit.typeId);
    const level = ability.level > 0
        ? data.abilities.get(ability.typeId)?.levels[ability.level - 1]
        : undefined;
    const manaCost = level?.manaCost;
    const mana = hero.mana;
    const missingMana = mana && manaCost !== undefined
        ? Math.max(0, manaCost - mana.current) : undefined;
    const rate = mana?.regenerationPerSecond;
    const secondsUntilMana = missingMana === 0 ? 0
        : missingMana !== undefined && rate !== undefined && rate > 0
            ? missingMana / rate : undefined;
    return {
        unit: {
            name: definition?.name, cost: definition?.cost,
            supply: definition?.supply,
            icon: data.assets.unitIcon(unit.typeId, { graphics }),
            hitpoints: unit.hitpoints
        },
        ability: {
            manaCost, missingMana, secondsUntilMana,
            icon: ability.level > 0 ? data.assets.abilityIcon(ability.typeId,
                { graphics, level: ability.level }) : undefined,
            cooldown: abilityCooldown(ability, state.match.gameTime, data)
        }
    };
}

Call the loader when the advertised revision changes, then reuse the returned catalog across snapshots. Successful loads are cached by revision, base URL and fetch implementation. Failed or aborted loads can be retried. For asynchronous UI work, discard results if the match or revision changed while loading; an AbortSignal also lets you cancel work when the view closes.

If gameDataId is absent or loading fails, continue showing the live state and omit unavailable catalog details. Never silently substitute the latest catalog, a nearby build, a similarly named object or a guessed icon.

Practical details that matter

Army values and ordering

Copy SDK arrays before sorting them. Choose a clear cost order, for example gold ascending, then lumber ascending, then typeId for ties, with unknown values last. Gold and lumber have no universal exchange rate: multiplying a unit's catalog costs by observed counts gives separate gold and lumber values, not a single authoritative army-strength score. Also decide explicitly whether your view includes workers, buildings, summons and illusions.

Ability levels, mana and cooldowns

ability.level is one-based; level zero means unlearned. Index catalog levels with level - 1 only for learned abilities. Rawcodes are case-sensitive and keep their actual variants: do not normalize an ability into a similar-looking one.

A mana bar can show currentMana / manaCost, clamped to 0–1, and disappear once the cost is met. Mana sufficiency alone does not prove an ability is castable: cooldowns, targets, silence and other game rules may still prevent casting. regenerationPerSecond is an observed net change per game second, so an estimate of missing mana divided by a positive rate can change after the next sample. Zero, negative or unavailable rates do not supply a finite estimate.

abilityCooldown(ability, gameTime, data) derives an estimate from the observed activation and catalog duration. abilityCooldownsForState provides the whole-state equivalent and checks the revision. These are different from the engine timers in hero.inventoryCooldowns, which are aligned to inventory slots. Use the match game clock; do not advance an observed snapshot with wall-clock time while a replay is paused.

Inventory capacity and remaining charges

Hero.inventoryCharges supplies live remaining counts, aligned with hero.inventory. The catalog's optional ItemType.initialCharges supplies the normal starting capacity. Keep both arrays in slot order, including empty slots and duplicate item types. A known 0 stays zero; null means an empty or unobserved slot, and an absent array means counts are unavailable.

For example, these hero fields represent two salves, a single-use potion and three empty slots:

{
  "inventory": ["hslv", "hslv", "phea", "", "", ""],
  "inventoryCharges": [3, 1, 1, null, null, null]
}

The salve's catalog definition has initialCharges: 3. A compact indicator can show ● ○ ○ for the second salve, while hiding indicators for single-use items. Zero remaining renders three empty dots. Hide unavailable counts; never replace them with the starting default. Use a number if the live count exceeds capacity or if the capacity is too large for your icon.

import type { Hero } from '@w3booster/sdk';
import type { GameData } from '@w3booster/sdk/game-data';

// Load data from the current match.gameDataId; reuse it across snapshots.
export function inventoryChargeIndicator(hero: Hero, slot: number, data: GameData) {
    const typeId = hero.inventory?.[slot];
    const initial = typeId ? data.items.get(typeId)?.initialCharges : undefined;
    const remaining = hero.inventoryCharges?.[slot];
    if (initial === undefined || initial <= 1 || remaining == null) return undefined;
    return {
        remaining,
        // Keep large/custom counts readable and bound the number of dots.
        dots: remaining <= initial && initial <= 10
            ? Array.from({ length: initial }, (_, index) => index < remaining)
            : null // Render a numeric badge instead.
    };
}

This is standard capacity, not a recorded starting count for that item instance or proof of how many uses occurred. Custom maps and modified items may differ. Starting charges are distinct from shop stock and item cooldowns. Subscribe to state or hero.changed to refresh counts even when inventory rawcodes stay the same. Existing heroes:read and player-ownership rules still apply. Older recorders or catalogs can omit either optional field.

Upgrade levels and building transitions

Resolve research through data.upgrades.get(upgrade.typeId) and select levels[upgrade.level - 1]. Use the same one-based level for its artwork; do not parse a level suffix from the rawcode. Categories such as armor, melee, ranged and caster come from the source data.

A building upgrading into another building is a separate activity: building.upgrade.typeId resolves through data.units, and its observed progress remains authoritative. Do not infer that progress from the catalog's base build time or merge it into the training/research queue.

Go deeper with the optional gameplay projection

await data.unitGameplay(typeId) loads a broader retained unit-data projection on demand. It includes source fields for attacks, targeting, damage dice, movement, regeneration, attributes, production, prerequisites and related configuration. Values are source strings grouped by source section, including empty values and sentinels; this is not another set of normalized live stats.

Use the typed collections for common UI. Reach for the broad projection when building an inspection tool or an explicitly defined calculation that needs source fields beyond the small typed view. The optional fetch and decoding keep that extra data off the ordinary application path.

What the catalog does—and does not—represent

The pipeline extracts the build's current-melee object configuration and exact artwork references together. The SDK verifies checksums and build identity and exposes immutable data. English display names are labels; rawcodes are keys. Classic and Reforged references are selected during generation rather than constructed by guessing filenames in the app.

Base configuration is not effective live combat state. Hero attributes, learned upgrades, buffs and game rules can change health, damage, armor or mana costs. Prefer observed values whenever they exist. Do not present a base-health field as a hero's current maximum or precompute an authoritative live DPS from catalog damage alone.

The dataset describes current melee rules for that build; it does not resolve custom-map overrides. Item definitions currently expose names, costs and linked abilities, not normalized numeric item damage or armor effects. Never parse an item's display name or rawcode suffix as its numeric balance value.

Continue building