Valerio Bigiani, AKA The Bigg
// 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 ENDDialogues 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 ENDrepresents 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.
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 EXITReplies 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.
IF ~~ THEN BEGIN 2 // from: 0.1This 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 1This 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.
COPY_TRANS filename labelDuring 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.
|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 [ IF_FILE_EXISTS ] filename state list END||This tells WeiDU to place the given states at the end of the already-existing dialogue filename.DLG. If there is IF_FILE_EXISTS and the file doesn't exists, this action is skipped.|
|or||APPEND_EARLY filename state list END||Works like APPEND, but the states are added early on in the compilation timeline (just after BEGIN is processed). Thus they can be the targets for INTERJECT_COPY_TRANS and friends.|
|or||CHAIN [ IF [ WEIGHT #weight ] stateTriggerString THEN ] [ IF_FILE_EXISTS ] 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. If the entryFilename file is missing and there is IF_FILE_EXISTS, the whole CHAIN is not compiled.|
|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||INTERJECT_COPY_TRANS2 entryFilename entryLabel globalVariable chainText list||This works just like INTERJECT_COPY_TRANS, except that any actions taken in the transitions of the state specified by entryFilename and entryLabel are preserved and kept with the speaker associated with entryFilename (rather than being mistakenly performed by your new speaker). We are expecting more documentation on this feature in the future.|
|or||INTERJECT_COPY_TRANS3 entryFilename entryLabel globalVariable chainText list||This works just like INTERJECT_COPY_TRANS, except that all states in chainText get a link in the entry state, rather than only the first one. Expecting documentation.|
|or||INTERJECT_COPY_TRANS4 entryFilename entryLabel globalVariable chainText list||This is either INTERJECT_COPY_TRANS3 with INTERJECT_COPY_TRANS2-style action handling, or INTERJECT_COPY_TRANS2 with the extended INTERJECT_COPY_TRANS3 state creation rules, depending at how you look at it.|
|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||ADD_TRANS_ACTION filename BEGIN stateNumber list END BEGIN transNumber list END transActionString||This instructs WeiDU to add the transActionString to all of the transitions in all of the states specified by the stateNumber list and the transNumber list. You may use state labels in the stateNumber list. If the transNumber list is empty, the text added to all transitions on all listed states. Note that the BEGIN and END keywords must be present, even if you specify an empty list of transNumbers. The “first” transition is number 0. Any out-of-bounds transNumbers are silently ignored. The transActionString is prepended to any existing action text on a per-transition, per-state basis.|
|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_TRIGGER_TEXT_REGEXP filenameRegexp oldText newText||Just like REPLACE_TRIGGER_TEXT but the filename is a regexp. The .DLG is implied. Do not use this.|
|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_REGEXP filenameRegexp oldText newText [ moreFilenameRegexps ]||Just like REPLACE_ACTION_TEXT but the filenames are regexps. The .DLG is implied, do not include it in your regexps. Do not use this.|
|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.
|or||R_A_T_P_R filenameRegexp oldText newText [ moreFilenameRegexps ]||Just like REPLACE_ACTION_TEXT_PROCESS, but the filenames are regexps. The .DLG is implied. Do not use this. R_A_T_P_R is shortand for REPLACE_ACTION_TEXT_PROCESS_REGEXP (to avoid undue scrollbars in the readme on a 1024x768 monitor).|
|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.|
|sayText||sayText and replyText are displayed to the user as part of a dialogue.|
|is||text||sayText and replyText are both text.|
|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 [ IF_FILE_EXISTS ] filename stateLabel||The dialogue continues at the state with label stateLabel in the file filename.DLG. The whole transition is not compiled if there's IF_FILE_EXISTS and the file filename doesn't exist.|
|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_FILE_EXISTS ] [ 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. If the IF_FILE_EXISTS part is present, these lines are not compiled at all if the current file is not missing.|
|or followed by||BRANCH transTriggerString BEGIN [ == fileName [ IF_FILE_EXISTS ] [ IF transTriggerString THEN ] sayText = sayText ... ] END||As above, except that the first transTriggerString is appended to all existing dialogue units.|
|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.|
|or||!integer text||Forced String Reference. As with text in general, but rather than being assigned a new, previously-unused DIALOG.TLK string entry (or merging with an existing one that has the same text), this text is written over DIALOG.TLK string entry #integer. Do not use this feature.|
|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.|
||A string can be any sequence of
characters not including a
|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
||That's five consequtive
tildes on each side. A string can be any
sequence of characters not including
|or||String ^ String||String literal concatenation. The second string is appended to the first string. No whitespace is added. Thus "hello" ^ "World" is the same as "helloWorld".|
weidu --string 1 --string 2 --string 3This does not apply to *-rest commands like --biff-get-rest and --force-install-rest.
weidu --biff-get-rest sw1h01.itm --out foowill try to extract sw1h01.itm, --out and foo from the biffs; you have to express the above as
weidu --out foo --biff-get-rest sw1h01.itm*-list commands work like the -rest variants, except that they stop parsing for the current switch once they find an option starting with '-'. Basically, this works like you'd expect:
weidu --biff-get-list sw1h01.itm --out foo
|Compiling And Decompiling|
|FILE.D||Compile FILE to a DLG (dialogue file).|
|FILE.DLG||Decompile FILE to a D (dialogue text file).|
|number||When decompiling a DLG file to a D file, emit only state number. You may specify this multiple times and only the states you specify will be emitted.|
|numberA-numberB||When decompiling a DLG file to a D file, emit only states between numberA and numberB (inclusive). You may specify this multiple times and only the states you specify will be emitted.|
|FILE.BAF||Compile FILE to a BCS (script file).|
|FILE.BCS||Decompile FILE to a BAF (script text file).|
|--script-style X||Use the given BAF/BCS scripting style. X must be BG or BG2 or PST or IWD or IWD1 or IWD2.|
|--transin X||Use FILE as a source of translation strings when processing D and BAF files.|
|FILE.TRA||Equivalent to --transin FILE.TRA.|
|Module Packaging Input And Control|
|FILE.TP or FILE.TP2||Read FILE and ask the user whether to install, reinstall or uninstall its TP2 Components.|
|--yes||Answer all TP2 questions with 'Yes' and do not prompt for a key to be pressed at the end of TP2 processing.|
|--uninstall||Answer all TP2 questions with 'Uninstall' and do not prompt for a key to be pressed at the end of TP2 processing.|
|--reinstall||Re-install all TP2 components that are already installed and do not promtp for a key to be pressed at the end of TP2 processing.|
|--languagem X||Sets the TP2 Language to the one passed here. Has no effect if the value is bigger than the language count (I.E. you'll get asked for the language, unless you gave --uninstall, --reinstall or --force-uninstall).|
|--force-install X||installs component number X, skips the others (cumulative).|
|--force-uninstall X||uninstalls component number X, skips the others (cumulative). If there is no --force-install, you don't get asked for the language.|
|--force-install-rest X Y...||installs component number X Y..., skips the others (cumulative).|
|--force-uninstall-rest X Y...||uninstalls component number X Y..., skips the others (cumulative).|
|--force-install-list X Y...||installs component number X Y..., skips the others (cumulative).|
|--force-uninstall-list X Y...||uninstalls component number X Y..., skips the others (cumulative).|
|--skip-at-view||AT_* VIEW this actions (and the extra chromosome versions like NOTEPAD this ) aren't processed, while still processing batch files and similia.|
|--ask-every||Behave as if ASK_EVERY_COMPONENT were present for all TP2 components.|
|--continue||Continue TP2 processing despite TP2 Action errors.|
|--args X||X will be stored in the tp2 variable
If the installation is non-interactive, said variables will be loaded from the last interactive session.
|--args-rest X Y...||X Y Z will be stored in the tp2 variables
If the installation is non-interactive, said variables will be loaded from the last interactive session.
|--args-list X Y...||X Y Z will be stored in the tp2 variables
If the installation is non-interactive, said variables will be loaded from the last interactive session.
|--debug-presence||When compiling BAF files, look if the files referenced are found in the game, and report if they're missing. I can't print out the file being compiled or the name of the action/trigger (only the IDS number), so good hunting. Note: it works not for .D files.|
|--debug-assign||Print out all values assigned to TP2 variables (even implicit ones created by WeiDU).|
|--debug-value||Print out all values encountered in TP2 processing and the results they evaluate to. Among other things, this is useful for catching parenthesis errors in your values.|
|Automatic Updating Options|
|--update-all||Auto-update all WeiDU setup files (e.g., Setup-MyMod.exe) in the current directory.|
|--noautoupdate||If you are running WeiDU as Setup-MyMod.exe, do not attempt to update yourself or any other mod.|
|--noselfupdatemsg||If you are running WeiDU as Setup-MyMod.exe and it automatically updates itself, do not display a message or ask the user to press return.|
|Infinity Engine Game Location Options|
|--game X||Set main game directory to X. WeiDU looks for CHITIN.KEY and DIALOG.TLK and the override directory in the main game directory (but see --tlkin and --search). 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. Unless you also specified --tlkin, no DIALOG.TLK will be loaded. Unless you also specified --search, no override directory will be used.|
|--search X||Look in X for input files (cumulative). X is treated as an override directory and is given priority over the default override directory.|
|Game Text (TLK) File Input|
|--tlkin X||Use X as DIALOG.TLK (instead of looking for DIALOG.TLK in the game directory).|
|--ftlkin X||Use X as DIALOGF.TLK (instead of looking for DIALOGF.TLK in the game directory).|
|FILE.TLK||Equivalent to --tlkin X.|
|--tlkmerge X||Merge strings from X over the strings from any other loaded DIALOG.TLK.|
|General Output Options|
|--out X||Emit most output files generated by command-line options (e.g., D, DLG, kits, --biff-get, BAF, BCS, --automate, --traify-tlk, --extract-kits, --list-biff, --cmp-from, --dcmp-from, etc.) to file X. If X is a directory, certain commands (e.g., D, DLG, --biff-get, etc.) will place their output there. Does not affect TP2 processing.|
|--append X||Like --out, but if X is an existing file then the result will be appended to it instead of overwriting it.|
|--backup X||Backup files to directory X before overwriting. Does not affect TP2 processing.|
|--tlkout X||If any strings were added to or changed in the loaded DIALOG.TLK, emit X as an updated version that reflects those changes. Many operations (e.g., compiling D files, --tlkmerge, STRING_SET) can add to or modify DIALOG.TLK.|
|--ftlkout X||If any strings were added to or changed in the loaded DIALOGF.TLK, emit X as an updated version that reflects those changes.|
|Dialogue Text File (D) Options|
|--noheader||Do not emit D header comments.|
|--nofrom||Do not emit D // from: comments.|
|--full-from||Generate complete // from: comments with a slower two-pass process.|
|--nocom||Do not emit ANY D or BAF comments.|
|--text||Emit string text with string references in comments.|
|--transitive||Follow EXTERN links when making D files. See the tutorial on --transitive.|
|--toplevel||Emit only top-level dialogue states -- that is, states with non-empty triggers.|
|--traify X||Convert X (which should be a D or TP2 or BAF) so that it uses translation references instead of literal strings. Use --out Y to specify a name for the transformed version of X and its new TRA file.|
|--traify# X||Use with --traify and --traify-tlk. Start the newly-created TRA file at translation string @X instead of @0.|
|--untraify-d X||convert .D file X to use hardcoded strings...|
|--untraify-tra X||...from TRA file X. Please note that the outcoming file
could not work properly (if baf code embedded in a d file contains @ references, or if a string contains
|--trans||Emit coupled D and TRA files when decompiling a DLG.|
|--transref||Emit string reference numbers in TRA files when using --trans.|
|--traify-tlk||Emit a TRA file for the loaded TLK file (see --tlkin, --out, --min and --traify#).|
|--make-tlk X||Create a TLK file from TRA file X (cumulative, see --tlkout).|
|--testtrans||Test all specified TRA translation files to see if any of them use text that is already in the loaded DIALOG.TLK. If they do you can save translation effort by using string references instead.|
|--forceify X||Convert the given D file to use forced strrefs (see --out, SAY, Forced String Reference).|
|Game Text Repository (TLK) Options|
|--string X||Display string reference #X (cumulative). If you also specify --min or --max, all string references between --min (or 0) and --max (or infinity) will be displayed.|
|--strfind X||Display strings that contain X (cumulative, regexp allowed).|
|--strapp X||Append string X to DIALOG.TLK (cumulative).|
|Game Archive (BIFF) Options|
|--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-get X||Extract resource X from game BIFFs (cumulative, regexp allowed).|
|--biff-get-rest X Y ...||Every argument given on the command line after --biff-get-rest is treated as if it were preceeded by --biff-get. Use this command to extract multiple different files (or regexps) at once.|
|--biff-get-list X Y ...||Every argument given on the command line after --biff-get-rest is treated as if it were preceeded by --biff-get, with the -list exception. Use this command to extract multiple different files (or regexps) at once.|
|--biff-str X||Search all game BIFFs for files containing X (regexp allowed).|
|--biff-value X||Search all game BIFFs for files containing value X at offset ADDR. Must be used with --biff-value-at.|
|--biff-value-at ADDR||Gives the offset address for a --biff-value search.|
|--biff-type X||Limit --biff-str or --biff-value searches to resources of type X (cumulative).|
|--biff-name X||When a --biff-str or --biff-value search finds a matching file, assume it has a strref name at offset X and print that name out as well.|
|--make-biff X||Create data/X.bif from all files in folder X and destructively update CHITIN.KEY. Do not use this feature. Do not even think of using this feature without backing up CHITIN.KEY.|
|--remove-biff X||Remove references to BIFF X and all of its resources from CHITIN.KEY. Do not use this feature.|
|Comparison Options (see --out)|
|--cmp-from X||Emit WRITE_BYTEs to turn this file ...|
|--cmp-to X||... into this one.|
|--dcmp-fromX||Emit REPLACEs to turn this DLG file ...|
|--dcmp-to X||... into this one.|
|--tcmp-fromX||Compare this TRA file (or directory of TRA files)...|
|--tcmp-to X||... with this one (or this directory).|
|--tlkcmp-fromX||Emit STRING_SETs to convert this TLK file ...|
|--tlkcmp-toX||... into this one.|
|--tlkcmp-use-strings||When using --tlkcmp-from, emit commands of the form STRING_SET "Hello" @1 instead of STRING_SET #1 @1.|
|--bcmp-from X||Emit APPLY_BCS_PATCH to turn this BCS file ...|
|--bcmp-to X||... into this one.|
|--bcmp-orig X||Original file X to apply ...|
|--bcmp-patch X||... this patch to.|
|--min X||Lower range for some commands. See --traify-tlk, --tlkcmp-from and --string.|
|--max X||Upper range for some commands.|
|Automatic Module Packaging Options|
|--automate X||Automatically create a TP2 file for resources in folder X. See --out.|
|--automate-min X||Only --automate string references above X.|
|--extract-kits X||Extract all kits starting with kit #X and create TP2 actions to install those kits as part of a module.|
|Resouce Exploration Options|
|--list-eff X||List effects in resource X. See --out.|
|F.ITM or F.EFF or F.SPL||Equivalent to --list-eff F.EXT.|
|--log X||Log output and details to X.|
|--autolog||Log output and details to WSETUP.DEBUG.|
|--logapp||Append to log file instead of overwriting it.|
[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.
... [.\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.
... [.\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~ */
... [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.
... [bodhi.d] parsed [BODHI.DLG] saved 135 states, 259 trans, 16 strig, 66 ttrig, 54 actionsThis 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.
... [bodhi.d] parsed [BODHI.DLG] saved 135 states, 259 trans, 16 strig, 66 ttrig, 54 actions [new-DIALOG.TLK] created, 84459 string entriesThis 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.
... [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 actionsThis 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.
... [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 actionsIt 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.
... [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 actionsIt 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.
... [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.
[C:\Program Files\Black Isle\BGII - SoA\DIALOG.TLK] 84458 string entries [.\happy.tlk] created, 84459 string entriesNot much to say here. String reference #84459 in happy.tlk is now “ANewString”.
... [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.
[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.
[.\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"
... 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:
... 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).
... [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).
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.
... @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:
... @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 --out and read from a different TLK file by adding it on the command line.
[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 entriesString @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.
[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.
[c:\src\weidu\weidu.exe] WeiDU version 157 [./chitin.key] 182 BIFFs, 41793 resources [./dialog.tlk] 83772 string entries Removing references to 16 resources in [data\25ArMisc.bif] KEY saved (181 biffs, 41777 resources)Note that the specified BIFF is not deleted from your computer's file system, it is merely removed from CHITIN.KEY. When specifying the BIFF case does not matter but the folder separator, usually “\” or “:”, does. Use --list-biffs to get the official list of possible BIFFs to remove. Do not use this feature.
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 ENDNote 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.
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 ENDThis 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.
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 ENDThis will create three dialogue states, separated by simple "click to continue" transitions.
SAY ~One~ = ~Two~ = ~Three~is perfectly valid as well.
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#KLSYJHowever, 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.
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)~ EXITNote how this dialogue works.
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)~ EXITIn 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.
EXTEND_BOTTOM PLAYER1 25 IF ~IsValidForPartyDialogue("Sola") Global("SolaWelcomeHell","GLOBAL",0)~ THEN DO ~SetGlobal("SolaWelcomeHell","GLOBAL",1)~ EXTERN SOLA inHell1 ENDThis 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.
APPEND SOLA IF ~~ THEN BEGIN inHell1 SAY @2 = @3 // use strings @2 and @3 from translation file COPY_TRANS PLAYER1 25 END ENDInstead 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.
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 ENDThis 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.
IF ~IsValidForPartyDialogue("Sola") Global("SolaWelcomeHell","GLOBAL",0)~ THEN DO ~SetGlobal("SolaWelcomeHell","GLOBAL",1)~ EXTERN SOLA inHell1If 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.
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 ENDThis 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.)
IF ~~ THEN BEGIN 1 // from: SAY #11080 /* ~We are his possessions.~ */ IF ~~ THEN EXTERN ~IDRYAD2~ 1 ENDMinsc 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 1Here's what's going on here.
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 1Note that we repeat the
IF ~IsValidForPartyDialog("Minsc")~ THENcheck 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.
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?~ ENDSo, 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.
IF ~~ THEN BEGIN 8 // from: 7.0 SAY #44931 /* ~Blah blah blah~ */ IF ~!IsValidForPartyDialog("Jaheira") !IsValidForPartyDialog("Anomen") !IsValidForPartyDialog("Edwin") IsValidForPartyDialog("Viconia")~ THEN DO ~SetGlobal("WackoArmy","GLOBAL",1) OpenDoor("DOOR12") EscapeArea()~ UNSOLVED_JOURNAL #7045 EXTERN ~VICONIJ~ 129 ENDNote that the DO actions include EscapeArea(). If you use INTERJECT_COPY_TRANS to add an interjection here, the DO actions will be performed by your interjector (usually a party member). The result is that after encountering this dialogue in the game the party member will leave the area (and Saemon will stay where he is!) promptly after performing the interjection. Instead, we want to keep the DO action associated with Saemon.
INTERJECT_COPY_TRANS2 PPSAEM2 8 ~Blah blah blah~ ENDThe dialogue state that WeiDU will create from this INTERJECT_COPY_TRANS2 statement looks something like:
IF ~~ THEN BEGIN 133 // from: SAY #78199 /* ~Blah blah blah~ */ IF ~!IsValidForPartyDialog("Jaheira") !IsValidForPartyDialog("Anomen") !IsValidForPartyDialog("Edwin") IsValidForPartyDialog("Viconia")~ THEN UNSOLVED_JOURNAL #7045 EXTERN ~VICONIJ~ 129 ENDNote that INTERJECT_COPY_TRANS2 is not intended as a universal replacement for INTERJECT_COPY_TRANS. This is most obvious when using the command to interject into a state that starts a cutscene. Here is an example of an interjection into PPIRENI2 27.
IF ~~ THEN BEGIN 27 // from: 28.0 26.0 SAY #44869 /* ~I bid you farewell, child of Bhaal. We shall not meet again.~ [IRENIC52] */ IF ~~ THEN DO ~EraseJournalEntry(7252) EraseJournalEntry(7253) EraseJournalEntry(22952) EraseJournalEntry(23306) SetGlobal("AsylumPlot","GLOBAL",40) StartCutSceneMode() StartCutScene("Cut41j")~ SOLVED_JOURNAL #7255 EXIT ENDUsing INTERJECT_COPY_TRANS2 to code your interjection into this state will cause the game to crash when your interjection plays (because the special StartCutSceneMode() action cannot occur in the middle of a dialogue, loosely). INTERJECT_COPY_TRANS will work properly, however.
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~ ENDIf 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~ ENDWith 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.
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 ENDto remind you that the order is not what you think.
APPEND BJAHEIR IF WEIGHT #-999 ~MyCondition()~ THEN BEGIN mystate SAY ~My Stuff~ END ENDSince 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.
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.
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.
////// // 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.
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 ENDNote that all of the strings have been replaced by @number and the texts have been put in comments.
// 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.
// 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.
APPEND J#KLSYJ IF ~~ THEN BEGIN KelseySAHPR4 SAY ~Urk. Who was the lucky donor?~ IF ~~ THEN EXTERN SAHPR2 10 END ENDinto fwki-new.d's
APPEND J#KLSYJ IF ~~ THEN BEGIN KelseySAHPR4 SAY @0 IF ~~ THEN EXTERN SAHPR2 10 END ENDand 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.
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.
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.
|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.|
TP2 Flag 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. This directory will be created automatically if it is missing. The AUTHOR directive gives an email address for users to send bugs to if there are problems during the installation. The TP2_AUTHOR variable is set to the “emailAddress” value. The TP2_FILE_NAME variable is set to the name of the tp2 file. 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 END||This flag specified a TP2 Action that is executed just before any normal TP2 Action in the file is installed.|
|or||SCRIPT_STYLE style||This flag determines how WeiDU will read in BAF and BCS files and write out BAF and BCS files. Possible options for “style” include BG (the default), IWD1, IWD2, and PST. See the Scripting Styles tutorial.|
|or||NO_IF_EVAL_BUG||this action solves a long-standing bug with the IF_EVAL action. Since solving it is not directly possible without damaging backwards compatibility, you have to use this action (preferably in an ALWAYS statement) to solve the bug for yourself. IF_EVAL will not work with this action in the TP2 - use PATCH_IF and BUT_ONLY_IF_IT_CHANGES instead.|
|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". The variable named LANGUAGE is set to languageDirectory if the user selects this language.. 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.|
|or||REQUIRE_PREDICATE value String||This component can only be installed if the value evaluates to true (non-zero).|
|or||SUBCOMPONENT String [ value ]||At most one component of the given subcomponent group can be installed at any time. All subcomponents of the same group are listed together for the user. See the SUBCOMPONENT tutorial.|
|or||FORCED_SUBCOMPONENT String [ value ]||See the SUBCOMPONENT tutorial.|
|or||GROUP String||Used for grouping components; the user will be asked to display these or not. See the GROUP tutorial.|
|or||INSTALL_BY_DEFAULT||If WeiDU would ask the user whether to install this component or not, and this component is not already installed, WeiDU will instead install it by default (without asking the user). If there is an error or the component is already installed, WeiDU will ask the user. The --uninstall command-line argument overrides this. See also REQUIRE_COMPONENT and ALWAYS.|
|or||DESIGNATED forcedNumber||Normally module components are numbered based on their order in the TP2 file (starting from 0). This flag sets the current component number to forcedNumber. The next component (if it lacks a DESIGNATED flag) will be forcedNumber+1. You can easily shoot yourself in the foot by setting forcedNumber too low (e.g., so that multiple components have the same number). Do not use this flag.|
|or||NO_LOG_RECORD||Normally all module components are recorded in WeiDU.log and can be uninstalled later. This component flag prevents this component from writing a log record when it is successfully installed. As a result it is “invisible” to WeiDU, can be installed multiple times, and cannot be uninstalled with WeiDU. Do not use this flag.|
|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. If there are any WeiDU
variables inside explicit %s in toFile or fromFile, they are
replaced by their values. 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"
COPY commands set the user-defined SOURCE_DIRECTORY, SOURCE_FILESPEC, SOURCE_FILE, SOURCE_RES, DEST_DIRECTORY, DEST_FILESPEC, DEST_FILE, and DEST_RES 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, SOURCE_FILE is bigboss.cre and SOURCE_RES is bigboss. The DEST_ variables are similarly based on toFile. In addition, SOURCE_SIZE is set to the size (in bytes) of the source file.
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.
See the Module Distribution section for information about finding a good unique prefix for your mod-created resources.
|or||COPY_EXISTING optNoBackup fromFile toFile ...||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
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||COPY_LARGE optNoBackup optGlob fromFile toFile ...||Behaves like COPY except that the fromFiles can be of arbitrary size (the limit should be over a Gigabyte), but on the other side of the coin you can't apply patches to the copying.|
|or||COPY_RANDOM ( file1 list ) [ ( fileN list ) list ] patch list when list||This command works like COPY_EXISTING but the destination for any given souce file in the file1-list is some other different file in the file1-list. Similarly, the destination for any file in the fileN-list is some other file in the fileN-list. This allows you to randomly shuffle categories of game resources.|
|or||COPY_ALL_GAM_FILES patch list when list||Copies Default.gam from the biff and the ones in the save/ and mpsave/ saved games, applying the list of patches. If there are any when conditions and any of them are false, the copy does not happen. NO BACKUP is applied to files in the save/ and mpsave/ directories. The savegame from the biffs is backuped as usual.|
|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 BAF files are compiled. Any DLGs or BCSs they create or modify are placed in the override directory.
|or||CLEAR_MEMORY||removes all variables from the memory, then reloads the automatic ones (TP2_AUTHOR, TP2_FILE_NAME, LANGUAGE, WEIDU_ARCH, WEIDU_OS, COMPONENT_NUMBER, all numeric constants such as NAME1, the soundsets, or SCRIPT_OVERRIDE). INTERACTIVE is automatically set when you call the next component. Don't use this feature.|
|or||CLEAR_IDS_MAP||force WeiDU to remove all loaded IDSes in memory (otherwise, if you compile a script, append to an IDS and compile another script, WeiDU will not handle the new IDS entry).|
|or||SILENT||makes WeiDU skip all messages; it's reenabled once a component is installed (or fails to), or a PRINT, PATCH_PRINT, VERBOSE or PATCH_VERBOSE is found.|
|or||VERBOSE||undoes the latest SILENT command.|
|or||MKDIR dirName list||Instructs WeiDU to create all of the directories in the list.|
|or||RANDOM_SEED someInteger||Sets the random number generator seed to someInteger. This allows you to get reproducible results when using random functions. If you specify a string that is not a valid integer the system initializes itself (e.g., by using the current time).|
|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). Any variables in newText are replaced by their values.|
|or||APPEND_OUTER 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 treated as a path to a file). Any variables in newText are replaced by their values.|
|or||APPEND_COL filename newText [ prepend ] 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. prepend empty cells are assumed to be at the beginning of newText. A cell containing exactly
|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. User variables in the filenames existingFile and newFile are replaced by their values. Use EVALUATE_BUFFER if you want to evaluate variables inside the body of newFile before parsing it.|
|or||EXTEND_BOTTOM existingBCS newFile patch list||As EXTEND_TOP, but the newFile file is put at the bottom of the existingBCS file. User variables in the filenames existingFile and newFile are replaced by their values.|
|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 value THEN BEGIN TP2 Action list END [ ELSE BEGIN TP2 Action list END ]||If value evaluates to true (non-zero), 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 attemps to be installed, commandToRun
is executed. Variables (e.g., %LANGUAGE%) in the string
commandToRun are replaced by their values. Note that the command is
executed even if the component does not install correctly, so
AT_EXIT should probably be the last command in a component.
AT_INTERACTIVE_EXIT ~VIEW mymod\README.txt~This causes your README file to be displayed using a system appropriate viewer. Here's a more complicated example that pulls up a language-specific README if one is available:
ACTION_IF FILE_EXISTS ~mymod\README.%LANGUAGE%.txt~ THEN BEGIN AT_INTERACTIVE_EXIT ~VIEW mymod\README.%LANGUAGE%.txt~ END ELSE BEGIN AT_INTERACTIVE_EXIT ~VIEW mymod\README.txt~ END
|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. Only the %LANGUAGE% variable is guaranteed to be replaced, so do not count on any others.|
|or||AT_NOW commandToRun||As AT_EXIT, but commandToRun is executed when found, rather than at the end of the installation. Use with oggdec, tisunpack, wav2acm... but not with readmes (you'll create dozens of windows), nor with --make-biff (you'll crash the installation).|
|or||AT_INTERACTIVE_NOW commandToRun||As AT_NOW, but commandToRun is executed only if the installation is run interactively.|
|or||MAKE_BIFF name-of-biff BEGIN directory-file-regexp list END||Create a biff Data/name-of-biff.bif from the files that can be matched by directory-file-regexp. The chitin.key file is restored as a normal file, and reloaded whenever the component is installed or uninstalled (which means that its contents will be available exactly when the biff exists), while the .bif file itself is NOT deleted on uninstall.|
|or||LOAD_TRA traName list||Loads all traName files (variable substitution is done, the file cannot be inlined) to be used in the following tp2 actions, as if you had declared them in LANGUAGE.|
|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://forums.gibberlings3.net/index.php?showtopic=584 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||ADD_SPELL newSPLfile type level idsName patch list when list||adds newSPLfile as a spell of type and level into the game (IE, SPPR102 or
whatnot), in the first empty slot possible, appends the idsName to
spell.ids, and sets
|or||STRING_SET indexOrString newValue list [ USING traFile ]||This command replaces each given string in the user's TLK file with the associated newValue. Do not use this command. If a traFile is given, that file is is read once before all of the replacements take place and its contents are forgotten after. Do not use this command.|
|or||STRING_SET_EVALUATE value newValue list [ USING traFile ]||This command replaces the string at index value in the user's TLK file with the associated newValue. Do not use this command. If a traFile is given, that file is is read once before all of the replacements take place and its contents are forgotten after. Do not use this command.|
|or||STRING_SET_RANGE #min #max USING traFile||For every integer i between min and max (inclusive) we do STRING_SET i @i USING traFile (except that this command should be executed more rapidly). The command will fail if @i is not defined (either by traFile or by some other tra file in scope) for some i between min and max. Do not use this command.|
|or||REQUIRE_FILE filename warningString||If filename does not exist (or the bif file is not referenced inside the chitin.key), warningString is displayed and this component cannot be installed. This is checked before any actions are executed.|
|or||FORBID_FILE filename warningString||If filename does exist (or the bif file is referenced inside the chitin.key), warningString is displayed and this component cannot be installed. This is checked before any actions are executed.|
|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||OUTER_SPRINT variable stringWithVars||Any WeiDU variables (enclosed in %s) inside stringWithVars are replaced by their values and the resulting string (constructed at mod-installation time!) is assigned to the variable variable. This works somewhat like sprintf() but it not as cool.|
|or||OUTER_SET variable = value||Update variable so that it is equal to value.|
|or||OUTER_INNER_PATCH buffString BEGIN patch list END||Any WeiDU variables inside %s within buffString are replaced by their values. All of the patches given are evaluated as if the contents of the current file were buffString. Any modifications to buffString are thrown away. This is considered an action, not a patch.|
|or||OUTER_PATCH buffString BEGIN patch list END||Equal to OUTER_INNER_PATCH, if you feel that OUTER_INNER sounds wrong.|
|or||OUTER_INNER_PATCH_SAVE savevar buffString BEGIN patch list END||As with OUTER_INNER_PATCH, except that any modifications to buffString are stored inside savevar.|
|or||OUTER_PATCH_SAVE savevar buffString BEGIN patch list END||Equal to OUTER_INNER_PATCH_SAVE, if you feel that OUTER_INNER sounds wrong.|
|or||OUTER_FOR ( patch list ; value ; patch list ) BEGIN action list END||The action OUTER_FOR (init;pred;inc) BEGIN body END is equivalent to init WHILE pred BEGIN INNER_ACTION BEGIN body END inc END, except that it's considered an action. Note that the predicate value cannot be empty.|
|or||ACTION_BASH_FOR directory-file-regexp BEGIN TP2 Action list END||for all files that match directory-file-regexp, sets a bunch of variables
and executes the actions for each file found. Assuming the file being copied
is somedir/yourfile.cre, the following variables will be set:
"%BASH_FOR_DIRECTORY%" = "somedir" "%BASH_FOR_FILESPEC%" = "somedir/yourfile.cre" "%BASH_FOR_FILE%" = "yourfile.cre" "%BASH_FOR_RES%" = "yourfile"
|or||ACTION_FOR_EACH string1 IN string list BEGIN action list END||will set the string1 variable to each value in string list and process each action.|
|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.|
|or||<<<<<<<< fileName fileBody >>>>>>>>||(That's eight (8) angle brackets on each side.)
For the purposes of copying and compiling files, WeiDU will pretend
that fileName is a real file with contents fileBody. This allows you to
define inlined files inside your TP2 file. Such definitions
must be executed before the inlined file is used. Other operations
on fileName (such as FILE_EXISTS, FILE_MD5 or
FILE_SIZE) are undefined. Inlined files will be skipped by
COPY_EXISTING_REGEXP and other “wildcard” approaches: you
must name them directly. Unlike most other WeiDU things,
spacing matters here. After the initial <<<<<<<< there can be
any number of spaces. The first non-space character after the
<<<<<<<< is the first character of fileName. All other
characters up to and excluding the newline on that line are part of
fileName (and thus fileName cannot start with a space or contain a
newline). All user variables (e.g., %foo%) in fileName will be
replaced by their values.
The fileBody contains all characters after that newline up to
and excluding the <<<<<<<<. Note that a single inlined filename
namespace is shared by WeiDU for all TP2 files it reads (it
might read other TP2 files in the process of installing yours
in order to uninstall or reinstall another mod's components), so it is
criticially important that you use some sort of unique prefix.
I also suggest that you avoid using your mod's actual directory
structure (if any) in order to avoid confusion with real files.
Consider using .../yourmod-inlined/fileName. Here is a concrete
BEGIN ~MyMod Component 1~ <<<<<<<< .../mymod-inlined/myfile.baf IF True() THEN RESPONSE #100 Kill("Anomen") END >>>>>>>> COMPILE ~.../mymod-inlined/myfile.baf~This inclusion method is eight-bit clean, so if you are very careful you can inline a binary file (e.g., a CRE or BAM) with this method. Be careful to shave off the newline before the >>>>>>>> in such cases. Finally, note that inlined files have the same maximum size as other (non-BIFF) WeiDU files and strings (usually about 16 megs).
|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.|
|or||-||If you put a - here, WeiDU will not copy the file, but only store it as an inlined file (so you can patch a file and then use it for EXTEND_BOTTOM or whatever. This option is not available for COPY_LARGE.|
|directory-file-regexp||This is a directory path and a regexp string. It's used by BIFF, ACTION_BASH_FOR and PATCH_BASH_FOR for listing patterns to be matched. Please note that the directory / filename distinction must be enforced.|
|is||directory regexp||get a list of all files in directory that match regexp. Case doesn't matter.|
|or||directory EVALUATE_REGEXP regexp||get a list of all files in directory that match regexp. Case doesn't matter. This is the same as the above item.|
|or||directory EXACT_MATCH name||get the file directory/name. Please note that the directory / filename distinction must be enforced.|
|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
Here is a concrete example. Imagine that CHITIN.KEY contains two files: C1.ITM and C2.ITM. The override contains C2.ITM and C_MOD.ITM.
COPY_EXISTING_REGEXP "C.*.ITM" "override" // catches C1.ITM, C2.ITM COPY_EXISTING_REGEXP GLOB "C.*.ITM" "override" // catches C1.ITM, C2.ITM, C_MOD.ITM COPY "override" "override" // catches C2.ITM, C_MOD.ITMTo put it another way: if you do not specify GLOB with COPY_EXISTING, WeiDU pretends that the override directory contains 0 files that are not in CHITIN.KEY. Finally, GLOB has no effect on wildcards in the "destination" part of the COPY:
COPY GLOB "mymod/foo" "music/*" // this is illegal: DO NOT DO THIS
|optcase||Allows you to decide wether regexp matching is case-sensitive or not.|
|is||Uses the WeiDU default (which is undocumented and varies with the action). Please specify case-sensitiveness manually.|
|or||CASE_SENSITIVE||matching is case-sensitive.|
|or||CASE_INSENSITIVE||matching is case-insensitive.|
|optexact||Allows you to decide wether regexp matching is exact.|
|is||Uses the WeiDU default (which is undocumented and varies with the action). Please specify case-sensitiveness manually.|
|or||EXACT_MATCH||only the given string is searched for.|
|or||EVALUATE_REGEXP||matching is evaluated following the usual regexp conventions.|
|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||PATCH_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. See also PRINT.
COPY_EXISTING_REGEXP ~.*\.CRE~ ~override~ READ_BYTE 0x272 race READ_BYTE 0x273 class PATCH_IF class = 3 THEN BEGIN PATCH_PRINT ~%SOURCE_FILE% is a cleric with race = %race%.~ END
|or||SAY_EVALUATED offset stringWithVars||Any WeiDU variables (enclosed in %s) inside stringWithVars are
replaced by their values and the resulting string (constructed at
mod-installation time!) is added to DIALOG.TLK and its string
reference it written to the offset. Example:
COPY_EXISTING_REGEXP ~RING.*.ITM~ ~override~ READ_LONG 0x38 cost SAY_EVALUATED IDENTIFIED_DESC ~I Am %SOURCE_RES%, I Cost %cost%~Do not use this feature.
|or||TO_UPPER variable||Turns to upper-case the contents of
|or||TO_LOWER variable||Turns to lower-case the contents of
|or||SPRINT variable stringWithVars||Any WeiDU variables (enclosed in %s) inside stringWithVars are replaced by their values and the resulting string (constructed at mod-installation time!) is assigned to the variable variable. This works somewhat like sprintf() but it not as cool. Currently this is the only way to assign a string value to a variable.|
|or||SNPRINT value variable stringWithVars||As SPRINT, but only the first N characters are stored in
the variable, where N is the result of evaluating the
value. This works somewhat like snprintf(). Thus:
SPRINT "author" "Jason" SNPRINT 3 "myvar" "1:%author%"... assigns 1:J to myvar.
|or||REPLACE optcase optexact 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 optcase optexact regexp string [ ( sizeValue ) ]||All occurences of the given regexp in the file are replaced with the given string. variable substitution (e.g., kit and music names) is performed on both the string and the regexp. If you use the sizeValue field, optexact is automatically set to true (IE without regexp patterns). The regexp and the string are padded by zeros to be long exactly sizeValue bytes.|
|or||EVALUATE_BUFFER||Any WeiDU variables (like %myvar%) inside the current file (which
should probably be a plain text file in order for this to make much sense)
are replaced by their values. Example:
<<<<<<<< .../script.baf IF See(%myvar%) THEN RESPONSE #100 Kill(%myvar%) END >>>>>>>> EXTEND_TOP ~sola.bcs~ ~.../script.baf~ SPRINT myvar = ~"Anomen"~ EVALUATE_BUFFERThose two actions extend the top of sola.bcs with the script block IF See("Anomen") THEN RESPONSE #100 Kill("Anomen") END. You can also use EVALUATE_BUFFER in COMPILE actions or before strings in values.
|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 [ #requiredSize ]||The ASCII ascString is written to the file starting at offset. If you specify a requiredSize then exactly that many bytes are written (if ascString is smaller, it is padded with NULs; if ascString is larger, it is truncated). If you do not specify a requiredSize, the terminating NUL is not written.|
|or||WRITE_ASCII_TERMINATE offset ascString||The ASCII ascString is written to the file starting at offset. If you do not specify a requiredSize, the terminating NUL is written.|
|or||WRITE_EVALUATED_ASCII offset ascString [ #requiredSize ]||The ASCII ascString is evaluated (so %variable% is replaced by its value) and written to the file starting at offset (as in WRITE_ASCII.|
|or||WRITE_FILE offset filename||The entire contents of “filename” (which may contain variables) are loaded and copied over the current file starting at offset offset. “filename” must be a literal filename like mymod/data/file.bam. If there is not enough room between offset and the end of the file for the contents of “filename” the patch will fail with an error message.|
|or||INSERT_FILE offset filename||Just like WRITE_FILE except that the entire contents of “filename” are inserted at offset, just as if you had done an INSERT_BYTES with the size of “filename” to that offset followed by a WRITE_FILE to that offset.|
|or||APPEND_FILE filename||Just like INSERT_FILE except that the entire contents of “filename” are appended to the end of the current file.|
|or||APPEND_FILE_EVALUATE filename||Just like APPEND_FILE except that
|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 [ ELSE value ]||An 8-bit value is read from the file at the given offset and is stored in the given variable. If offset is out-of-bounds and the ELSE is present, the ELSE-value is assigned to variable. If offset is out-of-bounds and the ELSE is not present, the patch fails with a visible error.|
|or||READ_SBYTE offset variable [ ELSE value ]||As READ_BYTE, but the value is interpreted as signed.|
|or||READ_SHORT offset variable [ ELSE value ]||A 16-bit value is read from the file at the given offset and is stored in the given variable. See READ_BYTE.|
|or||READ_SSHORT offset variable [ ELSE value ]||As READ_SHORT, but the value is interpreted as signed.|
|or||READ_LONG offset variable [ ELSE value ]||A signed 32-bit value is read from the file at the given offset and is stored in the given variable. See READ_BYTE.|
|or||READ_ASCII offset variable [ ELSE string ] [ ( value ) ]||A nul-terminated string is read from the file at the given offset and is stored in the given variable. The terminating nul is not stored. The default read size is 8 bytes. If an explicit size value is specified then that many bytes are read into the variable, even if some of them are nuls. See READ_BYTE. If the offset is out-of-bounds and the ELSE clause is present, the string is evaluated as in WRITE_EVALUATED_ASCII and then assigned into variable.|
|or||READ_STRREF offset variable [ ELSE string ]||A 32-bit Infinity Engine DIALOG.TLK string reference is read from the file at the given offset. The string reference is looked up in DIALOG.TLK and the (male) string value for it (without any quotes) is stored in the variable. In some sense this is the opposite of SAY_EVALUATED.|
|or||GET_STRREF value variable||The string reference value is looked up in DIALOG.TLK and the (male) string (without any quotes) is stored in the variable.|
|or||SET variable = value||Update variable so that it is equal to value.|
|or||variable = value||Update variable so that it is equal to value.|
|or||[ SET ] variable += value||Equivalent to SET variable = variable + value.|
|or||[ SET ] variable -= value||Equivalent to SET variable = variable - value.|
|or||[ SET ] variable *= value||Equivalent to SET variable = variable * value.|
|or||[ SET ] variable /= value||Equivalent to SET variable = variable / value.|
|or||[ SET ] variable &= value||Equivalent to SET variable = variable BAND value.|
|or||[ SET ] variable |= value||Equivalent to SET variable = variable BOR value.|
|or||[ SET ] variable <<= value||Equivalent to SET variable = variable BLSL value.|
|or||[ SET ] variable >>= value||Equivalent to SET variable = variable BLSR 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||FOR ( patch list ; value ; patch list ) BEGIN patch list END||The patch FOR (init;pred;inc) BEGIN body END is equivalent to init WHILE pred BEGIN body inc END. Note that the predicate value cannot be empty.|
|or||PATCH_BASH_FOR directory-file-regexp BEGIN patch list END||for all files that match directory-file-regexp, sets a bunch of variables
and executes the patches for each file found. Assuming the file being copied
is somedir/yourfile.cre, the following variables will be set:
"%BASH_FOR_DIRECTORY%" = "somedir" "%BASH_FOR_FILESPEC%" = "somedir/yourfile.cre" "%BASH_FOR_FILE%" = "yourfile.cre" "%BASH_FOR_RES%" = "yourfile"
|or||PATCH_FOR_EACH string1 IN string list BEGIN patch list END||will set the string1 variable to each value in string list and process each patch.|
|or||PATCH_IF value [ THEN ] BEGIN patch list END [ ELSE BEGIN patch list END ]||If value is non-zero, execute the first patch list once. Otherwise, execute the second patch list (if any). As a convenient shorthand, you may omit the BEGIN-END in the ELSE branch if the ELSE branch contains exactly one patch.|
|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||SET_2DA_ENTRY_LATER string value value value||Similar to SET_2DA_ENTRY, memorizes the 2da changes in the RAM without flushing them to the file. See the SET_2DA_ENTRY_LATER tutorial.|
|or||SET_2DA_ENTRIES_NOW string value||To be used with SET_2DA_ENTRY_LATER: flushes the file changes in one go. See the SET_2DA_ENTRIES_NOW tutorial.|
|or||INSERT_2DA_ROW value1 value2 String||String (after variable substitution) will be inserted as a new line in the table.
Value1 is the row count, value2 is the required column count. If row count is
the same as the number of long enough lines in the file, then the line will be inserted
at the end; if row count is lesser than the number of lines, it will be inserted just before
the nth line in the original file (starting count from 0); finally, if row count is greater
than the number of lines, the component will fail to install.
<<<<<<<< 2dafile asd foo a b c d e f g h i >>>>>>>> COPY ~2dafile~ ~2dafile~ INSERT_2DA_ROW 3 3 ~4 4 4~ INSERT_2DA_ROW 2 3 ~3 3 3~ INSERT_2DA_ROW 1 3 ~2 2 2~ INSERT_2DA_ROW 0 3 ~1 1 1~will result in
asd foo 1 1 1 a b c 2 2 2 d e f 3 3 3 g h i 4 4 4
|or||READLN variable||Waits for the user to provided an enter-terminated string and store it in variable. Said string will be stored and re-used at reinstall time. See the READLN tutorial that SimDing0 will provide shortly.|
|or||PATCH_RANDOM_SEED value||See RANDOM_SEED.|
|or||ADD_STORE_ITEM [ + ] itemName [ position ] numCharges ext2 ext3 itemFlag maxInStack [ unlimited ]||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.|
|or||READ_2DA_ENTRIES_NOW string value||Reads in one go all the 2da in the RAM for READ_2DA_ENTRY_FORMER. See the READ_2DA_ENTRIES_NOW tutorial.|
|or||READ_2DA_ENTRY_FORMER string value value value||Similar to READ_2DA_ENTRY, but reads from a prepared READ_2DA_ENTRIES_NOW command rather than from the file. See the READ_2DA_ENTRY_FORMER tutorial.|
|or||COUNT_2DA_ROWS value variable||The first value is the required column count. This command counts the number of rows in the current file (which should be a 2DA file) that have at least as many columns as the required column count and stores the result in the variable.|
|or||COUNT_REGEXP_INSTANCES optcase optexact regexp variable||This command counts the number of times regexp appears in the current file
and stores the result in the variable. Variable substitution is performed
on regexp prior to regexp handling (EG, if
|or||LOOKUP_IDS_SYMBOL_OF_INT variable idsFile value||The symbolic constant associated with value in idsFile (which may
contain user variables) is stored
in variable. If that doesn't work, value is stored in variable.
LOOKUP_IDS_SYMBOL_OF_INT foo ~spell~ 1101 SPRINT myfile "SPELL" LOOKUP_IDS_SYMBOL_OF_INT bar ~%myfile%~ (0x44c + 1) LOOKUP_IDS_SYMBOL_OF_INT baz ~spell~ 77777Both foo and bar are CLERIC_BLESS while baz is 777777.
|or||COMPILE_BAF_TO_BCS||The current file, which must be a valid BAF script, is compiled to a BCS. In general you should use the COMPILE TP2 Action instead, unless you are using other patch commands to modify the file under consideration.|
|or||DECOMPILE_BCS_TO_BAF||The current file, which must be a valid BCS script, is decompiled to a BAF.|
|or||DECOMPILE_DLG_TO_D||The current file, which must be a valid DLG file, is decompile to a textual D file (with string refs and no comments). Once you have a D file you can use other patch commands to change the actions and triggers around. You should use D actions (like REPLACE_ACTION_TEXT instead whenever possible.|
|or||COMPILE_D_TO_DLG||The current file, which must be a valid D file that defines a single DLG file (via an obvious BEGIN action) is compiled to a DLG. Typically this is only used after a DECOMPILE_DLG_TO_D.|
|or||REPLACE_EVALUATE optcase findRegexp BEGIN patch list END replaceRegexp||For every instance of the regexp findRegexp found, the
patch list is evaluated (with the variable MATCHi set to
the ith matched group in findRegexp), variable substitute is performed
on replaceRegexp, and then findRegexp is replaced by replaceRegexp. Any
writes dones by the patch list (e.g., SAY or
WRITE_ASCII) are ignored: SET should be the main
component of the patch list. For example:
COPY ~nice.baf~ ~mean.baf~ REPLACE_EVALUATE ~Give(\([0-9]+\),\([0-9]+\))~ BEGIN SET "RESULT" = ("%MATCH1%" + "%MATCH2%") / 2 END ~Take(%RESULT%)~This COPY TP2 Action would replace Give(10,20) with Take(15). optcase allows you to decide if the matching is case-sensitive or not.
|or||ADD_MAP_NOTE xCoord yCoord color String||If the file currently being patched is an ARE area file, this
patch command adds a map note to it. Valid colors include: gray,
violent, green, orange, red, blue, darkblue, lightgray.
COPY_EXISTING ~ar0202.are~ ~override/ar0202.are~ ADD_MAP_NOTE #123 #777 ~violet~ ~This is my new map note! Yippee!~Special thanks to Japh for coding this feature.
|or||ADD_KNOWN_SPELL splName spellLevel spellType||When applied to a CRE file, this patch causes the given spell to
be known. Note that spellLevel counts from 0 (e.g., you should say
#2 for a third-level Fireball). Possible values for spellType are
priest, innate and wizard. Example:
COPY_EXISTING ~some.cre~ ~override/some.cre~ ADD_KNOWN_SPELL ~SPPR314~ #2 ~priest~ // Unholy Blight is now known as a 3rd level priest spellSpecial thanks to Japh for coding this feature.
|or||REMOVE_KNOWN_SPELL splName list||When applied to a CRE file, this patch causes all of the
listed spells to be removed. Example:
COPY_EXISTING ~aerie.cre~ ~override/aerie.cre~ REMOVE_KNOWN_SPELL ~sppr101~ ~sppr102~Special thanks to Japh for coding this feature.
|or||REMOVE_MEMORIZED_SPELL splName list||When applied to a CRE file, this patch causes all of the
listed spells to be removed from their memory. Example:
COPY_EXISTING ~aerie.cre~ ~override/aerie.cre~ REMOVE_MEMORIZED_SPELL ~sppr101~ ~sppr102~
|or||ADD_CRE_ITEM itmName #charge1 #charge2 #charge3 flags slot [ EQUIP ] [ TWOHANDED ]||See the ADD_CRE_ITEM tutorial.|
|or||REPLACE_CRE_ITEM itmName #charge1 #charge2 #charge3 flags slot [ EQUIP ] [ TWOHANDED ]||If there's an item in the slot position, replace it; otherwise, process ADD_CRE_ITEM|
|or||PATCH_SILENT||makes WeiDU skip all messages; it's reenabled once a component is installed (or fails to), or a PRINT, PATCH_PRINT, VERBOSE or PATCH_VERBOSE is found.|
|or||PATCH_VERBOSE||undoes the latest SILENT command.|
|or||INNER_PATCH buffString BEGIN patch list END||Any WeiDU variables inside %s within buffString are replaced by
their values. All of the patches given are evaluated as if the
contents of the current file were buffString. Any modifications to
buffString are thrown away (making this mostly useful for reads).
INNER_PATCH "ABC" BEGIN READ_BYTE 0x2 "myvar" END PATCH_PRINT "myvar is %myvar%"This sequence will always print myvar is 67 (since 67 is the ASCII code for C).
|or||INNER_PATCH_SAVE savevar buffString BEGIN patch list END||As with INNER_PATCH, except that any modifications to buffString are stored inside
SPRINT foo "ABC" INNER_PATCH_SAVE bar "%foo%" BEGIN WRITE_BYTE 0x2 65 END PATCH_PRINT "foo is %foo%, bar is %bar%"This sequence will always print foo is ABC, bar is ABA (since 65 is the ASCII code for A).
|or||INNER_PATCH_FILE resource BEGIN patch list END||Any WeiDU variables inside %s within resource are replaced by
their values. If the resulting resource is present in the game or in the
override folder, the patches given are evaluated as if the
current file were that resource. If not, nothing happens. Any
modifications to that resource are thrown away (making this mostly useful
INNER_PATCH_FILE "SW1H01.ITM" BEGIN READ_BYTE 0x1 "myvar" END PATCH_PRINT "myvar is %myvar%"This sequence will always print myvar is 84 (since 84 is the ASCII code for T and SW1H01.ITM starts with ITM).
|or||INNER_ACTION BEGIN TP2 Action list END||See the INNER_ACTION tutorial, but loosely the current COPY is paused, the given TP2 Actions are executed, and then the current COPY is resumed. Note that an INNER_ACTION should never modify a file that is being modified by the current action. For example, never put APPEND foo.2da inside of COPY_EXISTING foo.2da . More formally, if the inner action and the outer action both modify the same file, the results are undefined.|
|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.|
|value||An expression that evaluates to an integer. See --debug-value.|
|is||integer||An absolute location or amount. 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. Briefly, fractions are dropped and the result is an integer (so 11 / 6 = 1). More technically, “this division rounds the real quotient of its arguments towards zero” -- see http://caml.inria.fr/pub/docs/manual-ocaml/libref/Pervasives.html for more information.|
|or||value ** value||Exponentiation. The first value is raised to the power of the second. This is done by converting both to floating point numbers, calculating the exponent, and converting the answer back to a 32-bit integer. If anything goes wrong the value zero is returned or the result is undefined.|
|or||value ** ( value value )||Fractional exponentation. a ** (b c) yields a raised to the
power of (b/c). All of the internal operations are done in floating
point. If anything goes wrong, zero is returned or the result is
SET x = 100 ** 2 SET y = 100 ** (1 2) // square root of 100 = 10 SET z = 100 ** (1 3) // cube root of 100 = 4.64 PATCH_PRINT "w = %w%, x = %x%, y = %y%, z = %z%"yields x = 10000, y = 10, z = 4.
|or||value = value||Integer Equality. If the two values evaluate to equal integers, the result is 1. Otherwise, the result is 0. See STRING_COMPARE for comparing Strings. Synonym: ==.|
|or||NOT value||Negation. If the value is 0, the result is 1. Otherwise the result is 0.|
|or||value != value||Integer Disequality. If the two values evaluate to equal integers, the result is 0. Otherwise the result is 1. See STRING_COMPARE for comparing Strings.|
|or||value OR value||Disjunction. If either value is non-zero, the result is 1. Otherwise, the result is 0. Synonym: ||.|
|or||value AND value||Conjunction. If both values are non-zero, the result is 1. Otherwise, the result is 0. Synonym: &&.|
|or||value BAND value||Bitwise And. 0b101 BAND 0b110 = 0b100. Synonym: &.|
|or||value BOR value||Bitwise Or. 0b101 BOR 0b110 = 0b111. Synonym: |.|
|or||value BXOR value||Bitwise Exclusive Or.
0b101 BXOR 0b110 = 0b011. Synonym:
|or||BNOT value||Bitwise Not.
BNOT 0b111 = 0b1111111111111111111111111111000. Synonym:
|or||value BLSL value||Bitwise Logical Shift Left. 0b101 BLSL 2 = 0b10100. Synonym: <<.|
|or||value BLSR value||Bitwise Logical Shift Right. 0b101 BLSR 2 = 0b1. Synonym: >>.|
|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||String STRING_COMPARE String||This is legacy synthax for STRING_EQUAL: This expressione valuates to 0 if and only if its two string arguments are equal (have the same length and the same contents). variables within the strings (e.g., “%mykit%”) are replaced by their values. Note that variables that you want expanded must be put in %'s, otherwise the raw text will be used. You may use STR_CMP as a synonym for STRING_COMPARE. This function works just like C's strcmp. Note also that STRING_EQUAL and STRING_COMPARE are similar, but STRING_EQUAL has more intuitive return values.|
|or||String STRING_COMPARE_CASE String||This is legacy synthax for STRING_EQUAL_CASE: As STRING_COMPARE, but the comparison ignores case. That is, "ANOMEN" and "aNoMeN" are considered equal.|
|or||String STRING_EQUAL String||This expressione valuates to 1 if and only if its two string arguments are equal (have the same length and the same contents), otherwise it values to 0. variables within the strings (e.g., “%mykit%”) are replaced by their values. Note that variables that you want expanded must be put in %'s, otherwise the raw text will be used. Note also that STRING_EQUAL and STRING_COMPARE are similar, but STRING_EQUAL has more intuitive return values.|
|or||String STRING_EQUAL_CASE String||As STRING_EQUAL, but the comparison ignores case. That is, "ANOMEN" and "aNoMeN" are considered equal.|
|or||String STRING_MATCHES_REGEXP String||As STRING_COMPARE_CASE, but the second string is treated as a regexp. Thus "AR1005" STRING_MATCHES_REGEXP "AR[0-9]+" evaluates to 0 (“no difference”). You may use STRING_COMPARE_REGEXP as a synonym.|
|or||String STRING_CONTAINS_REGEXP String||As STRING_MATCHES_REGEXP, but it evaluates to 0 if the first string contains the second regexp. Thus "AR1005" STRING_CONTAINS_REGEXP "[w-z]" evaluates to 1 (“mismatch”).|
|or||GAME_IS String||Returns true if the IE game variant
is one from the String, otherwise it returs false. String is a list of
whitespace separated entries, chosen between (case doesn't matter)
|or||IDS_OF_SYMBOL (File String)||will return the number associated with String in File.ids, or -1 if String is not associated in File.ids.|
|or||VARIABLE_IS_SET String||Returns true if the variable String is set (there's a variable called either
|or||VARIABLE_IS_SET String||Returns true if the variable String is set to an integer (there's a variable called either
|or||MOD_IS_INSTALLED modTp2Name modComponent||Returns true if the modComponent of modTp2Name is installed. The synthax is the same as with REQUIRE_PREDICATE.|
|or||STATE_WHICH_SAYS text FROM String||returns:
|or||STATE_WHICH_SAYS value IN String1 FROM String2||As above, except that @x references are taken from
the String1 tra file. In particular, one
solarom/american/epilogue.tra solarom/french/epilogue.tra solarom/german/epilogue.tra solarom/italian/epilogue.tra solarom/polski/epilogue.tra solarom/portuguese/epilogue.tra solarom/russian/epilogue.tra
|or||RANDOM ( value value )||A random-number generator. The first value is the lower bound, the second value is the upper bound. A random integer between the lower bound and the upper bound (inclusive) is returned. Thus RANDOM(3 5) can return 3, 4 or 5. If the lower bound is greater than the upper bound, zero is returned. See also RANDOM_SEED.|
|or||FILE_CONTAINS fileName regexp||Evaluates to 1 if the file fileName contains the regular expression regexp and 0 otherwise. Case is ignored.|
|or||FILE_CONTAINS_EVALUATED ( fileName varsRegexp )||First, all WeiDU variables enclosed in %s in varsRegexp and
substituted. The expression is 1 if the resulting regexp occurs
in fileName and 0 otherwise. If fileName does not exist or has
size 0, the result is 0. The comparison ignores case. See also
COPY_EXISTING_REGEXP ~.*\.CRE~ ~override~ READ_BYTE 0x273 class READ_ASCII 0x280 deathvar PATCH_IF (class = 14) AND // class 14 = CLERIC_MAGE (NOT FILE_CONTAINS_EVALUATED(~pdialog.2da~ ~%deathvar%~)) THEN BEGIN ADD_CRE_ITEM ~POTN08~ #10 #10 #10 ~IDENTIFIED~ ~INV15~ END BUT_ONLY_IF_IT_CHANGESThe example gives ten healing potions to all cleric-mages who cannot join the party. Notably it excludes Aerie since she has the death variable Aerie and pdialog.2da contains AERIE.
|is||FILE_EXISTS filename||Evaluates to 1 if the file exists in the filesystem and has non-zero size (or the bif file is referenced inside the chitin.key) and evaluates to 0 otherwise.|
|or||FILE_EXISTS_IN_GAME filename||Evaluates to 1 if the file exists as a game resource and has non-zero size. Evaluates to 0 otherwise. BIFFs and the override directory are searched in the standard manner. Evaluates to 0 for a file that does not exist but has been ALLOW_MISSING'd.|
|or||FILE_MD5 filename md5sum||Evaluates to 1 if the file exists and has the given MD5 checksum. Evaluates to 0 otherwise. 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, it evaluates to 0.|
|or||FILE_SIZE fileName fileSize||Evaluates to 1 if the size of the file fileName is exactly fileSize. Evaluates to 0 otherwise.|
|or||%variable%||The value of the variable is used.|
|or||EVALUATE_BUFFER variable||User variables inside the given string are evaluated one additional
time. You may prepend EVALUATE_BUFFER when a value is
called for and you would normally use a string. You may also use it for
SET and SPRINT statements. Example:
SPRINT x ~y~ SET y = 5 SPRINT z EVALUATE_BUFFER ~tricky %%x%%~ SET EVALUATE_BUFFER "%x%" += 77 PATCH_PRINT "y is %y% ; z is %z%"This prints out y is 82 ; z is tricky 5. You may also do hackery like FILE_SIZE "myfile" "%%indirection%%". Be very careful with this feature.
|or||%WEIDU_ARCH%||The special variable WEIDU_ARCH is set to either "x86" or "mac" at WeiDU startup and can be used to determine the underlying system architecture.|
|or||%WEIDU_OS%||The special variable WEIDU_OS is set to either "win32" or "osx" or "unix" at WeiDU startup and can be used to determine the underlying operating system.|
|or||%COMPONENT_NUMBER%||The special variable COMPONENT_NUMBER is set to the number of the component being installed.|
|or||%INTERACTIVE%||The special variable INTERACTIVE is set to 1 if the install is being done interactively (IE you launched setup-mymod.exe and you're installing mymod), or is set to 0 if the install is being done non-interactively (IE you launched setup-mymod.exe and now weidu is reinstalling othermod due to the domino uninstall and reinstall).|
|or||variable||The value of the variable is used.
In a patch value, you may use either %myvar% or
myvar to get the value of a variable, provided that your variable's
name is not the same as a WeiDU syntactic keyword or known
constant. Also note that while it is common to “call out”
variables by putting quotation marks around them, this is not necessary
if the variable is unambiguous. Thus the following three patches
are all equivalent:
SET "x" = "%y%" + "%z%" // these all SET "x" = "y" + "z" // do the SET x = y + z // same thing
|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, SET or SPRINT.|
|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.
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.
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.
COPY_EXISTING_REGEXP ~.*\.itm~ ~override~All this does is copy all files with a .itm extension to the override folder.
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.
PATCH_IF ("%type%" = "0x14") BEGINSo, 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.
WRITE_SHORT 0x88 6 WRITE_SHORT 0x8a 10x88 is the offset for dicesize and 0x8a is the offset for the number of dice. Again, I found these offsets using Near Infinity beforehand.
END BUT_ONLY_IF_IT_CHANGESFinally, we close the BEGIN that was after PATCH_IF and tell WeiDU not to copy the item if it didn't change (IE, if it wasn't a long sword).
COPY_EXISTING_REGEXP ~.*\.itm~ ~override~ READ_BYTE "0x1c" "type" PATCH_IF ("%type%" = "0x14") BEGIN WRITE_SHORT 0x88 6 WRITE_SHORT 0x8a 1 END BUT_ONLY_IF_IT_CHANGESOh 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.
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.
%SOURCE_SIZE%variable, which is set to the original file size. We can simply check that the file is large enough (using either NI or the IESDP, we see that a basic itm file must have at least 0x72 bytes long, so we can check that before doing anything:
COPY_EXISTING_REGEXP ~.*\.itm~ ~override~ PATCH_IF ("%SOURCE_SIZE%" > "0x71") BEGIN READ_BYTE "0x1c" "type" PATCH_IF ("%type%" = "0x14") BEGIN WRITE_SHORT 0x88 6 WRITE_SHORT 0x8a 1 END END BUT_ONLY_IF_IT_CHANGESAgain, we instruct WeiDU to not do a thing if the file is not large enough.
COPY_EXISTING ~acolyte1.cre~ ~override/acolyte1.cre~ PATCH_IF ("%SOURCE_SIZE%" > "0x2d3") BEGINNext, 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.
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.
READ_SHORT ("%itemslot%" + 0x04) "shield"Note: To access your variables after they've been read, you always have to wrap them in percent signs.
PATCH_IF ("%shield%" = "65535") BEGINNow, 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.
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.)
INSERT_BYTES ("%itemsoffset%" + "%#items%" * 0x14) 0x14Now 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) 1Finally, we close the PATCH_IF statements and add BUT_ONLY_IF_CHANGES.
END END BUT_ONLY_IF_IT_CHANGESHere's the full code:
COPY_EXISTING ~acolyte1.cre~ ~override/acolyte1.cre~ PATCH_IF ("%SOURCE_SIZE%" > "0x2d3") BEGIN READ_LONG "0x2bc" "itemsoffset" READ_LONG "0x2b8" "itemslot" READ_LONG "0x2c0" "#items" READ_SHORT ("%itemslot%" + 0x04) "shield" PATCH_IF ("%shield%" = "65535") BEGIN 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~ END END BUT_ONLY_IF_IT_CHANGESNote 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~ PATCH_IF ("%SOURCE_SIZE%" > "0x2d3") BEGIN READ_LONG "0x2bc" "itemsoffset" READ_LONG "0x2b8" "itemslot" READ_LONG "0x2c0" "#items" READ_SHORT ("%itemslot%" + 0x04) "shield" PATCH_IF ("%shield%" = "65535") BEGIN 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~ END END BUT_ONLY_IF_IT_CHANGES 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:
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.
ADD_STORE_ITEM + ~hamm05~ #10 #0 #0 ~IDENTIFIED~ #5One final note: remember to copy over your new item because ADD_STORE_ITEM doesn't do that part for you.
COPY_EXISTING ~ribald.sto~ ~override/ribald.sto~ ADD_STORE_ITEM ~myitem~ #10 #0 #0 ~IDENTIFIED~ #5 COPY ~mymod/myitem.itm~ ~override/myitem.itm~Note that you can also add a final optional string argument if you want the store to have unlimited copies of that item:
ADD_STORE_ITEM ~myitem~ #10 #0 #0 ~IDENTIFIED~ #1 ~UNLIMITED~That's about all there is to it.
COPY_EXISTING ~ribald.sto~ ~override/ribald.sto~ ADD_STORE_ITEM ~myitem~ AFTER ~olditem~ #10 #0 #0 ~IDENTIFIED~ #5 ADD_STORE_ITEM ~otheritm~ BEFORE ~olditem~ #10 #0 #0 ~IDENTIFIED~ #5Valid options for position are:
~item1 item2 item3), we add the new item BEFORE or AFTER the first item that is present in the list.
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.
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.
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 #456Note: 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.
SET_2DA_ENTRY value value value newEntryThe 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 25206Then 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 25206So 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 25206The 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 25206So 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 25206Hopefully, that explains the required column count a little better.
COPY_EXISTING_REGEXP ~.*\.itm~ ~override~ PATCH_IF ("%SOURCE_SIZE%" > "0x71") BEGINWe 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 PATCH_IF to tell WeiDU to only patch axes:
PATCH_IF ("%category%" = 25) BEGINNow, 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) BEGINFor 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) ENDThese 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.
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 a PATCH_IF command to do this
PATCHING ("%abilitytype%" = 1) BEGINSo we are patching when the ability type is 1 (melee). Time for the actual patch:
WRITE_SHORT ("%abilitiesoffset%" + ("%#abilities%" - 1) * 0x38 + 0x16) 6 WRITE_SHORT ("%abilitiesoffset%" + ("%#abilities%" - 1) * 0x38 + 0x18) 2Time to close the PATCH_IF statement.
ENDWe use exactly the same method for ranged abilities:
PATCH_IF ("%abilitytype%" = 2) BEGIN WRITE_SHORT ("%abilitiesoffset%" + ("%#abilities%" - 1) * 0x38 + 0x16) 12 WRITE_SHORT ("%abilitiesoffset%" + ("%#abilities%" - 1) * 0x38 + 0x18) 1 ENDWe can now close the main loop (using the aforementioned method):
SET "#abilities" = ("%#abilities%" - 1) ENDWe are done with our patches so we can add our usual END and BUT_ONLY.
END END BUT_ONLY_IF_IT_CHANGESThe full code is:
COPY_EXISTING_REGEXP ~.*\.itm~ ~override~ PATCH_IF ("%SOURCE_SIZE%" > "0x71") BEGIN READ_BYTE 0x1c "category" PATCH_IF ("%category%" = 25) BEGIN READ_LONG 0x64 "abilitiesoffset" READ_SHORT 0x68 "#abilities" WHILE ("%#abilities%" > 0) BEGIN READ_BYTE ("%abilitiesoffset%" + ("%#abilities%" - 1) * 0x38) "abilitytype" PATCH_IF ("%abilitytype%" = 1) BEGIN WRITE_SHORT ("%abilitiesoffset%" + ("%#abilities%" - 1) * 0x38 + 0x16) 6 WRITE_SHORT ("%abilitiesoffset%" + ("%#abilities%" - 1) * 0x38 + 0x18) 2 END PATCH_IF ("%abilitytype%" = 2) BEGIN WRITE_SHORT ("%abilitiesoffset%" + ("%#abilities%" - 1) * 0x38 + 0x16) 12 WRITE_SHORT ("%abilitiesoffset%" + ("%#abilities%" - 1) * 0x38 + 0x18) 1 END SET "#abilities" = ("%#abilities%" - 1) END END END BUT_ONLY_IF_IT_CHANGESSo what have we done? Let's list the steps involved:
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 = 1So if byte 0x23 is 0b00110101 and byte 0x24 is 0b10010001, then 0x23 BAND 0x24 is 0b00010001 whereas 0x23 BOR 0x24 is 0b10110101.
COPY_EXISTING_REGEXP GLOB ~.*\.itm~ ~override~ //copies all item files PATCH_IF ("%SOURCE_SIZE%" > "0x71") BEGIN // avoid crashing on empty items READ_BYTE "0x20" "mage" //reads the byte containing the mage usability flag PATCH_IF (("%mage%" BAND "0b00000100") = "0b00000000") BEGIN // if it is usable by mages READ_BYTE "0x1E" "bard" //reads the byte containing bard usability flag READ_SHORT "0x1C" "type" //reads the byte containing item type PATCH_IF ("%type%" = "67") OR ("%type%" = "2") BEGIN // if it is a robe or armor WRITE_BYTE "0x1E" ("%bard%" BAND "0b10111111") // makes usable by bards END END END BUT_ONLY_IF_IT_CHANGESThis 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.
COPY_EXISTING_REGEXP GLOB ~.*\.itm~ ~override~ //copies all item files PATCH_IF ("%SOURCE_SIZE%" > "0x71") BEGIN // avoid crashing on empty items 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 PATCH_IF ("%prof%" = "93") OR // two-handed sword ("%prof%" = "99") BEGIN // or halberd WRITE_BYTE "0x21" ("%race%" BOR "0b00010101") // makes unusable by dwarves, halflings, and gnomes END END BUT_ONLY_IF_IT_CHANGESIn 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.
|BG BAF Object||EA.GENERAL.RACE.CLASS.SPECIFIC.GENDER.ALIGN|
|PST BAF Object||EA.FACTION.TEAM.GENERAL.RACE.CLASS.SPECIFIC.GENDER.ALIGN|
|IWD2 BAF Object||EA.GENERAL.RACE.SUBRACE.CLASS.SPECIFIC.GENDER.ALIGNMNT|
Install Component [Delainy Portrait 1 by Bob]? [I]nstall, [N]ot Install, or [Q]uit Install Component [Delainy Portrait 2 by Fred]? [I]nstall, [N]ot Install, or [Q]uit Install Component [Delainy Portrait 3 by Wilhelmus]? [I]nstall, [N]ot Install, or [Q]uitThe end user would need to click through the options each time. Worse, conflicting components could be installed. These problems can be limited somewhat by judicious use of predicates, but taking advantage of the SUBCOMPONENT feature yields far superior results:
Install Component [Delainy Portrait]? [N]o, [Q]uit, or choose one: 1] Portrait 1 by Bob 2] Portrait 2 by Fred 3] Portrait 3 by WilhelmusOnly one of these options can be installed at any time; re-installing and selecting a different SUBCOMPONENT will automatically uninstall the previously installed one. Setting this up is dead simple:
BEGIN ~Portrait 1 by Bob~ /* The string above is displayed in the subcomponent listing, i.e. the list with 1] 2] 3] etc. You can, of course, use TRA references instead for this and the SUBCOMPONENT string below. */ SUBCOMPONENT ~ Delainy Portrait~ /* The string above is displayed as the component listing and must be the same for each SUBCOMPONENT. The tp2 code that follows is only executed if this SUBCOMPONENT is selected. */ COPY ~Delainy/portraits/opt1G.bmp~ ~override/CDDELAIG.bmp~ COPY ~Delainy/portraits/opt1M.bmp~ ~override/CDDELAIM.bmp~ COPY ~Delainy/portraits/opt1S.bmp~ ~override/CDDELAIS.bmp~ BEGIN ~Portrait 2 by Fred~ SUBCOMPONENT ~ Delainy Portrait~ COPY ~Delainy/portraits/opt2G.bmp~ ~override/CDDELAIG.bmp~ COPY ~Delainy/portraits/opt2M.bmp~ ~override/CDDELAIM.bmp~ COPY ~Delainy/portraits/opt2S.bmp~ ~override/CDDELAIS.bmp~ BEGIN ~Portrait 3 by Wilhelmus~ SUBCOMPONENT ~ Delainy Portrait~ COPY ~Delainy/portraits/opt3G.bmp~ ~override/CDDELAIG.bmp~ COPY ~Delainy/portraits/opt3M.bmp~ ~override/CDDELAIM.bmp~ COPY ~Delainy/portraits/opt3S.bmp~ ~override/CDDELAIS.bmp~Any REQUIRE_FILEs or other module requirements for the whole group should be put with the first subcomponent. If such a requirement fails, none of the subcomponents can be installed. In addition, each individual subcomponent can be guarded by its own predicate. If that predicate fails, that particular subcomponent cannot be installed. Example:
BEGIN ~Imoen: Turnip-Mage~ SUBCOMPONENT ~Imoen Mage Kit~ (FILE_EXISTS_IN_GAME ~turnip.spl~) // This particular subcomponent will only be displayed if TURNIP.SPL // exists. If it does not, you can still install other subcomponents.One note about SUBCOMPONENTs and mod ordering: WeiDU will display SUBCOMPONENTs in a single grouping no matter if they fall consecutively in the TP2. However, the component number (the one that gets placed in weidu.log and the one you check for in REQUIRE_COMPONENT et al.) is still based on their TP2 order.
COPY_EXISTING ~alynar.cre~ ~override/alynar.cre~ ADD_CRE_ITEM ~itemname~ #charge1 #charge2 #charge3 ~Flags~ ~Inventory Slots~ [EQUIP] [TWOHANDED]EQUIP and TWOHANDED are optional and are only needed when dealing with weapons. I'll give some examples now.
COPY_EXISTING ~alynar.cre~ ~override/alynar.cre~ ADD_CRE_ITEM ~ring06~ #0 #0 #0 ~IDENTIFIED~ ~RRING LRING~This will add the item to his right ring slot. If that slot is full, it will be added to the left ring slot. If both are full, we move the current right ring (the first slot in the list) to an an empty inventory slot so that the Ring of Protection is still put in it's appropriate slot.
COPY_EXISTING ~alynar.cre~ ~override/alynar.cre~ ADD_CRE_ITEM ~sw1h06~ #0 #0 #0 ~IDENTIFIED~ ~WEAPON2~Again, if there happens to be a weapon in his second weapon slot already, then we simply move that weapon to an empty inventory slot.
COPY_EXISTING ~alynar.cre~ ~override/alynar.cre~ ADD_CRE_ITEM ~sw1h06~ #0 #0 #0 ~IDENTIFIED~ ~WEAPON2~ EQUIPThis will put the sword in his second weapon slot and equip it. And again, if there's already a weapon in that slot, it will be moved to an empty inventory slot.
COPY_EXISTING ~alynar.cre~ ~override/alynar.cre~ ADD_CRE_ITEM ~sw2h01~ #0 #0 #0 ~NONE~ ~WEAPON2~ EQUIP TWOHANDEDThis will place the item in the second weapon slot (and again, if there's already a weapon there it's moved to inventory), remove anything that's in the shield slot and put it in an empty inventory slot and finally it will equip the weapon.
You are a big stupid moron if you do not install the [Component Name]. Install? [Y]es you idiot or [N]o, bonehead or [Q]uitFor obvious reasons, you would never want your mod's installer to actually say that but it will serve as a good working example.
@-1006= "You are a big stupid moron if you do not install the [" @-1008= "]. Install?\n[Y]es you idiot or [N]o, bonehead or [Q]uit"After making these changes, place prompts.tra into the TRA folder.
COPY_EXISTING_REGEXP GLOB ~.*\.cre~ ~override~ PATCH_IF (SOURCE_SIZE > 0x2c8) AND (NOT FILE_CONTAINS_EVALUATED("%SOURCE_RES%.ITM" "ITM")) THEN BEGIN // %SOURCE_RES%.ITM does not exist.See SOURCE_RES. Using "ITM" as the second argument makes FILE_CONTAINS_EVALUATED behave like FILE_EXISTS_EVALUATED, since any real item contains ITM as part of its header.
READ_SHORT 0x28 anim_id READ_SHORT 0x46 natural_ac READ_ASCII 0x59 resists (11) // read 11 bytes (even 0's) READ_LONG 0x2c4 eff_off READ_LONG 0x2c8 num_eff READ_ASCII eff_off effects (num_eff * 264) ADD_CRE_ITEM ~%SOURCE_RES%~ #0 #0 #0 ~IDENTIFIED~ ~INV10~Since there are 11 resistances (e.g., fire, cold, magic fire, ...) we are just leaving them in a string as an array of bytes rather than making up individual variables for each one. Now we want to go about creating our special skin item. We'll use the mysterious INNER_ACTION to do so.
INNER_ACTION BEGIN COPY ~foo/footemp.itm~ ~override/%SOURCE_RES%.ITM~ WRITE_SHORT 0x76 anim_id WRITE_SHORT 0xa6 natural_acUsing SOURCE_RES as part of a computed COPY destination is normally somewhat dangerous because it is reset every time you enter a COPY -- but we're OK here.
// copy over all resistances FOR (i=0; i<11 ; i=i+1) BEGIN INNER_PATCH "%resists%" BEGIN READ_BYTE i resist_i END foo_resist_off = 0xd6 + (i * (0x102 - 0xd2)) WRITE_LONG foo_resist_off resist_i ENDFor each of the 11 resistances we remember its value from the creature by using INNER_PATCH to get it out of our resists “array”. The resistance effects in footemp.itm are stored in the same order as the creature resistances are in a CRE file.
// copy all CRE effects PATCH_IF num_eff > 0 THEN BEGIN READ_ASCII 0x72 dummy_eff (0x30) FOR (i=0; i<num_eff; i=i+1) BEGIN INNER_PATCH "%effects%" BEGIN READ_ASCII ((i * 264) + 8) "cre_eff_opcode" (0x4) READ_ASCII ((i * 264) + 20) "cre_eff_value" (0x8) READ_ASCII ((i * 264) + 40) "cre_eff_res" (0x8) END INSERT_BYTES 0x72 0x30 // step 1 WRITE_EVALUATED_ASCII 0x72 "%dummy_eff%" // step 2 WRITE_EVALUATED_ASCII 0x72 "%cre_eff_opcode%" // step 3 WRITE_EVALUATED_ASCII 0x76 "%cre_eff_value%" // step 3 WRITE_EVALUATED_ASCII 0x86 "%cre_eff_res%" // step 3 END READ_SHORT 0x70 num_global_itm_eff WRITE_SHORT 0x70 (num_global_itm_eff + num_eff) END ENDHuzzah. Notice our use of variables to “pass information” between the main copying action and the INNER_ACTION. Yes, I hate WeiDU syntax as well. Why do you ask? Anyway, after that we update the item header to take into account the new global effects.
END BUT_ONLY_IF_IT_CHANGESFinally we close off our sanity-checks and throw in the ever-popular BUT_ONLY_IF_IT_CHANGES. It would be “easy” (read: annoying and time-consuming but possible) to extend this hackery so that it also stole all of the effects from undroppable items held by creature (e.g., most “undead” immunities are actually stored in RING95.ITM and not as creature effects) by nesting yet-another INNER_ACTION. You could even be selective and avoid copying over effects like “minimum hit points”. Then you could perhaps create an on-the-fly description for the “skin” item using SAY_EVALUATED.
COPY_EXISTING ~weapprof.2da~ ~override~ SET_2DA_ENTRY 34 7 1 3 SET_2DA_ENTRY 34 39 1 3 SET_2DA_ENTRY 34 40 1 3using S_2_E_L/N:
COPY_EXISTING ~weapprof.2da~ ~override~ SET_2DA_ENTRY_LATER ~_#_#_#weapprof~ 34 7 3 SET_2DA_ENTRY_LATER ~_#_#_#weapprof~ 34 39 3 SET_2DA_ENTRY_LATER ~_#_#_#weapprof~ 34 40 3 SET_2DA_ENTRIES_NOW ~_#_#_#weapprof~ 1The things to change are the following:
~_#_#_#weapprof~) before other parameters.
// this is OK and works COPY_EXISTING ~weapprof.2da~ ~override~ SET_2DA_ENTRY_LATER ~_#_#_#weapprof~ 34 7 3 SET_2DA_ENTRY_LATER ~_#_#_#weapprof~ 34 39 3 DO_SOMETHING_ELSE COPY_EXISTING ~weapprof.2da~ ~override~ SET_2DA_ENTRY_LATER ~_#_#_#weapprof~ 34 40 3 SET_2DA_ENTRIES_NOW ~_#_#_#weapprof~ 1 // this, however, is a bug: COPY_EXISTING ~weapprof.2da~ ~override~ SET_2DA_ENTRY_LATER ~_#_#_#weapprof~ 34 7 3 /* (*) */ DO_SOMETHING_ELSE COPY_EXISTING ~kitlist.2da~ ~override~ SET_2DA_ENTRY_LATER ~_#_#_#weapprof~ 34 40 3 SET_2DA_ENTRIES_NOW ~_#_#_#weapprof~ 1 // BUG: the (*) action is processed to kitlist.2da, not to weapprof.2da. // you can use this to scare off newbies if you're mean.Now, the formalities - which could be useful to avoid hopelessly crashing of your tp2.
SET_2DA_ENTRY_LATER ~string~ row col valuedoes the following variable associations (%i% is a counter which increases with each S_2_E_L instruction):
SET string%i%r = row
SET string%i%c = col
SPRINT string%i%v value
SET_2DA_ENTRIES_NOW ~string~ row_countbegins scanning all variables of the form string%i%r, string%i%c and string%i%v which I spoke about earlier, applies their changes and erases all those variables (so you can use the same string again).
COPY_EXISTING ~weapprof.2da~ ~override~ SET_2DA_ENTRY_LATER ~_#_#_#weapprof~ 34 7 3 SET_2DA_ENTRY_LATER ~_#_#_#weapprof~ 34 39 3 SET_2DA_ENTRY_LATER ~_#_#_#weapprof~ 34 40 3 SET _#_#_#weapprof1v = 5 // BUG: you're doing SET_2DA_ENTRY_LATER ~_#_#_#weapprof~ 34 39 5 (and not 3) SET_2DA_ENTRIES_NOW ~_#_#_#weapprof~ 1SUGGESTION: prefix your S_2_E_L/N strings (which can accept %variables%, by the way) with a standard prefix (and don't use variables starting with that prefix), to avoid accidental overwrites. I like to use, as a string,
_#_#_#FILE_NAMEwhere FILE_NAME is the name of the file I'm editing.
COPY_EXISTING ~kitlist.2da~ ~override~ READ_2DA_ENTRY 0 1 9 somevar // somevar will be "RESERVE"using R_2_E_N/F:
COPY_EXISTING ~kitlist.2da~ ~override~ READ_2DA_ENTRIES_NOW ~_#_#_#read_kitlist~ 9 READ_2DA_ENTRY_FORMER ~_#_#_#read_kitlist~ 0 1 somevar // somevar will be "RESERVE"If you have read the tutorial about SET_2DA_ENTRY_LATER and SET_2DA_ENTRIES_NOW you should already be familiar with the way to translate this, so I'll only explain the technicalities.
READ_2DA_ENTRIES_NOW string col_countwill store inside the variable
%string_row_col%the contents of the cell at row %row% and coloumn %col%, for all rows with at least
col_countitems. The variable
%string%will be set to the number of large enough rows (so, in effect, you also have processed COUNT_2DA_ROWS freely).
READ_2DA_ENTRY_FORMER string row col variablewill store inside
variablethe contents of
%string_row_col%which should be the content of the cell
row col col_countas defined by READ_2DA_ENTRY, unless the user edited the variable on his own. Here,
col_countis defined by READ_2DA_ENTRIES_NOW.
_#_#_#read_FILE_NAMEas a string (the
read_part is to proactivelu avoid possible overlappings with SET_2DA_ENTRY_LATER, while the WeiDU code also makes sure that these problems to not occur accidentally).
// at the tp2 flag level, IE after AUTHOR but before components. DEFINE_ACTION_MACRO ~HW~ BEGIN PRINT ~Hello World!~ END /* ... */ // a component BEGIN ~Print Hello World and do nothing~ LAUNCH_ACTION_MACRO ~HW~So, the synthax is quite simple:
DEFINE_ACTION_MACRO name-of-the-macro BEGIN local variables list action list ENDwill bind the action listed here with name-of-the-macro. We'll deal with local variables later. After that, LAUNCH_ACTION_MACRO name-of-the-macro will work like if you copy-pasted there the action list defined in DEFINE_ACTION_MACRO.
// before calling, have the string ~HW_Name~ contain the name of the person // to greet. DEFINE_ACTION_MACRO ~HW~ BEGIN PRINT ~Hello %HW_Name%!~ END /* ... */ OUTER_SPRINT ~HW_Name~ ~Jon~ LAUNCH_ACTION_MACRO ~HW~ OUTER_SPRINT ~HW_Name~ ~Jack~ LAUNCH_ACTION_MACRO ~HW~Variables inside macros that are changed keep their new value once the macro has finished being computed. This is bad and good at the same time. For example, you might want to calculate a factorial:
// set ~%factorial_index%~ to the factorial you'd like to compute, // ~%factorial_result%~ to 1, // keep a backup copy of ~%factorial_index%~ // ~%factorial_result%~ will yield the resulting factorial. DEFINE_ACTION_MACRO ~factorial~ BEGIN ACTION_IF ~%factorial_index%~ != 1 THEN BEGIN OUTER_SET ~factorial_result~ = ~%factorial_result%~ * ~%factorial_index%~ OUTER_SET ~factorial_index~ = ~%factorial_index%~ - 1 LAUNCH_ACTION_MACRO ~factorial~ END END /* ... */ OUTER_SET ~factorial_index_old~ = 5 OUTER_SET ~factorial_index~ = 5 OUTER_SET ~factorial_result~ = 1 LAUNCH_ACTION_MACRO ~factorial~ PRINT ~%factorial_index_old%! = %factorial_result%~TThe fact that variables are kept through the entire tp2 execution (formally, that they are global) is good, since you have a way of 'remembering' the result; however, the bad part is that it may happen that variables are changed even if you didn't want them to.
LOCAL_SET name = value LOCAL_SPRINT variable stringto declare a variable as local, and assign it a new value. Use this for all variables that don't need to be changed by the macro. After computing the macro, these variables are set to the value they had before the macro was processed. They work for both actions and patches. For example, here is a corrected factorial macro (also converted to patch):
// set ~%tb#factorial_index%~ to the factorial you'd like to compute, // ~%tb#factorial_result%~ to 1, // ~%ftb#actorial_result%~ will yield the resulting factorial. DEFINE_PATCH_MACRO ~tb#factorial~ BEGIN LOCAL_SET ~tb#factorial_index~ = ~%factorial_index%~ PATCH_IF ~%tb#factorial_index%~ != 1 THEN BEGIN SET ~tb#factorial_result~ = ~%tb#factorial_result%~ * ~%tb#factorial_index%~ SET ~tb#factorial_index~ = ~%tb#factorial_index%~ - 1 LAUNCH_PATCH_MACRO ~tb#factorial~ END END /* ... */ SET ~tb#factorial_index~ = 5 SET ~tb#factorial_result~ = 1 LAUNCH_PATCH_MACRO ~tb#factorial~ PATCH_PRINT ~%tb#factorial_index%! = %tb#factorial_result%~
tb#factorial: computes the factorial of a number.
tb#factorial_indexto the factorial you'd like to compute.
SET ~tb#factorial_index~ = 5 SET ~tb#factorial_result~ = 1 LAUNCH_PATCH_MACRO ~tb#factorial~ PATCH_PRINT ~%tb#factorial_index%! = %tb#factorial_result%~(since you've read the warnings from the previous tutorial, standard macros make use of LOCAL_SET/SPRINT to avoid overwriting variables that shouldn't change).
tb#fix_file_size: overwrites all files matching a certain regexp with a certain standard file, if they are under a certain size.
tb#fix_file_size_minto the maximum allowed size (files whose size is exactly this value are NOT overwritten).
tb#fix_file_size_targetto the standard file used to replace file(s) that match the regexp.
tb#fix_file_size_categoryto a descriptive name.
tb#fix_file_size_regexpto the regexp for the file(s) to be checked for a match.
LOAD ~tb#fix_file_size~ /* ... */ OUTER_SET tb#fix_file_size_min = 0x72 OUTER_SPRINT tb#fix_file_size_target "SW1H01.ITM" OUTER_SPRINT tb#fix_file_size_category "items" OUTER_SPRINT tb#fix_file_size_regexp "^.*\.itm$" LAUNCH_ACTION_MACRO ~tb#fix_file_size~
GROUP string Code: BEGIN ~100% Learn Spells~ GROUP ~Convenience Tweaks~ // component code BEGIN ~Identify All Items~ GROUP ~Convenience Tweaks~ // component code BEGIN ~Give Edwin his BG2 Stats~ GROUP ~NPC Tweaks~ // component code BEGIN ~ Give Jaheira her BG2 Stats~ GROUP ~NPC Tweaks~ // component codeUpon installing the mod, the player is now presented with meta-options on each group at a high level:
Would you like to display the category [Convenience Tweaks]? [Y]es/[N]o Would you like to display the category [NPC Tweaks]? [Y]es/[N]oSelecting
[N]oon any group suppresses those options from being displayed, leading to a simpler and more controlled installer experience for the player. In the provided example, selecting
[N]oto Convenience Tweaks and
[Y]esto NPC Tweaks would result in WeiDU starting installation by asking to install the Give Edwin his BG2 Stats component.
tb#factorial: computes the factorial of a number.
tb#factorial_indexto the factorial you'd like to compute.
tb#fix_file_size: overwrites all files matching a certain regexp with a certain standard file, if they are under a certain size.
tb#fix_file_size_minto the maximum allowed size (files whose size is exactly this value are NOT overwritten).
tb#fix_file_size_targetto the standard file used to replace file(s) that match the regexp.
tb#fix_file_size_categoryto a descriptive name.
tb#fix_file_size_regexpto the regexp for the file(s) to be checked for a match.
$^.*+?'"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 "" literallySo spe.* matches "sper01.itm" and "sper.eff" and "special".
// KEYWORD VALUE AREA_CITY 0252 AREA_DAY 0260 AREA_DUNGEON 0256 AREA_FOREST 0248 AREA_NIGHT 0264 ATTACK1 0220 ATTACK2 0224 ATTACK3 0228 ATTACK4 0232 BATTLE_CRY1 0200 BATTLE_CRY2 0204 BATTLE_CRY3 0208 BATTLE_CRY4 0212 BATTLE_CRY5 0216 BIO 0x1cc BORED 0196 COMPLIMENT1 0352 COMPLIMENT2 0356 COMPLIMENT3 0360 CRITICAL_HIT 0424 CRITICAL_MISS 0428 DAMAGE 0236 DESC 0x54 DIALOGUE_DEFAULT 0412 DIALOGUE_HOSTILE 0408 DYING 0240 EXISTANCE1 0444 EXISTANCE2 0448 EXISTANCE3 0452 EXISTANCE4 0456 EXISTANCE5 0460 HAPPY 0172 HURT 0244 IDENTIFIED_DESC 0x54 INITIAL_MEETING 0164 INSULT 0340 INTERACTION1 0320 INTERACTION2 0324 INTERACTION3 0328 INTERACTION4 0332 INTERACTION5 0336 INVENTORY_FULL 0436 LEADER 0188 MISCELLANEOUS 0384 MORALE 0168 NAME1 8 NAME2 12 PICKED_POCKET 0440 REACT_TO_DIE_GENERAL 0376 REACT_TO_DIE_SPECIFIC 0380 RESPONSE_TO_COMPLIMENT2 0388 RESPONSE_TO_COMPLIMENT3 0392 RESPONSE_TO_INSULT1 0396 RESPONSE_TO_INSULT2 0400 RESPONSE_TO_INSULT3 0404 SELECT_ACTION1 0292 SELECT_ACTION2 0296 SELECT_ACTION3 0300 SELECT_ACTION4 0304 SELECT_ACTION5 0308 SELECT_ACTION6 0312 SELECT_ACTION7 0316 SELECT_COMMON1 0268 SELECT_COMMON2 0272 SELECT_COMMON3 0276 SELECT_COMMON4 0280 SELECT_COMMON5 0284 SELECT_COMMON6 0288 SELECT_RARE1 0416 SELECT_RARE2 0420 SPECIAL1 0364 SPECIAL2 0368 SPECIAL3 0372 TARGET_IMMUNE 0432 TIRED 0192 UNHAPPY_ANNOYED 0176 UNHAPPY_BREAKING 0184 UNHAPPY_SERIOUS 0180 UNIDENTIFIED_DESC 0080 HIDDEN_IN_SHADOWS 0444 SPELL_DISRUPTED 0448 SET_A_TRAP 0452 STORE_NAME 12 SCRIPT_OVERRIDE 0x248 SCRIPT_CLASS 0x250 SCRIPT_RACE 0x258 SCRIPT_GENERAL 0x260 SCRIPT_DEFAULT 0x268 DEATHVAR 0x280 DIALOG 0x2cc AREA_SCRIPT 0x94
This document was translated from LATEX by HEVEA.