Краткий справочник к WeiDU

Westley Weimer
weimer@cs.berkeley.edu

Переводчик: Алина Соболева (AERIE Team)
alina@aerie.ru




Table of Contents
1 Вкратце о WeiDU

Главная страница WeiDU: http://www.cs.berkeley.edu/~weimer/bgate/. Настоятельно рекомендую вам регулярно скачивать последнюю версию.

примечание переводчика: я пока что перевожу самое, как мне кажется, главное, лирику оставляю на потом.
WeiDU is designed to make it easier to write and distribute modifications to Infinity Engine games. It can load and modify Infinity Engine resources according to instructions you provide. WeiDU is ideal for packaging modifications that include dialogue or that want to be compatible with other modifications.

I'll be honest with you up front: WeiDU is initially harder to use than some of its alternatives. However, most users report that (1) the alternatives are insufficient because they lack features that only WeiDU provides and (2) WeiDU grows on you over time.

You are welcome to use these utilities to make and distribute your own Infinity Engine mods. This utility is covered by the GNU General Public License, but you are also allowed to distribute an unmodified binary copy of WeiDU.EXE (without the source code) with your mod if you like.

I decided to write my own Infinity Engine DLG and TLK utilities because I was unable to get the TeamBG DLG Editor and Mass Converter to work properly. Either they wouldn't parse the strings or they would mangle the text or they would randomly crash ... it was bad all around. Also, they were all GUIs. As a unix weenie I'm in love with command line utilities and as a PL doctoral student I love making little languages and compilers. WeiDU was originally a family of small programs with unimaginative names like DC, DD and TP. The more appealing term "WeiDU" (which rhymes with "IDU", Eye-Dee-You) was coined by Jason Compton and Ghreyfain, noted BGII mod authors.

2 Без паники!

Пошаговое руководство к WeiDU для новичков:
  1. Без паники. Многие из вас - дети эры графических пользовательских интерфейсов (GUI). Но программы, которые работают в командной строке, тоже могут быть вашими друзьями, и, по большому счету, они работают гораздо быстрее, и использовать их, на самом деле, гораздо проще.

  2. Наилучший способ научиться писать код в формате WeiDU - формате D - прочитать код, написанный в формате WeiDU - формате D. Начните с декомпиляции уже существующих игровых файлов DLG, которые вы хорошо знаете, и прочитайте полученные файлы. Сравните то, как они выглядят в WeiDU и то, как они выглядят в других редакторах, с которыми вы, возможно, лучше знакомы (например, Near Infinity or Infinity Explorer).

  3. Наилучший способ научиться писать код в формате WeiDU - формате D - прочитать код, написанный в формате WeiDU - формате D, часть 2. Все больше аддонов к BG2 создаются при помощи WeiDU. Список можно найти здесь: http://www.cs.berkeley.edu/~weimer/bgate/weidumods.html. Они помогут вам понять, как использовать более сложные функции WeiDU, такие как добавление диалогов, script and 2DA patching, and item/spell/creature patching work in a "real-world" setting. Поставьте себе такую задачу и скачайте некоторые из них - поймите КАК ИМЕННО они работают.

  4. Посмотрите некоторые примеры в этом документе. Есть замечательное руководство к WeiDU(написанное Japheth): http://dragon-forge.fwstudios.net/tutorial-weidu.htm (русский перевод: http://alina.aerie.ru/tutorial_rus.htm). Если чувствуете, что здесь для вас все слишком сложно, зайдите сначала туда. Там также объясняется как установить утилиту. Ghreyfain написал туториал "как создать NPC при помощи WeiDU" http://forums.db-forge.com/forgottenwars/index.php?board=13;action=display;threadid=678 (русский перевод вот тут )

  5. Вот здесь находится форум, обсуждающий WeiDU http://forums.fwstudios.net/index.php?act=SF&f=50.
3 D and DLG File Concepts

This section is a gentle introduction to how Infinity Engine DLG files are structured. First, let's use WeiDU to create SCSARLES.D and take a look at the dialogue of Sir Sarles.

You may install WeiDU.exe anywhere on your system. However, I recommend that you put it in your Baldur's Gate 2 installation directory. However, WeiDU will use the Windows Registry to attempt to locate your BG2 game files.

To run the effect described, open up a DOS prompt window and change directories to get to your BGII directory. Then just type in the text in red at the DOS Prompt.

C:\Program Files\Black Isle\BGII - SoA\> weidu SCSARLES.DLG


This will create a text file called SCSARLES.D in the current directory. Open it up with Notepad or Microsoft Word or something. It's just a text file that describes the game dialogue.

It will look something like:
// creator  : c:\bgate\weidu\weidu.asm.exe 
// argument : SCSARLES.DLG
// game     : C:\Program Files\Black Isle\BGII - SoA
// source   : C:\Program Files\Black Isle\BGII - SoA\data\Dialog.bif
// dialog   : C:\Program Files\Black Isle\BGII - SoA\\t{DIALOG.}\ttref{TLK}
// dialogF  : (none)

BEGIN ~SCSARLES~

IF ~NumTimesTalkedTo(0)~ THEN BEGIN 0 // from:
  SAY #28655 /* ~Who is it? Might I ask why you have disturbed my
    meditations? My creative muse must be gently awakened, and your
    stomping about is simply not conducive to this.~ [SARLES02] */
  IF ~~ THEN REPLY #28656 /* ~My apologies. I will leave you to your
    thinking.~ */ GOTO 1
  IF ~~ THEN REPLY #28657 /* ~I apologize, but I have come to request your
    talent on a commissioned artwork.~ */ 
      DO ~SetGlobal("TalkedToSarles","GLOBAL",1)~ GOTO 2
END

IF ~~ THEN BEGIN 1 // from: 0.0
  SAY #28661 /* ~Then I shall forget you were ever here. Actually, it is an
    astoundingly easy thing to do.~ */
  IF ~~ THEN DO ~SetNumTimesTalkedTo(0)~ EXIT
END
Dialogues in Infinity Engine games behave like finite state machines. If you aren't familiar with the concept of a finite state machine, see http://whatis.techtarget.com/definition/0,,sid9_gci213052,00.html or http://www.c3.lanl.gov/mega-math/workbk/machine/mabkgd.html. Each block of the form:
  IF ~Initial Condition~ THEN BEGIN state1
    SAY ~Something~
    IF ~Reply Condition~ THEN REPLY ~Reply Text~ GOTO state2
  END
represents a state (more details below). When the player starts a conversation with an NPC, the game engine scans through all of the states in that NPC's DLG file in a special WEIGHTed order and picks the one with a non-empty and true "Initial Condition". If no state has a non-empty and true "Initial Condition" then you get that "Bob - has nothing to say to you." message. Don't worry about the weighting process for now.

The speaker (in this case, Sir Sarles) then says whatever appears after SAY. The REPLY lines represent responses the PC can say back. If the "Reply Condition" is true, the player is given the option of saying the "Reply Text" and moving to another state in the dialogue (where Sarles will probably say something else).

Remember: SAY is for what the NPC says, REPLY is for what the player says back. If you think carefully, you'll notice that all dialogue in Infinity Engine games is structed in this manner.

Conditions use the same syntax as triggers do in Infinity Engine BCS scripting. You will need to learn Infinity Engine scripting before too long. Strings are delineated by tildes or "" (your choice, but WeiDU uses the tilde by default). After SAY or REPLY or JOURNAL you may give two strings instead of one. The first is used with DIALOG.TLK, the second is used with DIALOGF.TLK (foreign language version for when the main character is female). If you do not give two strings, the one string you gave is used for both.

