GUISCRIPT INTRODUCTION
----------------------
$Id: GUIScript_introduction.txt 4883 2007-12-07 17:05:22Z lynxlupodian $

This file is intended as a brief overview for one of two scripting systems
in GemRB - GUIScript. You should read it if you want to hack on GemRB's GUI.


Contents
--------

  - Introduction - GUIScript and GameScript
  - Script execution
  - Typical GUIScript
  - GUIScripter's Workflow
  - Notes

Introduction - GUIScript and GameScript
---------------------------------------

As said above, there are two scripting systems within GemRB - GUIScript
and GameScript. What's the difference between them? 

The first one, GUIScript, concerns mostly with game GUI, i.e. user
interaction, opening and closing windows, pushing of character sheet
buttons, inventory, etc. This functionality was hardwired in the
original games (probably with Lua scripts compiled-in into the main
EXE) and so we need to implement it anew. GUIScript scripts have to be
tailored for each game type, because each game uses more or less
different GUI, string refs, and so on. We program these scripts in
Python.

The second one, GameScript, governs almost all AI and "AI" in game,
effects of opening a trapped chest, annoying an important NPC or
putting together pieces of a puzzle. These scripts are of course
totally story (and hence game) dependent, fortunately they are stored
in game's data files. They are written in special language and
usually compiled in *.bcs files. Some of them are uncompiled (*.bs
files) or even stored in dialog.tlk.

This file concerns with the first type of scripts, GUIScripts.


Script execution
----------------

GUIScripts are stored in gemrb/GUIScripts/<gametype>/. First script to
be run is always named Start.py. Main window with messages, map
display, character portraits, action buttons etc. is named
MessageWindow.py. Other scripts are usually named after *.CHU resource
file they mostly use.

  Example: 
     Planescape: Torment startup script:
       gemrb/GUIScripts/pst/Start.py   
  
     Baldur's Gate 2 inventory:
       gemrb/GUIScripts/bg2/GUIINV.py

GemRB API is provided to the scripts by means of GemRB module. The
module is implemented together with Python interpreter glue in
GUIScript plugin in gemrb/plugins/GUIScript/GUIScript.cpp.

  Example:
    import GemRB
    GemRB.LoadTable ("RACES")

Description of all functions in the huge GemRB module is in
gemrb/docs/en/GUIScript directory, GUIScript.cpp file itself or obtained
by typing help(GemRB) in GemRB console (type ESC to open console). In 
that case the help is dumped to stdout.

All scripts to be run have to define OnLoad() function, which is
called by GemRB each time the script is loaded. Those python files
without OnLoad() function are merely modules imported into another
script, usually MessageWindow.py.

The control between scripts is passed with GemRB.SetNextScript()
function, which causes another script to be loaded. Repeated loading
of a script causes repeated calling of its OnLoad() function.

All scripts automagically import all symbols from file
gemrb/GUIScripts/GUIDefines.py, which contains definition of constants
commonly used in the scripts. Other files in this directory can be
imported too.

  Example:
    import GemRB
    from ie_stats import IE_SAVEVSPOLY
    def OnLoad():
        print IE_GUI_BUTTON_NORMAL, IE_SAVEVSPOLY
        GemRB.SetNextScript("MessageWindow")



Typical GUIScript
-----------------

A typical script's task is to open a window, setting button labels and
callbacks and then return control back to GemRB. The script also
provides a mean to close the window created.

