Author Topic: Scripting Tutorial by Cirerrek  (Read 16241 times)

0 Members and 1 Guest are viewing this topic.

Offline Amy

  • Global Moderator
  • Level 3
  • *****
  • Posts: 1235
  • Karma: +85/-10
  • Gender: Female
    • View Profile
Scripting Tutorial by Cirerrek
« on: July 11, 2012, 06:25:21 AM »
_____________________________________________

Goran's Beginner Scripting Guide Part 1

Author: Göran Rimén
Date: Feb 2001
______________________________________________

This document is based on Biowares Quick Reference Guide. Any information
contained herein, is provided in an as is state.
The aim is that this guide should only contain information relevant to creating
a player script.

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 except
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 hot key.

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 (ssq.) from Myself
THEN
RESPONSE #100
SetInterrupt(FALSE)
MoveToObject(LastHeardBy()) (LastHeardBy())// Myself will walk to the caller without any delay
SetInterrupt(TRUE)
END

Note: I wonder if there is a way to make them run.

[Cirerrek: Well, you could have them cast haste or improved haste on themselves if they have that
capability and aren't already hasted or improved hasted. This would simulate running, but of the current
IE games, only Torment allows you to actually run]

In the first module of the script, I have two triggers ActionListEmpty() and
Hot Key(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 file name 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 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 scripts file name 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 file name
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 characters. I select my first
character and open the Record window, press the button Customize to open the
Customize 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.

[Cirerrek: I'm not entirely sure of the Test.bat bit, I've never used it before. I currently
use Near Infinity created by Jon Hauglid http://www.idi.ntnu.no/%7Ejoh/ni/
to compile my scripts. The error handling has been tuned and improved well over that
which is offered by the out of the box compiler, plus it compiles scripts in seconds
compared to the several minutes it takes the out of the box compiler to compile a similar
sized script. If you browse around the website the link above will take you to, there is
a section that explains how to compile scripts using NI (Note: NI by default compiles
scripts as .bcs files. You can simply right click on the file in Windows Explorer and
select rename and rename the file to .bs with no harm done).]

If you start writing huge Party AI scripts, you may find it beneficial to break your
scripts up into sections such as Out of Combat Stuff, Weapon Switching, Attacking, Defensive
Spells, Attack Spells, etc.,. And use some method to concatenate the files together. Some programs
that have been written to concatenate IE scripts together are SnipSorter and AIScriptFactory.
I've had some problems with SnipSorter, so I have switched to a command line method of
concatenating the scripts, which involves emulating a UNIX environment with CgWin and using
some simple programs created for me]

Offline Amy

  • Global Moderator
  • Level 3
  • *****
  • Posts: 1235
  • Karma: +85/-10
  • Gender: Female
    • View Profile
Scripting Tutorial by cirerrek
« Reply #1 on: July 11, 2012, 06:25:22 AM »
Goran's Beginner Scripting Guide Part 2

Author: Göran Rimén
Date: Feb 2001
______________________________________________


In all programming it is important to maintain a good code structure, most for
your own convenience when building and maintaining your script, but sometimes
the programming language requires that a certain structure is maintained.
Hmm nice words but difficult to follow.

This scripting language is very special, as it is continues looping through
all modules of a script and as soon as the conditions are true in one module
the script will restart.

One cycle through the modules (from start to the
next start) is called an AI cycle.

Triggers and variables that are set by one module can not be used in a
following module until the next AI cycle begins.

Hmm, important stuff this.

EVENT TRIGGERS

Some so-called 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.
This paragraph was copied from the BG scripting manual.

These requirements must be fulfilled and it is very easy to make mistakes here.
Believe me I have done all possible mistakes, and this is a very difficult
programming language to master.

The first thing you must know is the different event triggers and make sure
that they are checked in the first modules of the script.

In the sample script Thief.baf I have used Hotkey, Heard, HitBy and AttackedBy.
There are some more but we leave them out for now.

STATUS TRIGGERS

All triggers that are not listed as event triggers are called status triggers.

The BG scripting manual has this definition, "Status triggers are checked every
time through the AI cycle. As a result they always apply if they are true."

A complete list of all available triggers can be found in the file Triggers.ids.

Please DO NOT CHANGE anything in any of this .ids files. Only use the files
that are shipped with the game until you understand how they are used by the
compiler. Promise??

