Beginner’s Guide to BG2 Scripting.

 

Version History

 

Original Release:           Goran Rimén                V1.1 Nov 2001

Additional Notes:          Yovaneth Scet               V1.2 Dec 2004

 

Introduction

 

This document is based on Bioware’s Quick Reference Guide and has been reworked by Goran Rimén. Any information contained herein, are provided in an “as is” state.

 

The aim is that this guide should only contain information relevant to creating a player script. I have deleted everything that I feel is only useful for Bioware when creating a game. Maybe I have deleted a line too much if that line is of importance you can mail me at goran.rimen@swipnet.se.

 

All Yovaneth’s Additional Notes are in italic. If it’s italic, it’s  Yovaneth’s. If it’s not italic, it’s Goran’s unless specifically stated. I have also changed the code samples to navy so that they display more easily.

 

Additional Notes on ids files

All Infinity Engine games are not created equal. This guide refers to BG2:SoA and in many places refers you to the ids files as supplied with the original Bioware script compiler. Over the years these ids files have been modified, upgraded, lost and resurrected by the many game scripters that have worked on the Infinity Engine. In general anything written here can be applied to the other games, but note the phrase “in general”. BG1 is missing many of the BG2  identifiers (ids) and IWD1/II has a whole load of different ones. Planescape:Torment is in a whole new park by itself.  Additionally, some game compilers did not include a full listing of the working ids files for that game. Worse still, many ids that are included are either broken or simply did not work when the games were released. I am reasonably certain that the downloadable ids file packages available on this site are as good as can currently be had; with Kensai Ryu’s help and encouragement I spent a lot of time cross-checking ids against BG1, BG2 and IWD1. I don’t, however, claim that they are complete.

 

There was a useful page on the PlagueZone forums where Horred had spent a great deal of time checking triggers and actions. PlagueZone is no longer with us (although Horred still is!) and so I have taken the liberty of appending Horred’s work to the end of this file. I have also added links to other scripting guides: no one guide has all the information but used together, they do cover almost all of what is possible.

 

 

Creating Scripts

 

Initial Planning:

Decide what you want the script to do before actually starting one. Well I suppose that is the reason why you are reading this.  This scripting language has its limits and there are just a few things you can do expect attacking and killing an enemy, which is very useful indeed. You can use items such as potions, wands etc. You can cast a spell for killing an enemy or healing yourself. A problem with large parties is to keep them together when moving for one area to another. The process is somewhat easier if you use a hotkey. When I press key S, I want all my party members to move to the calling Player x on the double. I also want to hear the caller say something so I know that the first module of the script works.

 

Preparation:

After you have decided what you want the script to do open notepad or whatever else you would like to use to create plain text file and begin entering in the commands you will need. 

 

Coding: 

This is how my script would look:

 

//Module#1

IF

      ActionListEmpty()       // If true, I have no actions on my queue

      HotKey(S)               // if true, key S is pressed

THEN

      RESPONSE #100

      VerbalConstant(Myself(),LEADER)     // the selected player will say something

      GlobalShout(1001)                   // Send all in the area Heard trigger #1001

      Wait(6)// seconds

END

//

// Module#2, Respond to GlobalShout or Shout

// heard 1001 = COME_TO_ME

IF

      ActionListEmpty()             //true if I have no actions on my queue

      Heard([PC],1001)              //true if someone has send Heard trigger #1001

      !Range(LastHeardBy(),12)      //True if the range to the caller is > 12 search

                                    //squares from Myself

THEN

      RESPONSE #100

      SetInterrupt(FALSE)

            MoveToObjectFollow(LastHeardBy())   // Myself will walk to the caller without any delay

            SetInterrupt(TRUE)

END

 

 

In the first module of the script, I have two triggers  ‘ActionListEmpty()’ and ‘HotKey(S)’ in the condition part of the module. Both triggers must be true in order to have the response part of the module being carried out. There are three actions, VerbalConstant(Myself(),LEADER), GlobalShout(1) and Wait(6), in the response part of module 1. In module #2, I have three triggers in the condition part and three actions in the response part. Note the use of !, when checking that the range to the caller is not (using the ! to reference not) within 12 ssq. Any command with the ! operator should be resolved without it and then the resulting TRUE is switched to FALSE and vice versa.

 

Compiling:

Once the script is done I save it to the Source directory of the script compiler.  The filename must be 1 – 8 characters with the extension .BAF.  Eg. Myscript.baf. In the “BG2/script compiler” directory you will find Compile.Bat and mybe test.bat

 

Compile.bat contains following line:

 

      AICompile FILE source\%1.baf errors\%1.err compiled\%1.bs

 

Don’t change anything in Compile.bat unless you know what you are doing.

 

If Test.bat is missing you can create it with Notepad. Test.bat should contain following line:


      call Compile Myscript

 

I then run Test.bat. Of course you can rename Test.bat to whatever you like, just remember to type <FILENAME> without the extension .Baf.

 

The compiler will notify you of any errors in the script by placing an error file in the ERRORS directory with the script’s filename and extension .ERR.  If the file size is 0 then the script compiled successfully.  And the final script will be placed in the COMPILED directory with the filename and extension .BS. 

 

This will then have to be copied into your SCRIPTS directory of BG2.

 

Note. The error handling is rather primitive and I know some errors that will be compiled without warning.

 

Next step is to attach Myscript to two of my BG2 characters. I select my first character and open the Record window, press the button Customise to open the Customise Character window, press button Script to open the Pick Script window. In the list on the left side I will after some scrolling find a line containing Myscript.  I select that and close the windows by pressing Done, Done. Now it is time to test Myscript and start improving the script. I have these two modules in the very beginning of all my scripts.

 

Additional Notes on Compiling

The original AICompile.exe from Bioware is lacking in a number of areas; as Goran has said, it will quite often compile faulty .baf files without generating warnings. It is not really a compiler, but a symbol generator whereby long script actions are reduced to a few symbolic letters. To be sure that you do get a script compiled without errors, use Near Infinity’s Script Dropzone. Both DLTCEP and WeiDU will also compile scripts but I am unsure how much checking they do in the process.

 

 

Definitions:

 

Creature:

Creatures include all objects capable of action. This includes player characters.

 

Trigger:

A trigger is sent to a creature when a certain event occurs.  A trigger includes the knowledge of the cause of the trigger

 

Condition:

Condition is a combination of triggers joined with ands.

 

