JASS channel
The 1291 JASS functions available to map makers can now be called by name from outside the game: create units, change properties, effects, boards, dialogs, sound, camera, fog… Four ways to use it: the Farsight console, the command line, HTTP and Python.
The 1291 JASS natives that map makers can use in map scripts can now all be called by name from outside the game: create units, change properties, draw effects, pop up boards and dialogs, play sounds, move the camera, change the fog… Use them to customize the game further — RPG helpers, an AI companion, homemade mini-games, debugging tools.
| Usage | Good for | Where |
|---|---|---|
| Farsight “JASS console” page | Trying things by hand, tweaking as you watch | Sidebar “System → JASS console”: write a script and click Run; the right side lists functions by category, click one to insert it into the script |
| Command line | Trying things by hand, or saving a script file to run again and again | python -m openwar3 jass --inst 20 (interactive), -e "code", my_script.j, --list keyword |
| HTTP | External programs in any language | POST /api/instances/{n}/jass and others (see below); the Farsight backend listens only on the local machine |
| Python | Writing schemes, companions and tools | g.jass.AnyFunction(...); common visuals and interactions are wrapped in openwar3.visual |
Three boundaries, all dictated by how it works:
- Only single-player games (against the computer on your machine) can change the world. Creating objects or changing units unilaterally on your machine would desync the other players in a multiplayer game — so in multiplayer games only read-only functions (
Get*,Is*,Count*…) are allowed. - It’s only for your machine’s own tools; calls made while connected as a player (
Game(player=N)) or in fair mode are rejected. - Only for single-player and self-hosted LAN games.
To add things to the screen in a multiplayer game, use the canvas: the runtime draws it itself, and it doesn’t change game state.
Script syntax
The console, the command line and HTTP all use the same scripts. One statement per line; you can paste JASS directly (call / set / local, true / false / null, 'Hpal' four-character codes, // comments), or write it Python-style:
set h = hero() // built-in: our main hero
local texttag t = CreateTextTag()
call SetTextTagText(t, "|cffffcc00+128 Critical!|r", 0.024)
call SetTextTagPosUnit(t, h, 60)
call SetTextTagVelocity(t, 0, 0.03)
call SetTextTagPermanent(t, false)
call SetTextTagLifespan(t, 4)
call SetTextTagVisibility(t, true)
call PingMinimapEx(h.x, h.y + 300, 3, 255, 0, 0, false)
set u = CreateUnit(Player(0), 'hfoo', h.x + 200, h.y, 270)
print("created", u, "hero level", GetHeroLevel(h))
- Variables persist: within the same instance and the same game, variables you
setin one snippet can be used in the next; they’re cleared automatically when a new game starts, and you can also clear them manually. - Built-in functions:
hero()our main hero,me()the local player,unit('hfoo')finds a unit,unit_at(x, y),wait(seconds),print(...). Units expose.x,.y,.hp,.hp_max,.mana,.type,.ownerand.level, and arithmetic and comparisons are supported. - Not supported:
if,loop,function— for logic, use Python’sg.jass(it’s just ordinary function calls), or write a scheme. - Errors tell you which line failed and why (no such function, wrong number of arguments, undefined variable…); statements before the error have already taken effect.
Arguments and return values:
| In the signature | What to pass | Notes |
|---|---|---|
| integer | A number; 'Hpal' four-character codes are converted automatically | |
| real | A number | The runtime converts it to the format the engine expects |
| boolean | true / false | |
| string | "..." | Chinese text and the game’s color codes are supported; strings the game keeps (floating text, boards, buttons, chat commands) are copied on the spot, so it’s safe |
| handle | A handle held in a variable, or a unit (things like hero() are converted to handles automatically) | |
| function (code) | Only null | A JASS function can’t be passed in from outside; something like TimerStart(t, 60, false, null) works |
| string return value | — | The engine returns a string table index, so the text can’t be read back. For unit names, use g.map_data.name_of |
Categories
Functions are grouped into categories by name; the console’s right panel and --list both use these:
| Category | Count | Examples |
|---|---|---|
| Visual effects | 80 | Floating text, lightning links, special effects, ground images, ground splats, unit tint / scale / animations |
| Interface and boards | 146 | Multiboards, leaderboards, timer windows, dialogs, quests, on-screen text, minimap pings, portrait dialogue, full-screen filters |
| Camera | 44 | Camera fields, panning, camera shake |
| Sound and music | 50 | Creating and playing sounds, playing music |
| Fog and vision | 25 | Visibility modifiers, toggling fog of war |
| Item / hero / unit | 63 / 32 / 161 | Create items, set hero level, change owner, add abilities |
| Player / alliance / resources | 71 | Set alliances, change gold and lumber |
| Trigger / event / timer | 62 | Create triggers, register events, timers |
| Terrain / weather / destructable | 45 | Weather effects, changing terrain, creating destructables |
| Game flow | 57 | Game speed, pause, time of day |
| Other | … | Unit groups and regions, storage, computer AI scripts, type conversion and math, event responses… |
On 2026-09-24, 94 of them were called one by one in a live game and their effects checked by eye; the rest go through the same path, just without each effect being checked individually.
Event response functions (GetTriggerUnit, GetClickedButton…) only have a value at the moment a trigger runs; called from outside, they return 0 or null. To find out whether something happened, use the event counters described below.
HTTP
Farsight backend (default 127.0.0.1:8866, listens only on the local machine):
GET /api/jass/natives?q=TextTag&cat=visual
POST /api/instances/20/jass {"code": "set h = hero()\ncall PingMinimapEx(h.x, h.y, 3, 255, 0, 0, false)"}
-> {"ok": true, "rows": [...], "printed": [...], "vars": {...}}
-> on error: {"ok": false, "error": "第 2 行:...", "line": 2}
POST /api/instances/20/jass/call {"name": "SetUnitScale", "args": [{"unit": 599669636}, 1.4, 1.4, 1.4]}
POST /api/instances/20/jass/reset clear the remembered variables
For a unit argument, write {"unit": addr}, where addr is the unit’s addr in the snapshot. Measured at 60–90 ms per request.
Python: g.jass and openwar3.visual
j = g.jass
t = j.CreateTextTag()
j.SetTextTagText(t, "Hello", 0.024) # same argument rules as scripts; unit and item objects from the snapshot can be passed directly
j.signature("CreateImage") # look up a signature
openwar3.visual.Visual(g) wraps the tested, commonly used visual effects into one-liners (call v.tick() once per tick: it removes expired effects and moves the lines and circles that follow units; v.clear() removes everything):
| Method | Effect |
|---|---|
float_text(text, unit_or_point, ...) | Floating text: damage numbers, overhead hints; Chinese text and colors both work |
link(a, b, kind) | A line between two units that follows them: magic leash / spirit link / life drain / healing wave |
effect(model, unit_or_point, ...) | Special effect model: overhead, at the feet, or played once (explosion, pillar of light) |
ring(unit_or_point, radius, color) | Range circle on the ground: ability range, danger zone, rally point; can follow a unit |
ping(point, color) | Minimap ping |
board(title, rows...) | Multi-row board in the top-right corner (with icons); cells can be updated one by one |
countdown(title, seconds) | Timer window in the top-right corner; the game counts down the seconds itself |
scene(name, line, portrait) | Portrait dialogue: the portrait at the bottom switches to a talking unit, and a “Name: line” subtitle appears on screen |
screen_tint(color, alpha) | Full-screen filter (by default a red glow around the edges: a low-health warning) |
sound(path) / reveal(point, radius, seconds) / look(unit, ...) | Play a sound / clear the fog over an area / tint, scale, animate or flash a unit |
Interaction: knowing what the player did without writing JASS functions
Responding to the player in JASS means writing trigger functions, and a function can’t be passed in from outside. The workaround: create an empty trigger with no conditions and no actions, register only the event, and count how many times it has run. Testing confirmed that an empty trigger still counts.
| Method | Use |
|---|---|
chat_commands(["-follow", "-stay"]) → .poll() | Commands the player types in the chat box (exact match, or prefix match) |
menu(title, [buttons...]) → .clicked() | A button menu in the middle of the screen, and which button was clicked |
hotkeys(("left", "right", "up", "down", "esc")) → .poll() | How many times the arrow keys and Esc were pressed |
on("TriggerRegister...Event", args...) → .poll() | How many times any JASS event happened: unit death, entering a region, taking damage, timers… |
The limitation is that you only know how many times something happened, not who did it or what they typed. To tell who, create a separate counter for each object. That’s how the AI companion’s chat commands are wired up.
Caveats
- Clean up what you create: floating text, links, images, boards, triggers… stay around until removed (
Visual.clear()removes the ones it created). The game can show at most about 100 floating texts at once. - BJ functions aren’t natives: functions like
CreateTextTagUnitBJare built out of natives in the map script and aren’t available here — call the natives the way their implementation does. - Some constants need converting first: e.g.
ConvertPlayerColor(1),ConvertFogState(4)(see common.j for the values). - One call takes about 13 ms (including handle conversion); at the protocol level these are W3P opcodes 70–72; see W3P protocol.