|VR| MADMANs Scripting Site
Po's Scripting Guide

Home

A Good Start
Po's Scripting Guide
An fairly quick reference
Defence
Offence
Miscellaneous
Links
DownLoads
Scripting Forum

OK from the start.
 
Ok so This is Po's scripting guide its the best one iv seen so i must say thanks to him

Using Your Script In-Game

 

This is very simple. If you have compiled your script correctly and saved it into the Scripts folder, the script will be available to use from the Character Information Screen. It will appear beneath the game's scripts saying CUSTOM. Simply select it and be on your way.

IF - THEN Logic

IF - THEN logic is the system used to determine why, when, and how things happen.

It is used to say "IF this happens, THEN do this." The format used to do this for the Infinity Engine looks like this:

 

IF                                     //if

    ThisHappens()              //something specific happens     (Trigger)

THEN                              //then

  RESPONSE#100          // the chance that this will happen. In this case it is 100%.

    DoThis()                      // Do something specific            (Action)

END                               // End this IF - THEN statement

 

RESPONSES

RESPONSE#yyy, where yyy is the number, is like a weight system. It sets the percent chance that the action will take place. Hence-- the actions under RESPONSE#100 would occur every time a specific circumstance is true. The actions under RESPONSE#50 would occur 50% of the time.

 

If you have an IF - THEN statement like....

 

IF

    ThisHappens()

THEN

  RESPONSE#100

    DoThis()

  RESPONSE#100

    DoSomethingElse()

  RESPONSE#100

    DoAnotherThing()

END

 

... then there would be a 33% chance that DoThis() would happen, a 33% chance that DoSomethingElse() would happen, and a 33% chance that DoAnotherThing() would happen. Only ONE of the three responses would occur. Which one will happen is chosen randomly. Get it?

 

THEN -- when is it used?

THEN is the dividing point of an IF - THEN statement. It marks where the triggers in the        IF - THEN statement have ended, and where the actions begin. THEN is used only once in an IF -THEN statement.

 

 

Triggers, Actions, and Everything In Between

 

All possible triggers can be found in the trigger.IDS. All possible actions can be found in the action.IDS. Targets (called Objects) can be found in the object.IDS. Everything else that is related to scripting (EA.IDS, RACE.IDS, SPECIFICS.IDS, etc.) can be found in the other IDS files.

 

Triggers are what check to see if something is true. They always go under IF. So, if you wanted to check to see if your character can see Player 1, you would use a trigger like...

 

IF

    See(Player1)

 

Actions are what occurs if a Trigger is true. Actions always go under RESPONSE#yyy. So, if you wanted to attack Player 1, you would use an action like...

 

  RESPONSE#100

    Attack(Player1)

 

Now, let's say you want to check if you can see Player1. If you CAN see him, then you will attack him. If you cannot see Player1, then the action will not occur. When that IF - THEN statement is put together, it looks like...

 

IF

    See(Player1)

THEN

  RESPONSE#100

    Attack(Player1)

END

 

Objects are the targets that are used in both the Triggers and Actions. In the above IF - THEN statement, Player1 is the object. Objects can be combined with each other (this is called Nesting). So, if you wanted to attack the nearest enemy of player1, you would use an action that looks like...

 

  RESPONSE#100

    Attack(NearestEnemyOf(Player1))

 

Note that the actual object looks like NearestEnemyOf(Player1) . The parentheses on either side separate the Object: NearestEnemyOf(Myself), from the Action: Attack. Each nested object is separated by an opening parentheses "(".

A good way to remember how many end parentheses to use is to count the number of different objects used in that action. NearestEnemyOf  + Player1 equals 2 objects. Hence there would be 2 parentheses at the end "))".

 

Nesting - Protector(LastAttackerOf(LastTargetedBy(ProtectorOf(Myself)))) is valid and will return the protector of the person last attacked by the person last targeted by the person protecting myself.  Any more then this depth of nesting is NOT allowed, however.

 

Using OR() - OR() is classified as a trigger, but it is really something else. OR() lets you check to see...

 

IF

    ThisIsTrue()

    or

    ThatIsTrue()

    or

    SomethingElseIsTrue()

 