AN IMPORTANT KEYWORD

The Action Continue() is perhaps the most important action to master.
The description in the manual is, "Use the actions currently selected, but
continue looking for more."

Well, I hope you understand that. I did not understand it to start with.

Why is that so important do you think?

It is important that all modules that are dependent of the value of an event
trigger are reached before the next AI cycle starts.

You can by linking a number of modules together in a chain do things that
should be impossible to achieve without module linking.

MODULE LINKING

Let's check how this works in the sample file.

// Module #1A. Hotkey S pressed, set heard trigger 1001
IF
HotKey(S)
THEN
RESPONSE #1
Shout(1001)
Continue()
END
// Module #1B. Say something
IF
HotKey(S)
THEN
RESPONSE #1
VerbalConstant(Myself,3)
Wait(6)
END

Take a look at Block #1 and modules 1A and 1B.

The event trigger in both modules is HotKey, and the last action in M1A is Continue().

Ok Compile Thief.baf and make at least two of your characters use the script.

Select one of these characters and press the S key and hold it pressed for
a couple of seconds.

The selected PC will now say something.

Experiment with different distances between the two. Which is the min and max
distance for a reaction from the character that was called?

Now open Thief.baf file and remove the line Continue() in M1A. Compile this version
of Thief.baf and check the result.

The result is that Module #1B will never be reached when Continue() is removed.

Think about it.

But if you think that using Continue() at the end of every
module would be great idea then you are wrong. The script will work but it will
have a very bad reaction time. So don't do that. OK?

BLOCK #1

All hotkey and heard triggers must be in the first block if you expect your
character to react on them in combat.

BLOCK #2

In all my scripts is health care Block #2. All modules that have the event
trigger HitBy must be the first modules in this block.

BLOCK #3

Well here starts the funny part. This block is divided in three parts,
targeting, fighting, and special for a thief is the back stab module.
And believe it or not the structure must be as shown.

Let's start with the broken Block #3C

Module #2 retreat after back stab is the first module in the combat section and
Module #1 is the last module in the script. Do you know why?

// Module #2. Retreat after back stab
IF
Exists([ENEMY])
!StateCheck(Myself,STATE_INVISIBLE)
Global("GR_RetreatAfterBackstab","LOCALS",1)
THEN
RESPONSE #1
MoveToObjectNoInterrupt(Player1)
SetGlobal("GR_RetreatAfterBackstab","LOCALS",2)
END
//---------------------------------------------------------------
.
.
//---------------------------------------------------------------
// Module #1. Back stab attack
IF
Exists([ENEMY])
StateCheck(Myself,STATE_INVISIBLE)
Range(LastSeenBy(Myself),12)
THEN
RESPONSE #1
SetGlobal("GR_RetreatAfterBackstab","LOCALS",1)
EquipMostDamagingMelee()
AttackOneRound(LastSeenBy())
END

Yes, of course, its due to this important stuff in the beginning.

Triggers and variables that are set by one module can not be used in a following
module until the next AI cycle begins. You remember that know?

In this module it is the status trigger, StateCheck(Myself,STATE_INVISIBLE),
that is true when the AttackOneRound is started and will be set to false as soon
the thief scores an hit and becomes visible.

!StateCheck(Myself,STATE_INVISIBLE) which is true when the thief is visible can
not be used until the next AI cycle starts.

Block #3A select a target

This block consists of two parts, the linked modules M1A, M1B and M1C are
used for selecting a target and the two modules M2A and M2B are used for
analyzing the selected target.

The key elements are, in the M1 group the status trigger See(O:Object*) and in
the M2 group the special named object LastSeenBy(Myself) which is the same as
LastSeenBy().

After you start using LastSeenBy() do not use See(Object) again because if you
do that, LastSeenBy() will change focus to that object pointed at by the last
See(Object). I hope you understand this.

Block #3C attack the target

I am working on this text

Block #4. Hide in shadows

I am working on this text

Source structure of Thief.baf

