Jump to content


Photo

Goran Rimén's Scripting Guide


  • Please log in to reply
1 reply to this topic

#1 Yovaneth

Yovaneth

    The newly-appointed Master Builder of Baldur's Gate

  • Modder
  • 3058 posts

Posted 18 September 2008 - 08:27 AM

Version History

Original Release: Goran Rimen 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.



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.

Lastly, check the IESDP over at gibberlings3.net. This contains the most up-to-date information on the ids files.


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 maybe 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 e.g. 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.

E.g.

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 the 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.

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 Case 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 e.g.

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

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 there are not ten enemies then See(TenthNearestEnemyOf()) will default to 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 to 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. It is broken in that it does not check for magical powers, only for dice roll.

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.

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 e.g. 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.

E.g 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.



#2 horred the plague

horred the plague

    Scourge of the Seven Seas

  • Modder
  • 1899 posts

Posted 30 January 2010 - 02:19 PM

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



I wrote this specifically for BG2, and tested each claim in-game. And thank you VERY much for preserving this little gem.

--Horred