J.C.'s Baldur's Gate Teamwork AI Scripts

By J.C. Hamlin <jch@citilink.com>
Version 2.1 (2/6/00)


Table of Contents


Where can I get them?

The most recent version of these scripts and their development environment can be found at
J.C.'s Baldur's Gate Scripting Center

Please link to me!

<a href="http://www.citilink.com/~jch/bg/"
   target="_top"
   onMouseOver="jcbgsc.src='http://www.citilink.com/~jch/bg/jcbgsc-on.gif'"
   onMouseOut="jcbgsc.src='http://www.citilink.com/~jch/bg/jcbgsc-off.gif'">
  <img name="jcbgsc"
       src="http://www.citilink.com/~jch/bg/jcbgsc-off.gif"
       alt="J.C.'s Baldur's Gate Scripting Center"
       border=0>
</a>

How do I use them?

Quick Start

  1. Place the TWFights.bs and TWSpells.bs files in your BG\scripts directory
  2. In the game go to the character information screen
  3. Select "Customize"
  4. Select "Script"
  5. Choose "CUSTOM(TWFIGHTS)" or "CUSTOM(TWSPELLS)"

Important Note

I tested these scripts with the most recent version of Baldur's Gate (BG + TOSC + v1.3.5512 patch). I don’t know if they work in earlier versions although I have no reason to believe they would not.

What's New?

Version 2.1 (2/6/00)

Working with Jesse LaCroix (TMPsycho) <
jlacroix@home.com> we identified and fixed a few problems.
  1. Attempt to use innate healing and lay on hands before spells since they are fast and work like potions and can't be interrupted.
  2. Replaced [ENEMY] with NearestEnemyOf(Myself) and removed See() since it has a shorter range than the max range of ranged weapons. Archers will now pursue their targets.
  3. Replaced LastTargetedBy() (broken) with NearestEnemyOf(). Characters should now coordinate attacks better.

Version 2.0 (9/10/99)

As promised I did a major overhaul on the guts of the code replacing the Help() event trigger with the custom event trigger Shout() and the Help() event receiver with the custom even receiver Heard(). I am now using four custom events, POISONED, HURT, UNDER_ATTACK, and ATTACKING (see the shout.ids file). The party now coordinates offense and defense much better than ever before. In addition party members with anti-poison ability can now help others who are poisoned. And finally, InParty() seems to be working and can be used to detect hostile party members (such as when charmed). So, the party will no longer attack its own members (unless you order them to) and instead will attempt run away and focus their efforts on other enemies.

Version 1.1 (9/6/99)

Now there are two versions of the Teamwork script: TWFights and TWSpells. TWSpells was designed with spell casters in mind but it can be used for any character you want to keep out of melee combat and just stand back and attack from range or stand back and await orders. TWFights is for the true fighters, those characters you want to give the option of using melee weapons in combat when appropriate.
Also, there is a bug in EquipMostDamagingMelee(). It chooses to use fists over a ranged weapon full of ammo for melee. This poses a big problem for classes like the Fighter/Mage/Thief that only have one quick weapon slot. With a ranged weapon in their only quick weapon slot this command will switch to using fists as a melee weapon. The fix to the TWFights script makes it so that this character class will now never do an EquipMostDamagingMelee(). It's too bad BioWare couldn't fix the interface to allow *every* character class to have two QW slots. If this same bug applies to other character classes (ones with only one quick weapon slot) please let me know and I'll fix them as well.

Version 1.0 (9/3/99)

Initial release.

What if I find problems or have new ideas?

If you find problems or have comments please drop me a line and let me know what you think. If you modify these scripts and add new features please send me an update. I will be periodically updating these scripts and will update the WWW site. post to news:alt.games.baldurs-gate, and send e-mail to anyone who has e-mailed me a response. If you are a Baldur's Gate scriptwriter please read my "Unresolved Issues and Unanswered Questions" section. If you have any ideas about how to solve any of those problems or can confirm anything I have discovered please let me know. I'm going to bring this list to BioWare's attention when I'm ready.

What do they do?