Action:

Action is a specified response to a condition.  An action requires the knowledge of who (or what) to perform the action upon.

 

Response:

Response, is a combination of actions, which will be performed sequentially.  It is possible for a response to be interrupted.

Note. Eg. spellcasting but this may also be a way of telling that the game engine is not that reliable

 

Response Set:

Response set is a combination of Responses, each with weighting from 1 to 100.  This weighting is used to randomly determine which response is actually executed.

Eg. 1

 

IF

      Heard([PC],1001)

      !Range(LastHeardBy(),8)

THEN

      RESPONSE #1

            MoveToObject(LastHeardBy())

END

 

The chance that this action will be carried out is 1 of 1 or 100%

Eg. 2

 

IF

      HotKey(S)

THEN

      RESPONSE #1

            VerbalConstant(Myself,3)

            Wait(6)

      RESPONSE #2

            VerbalConstant(Myself,4)

            Wait(6)

      RESPONSE #3

            VerbalConstant(Myself,5)

            Wait(6)

END

 

To calculate the chance for a certain response to be activated, when you have several to chose of. Do following.  Sum all the response numbers. In this case 1+2+3 = 6.

The chances are, for response #1, 1 chance of 6, for response #2, 2 chances of 6, and for response #3, 3 chances of 6.

 

Distances

Distances are measured in search squares (ssq.). How long is a ssq? , Well, nobody can give a good answer, and it is of no real interest. Here are some useful distances:

 

Range 0, distance to myself

Range 1, touch spells, all one-handed weapons

Range 2, two-handed weapons

Range 4, lower limit for use of ranged weapon. If you are at range 4 or closer you will get an attack penalty.  

Range 25, max distance your character can see

 

Time

The duration of certain spells is measured in rounds or turns, and one round is six seconds, and one turn is ten rounds.

 

The casting time of a spell is measured 1/10 of a round. Eg, Casting time 5 equals 3 seconds

In some actions, such as RunAwayFrom(ObjectType,Time), time is measured in AI ticks and one tick is 1/15 of a second.

In SetGlobalTimer(“Name”,”Area”,Time), time is normal seconds.

 

 

Additional Notes on Time

See also Igi’s tutorial “Time Conversion: Game Time ó Real Time”. Link at the end of the document.

 

 

Creature and Object Identification:

 

Creature and Object Identification is done through the ObjectType Class.

 

Object

The IDS file used is Object.ids, which you can find in the ScriptCompiler directory. Open it with notepad and you will find all the different objects used by Bioware in the game. However many are not implemented and can not be used in a player script. This is true for all .ids files.   

 

Useful objects are:

 

Myself     

LeaderOf

MostDamagedOf    

LastAttackerOf

LastHelp   

NearestEnemyOf

LastHeardBy

LastSeenBy

Player1    

Player2

Player3    

Player4

Player5    

Player6

Protagonist

SecondNearestEnemyOf

ThirdNearestEnemyOf    

FourthNearestEnemyOf

FifthNearestEnemyOf    

SixthNearestEnemyOf

SeventhNearestEnemyOf  

EighthNearestEnemyOf

NinthNearestEnemyOf    

TenthNearestEnemyOf

NearestEnemyOfType     

SecondNearestEnemyOfType etc

             

Note. All of these objects are only for use within the party. You can not do things like:

 

