GUIs: Event Based Scripting

From Mod Wiki

Overview

The Quake Wars GUIs are event driven.

One of the most important events that occurs is the onCreate event which will be called the first time the GUI is created. Once the GUI has been created onCreate will be called for all windows that has it defined. You do still want to initialize properties as much as possible in the constructor (the properties block) instead of in an onCreate event.

Here's a simple window that uses events. This list in guis/game/vehicles/gdf/mcp.gui draws all the players in the vehicle:

windowDef vPlayerList {
	type list;
	properties {
		float 	fontSize 		= 12;
		float	flags			= immediate( flags ) | WF_AUTO_SIZE_WIDTH | WF_TRUNCATE_TEXT;
		rect 	rect 			= 440, 415, 75, 150;
		color	backColor		= 0,0,0,0;
	}
	// All events must be defined within an events script block.
	events {
		// Define an event handler for the onCreate event for this window.
		onCreate {
			// You often want to initialize a window with this event.
			// In this window we insert two columns when the window is created.
			insertColumn( gui.blankWStr, 0, 0 ); // player class icon
			insertColumn( gui.blankWStr, 590, 1 );	// player name
		}	
	}
	// Create a default timeline that updates the list of players every 250 milliseconds.
	timeLine {
		onTime 250 {
			fillFromEnumerator( "vehiclePlayerList" );
			resetTime( 0 );
		}
	}
}

Valid Script Code

Valid script code within events:

  • if/else blocks may be used.
  • return may be called to terminate the execution early. If a return value is expected on the stack this must be pushed using gui.scriptPush<type>( value ) before calling return.
  • Properties can can only be defined in a properties block, there's no support for local properties.
  • For debugging purposes breakpoint and breakonparse may be used.
  • callSuper may be called and is often used where a template that is included first in the window defines an onCreate event and the window defines its own onCreate after the template has been included. Only the last onCreate event may be called, but calling callSuper() within the event will call the previous definition of onCreate.

Window Naming Convention

The naming convetion for GUI windows, window names are often prefixed by a couple of characters (almost always if the window is defined in a template, the prefixes does not always have to relate to the window type. Here are some of the common ones:

  • lyt - Layout windows.
  • lst - List windows.
  • edt - Edit box.
  • tab - Tab window.
  • tabp - Tab page, child of a tab window.
  • btn - A button.
  • chk - A checkbox.
  • lbl - A text label.
  • rad - Radio window.
  • sld - Slider window.
  • pane - A window pane.

Custom Window Drawing

Many windows do their own drawing instead of or in addition to the drawing done by the gamecode by defining one or more of these events:

  • onPreDraw - Called before the gamecode draws for this window.
  • onPostDraw - Called after the gamecode has drawn for this window.
  • onPostChildDraw - Called after the childrend have drawn their windows.

Here is the order of which a window and it's children are drawn:

  1. onPreDraw is called.
    1. if onPreDraw returns false then skip to 3.
  2. Gamecode does drawing for the window.
  3. onPostDraw is called.
  4. Loop through and draw all child windows.
  5. onPostChildDraw is called.

This example in guis/game/hud/hud.include draws the "You are driving in the wrong direction" to the driver of the MCP.

windowDef mcpWarning {
	properties {
		rect rect = 0, -3, SCREEN_WIDTH, 15;
		// Only visible when driving in the wrong direction.
		float visible = player.vehicleWrongDirection == 1.0f;
		color textColor = COLOR_WHITE;
	}
	events {
		// Define the onPreDraw event.
		onPreDraw {
			// Call the drawLocalizedText
			drawLocalizedText( localize( "guis/vehicles/mcp_direction_warning" ), absoluteRect, textColor, 14, DTF_CENTER | DTF_VCENTER | DTF_SINGLELINE | DTF_DROPSHADOW );

			// onPreDraw events must return a float which will decide if
			// the gamecode should draw for the window.
			gui.scriptPushFloat( false );
		}
	}
}

Many events expect input parameters (onEnterItem for example) or a return value (onPreDraw for example). If an event expects you to pop values (the parameters) or push values on the stack (return value) and you forget to do that or you pop/push the wrong number of times you run the risk of getting getting an error like this:

********************
ERROR: scoreboard: Float Stack Underflow
********************

Property Change Callback

Another event that is used often is onPropertyChanged which is used to execute some script code whenever one or more of the properties specified as parameters for the event occurs.

An example from guis/game/hud/hud.gui:

windowDef parachuteIndicator {
	properties {
		// draw the parachute hint icon at the center of the screen.
		string material = "parachute_hint";
		rect rect = gui.screenCenter.x - 32, gui.screenCenter.y + 32, 64, 64;
		// Start invisible (alpha is 0).
		color backColor = 1,1,1,0;
	}
	events {
		// Whenever the property globals.gameHud.needsParachute changes value, call this event.
		onPropertyChanged "globals.gameHud.needsParachute" {
			if( globals.gameHud.needsParachute ) {
				// Show the hint icon.
				backColor.a = 1;
			} else {
				// Fade out the hint icon.
				backColor.a = transition( backColor.a, 0, 1000 );
			}
		}
	}
}

The needsParachute property is being set by the game scripts.

This is very useful and makes updating the GUIs a lot faster since you only respond to properties changing once when they occur instead of constantly setting properties every frame.

A single event may respond to multiple propereties or you could have multiple onPropertyChanged events for a single window.

onPropertyChanged "globals.weapons.showHeat" "globals.gameHud.weaponReloadTime" {
...

Which is used in guis/game/hud/hud.gui to update the red progress bar shown on top of the ammo bar whenever the weapon is overheating or if you're reloading the weapon.