The main home page for WeiDU is:
http://www.cs.berkeley.edu/~weimer/bgate/.
I encourage you to download the latest version.
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.
Step-By-Step Beginner's Guide to WeiDU:
- Don't Panic. Many of you are children of the GUI era. But programs
that run from the command line can be your friend, and in the long run are
often much faster and, yes, easier to use.
- The Best Way To Learn How To Write Code In WeiDU's D Format
Is To Read Code Written In WeiDU's D Format. Start by decompiling
existing in-game DLGs that you understand and read through them.
Compare how they appear in WeiDU to how they appear in other BG2 editing
tools you may be more comfortable with, such as Near Infinity or Infinity
Explorer.
- The Best Way To Learn How To Write Code In WeiDU's D Format
Is To Read Code Written In WeiDU's D Format, Part 2. A growing
number of BG2 add-on packs are being created using WeiDU. A list is
available at
http://www.cs.berkeley.edu/~weimer/bgate/weidumods.html.
These
can help you understand how WeiDU's advanced features, such as dialogue
appending, script and 2DA patching, and item/spell/creature
patching work in a ``real-world'' setting. Make it a point to download some
of them and understand HOW they work.
- Take a look at some of the examples in this document.
There is a lovely WeiDU tutorial (written by
Japheth) available at
http://www.forgottenwars.net/?page=tutorials/weidubasics.
If you
are feeling overwhelmed, start there first. It also covers installation.
Ghreyfain also has a ``how to create an NPC with WeiDU'' tutorial at
http://www.forgottenwars.net/?page=tutorials/weidunpc.
- There is a WeiDU discussion board at
http://forums.fwstudios.net/index.php?act=SF&f=50. The
discussion board is the best place to have your WeiDU (and mod-making)
questions answered.
- Finally, if you are using a Mac and you want to play around with
WeiDU, check out http://weidu.org/Mac.html for more
information on obtaining a copy of WeiDU that works under OS X.
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.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\DIALOG.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 %%
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.
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:
-
bar list means "0 or more copies of bar".
- [ foo ] means "an optional foo" or "0 or 1 copies of foo".
- foo bar ... means "you may repeat foo bar as often as you like here".
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 set nonPausing to 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
[ IF [ WEIGHT #weight ] stateTriggerString THEN ] entryFilename entryLabel chainText list chainEpilogue |
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 chainEpilogue |
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 [ moreStateNumbers list ] [ DO transNumber 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.
If a list of transNumbers is specified, only those transitions
will have transTriggerString added to them. If such a list is not
specified, every transition in every specified state will be modified.
Note that the ``first'' transition is number 0. |
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 destructively 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
[ moreFilenames ] |
This instructs WeiDU to destructively 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. |
or |
REPLACE_ACTION_TEXT_PROCESS filename oldText newText
[ moreFilenames ] |
This instructs WeiDU to destruveily replace every occurrence of oldText
(which may be a regexp) in the stateActionStrings
of filename.DLG (which must exist) with newText. However,
newText is first compiled as a BAF action list. In particular,
this means that replacing with commands like:
~DisplayString(Myself,@123)~
... will do what you expect. This should only be used to patch or
workaround existing dialogues. Never use this if you can help it. |
|
chainEpilogue |
|
Determines where the dialogue should flow at the
end of the CHAIN. |
is |
END filename stateNumber |
Transfer to the given state in the given dialogue file. |
or |
EXTERN filename stateNumber |
Transfer to the given state in the given dialogue file. |
or |
COPY_TRANS filename stateNumber |
At the end of the
CHAIN text, copy all transitions from the given state in the
given file. This is useful for interjections (see INTERJECT). |
or |
EXIT |
At the end of the CHAIN text, exit the dialogue. |
or |
END transition list |
Execute the given transitions
after the final state in the CHAIN. |
|
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.
Finally, once you are familiar with the syntax you may omit the THEN
and BEGIN keywords if you like. |
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 |
+ [ transTriggerString ] + replyText
transFeature list transNext |
This abbreviated syntax for transitions that would contain REPLY
(which is by far the most common case) allows you to save yourself
some time and typing. It behaves like the full form above. |
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 |
SOLVED_JOURNAL text |
If this transition is taken, the text is added to the
``solved'' section of the PC's journal. |
or |
UNSOLVED_JOURNAL text |
If this transition is taken, the text is added to the
``unsolved'' section of 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. |
or |
+ stateLabel |
This is a synonym for GOTO. |
|
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 |
[ == 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 sound 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. |
|
String |
|
This is how you tell WeiDU what text you want shown
to the player. For international mods or international translations, you
may use any encoding you like (that is, you are not restricted to 7-bit
characters or Latin-1 or anything like that). |
is |
"abcdef" |
A string can be any sequence of characters not
including a " that is enclosed in ""s. |
or |
~ abcdef~ |
A string can be any sequence of
characters not including a ~ that is enclosed in
~~ s. |
or |
%abcdef% |
A string can be any sequence of characters not
including a % that is enclosed in %%s. This is handy for Big5
translations, since " and ~ can be part of Big5-encoded characters. |
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. Note that not all options are described below:
run WeiDU --help for more a complete list.
|
|
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 or BAF
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: |
--traify-tlk |
emit a TRA file for the given TLK file (see --textout --min --strfind) |
--make-tlk X |
make a TLK file from TRA file X (cumulative, see --tlkout) |
--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 |
Finally, note that WeiDU will not add duplicate strings to
DIALOG.TLK. If you instruct WeiDU to make use of the string
``Imoen'' (via SAY or --strapp or whatever) it will re-use any
existing definition of ``Imoen'' instead. Two strings are equivalent in
this sense only if they have the same text and the same associated sounds
(normally strings have no associated sounds).
- Decompiling a DLG to a D
C:\Program Files\Black Isle\BGII - SoA\> weidu bodhi.dlg
[C:\Program Files\Black Isle\BGII - SoA\chitin.key] 182 BIFFs, 41793 resources
[C:\Program Files\Black Isle\BGII - SoA\DIALOG.TLK] 84458 string entries
[C:\Program Files\Black Isle\BGII - SoA\data\Dialog.bif] 2729 file entries
[BODHI.DLG] loaded
[.\BODHI.D] created from [BODHI.DLG]
This loads BODHI.DLG from the standard search path (i.e., the
current directory, your override directory, then the game BIFFs) and
creates BODHI.D from it.
- Decompiling a DLG file with translations
C:\Program Files\Black Isle\BGII - SoA\> weidu bodhi.dlg --trans
...
[.\BODHI.TRA] created as translation file
[.\BODHI.D] created from [BODHI.DLG]
This creates BODHI.D as above and also the translation file
BODHI.TRA (listing all of the strings in BODHI.D
in an easy-to-traslate or spell-check format). BODHI.D will
be created with special references to those strings.
This is particularly useful if you are converting existing modifications
you may have created with another tool, such as IDU, into WeiDU format.
It allows you to both create the WeiDU D code and the
translation-friendly string labels at the same time.
- Decompiling a DLG files with options
C:\Program Files\Black Isle\BGII - SoA\> weidu --nofrom bodhi.dlg --dout foozle.d --text
...
[.\foozle.d] created from [BODHI.DLG]
This creates foozle.d (instead of BODHI.D) and does not put any
"// from:" comments in foozle.d. It will include states
with SAYs of the form
SAY ~Hello~ /* #1 */
instead of
SAY #1 /* ~Hello~ */
- Decompiling multiple DLG files
C:\Program Files\Black Isle\BGII - SoA\> weidu bodhi.dlg jaheira.dlg --out test
...
[test\JAHEIRA.D] created from [JAHEIRA.DLG]
[test\BODHI.D] created from [BODHI.DLG]
This loads BODHI.DLG and JAHEIRA.DLG and creates BODHI.D and
JAHEIRA.D. The optional
--out test argument instructs WeiDU to put the resulting D
files in the test directory.
- Compiling a D file
C:\Program Files\Black Isle\BGII - SoA\> weidu bodhi.d
...
[bodhi.d] parsed
[BODHI.DLG] saved 135 states, 259 trans, 16 strig, 66 ttrig, 54 actions
This loads and parses bodhi.d and then executed all instructions in it.
This bodhi.d file just defines BODHI.DLG, which is created. If
bodhi.d contains strings that do not occur in DIALOG.TLK,
BODHI.DLG will be created with invalid string references.
- Compiling a D file that includes new text
C:\Program Files\Black Isle\BGII - SoA\> weidu bodhi.d --tlkout new-DIALOG.TLK
...
[bodhi.d] parsed
[BODHI.DLG] saved 135 states, 259 trans, 16 strig, 66 ttrig, 54 actions
[new-DIALOG.TLK] created, 84459 string entries
This loads and parses bodhi.d and then executed all instructions in it.
This bodhi.d file just defines BODHI.DLG, which is created. If
there are any new strings a new version of DIALOG.TLK is written to
new-DIALOG.TLK.
- Compiling multiple D files
C:\Program Files\Black Isle\BGII - SoA\> weidu ppworker.d bodhi.d --out test
...
[bodhi.d] parsed
[ppworker.d] parsed
[BODHI.DLG] saved 135 states, 259 trans, 16 strig, 66 ttrig, 54 actions
[PPWORKER.DLG] saved 33 states, 81 trans, 4 strig, 12 ttrig, 10 actions
This creates test/BODHI.DLG and test/PPWORKER.DLG based on the
instructions in bodhi.d and ppworker.d.
If these D files include new text, use --tlkout to make a new
DIALOG.TLK.
- Compiling a D file that defines many DLG files
C:\Program Files\Black Isle\BGII - SoA\> weidu examples/sola/solae1.d
OR
C:\Program Files\Black Isle\BGII - SoA\> weidu examples\sola\solae1.d
...
[examples/sola/solae1.d] parsed
[SOLA.DLG] loaded
[SOLA.DLG] saved 336 states, 401 trans, 64 strig, 18 ttrig, 125 actions
[SOLAE1.DLG] saved 36 states, 49 trans, 1 strig, 11 ttrig, 1 actions
[SOLAE2.DLG] saved 3 states, 3 trans, 0 strig, 0 ttrig, 0 actions
[SOLAE3.DLG] saved 2 states, 2 trans, 0 strig, 0 ttrig, 0 actions
[SOLAE4.DLG] saved 3 states, 3 trans, 1 strig, 0 ttrig, 0 actions
[SOLAE5.DLG] saved 2 states, 2 trans, 0 strig, 0 ttrig, 0 actions
[SOLAE6.DLG] saved 4 states, 5 trans, 0 strig, 2 ttrig, 0 actions
It just so happens that solae1.d APPENDs text to
SOLA.DLG and
creates SOLAE1.DLG, SOLAE1.DLG,
SOLAE3.DLG, ..., SOLAE6.DLG. You could
have put them all in the override directory with --out override. You
may use the forward slash (/) or the backslash (\) for
directories. Use --tlkout to make a new DIALOG.TLK if these
contain new text.
- Compiling a D file that uses a TRA file
C:\Program Files\Black Isle\BGII - SoA\> weidu examples/sola/solafoe.d --transin examples/sola/solafoe.tra
OR
C:\Program Files\Black Isle\BGII - SoA\> weidu examples/sola/solafoe.d examples/sola/solafoe.tra
...
[examples/sola/solafoe.tra] parsed (15 translation strings)
[examples/sola/solafoe.d] parsed
[SOLA.DLG] loaded
[SOLA.DLG] saved 336 states, 401 trans, 65 strig, 18 ttrig, 124 actions
[SOLAFOE.DLG] saved 11 states, 14 trans, 1 strig, 2 ttrig, 1 actions
It happens that solafoe.d uses 15 strings from a translation file,
APPENDs to SOLA.DLG and creates
SOLAFOE.DLG. You may use --transin to
specify a translation file or (if it ends in TRA) just throw it
on the command line. If you include multiple TRA files, the last
one to define a particular string index wins for that string. They need
not all cover the same set. Use --tlkout if there is new text involved.
- Displaying String References
C:\Program Files\Black Isle\BGII - SoA\> weidu --string 123 --strfind understudy --strfind acid.*rows
...
[C:\Program Files\Black Isle\BGII - SoA\chitin.key] 182 BIFFs, 41793 resources
[C:\Program Files\Black Isle\BGII - SoA\DIALOG.TLK] 84458 string entries
String #123 is ~Haer' Dalis, all of you, stop them!~
String #6763 is ~Acid Arrows~
String #11662 is ~Biff The Understudy~
...
This displays string #123 and all strings that contain the string
"understudy" and all strings that match the regular expression
(regexp) "acid.*rows". Note that case does not matter.
- Updating DIALOG.TLK Manually
C:\Program Files\Black Isle\BGII - SoA\> weidu --strapp ANewString --tlkout happy.tlk
[C:\Program Files\Black Isle\BGII - SoA\DIALOG.TLK] 84458 string entries
[.\happy.tlk] created, 84459 string entries
Not much to say here. String reference #84459 in happy.tlk is now
``ANewString''.
- Listing BIFF Contents
C:\Program Files\Black Isle\BGII - SoA\> weidu --biff data/dialog.bif
...
[data\Dialog.bif] contains ABELA.DLG at index 0
[data\Dialog.bif] contains ACHEN.DLG at index 1
...
This shows all of the resources (e.g., ACHEN.DLG is a resource) that
are contained in data/Dialog.bif.
- Extracting BIFF Contents
C:\Program Files\Black Isle\BGII - SoA\> weidu --biff-get dragred.cre
[C:\Program Files\Black Isle\BGII - SoA\chitin.key] 182 BIFFs, 41793 resources
[C:\Program Files\Black Isle\BGII - SoA\DIALOG.TLK] 84458 string entries
[C:\Program Files\Black Isle\BGII - SoA\data\Creature.bif] 3194 file entries
[.\dragred.cre] 1776 bytes, created from [C:\Program Files\Black Isle\BGII - SoA\data\Creature.bif]
This grabs Firkraag's dragon-form CRE creature file from the game
BIFFs and saves it in the current directory.
- Extracting BIFF Contents with Regular Expressions
C:\Program Files\Black Isle\BGII - SoA\> weidu --biff-get sper.*itm
[.\chitin.key] loaded, 590551 bytes
[.\chitin.key] 182 BIFFs, 41793 resources
[.\DIALOG.TLK] loaded, 10154904 bytes
[.\DIALOG.TLK] 77666 string entries
[.\data\Items.bif] loaded, 659688 bytes
[.\data\Items.bif] 1990 file entries
[.\SPER01.ITM] 266 bytes, created from [.\data\Items.bif]
[.\SPER02.ITM] 314 bytes, created from [.\data\Items.bif]
[.\SPER03.ITM] 362 bytes, created from [.\data\Items.bif]
[.\SPER04.ITM] 322 bytes, created from [.\data\Items.bif]
[.\SPER05.ITM] 266 bytes, created from [.\data\Items.bif]
[.\SPER06.ITM] 266 bytes, created from [.\data\Items.bif]
[.\SPER07.ITM] 554 bytes, created from [.\data\Items.bif]
[.\SPER08.ITM] 314 bytes, created from [.\data\Items.bif]
[.\SPER09.ITM] 314 bytes, created from [.\data\Items.bif]
[.\SPER10.ITM] 362 bytes, created from [.\data\Items.bif]
[.\data\25Items.bif] loaded, 222370 bytes
[.\data\25Items.bif] 479 file entries
[.\SPER11.ITM] 314 bytes, created from [.\data\25Items.bif]
[.\SPER12.ITM] 1610 bytes, created from [.\data\25Items.bif]
[.\SPERMEL.ITM] 890 bytes, created from [.\data\25Items.bif]
This one assumes that the game is in the current directory and asks for
every spear item in the game. Note that --biff-get uses regular
expressions (regexp), not DOS-style wildcards. Note also that
--biff-get does not look in the override directory. Finally, if
you are using a Mac (or otherwise running unix) you'll want to put the
regular expression in double quotes, like so:
C:\Program Files\Black Isle\BGII - SoA\> weidu --biff-get "sper.*itm"
- Searching BIFF Contents
C:\Program Files\Black Isle\BGII - SoA\> weidu --biff-type CRE --biff-str SPWI911
...
LICH01.CRE in [data\Creature.bif] matches
HLKANG.CRE in [data\Creature.bif] matches
...
This finds all CRE files that contain the string "SPWI911", which is
equivalent to finding all enemy mages that know the spell Meteor Swarm
(which has resource name "SPWI911"). You could also try something like:
C:\Program Files\Black Isle\BGII - SoA\> weidu --biff-type BCS --biff-str Terminsel
...
AR0300.BCS in [data\Scripts.bif] matches
AR0308.BCS in [data\Scripts.bif] matches
JAHEIRA.BCS in [data\Scripts.bif] matches
...
to find all of the game scripts that include a variable that includes the
substring "Terminsel". As you would expect, Jaheira shows up. Note that
these searches are moderately time-consuming (e.g., searching all scripts
takes about 20 seconds).
- Converting one TLK file to another
C:\Program Files\Black Isle\BGII - SoA\> weidu --tlkcmp-from DIALOG.TLK --tlkcmp-to dialog-asc.tlk
...
[DIALOG.TLK] loaded, 8692747 bytes
[DIALOG.TLK] 74107 string entries
[dialog-asc.tlk] loaded, 10211578 bytes
[dialog-asc.tlk] 82805 string entries
WARNING: DIALOG.TLK has 74107 entries, dialog-asc.tlk has 82805 entries
STRING_SET 70866 ~Babau~ []
STRING_SET 70867 ~Babau~ []
This compares all strings in common between two DIALOG.TLK files and
generates a list of STRING_SET TP2 entries to convert the
TLK file named in --tlkcomp-from into the TLK file named in
--tlkcomp-to. In this case, WeiDU indicates there are two differences in
the strings shared between a standard ToB TLK file and an Ascension
Classic TLK file: strings 70866 and 70867 were changed to "Babau".
Also note that the Ascension Classic TLK file has more entries (82805
compared to 74107).
If you have made a large number of manual changes to a TLK file
(such as grammar/spelling corrections, or other dialogue tweaks), this is a
handy way to generate install-ready scripting to apply those changes to an
end user's version of BG2.
Note that the use of --textout will be helpful for a long list.
C:\Program Files\Black Isle\BGII - SoA\> weidu --tlkcmp-from DIALOG.TLK --tlkcmp-to dialog-asc.tlk --textout mylist.txt
This will make a new file mylist.txt file that contains the
STRING_SET parts of the output, which can then be put into a
TP2 file.
- Automating File Descriptions
C:\Program Files\Black Isle\BGII - SoA\> weidu --automate MyMod/SomeFolder --textapp MyMod.tp2
WeiDU's --automate feature can save you oodles of time, so you probably
will want to learn how to use it. It's rather simple when you get the hang
of it (but everything is right?).
Suppose you have just created some items, some spells and some creatures
and some areas for you mod and you want to distribute them to others using
WeiDU. You could manually write out string patching code by hand for each
resource. Or you can get WeiDU to do it automatically.
WeiDU will scan every item, spell and creature inside the given folder (in
this example, the folder is MyMod/SomeFolder) and emit TP2
commands to COPY those resources from that folder into the
override folder. In addition, each resource's current strings (like
item descriptions and monster names) will be loaded from your TLK
file and used to patch that resource as it is copied.
For example, the output of --automate on a folder that contains a
potion of extra healing looks like this:
COPY ~MyMod/SomeFolder/potn52.itm~ ~override/potn52.itm~
SAY NAME ~Potion~
SAY NAME2 ~Potion of Extra Healing~
SAY UNIDENTIFIED_DESC ~Potions are typically found in ceramic, crystal, glass,
or metal flasks or vials. Flasks or other containers generally contain
enough fluid to provide one person with one complete dose to achieve the
effects of the potion.~
SAY DESC ~When wholly consumed, this potion restores 27 hit points to the
person. The effect is instantaneous and the potion is destroyed in the
process.~
SAY 0xde ~Gulp!~ [GULP]
And there you have it, apparently there is a substitute for hard work.
- Converting Between TLK and TRA Files
Some translators who are using WeiDU to translate non-WeiDU mods find it
handy to be able to convert between TLK and TRA files.
First, let's create a TRA file:
C:\Program Files\Black Isle\BGII - SoA\> weidu --traify-tlk --min 2000 --max 2002
...
@2000 = ~Indeed! It's been quite tasty so far. Listen, we're not here to
devour everything. In fact, we'd like to help a little girl named
Jaella.~
@2001 = ~No, we haven't. We will devour you if you don't tell us what we
need to know.~
@2002 = ~Let us stop this charade. I'm only here to ask you a few
questions.~
...
You may also extract only those strings matching a regexp:
C:\Program Files\Black Isle\BGII - SoA\> weidu --traify-tlk --strfind lawyer
...
@36568 = ~Honor-bound and honor-branded, then, is it? Very well, lawyer,
you have set me free and for that I thank you.~
...
Finally, you may redirect the output to a file using --textout and
read from a different TLK file by adding it on the command line.
Once you have a TRA file with a few entries you can create a
TLK file from it:
C:\Program Files\Black Isle\BGII - SoA\> weidu --make-tlk my.tra --tlkout new.tlk
[c:\src\weidu\weidu.exe] WeiDU version 109
[C:\Program Files\Black Isle\BGII - SoA/chitin.key] 182 BIFFs, 41793
resources
[C:\Program Files\Black Isle\BGII - SoA/dialog.tlk] 82405 string entries
[my.tra] parsed
[my.tra] has 100 translation strings
New TLK will have 200 entries
[new.tlk] created, 200 string entries
String @1 in your TRA file will become string reference #1 in the TLK file. If your TRA file has ``holes'' the new
TLK file will have blank entries. You may specify --make-tlk
multiple times: the last TRA file to define a translation string
determine that string reference.
- Creating a BIFF File
This tutorial was thoughtfully provided by Japheth.
Using --make-biff is dead easy. Here's a quick and dirty example:
Assuming WeiDU is in your BGII directory, grab a bunch of files from your
override folder and stick them into a folder inside your BGII directory.
We'll call the folder mybiff for this example.
Once the files are inside the folder, this is all you would have to do to
make WeiDU create a biff of the files:
C:\Program Files\Black Isle\BGII - SoA\> weidu --make-biff mybiff
[c:\src\weidu\weidu.exe] WeiDU version 122
[./chitin.key] 182 BIFFs, 41793 resources
[./dialog.tlk] 82479 string entries
[data\mybiff.bif] will contain 20 resources totalling 211096 bytes
[data\mybiff.bif] incorporating [mybiff/udsola02.cre]
...
[data\mybiff.bif] incorporating [mybiff/sola10.cre]
KEY saved (183 biffs, 41813 resources)
WeiDU will add the files into the BIFF mybiff.bif and stick
that file in your data directory. It will also update the
CHITIN.KEY.
Before using this feature, make sure you backup your
CHITIN.KEY.
- Viewing Banter Offline
C:\Program Files\Black Isle\BGII - SoA\> weidu --nocom --text --transitive banomen.dlg
The --transitive flag tells WeiDU to follow EXTERN
references when emitting D files. So the resulting BANOMEN.D
file has lines like this:
IF WEIGHT #31 ~InParty("Edwin")
See("Edwin") Gender("Edwin",FEMALE)
!StateCheck("Edwin",STATE_SLEEPING)
Global("BAnomen1","LOCALS",0)~ THEN BEGIN 10
SAY ~Hey, Edwina! I shall be your champion at the next tournament that we come to if only you give me a piece of your robe, uh, that is, dress to adorn my shield.~ [ANOMEN49]
IF ~~ THEN DO ~SetGlobal("BAnomen1","LOCALS",1)~ EXTERN ~BEDWIN~ 104
END
IF ~~ THEN BEGIN BEDWIN 104
SAY ~(My condition draws fools like flies to honey). Silence, you idiot! You've a death wish that is larger than your swollen head.~ [EDWINW39]
IF ~~ THEN GOTO 11
END
IF ~~ THEN BEGIN 11
SAY ~Fair Edwina, I am truly bereft by your non-acceptance. It is tragic when a knight has no fair maiden to moon over. Heh he he...~
IF ~~ THEN EXIT
END
Note that both lines from both Edwin and Anomen are presented. The
resulting ``D'' file is not valid in that it cannot be fed back to WeiDU
directly, but it should make it easier for you to read all of the jokes
offline.
This section includes tutorials on specific parts of WeiDU. Many of them
were contributed by users like you.
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.
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:
-
Kelsey: Imoen, what do you like on your pizza?
- Imoen: Oregano.
- Imoen: Oh, and maybe with a little basil mixed in.
- Kelsey: Well, yeah, but anything else?
- Imoen: Sauce is good.
- Kelsey: (laughs) You're not being very helpful, Imoen.
- Imoen: Crust. I like crust on my pizza. Cooked crust is better.
- Kelsey: Do you want me to make you this pizza or not?
- Kelsey: It WAS your idea.
- Imoen: I can't decide. Never mind, I'm just gonna have yogurt.
- Kelsey: (sigh)
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.
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.
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).
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.
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.
Finally, note that --traify works for BAF files as well.
This tutorial was thoughtfully provided by Japheth.
REPLACE_ACTION_TEXT can be used with regular expressions
(regexp). So, some actions such as
CreateCreature("blah",[0.0],1) won't be matched by WeiDU because when
you say [0.0] it is looking for a character set.
Here's one dialogue that I'm fixing and how to do it correctly.
Arghai.dlg has this action trigger before
REPLACE_ACTION_TEXTing it.
CreateCreature("OGREHA",[1351.1078])}
There's no point given, which makes WeiDU and NI angry. To fix it using
REPLACE_ACTION_TEXT this is what you have to do:
REPLACE_ACTION_TEXT arghai
~CreateCreature("OGREHA",\[1351.1078\])~
~CreateCreature("OGREHA",[1351.1078],0)~
So, all you have to remember to do is escape the square brackets with a
backslash so WeiDU doesn't confuse them with a regular expression.
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
TP2 Flag list
Language 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. TP2 Flags set global options.
Languages are
the various languages in which your mod is available. The
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. |
|
TP2 Flag |
|
A TP2 Flag declaration tells WeiDU to apply some
global option to your TP2 file. |
is |
AUTO_TRA path |
The AUTO_TRA flag is used
with the COMPILE TP2 Action. It automatically loads
TRA files that match your D files. |
or |
ALLOW_MISSING file list |
ALLOW_MISSING directive allows you to specify files that can
be missing (when you try to copy them or reference them from
D files). Empty versions of those files will be created on
demand. Try to use ACTION_IF instead of this. |
or |
ASK_EVERY_COMPONENT |
This flag instructs WeiDU to ask about installing every component in
this TP2 file individually, rather than asking questions
like "Would you like to install them all?" |
or |
ALWAYS TP2 Action list |
This flag specified a TP2 Action that is executed just
before any normal TP2 Action in the file is installed. |
|
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
Component Flag list 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. |
|
Component Flag |
|
A Component Flag determines how WeiDU treats a component. |
is |
DEPRECATED string |
Mark the given component as deprecated. If it is currently installed,
it will be uninstalled and the given string will be
displayed. The user will never be asked to install the given
component -- it will be silently skipped in all listings. However, it
will still take up a ``component number''. |
or |
REQUIRE_COMPONENT modToUninstall modComponent
string |
Make this component so that it can only be installed if another
component is installed. If that other component is not installed, the
string will be displayed and the user will not get a chance
to install this component. This is in some sense the opposite of the
UNINSTALL TP2 Action. For example,
REQUIRE_COMPONENT "setup-ease.tp2" "0" "You must have infinite
stacking installed!" prevents a component from being installed
unless the infinite stacking part of the Ease-of-Use mod is
installed. |
or |
FORBID_COMPONENT modToUninstall modComponent
string |
Make this component so that it can only be installed if another
component is not installed. This does the opposite of
REQUIRE_COMPONENT. |
|
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 optNoBackup
optGlob 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".
COPY commands set the user-defined SOURCE_DIRECTORY,
SOURCE_FILESPEC, SOURCE_FILE,
DEST_DIRECTORY, DEST_FILESPEC, and
DEST_FILE
variables based on fromFile and toFile as follows. If
fromFile is mymod/cre/bigboss.cre, then
SOURCE_DIRECTORY is mymod/cre,
SOURCE_FILESPEC is mymod/cre/bigboss.cre, and
SOURCE_FILE is bigboss.cre. The DEST_ variables
are similarly based on toFile.
This is generally only useful if you have enabled globbing. Any
user-defined variables in toFile are replaced with their
values. You may also reference these variables in
patches. |
or |
COPY_EXISTING optNoBackup 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 optNoBackup optGlob 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.
If GLOB is specified, matching files in override will
also be patched and copied. If a file appears in both the
BIFFs and the override folder, it will only be copied
once. For example, if HARM.ITM is in the BIFFs and
HARM2.ITM is in override, this code will copy and patch them
both:
COPY_EXISTING_REGEXP GLOB ~HARM.*.ITM~ ~override~
SAY // ... whatever
|
or |
COMPILE sourceFile list [ USING traFile list ] |
This command compiles D and BAF source files. If
sourceFile is a directory, all D and BAF files within
that directory are processed individually.
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/sourceFile.tra for
every sourceFile in the list. Once all of the TRA files are
loaded, the D and BAD files are compiled. Any
DLGs or BCSs 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 newFile patch list |
Loads existingFile (which may be BAF or BCS), 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 newFile patch list |
As EXTEND_TOP, but the newFile file is put at the bottom of the
existingBCS file. |
or |
EXTEND_TOP_REGEXP existingBCSregexp newFile patch list |
As EXTEND_TOP, but the newFile file is put at the bottom of the
every BCS file that matches the regexp
existingBCSregexp. |
or |
EXTEND_BOTTOM_REGEXP existingBCSregexp newFile 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.
-
If commandToRun consists of a single TP2 filename, WeiDU will
enqueue that TP2 file and run it when the current one is done
(asking the user all the standard questions about languages and which
components to install).
- If commandToRun consists of the word VIEW followed by a file, a
system-specific viewer will be used to present the file to the user.
For example, on Windows systems notepad will be used to view
txt files and a web browser will be used to view html files.
- Otherwise, commandToRun is executed by the underlying operating system
(and is thus system dependant). 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.
In any case, slashes and backslashes will be converted appropriately
for the underlying operating system. 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_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. |
or |
AT_UNINSTALL commandToRun |
As AT_EXIT, but when this component is removed,
commandToRun is executed. |
or |
AT_INTERACTIVE_UNINSTALL commandToRun |
As AT_EXIT, but whenever the user specifically asks for this
component to be removed, commandToRun is executed. |
or |
UNINSTALL modToUninstall modComponent |
If the given component of the given mod is currently installed,
uninstall it before proceeding. Do not use this action. This
should only be used if you release a new version of a component under a
new name. For example, many Tactics Mod components replace old
Solaufein mod components. In order to prevent such a component from
being installed twice, the Tactics version uninstalls the Solaufein
version. |
or |
ADD_KIT internalKitName manyComplexArguments |
This command allows you to add new kits to the BGII.
See the example file mymod.tp2 or the tutorial at
http://tutorials.teambg.net/index.php?page=opentut&tutID=28
for information on how to do this. |
or |
ADD_MUSIC internalMusicName newMUSFile |
No documentation yet! |
or |
ADD_PROJECTILE modpath/PROName.PRO |
Appends an entry for PROName to PROJECTL.IDS and assigns it the next
available ProRef number. Then copies the file modpath/PROName.PRO
to the override folder. The new ProRef number can be accessed
through the variable %PROName% and used to updated the
Projectile type field of an ITM or SPL file's Item Ability or
Spell Ability sub-structures. (The hexadecimal offsets for these
fields can be found using NearInfinity.) In the following example, a
new PRO file and an ITM file that will use it are added to the game:
ADD_PROJECTILE ~MyMod/MYDXP.PRO~
COPY ~MyMod/MYDART.ITM~ ~override/MYDART.ITM~
WRITE_SHORT 0x09c ~%MYDXP%~
|
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. If displayString contains %variable%
references, their values will be displayed. |
or |
ADD_GAM_NPC npcCRE npcARE xCoord yCoord |
See the ADD_GAM_NPC tutorial for more information about this
action, which is used when adding NPCs to Baldur's Gate 1. BG2 mods
should not use this command. |
|
optNoBackup |
|
A COPY command normally makes a backup copy of its target (if one
exists) so that the mod can be uninstalled later by restoring the backup. |
is |
|
If you don't say anything here, WeiDU will make a backup
copy of the file and the COPY will be undone if the mod
is uninstalled. |
or |
+ |
If you put a + here, WeiDU will not make a backup
copy of the file and the COPY will not be
undone if the mod is uninstalled. Do not use this feature. |
|
optGlob |
|
A COPY command may use globbing to expand filename
wildcards with respect to files on the host filesystem. Unlike
COPY_EXISTING_REGEXP, glob wildcards do not range over game
resources. Instead, they range over physical files actually on the disk. |
is |
|
Do not use local filesystem globbing. This is the default. |
or |
GLOB |
Use local filesystem globbing. Globbing is
generally architecture specific! Do not use globbing if you can
help it. |
|
patch |
|
A patch tells WeiDU how to modify a file. |
is |
SAY offset string |
The string-ref associated with string is written at
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 (e.g., kit and music names) is performed on string2. |
or |
APPLY_BCS_PATCH patchFile
Applies patchFile to the current file. See --bcmp-from and similar
command-line arguments for constructing these patches. |
or |
APPLY_BCS_PATCH_OR_COPY patchFile copyFile
Applies patchFile to the current file, as APPLY_BCS_PATCH.
However, if the patching fails the current file is replaced with copyFile
instead. |
or |
WRITE_BYTE offset value |
The first argument is the offset at which the second argument (an 8-bit
byte value) is written. |
or |
WRITE_SHORT offset value |
The first argument is the offset at which the second argument (a 16-bit
short value) is written. |
or |
WRITE_LONG offset value |
The first argument is the offset at which the second argument (a 32-bit
long word value) is written. |
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 |
WRITE_EVALUATED_ASCII offset ascString |
The ASCII ascString is evaluated (so %variable% is replaced by its
value) and written to the file starting at offset. |
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.
oldFile and newFile may be BCS or BAF files. If they
are BAF files they will not get the benefit of AUTO_TRA. |
or |
INSERT_BYTES offset value |
The first argument is the offset, the second argument is the count.
The file will be expanded at the given offset with count bytes worth of
zeroes. |
or |
DELETE_BYTES offset value |
The first argument is the offset, the second argument is the count.
The file will shrink as count bytes are deleted starting at the given
offset. |
or |
READ_BYTE offset variable |
An 8-bit value is read from the file at the given offset and is stored
in the given variable. |
or |
READ_SHORT offset variable |
A 16-bit value is read from the file at the given offset and is
stored in the given variable. |
or |
READ_LONG offset variable |
A 32-bit value is read from the file at the given offset and is
stored in the given variable. |
or |
READ_ASCII offset variable |
An 8-byte string is read from the file at the given offset and is
stored in the given variable. |
or |
SET variable = value |
Update variable so that it is equal to value. |
or |
WHILE value BEGIN
patch list END |
If value is non-zero, execute the given patch list and
then repeat, re-evaluating the value.
Be very careful when using this command. You can easily describe
an infinite loop. See the WHILE loop tutorial for more
information. |
or |
SET_2DA_ENTRY value value value
value |
The first value is the row, the second is the column and the
third is the required column count. The entry on the given column
of the given row is set to the fourth value, but only rows with
at least as many columns as the required column count are considered.
The fourth value, the new entry, is evaluated specially: if it
can be evaluated like a value (e.g., ``3+4'') it will be evaluated
and its integer result will be written as an ASCII string. Otherwise if
it is a single string (that is not a variable in scope) that string will
be written at the new value.
See
the SET_2DA_ENTRY tutorail for more information. |
or |
ADD_STORE_ITEM [ + ] itemName numCharges ext2 ext3
itemFlag maxInStack |
See the ADD_STORE_ITEM tutorial for more information. |
or |
READ_2DA_ENTRY value value value
variable |
The first value is the row, the second is the column and the
third is the required column count. The variable specified is set
to the the entry on the given column of the given row, but only column
with at least as many columns as the required column count are
considered. This is the reverse of SET_2DA_ENTRY. |
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. |
or |
BUT_ONLY_IF_IT_CHANGES |
True only if the file is actually
changed by patching actions. Unlike all other when clauses, this
one is evaluated just before the result would be written out to the disk. |
Predicate |
|
A Predicate allows you to conditionally
execute TP2 Actions using an ACTION_IF. |
is |
FILE_EXISTS filename |
True if the file exists in the
filesystem and has non-zero size. |
or |
FILE_EXISTS_IN_GAME filename |
True if the file exists as a
game resource and has non-zero size. BIFFs and the override
directory are searched in the standard manner. |
or |
FILE_MD5 filename md5sum |
True if the file exists and has
the given MD5 checksum. Two different files are exceptionally unlikely to
have the same MD5 checksum. In any event, the discovered checksum is
printed to the log. If the file does not exist, the predicate evaluates to
false. |
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 |
value |
Offsets are values. |
|
value |
|
An expression that evaluates to an integer. |
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 |
( value ) |
|
or |
value + value |
Addition. |
or |
value - value |
Subtraction. |
or |
value * value |
Multiplication. |
or |
value / value |
Division. Division by zero yields
the value zero. |
or |
value = value |
Equality. If the two values are
equal, the result is 1. Otherwise, the result is 0. |
or |
NOT value |
Negation. If the value is 0, the result is 1.
Otherwise the result is 0. |
or |
value OR value |
Disjunction. If either value is
non-zero, the result is 1. Otherwise, the result is 0. |
or |
value AND value |
Conjunction. If both values are
non-zero, the result is 1. Otherwise, the result is 0. |
or |
value BAND value |
Bitwise And.
0b101 BAND 0b110 = 0b100. |
or |
value BOR value |
Bitwise Or.
0b101 BOR 0b110 = 0b111. |
or |
value BXOR value |
Bitwise Exclusive Or.
0b101 BXOR 0b110 = 0b011. |
or |
value BNOT value |
Bitwise Not.
BNOT 0b111 = 0b1111111111111111111111111111000. |
or |
value BLSL value |
Bitwise Logical Shift Left.
0b101 BLSL 2 = 0b10100. |
or |
value BLSR value |
Bitwise Logical Shift Right.
0b101 BLSR 2 = 0b1. |
or |
value BASR value |
Bitwise Arithmetic Shift Right.
This is an arithmetic shift: the sign bit of the first value is replicated
and inserted in the vacated bits. 0b101 BASR 2 = 0b1. |
or |
value > value |
If the first value is
greater than the second, the result is 1. Otherwise, the result is 0. |
or |
value >= value |
If the first value is
greater than or equal to the second, the result is 1. Otherwise, the result
is 0. |
or |
value < value |
If the first value is
less than the second, the result is 1. Otherwise, the result
is 0. |
or |
value <= value |
If the first value is
less than or equal to the second, the result is 1. Otherwise, the result
is 0. |
or |
value ? value : value |
An expression-valued conditional. If the first value is not 0 then the
second value is evaluated and returned, otherwise the third value is
evaluated and returned. |
or |
%variable% |
The value of the variable is used. |
or |
NAME1 |
The offset within an infinity engine resource where the unidentified general name (e.g., "Battle Axe") is stored. |
or |
NAME2 |
The offset within an infinity engine resource where the identified general name (e.g., "K'logarath +4") is stored. |
or |
UNIDENTIFIED_DESC |
The offset within an infinity engine resource where the unidentified description (e.g., "The hand axe or throwing axe is also known as a hatchet ...") is stored. |
or |
IDENTIFIED_DESC |
As above ... ("Clans have gone to war to possess K'log...") |
or |
BIO |
As above ... NPC Biography |
or |
... |
Almost everything in SNDSLOT.IDS or SOUNDOFF.IDS
works as well. |
|
variable |
|
A variable is a textual name that holds a
value. Variables are usually set with READ_BYTE,
ADD_KIT or ADD_MUSIC. |
is |
string |
You may name the variable whatever you like, but stay
away from the special characters used in regexps. When you want to
obtain the value of a variable, enclose it in %s.
Example: If a file contains the two binary integers 33 and 77, then after
executing:
READ_LONG 0 ~myvar~
WRITE_LONG 4 ( ~%myvar%~ + 11 )
The file will contain the two binary integers 33 and 44. |
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.
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 1 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.
This tutorial was thoughtfully provided by Japheth.
READ_BYTE, READ_SHORT and READ_LONG can be
potentially powerful functions when used in conjunction with
IF_EVAL.
Here's a brief demonstration.
The scenario: I want to make all longswords in the game do 1D6 damage,
rather than their normal 1D8. To do that manually would be a pain, so why
not use the great feature of READ_BYTE/LONG/SHORT?
Here's how it's done.
First of all, we need to copy all the items so they are ready to be patched.
Here's how we do that:
COPY_EXISTING_REGEXP ~.*\.itm~ ~override~
All this does is copy all files with a .itm extension to the override folder.
Next, we read the item category offset into a variable. We'll use the variable
name "type".
READ_BYTE "0x1c" "type"
I found the offset by looking at an item file in Near Infinity. The
variable "type" can really be anything, it's just what I've chosen.
Now that we're done reading that into a variable, we can now
WRITE_SHORT the values we want to give our new longswords. Namely,
6 for damage and 1 for dice size. (Or, 1D6 for simplicity sake.)
WRITE_SHORT 0x88 6
WRITE_SHORT 0x8a 1
0x88 is the offset for dicesize and 0x8a is the offset for the number of
dice. Again, I found these offsets using Near Infinity beforehand.
Finally, we tell WeiDU that we only want to do this if the item file is infact a
longsword.
IF_EVAL ("%type%" = "0x14")
So, to call back our variable, you just wrap it with % signs. 0x14 is just
the value for longsword. If you look at the Category offset in Near
Infinity (which is 0x1c), and scroll down in the box below, you'll see
Longsword (20). The 20 just represents the decimal value. 0x14 is just 20
in hexadecimal.
So, to loop through the logic again, this is what we're saying:
If the category is longsword, make the dice size 6 and the dice damage 1. If
it's not a longsword, ignore it and don't copy it over.
And that's it. Now all items with the category Longsword will have 1D6 as their
dice damage. Pretty neat eh? Obviously you can do many, many other types of
thing with these functions. So go ahead and experiment.
Here's the full code:
COPY_EXISTING_REGEXP ~.*\.itm~ ~override~
READ_BYTE "0x1c" "type"
WRITE_SHORT 0x88 6
WRITE_SHORT 0x8a 1
IF_EVAL ("%type%" = "0x14")
Oh yeah, and if you're going "But I don't know when to READ_SHORT
or WRITE_LONG. It's so confusing," don't worry, cause
it kind of is if you've never worked with bytes before. Here's a very brief
synopsis.
Use READ/WRITE_SHORT for anything that is two bytes. You can determine this
easily by looking at the field you want to change in NI and subtracting the
next listed field's offset from the offset of the field you want to change.
For example, if I wanted to change Bodhi's Max HP here's what I would do.
First look at her creature file in NI. (She has many, but for the sake of
argument, we'll use bodhi.cre). If you look at the Max HP field, it says
it's offset is 26h. The next field in the creature file is Animation ID,
which is 28h. So, subtracting 28h from 26h gives you 2h. The perfect
WRITE_SHORT size.
Use READ/WRITE_LONG for anything that is 4 bytes. Use the above example to
figure this out as well.
Use READ/WRITE_BYTE for anything that is one byte. Again, use the
above example to figure this out.
Use WRITE_ASCII for things like death variable, dialogues, scripts,
etc. Note, that if you're WRITE_ASCIIing to say, the death
variable, and the previous death variable entry is longer than the one
you're writing, then you'll have spillover after WRITE_ASCII. To
avoid this, embed a null character at the end of your string. So, if I have
WRITE_ASCII 0x280 ~Guy~
I would want to put a null character right after the y in Guy. You can do
this by using a hex editor and inserting 00 after the y.
These are by no means "hard and fast" rules, but generally they ring true.
However, there could be times when you need to WRITE_LONG twice if
the field is 8 bytes long, etc.
And that, as they say, is that.
This tutorial was thoughtfully provided by Japheth.
Using expressions with WeiDU can get quite complex, however the power they
give you is more than worth the effort in learning how to use them.
Say, for instance, that you want to give a creature file a shield that
doesn't have one originally. The old way to do this would be to add the
item to the creature file using Near Infinity or ShadowKeeper and then copy
that creature file over upon installation of your mod. However, now that
INSERT/DELETE_BYTES and READ/WRITE_BYTE/LONG/SHORT take expressions, we can
do this "on the fly" when installing your mod.
The benefits of doing it this way should be obvious. Now you no longer
have to overwrite someone else's modifications to that creature file. You
can simply add your shield and leave their modifications intact. (Unless
of course *they* also decided to give the exact same creature a shield.
And if that's the case, the shield you specify won't be used because of our
IF_EVAL statement.)
Anyways, on to the example. I'll use acolyte1.cre as my example creature.
The first thing we want to do is copy the creature over so it's ready for
patching. That's easy to do using a simple COPY_EXISTING
statement:
COPY_EXISTING ~acolyte1.cre~ ~override/acolyte1.cre~
Next, we need to read in the offsets so we know where to insert our bytes and
where to update our number of items. I figured out these offsets by simply
viewing the creature file in Near Infinity.
The offsets we need to read in are the item slots offset, the items offset and
the number of items. This is how we do it:
READ_LONG "0x2bc" "itemsoffset"
READ_LONG "0x2b8" "itemslot"
READ_LONG "0x2c0" "#items"
"itemsoffset", "itemslot" and "#items" can be anything you choose, but I
would name them something that's easy to remember because we'll be using
them later.
Next, we're going to read in the shield slot to make sure that it's empty.
We do this by taking the "itemslot" variable and adding 0x04 to it which we
know is always going to be the shield slot. (You can verify this by
looking at a creature file in NI. If you look at the offset 0x2b8 it will
have the offset for item slots. If you add 0x04 to that number, it should
equal the offset for the shield slot.)
This is done like so:
READ_SHORT ("%itemslot%" + 0x04) "shield"
Note: To access your variables after they've been read, you always have to
wrap them in percent signs.
Now, we have to update the shield slot to reflect that it's the newest item
out of all the items the creature has equipped. This is a bit misleading
because in the slots part of the creature file, they count from 0. So, all
we need to do is take the value of 0x2c0 (The number of items the creature
has.) and use that as our value to write.
To do that, this is what we do:
WRITE_SHORT ("%itemslot%" + 0x04) "%#items%"
Now we have to update the itemslot offset value to reflect the fact that
we've added a new item to the creature. An item is *always* 0x14 bytes.
We already know the offset of the itemslot offset and we've already read
it's value, so all we need to do is add 0x14 to it
WRITE_LONG 0x2b8 ("%itemslot%" + 0x14)
The last step before inserting our bytes is to increase the #items by 1 to
reflect the fact that we've added an item to the creature. We've already
read in the number of items into "%#items%" so this is dead easy:
WRITE_LONG 0x2c0 ("%#items%" + 1)
Now we can actually insert out bytes. To do this we have to take the
number of items that the creature has multiplied by 0x14 and add that to
the itemsoffset. (The reason we can't just insert it right at
"%itemsoffset%" is because it would mess up the order of the items in game.
If we inserted the shield at the beginning, then the creature would more
than likely have some items equipped in some weird places in the game.)
This is done like so:
INSERT_BYTES ("%itemsoffset%" + "%#items%" * 0x14) 0x14
Now we can actually write our information for our item. You don't need the
.itm extension at all, just everything before the .itm. And again, since
the item entry is always the first entry in an item field on a creature
file, we just need to use the same formula as above because we'll be
writing at the same offset that we inserted our bytes at:
WRITE_ASCII ("%itemsoffset%" + "%#items%" * 0x14) ~shld01~
If you wanted to add a magical shield to the creature file and wanted it to
be already identified in the slot, then you would simply have to add 0x10
bytes to the previous expression and WRITE_LONG the value of 1.
Like this:
WRITE_LONG ("%itemsoffset%" + ("%#items%" * 0x14) + 0x10) 1
Finally, we want to tell WeiDU only to do this if the shield slot is
*empty*. This is where IF_EVAL comes in handy. The way a creature
file is setup is that if a slot is set to -1, it means it's empty. WeiDU
doesn't read unsigned bytes, so if the value is indeed -1, it will come out
as 65535 as the value. So, this is the statement we use to tell WeiDU only
to execute all of the above code if "%shield%" is -1:
IF_EVAL ("%shield%" = "65535")
Here's the full code:
COPY_EXISTING ~acolyte1.cre~ ~override/acolyte1.cre~
READ_LONG "0x2bc" "itemsoffset"
READ_LONG "0x2b8" "itemslot"
READ_LONG "0x2c0" "#items"
READ_SHORT ("%itemslot%" + 0x04) "shield"
WRITE_SHORT ("%itemslot%" + 0x04) "%#items%"
WRITE_LONG 0x2b8 ("%itemslot%" + 0x14)
WRITE_LONG 0x2c0 ("%#items%" + 1)
INSERT_BYTES ("%itemsoffset%" + "%#items%" * 0x14) 0x14
WRITE_ASCII ("%itemsoffset%" + "%#items%" * 0x14) ~shld01~
IF_EVAL ("%shield%" = "65535")
Note that you can use the PRINT action in a tp2 to debug
your code as well. Like this:
COPY_EXISTING ~acolyte1.cre~ ~override/acolyte1.cre~
READ_LONG "0x2bc" "itemsoffset"
READ_LONG "0x2b8" "itemslot"
READ_LONG "0x2c0" "#items"
READ_SHORT ("%itemslot%" + 0x04) "shield"
WRITE_SHORT ("%itemslot%" + 0x04) "%#items%"
WRITE_LONG 0x2b8 ("%itemslot%" + 0x14)
WRITE_LONG 0x2c0 ("%#items%" + 1)
INSERT_BYTES ("%itemsoffset%" + "%#items%" * 0x14) 0x14
WRITE_ASCII ("%itemsoffset%" + "%#items%" * 0x14) ~shld01~
IF_EVAL ("%shield%" = "65535")
PRINT ~The value of the items offset is %itemsoffset%~
I should also mention that the above code for adding an item to a creature
file is "portable" in a sense. You would only have to change a couple
things. The changes that would have to be made are as follows:
-
Change acolyte1.cre to the actual creature that you want to modify
- You'll have to change ("%itemslot%" + 0x04) to something else if you
are adding anything else but a shield. You can figure this out by
subtracting the slot offset of the slot you want to modify from the item
slots offset.
- You would need to change shld01 to the item you are going to add.
- You'd have to add your own flags if they need adding (i.e.
Identified, Not Stealable, etc.).
And that's the end of it. All this can be a bit tricky, but as I mentioned
above, it can be quite powerful as well.
This tutorial and this feature were thoughtfully provided by Japheth.
Using ADD_STORE_ITEM is a relatively painless procedure. Consider this
example: I want to add a new item to ribald.sto in BGII. There were
two ways of doing this before.
-
I could add the item to the store beforehand and then simply copy over
the new store file upon installing my mod.
- I could use WeiDU's READ_BYTE and WRITE_BYTE patch
expressions to patch the item into the store while installing.
Now we can do #2 rather easily thanks to ADD_STORE_ITEM.
First off copy over the store file to set it up for patching as you
normally would.
COPY_EXISTING ~ribald.sto~ ~override/ribald.sto~
Next we issue the ADD_STORE_ITEM patch expression with the following
arguments:
ADD_STORE_ITEM "myitem" #10 #0 #0 ~IDENTIFIED~ #5
``myitem'' is the name of the item file that we will want to appear in the
store without it's .itm extension.
#10 is the first extension headers number of charges
The two #0s that follow are the second and third extension headers number
of charges.
IDENTIFED is the flag that we want on the item. Here are the flags
that you can use:
IDENTIFIED
UNSTEALABLE
STOLEN
IDENTIFIED&STOLEN
IDENTIFIED&UNSTEALABLE
#5 is the number of items that will be in stock.
Pretty easy stuff right?
You can also add an optional + after ADD_STORE_ITEM if you want to
overwrite an item that already exists in the store.
So, if we wanted to replace HAMM05.ITM in ribald.sto this is what we'd
do:
ADD_STORE_ITEM + ~hamm05~ #10 #0 #0 ~IDENTIFIED~ #5
One final note: remember to copy over your new item because
ADD_STORE_ITEM doesn't do that part for you.
So, a complete set of actions that would patch a store file and copy over
the new item would look like this:
COPY_EXISTING ~ribald.sto~ ~override/ribald.sto~
ADD_STORE_ITEM ~myitem~ #10 #0 #0 ~IDENTIFIED~ #5
COPY ~mymod/myitem.itm~ ~override/myitem.itm~
That's about all there is to it.
This tutorial and this feature were thoughtfully provided by Japheth.
In BGII, the game engine was refined so that when any NPC joined your party
they would be added to the baldur.gam file, regardless of the fact if they
were in there already or not.
Sadly, in BG1 this is not the case. If you were to simply CreateCreature
any old NPC and add them to your party, as soon as you moved to another
area the game would crash and die.
This is where ADD_GAME_NPC comes in handy for all you BG1 modders out there.
By using ADD_GAME_NPC you can patch your NPC into all the baldur.gam
files on an end users install. This includes the default GAM file
that's loaded up when you start a new game as well as all the GAM
files in the save and mpsave directories.
Note: However, there really is no foolproof way to back up all the save
games, so you may want to warn the end user in a readme to manually back
them up themselves beforehand.
Anyways, I digress.
To use ADD_GAME_NPC you have to first copy over the CRE file that
you will want to appear in the GAM file. I'm pretty sure we all
know how to do this, but for completeness, here it is:
COPY ~mymod/mynpc.cre~ ~override/mynpc.cre~
After the COPY statement, make sure you do all your SAYs
and any other type of patching that you'll want to do for your .cre file.
You will always want to use ADD_GAME_NPC last since you want the
updates to your cre file to be reflected in the GAM file as well.
Here's a typical patch statement for most NPC mods:
COPY ~mymod/mynpc.cre~ ~override/mynpc.cre~
SAY NAME1 ~Japh~
SAY NAME2 ~Japh~
So, after doing all that, we would issue ADD_GAME_NPC with the following
arguments:
ADD_GAME_NPC "mynpc" "ar2600" #123 #456
``mynpc'' is the name of your NPCs cre file that you're copying over without
the .cre extension.
``ar2600'' is the area that you want him/her placed in. In this case we're
placing the NPC in Candlekeep.
#123 is the x co-ordinate on the map.
#456 is the y co-ordinate on the map.
That's all there is to it. Just to recap, here's the full set of
expressions we would use:
COPY ~mymod/mynpc.cre~ ~override/mynpc.cre~
SAY NAME1 ~Japh~
SAY NAME2 ~Japh~
// Do any other patching stuff here
ADD_GAME_NPC ~mynpc~ ~ar2600~ #123 #456
Note: Again, I have to stress the importance of warning the end users to
backup their save and mpsave directories beforehand. There really is no
elegant way to do this via WeiDU (well, maybe there is, but I can't think
of a way) so I'd just warn them.
This tutorial was thoughtfully provided by Idobek.
There have been some queiries about SET_2DA_ENTRY in the WeiDU
forum recently. This is a consolidation of my posts on the subject.
The command takes this form:
SET_2DA_ENTRY value value value newEntry
The first value is the row, the second is the column and the third is the
required column count. The entry on the given column of the given row is
set to newEntry, but only rows with at least as many columns as the
required column count are considered. The upper-left entry is 0,0. For
example, given the following 2DA file:
2DA V1.0
*
ROWNAME LOWER MIXED HELP
0 RESERVE * * *
1 BERSERKER 25179 25151 25201
2 WIZARD_SLAYER 25180 25152 25203
3 KENSAI 25181 25153 25204
4 CAVALIER 25182 25154 25206
Then the patch:
SET_2DA_ENTRY 3 1 5 ~SAMURAI~
would result in:
2DA V1.0
*
ROWNAME LOWER MIXED HELP
0 RESERVE * * *
1 BERSERKER 25179 25151 25201
2 WIZARD_SLAYER 25180 25152 25203
3 SAMURAI 25181 25153 25204
4 CAVALIER 25182 25154 25206
So the columns and rows you want WeiDU to consider are:
Column0 Column1 Column2 Column3 Column4
ROWNAME LOWER MIXED HELP
Row0: 0 RESERVE * * *
Row1: 1 BERSERKER 25179 25151 25201
Row2: 2 WIZARD_SLAYER 25180 25152 25203
Row3: 3 KENSAI 25181 25153 25204
Row4: 4 CAVALIER 25182 25154 25206
The total number of columns is five. If, however, you put 1 as your
required column count then you tell WeiDU you want to consider all rows
with at least 1 column. So your row numbers change:
Row0: 2DA V1.0
Row1: *
Row2: ROWNAME LOWER MIXED HELP
Row3: 0 RESERVE * * *
Row4: 1 BERSERKER 25179 25151 25201
Row5: 2 WIZARD_SLAYER 25180 25152 25203
Row6: 3 KENSAI 25181 25153 25204
Row7: 4 CAVALIER 25182 25154 25206
So the required column count is what you use to tell WeiDU which row you
want to use as row0. So using the code
SET_2DA_ENTRY 3 1 1 ~SAMURAI~
would result in:
2DA V1.0
*
ROWNAME LOWER MIXED HELP
0 SAMURAI * * *
1 BERSERKER 25179 25151 25201
2 WIZARD_SLAYER 25180 25152 25203
3 KENSAI 25181 25153 25204
4 CAVALIER 25182 25154 25206
Hopefully, that explains the required column count a little better.
If you have tried any of the above code you will have found that the
results are not quite as described.
SET_2DA_ENTRY 3 1 5 ~SAMURAI~
actually resulted in:
2DA V1.0
*
ROWNAME LOWER MIXED HELP
0 RESERVE * * *
1 BERSERKER 25179 25151 25201
2 WIZARD_SLAYER 25180 25152 25203
3 SAMURAI 25181 25153 25204
4 CAVALIER 25182 25154 25206
This, I imagine, is not what you were expecting. Well don't worry: Nothing
is wrong and the file will work fine. Not aesthetically pleasing
though. To fix this all you need to do is add another column header using
SET_2DA_ENTRY on a file and removing it once you are done, like so:
SET_2DA_ENTRY 0 0 4 ~IDOBEK ROWNAME~
SET_2DA_ENTRY 6 1 1 ~SAMURAI~
SET_2DA_ENTRY 0 0 5 ~~
That's the end of this tutorial.
This tutorial was thoughtfully provided by Idobek.
So what is a WHILE loop? Well very simply put it is a way of
applying the same patch to a file multiple times under a different
condition each time. Or it is a way of applying different patches depending
upon the conditions. This example will show both of these methods. Keeping
in theme we are going to modify the damage done by axes. Now, some axes
have both a melee and a ranged ability. We are going to want to
change both of these abilities but give different damage to ranged and
melee.
First, as always, we need to copy the files ready for patching:
COPY_EXISTING_REGEXP ~.*\.itm~ ~override~
We only want to modify axes so let's find out what the item is:
READ_BYTE 0x1c "category"
Once we are done with our patching we will use IF_EVAL to tell
WeiDU to only patch axes:
IF_EVAL ("%category%" = 25)
Now, we need to find out how many abilities there are and where they are
located:
READ_LONG 0x64 "abilitiesoffset"
READ_SHORT 0x68 "#abilities"
So far, so good. Here's where the fun begins. We want to patch examine
every ability in the item. We need to use a WHILE loop to patch the
abilities one by one. We open the loop like so:
WHILE ("%#abilities%" > 0) BEGIN
For the WHILE loop to progress and close we need to modify the
"#abilities" variable. We use the SET command to do this:
SET "#abilities" = ("%#abilities%" - 1)
END
These lines are placed at the end of the WHILE loop. WeiDU will
process the abilities from last to first and reduce the "#abilities"
variable by one each time. Once the "#abilities" variable hits zero the
WHILE loop will stop.
OK, now we need to find out what type of ability we are looking at. Within
an item an ability is 0x38 bytes, abilities also count from 0 within items
so we must take this into account:
READ_BYTE ("%abilitiesoffset%" + ("%#abilities%" - 1) * 0x38) "abilitytype"
Now we get to the meat of the patch. This patch is conditional on the
ability type we have just read. First we will deal with the melee damage
patch. We are going to be using another WHILE loop to do this and we
are going to need a method to tell the loop to stop patching. So the first
thing we need to do is define a new variable.
SET "patchmelee" = 0
Having done this we can open a new loop:
WHILE ("%abilitytype%" = 1)
AND ("%patchmelee%" = 0) BEGIN
So we are patching when the ability type is 1 (melee) and when our melee patching variable is 0. Time for the actual patch:
WRITE_SHORT ("%abilitiesoffset%" + ("%#abilities%" - 1) * 0x38 + 0x16) 6
WRITE_SHORT ("%abilitiesoffset%" + ("%#abilities%" - 1) * 0x38 + 0x18) 2
Time to tell this loop to stop. This is simply a case of setting our melee patching variable to 1.
SET "patchmelee" = 1
END
We use exactly the same method for ranged abilities:
SET "patchranged" = 0
WHILE ("%abilitytype%" = 2)
AND ("%patchranged%" = 0) BEGIN
WRITE_SHORT ("%abilitiesoffset%" + ("%#abilities%" - 1) * 0x38 + 0x16) 12
WRITE_SHORT ("%abilitiesoffset%" + ("%#abilities%" - 1) * 0x38 + 0x18) 1
SET "patchranged" = 1
END
We can now close the main loop (using the aforementioned method):
SET "#abilities" = ("%#abilities%" - 1)
END
We are done with our patches so we can add our IF_EVAL statement here.
IF_EVAL ("%category%" = 25)
The full code is:
COPY_EXISTING_REGEXP ~.*\.itm~ ~override~
READ_BYTE 0x1c "category"
READ_LONG 0x64 "abilitiesoffset"
READ_SHORT 0x68 "#abilities"
WHILE ("%#abilities%" > 0) BEGIN
READ_BYTE ("%abilitiesoffset%" + ("%#abilities%" - 1) * 0x38) "abilitytype"
SET "patchmelee" = 0
WHILE ("%abilitytype%" = 1)
AND ("%patchmelee%" = 0) BEGIN
WRITE_SHORT ("%abilitiesoffset%" + ("%#abilities%" - 1) * 0x38 + 0x16) 6
WRITE_SHORT ("%abilitiesoffset%" + ("%#abilities%" - 1) * 0x38 + 0x18) 2
SET "patchmelee" = 1
END
SET "patchranged" = 0
WHILE ("%abilitytype%" = 2)
AND ("%patchranged%" = 0) BEGIN
WRITE_SHORT ("%abilitiesoffset%" + ("%#abilities%" - 1) * 0x38 + 0x16) 12
WRITE_SHORT ("%abilitiesoffset%" + ("%#abilities%" - 1) * 0x38 + 0x18) 1
SET "patchranged" = 1
END
SET "#abilities" = ("%#abilities%" - 1)
END
IF_EVAL ("%category%" = 25)
So what have we done? Let's list the steps involved:
-
First off we copied all the item files ready for patching.
- Then we the read in the item category.
- We also read in the number of abilities and the file location of those abilities.
- We open a WHILE loop based upon the number of abilities.
- Within the WHILE loop, we read in the ability type.
- Next we defined a new variable to indicate that we had not yet patched any melee type abilities.
- We opened another WHILE loop based upon ability type and our patch melee variable.
- We patched melee abilities to do 2D6 damage.
- We closed this WHILE loop by indicating the the melee ability had been patched.
- We repeated steps 6-9 to give the ranged abilities 1D12 damage.
- We closed our main WHILE loop by reducing the number of abilities by 1 and indicating that it should stop if the number of abilities reached 0.
- We used an IF_EVAL statement to ensure the patch is only applied to axes.
It is important to note that this method has assumed that the item only has
one (or none) ranged ability and one (or none) melee ability. If an item
has two melee abilites (or two ranged abilities) this code will only patch
one of them.
That's the end of this tutorial.
This tutorial was thoughtfully provided by CamDawg.
This was a topic that I struggled with while trying to convert some kits
with unusual item restrictions. Thanks to WeiDU's new bitwise operators,
this is a process that can done dynamically and non-destructively and
affect all items of a particular type, even if added or altered by another
mod (assuming yours is installed after others of course ). The basic idea
is to construct a function in WeiDU that:
-
Searches through all item files in the game
- Reads selected data from an item (type, usability, etc)
- Alters specific data on an item without changing anything else
This tutorial is basically an expansion on Japheth's excellent
READ/BYTE/LONG/SHORT tutorial and I suggest you take a look at that before
going further.
My initial problem came about when trying to make specific items usable or
unusable by a particular class/race/kit. If you look at the item file
structure at IESDP, you see that all of the unusability flags of an item
are controlled by the individual bits of bytes 0x1E, 0x1F, 0x20 and 0x21
for class/race alignment restrictions and 0x29, 0x2B, 0x2D and 0x2F for the
individual kit restrictions. (Near Infinity combines and displays the four
bytes of class/race restrictions as a single chunk of data.)
A quick aside about notation before I proceed. Numbers in binary (the
strings of bits) are preceded with 0b and hexadecimal numbers (typically
the bytes) are preceded with 0x. Eight bits make a byte and bits read
right-to-left. So if the second bit is 1 and the rest 0, then the bit would
be written as 0b00000010.
If you were trying to alter the usability of a specific class or race,
simply using a WRITE_BYTE command on any of these particular bytes
would result in changing the usability of an item by all the classes in the
particular byte. The new bitwise operators provide an easier solution.
First we need to look at what the new operators BAND and
BOR do with bits. They are both ways of combining two bytes, based
on different rules. Both BAND and BOR compare the
individual bits (bit 0 vs bit 0, bit 1 vs bit 1, etc. all the way through
bit 7 vs bit 7) of two bytes. For each individual bit, the following tables
are used to determine the value:
0 BAND 0 = 0
0 BAND 1 = 0
1 BAND 0 = 0
1 BAND 1 = 1
0 BOR 0 = 0
0 BOR 1 = 1
1 BOR 0 = 1
1 BOR 1 = 1
So if byte 0x23 is 0b00110101 and byte 0x24 is 0b10010001, then 0x23 BAND
0x24 is 0b00010001 whereas 0x23 BOR 0x24 is 0b10110101.
Back to relating this to unusability in items. For an item to be flagged as
unusable, the corresponding bit must be set to 1. A 0 means a particular
item is usable by the class/race. Let's look at the example of changing
mage robes usable by bards. This would go into your TP2 file.
COPY_EXISTING_REGEXP GLOB ~.*\.itm~ ~override~ //copies all item files
READ_BYTE "0x1E" "bard" //reads the byte containing bard usability flag
READ_BYTE "0x20" "mage" //reads the byte containing the mage usability flag
READ_SHORT "0x1C" "type" //reads the byte containing item type
WRITE_BYTE "0x1E" ("%bard%" BAND "0b10111111") // makes usable by bards
IF_EVAL (("%mage%" BAND "0b00000100") = "0b00000000") // if it is usable by mages
IF_EVAL
("%type%" = "67") OR // and if it is a robe
("%type%" = "2") // or armor
This is the same basic idea in Japheth's BYTE tutorial , except now we're
utilizing the bitwise operators in the writing and evaluation commands. The
mage usability flag is the bit 2 in byte 0x20 (IESDP). By checking "%mage%"
BAND 0b00000100, the values of any of the 7 other bits (0-1,3-7) are set to
0, whereas bit 2 is equal to 0 if and only if it is 0 to begin with.
Therefore the statement will only be true if the item is usable by mages
(bit 2 = 0). If that is true and the item is a robe or armor, then we write
("%bard%" BAND "0b10111111") into the bard usability byte, 0x1E. By using 1
in bits 0-5 and 7, this ensures that the original value of the flag is
preserved, and using 0 in bit 6 ensures that bit 6 is set to 0 regardless
of its previous value--making it usable by bards whether it was before or
not.
Just as an aside, you could read the entire usability block with a
READ_LONG at 0x1E. However, it becomes a pain because then you need
to start writing out all 32 bits when doing the bitwise operations.
Let's try another example. Let's try making large weapons such as
two-handed swords and halberds unusable by short folks--gnomes, halflings,
and dwarves.
COPY_EXISTING_REGEXP GLOB ~.*\.itm~ ~override~ //copies all item files
READ_BYTE "0x21" "race" //reads the byte containing race usability flags we're interested in
READ_BYTE "0x31" "prof" //reads the byte containing item type
WRITE_BYTE "0x21" ("%race%" BOR "0b00010101") // makes unusable by dwarves, halflings, and gnomes
IF_EVAL
("%prof%" = "93") OR // two-handed sword
("%prof%" = "99") // or halberd
In this case, rather than reading the 'type' byte of the item, I've opted
for the 'proficiency' byte. Many two-handed swords in BG2 are classed as
long swords for some reason so in this case proficeiency is a better
indicator if it is a two-handed sword IMHO.
The unusability flags for dwarves, halflings and gnomes are all in byte
0x2F at bits 0, 2, and 4 respectively (thanks again IESDP). By writing
"%race%" BOR "0b00010101" to byte 0x2F we're preserving the values of bits
1,3, and 5-7 while setting the 0, 2, and 4 bits to 1 (unusable) regardless
of their previous values.
Thanks especially to Smoketest for helping me with this, Japheth for the
tutorial that inspired this and the help given, and the IESDP team.
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:
-
--tlkout DIALOG.TLK
- --ftlkout DIALOGF.TLK (if DIALOGF.TLK is present)
- --log MyMod.DEBUG
- Setup-MyMod.tp2
- MyMod.tp2
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.
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.
This section briefly explains some common file formats. The definitive
reference is
http://dragonlance.teambg.net/iesdp/index.htm.
- 2DA. A 2DA file is a text file that includes some game rules
or file information. Kits, music, weapon proficiencies and experience point
caps are all controlled by 2DA files.
- BIFF. A BIFF file is like a big ZIP file that contains other
files. Basically every file used by an Infinity Engine game is either in a
BIFF somewhere or in the override folder.
- BCS. An Infinity Engine script. These are used to controll
the actions of creatures and areas. They are basically big lists of
IFs. D files (and also DLG files) use the same
predicate and action syntax as BCS files.
- EFF. An Infinity Engine effect file. These are somtimes used
by ITMs or SPLs to do one specific thing. You probably
won't use them.
- ITM. An Infinity Engine item definition file. Notably,
ITM files have identified and unidentified names that are often set
using the TP2 Action COPY.
- KEY. A KEY file is an index into BIFF files. Given a
resource (like sw1h01.itm) you can use the KEY file to find out where
it is in the BIFFs. The default KEY file is CHITIN.KEY.
- SPL. An Infinity Engine spell definition file. Spells have
names that are often set using the TP2 Action COPY.
- TLK. A TLK file is basically a big array of strings. Items
and spells and dialogues in the game have references into this big array
instead of having actual text in them. So a sword item doesn't have the
text "Longsword" anywhere in it. Instead if says "use strref #1234 for my
name". The game looks up the #1234th entry in the TLK file and prints that
on the screen. The default TLK file is called DIALOG.TLK.
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.06 (or newer), sed (or Perl
5.6), make and either gcc or (optionally) cl (the 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.
WeiDU uses the GPL'd zlib and xdiff libraries.
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://dragonlance.teambg.net/iesdp/index.htm.
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.
Kudos to my main out-of-house developer:
-
Fred S. Richardson (he slices, he dices, he sends diffs!)
Special thanks to my main bug-finders *cough* I mean beta-testers:
-
Jason Compton (and his witty wife)
- Ghreyfain
- Michael Lyashenko
- Quitch
- Dyara
- M
- Rene Heroux
- Avenger TeamBG
- Sim
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. Scott
McPeak is the first person to mention WeiDU to me in a face-to-face
conversation (and he was quite well-informed).
Currently undocumented:
-
We might want to mention somewhere that we compile BAF files.
- IF_EVAL.
- --extract-kit.
- COPY_RANDOM, RANDOM_SEED.
- REPLACE_BCS_BLOCK_REGEXP.
- Fred's BCS diff stuff.
-
--transitive, 6
- 2DA, 12
- ?, 8
- ACTION_IF, 8
- ADD_GAME_NPC, 9.6
- ADD_KIT, 8
- ADD_MUSIC, 8
- ADD_PROJECTILE, 8
- ADD_STATE_TRIGGER, 4
- ADD_STORE_ITEM, 9.5
- ADD_TRANS_TRIGGER, 4
- ALLOW_MISSING, 8
- ALWAYS, 8
- AND, 8
- APPEND, 4
- APPEND_COL, 8
- APPENDI, 4
- APPLY_BCS_PATCH, 8
- APPLY_BCS_PATCH_OR_COPY, 8
- ASK_EVERY_COMPONENT, 8
- AT_EXIT, 8
- AT_INTERACTIVE_EXIT, 8
- AT_INTERACTIVE_UNINSTALL, 8
- AT_UNINSTALL, 8
- AUTHOR, 8
- AUTO_TRA, 8
- BACKUP, 8
- BAND, 8
- BASR, 8
- BCS, 12
- BEGIN, 4
- BIFF, 12
- BLSL, 8
- BLSR, 8
- BNOT, 8
- BOR, 8
- BUT_ONLY_IF_IT_CHANGES, 8
- BXOR, 8
- CHAIN, 4
- CHAIN2, 4
- COMPILE, 8
- COPY, 8
- COPY_EXISTING, 8
- COPY_EXISTING_REGEXP, 8
- COPY_TRANS, 4
- Component, 8
- Component Flag, 8
- chainEpilogue, 4
- chainText, 4
- D Action, 4
- D File, 4
- DELETE_BYTES, 8
- DEPRECATED, 8
- DEST_DIRECTORY, 8
- DEST_FILE, 8
- DEST_FILESPEC, 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_EXISTS_IN_GAME, 8
- FILE_MD5, 8
- FILE_SIZE, 8
- FLAGS, 4
- FORBID_COMPONENT, 8
- FORBID_FILE, 8
- GLOB, 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
- optGlob, 8
- optNoBackup, 8
- PRINT, 8
- Predicate, 8
- patch, 8
- READ_2DA_ENTRY, 8
- READ_ASCII, 8
- READ_BYTE, 8
- READ_LONG, 8
- READ_SHORT, 8
- REPLACE, 4
- REPLACE_ACTION_TEXT, 4
- REPLACE_ACTION_TEXT_PROCESS, 4
- REPLACE_BCS_BLOCK, 8
- REPLACE_SAY, 4
- REPLACE_STATE_TRIGGER, 4
- REPLACE_TEXTUALLY, 8
- REPLACE_TRIGGER_TEXT, 4
- REPLY, 4
- REQUIRE_COMPONENT, 8
- REQUIRE_FILE, 8
- regexp, 11
- replyText, 4
- SET, 8
- SET_2DA_ENTRY, 9.7
- SET_WEIGHT, 4
- SOLVED_JOURNAL, 4
- SOURCE_DIRECTORY, 8
- SOURCE_FILE, 8
- SOURCE_FILESPEC, 8
- SPL, 12
- STRING_SET, 8
- String, 4
- sayText, 4
- state, 4
- stateActionString, 4
- stateLabel, 4
- stateNumber, 4
- stateTriggerString, 4
- TLK, 12
- TP2, 8
- TP2 Action, 8
- TP2 File, 8
- TP2 Flag, 8
- TRA, 7.7
- text, 4
- transFeature, 4
- transition, 4
- transNext, 4
- transTriggerString, 4
- UNINSTALL, 8
- UNLESS, 8
- UNSOLVED_JOURNAL, 4
- USING, 8
- value, 8
- variable, 8
- WEIGHT, 4
- WHILE, 8
- WRITE_ASCII, 8
- WRITE_BYTE, 8
- WRITE_EVALUATED_ASCII, 8
- WRITE_LONG, 8
- WRITE_SHORT, 8
- when, 8
|
(If for some reason you are translating this file or any other
Weimer-written file, do not translate this "Changes" section.)
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 strings 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.
Version 92:
* Fixed a decompiler bug where the wrong trigger (e.g.,
HaveSpellRES instead of HaveSpell) might be printed.
* WeiDU now checks actions and triggers in D files.
* Documentation is no longer "too wide" for easy viewing. Thanks to
Tapio Kivikkola for pointing this out.
* Roland has taken up the "compile WeiDU for Mac users" mantle.
Thanks!
* Fixed a problem with CHAIN forgetting some actions. Thanks,
Quitch.
* CHAIN3 now supports weights for its intial condition.
Version 93:
* Fixed a problem with IDS files that have too much whitespace.
Version 94:
* Fixed a problem with I_C_T reversing transition lists. Thanks for
making it perfectly clear, Jason.
Version 95:
* We now verify D-file transition conditions. They were skipped before.
* Invalid actions and trigger lists in D files are now only
WARNINGS, as they should have been.
* Location information for actions and trigger lists in D files
is now much more accurate. Still not perfect, but at least it's
on the right line.
* BCS->BAF decompilation no longer throws up a bunch of warnings
about "this file not found".
* Added Japheth's R_C_T tutorial.
* In BAF-land, typing things like "SeE(MySelF)" instead of
"See(Myself)" is now only a warning.
* If you say something like "LastSeenBy()" you are now given an
explicit warning to use "LastSeenBy(Myself)" instead. The first
one sometimes gets weird treatment by the game engine.
* Fixed a case bug in APPENDI. Thanks, Quitch.
* We are throwing a surprise 50th birthday party for my mother today.
Wish us luck.
Version 96:
* Parse errors and warnings are now logged.
* XOR-encrypted 2DA files are now decrypted. Previously only IDS
files were decrypted.
Version 97:
* Added support for reading in some broken IDW DLG files (they come
with out-of-range triggers/actions).
* Added support for reading in some broken BCS files (they omit
a non-optional action parameter).
* ALLOW_MISSING is no longer case sensitive. Thanks, Carl Grenthe.
* Fixed a lexer error involving unterminated // comments. Thanks,
Moonfruit.
* We now handle cd paths like "C:\CD2;E:\Other\CD2".
* AUTO_TRA files are now associated only with their (single)
associated D files and not with any other files. Thanks, Dyara
and Quitch.
* Fixed "See(LastSeenBy(Nearest([EVILCUTOFF])))" ->
"See(LastSeenBy(Nearest)([EVILCUTOFF]))"
problem. Thanks, Dyara.
Version 98:
* Fixed an [ANYONE] problem in BCS->BAF world. Thanks, Dyara.
* Fixed a problem where re-installing a mod with an error in the
new version would give you "yes/no/uninstall" instead of
"yes/no". Thanks, Quitch.
Version 99:
* CHAIN "IF"s are now checked for validity.
* Setup-Foo will now copy itself over all older Setup-Bar's in the
same directory.
* You can now COMPILE and EXTEND BAF files. See Setup-Solaufein.tp2.
* BAF files can now take ~String Refs~ and @130 trans refs.
Example:
DisplayStringHead(Myself,~Flame Arrow~)
DisplayString("Sola",@144)
Version 100:
* Fixed a key path-separator problem. Thanks, Devon.
* COMPILE ~foo.baf~ will now uninstall correctly. Thanks, Japh.
Version 101:
* Unknown IDS entries (like ToB spells in SoA's SPELL.IDS) now
default to 0. This was causing Sola installing problems.
Version 102:
* Added READ_BYTE, etc. WRITE_BYTE and friends can now use compound
expressions (possibly involving variables). See IWD2-Ease.
* Added COPY_RANDOM.
* Added the --biff-get-rest option, which basically applies
--biff-get to all the rest of the command line arguments.
Version 103:
* Added ASK_EVERY_COMPONENT TP2 flag for Jason. Put up near
AUTO_TRA.
Version 104:
* One last sanity check for auto-update: ensure that when we copy A
over B we set B's timestamp to that of A.
* You may say "COMPILE ~mymod/big-folder-of-scripts~" and every D
or BAF file in that folder will get compiled. This works just
like giving COPY a directory.
Version 105:
* Fix a WEIGHT problem. Thanks, GB.
* Added --min and --max which work with --tlkcmp and --string.
Fixed a minor bug in --tlkcmp. Thanks Harden Coonor.
Version 106:
* IDENTIFIED_DESC now works as an offset.
* Added SOLVED_JOURNAL and UNSOLVED_JOURNAL keywords. Thanks,
Avenger.
Version 107:
* Fixed a bug where the temp file for a big compressed biff would
not be removed if weidu choked because the biff was way too big
(or any other reason).
* Actually, the entire handling of compressed biffs has been changed.
Now we only do on-demand decompression. For example, the time to
extract AR0602.WED drops from 9.6 seconds to 0.3 on my machine.
No more temporary files at all.
* Now faster when something adds a large number of new strings to
dialog.tlk.
* Fixed our handling of WEIGHTs (again!). Thanks, Dyara. Everyone,
let me know if you find any flaws in the new handling.
Version 108:
* Allow things like LastSeenBy("foo") in scripts.
* Added support for biff creation. Use the --make-biff option and
pass it a directory argument. All files in that directory will be
made into a buff, CHITIN.KEY will be updated.
Version 109:
* Fixed on BCS decompilation probs. Thanks, Dyara.
* --biff-get and BCS decompiling no longer stop after the first
error.
* --biff-get can now get arbitrarily large files (e.g., AR1000.TIS).
I officially *do not care* for at least a few weeks if there is
some random file hiding somewhere that WeiDU still cannot
extract. Use WinBiff.
Version 110:
* D-file actions and triggers are now replaced with their converted
form. So you can say DO ~EraseJournalEntry(@1000)~ and have it
work in the end. Thanks, Triangle.
* TP2 logging is now case insensitive.
* Added --make-tlk and --traify-tlk command-line arguments.
* Extracting from BIFFs will no longer cause WeiDU to segfault if
your KEY and BIFF files do not match up.
* Fixed a problem with extracting biff resources that was causing
COPY_EXISTING "this.cre" to fail. Thanks, Edsel Sabulao.
Version 111:
* Added ALWAYS blocks for TP2 files.
Version 112:
* Allow ! in tokens (but not as the lead character).
* --nocomm disables comments in produced BAF files. Thanks,
AvengerTeamBG.
Version 113:
* We warn if you add strings but don't specify --tlkout. Thanks,
AvengerTeamBG.
* Better support for GB's multiple-directory TP2 scheme. However,
the only truly supported method is to have all of your TP2 files
in the main game directory and run Setup-Foo.
* Updated up the CHAIN epilogue in the documented grammar.
Version 114:
* Fixed a problem with --biff-get and encrypted files.
* --trans now looks inside DLG actions (like EraseJournalEntry())
and gives translation strings for them as well.
* --biff-name bug with invalid strrefs fixed. Thanks, Sqweek.
* Fixed a hideous bug in v113 that was causing WeiDU to overwrite
TP2 files with itself.
Version 115:
* Better error handling for out-of-range INTERJECT_COPY_TRANS.
Thanks, Sim!
* Yet another non-Setup-Foo.exe bugfix. This one should allow
uninstallation. Thanks, GB.
* CHAIN3 conditional fixing. Thanks, Jason.
* Added REPLACE_ACTION_TEXT_PROCESS. Good luck, Ras.
Version 116:
* Various portability fixes by Ras.
* Fixed a bug with INTERJECT_COPY_TRANS. Thanks, Elanor.
* R, I tried to send you some email and it bounced. Send me some
more email with a working address.
Version 117:
* --automate NAME1 fix. Thanks, Thorfinn and others.
* READ_*, WRITE_*, INSERT_BYTES, DELETE_BYTES, etc., may now all
take "expressions" for their offsets and their values (where that
makes sense).
* BAF files may now use hex values (like 0x34).
Version 118:
* Jason's brief transition syntax is now accepted:
+ optional_trigger + reply_text do_journal_list next
* + is now accepted as a synonym for GOTO in a 'next' block.
* CHAIN/CHAIN3 blocks can now end with
END transition_list
* SAY can now take "expressions" for offsets.
* You may now put a + after COPY (existing/regexp) to mean "do not
make a backup of this file and don't uninstall it later". Do not
use this feature.
* Noted in the docs that the THEN BEGIN keywords in a state decl
* are optional and that the THEN part of a transition is optional.
* Massive documentation update to cover new syntax that has been
creeping in.
* PRINT ~Variable foo is %foo%.~ does what you would expect.
Version 119:
* Empty TLK files now get a default entry 0 <NO_TEXT>. Thanks,
Talen.
* --tlkmerge (and general file loading) bug fixed. Thanks, Dyara.
* WeiDU will now notice if dialog.tlk and dialogf.tlk have
different lengths and it will do something about it. Thanks,
Dyara.
Version 120:
* Added "exp ? exp : exp" -- the expression-valued if.
* Added "SET variable = exp".
* Added "WHILE exp BEGIN patch list END".
* Case-insensitivity for actionoverride.
* Added GLOB as an option to COPY for Japh. Do not use this
non-portable feature.
Version 121:
* COPY sets the %SOURCE_DIRECTORY% variable.
* --debug-assign flag allows you to watch the values of TP2
variables change.
* Fixed a bug where WHILE loops were not processing READ
commands. Thanks, Japh.
* Added "SET_2DA_ENTRY row col req_col value" as a patch. Someone
wanted this.
Version 122:
* Added --extract-kits. Use it like this:
mkdir kitmod
weidu --extract-kits 1 --textout Setup-Kitmod.tp2 --out kitmod
It will extract all of the kits (and their weapon and proficiency
information and their special abilities ...) with ID >= 1 from
KITLIST.2DA. Search the resulting TP2 file and look for "FIX ME"
-- if it occurs, WeiDU was not able to find some information (or
it was not in the original game) and you should think about
providing it. You can run the TP2 file through --traify later if
you want. All required resources (ability files, etc.) will be
put in the 'kitmod' directory.
* Fixed a bug in COPY GLOB that was corrupting the OCAML runtime
and causing WeiDU to hang.
Version 123:
* Added three Japheth tutorials (values, read_byte, make-biff).
Thanks!
* Fixed a bug where variables were not being substituted properly
in COPY destinations.
* NOTE_ You cannot uninstall correctly from something like:
COPY GLOB ~save\**\worldmap.wmp~ ~%SOURCE_DIRECTORY%~
Only the last one will correctly uninstall. This is because WeiDU
was not designed with the notion that their might be multiple
files with the same name copied to different locations. Coding
uninstall support for this would be boring, annoying and
error-prone. If you want it, consider coding it up yourself or
somehow getting you and your five friends to convince me that the
fate of the world hangs in the balance.
Version 123:
* Fixed a bug in SAY (actually, offset in general) handling that
was causing UNIDENTIFIED_DESC to have the DESC part replaced by
its value instead of causing the entire thing to be replaced by
its value. Thanks: Timothy Hoffman, Kish and Jason.
Version 124:
* Fixed a globbing definition problem in the OSX arch stuff.
Thanks, Devon.
* Added Unix globbing support. Thanks again to Devon Smith. Note
that care should be taken to make sure your globbing works on
multiple platforms -- try to use lower-case letters, for example.
* Fixed a --traify problem with ~male~ ~female~ strings. Thanks,
Jason and Bhasbuto.
* --traify-tlk will no longer print the female text if it is the
same as the male text.
* Refactor WeiMorph a bit to make Japh's BG1->BG2 quest a bit
easier.
Version 125:
* Added the "FILE_MD5 filename md5sum" predicate for Fred
Richardson.
* Documented the UNINSTALL tp2 action. Don't use it.
Version 126:
* You may now --traify BAF files.
* IDS files are now only loaded once each.
* Better handling of actions that take two "concatenation string"
arguments, like Torment's
IncrementGlobalOnce("Morte_Zombie_1","GLOBAL","Law","GLOBAL",-1)
Thanks, Luke Carroll. WeiDU can now compile DMORTE.D from PST.
Version 127:
* WeiMorph changes for Japh and the boys. No real WeiDU changes.
Version 128:
* We now give an error on multiple JOURNALs per DLG transition.
* IWG1 updates for Japh.
Version 129:
* weimorph fixes: duplicate effects (again), poison, regeneration,
biffing, cre-in-name, more "not in dialogue" game crashes, brown
bears with crossbows, etc.
* Fred Richardson has coded up the REPLACE_BCS_BLOCK_REGEXP tp2
command. Docs to follow.
* Fred Richardson's bugfix for REPLACE with WEIGHTs is included.
* Massive weimorph changes.
Version 130:
* Weimorph changes.
* WeiDU.log now includes the component name rather than just the
number (when we have that info). This may help the people who
seem to be posting that file to boards and whatnot. Notably, it
helps me!
* Fixed a CHAIN bug involving duplicate labels. Thanks, Grim
Squeaker.
Version 131:
* Fixed a problem with decompiling some IWD2 scripts. Thanks,
Avenger.
* "THEN" keywords inside CHAINs are now truly optional.
* Fixed a problem with --traify and partially tra'd files. Jason
here, Jason there.
Version 132:
* Added Fred's Cygwin/GCC patch.
* Added Fred's BCS-Diff magic. Presumably Fred will continue to bat
1.000 by sending us some documentation. Look for a better
multi-romance patch in the future.
Version 133:
* Fixed a problem with --traify FOO.BAF. Thanks, Kismet.
* Fixed a problem in --traify-tlk with ~s in strings.
* Fixed a problem with --make-tlk and a stack overflow.
* Include Japh's STO automating code.
* "setup-foo.exe this.tp2" runs "this.tp2" and not "setup-foo.tp2".
* Added "component flags" in TP2 files.
Version 134:
* WeiDU auto-updating now works even if the running WeiDU is not
the newest WeiDU. However, all WeiDU mods should upgrade to at
least 134. Get the word out.
* WeiDU.log format changes undone for now ... but they (or the next
syntax change) will eventually come back. Compton begged for a
reprieve, and he shall have it, but it's hardly permanent.
Version 135:
* Fixed (hopefully!) an auto-update bug that was preventing
Win98/WinME from using WeiDU. Auto-update has been revamped a
bit: after an auto-update WeiDU will quit and you will have to
click on Setup-Foo again (there will be a big message telling you
to do this). Windows prevents me from doing it for you, sorry.
Version 136:
* Auto-Update now uses WeiDU versions. Let's try this once more.
Version 137:
* Too many open files bug fixed.
* Added some #defines to make xdiff compile more often.
* Added Japh's ADD_STORE_ITEM and ADD_GAME_NPC commands.
* Auto-Update process creation code now hand-tweaked on win32
platforms for bonus win98 compatibility. Special thanks to Kish
for testing.
Version 138:
* WRITE_EVALUATED_ASCII added.
Version 139:
* INTERJECT may now use chain-style epilogues.
* --make-biff may well work on OSX.
Version 140:
* REQUIRE_COMPONENT bugfix. Thanks, Fred!
Version 141:
* --make-biff OSX fix broke --make-biff on all other platforms.
Whoops. Fixed. Thanks, C Bisson.
* COPY_EXISTING_REGEXP can now take GLOB as a modifier.
Apparently everyone wanted this feature.
* SOURCE_DIRECTORY, SOURCE_FILESPEC, SOURCE_FILE,
DEST_DIRECTORY, DEST_FILESPEC, DEST_FILE are now all set
on every single file that is copied. Darious wanted this
feature.
Version 142:
* ADD_KIT now adds to KIT.IDS.
* Wow, I somehow left a "failwith foo" in the middle of the
trigger list parsing code. Sigh! Thanks, Today's Newbie.
Version 143:
* WeiDU now requires OCaml 3.07.
* WeiDU now reloads KIT.IDS (and all other IDS files) after
an ADD_KIT.
Version 144:
* Added Fred S. Richardson's ADD_PROJECTILE. Thanks, Fred!
* Fixed a bug that was causing "SAY NAME1 ~Foo~" to fail
because NAME1 would not be evaluated.
Version 145:
* Better error messages in --tcmp.
* First attempt at handling both CHITIN.KEY and chitin.key (unix).
Version 146:
* ADD_STATE_TRIGGER now takes an optional transition list. CBisson
wanted it.
* ADD_STORE_ITEM and ADD_GAME_NPC docs added by Japh.
* BAND, BOR, BNOT, BXOR, BLSL, BASR, BLSR -- Smoketest's expression
bitwise operators added.
Version 147:
* Fred S. Richardson patches: MinGW compiler support, HD0: .INI
path support (apparently handy for people with multiple BG2
installs).
* WeiDU now supports dialog.tlk of basically arbitrary size.
TLK files above 16 megs load more slowly (surprise).
Version 148:
* Unknown transition strings are now written out as -1, not 0.
* --make-biff should no longer make read-only biffs. Thanks, C
Bisson.
* Added Idobek's WHILE loop and SET_2DA_ENTRY tutorials. Thanks!
* Added CamDawg's bitwise operator tutorial. Thanks!
* Some Mac niceities by Loriel.
* REPLAC_BCS_BLOCK can take BAF file arguments. Idobek is lazy.
* TP2 Flag parsing was messed up -- thanks, Idobek.
Version 149:
* Auto-update only on win32 ...
* Read in triggers with 3 integer parameters from BAF files
correctly now. Thanks, Avenger.
* EXTEND_TOP, EXTEND_BOTTOM now handle missing arguments more
gracefully (assume empty script).
Version 150:
* Biffs made by WeiDU now write out the correct (hopefully)
file resource location and tis resource location values. Thanks
Avenger and Horrid.
* APPLY_BCS_PATCH_OR_COPY added to make Ease easier.
* More accurate output on "compiling X files" statements.
Version 151:
* CHITIN.KEY.FOO will no longer be found automatically.
* Strings can now be %abc% as well as "abc" and ~abc~. This will
apparently help with Big5 (Chinese) translations.
Version 152:
* EXTEND_TOP / _BOTTOM now automatically handle target scripts that
are not there.
Version 153:
* Bugfix involving ALLOW_MISSING in mod A not being honored when
mod B tries to reinstall A's component.
* STRING_SET and "forced string references" now uninstall
automatically with the standard WeiDU rollback. How many times do
I have to tell you guys not to use this feature? Since no one
is listening to me in that regard, you can all thank JRM for
convincing me to code this up.
* If you pass AT_INTERACTIVE_UNINSTALL (and friends) a single
argument that is a .TP2 file, WeiDU will enqueue that TP2 file
for you and run it when it is done with the current one
(and any others you have previously enqueued). This prevents you
from having to guess the executable to invoke or whatnot,
and is apparently handu for people with voodoo uninstall
procedures. Note that you can make it so that A.TP2 calls B.TP2
and B.TP2 calls A.TP2 and the user loops forever. Don't. JRM
again.
Version 154:
* Added BUT_ONLY_IF_IT_CHANGES.
* SET_2DA_ENTRY now takes a patchexp.
* Fixed a bug where READ patch actions were evaluated out-of-order
in some cases.
* Note: There is a KNOWN BUG involving --traify crashing on
certain CHAIN constructors. Sorry. We're working on it.
Version 155:
* Added Gwen's "--transitive" for viewing banters.
* Added FORBID_COMPONENT.
* REQUIRE_FILE and FORBID_FILE are now handled in a "preprocessing"
step.
This document was translated from LATEX by
HEVEA.