However, that is NOT the format OR() is used in. OR() does a count of the triggers beneath it that will be OR()ed together. So, if you wanted to check if you can see player1, or player2, or player3, you would use...

 

IF

    OR(3)

    See(Player1)

    See(Player2)

    See(Player3)

 

There are 3 triggers that are OR()ed together, so 3 is in the "OR count."

 

 

Writing Your Script

 

Here's where the tutorial is. Brace yourself, this will be a VERY simple script for a fighter character.

It will make him attack an enemy if he sees one, drink a potion of healing if he is near-death, and make him equip certain weapons based on how close the enemy is.

  

Tutorial Time!

All right, first things first. We want our fighter to attack the nearest enemy with his ranged weapon if he is relatively far away from the enemy. So, you need a trigger to check the range...

 

IF

    !Range(NearestEnemyOf(Myself),7)        // An exclamation point in front of the Trigger

                                                                   // means "Not true." So, this trigger means: If

                                                                   // it is not true that the nearest enemy to myself 

                                                                   // is within 7 feet.

 

And we need two actions-- One that will equip the ranged weapon, and one that will attack the nearest enemy. Those actions are

 

  RESPONSE#100

    EquipRanged()                                       // Note that actions under the RESPONSE will

    Attack(NearestEnemyOf(Myself))          // always occur in the order they are presented.

                                                                 // If Attack() was before EquipRanged(), then

                                                                 // EquipRanged() would not take place until the

                                                                 // character finishes attacking the nearest enemy to

                                                                 // himself.

 

So, put the triggers and the actions together and you will have your first IF - THEN statement of the script...

 

IF

    See(NearestEnemyOf(Myself))

    !Range(NearestEnemyOf(Myself),7)

THEN

  RESPONSE#100

    EquipRanged()

    Attack(NearestEnemyOf(Myself))

END

 

Next, you need an IF - THEN statement to equip a melee weapon and attack if the enemy is closer than 7 feet. There are only 2 differences in this IF - THEN statement from the one above. 1- Range would not have an exclamation point because now you are checking to see if the enemy is within 7 feet. 2- EquipRanged() would be changed to

EquipMostDamagingMelee().

So, this IF - THEN statement would look like...

 

IF

    See(NearestEnemyOf(Myself))                     //I can see the Nearest Enemy of myself

    Range(NearestEnemyOf(Myself),7)              //The nearest enemy of myself is within 7 feet.

THEN                                                                    

  RESPONSE#100

    EquipMostDamagingMelee()                       // Equip my melee weapon

    Attack(NearestEnemyOf(Myself))               // Attack the enemy

END

 

 

Ok, now our fighter is going to drink a potion of extra healing (POTN52) if he has less than 25% of his hit points.

 

First, we want to check to see if he he has that specific potion. So we would need a trigger like...

 

IF

    HasItem("POTN52",Myself)

 

Then, we need another trigger to see if he has less than 25% of his hit points. That would look like...

 

IF

    HPPercentLT(Myself,25)

 

Last, we need an action that will use the potion if he has it and has less than 25% of his hit points. That action would look like...

  RESPONSE#100

    UseItem("POTN52",Myself)             //The potion will be used even if not in quickslot.

 

So, we combine these into an IF -THEN statement that says... IF I have the Potion of Extra Healing and I have less than 25% of my hit points, THEN there is a 100% chance that I will attempt to use the Potion of Extra Healing.

 

That statement would look like this...

 

IF

    HasItem("POTN52",Myself)

    HPPercentLt(Myself,25)

THEN

  RESPONSE#100

    UseItem("POTN52",Myself)

END

 

So, the final script for the fighter would look like this...

 

IF

    See(NearestEnemyOf(Myself))

    !Range(NearestEnemyOf(Myself),7)

THEN

  RESPONSE#100

    EquipRanged()

    Attack(NearestEnemyOf(Myself))

END

 

IF

    See(NearestEnemyOf(Myself))

    Range(NearestEnemyOf(Myself),7)

