Real TBAF patterns using @bgforge/iets/bg2. These are TypeScript snippets as they appear in .tbaf files - the TBAF transpiler wraps top-level if blocks into IF condition THEN action END and inlines all variables, functions, and loops at compile time. The resulting .baf contains no TypeScript abstractions.
For full transpiler semantics see the TBAF forum thread. For dialog scripting in .td files see the TD forum thread.
From the tbaf-example project - on-sight talk-then-fight behavior for an NPC mage:
import { LOCALS } from "@bgforge/iets";
import {
FaceObject, Global, LastSeenBy, LevelLT, Myself, NearestEnemyOf, Player1,
ReallyForceSpell, ReallyForceSpellRES, See, SetGlobal, SmallWait, StartDialog,
WIZARD_ARMOR, WIZARD_SHIELD,
} from "@bgforge/iets/bg2";
const LVAR_doomed = "doomed";
const LVAR_castSpellTrigger = "castSpellTrigger";
// First talk, then fight
if (See(Player1) && Global(LVAR_doomed, LOCALS, 0)) {
SetGlobal(LVAR_doomed, LOCALS, 1);
FaceObject(Player1);
SmallWait(8);
StartDialog("WM_RHIA", Player1);
}
// Spell trigger setup, branching on observed enemy level
if (See(NearestEnemyOf(Myself)) && Global(LVAR_castSpellTrigger, LOCALS, 0)) {
if (LevelLT(LastSeenBy(Myself), 3)) {
ReallyForceSpell(Myself, WIZARD_ARMOR);
ReallyForceSpell(Myself, WIZARD_SHIELD);
}
if (LevelLT(LastSeenBy(Myself), 8)) {
ReallyForceSpellRES("WM_LIGHT", Myself);
ReallyForceSpell(Myself, WIZARD_SHIELD);
}
}
Each top-level if becomes one or more IF/THEN/END blocks. Nested ifs accumulate conditions: the inner block's condition list becomes parent-conditions plus child-condition.
Most action and trigger arguments take an ObjectPtr. Common forms:
Player1, Myself, LastSeenBy, LastAttackerOf(Myself), NearestEnemyOf(Myself).obj(): [ENEMY.0.0.MAGE]-style strings.import { obj } from "@bgforge/iets";
import { Attack, Kill, Myself } from "@bgforge/iets/bg2";
if (See(obj("[ENEMY.0.0.MAGE]"))) {
Attack(obj("[ENEMY.0.0.MAGE]"));
}
The TBAF transpiler maps TypeScript boolean operators to BAF condition algebra:
&& -> implicit AND between conditions in the same IF block|| -> OR(n) group! -> !if (See(Player1) && Global("hostile", LOCALS, 1)) { Attack(Player1); }
// -> IF See(Player1) Global("hostile","LOCALS",1) THEN Attack(Player1) END
if (See(Player1) || See(Player2)) { Attack(NearestEnemyOf(Myself)); }
// -> IF OR(2) See(Player1) See(Player2) THEN Attack(NearestEnemyOf(Myself)) END
if (!See(Player1)) { NoAction(); }
// -> IF !See(Player1) THEN NoAction() END
IDS constants are typed IE<number, "..."> and exported from the same bg2 barrel as actions and triggers. Use the symbolic name; the transpiler emits it verbatim into the .baf:
import { ReallyForceSpell, Myself, WIZARD_FIREBALL, CLERIC_BLESS } from "@bgforge/iets/bg2";
ReallyForceSpell(Myself, WIZARD_FIREBALL);
ReallyForceSpell(Myself, CLERIC_BLESS);
Many actions come in two flavors: one taking an IDS-typed ID, one taking a resref string. The resref form is suffixed RES:
ReallyForceSpell(Myself, WIZARD_SHIELD); // ID form -> typed SpellID
ReallyForceSpellRES("WM_LIGHT", Myself); // RES form -> raw resref string
Resrefs (ResRef, SplRef, ItmRef, AreRef, CreRef) are intentionally unbranded string & {} - pass string literals directly, no cast needed. See Type system for the rationale.
Variable names are plain string literals. Scope accepts GLOBAL, LOCALS, MYAREA, or any area resref:
import { GLOBAL, LOCALS } from "@bgforge/iets";
import { Global, SetGlobal } from "@bgforge/iets/bg2";
if (Global("chapter", GLOBAL, 3)) {
SetGlobal("seen_chapter3_intro", LOCALS, 1);
}
if (Global("ar0602_explored", "AR0602", 1)) {
// ...
}
Point is a [number, number] tuple - no helper needed:
import { MoveToPoint } from "@bgforge/iets/bg2";
MoveToPoint([1024, 768]);
Both expand at compile time. Loops over arrays become repeated blocks; user-defined functions inline their bodies at each call site. This is how TBAF gives you abstractions over a flat BAF target:
function attackIfVisible(target: ObjectPtr) {
if (See(target)) { Attack(target); }
}
for (const enemy of [obj("[ENEMY.0.0.MAGE]"), obj("[ENEMY.0.0.FIGHTER]")]) {
attackIfVisible(enemy);
}
The generated .baf contains four IF/THEN/END blocks (two enemies x one condition each), with no surviving function or loop construct.