IETS - v0.5.1
    Preparing search index...

    Type system

    Most numeric IDs in Infinity Engine scripting are interchangeable at the byte level - a spell ID, a class ID, and a sound slot are all just number. The type system has nothing to say about whether 42 is a valid SpellID or a stray race ID someone fat-fingered. @bgforge/iets uses branded types to recover that distinction.

    type Brand<B> = { __brand: B };
    export type IE<T, B> = T & Brand<B>;

    IE<number, "SpellID"> is structurally a number, but nominally distinct. The compiler treats SpellID and ClassID as incompatible even though both compile down to plain numbers.

    The package ships pre-typed constants for every IDS file, so 99% of the time you never construct a branded value by hand:

    import { ReallyForceSpell, Myself, WIZARD_SHIELD, WIZARD_FIREBALL } from "@bgforge/iets/bg2";

    ReallyForceSpell(Myself, WIZARD_SHIELD); // OK
    ReallyForceSpell(Myself, WIZARD_FIREBALL); // OK
    ReallyForceSpell(Myself, 42); // type error: 42 is not a SpellID

    When you need a custom value (a spell from a mod that isn't in the stock IDS, for example), cast it once at the boundary:

    import type { SpellID } from "@bgforge/iets";

    const MY_CUSTOM_SPELL = 9001 as SpellID;
    ReallyForceSpell(Myself, MY_CUSTOM_SPELL); // OK

    If your custom spell is reachable by resref rather than ID, use the *RES form of the action and pass a plain string:

    ReallyForceSpellRES("WM_LIGHT", Myself);    // OK - resref form, no cast needed
    

    ResRef, SplRef, ItmRef, and friends are string & {} - they look branded but are actually equivalent to plain string. This is deliberate. Resrefs are almost always raw string literals ("SWORD01", "AR0602"), and there is no finite IDS to give you pre-typed constants. Branding would force a cast on every usage:

    GiveItemCreate("SWORD01" as ItmRef, Player1, 0, 0, 0);  // would be required
    GiveItemCreate("SWORD01", Player1, 0, 0, 0); // what we actually want

    The string & {} shape preserves literal-type inference (so "SWORD01" doesn't widen to plain string in inferred contexts) without adding ceremony.

    Engine action functions return a branded Action interface:

    export interface Action {
    readonly __brand: "Action";
    }

    This lets ActionOverride enforce that its argument is an actual action call, not an arbitrary expression:

    ActionOverride(Player1, MoveToObject(Player2));  // OK
    ActionOverride(Player1, "MoveToObject"); // type error: not an Action

    Some IDS types use a *ID suffix (ClassID, GenderID, KitID), others use bare names (Align, EA, State). The suffix exists only when a same-named trigger or action function would clash:

    • Class() is a trigger -> the IDS type is ClassID.
    • Align has no same-named function -> the IDS type is Align.

    If you're hunting for a type and don't find the bare name, try the *ID form.