Since most scripts are modules of MessageWindow.py instead of
independent scripts, we will use a simplified version of one of them
for an example


    ## Standard header and boilerplate, put in all the scripts.
  
    # -*-python-*-
    # GemRB - Infinity Engine Emulator
    # Copyright (C) 2003 The GemRB Project
    .....
    # $Id: GUIScript_introduction.txt 4883 2007-12-07 17:05:22Z lynxlupodian $
  
  
    ## Since this is only a module, GemRB and GUIDefines have to be explicitly 
    ## imported
  
    ###################################################
    import GemRB
    from GUIDefines import *
    from GUICommon import CloseOtherWindow
  
    ###################################################
    JournalWindow = None
    LogWindow = None
    QuestsWindow = None
    ## Function called from a callback in MessageWindow.py
  
    ###################################################
    def OpenJournalWindow ():
	global JournalWindow
  
        ## This is a common way of closing another window before opening
	## our own one (here Journal window). If Journal window already
	## exists, it's closed and the control is returned to the caller
	## (this implements toggling of a window)
  
	if CloseOtherWindow (OpenJournalWindow):
	        ## this part is called if we are closing Journal window
  
	        ## Close any opened children windows
  
		if LogWindow: OpenLogWindow()
		if QuestsWindow: OpenQuestsWindow()
  
		## Freezes the GUI
		GemRB.HideGUI ()
  		
		## Free the window resource and delete Journal window
		## from window manager
		GemRB.UnloadWindow(JournalWindow)
		JournalWindow = None
		GemRB.SetVar("OtherWindow", -1)
  		
		## Unfreeze the GUI, redraws screen, reshuffles windows in 
		## window manager
  
		GemRB.UnhideGUI ()
  
		## Return to the caller
		return
  	
	## This part is executed when we did not close Journal window
	## and will open it instead
  
	## Freeze the GUI
	GemRB.HideGUI ()
  
	## Loads CHU resource file. This one contains windows for Journal,
	## Log, and PC/NPC encyclopedia. 
  
	GemRB.LoadWindowPack ("GUIJRNL")
  
	## Load window with ID 0 (Main Journal window) from GUIJRNL.CHU file.
  
	JournalWindow = GemRB.LoadWindow (0)
  
	## Add the new window to window manager. "OtherWindow" is name reserved
	## for non-floating windows in the center of the screen.
  
	GemRB.SetVar("OtherWindow", JournalWindow)
  
	## Now access some buttons in the new window and assign 
	## labels and callbacks to them
  
	## Comment the button processing with the label of the button for
	## easy maintenance
  
	# Quests
  
	## Quest button has ID 0 in GUIJRNL.CHU in window ID 0
	Button = GemRB.GetControl (JournalWindow, 0)
  
	## Set the label for the button to strref 20430 ("Quests")
	GemRB.SetText (JournalWindow, Button, 20430)
  
	## When the button is clicked by mouse, call fn OpenQuestsWindow()
	GemRB.SetEvent (JournalWindow, Button, IE_GUI_BUTTON_ON_PRESS, "OpenQuestsWindow")
  
	## Now some other buttons. Note that "Done" button calls this 
	## function, which causes closing of the Journal window 
  
	# Beasts
	Button = GemRB.GetControl (JournalWindow, 1)
	GemRB.SetText (JournalWindow, Button, 20634)
	GemRB.SetEvent (JournalWindow, Button, IE_GUI_BUTTON_ON_PRESS, "OpenBeastsWindow")
  
	# Done
	Button = GemRB.GetControl (JournalWindow, 3)
	GemRB.SetText (JournalWindow, Button, 20636)
	GemRB.SetEvent (JournalWindow, Button, IE_GUI_BUTTON_ON_PRESS, "OpenJournalWindow")
  
  
	## Unfreeze the GUI, redraw screen and restart window manager
	GemRB.UnhideGUI()


GUIScripter's Workflow
---------------------- 

- Run GemRB and an original game in parallel and notice GUI parts
  missing or badly done in GemRB.

- Divine the CHU file containing the window resource. It can either be
  guessed by comparing names of all CHU files in the game to required
  functionality, from GUIScript already implementing related part of
  the GUI or by viewing all CHU files in CHU/BAM/MOS viewer (DLTCEP,
  NearInfinity) and finding the right looking one.

- Find ID of the window to be implemented in the CHU file, usually
  with DLTCEP or NearInfinity.

- Copy & paste common parts of the script from another one opening
  similar windows.

- Using CHU viewer find control IDs for the controls you need to
  configure in the script and add their setting into the script, again
  copy & pasting from another script. Usually start with magic trinity
  of GetControl, SetText, SetEvent for each button. Use dummy numbers
  for now. Beware, NearInfinity displays wrong IDs for labels. You will
  have to add the label's control ID to "buffer size" << 32 to get the
  right control ID.

- Write down button labels as they are seen in the original game and
  find their strrefs using DLTCEP or NI. Alternatively, you can set
  "Strref On=1" in *.INI of the original games and write down directly
  the strrefs as they are displayed instead of the labels. This
  however does not work with Planescape: Torment.

- Put the strrefs into the SetText() functions in the script.

- Now when the trivial parts are done, the real fun can begin.
  But that's a subject too advanced for this file :-).


Notes
-----

Please try to follow the indentation style of other GUIScript scripts,
in particular:

  - indent each level with 1 TAB character

  - insert space between function name and opening parenthesis in
    function calls and around operators and after the hash sign in 
    text comments

  - insert blank lines between logically separated chunks of code,
    e.g. between setting of different controls

  - insert blank lines after functions and a row of hashes

  - keep naming convention for windows, callback etc.

  - comment your code