Block #1. CONVERSATION
Module #1A. Hotkey S pressed, set heard trigger 1001
Module #1B. Say something
Module #2. Respond to heard trigger 1001
Block #2. HEALTH CARE MYSELF
Module #1. Use item "POTN20"
Module #2A. Use item "POTN17"
Module #2B. Use item "POTN17"
Module #3. Use item "ADISEASE"
Module #4. Use item "EXTHEAL"
Module #5. Use item "POTN17"
Module #6. Use item "POTN08"
Module #7. Cheat code. Create item "POTN08"
Block #3C. COMBAT Thief back stab
Module #2. Retreat after back stab
Block #3A. COMBAT, SELECT A TARGET
Module #1A. This target has the lowest priority.
Module #1B. This target has the next lowest priority.
Module #1C. This target has the highest priority.
Module #2A. Check that this is a valid target
Module #2B. Check that this is a valid target
Block 3B. COMBAT attack the target
Module #1. Melee attack
Module #2A. Ranged attack, select ammo
Module #2B. Ranged attack, equip ranged weapon
Module #2C. Ranged attack, if you have a ranged weapon
Block #4. Hide in shadows
Module #1. Hide in shadows. Cheat code
Block #3C. COMBAT Thief back stab
Module #1. Back stab attack

Offline Amy

  • Global Moderator
  • Level 3
  • *****
  • Posts: 1235
  • Karma: +85/-10
  • Gender: Female
    • View Profile
Scripting Tutorial by cirerrek
« Reply #2 on: July 11, 2012, 06:25:23 AM »
Goran's Beginner Scripting Guide Part 3

Author: Göran Rimén
Date: Feb 2001
______________________________________________

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 specific 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. spell casting 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.

Distances:
Distances are measured in search squares (ssq.). How long is a ssq. , Well,
nobody can give an good answer, and it is of no 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
to the target you are trying to attack with a ranged weapon
you will receive a -4 penalty to your armor class
Range 25: max distance your character can see and hear

[Cirerrek: Extremely large creatures such as dragons can
pose some difficulties as a dragon is considered to be 7 ssq.
in size, so if you have your weapon switching routines set to switch to melee when you are within 4 ssq. of a creature, you may notice your party continuing to use missile weapons even though they appear to be standing toe to toe with the dragon. As far as their scripts are concerned, they are still at missile weapon range with respect to the dragon.]

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

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.

Offline Amy

  • Global Moderator
  • Level 3
  • *****
  • Posts: 1235
  • Karma: +85/-10
  • Gender: Female
    • View Profile
Scripting Tutorial by cirerrek
« Reply #3 on: July 11, 2012, 06:25:24 AM »
Goran's Beginner Scripting Guide Part 4

Author: Göran Rimén
Date: Feb 2001
______________________________________________

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 Script Compiler
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
NearestEnemyOf
SecondNearestEnemyOf
ThirdNearestEnemyOf
FourthNearestEnemyOf
FifthNearestEnemyOf
SixthNearestEnemyOf
SeventhNearestEnemyOf
EighthNearestEnemyOf
NinthNearestEnemyOf
TenthNearestEnemyOf
NearestEnemyOfType

Note. All of these objects are only for use with 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<-Special Case

[0.0.0.0.0.0.0]

You can use ObjectType to identify special targets

ENEMYALLY:
EnemyAlly is a range between the PC and the evil NPCs . Creatures can fall anywhere along this range.
The IDS file used is EA.ids, which you can find in the Script Compiler 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 Script Compiler
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 Script Compiler 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 Script Compiler directory.

In this file there are several useful class objects
Eg. See(NearestEnemyOfType([0.0.0.DRUID_ALL]))
Eg. See(NearestEnemyOfType([0.0.0.BARD_ALL]))
Eg. See(NearestEnemyOfType([0.0.0.CLERIC_ALL]))
Eg. See(NearestEnemyOfType([0.0.0.MAGE_ALL]))

SPECIFICS:
Specifics holds the identification for special NPCs
The IDS file used is Specific.ids, which you can find in the Script Compiler directory.

The only use I found so far is following:
See(NearestEnemyOfType([0.0.YANTI.0.NO_MAGIC]))
This will identify a Yanti mage.

Note: I wonder way Bioware used NO_MAGIC to identify a mage? )

INSTANCE:
Instance holds the gender identification for special NPC?fs in BG2
The IDS file used is Gender.ids, which you can find in the Script Compiler directory.

Eg. NumCreatureLT([GOODCUTOFF.0.0.0.0.SUMMONED],1)// 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.

[Cirerrek: To be really specific with ObjectType, you can fill in several of the
objectTypes

Eg. See([GOODCUTOFF.0.ELF.MAGE.0.FEMALE.0])// true if whoever is running
the scripts sees any good elven female mage.]

