Author Topic: Max's Scripting Tutorial  (Read 19376 times)

0 Members and 1 Guest are viewing this topic.

Offline Solaufein

  • Lord of the Realms
  • Administrator
  • Level 5
  • *****
  • Posts: 5149
  • Karma: +127/-19
  • Gender: Male
  • The night is dark and full of terrors...
    • ICQ Messenger - 251194643
    • Yahoo Instant Messenger - gscott7833
    • View Profile
    • http://teambg.net
Max's Scripting Tutorial
« on: July 11, 2012, 06:25:07 AM »
Max's Scripting Tutorial

1. General Statement - What you will need to begin

2. IF THEN logic and RESPONSE

3. Triggers, Actions, and everything in between

4. Writing a script

5. Compiling your script

6. Using your script in-game

7. Differences Between the Games

8. Miscellaneous

NOTE: THIS TUTORIAL IS MADE FOR BG2. MANY ACTIONS THAT ARE IN BG2 LIKE "OR()" DO NOT EXIST IN THE OTHER GAMES OR ARE USED DIFFERENTLY. BE SURE TO LOOK IN THE SPECIFIC GAME'S IDS FILES FOR THE PROPER FORMAT OF ACTIONS AND TRIGGERS. AGAIN, THIS TUTORIAL IS MADE BASICLY FOR BG2.

General Statement

Scripting will ALWAYS seems hard the first time you try it. If you have programming experience it will be a bit easier, but anyone can learn how to do it. Scripting is used in the Infinity Engine to control what NPC's do, what spells to cast in battle, and when certain events will happen, among other things.

What You Will Need

The first thing I suggest doing is going to www.teambg.org and downloading and installing the VB6 package and the TeamBG AI Scriptor. The TeamBG Scriptor is what I use to script. Another useful program for studying scripts used by the game, and checking to see if your script compiled correctly is Infinity Explorer from www.sourceforge.net/infexp.html.

Two backslashes indicate side notes in a script. For Example, I could write...

IF

See(NearestEnemyOf(Player1)) //If I see the closest enemy to player 1

THEN

RESPONSE#100

Attack(NearestEnemyOf(Player1) // then I will attack him

END

... and word to the right of the slash marks are just my comments.

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 "))".
My mods:
Dark Horizons
The Undying
Nikita
IWD2 store

Co-contributor:
Dark Side of the Sword Coast BG1 Weidu
Aurils Bane
Encounters
Saerileth
Baldur's Gate - Enhanced Edition beta tester
Baldur's Gate 2 - Enhanced Edition beta tester
Icewind Dale - Enhanced Edition beta tester

Offline Solaufein

  • Lord of the Realms
  • Administrator
  • Level 5
  • *****
  • Posts: 5149
  • Karma: +127/-19
  • Gender: Male
  • The night is dark and full of terrors...
    • ICQ Messenger - 251194643
    • Yahoo Instant Messenger - gscott7833
    • View Profile
    • http://teambg.net
Re: Max's Scripting Tutorial
« Reply #1 on: July 11, 2012, 06:25:08 AM »
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.

First (using the TeamBG AI Scriptor), click Add Wizard, then click OK -- we won't be using Add Wizard, it just needs to be used once before saving.

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

My mods:
Dark Horizons
The Undying
Nikita
IWD2 store

Co-contributor:
Dark Side of the Sword Coast BG1 Weidu
Aurils Bane
Encounters
Saerileth
Baldur's Gate - Enhanced Edition beta tester
Baldur's Gate 2 - Enhanced Edition beta tester
Icewind Dale - Enhanced Edition beta tester

Offline Solaufein

  • Lord of the Realms
  • Administrator
  • Level 5
  • *****
  • Posts: 5149
  • Karma: +127/-19
  • Gender: Male
  • The night is dark and full of terrors...
    • ICQ Messenger - 251194643
    • Yahoo Instant Messenger - gscott7833
    • View Profile
    • http://teambg.net
Re: Max's Scripting Tutorial
« Reply #2 on: July 11, 2012, 06:25:09 AM »
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.

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.

Differences Between the Games

Baldur's Gate I is the first game of the Infinity Engine series and therefore has less commands than either BG II or IWD. Icewind Dale was not based around a party of NPCs, so commands like JoinParty() or InParty() were left out. BG II is the best of both BG I and IWD in terms of the variety of actions, triggers, and objects.

So, recap...

BG 2

Has MANY useful actions and triggers.

IWD

Has many triggers and actions that are hard to figure out or not very useful for scripting.

BG

Doesn't have as many actions or triggers, but can still be manipulated easily.

Miscellaneous

Make sure you check the Scripting Quick Reference.doc in your Script Compiler folder for info on what all the actions and triggers do.

For more info on scripting and another tutorial (more advanced) like this one, check out Max's Advanced Scripting Guide.
My mods:
Dark Horizons
The Undying
Nikita
IWD2 store

Co-contributor:
Dark Side of the Sword Coast BG1 Weidu
Aurils Bane
Encounters
Saerileth
Baldur's Gate - Enhanced Edition beta tester
Baldur's Gate 2 - Enhanced Edition beta tester
Icewind Dale - Enhanced Edition beta tester