I studied all of the provided AI scripts and searched the WWW for a couple of months to find and study any script I could. The Teamwork scripts provide the best features of every script I could find. They are suitable for any character class and are meant to be used by every member of your party to replace the stock scripts (especially if you're still using the Default script). It makes the game more enjoyable because it makes your characters behave smarter, it enables the party to effectively work as a team, and it eliminates much of repeated actions and micro-management. In addition, healing potions, antidotes, and good berries no longer need to take up a valuable quick item slot. Here’s a summary of what you get:
  1. Spell casters that keep themselves at range (TWSpells only)
  2. Smart selection of melee and ranged weapons (TWFights only)
  3. Coordinated attacks and defenses
  4. Automatic anti-poison and healing using items, spells, and innate abilities
  5. The ability to call for help with the hotkey "H"

TWSpells

For characters you want to keep out of melee combat (characters without a melee weapon in their quick weapon slot(s) such as: archers, mages, defensive clerics, some thieves, dual/multi classes that prefer not to engage in melee, etc). This script will keep them out 10' away from the action firing in ranged weapons and/or waiting there for you to tell them what to do or what spell to cast. Note however that they will get in close if you give them an order that requires them to move in (such as casting a spell that requires the target to be touched). Also, healers using this script are brave and will risk their own life to enter the fray and heal those in serious need. You still have to manage most of your spell casting with this script, so remember that when they run out of ranged ammo they will just keep their distance and wait for you to tell them to do something. Characters using this script consider "in need of healing" to be HP < 50%.

TWFights

For characters you want to give the choice of engaging in melee combat. Characters using this script will use melee weapons when enemies are close (within 5') and not running away in panic, and used ranged weapons otherwise. Note, you don't have to give these guys a ranged weapon, if a ranged weapon isn't available they'll just use their melee weapon. However, due to a bug in the EquipMostDamagingMelee() action, you should make sure these characters have a melee weapon, otherwise they will use their fists (except the F/M/T who can only have one weapon, bug fixed). Characters using this script consider "in need of healing" to be HP < 35%.

Priorities

  1. Process hotkey press
  2. Stop poison on self or if I can't then shout I'm POISONED
  3. Stop poison on others if I hear they are POISONED
  4. Heal self or if I can't then shout I'm HURT
  5. Heal others if I hear they are HURT
  6. Run from my own party members attacking me
  7. Defend myself if attacked and shout I'm UNDER_ATTACK
  8. Defend others if I hear they are UNDER_ATTACK
  9. Initiate attacks and shout I'm ATTACKING
  10. Coordinate with others when I hear they are ATTACKING
  11. Do stuff when there's nothing else better to do

The Code in English

begin

// if hotkey H was pressed then yell for help
if hotkey H was pressed then
  verbalize that I'm hurt on screen (to acknowledge the hotkey press)
  call for help (which will result in everyone that can hear me running to me)
  continue

// if I am poisoned then cure it
// try potions first, they are fastest and always work
// sometimes the spells fail
// CheckState(Myself,STATE_POISONED) does NOT work
if I am hit by poison then
  try these in this order:
  1. drink an antidote potion
  2. use my innate ability to slow poison on myself
  3. cast the cleric spell neutralize poison on myself
  4. cast the cleric spell slow poison on myself
  5. otherwise shout I'm POISONED

// if someone else is poisoned cure them
// CheckState(LastHeardBy(Myself),STATE_POISONED) does NOT work
if I heard that someone else is POISONED then
  try these in this order:
  1. use my innate ability to slow poison on them
  2. cast the cleric spell neutralize poison on them
  3. cast the cleric spell slow poison on them

// heal myself
if my hit points are less than 35%/50% then
  try these in this order:
  1. use my innate ability to lay on hands on myself
  2. use my innate ability to cure light wounds on myself
  3. cast the cleric spell cure critical wounds on myself
  4. cast the cleric spell cure serious wounds on myself
  5. cast the cleric spell cure light wounds on myself
  6. drink a healing potion
  7. otherwise if I am less than 35% then every 2 seconds shout I'm HURT

// if someone else is hurt then heal them
if I heard that someone else is HURT then
  try these in this order:
  1. use my innate ability to lay on hands on them
  2. use my innate ability to cure light wounds on them
  3. cast the cleric spell cure critical wounds on them
  4. cast the cleric spell cure serious wounds on them
  5. cast the cleric spell cure light wounds on them

// run from my own party members attacking me (charmed?)
if I am attacked by anyone in my party then
  run away from them

// defend myself
if I am attacked by an enemy that is not in our party then
  shout I'm UNDER_ATTACK
  TWFights:
    if enemy is not in STATE_PANIC and within range 5 then
       attack with melee
    else
       attack with ranged
  TWSpells:
    if the enemy is 10' or farther away then
       attack with ranged if possible
    else
       run away

// defend others
if I heard that someone else is UNDER_ATTACK by an enemy not in our party then
  TWFights:
    if the enemy attacking them is not in STATE_PANIC and within range 5 then
      attack with melee
    else
      attack with ranged
  TWSpells:
    if the enemy attacking them is 10' or farther away then
       attack with ranged if possible

// initiate attack
if I see an enemy that is not in our party then
  shout I'm ATTACKING
  TWFights:
    if enemy is not in STATE_PANIC and within range 5 then
      attack with melee
    else
      attack with ranged
  TWSpells:
    if the enemy is 10' or farther away then
       attack with ranged if possible
    else
       run away

// coordinate with others when they initiate an attack
if I heard that someone is ATTACKING then
  TWFights:
    if the enemy nearest to them is not in STATE_PANIC and within range 5 then
      attack with melee
    else
      attack with ranged
  TWSpells:
    if the enemy nearest to them is 10' or farther away then
       attack with ranged if possible

// do this stuff when there are no enemies around and
// I have nothing else better to do
if I don't see an enemy then
  if I hear a call for help and I'm not standing next to them then
     go help them (i.e. move to them)
  if my hit points are near full (85%-100%) and I have good berries then
     eat one (for 1 HP of health)
  every 30 seconds check if I don't have good berries then
     cast the cleric spell good berries if I can

end

How do they work?

Q: How does TWSpells keep my mages out of melee combat?
A: First check InWeaponRange() and !Range(<Target>,10) to attack. If the character has a melee weapon equipped this will never be true (unless there is a melee weapon that can attack someone 10' away, and if there was it is probably okay to attack with it!). Otherwise if the character has a missile weapon then use it. This relies on the fact that when a missile weapon runs out of ammo the game automatically equips the character's best melee weapon. So after the character runs out of ammo, the game switches to a melee weapon, InWeaponRange() will fail and the character will no longer attack. Then check Range(<Target>,10) to run away which keeps the character trying to move out beyond range 10 before attempting to attack or stand and await orders.

Q: How is the coordination done?
A: These scripts use he Shout() event poster and Heard([PC]) event receiver. One character shouts (i.e. posts an event) and then anyone that hears it and isn't busy with something more important hears it (i.e. receives the event) and then they reacts to the event with a handler (the code you write to react to the event). NOTE: Shout() is realistic in that it only works for recipients that are close enough to hear the character shouting! For readability I modified the Action.ids and Trigger.ids files and created a shout.ids file so that I could use readable string constants in my script code instead of numbers.

Q: How do they use the innate abilities automatically?
A: I discovered the numeric ids for the innate abilities to be 100-199 and they can be used directly in the scripts where a "spell" is called for. To make the scripts more readable I added the numbers and some string constants to the spells.ids file so I can use the string constants in the scripts instead of the numbers. NOTE: this requires you have my spells.ids file installed before you can compile the scripts and have them work correctly.

What issues are unresolved?

Decompiling

Transferring Items

Swapping Bow and Shield

Checking Poisoned State

Checking Charmed State

Attacking as I was Attacked (ranged to ranged, melee to melee)

Using OutOfAmmo()

Hiding and Trap Finding


Do you have any scriptwriting tips?


What is your script development environment?

I downloaded the latest ScriptCompiler directory from BioWare and started playing with it. I first noticed that AICompile.exe in DECOMPILE mode didn't work (see issues). I also noticed that AICompile.exe ran asynchronous -- when I ran AICompile the command prompt returned before AICompile was really done. Futhermore it didn't print errors to the terminal (is sent them to a .err file). So I built a better compile.bat which made their compiler behave more like a modern day command line compiler. Then I integrated my script editing with MSVC++ 6.0 which gave me context sensitive highlighting and automatic compiling through the custom build option. The BGSCRIPTS.* files are MSVC++ files project files. If you'd like to try this with MSVC++ then put the USERTYPES.DAT file in the directory with MSDEV.EXE and set the file properties of a .baf file to C/C++ when you view it in MSVC++. That will get you context sensitive highlighting.

What’s next?

I’ve spent four weeks of researching, writing, revising, and publishing (including this document and the WWW site) to create these scripts and I'll probably continue to build a better WWW site and better scripts. However, the Teamwork Scripts version 2.0 do everything I wanted, and the version 2.1 scripts with the bug fixes work even better. So for now I'm all out of ideas. If you have ideas please let me know.