THEN

  RESPONSE#100

    EquipMostDamagingMelee()

    Attack(NearestEnemyOf(Myself))

END

 

IF

    HasItem("POTN52",Myself)

    HPPercentLT(Myself,25)

THEN

  RESPONSE#100

    UseItem("POTN52",Myself)

END

 

Get it? Got it? Good. Now go up to File, to Save As, and save this script as a .BAF file anywhere you want. The .BAF file is the source for the script, and you will be using this source to compile it.

I also recommend copying and pasting this into a .TXT file for safe keeping. Depending on what triggers and actions are used, .BAF files can get a little messed up when reopening them.

 

 

Compiling

Compiling should be a simple process once your script's source is done.

Steps:

1. Make sure that the Path to the Compiler is set to the Script Compiler folder. The default location for this is C:/ProgramFiles/BlackIsle/BGII - SoA

2. Go to File, then Compile To... Save this script with 8 characters or less as a .bs file in the Scripts folder.

3. If everything goes well, you should get a message saying "AI Script Compiled and Ready to Go!" If there are mistakes in syntax, if something is out of place, or any trigger or action is spelled wrong or does not exist, you will get a Compiler Error and it will tell you what you have done wrong. Be sure to fix it, and save as a .baf file again before compiling.

 

Setting Global / Local Variabes and Timers

 

First off, you have to know that global variables set flags. These flags are like invisible markers that are set when an event occurs. There are 3 different types of variables-- GLOBAL, LOCALS, and ARyyyy.

 

Global / Local Triggers

0x400F Global(S:Name*,S:Area*,I:Value*)

0x4034 GlobalGT(S:Name*,S:Area*,I:Value*)

0x4035 GlobalLT(S:Name*,S:Area*,I:Value*)

0x4098 GlobalsEqual(S:Name1*,S:Name2*)

0x4099 GlobalsGT(S:Name1*,S:Name2*)

0x409A GlobalsLT(S:Name1*,S:Name2*)

0x409B LocalsEqual(S:Name1*,S:Name2*)

0x409C LocalsGT(S:Name1*,S:Name2*)

0x409D LocalsLT(S:Name1*,S:Name2*)

Timers

0x40B5 RealGlobalTimerExact(S:Name*,S:Area*)

0x40B6 RealGlobalTimerExpired(S:Name*,S:Area*)

0x40B7 RealGlobalTimerNotExpired(S:Name*,S:Area*)

 

Global / Local Actions

30 SetGlobal(S:Name*,S:Area*,I:Value*)

255 AddGlobals(S:Name*,S:Name2*)

Timers

SetGlobalTimer(S:Name*,S:Area*,I:Value*)

RealSetGlobalTimer(S:Name*,S:Area*,I:Value*)

 

 

GLOBAL Variables

