Author Topic: Max's Advanced Scripting Guide  (Read 17173 times)

0 Members and 1 Guest are viewing this topic.

Offline Solaufein

  • Lord of the Realms
  • Administrator
  • Level 5
  • *****
  • Posts: 5160
  • 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 Advanced Scripting Guide
« on: July 11, 2012, 06:25:10 AM »
Max's Advanced Scripting Guide

1. General Statement

2. Setting Global/ Local Variables and Timers

3. What Those Damned Letters in the IDS Files Mean

4. Writing a Complex Script

5. Miscellaneous

6. Appendix

General Statement

This Guide is made assuming you have mastered all the information in the original Max's Scripting Guide and have experimented a little yourself. If you have trouble, KEEP TRYING. Trust me, it gets easier after a while.

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.
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: 5160
  • 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 Advanced Scripting Guide
« Reply #1 on: July 11, 2012, 06:25:11 AM »
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... This one should be a good deal more complicated than the one in "Max's Scripting Guide."

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

Look at the APPENDIX. If you've read and understand "Max'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().

Miscellaneous

I strongly suggest that you come up with some of your own methods of scripting. There are many different possibilities for you to use. Experiment with different actions and different triggers until you get the script you want. A good idea would be to join bgscripts at egroups. Chances are, if you are running into a problem, or would like suggestions on your scripting method, it has been discussed there.
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: 5160
  • 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 Advanced Scripting Guide
« Reply #2 on: July 11, 2012, 06:25:12 AM »
APPENDIX

IF
ActionListEmpty()
See([ENEMY.0.TROLL])
HPLT([ENEMY.0.TROLL],20)
HaveSpell(WIZARD_FLAME_ARROW)
THEN
RESPONSE #100
Spell([ENEMY.0.TROLL],WIZARD_FLAME_ARROW)
END

IF
ActionListEmpty()
See([ENEMY.0.TROLL])
HPLT([ENEMY.0.TROLL],20)
!HaveSpell(WIZARD_FLAME_ARROW)
HaveSpell(WIZARD_AGANNAZAR_SCORCHER)
THEN
RESPONSE #100
Spell([ENEMY.0.TROLL],WIZARD_AGANNAZAR_SCORCHER)
END

IF
ActionListEmpty()
See([ENEMY.0.TROLL])
HPLT([ENEMY.0.TROLL],20)
!HaveSpell(WIZARD_FLAME_ARROW)
!HaveSpell(WIZARD_AGANNAZAR_SCORCHER)
HaveSpell(WIZARD_BURNING_HANDS)
!Range([ENEMY.0.TROLL],8)
THEN
RESPONSE #100
MoveToObject([ENEMY.0.TROLL])
END

IF
ActionListEmpty()
See([ENEMY.0.TROLL])
HPLT([ENEMY.0.TROLL],20)
!HaveSpell(WIZARD_FLAME_ARROW)
!HaveSpell(WIZARD_AGANNAZAR_SCORCHER)
HaveSpell(WIZARD_BURNING_HANDS)
Range([ENEMY.0.TROLL],8)
THEN
RESPONSE #100
Spell([ENEMY.0.TROLL],WIZARD_BURNING_HANDS)
END

IF
ActionListEmpty()
OR(2)
See([ENEMY.0.0.VAMPIRE])
See([ENEMY.0.0.VAMPYRE])
HaveSpell(CLERIC_NEGATIVE_PLANE_PROTECTION)
InParty([GOODCUTOFF.0.0.FIGHTER])
Global("fighter_leveldrain_protect","LOCALS",0)
THEN
RESPONSE #100
Spell([GOODCUTOFF.0.0.FIGHTER],CLERIC_NEGATIVE_PLANE_PROTECTION)
SetGlobal("fighter_leveldrain_protect","LOCALS",1)
END

IF
ActionListEmpty()
OR(2)
See([ENEMY.0.0.VAMPIRE])
See([ENEMY.0.0.VAMPYRE])
HaveSpell(CLERIC_NEGATIVE_PLANE_PROTECTION)
InParty([GOODCUTOFF.0.0.PALADIN])
Global("paladin_leveldrain_protect","LOCALS",0)
THEN
RESPONSE #100
Spell([GOODCUTOFF.0.0.PALADIN],CLERIC_NEGATIVE_PLANE_PROTECTION)
SetGlobal("paladin_leveldrain_protect","LOCALS",1)
END

IF
ActionListEmpty()
OR(2)
See([ENEMY.0.0.VAMPIRE])
See([ENEMY.0.0.VAMPYRE])
HaveSpell(CLERIC_NEGATIVE_PLANE_PROTECTION)
InParty([GOODCUTOFF.0.0.RANGER])
Global("ranger_leveldrain_protect","LOCALS",0)
THEN
RESPONSE #100
Spell([GOODCUTOFF.0.0.RANGER],CLERIC_NEGATIVE_PLANE_PROTECTION)
SetGlobal("ranger_leveldrain_protect","LOCALS",1)
END

IF
!See([ENEMY.0.0.VAMPIRE])
!See([ENEMY.0.0.VAMPYRE])
OR(3)
Global("fighter_leveldrain_protect","LOCALS",0)
Global("paladin_leveldrain_protect","LOCALS",0)
Global("ranger_leveldrain_protect","LOCALS",0)
THEN
RESPONSE #100
SetGlobal("fighter_leveldrain_protect","LOCALS",0)
SetGlobal("paladin_leveldrain_protect","LOCALS",0)
SetGlobal("ranger_leveldrain_protect","LOCALS",0)
END

IF
ActionListEmpty()
See([ENEMY.UNDEAD])
NumCreatureVsPartyGT([ENEMY],3)
HaveSpell(CLERIC_SUNRAY)
THEN
RESPONSE #100
Spell([ENEMY.UNDEAD],CLERIC_SUNRAY)
END

IF
ActionListEmpty()
See([ENEMY.UNDEAD])
NumCreatureVsPartyGT([ENEMY],3)
!HaveSpell(CLERIC_SUNRAY)
HaveSpell(CLERIC_FALSE_DAWN)
THEN
RESPONSE #100
Spell([ENEMY.UNDEAD],CLERIC_FALSE_DAWN)
END

IF
ModalState(TURNUNDEAD)
ActionListEmpty()
See([ENEMY.UNDEAD])
!HaveSpell(CLERIC_SUNRAY)
!HaveSpell(CLERIC_FALSE_DAWN)
THEN
RESPONSE #100
Turn()
END

IF
ActionListEmpty()
See([ENEMY])
!Range(NearestEnemyOf(Myself),8)
!OutOfAmmo()
THEN
RESPONSE #100
EquipRanged()
AttackReevaluate(NearestEnemyOf(Myself),30)
END

IF
ActionListEmpty()
See([ENEMY])
OR(2)
Range(NearestEnemyOf(Myself),8)
OutOfAmmo()
THEN
RESPONSE #100
EquipMostDamagingMelee()
AttackReevaluate(NearestEnemyOf(Myself),30)
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