You may also use raw numbers prefaced with a number sign (like #1234) to specify a strref inside DIALOG.TLK directly. This is useful when modifying existing dialogues (say, the Fate Spirit in ToB) so that you if a foreign user installs your dialogue they will retain all of the foreign versions of the strings you didn't change or add. Normally the string reference numbers are put right after the SAY keyword and the string text is put in comments. The --text command-line option causes string text to be emitted with the string number in comments.

You may also indicate that a sound file (WAV/WAVC) should be associated with a given string by including its up-to-8-letter resource name in [brackets] after the string, as in:
  SAY ~Hello~ [HELLO]
Comments are C/C++ style: everything from // to the end of the line is a comment, as is /* everything in these star-slash things */. Comments are ignored by WeiDU. They are there for your benefit. Example:
  SAY ~Hello~ [HELLO]   // this is a comment        ... way out to here!
  IF /* this is also a comment */ ~~ THEN EXIT
Replies can also contain actions (using the DO keyword) which behave just like Infinity Engine BCS script actions. They can also add Journal entries, end the dialogue or transfer to another speaker.

Examples:
  IF ~~ THEN BEGIN 2 // from: 0.1
This line marks the beginning of state 2 in a dialogue. The comment tells you that it can be reached by the first reply transition from state 0.
  IF ~~ THEN REPLY ~My apologies. I will leave you to your thinking.~ // #28656 
      GOTO 1
This REPLY can always be chosen and involves the spoken text "My apologies...". That text is string reference number 28656. If the PC chooses that reply, it transitions to state 1.

Finally, a transition may also take the form:
  COPY_TRANS filename label
During processing, COPY_TRANS will be replaced by all of the transitions from state "label" of file "filename". The copying takes place before all other D actions.

4 D Dialogue File Format

The D file format is a way of describing Infinity Engine dialogues and modifications to Infinity Engine Dialogues in a portable, easy-to-understand format. It supports foreign language translations and allows you to describe extensions to existing game dialogues without forcing you to describe their content. This allows you to write mods that work with mods written by others.

The D file format is presented here in an extended context-free grammar notation. If you are unfamiliar with CFGs, take a look http://www.wikipedia.com/wiki/Context-free_grammar, http://cs.wpi.edu/~kal/PLT/PLT2.1.2.html or http://www.cs.rochester.edu/users/faculty/nelson/courses/csc_173/grammars/cfg.html. You don't really need to understand a CFG formally, though.

To get a real idea of how they work, use WeiDU to create JAHEIRA.D for yourself and look at it in a text editor. You can also browse the examples and test directories that come with WeiDU.

All of the syntax keywords are given in a UPPERCASE COURIER. All other keywords are symbolic. Notes:
D File   A D file is a text file that contains a number of D Actions. D Files tell WeiDU how to create and modify Infinity Engine DLG files.
is D Action list A D File is a list of D Actions. Typically the first and only one is BEGIN, which defines the content of a new dialogue. Other D Actions can be used to modify existing dialogues.
 
D Action   A D Action tells WeiDU how to create or modify Infinity Engine DLG files.
is BEGIN filename [ nonPausing ] state list BEGIN tells WeiDU that you are creating a new DLG file from scratch. Any existing DLG file with the same name will be overwriten. The new DLG file contains exactly the states in the list. If you nonPausing is a non-zero integer, the game will not "stop time" while the conversation takes place. By default time stops during conversations.
or APPEND filename state list END This tells WeiDU to place the given states at the end of the already-existing dialogue filename.DLG.
or CHAIN entryFilename entryLabel chainText list exitFilename exitLabel This instructs WeiDU to make a long conversation in which the PC can say nothing. This is useful when you want the NPCs to talk among themselves for a long time. It and its friends, INTERJECT and INTERJECT_COPY_TRANS can incredible time-savers when you're writing non-trivial dialogue. See the examples for ideas. CHAIN will only append to existing dialogues. You cannot use CHAIN to create a new DLG.
or INTERJECT entryFilename entryLabel globalVariable chainText list exitFilename exitLabel Behaves like CHAIN except that all of the chainText is additionally guarded by the transition predicate Global("globalVariable","GLOBAL",0) and accompanied by the action SetGlobal("globalVariable","GLOBAL",1). If you pick globalVariable to be unique, this will ensure that the chainText is only ever seen once per game. This is useful for making interjections.
or INTERJECT_COPY_TRANS entryFilename entryLabel globalVariable chainText list This behaves just like INTERJECT except that the exitFilename and exitLabel are not present. Instead, whenever the dialogue would pass out of the chainText it follows a copy of the transitions that were at the state with stateLabel originally. This is convenient for making quick interjections from NPCs that do not actually change the true flow of the conversation. See the transition COPY_TRANS for more information about this idea.
or EXTEND_TOP filename stateLabel list [ #positionNumber ] transition list END This instructs WeiDU to add the transitions in list to the top of the transition list for the specified states in filename.DLG (which must already exist).
If a positionNumber is given, WeiDU to insert the transitions just between already-existing transitions #positionNumber and #positionNumber+1 in the given states for the given file. The first transition is number 1.
or EXTEND_BOTTOM filename stateNumber list [ #positionNumber ] transition list END Behaves just like EXTEND_TOP but adds the transitions to the bottom of the list instead.
or ADD_STATE_TRIGGER filename stateNumber stateTriggerString [ stateNumber list ] This instructs WeiDU to add the stateTriggerString to all of the states with the given stateNumbers in the file filename.DLG (which must already exist). This is handy for adding extra conditions to an existing dialogue state.
or ADD_TRANS_TRIGGER filename stateNumber transTriggerString [ stateNumber list ] This instructs WeiDU to add the transTriggerString to all of the transitions in all of the states with the given stateNumbers in the file filename.DLG (which must already exist). This is often used in conjunction with EXTEND_BOTTOM to make a new branch in an existing state. Use ADD_TRANS_TRIGGER to add the negation of some predicate to all of the existing transitions, then use EXTEND_BOTTOM to add a transition with that predicate to that state.
or REPLACE filename state list END This instructs WeiDU to load filename.DLG and replace some of its states with the new ones described in the state list. All of the states should have numeric stateLabels (e.g., "5" or "67"). A new state with label X will replace the old state number X.
or SET_WEIGHT filename stateLabel #stateWeight This instructcs WeiDU to destructively change the WEIGHT of the given state in filename.DLG (which must exist). This should only be used to patch or workaround existing dialogues. Never use SET_WEIGHT if you can help it.
or REPLACE_SAY filename stateLabel sayString This instructs WeiDU to destructively change the sayString of the given state in filename.DLG (which must exist). This should only be used to patch or workaround existing dialogues. Never use REPLACE_SAY if you can help it.
or REPLACE_STATE_TRIGGER filename stateNumber stateTriggerString [ stateNumber list ] This instructs WeiDU to destructively set the stateTriggerString of all of the states with the given stateNumbers in the file filename.DLG (which must already exist). It should be used with caution.
or REPLACE_TRIGGER_TEXT filename oldText newText This instructs WeiDU to destruveily replace every occurrence of oldText (which may be a regexp) in the stateTriggerStrings and transTriggerStrings of filename.DLG (which must exist). This should only be used to patch or workaround existing dialogues. Never use this if you can help it.
or REPLACE_ACTION_TEXT filename oldText newText This instructs WeiDU to destruveily replace every occurrence of oldText (which may be a regexp) in the stateActionStrings of filename.DLG (which must exist). This should only be used to patch or workaround existing dialogues. Never use this if you can help it.
 
state   In Infinity Engine games, this is the fundamental unit of dialogue.
is IF [ WEIGHT #weightNumber ] stateTriggerString THEN BEGIN stateLabel SAY sayText [ = SayText ... ] transition list END When you start conversing with a creature that uses a DLG file, the Infinity Engine searches through all of the states in that file in order of increasing WEIGHT and selects the first one it finds for which the stateTriggerString is both true and not empty. The creature then says all of the associated sayText. Finally, the transitions are evaluted in bottom-up (i.e., reverse) order. If a transition is found with a transTriggerString that evaluates to True and no replyText, that transition is immediately executed. Otherwise, all of the transitions are presented as options to the PC.
If a stateLabel is an integer it is called a stateNumber. All of the states in the DLG files that come with the original game use stateNumbers. Only D files use symbolic strings for stateLabels.
Including more than one bit of sayText here is often called Multisay.
or APPENDI filename state list END This is legacy syntax that behaves just like the D Action APPEND but is considered a state. Avoid it.
or CHAIN2 entryFilename entryLabel chain2Text list exitFilename exitLabel This is legacy syntax that behaves somewhat like the D Action CHAIN but is considered a state. In addition, chain2Text is slightly different from chainText. Avoid this construction.
 
transition   Transitions determine how dialogue flows from one state to another.
is IF transTriggerString THEN transFeature list transNext If the transTriggerString evaluates to true or is empty, this transition is viable. If it contains no replyText within its transFeature list, it is immediately taken. Otherwise, the replyText is presented as an option to the user. If the transition is taken, any actions in the transFeature list are performed and the dialogue flows to the point indicated by the transNext. transitions are evaluated in "reverse order". That is, the "bottom" or "last" response for a state is checked first. If its transTriggerString evaluates to true and it has no REPLY text, that transition is immediately taken. See SAREV25A state 1 for an example of a state with all kinds of transitions.
or COPY_TRANS filename stateLabel This instructs WeiDU to copy all of the transitions from the state with the given stateLabel in filename.DLG. This copying takes place before all other D Actions.
For example, this is a valid transition list:
IF ~Before()~ THEN GOTO my_state
COPY_TRANS PLAYER1 33
IF ~After()~ THEN EXTERN SOLA 55
 
transFeature   These are features or actions associated with taking a transition.
is REPLY replyText If this transition is taken, the PC says the replyText.
or DO stateActionString If this transition is taken, the stateActionString is executed.
or JOURNAL text If this transition is taken, the text is added to the PC's journal.
or FLAGS integer This allows you to set the features associated with a transition directly using the binary format of DLG files. Do not use this!
 
transNext   This determines where dialogue flows after a transition has been taken.
is GOTO stateLabel The dialogue continues at the state with label stateLabel in the same DLG file as the current state.
or EXTERN filename stateLabel The dialogue continues at the state with label stateLabel in the file filename.DLG.
or EXIT The conversation ends.
 
chainText   This is a rapid shorthand for chaining together many little bits of dialogue when the PC is not saying anything.
is [ IF transTriggerString THEN ] sayText = sayText ...  
followed by If the transTriggerString is true or if it is not present, each sayText is said in order by the current speaker. [ == fileName [ IF transTriggerString THEN ] sayText = sayText ... ] The == (that's two consecutive equal signs) marks the beginning of a new speaker (indicated by fileName). If the transTriggerString is true or if it is not present, this new speaker says all of its sayText in order.
 
text   This represents strings that are shown to the player, rather than strings that the game uses internally for predicates and actions.
is string [ [WAVEFILE] ] The given string is used for both male and female players. The optional [WAVEFILE] is the associated sound.
or string [ [WAVEFILE] ] string [ [WAVEFILE] ] The first string and soudn file are used if the PC is male, the second string and sound file are used if the PC is female. This is useful mainly for international versions of Infinity Engine games.
or #integer The string with reference number #integer from DIALOG.TLK should be used unchanged.
or @integer The last definition of the translation string @integer given in any TRA file should be used.

5 Command Line Options

WeiDU is a command-line utility. GUIs are available, but this document only describes command-line invocation. Use the DOS Shell ("command" or "cmd") to run WeiDU. You control its behavior by passing arguments to it on the command line.

You invoke WeiDU by typing WeiDU and then any number of options and files, as described below.
   
Input File Types:  
FILE.D Compile FILE to a DLG.
FILE.DLG Decompile FILE to a D.
FILE.TLK Use FILE as the main input TLK file for all operations.
FILE.TP or FILE.TP2 Read FILE and ask the user whether to install, reinstall or uninstall its TP2-Components.
FILE.TRA Use FILE as a source of translation strings when processing D files.
FILE.ITM or FILE.EFF or FILE.SPL List all effects in FILE.
 
General Input Options:  
--game X set main game directory to X. WeiDU looks for CHITIN.KEY and DIALOG.TLK in the main game directory. WeiDU will look in the current directory and use the registry to find your game. If this fails, you will need to run WeiDU using the --game switch to define the full path to the BG2 directory. WeiDU will also search for BG1, IWD and PST.
--nogame do not load any default game files
--search X look in X for input files (cumulative)
--tlkin X use X as DIALOG.TLK
--ftlkin X use X as DIALOGF.TLK
--tlkmerge X merge strings from X into loaded DIALOG.TLK
--autotp use DIALOG[F].TLK, read all TP2 files and make a log
--yes answer all TP2 questions with 'Yes'
--uninstall answer all TP2 questions with 'Uninstall'
 
General Output Options:
--out X emit all output files in directory X
--backup X backup files to directory X before overwriting
--tlkout X emit X as new DIALOG.TLK. You must specify this option for new strings in your D or TP2 or TRA files to show up in the game.
--ftlkout X emit X as new DIALOGF.TLK
 
D Options:
--transin X use TRA translation file X (cumulative)
--testtrans test all specified TRA translation files
--noheader do not emit D header comments
--nofrom do not emit D // from: comments
--nocom do not emit ANY D comments
--text emit string text with refs in comments
--dout X name of output D file to emit (cumulative)
--traify X convert D or TP2 file X to use TRAs. Use --dout to specify the name for the newly created TRA-aware file.
--traify# X Use with --traify. Start the TRA file at translation string @X instead of @0.
--transref emit string reference numbers in TRA files
--trans emit coupled D and TRA files when decompiling a DLG.
 
TLK String Options:
--string X display string reference #X (cumulative)
--strfind X display strings that contain X (cumulative, regexp allowed)
--strapp X append string X to DIALOG.TLK (cumulative)
 
BIFF Options:
--textout X put text output in file X
--textapp X append text output to end of file X
--list-biffs enumerate all BIFF files in CHITIN.KEY
--list-files enumerate all resource files in CHITIN.KEY
--biff X enumerate contents of BIFF file X (cumulative)
--biff-type X examine all BIFF resources of extension X ... (cumulative)
--biff-str X ... and list those containing X (cumulative, regexp allowed)
--biff-name X assume matching items have a strref name at offset X
--biff-get X extract resource X from game BIFFs (cumulative, regexp allowed)
 
ARE/ITM/SPL/EFF Options:
--automate X automatically create a TP2 file for resources in folder X
--automate-min X only automate string references above X
--list-eff X list effects in resource X
 
Comparison Options:
--cmp-from X emit WRITE_BYTEs to turn this file ...
--cmp-to X ... into this one
--dcmp-from X emit REPLACEs to turn this DLG file ...
--dcmp-to X ... into this one
--tcmp-from X compare this TRA file (or directory of TRA files)...
--tcmp-to X ... with this one (or this directory)
--tlkcmp-from X emit STRING_SETs to convert this TLK file ...
--tlkcmp-to X ... into this one
 
Log Options:
--log X log output and details to X
--autolog log output and details to WSETUP.DEBUG
 
Help Options:
-help display a terse list of options
--help display a terse list of options

6 Example Uses
7 WeiDU Tutorials

This section includes tutorials on specific parts of WeiDU. Many of them were contributed by users like you.

7.1 Multisay
This tutorial was thoughtfully provided by Jason Compton.

Although a single SAY line can be of any length, for style purposes (particularly in BG2) it is considered good form to break up very large lines into smaller chunks.

One can easily create a series of simple SAY blocks, one doing GOTO to the next, but if there are no special conditions being checked or actions being taken, you can very easily string several lines together.

Let's say you have a scenery NPC teaching a lesson about the Bill of Rights to the US Constitution.
BEGIN TEACHER

IF ~NumTimesTalkedTo(0)~ THEN BEGIN constitution_1
  SAY ~On September 25, 1789, the First Congress of the United States
    therefore proposed to the state legislatures 12 amendments to the
    Constitution that met arguments most frequently advanced against it. The
    first two proposed amendments, which concerned the number of constituents
    for each Representative and the compensation of Congressmen, were not
    ratified. Articles 3 to 12, however, ratified by three- fourths of the
    state legislatures, constitute the first 10 amendments of the
    Constitution, known as the Bill of Rights.~
  IF ~~ THEN EXIT
END 
This is a perfectly valid block of dialogue, but it is extremely long, and would likely scroll out of the text window for players with lower resolution.

Rather than break each sentence up into a new explicit state, we can use Multisay and save a lot of typing. Multisay is invoked with the = (equals) sign, which tells WeiDU that, "the current speaker should say another line here."

Here's how that D state would look with Multisay:
IF ~NumTimesTalkedTo(0)~ THEN BEGIN constitution_1
  SAY ~On September 25, 1789, the First Congress of the United States
  therefore proposed to the state legislatures 12 amendments to the
  Constitution that met arguments most frequently advanced against it.~
      =
  ~The first two proposed amendments, which concerned the number of
  constituents for each Representative and the compensation of Congressmen,
  were not ratified.~
      =
  ~Articles 3 to 12, however, ratified by three-fourths of the state
  legislatures, constitute the first 10 amendments of the Constitution,
  known as the Bill of Rights.~
  IF ~~ THEN EXIT
END
This will create three dialogue states, separated by simple "click to continue" transitions.

And that's Multisay in a nutshell. Note that (as always with WeiDU) the line break and spacing before and after the = are totally optional, and used here only for illustration.
SAY ~One~ = ~Two~ = ~Three~ 
is perfectly valid as well.

You may Multisay inside almsot any state, so you may use it within an APPEND D Action. This is valid:
APPEND J#KLSYJ
  IF ~~ THEN BEGIN Renal1_1
    SAY ~All right, CHARNAME. I can accept that... you are right, there
    are bigger issues to consider.~ 
      = 
    ~But I hope you do understand why I said something, why it would be
    upsetting to have someone so close to me, in a role like that.~
    IF ~~ THEN EXIT
  END                            // end of state Renal1_1
END                             // end of APPEND J#KLSYJ
However, you cannot use Multisay inside REPLACE, because the nature of REPLACE is to change (that is, replace) a single state, while Multisay's nature is to create multiple states.

7.2 CHAIN
This tutorial was thoughtfully provided by Jason Compton.

CHAIN is an extension of the Multisay concept, simply with multiple participants. If you have two NPCs bantering back and forth for a prolonged period of time, and you do not need to do any special condition checks or actions as they babble, it can get very tedious to set up a separate IF/THEN/BEGIN/SAY block for each line.

Imagine a conversation like this: If you wanted to add this witty little banter to your game, you could do it with 11 APPEND blocks for each line, or save a little time doubling up the back-to-back Imoen and Kelsey lines with Multisay inside their APPENDs, so you'd only need 9. In fact, you could get away with 2 APPEND blocks (remember that the states don't have to appear in the order they are spoken, so you could mention all of Kelsey's lines first and then all of Immy's as long as the labels thread up the conversation correctly), one with 5 state declarations and one 4. But there's an even better way, and that's to use CHAIN. It works very much like Multisay. You use = to indicate that the current speaker should speak again, and == (that's two consecutive equal signs) to indicate that a new speaker should take over.

Note that as of WeiDU 82, CHAIN can now define state triggers, perform DO actions, and end with an EXIT or COPY_TRANS. This means that for most simple NPC/NPC banters, where the PC does not have an opportunity to speak, you no longer need to use anything else.

Watch and see how this dialogue works using a CHAIN.
CHAIN
  IF ~Global("KelseyImoenPizza","LOCALS",0)
      InParty("Imoen2")
      See("Imoen2")
      !StateCheck("Imoen2",STATE_SLEEPING)~ THEN BJKLSY pizzachain
  ~Imoen, what do you like on your pizza?~
DO ~SetGlobal("KelseyImoenPizza","LOCALS",1)~
  == IMOEN2J
  ~Oregano.~
  =
  ~Oh, and maybe with a little basil mixed in.~
  == BJKLSY
  ~Well, yeah, but anything else?~
  == IMOEN2J
  ~Sauce is good.~
  == BJKLSY
  ~(laughs) You're not being very helpful, Imoen.~
  == IMOEN2J
  ~Crust. I like crust on my pizza. Cooked crust is better.~
  == BJKLSY
  ~Do you want me to make you this pizza or not?~
  =
  ~It WAS your idea.~
  == IMOEN2J
  ~I can't decide. Never mind, I'm just gonna have yogurt.~
  == BJKLSY
  ~(sigh)~
EXIT 
Note how this dialogue works.

We use the CHAIN statement to define the state trigger (the starting conditions that must be true) and assign it to BJKLSY.DLG. The "pizzachain" label is mostly just for internal reference. Kelsey delivers the first line, then we use == IMOEN2J to allow her to answer, "Oregano." Then, we can use the single = to indicate that the current speaker (Imoen) has two consecutive lines.

Then it's Kelsey's turn to speak, so we use == BJKLSY to tell WeiDU to tell WeiDU to switch to the other speaker (which is Kelsey, since we specified BJKLSY. They banter back and forth for a while, and then when it is Kelsey's turn to have back-to-back lines Do you want me to make you this pizza or not? and It WAS your idea., we separate with a single = to indicate that the current speaker (Kelsey) has two consecutive lines.

After Kelsey's final, exasperated sigh, we use the EXIT command to terminate the CHAIN, and exit the dialogue.

And that's all you need to know to use CHAIN. It saves a tremendous amount of time over setting up individual APPEND blocks, even Multisay blocks, for each NPC.

Advanced CHAINing:

You may include DO actions and conditionals inside chainText, as in:
CHAIN
  IF ~Global("KelseyImoenPizza","LOCALS",0)
      InParty("Imoen2")
      See("Imoen2")
      !StateCheck("Imoen2",STATE_SLEEPING)~ THEN BJKLSY pizzachain
  ~Imoen, what do you like on your pizza?~
DO ~SetGlobal("KelseyImoenPizza","LOCALS",1)~
  == IMOEN2J
    ~Oregano.~
    =
    ~Oh, and maybe with a little basil mixed in.~

  == BJKLSY
    ~Well, yeah, but anything else?~

  == IMOEN2J
    ~Sauce is good.~

    == BJKLSY   IF ~PartyHasItem("pepperoni")~ THEN
      ~Look, we HAVE pepperoni. Why don't I just use that? I'll eat it,
      anyway. If you don't like it, have yogurt instead.~

    == IMOEN2J  IF ~!PartyHasItem("pepperoni")~ THEN
      ~Crust. I like crust on my pizza. Cooked crust is better.~
    == BJKLSY IF ~!PartyHasItem("pepperoni")~ THEN
      ~Do you want me to make you this pizza or not?~
      =
      ~It WAS your idea.~

    == IMOEN2J  IF ~!PartyHasItem("pepperoni")~ THEN
      ~I can't decide. Never mind, I'm just gonna have yogurt.~
    == BJKLSY IF ~!PartyHasItem("pepperoni")~ THEN
      ~(sigh)~
EXIT
In this case, the dialogue changes if the party has the pepperoni item. If it does, Kelsey says the we HAVE pepperoni and then (sigh) and then the dialogue ends. If not, the dialogue works as before. The chainText lines with IFs in them are only spoken if their conditionals are true.

7.3 COPY_TRANS

This tutorial was thoughtfully provided by Jason Compton.

There are some complex branching dialogues in Infinity Engine games that you, as a mod creator, may wish to add to. Consider Baldur's Gate 2 and the "Arrival In Hell" dialogue. There is a brief internal dialogue as the protagonist comes to terms with the fact that he/she is now in Hell, and then all of the companions coded by Bioware have a chance to speak. (PLAYER1.DLG state 25. It will help the rest of this explanation if you go use WeiDU to decompile PLAYER1.DLG into PLAYER1.d, and/or open up PLAYER1.DLG in Near Infinity and look at state 25.)

After the PC's internal voice says You doubt they will be pleased with their present circumstance, when you don't even know why you are here yourself., every Bioware NPC has the opportunity to speak. If you are creating a new NPC and want it to have that full, rich Bioware flavor, you may wish to let your character speak here as well. For that, a simple EXTEND_BOTTOM will do the job.

Here's the example of how Weimer does this with Solaufein:
EXTEND_BOTTOM PLAYER1 25
  IF ~IsValidForPartyDialogue("Sola")
      Global("SolaWelcomeHell","GLOBAL",0)~ THEN
    DO ~SetGlobal("SolaWelcomeHell","GLOBAL",1)~ EXTERN SOLA inHell1
END
This puts a new transition at the bottom of PLAYER1 25 that tells the game to branch to Solaufein's observation about your arrival in Hell, if he is present (IsValid) and if we have not already seen his comment once before (the check for SolaWelcomeHell=0, then setting it to 1. This ensures that this path can only happen once, which is important for a reason I will explain later.) SOLA inHell1, which we will define later, contains Solaufein's comment.

Once Solaufein makes his comment, it would be very thoughtful of us to allow the other Bioware NPCs to have their say as well, as Bioware intended and as the experienced players out there expect. You could simply use the decompiled PLAYER1.D and copy and paste the transition list out. But there are some good reasons not to do that. On a trivial level, it's a big waste of space in your D file.

The most important reason is this: If another mod NPC has come along and done their own EXTEND_BOTTOM, you would have no way of knowing that. By putting the Bioware stock transition list in, you would ensure that only your mod NPC got to have their say. The rest would be silent. So if you were the developer of Solaufein, and Solaufein were installed after Kelsey and Tashia in a game, Kelsey and Tashia would be skipped, because you only copied the Bioware transition list. That's a heavy responsibility.

That's why COPY_TRANS exists. COPY_TRANS pulls the entire transition list from a specified state and makes it the transition list for your new state.

To illustrate, look at SOLA inHell1 :
APPEND SOLA
  IF ~~ THEN BEGIN inHell1
    SAY @2 = @3         // use strings @2 and @3 from translation file
    COPY_TRANS PLAYER1 25
  END
END 
Instead of copying and pasting that huge list of IF "" THEN EXTERN transitions from the PLAYER1.D, we let WeiDU do it for us. COPY_TRANS PLAYER1 25 tells WeiDU to grab the current list of transitions from PLAYER1 state 25, and use it as the transition list for SOLA inHell1. This ensures that Solaufein will be able to properly branch out to Imoen, Aerie, Minsc, and the rest of the gang, as well as grabbing the transitions that may have been added by other NPCs such as Kelsey, Valen, or Tashia.

COPY_TRANS can form all of your new state's transition list, or only part of it. This would be valid, for example:
IF ~~ THEN BEGIN commentary
  SAY ~Hey, I think I might like to run the transition list from TOLGER
    75... or I might want to do something else, if I'm in chapter six!~
  COPY_TRANS TOLGER 75
  IF ~Global("Chapter","GLOBAL",6)~ THEN GOTO chapter6commentary
END
This would make the GOTO commentary2 transition show up at the bottom of the transition stack (below the list copied from TOLGER 75). Remember that transition triggers are read bottom to top, so it would be the first transition evaluated. If you want it to be evaluated after the list of transitions in the COPY_TRANS, put it above. Note, however, that Bioware usually structures its transition lists so that the topmost trigger will always be true (in fact, sometimes it is "True()") so it is somewhat unlikely you would ever want to put a new transition trigger above the COPY_TRANS.

Now, that explanation for why the SolaWelcomeHell variable check is important: if a user accidentally installs the same mod more than once, and it employs COPY_TRANS, the list the second time around will include our new trigger:
IF ~IsValidForPartyDialogue("Sola")
      Global("SolaWelcomeHell","GLOBAL",0)~ THEN
    DO ~SetGlobal("SolaWelcomeHell","GLOBAL",1)~ EXTERN SOLA inHell1
If there was no flag being set to ensure that the transition could only run once, the user would get stuck in a loop. This can and has happened with mods in the wild. The end result would be Solaufein or Kelsey or whomever constantly offering their commentary, over and over again.

Important note: The WeiDU D compiler runs COPY_TRANS before other actions that you might take to affect a transition list within the same D file (like EXTEND_TOP and EXTEND_BOTTOM). This is a good thing.

7.4 INTERJECT
This tutorial was thoughtfully provided by Jason Compton.

Interjections, the little comments party members make, are a great way to spice up a new NPC or a new quest you create. It shows that the characters are paying attention to their game world, and that they have an opinion about what goes on around them.

Through interjections, an NPC can advise a course of action, complain about a decision, force your hand... fun things.

The traditional way to do an interjection is to find a state of a dialogue where another NPC might comment, and use EXTEND_BOTTOM and APPEND in conjunction.

Here's an old-school example:
EXTEND_BOTTOM SAHPR4 7
     IF ~IsValidForPartyDialog("J#Kelsey")~ THEN EXTERN J#KLSYJ KelseySAHPR4
END

APPEND J#KLSYJ
  IF ~~ THEN BEGIN KelseySAHPR4
    SAY ~Urk. Who was the lucky donor?~
    IF ~~ THEN EXTERN SAHPR2 10
  END
END
This works, but it's also more work than it needs to be since the introduction of INTERJECT. (Incidentally, for you WeiDU historians out there, there are two main reasons advanced functions have been added to WeiDU since the first versions of Solaufein and the original CHAIN command: Either Weimer needed them for his own modding goals, or a mod project, usually Kelsey, requested it. INTERJECT came about when Westley finally decided that Solaufein and Valen should comment about quests. On the other hand, COPY_TRANS and INTERJECT_COPY_TRANS were my idea.)

INTERJECT simplifies this process considerably. To run an INTERJECT, you need to know the source state (the line after which you want one or more NPCs to interject), and the destination state (where you want the dialogue tree to go after the interjection.) Typically, but not necessarily, the destination state will be wherever the dialogue was originally planning to go, but if the NPC takes the conversation in a new direction, that may change. We'll stick with the simpler cases for illustration.

INTERJECT is a specialized form of CHAIN. So if you're familiar with CHAIN, this will look familiar.

Consider the dryads in the Irenicus start dungeon. Perhaps Minsc should say something to them. IDRYAD1.DLG state 1 offers a good opportunity.
IF ~~ THEN BEGIN 1 // from:
  SAY #11080 /* ~We are his possessions.~ */
  IF ~~ THEN EXTERN ~IDRYAD2~ 1
END
Minsc is outraged. Here's how he can express it. The idea is that we want Minsc to comment, but for the dialogue to continue just as it would have if he was not there.
INTERJECT IDRYAD1 1 MinscDryad
  == MINSCJ IF ~IsValidForPartyDialog("Minsc")~ THEN 
    ~Boo is outraged that the strange wizard would own these lovely ladies!
    Can Minsc and Boo help you nice girls?~
END IDRYAD2 1
Here's what's going on here.

Invoking INTERJECT requires three arguments: the dialogue name and state we're interjecting into, plus a unique global variable name. This variable will be set from 0 to 1 after the INTERJECT runs, to ensure that it can only happen once. (This is important in case players accidentally install your mod twice, as it could create a looping problem similar to the one described in COPY_TRANS.)

So INTERJECT IDRYAD1 1 MinscDryad tells WeiDU "Put this dialogue after IDRYAD1 state 1. This dialogue will run if MinscDryad is 0. After it runs, we will set MinscDryad to 1."

Then we need to define who is speaking. == (that's two consecutive equal signs) is CHAIN-style notation for a new speaker, and MINSCJ is the proper dialogue to use for Minsc's "joined-party" commentary. If Minsc is in the party and valid for dialogue, he will say his line. After that, we transition to IDRYAD2 state 1 (END IDRYAD2 1), which is where the dialogue was heading in the first place. We can make this more complicated and let the dryad reply to his interruption before proceeding.
INTERJECT IDRYAD1 1 MinscDryad
  == MINSCJ     IF ~IsValidForPartyDialog("Minsc")~ THEN 
    ~Boo is outraged that the strange wizard would own these lovely ladies!
    Can Minsc and Boo help you nice girls?~
  == IDRYAD1    IF ~IsValidForPartyDialog("Minsc")~ THEN 
    ~Large mortal, we are having a dramatic scene. Please do not
    interrupt.~
END IDRYAD2 1
Note that we repeat the
IF ~IsValidForPartyDialog("Minsc")~ THEN
check for IDRYAD1. That is to ensure that she only says that line if Minsc is there, and by extension has already made his little comment. So now, Minsc interrupts, Dryad #1 scolds him, and then we proceed to the second dryad's line.

One general piece of advice: while it's not necessary to pick a state that has only a single transition to another state, unless you're willing to experiment (or you're intentionally trying to remove player choices from the equation, by making the NPC say something that forces immediate action, for instance), don't INTERJECT into a state that has player options (REPLYs).

7.5 INTERJECT_COPY_TRANS
This tutorial was thoughtfully provided by Jason Compton.

INTERJECT is good for creating interjections where none already exist. However, many of the really good opportunities for interjections in game dialogue already have interjections in them. If you use standard INTERJECT, chances are you'll skip right over them.

Consider TOLGER.DLG state 75. After Tolgerias lays down the law by saying This is a sensitive matter, and I cannot tell all to every curious soul. I must have your commitment that you agree to the task, four NPCs (Edwin, Jaheira, Yoshimo, Korgan) will tell you what they think of that rotten arrangement. What's more, Bioware structured the dialogue transitions so that they all can get their comment in, if all four of them are in the party.

However, using a standard INTERJECT for a new NPC line would skip over those four comments, which is rather impolite. INTERJECT_COPY_TRANS works much like regular INTERJECT, but instead of defining a state to transition to after END, WeiDU will COPY_TRANS the transition list from the state you are INTERJECTing into.

This is not as confusing as it sounds. Watch as hypothetical new NPC Aqualung responds to Tolgerias's terms:
INTERJECT_COPY_TRANS TOLGER 75 AquaTolger
  == AQUALUNJ   IF ~IsValidForPartyDialogue("Aqualung")~ THEN 
    ~Hey, that's a really crummy offer! Where did those little girls go? I
    could be sitting on a park bench, I don't need this aggravation! Who
    are you, anyway?~
  == TOLGER     IF ~IsValidForPartyDialogue("Aqualung")~ THEN 
    ~You poor old sod, you see it's only me. Now, did anyone else have a
    smart remark they wanted to make?~
END
So, if Aqualung is around, we'll hear from him and then Tolgerias will respond to him. After that, the game will look for the presence of Edwin, Jaheira, Yoshimo, and Korgan and we'll get their responses to Tolgerias as well.

Hint: INTERJECT_COPY_TRANS is fine to use even if there were no other interjections in the source state, i.e. if there's just a single IF "" THEN GOTO blah. That is, as long as you plan to proceed to the original destination. It saves you the trouble of having to look up and input the destination.

7.6 state WEIGHTs

stateTriggerStrings, the conditions that determine what state should be used for the beginning of a dialogue, may have WEIGHTs. These WEIGHTs are used by the Infinity Engine to choose which state to pick if multiple state triggers evaluate to "true". [ In reality, the WEIGHTs are just the offsets within the state trigger table in the DLG file, but this detail is not important unless you are writing your own tool. ] WEIGHTs only make sense for stateTriggerStrings that are not empty.

If multiple stateTriggerStrings evaluate to true, the Infinity Engine will pick the state with the lowest WEIGHT. Usually the weighting follows the order of state declaration in the D file. That is, the first state mentioned has the lowest weight (i.e., will be picked first in case of a tie) and the last state mentioned has the highest weight (i.e., will be picked last in case of a tie). However, you may include an explicit WEIGHT directive to change things around. For example, consider this D file:
BEGIN foozle
  IF ~True()~ THEN BEGIN a SAY ~Jason~ END
  IF ~True()~ THEN BEGIN b SAY ~yada~ END
  IF ~True()~ THEN BEGIN c SAY ~Compton~ END
  IF ~True()~ THEN BEGIN d SAY ~kelsey~ END
If you talk to foozle, it will always say Jason. However, you may explicitly alter the weights so that the third state is picked first, as in:
BEGIN foozle
  IF WEIGHT #10 ~True()~ THEN BEGIN a SAY ~Jason~ END
  IF            ~True()~ THEN BEGIN b SAY ~yada~ END
  IF WEIGHT #2  ~True()~ THEN BEGIN c SAY ~Compton~ END
  IF            ~True()~ THEN BEGIN d SAY ~kelsey~ END
With this D file, foozle will always say Compton. All states with WEIGHT directives come before all states without them. States without WEIGHT directives are ranked in order of appearance. So the state order for foozle.DLG is c-a-b-d. Yes, this is complicated.

Strong Style Suggestion: do not use the WEIGHT directive in your hand-made D files. Just use the implicit ordering.

The WEIGHT directive was introduced to facilitate handling of Bioware-created DLG files (e.g., BJAHEIR.DLG) that include tricky weighting. Only states with non-empty triggers are given implicit weights. If you create a D file from a DLG that features non-trivial weighting, WeiDU will emit comments like this:
IF WEIGHT #8 /* Triggers after states #: 11 12 24 25 26 36 58 even though
                they appear after this state */
  ~True()~ THEN BEGIN 10 // from:
    SAY #52190 /* ~Please do not interrupt our thoughts. We must prepare
      carefully if we are to see a weakness in the illithid web. ~ */
    IF ~~ THEN EXIT
END
to remind you that the order is not what you think.

All non-empty state triggers in DLG files are given weights counting up from 0 to the maximum number of state triggers in the DLG file. You may use any number you like (even a negative one): WeiDU will simply sort them. ADD_STATE_TRIGGERdoes not change the weight associated with that trigger. APPEND can be used to give a non-trivial weight to a state, as in:
APPEND BJAHEIR
  IF WEIGHT #-999 ~MyCondition()~ THEN BEGIN mystate SAY ~My Stuff~ END 
END
Since BJAHEIR will have implicit WEIGHTs in the range from #0 to about #50, this causes mystate to have priority over all states that already exist in BJAHEIR. Without such drastic action, APPENDed states will have use the implicit ordering, and will thus have the lowest priority (because they appear at the end of the file). Multisay and CHAIN also append states, but since they always append states with empty stateTriggerStrings, WEIGHTs are not relevant.

Consider the following example:
BEGIN foozle
  IF WEIGHT #10 ~True()~ THEN BEGIN a SAY ~Jason~ END
  IF            ~~ THEN BEGIN b SAY ~yada~ END
  IF WEIGHT #2  ~True()~ THEN BEGIN c SAY ~Compton~ END
  IF            ~True()~ THEN BEGIN d SAY ~kelsey~ END
  ADD_STATE_TRIGGER foozle 1 /* state b */ ~MyCondition()~
The resulting foozle dialogue will still have the c-a-b-d weighting order.

Here's another example:
BEGIN foozle
  IF            ~True()~ THEN BEGIN a SAY ~Jason~ END
  IF            ~~ THEN BEGIN b SAY ~yada~ END
  IF            ~True()~ THEN BEGIN c SAY ~Compton~ END
  IF            ~True()~ THEN BEGIN d SAY ~kelsey~ END
  ADD_STATE_TRIGGER foozle 1 /* state b */ ~MyCondition()~
The resulting foozle dialogue will have the (expected) a-b-c-d ordering.

However, consider this evil example:
  //////
  // foozle.DLG contents, assume it has already been created and is 
  // sitting on your hard drive somewhere
  // IF            ~True()~ THEN BEGIN a SAY ~Jason~ END
  // IF            ~~ THEN BEGIN b SAY ~yada~ END
  // IF            ~True()~ THEN BEGIN c SAY ~Compton~ END
  // IF            ~True()~ THEN BEGIN d SAY ~kelsey~ END
  //////

  // new D file
  ADD_STATE_TRIGGER foozle 1 /* state b */ ~MyCondition()~
This will update foozle and the resulting order will be a-c-d-b (because when foozle.DLG was loaded from the disk, a c and d were given weights but b was not (because it had an empty trigger)). Thus, you should avoid using ADD_STATE_TRIGGER on states with empty triggers unless you know what you are doing.

7.7 TRA Translation Files

If you are writing a mod and you would like to make it easier to translate it into another language you can use "translation files" (much like BGII itself uses DIALOG.TLK) to separate your dialogue structure and content. A translation file basically lists the string texts in order. For example,

C:\Program Files\Black Isle\BGII - SoA\> WeiDU --trans SCSARLES.DLG


This creates scsarles.D and scsarles.tra. scsarles.D now contains:
IF ~NumTimesTalkedTo(0)~ THEN BEGIN 0 // from:
  SAY @1 /* ~Who is it? Might I ask why you have disturbed my meditations?
    My creative muse must be gently awakened, and your stomping about is
    simply not conducive to this.~ [SARLES02] #28655 */
  IF ~~ THEN REPLY @2 
    /* ~My apologies. I will leave you to your thinking.~ #28656 */ GOTO 1
  IF ~~ THEN REPLY @3 /* ~I apologize, but I have come to request your
  talent on a commissioned artwork.~ #28657 */ 
    DO ~SetGlobal("TalkedToSarles","GLOBAL",1)~ GOTO 2
END 
Note that all of the strings have been replaced by @number and the texts have been put in comments.

The translation file scsarles.tra contains all of those strings:
// SCSARLES translation file
@1   = ~Who is it? Might I ask why you have disturbed my meditations? My creative muse must be gently awakened, and your stomping about is simply not conducive to this.~ [SARLES02]
@2   = ~My apologies. I will leave you to your thinking.~
@3   = ~I apologize, but I have come to request your talent on a commissioned artwork.~ 
You may then ask someone who speaks another language to write a new translation file by translating every string in scsarles.tra. This prevents the string text and the structure from getting out of sync and simplifies translation; non-technical players can translate raw text files easily.

When compiling a D file that contains @number translation references you must supply (at least) one translation file. For example, you might say:

C:\Program Files\Black Isle\BGII - SoA\> WeiDU SCSARLES.D italian.tra


You may specify multiple translation files. The last one to define a string wins. This is useful if one language is more up to date than the others. In this example:

C:\Program Files\Black Isle\BGII - SoA\> WeiDU SCSARLES.D english.tra italian.tra


Strings will be taken from the italian translation whenever possible, but if they are not available it will fall back on the english versions.

You may use WeiDU to check and make sure that translations are up to date. WeiDU will automatically generate a text file listing all of the strings that are present in one translation (usually your native one) that are missing in another. You can then send this file to your translators so that they know what to do. This example command compares all of the TRA files in the american and french directories and creates a file called MISSING.

C:\Program Files\Black Isle\BGII - SoA\> weidu --tcmp-from american --tcmp-to french --textout MISSING


7.8 Converting a "hard-coded" D to a D/TRA pair

This tutorial was thoughtfully provided by Jason Compton.

D and TP2 files allow programmers to describe text either literally:
// Greeting.d
SAY ~Hello.~
or with a companion TRA (translation) file that supports multiple languages:
// Greeting.d
SAY @1

// Greeting.tra
@1 = ~Hello.~
The idea being that one can make a French version of Greeting.tra which contains
// French-Greeting.tra
@1 = ~Bonjour.~
However, some WeiDU users, for reasons of convenience or simply never anticipating the opportunity to translate, may have originally chosen the "hard-coded" approach but now regret that decision. --traify will break out all the text in SAY, REPLY, and JOURNAL entries into translation-ready format. --traify may also be used on TP2 files.

To turn the hard-coded D file FWKI.d into a new D/TRA combo, use --traify and --dout to specify the input and output filenames, respectively:

C:\Program Files\Black Isle\BGII - SoA\> weidu --traify fwki.d --dout fwki-new.d


After a brief pause, fwki-new.d and fwki-new.tra will be created.

The --traify process turns fwki.d's
APPEND J#KLSYJ
  IF ~~ THEN BEGIN KelseySAHPR4
    SAY ~Urk. Who was the lucky donor?~
    IF ~~ THEN EXTERN SAHPR2 10
  END
END 
into fwki-new.d's
APPEND J#KLSYJ
  IF ~~ THEN BEGIN KelseySAHPR4
    SAY @0
    IF ~~ THEN EXTERN SAHPR2 10
  END
END 
and in the newly created fwki-new.tra, you will find
@0    = ~Urk. Who was the lucky donor?~
and this dialogue file is now ready for translation.

--traify works from the top of the D down, starting at @0. It will NOT skip over any existing @x translation references it finds, so if your D contains any translation support at all, it is best to use --traify# as well.

Because standard --traify starts at @0 and is unaware of any any existing @x entries in the D, if you have begun to convert a D to a D/TRA pair by hand, you may have @x entries that clash with --traify's results.

In other words, if you already have a state that says
IF ~~ THEN BEGIN blah
  SAY @0
  IF ~~ THEN EXIT
END
--traify will not skip @0 automatically, you will have two locations where @0 is used but you intended to use two different strings, and this will be bad.

To avoid this problem, add the --traify# argument to specify the starting number (rather than 0) for new @x entries.

C:\Program Files\Black Isle\BGII - SoA\> weidu --traify fwki.d --traify# 1000 --dout fwki-1000.d


will create new @x references in fwki-1000.tra that begin at @1000, instead of @0. --traify# may also be used on TP2 files.

8 Module Packaging: TP2 Files

At some point you will be done with your mod (a collection of CRE, ITM, D, etc., files) and you will want to package it up so that other users can install it (and then perhaps uninstall it later) easily. WeiDU can handle this task for you (and you may freely distribute WeiDU.exe with your module).

A TP2 describes how to install components of your module. WeiDU will read the file, ask the user questions, and then perform the installation. Uninstallation and upgrading are also handled.

See the file examples/mymod.tp2 for a commented example of how this all works.

Here is the context-free grammar syntax for the TP2 file format:
TP2 File   A TP2 file is a text file that contains a number of mod Components. TP2 Files tell WeiDU how to install various parts of your mod on an end-user's computer.
is BACKUP directoryName AUTHOR emailAddress [ AUTO_TRA path ] Language list [ ALLOW_MISSING file list ] Component list A TP2 File is basically a prologue and then a list of Components. The BACKUP declaration tells WeiDU where to put backed-up versions of files that would be overwritten so that they can be uninstalled later. The AUTHOR directive gives an email address for users to send bugs to if there are problems during the installation. The optional AUTO_TRA directive is used with the COMPILE TP2 Action. Languages are the various languages in which your mod is available. The ALLOW_MISSING directive allows you to specify files that can be missing (when you try to copy them or refernece them from D files). Empty versions of those files will be created on demand. Finally, the Components make up the actual meat of your mod. Different Components can be installed or uninstalled separately, but all of the parts within a Component are treated as a unit.
 
Language   A Language declaration tells WeiDU where to find TRA files.
is LANGUAGE languageName languageDirectory defaultLanguageTRA list The languageName is the name of the language as it is presented to the user. "American English" and "Traducciуn al Espaсol" are examples. The languageDirectory is the name of the subdirectory in which you have stored the TRA files for that language. Examples include "american" and "spanish". Finally, all of the TRA files in the defaultLanguageTRA list are loaded as soon as the user selects a language.
 
Component   A Component is a contiguous group of files and actions that a user can install, uninstall or upgrade.
is BEGIN componentName TP2 Action list Basically, if componentName is "Foo", the user will be asked: "Do you want to install Foo?". If so, all of the associated TP2 Actions are performed. If not, they are skipped.
 
TP2 Action   A TP2 Action tells WeiDU how to install a component. This usually involves copying files and writing in new string references.
is COPY fromFile toFile ... patch list when list You may specify as many fromFile-toFile pairs as you like. Each fromFile is copied to its associated toFile. All of the patches are applied. If there are any when conditions and any of them are false, the copy does not happen. A typical example is COPY "mymod/sword.itm" "override/sword.itm".
or COPY_EXISTING fromFile toFile ... patch list when list Behaves like COPY except that the fromFiles are drawn from the game BIFFs or override directory. This is useful for making changes to files that other mods may have changed as well.
or COPY_EXISTING_REGEXP fromFileRegexp toDir ... patch list when list Behaves like COPY_EXISTING except that fromFileRegexp may contain regexp regular exprsesions. All matching files in the game BIFFs will be copied to the directory specified by toDir.
or COMPILE dFile list [ USING traFile list ] First, this loads all of the traFiles presented. If any of their paths contain %s, the %s is replaced with the languageDirectory of from the Language the user selected. If you specified AUTO_TRA mymod/%s above, WeiDU will also attempt to load mymod/languageDirectory/dFile.tra for every dFile in the list. Once all of the TRA files are loaded, the D files are compiled. Any DLGs they create or modify are placed in the override directory.
or MKDIR dirName list Instructs WeiDU to create all of the directories in the list.
or APPEND filename newText when list If there are no when conditions or they are all true, the ASCII text newText is appended to the existing file filename (which is read from the game BIFFs or the override folder).
or APPEND_COL filename newText when list If there are no when conditions or they are all true, the string newText is appended column-wise to the existing file filename. If filename was: A B C D E F X Y Z P Q R and newText was "0 1 2 3", the result would be: A B C 0 D E F 1 X Y Z 2 P Q R 3 You must have the same number of whitespace-separated words in newText as there are columns in filename.
or EXTEND_TOP existingBCS newBCS patch list Loads existingBCS, prepends all of newBCS to the top of it, applies all of the patches, and then copies it to the override folder.
or EXTEND_BOTTOM existingBCS newBCS patch list As EXTEND_TOP, but the newBCS file is put at the bottom of the existingBCS file.
or EXTEND_TOP_REGEXP existingBCSregexp newBCS patch list As EXTEND_TOP, but the newBCS file is put at the bottom of the every BCS file that matches the regexp existingBCSregexp.
or EXTEND_BOTTOM_REGEXP existingBCSregexp newBCS patch list See EXTEND_TOP_REGEXP.
or ACTION_IF Predicate THEN BEGIN TP2 Action list END [ ELSE BEGIN TP2 Actoin list END ] If Predicate evaluates to true, the TP2 Actions in the THEN-branch are executed. Otherwise, if an ELSE-branch is present, its commands are executed. Otherwise nothing happens.
or AT_EXIT commandToRun Whenever this component is installed, commandToRun is executed by the underlying operating system. If you want to do something that WeiDU doesn't handle, like extracting WAVs from an MP3, make a batch file and run it from here.
or AT_INTERACTIVE_EXIT commandToRun As AT_EXIT, but the command is only executed if the user specifically asked for the component to be installed or upgraded. The most common use is: AT_INTERACTIVE_EXIT ~VIEW mymod\README.txt~ This causes your README file to be displayed using a system appropriate viewer.
or AT_UNINSTALL commandToRun Whenever this component is removed, commandToRun is executed.
or AT_INTERACTIVE_UNINSTALL commandToRun Whenever the user specifically asks for this component to be removed, commandToRun is executed.
or ADD_KIT internalKitName manyComplexArguments This command allows you to add new kits to the BGII. See the example file mymod.tp2 for information on how to do this.
or ADD_MUSIC internalMusicName newMUSFile No documentation yet!
or STRING_SET indexOrString newValue This command replaces the given string in the user's TLK file with newValue. Do not use this command.
or REQUIRE_FILE filename warningString If filename does not exist, warningString is displayed and this component cannot be installed.
or FORBID_FILE filename warningString If filename does exist, warningString is displayed and this component cannot be installed.
or FAIL warningString If this TP2 Action is execution, warningString is displayed and the component fails to install.
or PRINT displayString The string DisplayString is echoed to the user. Useful for debugging or status reports.
 
patch   A patch tells WeiDU how to modify a file.
is SAY offset string The string-ref associated with string is written to offset. This is commonly used to change the name or description of a spell or item.
or REPLACE regexp text All occurences of regexp in the file are replaced with the ASCII printing of the string reference for text. So if regexp is "FRED" and the text ends up being strref #1234, "FRED" will be replaced with "1234". This is usually used to replace string references in BCS files (where they are stored textually). Put a command like DisplayString(Myself,99999) in your BCS file and use something like REPLACE 99999 "Hello, World".
or REPLACE_TEXTUALLY string1 string2 All occurences of string1 in the file are replaced with string2. Variable substitution (which affects kit and music names) is performed on string2.
or WRITE_BYTE offset value The 8-bit byte value is written to the given offset.
or WRITE_SHORT offset value The 16-bit short value is written to the given offset.
or WRITE_LONG offset value The 32-bit short value is written to the given offset.
or WRITE_ASCII offset ascString The ASCII ascString is written to the file starting at offset. The terminating NULL is not written, so embed one in the string yourself if you want to write one.
or REPLACE_BCS_BLOCK oldFile newFile If the current file is a BCS file, the segment of it corresponding to oldFile is replaced with the contents of newFile.
or INSERT_BYTES offset howMany The file will be expanded at the given offset with howMany bytes worth of zeroes.
or DELETE_BYTES offset howMany The file will shrink as howMany bytes are deleted starting at the given offset.
 
when   A when clause gives you local control over when a COPY, COPY_EXISTING or APPEND_COL happens. If the COPY or COPY_EXISTING contains multiple files, each one is checked against the when clauses separately.
is IF_SIZE_IS fileSize True if the input file size is fileSize.
or IF regexp True if the input file contains regexp.
or UNLESS regexp False if the input file contains regexp.
 
Predicate   A Predicate allows you to conditionally execute TP2 Actions using an ACTION_IF.
is FILE_EXISTS filename True if the file exists and has non-zero size.
or FILE_SIZE filename fileSize True if the file size matches exactly.
or FILE_CONTAINS filename regexp True if the file contains the regular expression.
or Predicate AND Predicate  
or Predicate OR Predicate  
or NOT Predicate  
or ( Predicate )  
 
offset   An offset is a location within a file.
is integer An absolute location. You may format your numbers in decimal, hex, octal or binary. Use 0x for hex, 0o for octal and 0b for binary.
or NAME1 Unidentified general name ("Battle Axe")
or NAME2 Identified general name ("K'logarath +4")
or UNIDENTIFIED_DESC ("The hand axe or throwing axe is also known as a hatchet ...")
or IDENTIFIED_DESC ("Clans have gone to war to possess K'log...")
or BIO NPC Biography
or ... Almost everything in SNDSLOT.IDS or SOUNDOFF.IDS works as well.

9 WeiDU TP2 Tutorials

9.1 COPY_EXISTING_REGEXP
This tutorial was thoughtfully provided by Japheth.

The purpose of COPY_EXISTING and COPY_EXISTING_REGEXP is to patch a file for patching. It will grab the file out of the BIFFs, or if an override version exists, it will grab it out of the override folder.

COPY_EXISTING_REGEXP, EXTEND_BOTTOM_REGEXP and EXTEND_TOP_REGEXP can be potentially powerful actions if you need to make some changes to a certain set of files all in one shot.

Consider this example for COPY_EXISTING_REGEXP:

In my mod I want to make it so that all 1-handed swords only do D6 damage, rather than the varying damages they do in SOA. To do this with the "normal" COPY_EXISTING, I would have to write out all the one handed swords like this:
COPY_EXISTING ~sw1h01.itm~ ~override/sw1h01.itm~
              ~sw1h02.itm~ ~override/sw1h02.itm~
              etc...
That could take a lot of time to do. Using COPY_EXISTING_REGEXP I can minimize writing 70+ lines of code into 3 lines of code. Take a look at this:
COPY_EXISTING_REGEXP ~sw1h..[^abc].*itm~ ~override~
  WRITE_LONG 0x88 "6"
  WRITE_LONG 0x8a "1" 
I'll explain how the regexp wildcards in the above example work: I want to avoid copying over the files sw1h54a.itm, sw1h54b.itm and sw1h54c.itm because they are the three components that make up the Equalizer, and because of that, the offsets of 0x88 and 0x8a won't work because they don't exist in those items.

To find which offset to WRITE_LONG at I opened up Near Infinity, (found at http://www.idi.ntnu.no/~joh/ni/) and looked up the offset with it. Fortunately for us, the offset is the same in all items, so we can make the changes in one shot. WRITE_LONG 0x88 "6" is telling WeiDU to write the value of 6 at offset 88 hex. Similarly, WRITE_LONG 0x8a "1" is telling WeiDU to write the value 1 at offset 8a hex.

The 6 is the dice size and the 1 is the number of dice.

9.2 EXTEND_TOP_REGEXP
This tutorial was thoughtfully provided by Japheth.

Let's take this situation that I was in:

I am making a Hirelings mod, and in order to get non party NPC's to transition from area to area, I needed to make use of a combination of MakeGlobal(), InMyArea(O:Object*) and MoveGlobalObject(O:Object*,O:Target*).

I originally put the script that moves them from area to area into baldur.bcs (this script is constantly running in the game), but for some strange reason, the cre's wouldn't move to smaller areas, only the large "main" areas. (Like The Docks, Temple District, Slums, etc.) So I figured, "Huh, guess I'm going to have to put the script in every area script". (Area scripts are scripts that are assigned to areas that are ran while you are in the area. Pretty obvious right?)

Now, writing the code to EXTEND_TOP this little script into every area script would of taken quite a while, and well, rather than do that, I humbly requested Wes to implement the regexp feature of EXTEND_TOP and EXTEND_BOTTOM, and he obliged. (My penance for that request is writing the docs you are reading right now.)

So now with Wes' help, I could EXTEND_TOP to every area in 1 fell swoop. If you were to open up NI and expand the BCS tree, you would see a whole crapload of scripts that begin with the prefix AR. These are the area scripts I mentioned before. They go from AR0014 to AR6400. So now, using regexp, here is how you would extend the script to the top of every area script:
EXTEND_TOP_REGEXP ~ar[0-6].*bcs~ ~pathtoscript/patch.bcs~
So there we are, instead of 100+ lines of code, I minimized it to 7 lines. Now, if you are understanding regexp at all, you probably are going ``Hey wait, why didn't you just go EXTEND_TOP_REGEXP "ar.*bcs" "pathtoscript/patch.bcs" ?'' Well, infact, I did do that at first, but I forgot to account that there are other ``normal'' scripts that begin with AR. So, I had to write it so that the regexp had the number after the initial AR so that WeiDU would know only to patch the script to area files.

10 Module Distribution: Setup-MyMod.exe

If you rename WeiDU.EXE to something of the form Setup-MyMod.exe, it will behave as if the following arguments were present: Thus, to distribute your mod, rename WeiDU.EXE to Setup-MyMod.EXE (or whatever), put Setup-MyMod.TP2 file in the same directory and go! Typically mods are distributed a ZIP files or self-extracting archives that put the EXE, TP2 and module data files in the main BGII directory.

11 Regular Expressions

A regular expression or regexp is "somewhat" like a DOS wildcard but not quite. The big difference is that if you would say * in DOS you say .* in regexp-land. Here's a definition:

The syntax for regular expressions is the same as in Gnu Emacs. The special characters are:
$^.*+?[]'"
The following constructs are recognized:
   .      matches any character except newline
   *      (postfix) matches the previous expression zero, one or several times
   +      (postfix) matches the previous expression one or several times
   ?      (postfix) matches the previous expression once or not at all
   [..]   character set; ranges are denoted with -, as in [a-z];
          an initial ^, as in [^0-9], complements the set
   ^      matches at beginning of line
   $      matches at end of line
   \|     (infix) alternative between two expressions
   \(..\) grouping and naming of the enclosed expression
   \1     the text matched by the first \(...\) expression 
          (\2 for the second expression, etc)
   \b     matches word boundaries
   \      quotes special characters. 
   '      interpret the characters inside '' literally
   "      interpret the characters inside "" literally
So spe.* matches "sper01.itm" and "sper.eff" and "special".

Hopefully this is understandable to most people. If you're still scratching your head in regards to how this works, there is a nice tutorial at http://www.devshed.com/Server_Side/Administration/RegExp/page1.html.

12 Common File Formats

This section briefly explains some common file formats. The definitive reference is http://www.teambg.com/iesdp/.
13 The Source Code
The source code to WeiDU is available under the GNU General Public License, as detailed in the file COPYING. If for some reason you are unable to obtain a copy of the GPL, merely announce that fact on some public forum and your mailbox will be full of copies of it for life. It's a great way to meet new people.

Since this is the world of Windows, I distribute a pre-compiled binary. WeiDU is written in OCaml, a function programming language that includes automatic memory management, higher-order functions and efficient native code generation. If these terms mean nothing to you, you probably won't be able to modify the source code.

However, if you do want to modify the source and then recompile WeiDU, it's quite easy. Make sure that you have OCaml 3.04 (or newer), Perl 5.6, make and (optionally) cl (Microsoft Visual C Compiler).

Edit Makefile and pick your configuration. If you're not x86/windows/cygwin or x86/linux, you'll have to do some tweaking. Then just type make clean and then make. Presto, you've recompiled it.

14 Special Thanks
I would like to thank the fine folks at the Infinity Engine File Format Hacking Project, without which this would not have been possible: http://www.teambg.com/iesdp/.

In addition, I make heavy use of Near Infinity for general IE mods: http://www.idi.ntnu.no/~joh/ni/.

The Infinity Engine Editor Pro is very good at changing ITMs and SPLs. Thanks to everyone in BG-dom: http://www.teambg.com/?page=press/news/index.

Special thanks to my main bug-finders *cough* I mean beta-testers: Special thanks to Greg Henry for being the first person to mention WeiDU to me in a face-to-face conversation. I was quite impressed. Jason Compton is the first person to mention WeiDU to me in a telephone conversation.

15 Index of Terms

Index
  • 2DA, 12

  • ACTION_IF, 8
  • ADD_KIT, 8
  • ADD_MUSIC, 8
  • ADD_STATE_TRIGGER, 4
  • ADD_TRANS_TRIGGER, 4
  • ALLOW_MISSING, 8
  • AND, 8
  • APPEND, 4
  • APPENDI, 4
  • AT_EXIT, 8
  • AT_INTERACTIVE_EXIT, 8
  • AT_INTERACTIVE_UNINSTALL, 8
  • AT_UNINSTALL, 8
  • AUTHOR, 8
  • AUTO_TRA, 8

  • BACKUP, 8
  • BCS, 12
  • BEGIN, 4
  • BIFF, 12

  • CHAIN, 4
  • CHAIN2, 4
  • COMPILE, 8
  • COPY, 8
  • COPY_EXISTING, 8
  • COPY_EXISTING_REGEXP, 8
  • COPY_TRANS, 4
  • Component, 8
  • chainText, 4

  • D Action, 4
  • D File, 4
  • DELETE_BYTES, 8
  • DLG, 3
  • DO, 4

  • EFF, 12
  • EXIT, 4
  • EXTEND_BOTTOM, 4
  • EXTEND_BOTTOM_REGEXP, 8
  • EXTEND_TOP, 4
  • EXTEND_TOP_REGEXP, 8
  • EXTERN, 4

  • FAIL, 8
  • FILE_CONTAINS, 8
  • FILE_EXISTS, 8
  • FILE_SIZE, 8
  • FLAGS, 4
  • FORBID_FILE, 8

  • GOTO, 4

  • IF_SIZE_IS, 8
  • INSERT_BYTES, 8
  • INTERJECT, 4
  • INTERJECT_COPY_TRANS, 4
  • ITM, 12

  • JOURNAL, 4

  • KEY, 12

  • LANGUAGE, 8
  • Language, 8

  • MKDIR, 8

  • NOT, 8
  • nonPausing, 4

  • OR, 8
  • offset, 8

  • PRINT, 8
  • Predicate, 8
  • patch, 8

  • REPLACE, 4
  • REPLACE_ACTION_TEXT, 4
  • REPLACE_BCS_BLOCK, 8
  • REPLACE_SAY, 4
  • REPLACE_STATE_TRIGGER, 4
  • REPLACE_TEXTUALLY, 8
  • REPLACE_TRIGGER_TEXT, 4
  • REPLY, 4
  • REQUIRE_FILE, 8
  • regexp, 11
  • replyText, 4

  • SET_WEIGHT, 4
  • SPL, 12
  • STRING_SET, 8
  • sayText, 4
  • state, 4
  • stateActionString, 4
  • stateLabel, 4
  • stateNumber, 4
  • stateTriggerString, 4

  • TLK, 12
  • TP2, 8
  • TP2 Action, 8
  • TP2 File, 8
  • TRA, 7.7
  • text, 4
  • transFeature, 4
  • transition, 4
  • transNext, 4
  • transTriggerString, 4

  • UNLESS, 8
  • USING, 8

  • WEIGHT, 4
  • WRITE_ASCII, 8
  • WRITE_BYTE, 8
  • WRITE_LONG, 8
  • WRITE_SHORT, 8
  • when, 8

16 Changes
Version 12:
        * The dawn of recorded history.
        * Fixed an idempotence problem with the Un-Ininstall algorithm.
        * Fixed a translation problem with multiple packages in a TP file.
        * Allow multiple TRA files for one LANGUAGE in a TP file. 
        * Add the AT_EXIT command. 
Version 13:
        * Change TP to TP2 after a reported conflict with the Sola mod.
Version 14-20:
        * --biff-get now ignores override/
        * after a TP error we reload the translation file, so no more
          weird string during install
        * SETUP-FOO.EXE implies a search for FOO.TP2
        * Added COPY_TRANS (thanks, JC)
        * Added AUTO_TRA (thanks, Quitch)
Version 21:
        * Search the Registry for the game path
        * Handle overwriting read-only files
Version 22:
        * Replace [\r\n]+ in raw-text (triggers, actions) with "\r\n". 
          This should help people who were getting doubled new-lines. 
Version 23-24:
        * Incorporate Jason's README suggestions. Thanks a bundle!
        * Add support for IWD. 
        * Convert multiple newlines to the right format when compiling and
          decompiling. 
Version 25:
        * Now delay "successfully installed" until dialog.tlk has been
          saved. 
Version 26: 
        * Handle state trigger weighting. Special thanks to Jason Compton
          and Ghreyfain for doing the research. 
        * Log OR print "[FOO.DLG] created" but don't do both. 
        * Added AT_UNINSTALL
Version 27:
        * Sort transitions triggers in trans-trig-table order when loading.
          (under the assumption that transitions run from the bottom up
           in trans-trig-table order, so we should write them out that way
           in order to preserve the semantics) 
        * COPY_TRANS processed before all other actions. 
Version 28:
        * Really process COPY_TRANS. Sigh. 
Verison 29:
        * Really stay silent about saving DLGs.
Version 30:
        * Really process COPY_TRANS, this time for sure. :-)
Version 31:
        * Fixed a bug where Multi-say states would forget their WEIGHT
Version 32: 
        * Added an optional position to EXTEND_{TOP,BOTTOM} in .D files.
Version 33:
        * Minor profiling and algorithmic enhancements mean that WeiDU
          is now at least twice as fast as it was before when compiling D
          files. The "please be patient" notice is pretty much unnecessary.
        * Added a cheap menu option because I was getting tired of saying
          "No" to all three thousand of the options in the Sola mod.
          Unfortunately, DOS doesn't seem to like color. 
        * Added ADD_KIT as a TP2 option. See mymod.tp2 for details. 
Version 34:
        * Fixed a bug in CHAIN where the filename after == would be
          reported as "not found" even if it was present. Thanks Michael!
        * Added APPEND_COL as a TP2 option. 
        * Added COPY_EXISTING, WRITE_BYTE, WRITE_SHORT, WRITE_LONG. 
        * Missing TRA files are no longer fatal errors. Having an undefined
          string reference (e.g., @55) is, however. 
        * Missing files at uninstall-time are no longer fatal errors. 
        * Michael Lyashenko tutorial included. 
Version 35:
        * Added ADD_MUSIC
        * Added %VARIABLES% that are replaced by the kit number or music
          number from ADD_KIT or ADD_MUSIC. 
        * More robust TLK handling (handle possible error with DIALOG.TLK
          from Polish version of Ascension?).
Version 36-7:
        * Added REPLACE_TEXTUALLY
        * Really fix Polish TLK handling this time. When the TLK string
          length is 0, sometimes the offset is a big negative number. 
Version 38:
        * Switch : to / in BIFF path names for Mac-VPC compat.
        * Added SET_STRING for Ghrey. 
Version 39:
        * Allow patches after EXTEND_TOP, EXTEND_BOTTOM for Michael. 
        * Added MKDIR as a TP2 action. 
        * Added REQUIRE_FILE as a TP2 action.
        * Added REPLACE_SAY as a D action.
        * Added REPLACE_STATE_TRIGGER as a D action.
        * Added SET_WEIGHT as a D action.
        * ADD_STATE_TRIGGER, ADD_TRANS_TRIGGER and REPLACE_STATE_TRIGGER
          may now operate on lists of states. Just put the extra states
          after the trigger.
        * EXTEND_TOP and EXTEND_BOTTOM may now operate on lists of states. 
          Put the extra states before the transition list. 
        * Perhaps some kind soul could write some docs about these new 
          features? 
Version 40:
        * WeiDU now keeps a log of all installed WeiDU mods and does the
          "tetris uninstall" automatically, then puts back all
          temporarily-uninstalled mods.
        * Added AT_INTERACTIVE_EXIT so that you can avoid spamming the user
          with your readme file whenever your mod happens to be randomly
          uninstalled.
Version 41: 
        * Added WRITE_ASCII (cf. WRITE_LONG) for writing in BCS script
          names and ITMs and whatnot.
        * Fixed MKDIR so that it actually makes directories. 
        * Add AT_INTERACTIVE_UNINSTALL. 
        * Fixed an "tetris uninstall" bug. 
        * We can list all of the effects in a SPL or ITM. 
Version 42:
        * Convert / to \ in AT_EXIT-style commands. 
        * Fixed a bug in APPEND_COL that was causing it to never append
          anything. :-)
Version 43:
        * ADD_KIT now takes some ToB parameters (High-Level ability
          abbreviation and starting equipment). The starting equipment was
          very tricky to get right, since you can't just append a column,
          you have to explicitly set column X (which may or may not be an
          append). 
        * Compton's multisay and chain2 tutorial included!
Version 44:
        * Better handling of syntax changes in TP2 files (when one mod
          tries to uninstall another).
        * FORBID_FILE added. 
Version 45:
        * Fixed a bug in REPLACE_TEXTUALLY (etc.) that was causing it to
          not match a whole bunch of strings. 
Version 46:
        * Added REPLACE_BCS_BLOCK, INSERT_BYTES and DELETE_BYTES patch
          actions. 
        * Added --cmp-to and --cmp-from for simple diffing. 
        * Fixed a bug where copying FOO.EXT to folder1 and also folder2
          would create incorrect backup information: only folder1\FOO.EXT
          would get uninstalled. 
Version 47:
        * Added REPLACE_ACTION_TEXT and REPLACE_TRIGGER_TEXT as D
          actions. Go Jason! 
Version 48:
        * Added --dcmp-to and --dcmp-from options to automatically spit
          out REPLACE-diffs between DLG files. 
Version 49:
        * Pause the option list. 
        * You can now say 
            STRING_SET ~Hello~ ~Hola Boy~ ~Hola Girl~ [HOLA]
          to change every ~Hello~ string in DIALOG.TLK to that new one. The
          old STRING_SET 345 syntax still works. Have fun, Compton. 
        * --biff-get can now take regular expressions
        * If we die in the middle of a TP2 installation then we uninstall
          all of the files copied so far, restoring things to the clean
          pre-attempted-install state.
Version 50:
        * Reading from BIFF files of any size is now supported. Note that
          an individual file within a BIFF must still be <= 16777000 bytes.
        * More verbose error messages in a few places. 
Version 51:
        * Make WeiDU unix friendly for a friend who would like to compile
          it.
        * Add a --nogame option for people who would like to test it out
          but do not have any Infinity Engine games. 
        * Unify slash and backslash handling. 
Version 52:
        * Allow constraints on COPY. You may now say 
                COPY src dst    // multiple src-dst pairs OK
                 patch_list
                 constraint_list // "IF", "UNLESS", "IF_SIZE_IS" 
        * IF_SIZE_IS added as a constraint. 
        * See examples/copyif.tp2. 
Version 53:
        * Fix a bug with "--yes" and failed installations. 
        * Added CHAIN3 directive to support branching in chained dialogues. 
          This was recently used in the Improved Ilyich mod -- compare the
          new compact presentation to what was done for the Eclipse guys. 
Version 54:
        * AUTO_TRA bugfix. D files should now really get the matching TRA
          file. 
Version 55:
        * Added INTERJECT action for even easier banter. Now we are truly
          meddling with forces we were not meant to know! Muhaha!
        * Replace whitespace handling in DLG files to avoid messing up 
          CharName("Drizzt Do'Urden",Player1)
          in state 57 of c6drizz1.dlg. 
Version 56:
        * Say AT_EXIT "VIEW this" instead of AT_EXIT "notepad this". VIEW
          at the beginning of a shell command will be replaced by something
          appropriate for the user/architecture (notepad, mac osx viewer,
          whatever). 
        * Added INTERJECT_COPY_TRANS, which does just what you would expect
          if you already understand the tersely-documented INTERJECT and
          COPY_TRANS actions. :-) Compton, some docs? :-) 
Version 57:
        * Fixed a bug in the handling of COMPILE-USING TRA files when you
          do not have a LANGUAGE keyword in your TP2 file. Such TRA files
          are no longer ignored. I never noticed this myself because all of
          my mods have LANGUAGEs. 
Version 58:
        * Fixed a registry path bug. 
        * Added --transref string to have --trans emit string refs. 
        * Added Compton's new instructcions on COPY_TRANS and INTERJECT. 
Version 59:
        * Registry thing really this time, but if this doesn't fix it I
          don't know what's going on. 
Version 60:
        * Possible paths are no long displayed, since that was confusing
          users (no, really).
        * Added the "--biff-name X" option for "--biff-str" and
          "--biff-type" listings. A typical use would be something like
          weidu --biff-name 8 --biff-type CRE --biff-str SW1H
          to print the names of all creatures that have one-handed swords.
Version 61:
        * Added "--tlkcmp-to" and "--tlkcmp-from" for Jason (or something). 
        * Added "ACTION_IF pred THEN BEGIN actions END" as a tp2 action
        * Added FILE_EXISTS, FILE_SIZE, FILE_CONTAINS, AND, OR, NOT as
          tp2 predicates. 
        * Added FAIL and PRINT as tp2 actions. 
Version 62:
        * tlkcmp docs by JC added. 
        * Added "FILE_EXISTS_IN_GAME" predicate, which is true if the file
          is in EITHER the biffs OR the override directory. 
        * --traify option added. weidu --traify my.d --dout new.d 
          Will make new.d which is just like my.d except that all game
          strings now reference TRA strings in new.tra. Documentation of
          this feature will (we hope) be provided by our favorite
          mind-reading freelance journalist. 
Version 63:
        * CHAIN replaced by CHAIN3 internall (but you can still use CHAIN).
        * --traify now emits the TRA things in the same order as they
          appear in your original D file. This makes --traify slower. Deal.
        * --traify# option allows you to specify a starting offset for
          the created translation strings. 
Version 64:
        * Fix a bug noticed by Quitch where the internal DLG state was not
          being cleared after an (un)successful install. This meant that
          if you had two D files with "BEGIN foo" either in two separate
          components or in the same component that you tried to re-install
          it would fail. This could also explain some errors people have
          been reporting about "multiple install"-like effects. 
        * Complain EARLY if the output dialog.tlk file is read-only or a
          directory or something.
Version 65:
        * Another attempt at fixing that bug Quitch reported. :-) 
Version 66:
        * This time we're really sure about that Quitch bug. :-) See
          test\quitch\quitch.tp2 for a way of reproducing the failure. It
          used to die (as Quitch reported) but now it succeeds. 
          If this fixes it, the problem was much worse than I thought: the
          D-file action list could get "duplicated" (or at least, not
          deleted) when an error happened. 
Version 67:
        * Fixed a bug where temporarily uninstalled components would be
          handled poorly when you wanted to re-install them, blah, I can't
          even describe this bug. Anyway, it caused (at least) at lot of
          errors like "Unix.Stat(solarom/uninstall/1/uninstall.1)" but it
          didn't seem to actually hurt anything. Still, it marked a
          conceptual flaw in my understanding of what was going on. 
        * The --tlkcmp command now produces TP2 STRING_SETs with
          @translation references and a TRA file that fills those in. 
Version 68:
        * Fixed a bug where saying --uninstall on a mod that had any
          uninstalled components would put you in an infinite loop.
Version 69:
        * In a state declaration, IF ~StateTrig()~ THEN BEGIN label, 
          the THEN and BEGIN tokens are optional.
        * In a transition, IF ~TransTrig()~ THEN ..., the
          THEN token is optional. 
        * Abbreviated .D syntax: INTERJECT_COPY_TRANS can be I_C_T, etc.
          as per Compton's suggestions. 
        * INTERJECT_COPY_TRANS now does the copy-trans bit on all of the
          interjections, not just the last one. The copy_trans bit comes
          above your interjections, so it should still work like it worked
          before in all cases where it worked before. Sigh. 
Version 70:
        * New traify docs by compton. Fixed pizza example. 
        * Unify CHAIN/CHAIN3 docs. 
        * --traify works on TP2 files. Use --dout and --traify# as before.
Version 71-72:
        * New slightly spiffier cross-referenced docs.
        * SET_STRING can now take @translations.
Version 73:
        * COPY_EXISTING source files can now take regular expressions.
        * Fixed a bug where a D file with a parse error would be held open
          by WeiDU. This bug reported by Quitch. 
        * WeiDU works with Icewind Dale 2. 
Version 74:
        * Pulled COPY_EXISTING_REGEXP out of COPY_EXISTING. Little
          backwards compat bug, sorry.
Version 75:
        * TRA files with errors are no longer held open. 
        * TIS files can now be extracted correctly. Special thanks to
          Ghreyfain for providing enough data files to debug this problem. 
          Turns out that BIFF files store special TIS tables that are used
          only for TIS resources, so they must be handled as a special
          case.
Version 76:
        * Also look in GAME/CDx/Data/ instead of just GAME/Data for BIFFs.
Version 77:
        * Compressed BIFFs (sometimes called BIFCs) are now supported.
Version 78:
        * Fix a small typo noted by Jason Compton that caused all non-TIS
          files to be loaded incorrectly and all TIS files except the first
          to be loaded incorrectly. 
Version 79:
        * The current directory is no longer a search location. Compton
          wanted this, yell if it messes you up.
Version 80:
        * Handle empty BCS files in EXTEND_TOP/BOTTOM. 
Version 81:
        * CHAIN/CHAIN3 changes:
          + You may now specify an initial condition, a la:
            CHAIN IF ~Global("MyValygarBanter","GLOBAL",0)~ THEN BVALYGA foo
              "Valygar says Hello." 
            END BVALYGA 50
          + You may end with "EXIT" instead of "END FILE LABEL". 
          + You may end with "COPY_TRANS FILE LABEL" instead of "END FILE
            LABEL". 
        * CHAIN/CHAIN3/INTERJECT/I_C_T changes:
          + You may include DO actions after spoken text:
            CHAIN BVALYGA foo
              "I shall smite thee!" DO ~SetGlobal("ValySmite","GLOBAL",1)~
            == BVICONI
              "But not me, rivvil! I am magic resistant."
            EXIT 
          + If you are a true masochist, you may use DO and IF at the same
            time here: 
            CHAIN BVALYGA foo
              "I shall smite thee!" DO ~SetGlobal("ValySmite","GLOBAL",1)~
            == BKELDOR
              IF ~IsValidForPartyDialogue("Keldorn")~ THEN 
              "I shall prevent you from smiting anyone."
              DO ~SetGlobal("KeldornPreventsSmiting","GLOBAL",1)~
            EXIT 
        * Special thanks to "Blue" for making the tutorial that suggested
          these changes. See examples/chain-banter.d for the tutorial
          example. 
Version 82:
        * Setup-Foo.exe now dies if both Setup-Foo.tp2 and Foo.tp2 are not
          present.
        * --automate dir option added. Throw it at a directory of
          ITM/SPL/CRE files (say, "mymod/itm"), it will spit out a TP2 file
          for you. Example use:
          C:\> weidu --automate foo/itm --textapp Setup-Foo.tp2
        * Special thanks to Victoria, who will surely be famous at some
          point. Let me know when you've updated your CHAIN tutorial and I
          will link to it. 
Version 83-84: 
        * Fixed a bug in "interject" that was the wrong number of
          parameters to be passed to "Global()" and was putting triggers
          (like InParty("Valen")) in the action slot. Sigh! Sigh!
Version 85:
        * Fixed a bug where --automate was missing name offsets at 0xC (the
          identified name for ITMs, etc). Rerun --automate if you use it to
          make sure you aren't missing anything. 
        * Added some --automate docs based on Rene Heroux's suggestions. 
Version 86:
        * Much nicer handling of mods with more than 4 components.
        * Added the UNINSTALL TP2 action to allow one mod component (say, 
          the archer kit in a new tactics mod) to uninstall another (say,
          the old version of the archer kit in the sola mod). 
Version 87:
        * Yada yada, more prep to get ready for the tactics mod. 
Version 88:
        * EXTEND_TOP_REGEXP and EXTEND_BOTTOM_REGEXP TP2 actions added.
          Will the person who wanted this submit some docs? 
        * --automate now handles AREs 
        * You may now say !NUMBER ~Hello~ (or !NUMBER @44 or whatnot) in a
          D file instead of ~Hello~ to *FORCE* the string ~Hello~ to
          overwrite whatever was already in strref NUMBER in dialog.tlk 
        * --forceify option added. It behaves like --traify. So you might
          say: weidu --forceify my.d --dout new.d 
          The created D file will be just like the old one, except that all
          of the strings in it will become forced strings with their
          current strref. No, I do not understand why you would want this
          either. But some people do. 
Version 89:
        * Added Japeth's tutorials.
        * --tcmp no longer dies on an invalid file. Thanks, Falk!
        * WeiDU will now decrypt those annoying encrypted IDS files when
          extracting them from BIFFs. 
        * WeiDU is now a BCS -> BAF decompiler. Just pass BCS files as
          arguments. BAF files will be created in the current directory (or
          you can use --out). I consider NI to be the "reference"
          decompiler. Let me know if you find a script where WeiDU and NI
          disagree on something that is not a comment, a MYAREA or an
          "ar1234". 
        * You can now give WeiDU command-line arguments after seeing the 
          options list. 
Version 90:
        * Fixed a problem where SETUP-FOO didn't work. 
Version 91:
        * WeiDU is now a BAF -> BCS compiler. Just pass BAF files as
          arguments. BCS files will be created in teh current directory (or
          use --out). Again, let me know if we mis-compile something. 
          Future work: allowing D-style string references in BAF files, etc. 
        * WeiDU now reads all the .INI files in your game directory and
          looks for lines of the form CD1:=C:\My\Path. 
        * New Uberchain tutorial by Jason. 

This document was translated from LATEX by HEVEA.
Hosted by uCoz