When Global Variables are set, they remain throughout the game. Global variables are marked on every area and apply to every character, creature, or whatever else there is. Global variables  have a format that look like this...

    Global("variable_name","GLOBAL",#)

 

The variable_name can be anything you want it to be (just don't make it too long). The # is the value of that Global Variable. Global variables, when used in IF statements, default to 0. So, if you had something like...

IF

    Global("gonna_do_something","GLOBAL",0)

 

...then the variable "gonna_do_something" would be considered true and would have a       value of 0. However, if you used an action like...

  RESPONSE#100

    SetGlobal("gonna_do_something","GLOBAL",1)

 

... then the variable "gonna_do_something" with a value of 0 would be considered False because now "gonna_do_something" has a value of 1.

 

Global variables are usually used to mark something that will only be done once in the game. Take the script "KELDHOME" for example.

It says that if the Global variables, "KeldornEstate" and "KeldornPassesHouse" have a

value of 0 and Keldorn is in the party, to start a dialog, then set the Global variable "KeldornPassesHouse" to 1.

 

IF

      IsOverMe("Keldorn")                  //disregard IsOverMe() for now.

      InParty("keldorn")

      Global("KeldornEstate","GLOBAL",0)

      Global("KeldornPassesHouse","GLOBAL",0)

THEN

      RESPONSE #100

            ActionOverride("Keldorn",StartDialogueNoSet(Player1))

            SetGlobal("KeldornPassesHouse","GLOBAL",1)

END

 

So basicly, what this script says is... If Keldorn is near his house for the first time, to start a dialog with you. That dialog will never happen again unless the Global variable "KeldornPassesHouse" is set back to 0. You should understand how this works by now.

 

 

LOCAL Variables

 

When Local variables are set, they remain on the creature who set them. This way there can be many of the same variables running at the same time, but not affecting each other. Local variables have a format that looks like this...

IF

    Global("gotta_do_something","LOCALS",#)

 

The only differences between Local and Global variables is that Local variables can be run simultaneously not affecting one another, and Local variables are only useful if they are set and triggered by a specific creature.

 

AREA Variables

 

Area variables are set to a specifc Area's filename. A variable set as "AREA000" would only be valid in "AREA000." Area variables have a format that looks like this...

IF

    Global("will_do_something","ARyyyy",#)

 

 

What Those Damned Letters in the IDS Files Mean

 

If you've looked in the IDS files, you've seen those letters that have colons and stars next to them. If you're like me, you ask yourself.... "What the hell are those for?"

 

O:        Stands for Object. Can be anything from the Object.ids or resrefs in quotes.

P:         Stands for Point. It is always in the [x.y] format.

S:            Indicates a resref or name of something.

I:            Indicates something drawn from an IDS file. It could also be the ID number, timer,

                or value.

 

 

Writing a Complex Script

 

Here's where the actual tutorial is... Our goal for this tutorial will be to make a script for a mage/cleric that will cast offensive and defensive spells depending on varying conditions.

 

Note that this Script is just an overview. Any scripts that you make should be customized to your party. On that note, all of the NPC's in the game can be targeted as an object by just using their name in quotes. For example, Minsc would be "Minsc".

 

Open up the file tutorial.txt included with this document. If you've read and understand "Po's Scripting Guide," you should have a general idea of what this script does.

 

 

The first section, labeled TROLL portion shows priority for casting certain spells when a troll is present.

It says...

 

1. If I see a troll, he has less than 20 HP, and I have a Flame Arrow spell, cast Flame Arrow at the troll.

 

2. If those same conditions are true, and I don't have Flame Arrow, but DO have Agannazer's Scorcher, casts Agannazer's Scorcher on him.

 

3. Now, If I don't have either of those spells, but I do have a Burning Hands spell, move to the troll so I can cast Burning Hands on him. If I am less than 8 feet from the troll (Burning Hands Range), then cast Burning Hands on him.

 

Notice that every IF-THEN Statement uses ActionListEmpty(). This is used so that the script will not interrupt any manual control you wish to have over the character. ActionListEmpty() says... "IF I am not performing any actions."

 

The targeting I used in this should look like [ENEMY.0.TROLL]. To see why I did this, refer to the Scripting Quick Reference in the Script Compiler folder.

 

 

Next is the LEVEL DRAINERS portion. It basicly says....

 

1. If I see a member of the Vampire race (the level-drainers), a member of the party is a Fighter, Paladin, or Ranger, and I have a Negative Plain Protection spell...

 

2. Then cast Negative Plain Protection on a fighter, a paladin, and a ranger.

 

Parties generally consist of only one of these classes, so I use global variables to mark that I have casted Negative Plane Protection on each type of class.

 

Note that the targeting I used for this should be customized for your party. If you have someone who is usually the Vampire Attacker, then target him with this script.

 

The UNDEAD portion uses much the same method as the TROLL portion, but with False Dawn and Sunray.

 

The only new trigger I used in this script was...

NumCreatureVsPartyGT([ENEMY.UNDEAD],3).

 

I used this trigger because you would usually only want to use these spells on a group of undead.

 

Last is the DEFAULT portion. It simply says...

 

If I have ammo and the enemy is more than 8 feet away, attack him with my ranged weapon.

 

If I don't have ammo or the enemy is less than 8 feet away, attack him with my melee weapon.

 

AttackReevaluate is used to reevaluate the situation every 30 runs of the script, or 2 seconds. If this action does not work on your machine, replace it with AttackOneRound().