OpenWarcraft3 uses Quake-style runtime configuration: small cvars select subsystems, choose startup modes, and make diagnostics reproducible from the command line.
Project-private compile-time macros, generated binding helpers, environment toggles, and namespaced constants use the BZ_ prefix. Keep new project-prefixed names on that prefix instead of adding another project namespace.
// common/common.h
typedef struct cvar_s {
struct cvar_s *next; // linked list
LPCSTR name;
LPSTR string; // string value (heap-allocated)
FLOAT value; // float conversion
int integer; // integer conversion
DWORD flags; // bitmask: CVAR_ARCHIVE
bool modified; // set true when value changes
} cvar_t;Cvars are stored in a singly-linked list. Cvar_Get(name, value, flags) finds or creates a cvar by name, adding any new flags to existing cvars. Cvar_Set(name, value) sets the value and marks modified = true. If the old value matches the new one, it's a no-op.
There is one flag: CVAR_ARCHIVE (bit 0). A cvar with CVAR_ARCHIVE is written to the generated config file by writeconfig and restored on next launch. Session-only cvars like map and connect omit this flag.
When you type a bare cvar name in the console (e.g., scr_showfps 0), Cvar_Command() auto-dispatches: prints the current value with one arg, sets it with two or more. Console commands:
| Command | Effect |
|---|---|
set <name> <value> |
Set a cvar without changing flags |
seta <name> <value> |
Set a cvar and add CVAR_ARCHIVE |
cvarlist |
Print all cvars — * prefix marks archived ones |
writeconfig [path] |
Write all CVAR_ARCHIVE cvars to a config file, defaults to the value of cvar config |
exec <path> |
Load and execute a config file |
// Generated by openwarcraft3, do not modify
seta data "data/Warcraft III"
seta name "Player"
seta menu_module "ui"
seta g_module "game"
seta scr_showfps "1"
...
Session-only cvars (map, connect) are explicitly skipped. The removed r_module cvar is ignored if an old config still contains it, so seta r_module cannot recreate a renderer selector. When com_frame_limit > 0, the engine exits without writing config at all, so one-shot diagnostic runs don't pollute saved settings.
Config files are split by ownership: read-only defaults ship with the game, writable user settings live in a per-user home directory. The paths are resolved at startup (Sys_ResolveShareDirectory / Sys_ResolveHomeDirectory in common/main.c):
fs_basepath— read-only baseshare/dir, anchored at the executable (<exe>/share,<exe>/../share, or CWDshare). Engine-wide assets (fonts/) live at its top level.fs_homepath— writable$XDG_DATA_HOME/<game>/on Unix whenXDG_DATA_HOMEis set to an absolute path, otherwise~/.local/share/<game>/;%APPDATA%\<game>\on Windows. It is adopted only if creatable and writable and is empty otherwise. macOS follows the same XDG-or-~/.local/sharerule rather than~/Library/Application Support.
The load order in Com_Init() is:
| Step | File | Purpose |
|---|---|---|
| 1 | Programmatic Cvar_Get() in Cvar_Init() |
In-code defaults |
| 2 | -config CLI arg |
Override config path |
| 3 | Early + args (+set, +<cvar>) |
Command-line overrides |
| 4 | <base>/<game>/config.cfg |
Shipped game defaults (key bindings) |
| 5 | Cvar config (default $XDG_DATA_HOME/<game>/config.cfg, or ~/.local/share/<game>/config.cfg) |
Generated user config — written on shutdown or by writeconfig |
| 6 | <fs_homepath>/autoexec.cfg |
Optional local overrides |
| 7 | -data, -connect, -tft, -roc CLI args |
Data-dir / expansion settings |
| 8 | Remaining + args (+set, +<cvar>), consumed |
Final command-line overrides |
When neither a usable $XDG_DATA_HOME nor $HOME is available (portable/read-only deploy), fs_homepath is empty and steps 5–6 degrade to <base>/<game>/config.cfg and <base>/<game>/autoexec.cfg, so a share/ tree copied beside the executable still works.
Gameplay saves resolve under the same home directory: $XDG_DATA_HOME/<game>/saves/<name>.sav when set to an absolute path, otherwise ~/.local/share/<game>/saves/<name>.sav on Unix; %APPDATA%\<game>\saves\<name>.sav on Windows; else share/<game>/saves/. See Warcraft III Save/Load.
After step 6, map and connect cvars are explicitly cleared, then re-populated from command-line arguments in steps 7–8.
Cvar_LoadConfig(path) tries FS_ReadFileIntoString first (MPQ/loose filesystem), then falls back to raw fopen for local files. It queues the text; startup calls Cbuf_Execute() after each config load before consuming the resulting cvars.
bind <key> <command> stores a command string in client/keys.c. Key_Init registers bind before configs load, so shipped share/<game>/config.cfg lines populate the table. The bound command does not need to exist until the key is pressed.
Modifiers are a stroke, not a bag of flags. Like lite's keymap (ctrl, alt, shift), they must be written in that order. Keyboard strokes match exactly; mouse buttons first use an explicit modified bind and otherwise inherit the plain mouse-button bind so gameplay handlers can consume held modifiers such as Shift order queuing:
bind 1 "group 1"
bind SHIFT+1 "group add 1"
bind CTRL+1 "group assign 1"
bind CTRL+SHIFT+1 "group assign 1"
bind ALT+MOUSE1 "+pan"
SHIFT+CTRL+1 is rejected. CTRL+SHIFT+1 is not CTRL+1 or SHIFT+1. SHIFT+Q is not q. For mouse buttons, ALT+MOUSE1 overrides MOUSE1, while an unbound SHIFT+MOUSE1 falls back to MOUSE1 and the command handler still sees Shift held. CONTROL is an alias for CTRL. Letters fold to lowercase (SHIFT+Q is the Q key). writeconfig emits CTRL+ALT+SHIFT+<key>.
+command key-up reuses the modifiers captured on key-down, so releasing Alt before the mouse button still ends ALT+MOUSE1 "+pan".
SDL key-repeat is ignored while key_dest == key_game. Named keys include TAB, ESCAPE, F1–F12, UPARROW/DOWNARROW/LEFTARROW/RIGHTARROW (aliases UP/DOWN/LEFT/RIGHT), MOUSE1–MOUSE3, and MWHEELUP/MWHEELDOWN. zoom <delta> is a generic client command that adjusts cl.playerstate.distance, clamped by camera_min_distance / camera_max_distance when those cvars are set. New gameplay hotkeys belong in config bind lines.
| Argument | Effect |
|---|---|
-data <folder> |
Sets data cvar (game asset directory) |
-connect <host[:port]> |
Sets connect cvar (remote server address) |
-config <path> |
Sets config cvar (generated config path) |
-vid_modes |
Sets session cvar vid_modes to "1"; logs SDL display modes during renderer startup |
-tft |
Sets fs_expansion to "1" (TFT skin/data edition; expose mounted TFT MPQs) |
-roc |
Sets fs_expansion to "0" (RoC skin/data edition; hide War3x* archives from lookup) |
The + prefix is for command-line only. It tells Cbuf_AddEarlyCommands / Cbuf_AddLateCommands to queue the argument as a command for startup execution:
| Form | Behavior |
|---|---|
+set <name> <value> |
Cvar_Set(name, value) immediately |
+<cvar> [<value>] |
If <cvar> exists, sets it to <value> (or "1" if no value) |
+tft / +roc |
Compatibility startup flags; consumed early like -tft / -roc so the first menu uses the requested edition |
+<command> [<args>...] |
Queued via Cbuf_AddText — executed after module init |
SDL owns the native platform cursor on macOS, Linux, Windows, and other supported video backends. WoW changes that native cursor with SDL_CreateSystemCursor and SDL_SetCursor for hover context. The renderer does not duplicate platform cursor APIs or draw a generic software fallback.
r_cursor 0 keeps the SDL cursor and is the default. r_cursor 1 explicitly replaces it with a game-authored cursor when the active game renderer provides one; Warcraft III renders UI\\Cursor\\HumanCursor.mdx. If that asset cannot load, the client leaves the SDL cursor visible.
Early commands (+set, +<cvar>) are processed during Com_Init(), before module registration. Late commands (+map, +menu_main, etc.) are processed after CL_Init() when all command handlers are registered.
Game-module gi.MenuAction requests are also deferred. The callback only copies a validated map/menu/quit request; the next CL_Frame consumes it after SV_Frame has returned. This prevents a ChangeLevel native from entering SV_Map while the old game's VM or gameplay callback is still on the stack. Do not make MenuAction("map", ...) synchronously replace the world.
A game may also queue client presentation with game_import.QueueMovie. A queued movie does not execute immediately; if a deferred session action follows, the client preserves that action, pauses the outgoing simulation, plays the movie, and resumes the action after EOF/skip. This keeps media decoding out of game modules and keeps world teardown outside the active game/VM stack. See Warcraft III pre-rendered movies.
A deferred MenuAction("menu", target) is a full world→menu session boundary. The client disconnects without queuing an intermediate menu command, shuts down the local server/game module, clears the map cvar and renderer map scope, rebuilds the menu library state, then enters target (the rebuilt main menu is already active when target is menu_main). This is required after campaign missions because renderer/FDF/menu state may have crossed a map registration boundary while the level was active.
In code, always use the bare command name: "map ...", not "+map ...".
# Listen server + local client (loopback, no real socket)
openwarcraft3 -data "Warcraft III" +map "Maps\Campaign\Human02.w3m"
# Remote client
openwarcraft3 -data "Warcraft III" -connect 192.168.1.10:27910
# Client menu only
openwarcraft3 -data "Warcraft III"
# Expose expansion MPQs / select TFT (`+tft` is also accepted for compatibility)
openwarcraft3 -data "Warcraft III" -tft +map "Maps\FrozenThrone\Campaign\NightElfX01.w3m"
# One-frame UI diagnostic
openwarcraft3 -data "Warcraft III" +menu_main +com_frame_limit 1All cvars registered in Cvar_Init():
| cvar | Default | Flags | Description |
|---|---|---|---|
config |
<fs_homepath>/config.cfg (resolved) |
CVAR_ARCHIVE | Generated config path |
fs_basepath |
resolved share dir | 0 | Read-only engine/share data directory |
fs_homepath |
$XDG_DATA_HOME/<game>/ or ~/.local/share/<game>/ (empty if unavailable) |
0 | Writable per-user directory |
data |
"" |
CVAR_ARCHIVE | Game asset directory (contains MPQs) |
fs_expansion |
"0" |
0 | Select base (0) vs expansion (1) data/skin version |
fs_expansion_archive_prefix |
"" |
0 | Optional archive basename prefix hidden while fs_expansion=0; WC3 sets this in its shipped config |
map |
"" |
0 | Internal MPQ map path for listen-server mode |
connect |
"" |
0 | Remote server address |
cl_debug_entities |
"0" |
0 | Client entity debug logging |
sv_debug_entities |
"0" |
0 | Server entity debug logging |
r_debug_entities |
"0" |
0 | Renderer entity debug logging |
menu_module |
"ui" |
CVAR_ARCHIVE | UI module name (placeholder for dynamic loading) |
g_module |
"game" |
CVAR_ARCHIVE | Game module name (placeholder for dynamic loading) |
ui_game_setup_map |
"" |
0 | Pre-selected map for game setup screen |
wc3_cheat_starting_resources |
"0" |
0 | WC3 map-start cheat: requires sv_cheats 1 at map load and grant; add 5000 gold and lumber once a human slot reaches playable gameplay state |
game_port |
"27910" |
CVAR_ARCHIVE | UDP port |
name |
"Player" |
CVAR_ARCHIVE | Player name |
sv_hostname |
"OpenWarcraft3" |
CVAR_ARCHIVE | Server hostname |
com_frame_limit |
"0" |
0 | Exit after N frames; 0 means run forever |
scr_showfps |
"1" |
CVAR_ARCHIVE | Show FPS counter |
skip_cutscene |
"0" |
0 | Skip cutscenes |
vid_mode |
"0" |
CVAR_ARCHIVE | Fallback resolution-table index; mode 0 is 640x480 |
vid_native |
"0" |
CVAR_ARCHIVE | Use the current SDL desktop resolution instead of vid_mode |
vid_fullscreen |
"0" |
CVAR_ARCHIVE | Fullscreen policy; native + fullscreen uses fullscreen-desktop |
r_model_detail |
"2" |
CVAR_ARCHIVE | Model detail level |
r_anim_quality |
"2" |
CVAR_ARCHIVE | Animation quality |
r_texture_quality |
"2" |
CVAR_ARCHIVE | Texture quality |
r_particles |
"2" |
CVAR_ARCHIVE | Particle quality |
r_lights |
"2" |
CVAR_ARCHIVE | Light quality |
r_unit_shadows |
"1" |
CVAR_ARCHIVE | Unit shadow rendering |
r_occlusion |
"1" |
CVAR_ARCHIVE | Occlusion culling |
r_norefresh |
"0" |
0 | Skip all screen rendering while input, client networking, snapshots, and the server continue |
r_stats |
"0" |
0 | Print renderer stats, or client/server loop rate while r_norefresh=1 |
ui_chat_support |
"0" |
CVAR_ARCHIVE | Chat UI support |
s_provider |
"1" |
CVAR_ARCHIVE | Sound provider |
For Warcraft III gameplay data, fs_expansion supplies the V0/V1 half of the map-selected sheet overlay. After W3I is parsed, gameDataSet (with the melee_map fallback) selects Custom_V<edition> or Melee_V<edition>; missing prefixed files fall back to the ordinary path in the already-filtered archive view. See WC3 Data Model.
The runtime libraries are built into build/lib/:
libshared— math and shared primitiveslibjass— Warcraft III JASS VM fromgames/warcraft-3/jass/libsheet— Warcraft III SLK/profile parser fromgames/warcraft-3/sheet/librenderer— generic renderer sources fromrenderer/plus the selected game's renderer hookslibmenu— selected-game UI library; for Warcraft III this isgames/warcraft-3/menu/libgame— selected-game server-side game logic; for Warcraft III this isgames/warcraft-3/game/
Game-owned sources live under games/<game>/:
| Game | Game logic | Renderer hooks | UI | Other game-owned sources |
|---|---|---|---|---|
| Warcraft III | games/warcraft-3/game/ |
games/warcraft-3/renderer/ |
games/warcraft-3/menu/ |
jass/, sheet/, tests/ |
| World of Warcraft | games/world-of-warcraft/game/ |
games/world-of-warcraft/renderer/ |
games/world-of-warcraft/menu/ |
none today |
| StarCraft II | games/starcraft-2/game/ |
games/starcraft-2/renderer/ |
uses the default UI library today | none today |
The project follows the Quake 2/Quake 3 module style: modules communicate through import/export function tables, not by reaching directly into each other's internals. The renderer exposes R_GetAPI; the UI exposes M_GetAPI; the game module has a server/game API boundary.
The renderer has one extra internal boundary: engine code in renderer/ calls the R_Game* functions declared in renderer/r_game.h. Those hooks are implemented by the selected games/<game>/renderer/ tree. This keeps common renderer code from switching on game-specific model formats such as MDX, M2, or M3.
The cvars menu_module and g_module currently document the configured module names and keep the config shape ready for fully dynamic library selection. There is no renderer-module cvar; CL_Init always binds R_GetAPI.
Entity-aware flow-field routing is composed by each game world wrapper from server/sv_routing.c. It is excluded from the shared game-common unity scans because it consumes edict_t, ge, and EDICT_NUM; the algorithm is shared by the current game wrappers, but its dependency boundary is game-owned.
The wrappers also supply entity_is_live_walkable_surface: WC3 resolves live bridge/destructable state, while
SC2 currently has no dynamic walkable-surface state. The shared router cannot read WC3-only edict fields. Startup code in common/main.c uses the neutral declarations in common/server_api.h, including SV_IsActive, instead of importing server state structs.
-
Shared input — one orbit camera, independent control options, cvar aliases, and startup menus
-
Client Architecture — client main loop, input, and scene rendering
-
Network Architecture — loopback/UDP transport and connection handshake
-
UI System Architecture — menu screens, FDF layout, in-game HUD