As with any of the .ids files, you can
replace the word with the number from the appropriate .ids file, meaning the same
thing could be written as See([30.0.2.1.0.2.0]), but it is typically not done this
way because it makes the .baf file hard to read.

In the two examples above, you will notice that the General and Special
case Object Types are not being used and are thus being represented by zeroes, which
are acting as place holders. You can leave off any of the zeroes after the ObjectType
you are really interested in using.

Eg. Say I only really wanted to look for a GOODCUTOFF ELF then I could code it this way
See([GOODCUTOFF.0.ELF])//true if whoever is running the script sees any good elf.

Notice that there are no place holding zeroes after ELF, this is because they aren't really
necessary as the script compiler assumes them to be 0 if there is no placeholder. But
there is a zero placeholder for the General ObjectType which is necessary to include

Eg. See([GOODCUTOFF..ELF]) or See([GOODCUTOFF.ELF]) is not valid

Some scripters like to make sure all seven of the object types have something in them
even if it is just a placeholder. But it isn't really necessary, so it becomes a matter
of scripting style.]

Offline Amy

  • Global Moderator
  • Level 3
  • *****
  • Posts: 1235
  • Karma: +85/-10
  • Gender: Female
    • View Profile
Scripting Tutorial by cirerrek
« Reply #4 on: July 11, 2012, 06:38:58 AM »
______________________________________________

Goran's Beginner Scripting Guide Part 5

Author: Göran Rimén
Date: Feb 2001
______________________________________________

Special Object Identification

NAME:
This is the name used by Bioware to identify certain creatures.
Note. Only the names of an 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. Otherwise it must be my call !!
!Range(NearestEnemyOf(),4)//true if range to my nearest enemy is >4. I might be involved in combat
See(LastAttackerOf(LastHelp()))// true if I can see the last attacker of the last caller of help
THEN
RESPONSE #100
DisplayString(Myself,49769)//will print I shall help thee!in the 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 last 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 were 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 to 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

Note: 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().

Offline Amy

  • Global Moderator
  • Level 3
  • *****
  • Posts: 1235
  • Karma: +85/-10
  • Gender: Female
    • View Profile
Scripting Tutorial by cirerrek
« Reply #5 on: July 11, 2012, 06:38:59 AM »
Goran's Beginner Scripting Guide Part 6

Author: Göran Rimén
Date: Feb 2001
______________________________________________

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 Script Compiler 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 Script Compiler 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

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 Script Compiler 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)
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

Offline Amy

  • Global Moderator
  • Level 3
  • *****
  • Posts: 1235
  • Karma: +85/-10
  • Gender: Female
    • View Profile
Scripting Tutorial by cirerrek
« Reply #6 on: July 11, 2012, 06:39:00 AM »
Goran's Beginner Scripting Guide Part 7

Author: Göran Rimén
Date: Feb 2001
______________________________________________

ActionListEmpty():
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.

And 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: Spells are completed as soon as
the casting animation is finished.

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 Script Compiler directory

IF
AreaType(CITY)
THEN
RESPONSE #100
NoAction()
END
-------------------------------

CheckStat(AnyObject, X, Stat)
Returns: Boolean (True or False)

True if the Objects Stat is equal with X. (=X)

The IDS file used is Stats.ids, which you can find in the Script Compiler directory.

Note: Be warned Bioware has not implemented many of the constants in the Stats file.

I have used following:

CheckStat(Myself,0,MINORGLOBE)//True if I am not under the effect of a spell that alters my MINORGLOBE Stat

CheckStatGT(AnyObject, X, Stat):
//True if the Object?fs Stat is greater than X. (>X)

CheckStatGT(LastSeenBy(Myself),40,RESISTMAGIC)//True if the object last seen by myself has Magic Resistance >40%

CheckStatGT(Myself,1,ARMORCLASS)//True if my armor class is greater than 1 (remember in D&D 2E rules, lower ac is better)

CheckStatGT(LastSeenBy(Myself),8,SAVEVSSPELL)//True if the object last seen by myself has Save vs. Spells >8
(remember in D&D 2E rules, lower saves are better)

CheckStatGT(LastSeenBy(Myself),1,NUMBEROFATTACKS)//True if the object last seen by myself has more than 1 attack/round