See(MostDamagedOf([ENEMY])// this will not work because it is not supported.

 

ObjectType

ObjectType is arranged as follows

 

EnemyAlly <-General<-Race<-Class<-Specifics<-Instance<-SpecialCase

 

You can use ObjectType to identify special targets

 

EnemyAlly

EnemyAlly is a range between the PC and the evil NPC’s . Creatures can fall anywhere along this range. The IDS file used is EA.ids, which you can find in the ScriptCompiler directory.

 

Useful EA objects are:

 

ANYTHING    Refers to any object.

ENEMY       Refers to anyone evil, generally red circles.

PC          Refers to any of the possible six party members.

GOODCUTOFF  Refers to anyone good. Party members, allies, summoned monsters,

EVILCUTOFF  Refers to anyone evil. Creature or summoned

                 

Eg.

 

Exists([ENEMY]),

 

will identify any enemy to you regardless of generic characteristics, race, class etc 

 

General

General specifies the generic characteristics of the creature.

The IDS file used is General.ids, which you can find in the ScriptCompiler directory.

 

Useful General objects are:

 

HUMANOID,

UNDEAD,

GIANTHUMANOID,

MONSTER

 

Eg.

 

Exists([0. UNDEAD])

 

will identify any UNDEAD to you regardless of EnemyAlly, race, class etc

 

Eg.

 

Exists([ENEMY.UNDEAD])

 

will identify all UNDEAD that is an enemy to you.

 

Race

Race is the race of the creature.

The IDS file used is Race.ids, which you can find in the ScriptCompiler directory.

In this file there are several useful race objects

 

Eg.

 

Exists([0.0.GOLEM])

 

will identify any GOLEM regardless of EA, General or class 

Eg.

 

Exists([ENEMY.0.GOLEM])

 

will identify all GOLEM that are an enemy to you

 

Class

Class is the class information (Mage, Fighter …) Alternatively it can be used for more detailed information (e.g. Drow is more specific then elf). The IDS file used is Class.ids, which you can find in the ScriptCompiler directory. In this file there are several useful class objects

 

Eg.

 

See(NearestEnemyOfType([0.0.0.DRUID_ALL]))

See(NearestEnemyOfType([0.0.0.BARD_ALL]))

See(NearestEnemyOfType([0.0.0.CLERIC_ALL]))

See(NearestEnemyOfType([0.0.0.MAGE_ALL]))

 

Specifics

Specifics holds the identification for special NPC’s. The IDS file used is Specific.ids, which you can find in the ScriptCompiler directory. The only use I found so far is following:

 

See(NearestEnemyOfType([0.0.YUANTI.0.NO_MAGIC]))

 

This will identify a YUANTI mage.

Note. I wonder why Bioware used NO_MAGIC to identify a mage?

 

Instance

Instance holds the gender identification for special NPC’s

The IDS file used is Gender.ids, which you can find in the ScriptCompiler directory.

Eg

 

NumCreatureLT([GOODCUTOFF.0.0.0.0.SUMMONED],1)

 

is true if the total number of summoned creatures in my party is less than 1. The maximum number of summoned creatures allowed in a party is 5.

 

 

Special Object Identification:

 

NAME

This is the name used by Bioware to identify certain creatures.

Note. Only the names of a NPC in your party can be used, Nalia, Imoen, Imoen2, Minsc, but the name of the character you have created can not be identified this way.

 

IF

      Name("Nalia",Myself)          // true if I am Nalia

THEN

      RESPONSE #100

            DisplayStringHead(Myself,9102)//Nalia

END

 

This module will display the word Nalia above the head of Nalia, if the object “Nalia” exists

 

LeaderOf()

 

-Current leader of the Player group.

-The Character in portrait slot 1

 

IF

      ActionListEmpty()

      !Range(LeaderOf(),20)   // true if my distance to my leader is more than  20 ssq.

THEN

      RESPONSE #100

            SetInterrupt(FALSE)

            MoveToObject(LeaderOf())

            SetInterrupt(TRUE)

END

 

This module will make me follow the current leader of my group

 

LastAttackerOf(InPartyObject)

The Creature that last did the Object damage. By leaving out the Object within the brackets, the Object defaults to Myself.

 

IF

      Help([PC])                          // true if any PC has send Help trigger

      !Range(LastHeardBy(),0)             //true if range to caller is > 0

      !Range(NearestEnemyOf(),4)          //true if range to my nearest enemy is >4

See(LastAttackerOf(LastHelp()))     //true if I can see the last attacker of the last

//caller for help

THEN

      RESPONSE #100

      DisplayString(Myself,49769)         //will print ‘I shall help thee!’ in dialog window.

      EquipRanged()

      AttackOneRound(LastSeenBy())        // I will attack the last creature that attacked the

// caller of help

END

 

 

LastHeardBy(InpartyObject)           

Last heard by the Object. This module will make Myself to move to the last player that used SHOUT or GLOBALSHOUT

 

IF

      Heard([PC],1)

      !Range(LastHeardBy(),12)

THEN

      RESPONSE #100

            MoveToObject(LastHeardBy())

END

 

LastHelp(InpartyObject)

Last party member I heard call for help

 

IF

      Help([PC])              //true if any PC has sent a Help trigger

      See(LastHelp())         //true if I can see the caller that sent a Help trigger

      HaveSpell(CLERIC_HEAL)  // true if I have the spell CLERIC_HEAL

THEN

      RESPONSE #100

            MoveToObject(LastSeenBy ())

            Spell(LastSeenBy(),CLERIC_HEAL)

END

 

LastSeenBy(InpartyObject)

LastSeenBy() will hold a pointer to the object in the last See(AnyObject) statement used. This short script is very useful for testing different object. Just change NearestEnemyOf () to something else and see where the word ‘Nalia’ appears.

 

Note. LastSeenBy() is the backbone in my scripts        

 

IF

      See(NearestEnemyOf())

Range(LastSeenBy(),12)  //you can try different values to find the max value for Range() 

THEN

      RESPONSE #100

            DisplayStringHead(LastSeenBy(),9102)//Nalia

END

 

Myself

This is the Player that the script belongs to.

Eg.

 

UseItem("POTN17",Myself)

 

MostDamagedOf(InpartyObject)

Group member with the lowest percentage of remaining hit points

 

IF

      !Exists([ENEMY])        // true if there are no enemies around

      See(MostDamagedOf())    // true if I can see the most damaged member of my group

      HaveSpell(CLERIC_CURE_LIGHT_WOUNDS)

THEN

      RESPONSE #100

            Spell(LastSeenBy(),CLERIC_CURE_LIGHT_WOUNDS)

END

 

NearestEnemyOf(InpartyObject)

Returns the nearest creature with Enemy / Ally flag opposite to the Object.

 

IF

      Exists([ENEMY])               // true if there are any enemies with visual range

      See(NearestEnemyOf(Myself))   //true if I can see the nearest enemy of myself            

THEN

      RESPONSE #100

            Continue()              //with next module

END

 

IF

      Exists([ENEMY])               //Exists will not change the pointer stored in LastSeenBy

      Range(LastSeenBy(),4)

THEN

      RESPONSE #100

            EquipMostDamagingMelee()

            AttackOneRound(LastSeenBy())

END

 

NearestEnemyOfType(ObjectType)

Nearest enemy of myself that is of the Type. This module make LastSeenBy() to point at a Ranger if that ObjectType exists

 

IF

      Exists([ENEMY])

      !Range(NearestEnemyOf(),4)                //enemy too close

      See(NearestEnemyOfType([0.0.0.RANGER]))   // true if my nearest enemy is a Ranger

      !Range(LastSeenBy(),4)                    // must be ranged attacker

THEN

      RESPONSE #100

            SetGlobal("GR_IsAttackedByLongBow","LOCALS",1)//Yes

            Continue()

END

 

Player1 or Protagonist

This is the Main Character or Protagonist eg.

 

See(PLAYER1) or See(Protagonist)

 

Player2 – Player6

Character 2, 3, 4, 5 or 6 in the order they have joined.

 

IF   

      HaveSpell(4222)         // innate spell PALADIN_REMOVE_FEAR

      See(PLAYER2)

      StateCheck(LastSeenBy(Myself),STATE_PANIC)

THEN

      RESPONSE #100

            DisplayString(Myself,12083)//Remove Fear

            Spell(LastSeenBy(Myself),4222)

END

 

SecondNearestEnemyOf(InpartyObject)

You can check the first ten nearest enemies. This module will select the furthest enemy and attack for one round (6 seconds)

           

IF

      See([ENEMY])

      !Range(NearestEnemyOf(),4)    //true if the distance to my nearest enemy is >4 ssq. 

      OR(9)

            See(SecondNearestEnemyOf())

            See(ThirdNearestEnemyOf())

            See(FourthNearestEnemyOf())

            See(FifthNearestEnemyOf())

            See(SixthNearestEnemyOf())

            See(SeventhNearestEnemyOf())

            See(EighthNearestEnemyOf())

            See(NinthNearestEnemyOf())

            See(TenthNearestEnemyOf())

THEN

      RESPONSE #100

            EquipRanged()

AttackOneRound(LastSeenBy())  //I will attack the furthest creature that I see

//using a ranged weapon

END

 

Note1. The OR statement is read from the first line to the last. If all ten objects exists LastSeenBy will point at the TenthNearestEnemyOf(). But, if the listing is reversed and all ten objects exists LastSeenBy will point at the SecondNearestEnemyOf().

 

Note2. This is shorter script to target the furthest enemy.

 

IF

      See([ENEMY])

      !Range(NearestEnemyOf(),4)          //true if the distance to my nearest enemy is >4 ssq. 

      See(TenthNearestEnemyOf())

THEN

      RESPONSE #100

            EquipRanged()

            AttackOneRound(LastSeenBy())// I will attack the furthest creature that I see using

// a ranged weapon

END

 

SecondNearestEnemyOfType(ObjectType)

You can check the first ten nearest.

 

IF

      See([ENEMY])

      !Range(NearestEnemyOf(),4)//true if the distance to my nearest enemy is >4 ssq. 

      OR(9)

            See(SecondNearestEnemyOfType([0.0.0.RANGER]))

            See(ThirdNearestEnemyOfType([0.0.0.RANGER]))

            See(FourthNearestEnemyOfType([0.0.0.RANGER]))

            See(FifthNearestEnemyOfType([0.0.0.RANGER]))

            See(SixthNearestEnemyOfType([0.0.0.RANGER]))

            See(SeventhNearestEnemyOfType([0.0.0.RANGER]))

            See(EighthNearestEnemyOfType([0.0.0.RANGER]))

            See(NinthNearestEnemyOfType([0.0.0.RANGER]))

            See(TenthNearestEnemyOfType([0.0.0.RANGER]))

THEN

      RESPONSE #100

            EquipRanged()

            AttackOneRound(LastSeenBy())// I will attack the furthest Ranger that I can see

END

 

Event Triggers

Event Triggers only last until the next AI cycle.  At that point they are examined and processed.  At the end of the AI cycle all of the triggers are removed from the pending list. Triggers return True or False. The IDS file used is Trigger.ids, which you can find in the ScriptCompiler directory.

 

AttackedBy(Object,AttackStyle Y)

Returns: Boolean(True or False)

I was just attacked by an Object using this AttackStyle. The IDS file used is Astyles.ids, which you can find in the ScriptCompiler directory. The only useful style is DEFAULT. Note. I have not found that MELEE and RANGED works. I doubt that they are implemented. 

 

IF

      AttackedBy([ANYTHING],DEFAULT)

      See(LastAttackerOf())

      InParty(LastSeenBy())

THEN

      RESPONSE #100

      RunAwayFrom(LastSeenBy(),60)

END

 

This module will make me run away for 60 ticks if I am attack by a member of my party (Dominated? Dire charmed?)

 

Heard(Object, Integer: X)

Returns: Boolean (True or False).

I heard an Object shout integer X. Note. Any integer can be used. Bioware uses integers below 200 in the game.

 

IF

      Heard([PC],1)                 // true if anyone in my party sent Heard trigger #1

      !Range(LastHeardBy(),12)

THEN

      RESPONSE #100

            MoveToObject(LastHeardBy())

END

 

Help(Object)

Returns: Boolean (True or False).

Description: a call for help is heard from an object

 

IF

      Help([PC])                    // true if someone in my party has sent a Help trigger

      See(LastHelp())               // true if I can see the last caller that sent a Help trigger

      HaveSpell(CLERIC_CURE_HEAL)

      HPPercentLT(LastSeenBy(),60)

THEN

      RESPONSE #100

            Spell(LastSeenBy(),CLERIC_HEAL)

END

 

HitBy(Object, DamageStyle)

Returns: Boolean (True or False).

Description: An Object using a certain Damage Style has just hit me.

The IDS file used is damages.ids, which you can find in the ScriptCompiler directory.

 

ACID        COLD        CRUSHING    ELECTRICITY FIRE

PIERCING    MAGICCOLD   POISON      MISSILE

MAGICFIRE   SLASHING    MAGIC       STUNNING          

 

 

IF

      HitBy([ANYTHING],POISON)      // true if I was hit by something that caused a poison damage

      HasItem("POTN20",Myself)      //Antidote

THEN

      RESPONSE #100

      DisplayString(Myself,7029)    //Antidote

      UseItem("POTN20",Myself)      //Use antidote

END

 

HotKey(HotKey X)

Returns: Boolean(True or False)

Description: key X has just been pressed when I was the selected creature

The IDS file used is Hotkey.ids, which you can find in the ScriptCompiler directory. Currently hotkeys are restricted to letters A through Z.

 

IF

      ActionListEmpty() // true if, I have no actions on my queue

      HotKey(S)// true if, key S is pressed

THEN

      RESPONSE #100

            VerbalConstant(Myself(),LEADER)// the selected player will say something

            GlobalShout(1)// Send all in the area Heard trigger #1

            Wait(6)// seconds

END

 

Status Triggers

Status triggers are checked every time through the AI cycle.  As a result they always apply if they are true.

 

ActionListEmpty()

Id. 0x402B. Used in BG1, BG2 and IWD

Returns: Boolean (True or False)

Description: I have no current actions to complete.

ActionListEmpty() should only be used for things that are low priority. Do also keep in mind that when a character is given an action it is put on a queue. All actions will be run, eventually. If I am performing an action and get a call to do the same one again, the action will not be added until the first action has been completed. Actions are completed at different times: Spell is completed as soon as the casting animation is done, MoveToObject is completed once the character reaches the target or when it gives up.

 

AreaType(Type)

Returns: Boolean (True or False)

This command checks to see if the current area is of  a certain Type. The IDS file used is AreaType.ids, which you can find in the ScriptCompiler directory

 

IF

      AreaType(CITY)

THEN

      RESPONSE #100

            NoAction()

END

 

CheckStat(AnyObject, X, Stat)

Returns: Boolean (True or False)

True if the Object’s Statics is equal with X. (=X)

The IDS file used is Stats.ids, which you can find in the ScriptCompiler directory. Note. Be warned Bioware has not implemented many of the constants in the Stats file. I have used following:

 

CheckStat(Myself,0,MINORGLOBE)

 

CheckStatGT(AnyObject, X, Stat)

True if the Object’s Statics is greater than X. (>X)

 

CheckStatGT(LastSeenBy(Myself),40,RESISTMAGIC)

CheckStatGT(Myself,1,ARMORCLASS)

CheckStatGT(LastSeenBy(Myself),8,SAVEVSSPELL)

CheckStatGT(LastSeenBy(Myself),1,NUMBEROFATTACKS)

CheckStatGT(LastSeenBy(Myself),9,SAVEVSPOLY)

 

CheckStatLT(AnyObject, X, Stat)

True if the Object’s Statics is less than X. (<X)

 

CheckStatLT(Myself,1,STONESKINS)

CheckStatLT(LastSeenBy(Myself),31,RESISTCOLD)

CheckStatLT(LastSeenBy(Myself),31,RESISTFIRE)

CheckStatLT(LastSeenBy(Myself),31,RESISTACID)

CheckStatLT(LastSeenBy(Myself),50,RESISTELECTRICITY)

CheckStatLT(LastSeenBy(Myself),8,THAC0)

CheckStatLT(LastSeenBy(Myself),9,SAVEVSDEATH)

CheckStatLT(LastSeenBy(Myself),9,SAVEVSWANDS)

CheckStatLT(LastSeenBy(Myself),9,SAVEVSPOLY)

CheckStatLT(LastSeenBy(Myself),9,SAVEVSBREATH)

CheckStatLT(LastSeenBy(Myself),5,LEVEL)

CheckStatLT(LastSeenBy(Myself),31,RESISTMISSILE)

CheckStatLT(LastSeenBy(Myself),31,RESISTPIERCING)

 

Class(AnyObject, Class)

Returns: Boolean (True or False)

The Object is of the Class specified. The IDS file used is Class.ids, which you can find in the ScriptCompiler directory.

 

// ** Check if I have chosen a valid target

IF

      Exists([ENEMY])

      OR(2)

            InParty(LastSeenBy())

            Class(LastSeenBy(Myself),INNOCENT)

THEN

      RESPONSE #100

            ClearActions()    //sorry wrong target restart

END

 

CombatCounter(Number)

Returns: Boolean (True or False)

The combat counter is set to 150 every time an attack is made and decreases from there.  A value of 0 means that there is definitely no combat.

 

// ** FINDTRAPS

IF

      ActionListEmpty()

      !Exists([ENEMY])

      CombatCounter(0)              // true if  the combat counter = 0

      !ModalState(DETECTTRAPS)

      StateCheck(Myself,STATE_INVISIBLE)

THEN

      RESPONSE #100

      FindTraps()

END

 

CombatCounterGT(Number)

Note. I am not sure that this trigger works

 

CombatCounterLT(Number)

Note. I am not sure that this trigger works

 

Delay(Number)

Returns: Boolean (True or False)

Description: returns False except every X seconds

This command is used to delay the actions of a starting condition so they do not happen every time the script is checked. The delay value is in seconds.

 

// ** Hide the Rogue

IF

      Delay(20)

      !ModalState(DETECTTRAPS)

      !ModalState(STEALTH)

      !StateCheck(Myself,STATE_INVISIBLE)

      !StateCheck(Myself,STATE_IMPROVEDINVISIBILITY)

THEN

      RESPONSE #100

      Hide()

END

 

Detect(ObjectType)

Returns: Boolean (True or False)

The Object may not be visible but is within sight range.

 

IF

      Detect([ENEMY])

      HaveSpell(WIZARD_DETECT_INVISIBILITY)

      !See(NearestEnemyOf(Myself))

      GlobalTimerExpired("GR_UsedDetectInvisibility","LOCALS")

THEN

      RESPONSE #100

            SetGlobalTimer("GR_UsedDetectInvisibility","LOCALS",240)

            SpellNoDec(Myself,WIZARD_DETECT_INVISIBILITY)

END

 

Exists(AnyObject)

Returns: Boolean (True or False)

Description: Is X a valid ObjectType?

Note. Exists is excellent to use in conjunction with LastSeenBy because it will not change the target chosen by an earlier See(AnyObject) condition in a targeting module.

 

// ** Check if I can use an area spell such as, fireball, stinking cloud, etc

IF

      Exists([ENEMY])

      NumCreatureGT([ENEMY],2)

      !Range([PC],18)               // check range to nearest PC

      !Range(LastSeenBy(),18)       // check that target is not to close

THEN

      RESPONSE #100

            SetGlobal("GR_CanUseAreaSpell","LOCALS",1)      //Yes

            Continue()

END

 

Global(Name, Area, Value)

Returns: Boolean (True or False)

Check the status of a global variable. Is ‘Name = Value’

 

GlobalGT(Name, Area, Value)

Returns: Boolean (True or False)

Check the status of a global variable. Is ‘Name > Value’. The Name must be in quotes and can be 32 chars long. The Area must also be in quotes. I can only use LOCALS, which can be accessed by Myself. The default value is 0.

 

IF

      Exists([ENEMY])

GlobalGT("GR_InitilaizeTimers","LOCALS",0)      //true if variable GR_InitialiseTimersLOCALS>0

THEN

      RESPONSE #100

            SetGlobal("GR_InitilaizeTimers","LOCALS",0)     //set variable GR_InitialiseTimersLOCALS=0

            Continue()

END

 

GlobalLT(Name, Area, Value)

Returns: Boolean (True or False)

Check the status of a global variable. Is ‘Name < Value’

 

GlobalTimerExpired(Name, Area)

Returns: Boolean (True or False)

Checks the status of a global timer. Is ‘Name = 0’. This module will cast the spell with 24 seconds intervals

 

IF

      Exists([ENEMY])

      HaveSpell(WIZARD_MANTLE)                        // this spell will last for 4 rounds

      HPPercentLT(Myself,40)                          //If my hitpoints are less than 40

      GlobalTimerExpired("GR_UsedMantle","LOCALS")    // true if the timer is 0

THEN

      RESPONSE #100

            SetGlobalTimer("GR_UsedMantle","LOCALS",24)// 4 rounds is 24 seconds

            Spell(Myself,WIZARD_MANTLE)

END

 

Note. A Globaltimer must be initialized before you can use it. One way was to use !GlobalTimerNotExpired(“Name”,”LOCALS”) but I found that this method is not reliable. The safest way is to use an initializing module as the first module in a script.

 

// ** all new combat timers should be added to the this list.

// ** timers are reset to zero once after a combat.

IF

      !Exists([ENEMY])

      Global("GR_InitilaizeTimers","LOCALS",0)// must be done prior to use

THEN

      RESPONSE #100

      SetGlobalTimer("GR_ UsedMantle ","LOCALS",0)// fixed value

      .

      .

      SetGlobal("GR_InitilaizeTimers","LOCALS",1)

END

.

// some other modules

.

// this is the first module in the combat target analyze section

 IF

      Exists([ENEMY])

      GlobalGT("GR_InitializeTimers","LOCALS",0)// true if variable GR_InitilaizeTimersLOCALS>0

THEN

      RESPONSE #100

            SetGlobal("GR_InitializeTimers","LOCALS",0)// set variable GR_InitilaizeTimersLOCALS=0

            Continue()

END

 

Note. It is important to reset all combat timers to 0 when combat is over. All globals and values are saved in the savegame, timers will be saved in their current state.

 

GlobalTimerNotExpired(Name, Area)

Returns: Boolean (True or False)

Checks the status of a global timer. Is ‘Name > 0’. Note. I have not used this

 

HaveSpell(Spell)

Returns: Boolean (True or False)

This command check to see if I have the specified Spell memorized. The IDS file used is Spell.ids

 

IF

      Exists([ENEMY])

      HaveSpell(WIZARD_DEATH_SPELL)

      See([EVILCUTOFF.0.0.0.0.SUMMONED])

      !Range(LastSeenBy(Myself),12)

THEN

      RESPONSE #100

            Spell(LastSeenBy(Myself),WIZARD_DEATH_SPELL)

END

 

HaveAnySpells()

Returns: Boolean (True or False)

This command check to see if I have any spells memorized. Note. I have not used this, but I think it works.

 

HasItem(Item, AnyObject)

Returns: Boolean (True or False)

This command check to see if the Object has the Item specified.

 

IF

      HasItem("POTN08",Myself)

      HPPercentLT(Myself,50)

THEN

      RESPONSE #100

            DisplayString(Myself,6990)//Healing

            ActionOverride(Myself,UseItem("POTN08",Myself))

END

 

HasItemEquiped(Item, Object)

Returns: Boolean (True or False)

This command check to see if the Object has the specified Item equipped.

 

IF

      See([ENEMY])

      HasItemEquiped("WAND10",Myself)//Wand of Monster Summoning

      Range(LastSeenBy(),15)

      NumCreatureGT([ENEMY],2)

      NumCreatureLT([GOODCUTOFF.0.0.0.0.SUMMONED],2)

      HPGT(LastSeenBy(Myself),30)

THEN

      RESPONSE #100

            DisplayString(Myself,7272)//Wand of Monster Summoning

            UseItem("WAND10",LastSeenBy())

END

 

HasWeaponEquiped(Object)

Returns: Boolean (True or False)

This command check to see if the Object has a weapon equipped. Note. I have not used this, but this module works.

 

IF

      HasWeaponEquiped("Nalia")

THEN

      RESPONSE #100

            DisplayStringHead(Myself,9102)//Nalia

END

 

InParty(Object)

Returns: Boolean (True or False)

This command checks to see if the Object is in the party.

 

IF

      Exists([ENEMY])

      OR(2)

            InParty(LastSeenBy())

            Class(LastSeenBy(Myself),INNOCENT)

THEN

      RESPONSE #100

            ClearActions()    //sorry wrong target clear my actions and restart the script

END

 

InWeaponRange(Object)

Returns: Boolean (True or False)

This command checks to see if the Object is within my current weapon’s range.

 

// ** Check if target is a Troll and almost dead

IF

      Exists([ENEMY])               // true if there are any enemies with visual range

      See(NearestEnemyOf(Myself))   //true if I can see the nearest enemy of myself            

THEN

      RESPONSE #100

            EquipRanged()

            Continue()              // with next module

END

 

IF

      Exists([ENEMY])

      Race(LastSeenBy,TROLL)

      !Range(LastSeenBy(),6)        // true if my distance to the target is >6 ssq.

      InWeaponRange(LastSeenBy())   // true if I have a ranged weapon equipped

      HPLT(LastSeenBy(),10)         // true if target has < 10 hit points

THEN

      RESPONSE #100

      SelectWeaponAbility(SLOT_AMMO2,0) // ** right ammo slot for special ammo

      AttackOneRound(LastSeenBy())  // attack target for 6 sec.

END

 

ModalState(I:State*MODAL)

Returns: Boolean (True or False)

The IDS file used is Modal.ids, which you can find in the ScriptCompiler directory

 

// ** Hide the Rogue

IF

      Delay(20)

      !ModalState(DETECTTRAPS)

      !ModalState(STEALTH)

      !StateCheck(Myself,STATE_INVISIBLE)

      !StateCheck(Myself,STATE_IMPROVEDINVISIBILITY)

THEN

      RESPONSE #100

      Hide()

END

 

Name(Name, Object)

Checks the Object’s scriptname.

 

IF

      Name("Nalia",Myself)    // true if  I am Nalia

THEN

      RESPONSE #100

            DisplayStringHead(Myself,9102)//Nalia

            Continue()

END

 

NumCreature(ObjectType, Number)

Returns: Boolean (True or False)

Description: Is the number of ObjectType visible Equal to Number

 

NumCreatureLT(Object, Number)

Returns: Boolean (True or False)

Description: Is the number of ObjectType visible less than Number

 

IF

      See([ENEMY])

      HasItemEquiped("WAND10",Myself)//Wand of Monster Summoning

      Range(LastSeenBy(),15)

      NumCreatureGT([ENEMY],2)

      NumCreatureLT([GOODCUTOFF.0.0.0.0.SUMMONED],2)

      HPGT(LastSeenBy(Myself),30)

THEN

      RESPONSE #100

            DisplayString(Myself,7272)//Wand of Monster Summoning

            UseItem("WAND10",LastSeenBy())

END

 

NumCreatureGT(Object, Number)

Returns: Boolean (True or False)

Description: Is the number of ObjectType visible greater than Number

 

OR(Lines)

Returns: Boolean (True or False)

This command is used to OR a number of lines together. Note: if the lines are not constrained by an OR() group then they are ANDed together.

 

IF

      OR(2)

            Name("Imoen",Myself)

            Name("Imoen2",Myself)

THEN

      RESPONSE #100

            DisplayStringHead(Myself,9102)//Nalia

            Continue()

END

 

OutOfAmmo()

Returns: Boolean (True or False)

My weapon is out of ammo. Note. I have never found that this works. A work around is

 

IF

      Exists([ENEMY])

      !Range(LastSeenBy(),4)

THEN

      RESPONSE #100

            EquipRanged()

            Continue()

END

 

IF

      Exists([ENEMY])

      !Range(LastSeenBy(),4)

      InWeaponRange(LastSeenBy())   // is False, if EquipRanged() failed

THEN

      RESPONSE #100

            AttackOneRound(LastSeenBy())

END

 

Range(AnyObject, X)

Returns: Boolean (True or False)

The Object is within X search squares. You can see max 25 ssq.

 

Range(NearestEnemyOf (),15)   // true if distance to my nearest enemy is less than 15 ssq.

!Range(NearestEnemyOf (),15)  // true if distance to my nearest enemy is more than 15 ssq. 

 

StateCheck(Object, State)

The Object is in the specified State.

The IDS file used is State.ids, which you can find in the ScriptCompiler directory.

 

See(AnyObject)

Returns: Boolean (True or False)

The Object is visible to me.

 

True()

Description: Always returns True.

 

 

Actions:

 

NoAction()

This command does nothing.

 

ActionOverride(Object, Action)

This command passes the specified Action to the Object.  This is used to have one creature tell another to do something.  Eg.

 

ActionOverride(“Nalia”, Wait(1))

 

Attack(Object)

This command is used to attack an Object until unusable.

 

AttackReevaluate(Object, Period)

This command is used to attack an Object for a Period of time.

 

AttackOneRound(Object)

This command is used to attack an Object for one round.

 

Continue()

Continue through the script without leaving. This is very useful to keep a number of modules together.

 

ClearActions(Object)

This command will clear the actions of the Object.

 

IF

      See([ENEMY])

      OR(2)

            InParty(LastSeenBy())

            Class(LastSeenBy(Myself),INNOCENT)

THEN

      RESPONSE #100

            ClearActions()    //sorry wrong target clear my actions and restart the script

END

 

DisplayString(Object, StrRef)

StrRef is an integer pointer to a certain string in the games dialog library. I can not create new strings. This command displays the String in the dialog window originating from the Object.

 

Additional Notes on Adding Strings

It is possible to add new strings to dialog.tlk using a program such as WeiDU or Near Infinity. You would only really do this if you were scripting a new mod and then you would do it using the WeiDU .tra and .tp2 functions.

 

IF

      Name("Nalia",Myself)    // true if  I am Nalia

THEN

      RESPONSE #100

            DisplayString(Myself,9102)//Nalia

            Continue()

END

 

In the dialog window you will read Nalia – Nalia

 

DisplayStringHead(Object,StrRef)

Displays the specified string over the object’s head

 

IF

      Name("Nalia",Myself)    // true if  I am Nalia

THEN

      RESPONSE #100

            DisplayStringHead(Myself,9102)//Nalia

            Continue()

END

 

This will display the word Nalia above her head

 

EquipItem(Item)

Equip the specified Item.

 

FaceObject(Object)

Face the specified Object.

 

GiveItem(Item, Object)

Give the Item, must be in the inventory, to the Object.

 

Hide()

Try to hide in shadows.

 

RunAwayFrom(Object, Time)

Run away from the Object for a specified Time.  The time is in 15ths of a second.

 

IncrementGlobal(Name, Area, Value)

Increments the Global by the Value.

 

MoveToObject(Object)

Move to the Object specified.  Will not move to an unseen Object unless you are using the scriptname or Player1 – Player6. The MoveToObject action generally must finish before a new action can be run. I do not know of a way to make a party member stop before the action is complete.

 

MoveToObjectFollow(Object)

Move to the Object specified and follow until another condition becomes True.

 

IF

      ActionListEmpty()

      Heard([PC],1001)

      !Range(LastHeardBy(),12)

THEN

      RESPONSE #100

            SetInterrupt(FALSE)

            MoveToObjectFollow(LastHeardBy()) // Myself will walk to the caller without any delay

            SetInterrupt(TRUE)           

END

 

Note. The party members will stop a couple of ssq. behind the caller. 

 

MoveToObjectNoInterrupt(Object)

Move to the Object specified until completed.

 

IF

      Heard([PC],1001)

THEN

      RESPONSE #100

            MoveToObjectNoInterrupt(LastHeardBy())

END

 

Note. This is will make Myself walk to, and bump into the caller.

 

EquipRanged()

This command will equip a ranged weapon carried by the player e.g. bow, sling, crossbow.

 

EquipMostDamagingMelee()

This command will equip the most powerful melee weapon in my inventory.

 

ForceSpell(Object, Spell)

Cast the Spell at the specified Object with no interruptions.

 

Gender(Object, Gender)

The Object is of the Gender specified.

The IDS file used is Gender.ids, which you can find in the ScriptCompiler directory.

 

MALE

FEMALE

SUMMONED

ILLUSIONARY 

EXTRA

SUMMONED_DEMON

Etc.

 

General(Object, General)

The Object is of the General State specified.

The IDS file used is General.ids, which you can find in the ScriptCompiler directory.

 

GlobalShout(Number)

Description: Shout out a Heard trigger Number to anyone within the area.

Eg GlobalShout(136842) will set the event trigger Heard([PC], 136842) to true. You can use any number.

 

IF

      ActionListEmpty()                         // true if, I have no actions on my queue

      HotKey(S)                                 // true if, key S is pressed

THEN

      RESPONSE #100

            VerbalConstant(Myself(),LEADER)     // the selected player will say something

            GlobalShout(1)                      // Send all in the area Heard trigger #1

            Wait(6)                             // seconds

END

 

HasBounceEffects(O:Object*)

Object has magical shields up that will bounce any attacks back to the attacker e.g. Physical Mirror

 

HasImmunityEffects(O:Object*)

Object has magical immunity to any attacks e.g. Protection from Normal Weapons

 

HP(Object, X)

Object has X number of hitpoints

 

HPGT(Object, X)

Object has a greater number of hitpoints than X

 

HPLT(Object, X)

Object has a lesser number of hitpoints than X

 

HPPercent(Object, X)

Object has X percentage of hitpoints

 

HPPercentLT(Object, X)

Object has a lesser percentage of hitpoints than X

 

HPPercentGT(Object, X)

Object has a greater percentage of hitpoints than X

 

Kit(Object, Kit)

The Object is of the Kit specified.

The IDS file used is Kit.ids, which you can find in the ScriptCompiler directory.

 

Race(Object, Race)

The Object is of the Race specified.

The IDS file used is Race.ids, which you can find in the ScriptCompiler directory.

 

RunAwayFromNoInterrupt(Object, Time)

Run away from the Object for a specified Time without being interrupted.

 

SetGlobal(Name, Area, Value)

The Name must be in quotes and can be 32 chars long. The Area must also be in quotes. I can only use LOCALS, which can be accessed by Myself.

 

IF

      Exists([ENEMY])

      GlobalGT("GR_InitilaizeTimers","LOCALS",0)      // true if variable GR_InitilaizeTimersLOCALS>0

THEN

      RESPONSE #100

            SetGlobal("GR_InitilaizeTimers","LOCALS",0)// set variable GR_InitilaizeTimersLOCALS=0

            Continue()

END

 

SetGlobalTimer(“Name”,”Area”,Time)

The Name must be in quotes and can be 32 chars long. The Area must be “LOCALS” and must also be in quotes. Time is a value in seconds. I only use this for spells that last a certain time. The time is given in rounds or turns and 1 round is 6 seconds, 1turn is ten rounds or 60 seconds.

 

IF

      Exists([ENEMY])

      HaveSpell(WIZARD_MANTLE)      // this spell will last for 4 rounds

      HPPercentLT(Myself,40)

      GlobalTimerExpired("GR_UsedMantle","LOCALS")

THEN

      RESPONSE #100

            SetGlobalTimer("GR_UsedMantle","LOCALS",24)// 4 rounds is 24 seconds

            Spell(Myself,WIZARD_MANTLE)

END

 

SetInterrupt(Boolean)

Description: Turn interrupt on or off.

Set the interruption of current actions to True/False.

 

IF

      ActionListEmpty()

      Heard([PC],1)

      !Range(LastHeardBy(),12)

      Delay(3)

THEN

      RESPONSE #100

            SetInterrupt(FALSE)

            MoveToObject(LastHeardBy())

            SetInterrupt(TRUE)

END

 

SelectWeaponAbility(I:WeaponNum*Slots,I:AbilityNum*)

SelectWeaponAbility(I:WeaponNum*Slots,I:AbilityNum*) was used for selecting a quick weapon or ammo slot. It was used before EquipRanged and EquipMostDamagingMelee. The AbilityNum is only used for weapon abilities such as Melee or Thrown like for a throwing axe.

 

Shout(Number)

Description: Shout out a Heard trigger Number to anyone within sight range.

Eg Shout(136842) will set the event trigger Heard([PC], 136842) to true. You can use any integer.

 

Spell(Object, Spell)

Cast the Spell at the specified Object.

 

SpellNoDec(Object, Spell)

Cast the Spell at the specified Object and keep it memorized.

 

VerbalConstant(O:Object*,I:Constant*soundOff)

The IDS file used is soundOff.ids, which you can find in the ScriptCompiler directory.

 

IF

      ActionListEmpty()                        // true if, I have no actions on my queue

      HotKey(S)                                 // true if, key S is pressed

THEN

      RESPONSE #100

            VerbalConstant(Myself(),LEADER)     // the selected player will say something

            GlobalShout(1)                      // Send all in the area Heard trigger #1

            Wait(6)                             // seconds

END

 

Wait(Time)

Wait for the specified Time.  The time is in seconds.

 

 

Horred’s Findings

 

Note: I am unsure from the posting whether these apply specifically to IWDII or to BG2. With this in mind, use with caution.

 

OBJECT.IDS

 

0 Nothing                     ---parses into [ANYONE]

1 Myself                      ---works!

2 LeaderOf                    ---Not Tested

3 GroupOf                     ---hopelessly screwed

4 WeakestOf                   ---only detects PC's

5 StrongestOf                 ---only detects PC's

6 MostDamagedOf               ---only detects PC's

7 LeastDamagedOf             ---only detects PC's

8 ProtectedBy                 ---Not tested; should work in conjuction with   the working action

Protect(O:Object*,I:Range*)

9 ProtectorOf                 ---see above

10 LastAttackerOf             ---works!

11 LastTargetedBy             ---hopelessly screwed

12 NearestEnemyOf             ---works!

13 LastCommandedBy            ---not tested

14 Nearest                    ---cannot be "nested"

e.g., Nearest([EVILCUTOFF.0.0.MAGE]) will not work

15 LastHitter                 ---works!

16 LastHelp                   ---not tested

17 LastTrigger                ---works for traps, and detects "Last Caster of a Mage Spell"

18 LastSeenBy                 ---works!

19 LastTalkedToBy             ---works!

20 LastHeardBy                ---works!

21-26 Player(X)               ---works!

27 Protagonist                ---seems to work...

28 StrongestOfMale            ---not tested, probably detects PC's only

29-37 (X)NearestEnemyOf       ---works!

38-46 (X)Nearest              ---see Nearest (above)

47 WorstAC                    ---only detects PC's

48 BestAC                     ---only detects PC's

49 LastSummonerOf             ---works; simulacrums, etc. aren't "summoned"

50-59 (X)NearestEnemyOfType   ---works!

60-69 (X)NearestMyGroupOfType ---works!

70-75 Player(X)Fill           ---not tested

76-85 (X)NearestDoor          ---hopelessly screwed

 

 

TRIGGER.IDS

 

0x40B4 InMyGroup(O:Object*)   ---hopelessly screwed

 

 

Another thing I discovered: you can't use [GOODCUTOFF], etc for state checks. It has to be an actual object!

 

WRONG:        StateCheck([GOODCUTOFF],STATE_MIRRORIMAGE)

 

RIGHT:            See([GOODCUTOFF])

StateCheck(LastSeenBy(Myself),STATE_MIRRORIMAGE)

 

Also, [GOODCUTOFF], NearestEnemyOf(Myself), etc.

---i.e., anything that implies a proximity check is sight-dependent!!!

 

You'll never get a valid (true) answer from, say:

 

StateCheck(SecondNearestEnemyOf(Myself),STATE_INVISIBLE)

 

because if the second nearest enemy is invisible, they are automatically disqualified for a Nearest, SecondNearest, etc, check. Now, a check like:

 

StateCheck(LastAttackerOf(Myself),STATE_INVISIBLE)

 

should work fine, because you don't need to see you last attacker to know who he is. Same holds true for things like Player1, LastHitter, and so on.

 

 

Links:

 

SimDing0's Complete Scripting Guide

Igi's Game Time vs Real Time

IESDP (Look under Scripting)