Skip to content

Latest commit

 

History

History
250 lines (180 loc) · 16.9 KB

File metadata and controls

250 lines (180 loc) · 16.9 KB

Runtime Modules and Cvars

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.

Cvar System Internals

The cvar_t Struct

// 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.

Flags

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.

Console Integration

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

writeconfig Output

// 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 File Loading

Load Order

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 base share/ dir, anchored at the executable (<exe>/share, <exe>/../share, or CWD share). Engine-wide assets (fonts/) live at its top level.
  • fs_homepath — writable $XDG_DATA_HOME/<game>/ on Unix when XDG_DATA_HOME is 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/share rule 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.

Config File Execution

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.

Key Bindings

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.

Modifier keys

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.

Command-Line Arguments

- (dash) Prefix — set cvars immediately

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)

+ (plus) Prefix — queue commands

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

Cursor Ownership

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 ...".

Standard Invocations

# 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 1

Core Cvars

All 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.

Module Boundary

The runtime libraries are built into build/lib/:

  • libshared — math and shared primitives
  • libjass — Warcraft III JASS VM from games/warcraft-3/jass/
  • libsheet — Warcraft III SLK/profile parser from games/warcraft-3/sheet/
  • librenderer — generic renderer sources from renderer/ plus the selected game's renderer hooks
  • libmenu — selected-game UI library; for Warcraft III this is games/warcraft-3/menu/
  • libgame — selected-game server-side game logic; for Warcraft III this is games/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.

Game-Owned World Composition

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.

See Also