CheckStatGT(LastSeenBy(Myself),9,SAVEVSPOLY)//True if the object last seen by myself has Save vs. Polymorph >9

CheckStatLT(AnyObject, X, Stat):
True if the Object?fs Statics is less than X. (
CheckStatLT(Myself,1,STONESKINS)//True if I have less than 1 Stoneskin remaining

CheckStatLT(LastSeenBy(Myself),31,RESISTCOLD)//True if the object last seen by myself has <31% resistance to cold

CheckStatLT(LastSeenBy(Myself),31,RESISTFIRE)//True if the object last seen by myself has <31% resistance to fire

CheckStatLT(LastSeenBy(Myself),31,RESISTACID)//True if the object last seen by myself has <31% resistance to acid

CheckStatLT(LastSeenBy(Myself),50,RESISTELECTRICITY)//True if the object last seen by myself has <31% resistance to electricity

CheckStatLT(LastSeenBy(Myself),8,THAC0)//True if the object last seen by myself has a THAC0 (To Hit Armor Class 0)
<8 (remember in D&D 2E, lower THACO is better)

CheckStatLT(LastSeenBy(Myself),9,SAVEVSDEATH)//True if the object last seen by myself has a Save Vs. Death <9
(remember in D&D 2E, lower Saving Throws are better)

CheckStatLT(LastSeenBy(Myself),9,SAVEVSWANDS)//True if the object last seen by myself has a Save Vs. Wands <9
(remember in D&D 2E, lower Saving Throws are better)

CheckStatLT(LastSeenBy(Myself),9,SAVEVSPOLY)//True if the object last seen by myself has a Save Vs. Polymorph <9
(remember in D&D 2E, lower Saving Throws are better)

CheckStatLT(LastSeenBy(Myself),9,SAVEVSBREATH)//True if the object last seen by myself has a Save Vs. Breath Weapon <9
(remember in D&D 2E, lower Saving Throws are better)

CheckStatLT(LastSeenBy(Myself),5,LEVEL)//True if the object last seen by myself is less than 5th level

CheckStatLT(LastSeenBy(Myself),31,RESISTMISSILE)//True if the object last seen by myself has <31% resistance to missile damage

CheckStatLT(LastSeenBy(Myself),31,RESISTPIERCING)//True if the object last seen by myself has <31% resistance to piercing damage

Offline Amy

  • Global Moderator
  • Level 3
  • *****
  • Posts: 1235
  • Karma: +85/-10
  • Gender: Female
    • View Profile
Scripting Tutorial by cirerrek
« Reply #7 on: July 11, 2012, 06:39:32 AM »
______________________________________________

Compiler Error Handling

Author: Göran Rimén
Date: Feb 2001

______________________________________________


The compiler error handling system is not fully implemented and many
obvious errors are not handled at all.

The key words IF, THEN, RESPONSE #, END are case sensitive.

Any typing error of the key word RESPONSE #, will terminate the
compiler.

The key words IF, THEN, and END are handled by the system and will
give an error message.

The trigger and action constants such as:

Delay()
AttackedBy()
StateCheck()
CreateItem()
UseItem()
Continue()

Are not case sensitive, but the spelling must be identical with that in the .ids look up file.

The trigger look up file is Trigger.ids and the action look up file is
Action.ids, but be aware that they are not identical in the different
games BG1, IWD and BG2.

Any typing error of a trigger or action keyword will give an error
message.

The event trigger AttackedBy(O:Object*,I:Style*AStyles) is interesting
because its two arguments are not always correctly handled by the
compiler.

The first argument is the object and can be written in two ways either
in the generic way [EnemyAlly.General.Race.Class] or by using the
specific named objects such as Myself, NearestEnemyOf etc. which can
be found in the Objects.ids.

In this case the generic form was used AttackedBy([ANYONE], DEFAULT)
and the compiler will use the look up file EA.ids to find the constant
for ANYONE and the Astyle.ids for the constant for DEFAULT.

However you can use any word and the compiler will not respond with an error
message.

By the way ANYONE is not listed as a keyword in my look up file EA.ids.
The correct word should be ANYTHING.

[Cirerrek: As far as I know, the script compiler interprets ANYONE or ANYTHING as the same thing]

The status trigger !StateCheck(Myself,STATE_INVISIBLE) will compile
without an error message regardless of how the second argument
(STATE_INVISIBLE) is spelled. You can try XX.