IWDII Scripting Info
Gathered from various places - Interplay Discussion Boards, TeamBG Discussion Boards,
BlackIsle Studios Message Boards, PREFAB.IDS (came with the game).
The list is by no means complete and the boards mentioned above still contain quite
a bit of useful information, but the following info is what I gathered in a relatively
quick cut and paste raid.
I may get around to formating it in a more visually pleasing fashion, but don't count
on it. Feel free to use the info contained in however you like. If you are going
to use it to create a scripting guide, then please credit the people I have listed
as providing the question/info/answer/etc.,.
---------
Summoning
---------
-Caliban72
SummoningLimitLT(Myself, x) where x is the check against how many creatures are out there
currently. If you were testing for Animate Dead for instance, use:
SummoningLimitLT(Myself, 5)
Because the limit is actually six and AD will bring in one more.
-Caliban72
I've been using
SummoningLimitLT(Myself, x)
to determine whether it's OK to call more or not. Now I'm trying to script Bansihment
and Dismissal, which get rid of enemy summoned creatures.
How do I determine if, and how many opposing summons are in sight?
How do I target them?
-Weimer
I would try the object specifier "NearestEnemySummoned" or something of the form
[GOODCUTOFF.0.0.0.0.SUMMONED].
-caliban72
Q: What about Dismissal? What kind of checks would one do do determine if a target is a summons?
Will MSAO validate a target for this spell?
-Briareus
A: We never used MSAO and Dismissal or Banishment so I doubt MSAO will take care of that for you.
We used a specific IF block to cast those. In OBJECT.ids you'll find a new custom object
called NearestEnemySummoned. This will point to the nearest summoned creature that is an enemy
of you. Note that this might return a summoned Elemental or Demon if they have a red-circle.
-------
Timers
-------
-Gimble
StartTimer() and TimerActive() are local only, so global timers are needed for cross-character
coordination.
SetGlobalTimer - to coordinate party actions
-Gimble
As I noted in a post on the old boards, I was still "clinging to" Code:
GlobalTimerNotExpired("MyTimer","LOCALS")
and related actions/triggers, rather than switching toCode:
!TimerActive( 100 )
and related triggers.
My reasoning for this was that timer indexes (plain numbers) are hard to track, and hard to
remember which ones you have (or haven't) used.
However, after doing some reading of old information ( thanks to Caliban for the
"BI Analysis.txt" file), I found that string parsing is one of the functions in AI scripting
that is very, very slow.
As a result, it's probably a pretty significant hit to use GTNE instead of TA as a rule,
and should be avoided.
I did manage to find a solution, which IMO is an improvement over TA with numeric values.
I modified the following lines in ACTION.IDS:Code:
61 StartTimer(I:ID*TIMERS,I:Time*)
232 StartRandomTimer(I:TimerID*TIMERS,I:MinTime*,I:MaxTime*)
and the following in TRIGGER.IDS:Code:
0x0022 TimerExpired(I:ID*TIMERS)
0x4097 TimerActive(I:TimerID*TIMERS)
Simple enough. Now, I created a TIMERS.IDS file, and placed it into my override directory.
For example, my TIMERS.IDS file contains the following:Code:
5
200 SONG
201 MONK
202 SPELL
203 DAILY
-Briareus
(The 5 tells the compiler how many lines are in the .ids file.)
-Gimble cont'd
Now, in my script, I can use the following, which makes a bit more sense:
Code:
IF
ActionListEmpty()
!TimerActive(DAILY)
THEN
RESPONSE #100
// Do daily initialization here
SetGlobal("MonkFist","LOCALS",1)
// ...
StartTimer(DAILY, 2400)
END
...which is still very readable, and doesn't use any strings.
-Gimble
Q: What is the relationship between timer ticks ( SetGlobalTimer() ) and this interval?
-Briareus
A: Times used in SetGlobalTimer() and all the timers are in seconds. Intervals used in functions like AttackReevaluate() are in AI updates. So 15 AI updates would be 1 second.
-Caliban72
Q: #1 Daily Timer: Is there an accurate way to set a timer that will fire once per day?
-Briareus
A: You could set a timer for 28800 seconds. That's 8 hours. That way the code will know the
item is reusable once the party rests.
-Gimble
Trivia of the day:
Through the school of hard knocks, I found that the maximum valid timer number is 255.
Any timer number referenced above that value is always expired.
Code:
IF
!TimerActive(255)
THEN
RESPONSE #1
FloatMessage(Myself,1)
StartTimer(255,30)
END
This works as you might expect: it puts a message above the character's head every 30
seconds.
Code:
IF
!TimerActive(256)
THEN
RESPONSE #1
FloatMessage(Myself,1)
StartTimer(256,30)
END
This, however, does not. It floats a message above the character's head on every AI script
run.
-Avenger2
Well, apparently the timer ID is just one byte...
You must know that object ID's are just one byte as well (gender, class, align, etc..)
--------------------
MarkSpellAndObject()
--------------------
-Gimble
If there is any valid spell in the list, MSAO will find it even if RANDOM is used.
-Gimble
You probably already know this, but MSAO only allows a maximum of 8 spells (32 characters)
in a single call.
-Deadmeat
I've been playing a bit with MSAO().
I have some questions about the flags in SPLCAST.IDS.
Are these tests applied before, after, or during a spell selection?
-Gimble
MarkSpellAndObject() seems to apply the tests during the "mark" phase of the selection.
If a spell is not valid for that target(according to MSAO), it is not marked. An example:
Code:
IF
ActionListEmpty() // I'm not busy
ForceMarkedSpell(MARKED_SPELL) // make sure no spell is currently marked
SetSpellTarget(Nothing) // and invalidate any previously selected spell target
See(NearestEnemyOf(Myself),0) // duh.
THEN
RESPONSE #100
// 2104 - Charm Person
// 2316 - Dire Charm
// 3108 - Innate Charm Person or Animal (not used?)
// 3119 - Innate Charm Person (not used?)
// 1204 - Cleric Charm Person or Mammal
// 2507 - Dominate Person
// 2911 - Mass Domination
// 3220 - Innate Mass Dominate (not used?)
MarkSpellAndObject("21042316310831191204250729113220",LastMarkedObject, RANDOM)
Continue()
END
IF
!(IsMarkedSpell(MARKED_SPELL)) // If the above selected a spell
THEN
RESPONSE #100
Spell(SpellTarget, MARKED_SPELL) // cast the spell
WaitAnimation(Myself, WALK) // wait for an animation to complete, if necessary
WaitAnimation(Myself, CONJURE) // wait for an animation to complete, if necessary
WaitAnimation(Myself, CAST) // wait for an animation to complete, if necessary
ForceMarkedSpell(MARKED_SPELL) // and then make sure no spell is marked
END
The above attempts to look at the nearest target, and randomly choose one of your
Charm Person / Domination spells, if available (and the target is not immune to charm).
If the selection is successful, then the second IF block fires off the marked spell.
If it is not, the second IF block fails and is not entered.
Note that MSAO doesn't pick a good or bad target, it just states whether or not your
spell might have any effect on the target. Not all spells are scripted in MSAO either
-- some spells like Bless (IIRC) will be cast repeatedly on a target, even if they're
already under the spell effects.
-Deadmeat
Can a spell be chosen, and then discarded due to other qualifiers (eg. trying to cast
Call Lightning indoors)? (Leaving you with no spell to cast).
-Gimble
I'm not sure what you're asking here. You can manually choose to dump a MSAO()'d spell if you
like later on in the selection process. AFAIK, once the spell passes MSAO, the caster will
attempt to cast the spell on the target...regardless of whether or not the spell will do
anything good.
For example, if you do this:
Code:
IF
ActionListEmpty() // I'm not busy
ForceMarkedSpell(MARKED_SPELL) // make sure no spell is currently marked
SetSpellTarget(Nothing) // and invalidate any previously selected spell target
THEN
RESPONSE #100
// 1103 - Cure Light Wounds
MarkSpellAndObject("1103",Player1,0)
Continue()
END
IF
!(IsMarkedSpell(MARKED_SPELL)) // If the above selected a spell
THEN
RESPONSE #100
Spell(SpellTarget, MARKED_SPELL) // cast the spell
WaitAnimation(Myself, WALK) // wait for an animation to complete, if necessary
WaitAnimation(Myself, CONJURE) // wait for an animation to complete, if necessary
WaitAnimation(Myself, CAST) // wait for an animation to complete, if necessary
ForceMarkedSpell(MARKED_SPELL) // and then make sure no spell is marked
END
The above will attempt to cast Cure Light Wounds on Player 1 whether or not he is injured:
MSAO apparently believes that uninjured targets are still valid for cure spells.
-Deadmeat
Does SPELLCAST_RANDOM pick a spell at random from the list, and then check to see whether
you have it, or does it reduce the list to spells you have before picking one?
-Gimble
That depends on the last argument to MSAO (it's taken from SPLCAST.IDS, and the values
are additive).
If SPELLCAST_RANDOM (4) isn't selected, then it works the list front to back until it finds
a valid spell for your target (or fails). If SPELLCAST_RANDOM is selected, then it selects
from the list of valid spells randomly.
Do note that other flags can be set to disable certain spell checks, in case you don't
want MSAO to validate the target for something or other. For example, if you're wounded
(and invisible), you may want to use the SPELLCAST_IGNORE_SEE flag, since when you start
the Cure Light Wounds (or similar) spell on yourself, you will become visible.
-Gimble
Q: Is there a definite number on how many spells you can "chain" into MarkSpellAndObject?
I'm presuming that the spell string length can't exceed 255 characters, but is there a lower
limit than that? (or a higher one?)
-Caliban72
Briareus answered this one for me a while back. It's 8 spells, but you can stack multiple
MSAO calls in the same action block like so:
-Briareus
32 characters, or 8 spell IDs is the max.
-Gimble
Q: With a SPELLCAST_RANDOM option, what does MSAO() do if the first selection it makes
(randomly) isn't valid? For example, if I use a spell list that includes Mage Armor, and
the spell is selected (but already active), does MSAO pick another one at random? Not select
any spell? Select the following spell in the list (assuming it's valid)?
-Briareus
A: MSAO will pick another spell randomly.
*******************************************************************************
// 1724 CLERIC_GATE
// 1605 CLERIC_PLANAR_ALLY_FIRE_ELEMENTAL
// 1702 CLERIC_PLANAR_ALLY_EARTH_ELEMENTAL
// 2627 WIZARD_CONJURE_FIRE_ELEMENTAL
// 2628 WIZARD_CONJURE_EARTH_ELEMENTAL
// 2629 WIZARD_CONJURE_WATER_ELEMENTAL
// 2803 WIZARD_SUMMON_MONSTER_VIII
// 2809 WIZARD_SUMMON_FIEND
// 2902 WIZARD_SUMMON_MONSTER_IX
// 1901 CLERIC_ELEMENTAL_LEGION
// 2905 WIZARD_GATE
// 1723 CLERIC_SHAMBLER
// 1850 CLERIC_SUMMON_NATURES_ALLY_8
// 1950 CLERIC_SUMMON_NATURES_ALLY_9
IF
ActionListEmpty()
ForceMarkedSpell(MARKED_SPELL)
SetSpellTarget(Nothing)
GlobalGT("TW_Threat","LOCALS",3)
SummoningLimitLT(Myself,3)
!GlobalTimerNotExpired("TW_Summon","GLOBAL") // Co-Ordinated casting...
See(MyTarget,0)
THEN
RESPONSE #100
MarkSpellAndObject("17241605170226272628262928032809",LastMarkedObject,SPELLCAST_RANDOM)
MarkSpellAndObject("290219012905172318501950",LastMarkedObject,SPELLCAST_RANDOM)
Continue()
END
IF
ActionListEmpty()
!IsMarkedSpell(MARKED_SPELL)
THEN
RESPONSE #100
FloatMessage(Myself, 11125) // Better make your peace with your demons
SetGlobalTimer("TW_Summon","GLOBAL",7) // Co-Ordinated casting...
Spell(SpellTarget,MARKED_SPELL)
WaitAnimation(Myself,WALK)
WaitAnimation(Myself,CONJURE)
WaitAnimation(Myself,CAST)
ForceMarkedSpell(MARKED_SPELL)
END
Just imagine the amount of code this would take without MSAO!!
******************************************************************************************
-Caliban72
Q: BTW, Do you know if a MSAO can tell if you're outdoors or not for spell validation?
(Call Lightning, Rainstorm, etc...)
-Briareus
A: Yes, it can.
-Caliban72
Wait a minute!! MSAO doesn't check states like advertised! A cleric with two Bless
spells memorized using the code below will consistantly cast them sequentially in the
same combat! Using himself as the target you'd think already having the BLESS state on
himself would invalidate it as a spell to be marked.
-Briareus
Yeah, we weren't able to get all the state checks into MSAO, though I'm suprised Bless
doesn't work.
******************************************************************************************
// 1101 CLERIC_BLESS
// 1115 CLERIC_ARMOR_OF_FAITH
// 2120 WIZARD_MINOR_MIRROR_IMAGE (Mask)
IF
ActionListEmpty()
ForceMarkedSpell(MARKED_SPELL)
SetSpellTarget(Nothing)
GlobalGT("TW_Threat","LOCALS",1)
See(Myself,0)
THEN
RESPONSE #100
MarkSpellAndObject("110111152120",LastMarkedObject,0)
Continue()
END
IF
ActionListEmpty()
!IsMarkedSpell(MARKED_SPELL)
THEN
RESPONSE #100
Spell(SpellTarget,MARKED_SPELL)
WaitAnimation(Myself,WALK)
WaitAnimation(Myself,CONJURE)
WaitAnimation(Myself,CAST)
ForceMarkedSpell(MARKED_SPELL)
END
******************************************************************************************
-Caliban72
Since MSAO sn't checking spell states, I did a little experiment with the other two (focusing on BLESS).
NotStateCheck(Myself, STATE_BLESS)
doesn't work, while
!CheckSpellState(Myself, BLESS)
does exactly the checking needed.
Since MSAO has lost it's multiple spell advantages (except for those spells that share the
same states) and requires an extra code block (over the older HaveSpell, Spell logic), why
use it? Because if you are scripting for a bard or sorcerer HaveSpell is useless, and MSAO
is the only way to get them to fire thier spells properly.
-Briareus
You could still use it for spells it can check for. MSAO was written to make scripting enemy
spell casters easier, not uber PC scripts. As such I'm not suprised that there are issues
with it.
-Caliban72
Singular buffs can be used with MSAO like so (it seems that at least for Barkskin
the internal state checks work):
*****************************************************************************************
// 1202 CLERIC_BARKSKIN
IF
ActionListEmpty()
ForceMarkedSpell(MARKED_SPELL)
SetSpellTarget(Nothing)
GlobalGT("TW_Threat","LOCALS",2)
See(Myself,0)
THEN
RESPONSE #100
MarkSpellAndObject("1202",LastMarkedObject,0)
Continue()
END
IF
ActionListEmpty()
GlobalGT("TW_Threat","LOCALS",2)
See(Player1,0)
IsMarkedSpell(MARKED_SPELL)
THEN
RESPONSE #100
MarkSpellAndObject("1202",LastMarkedObject,0)
Continue()
END
IF
ActionListEmpty()
GlobalGT("TW_Threat","LOCALS",2)
See(Player2,0)
IsMarkedSpell(MARKED_SPELL)
THEN
RESPONSE #100
MarkSpellAndObject("1202",LastMarkedObject,0)
Continue()
END
IF
ActionListEmpty()
GlobalGT("TW_Threat","LOCALS",2)
See(Player3,0)
IsMarkedSpell(MARKED_SPELL)
THEN
RESPONSE #100
MarkSpellAndObject("1202",LastMarkedObject,0)
Continue()
END
IF
ActionListEmpty()
GlobalGT("TW_Threat","LOCALS",2)
See(Player4,0)
IsMarkedSpell(MARKED_SPELL)
THEN
RESPONSE #100
MarkSpellAndObject("1202",LastMarkedObject,0)
Continue()
END
IF
ActionListEmpty()
GlobalGT("TW_Threat","LOCALS",2)
See(Player5,0)
IsMarkedSpell(MARKED_SPELL)
THEN
RESPONSE #100
MarkSpellAndObject("1202",LastMarkedObject,0)
Continue()
END
IF
ActionListEmpty()
GlobalGT("TW_Threat","LOCALS",2)
See(Player6,0)
IsMarkedSpell(MARKED_SPELL)
THEN
RESPONSE #100
MarkSpellAndObject("1202",LastMarkedObject,0)
Continue()
END
IF
ActionListEmpty()
!IsMarkedSpell(MARKED_SPELL)
THEN
RESPONSE #100
Spell(SpellTarget,MARKED_SPELL)
WaitAnimation(Myself,WALK)
WaitAnimation(Myself,CONJURE)
WaitAnimation(Myself,CAST)
ForceMarkedSpell(MARKED_SPELL)
END
I know it's redundant in that one of the players is repeated (the internal state check
with prevent the spell from being cast twice on the same char) but I thought it better that
the caster try and protect him/herself imediately to better the chances of not being
interrupted un subsequent castings (also, this scriplet can be given to any character
regardless of what "PlayerX" they might be).
*****************************************************************************************
-Gimble
MarkSpellAndObject("1308",LastSeenBy(Myself),2)
You can find the MSAO values for that third argument in SPLCAST.IDS, and the values
are additive.
So a value of 2 indicates IGNORE_VALID_SPELL_TARGET, meaning that MSAO will just mark the
spell, and not check to make sure the target is valid.
-Briareus (In reference to a question that asks if I have a stack of MSAO spells that remove
negative effects, will MSAO work to select a spell that will get rid of the negative effect)
MSAO doesn't check if the target has the effect that one of the spells in the list will
remove. You'll have to explicitely check for Paralysis.
-Caliban72
Q: What about the casters capability (see post above)? Does MSAO check as to whether the caster
has some form of spell failure effect and then abort the spell selection? Or does it strictly
do target checks for validity?
-Briareus
A: It should. I should definitely work for spells like Vipergout. Once you cast that,
you cannot cast any spells, though you can move about. Is that what you were referring to?
-Caliban72
No, I was more looking for the Spellfailure effects to prevent wasting spells.
If I have Miscast Magic laid on me will MSAO still select a spell? Or do I need an explicit
check to prevent the atempt to cast.
-Briareus
You'll need to do explicit checks for spells that provide a percentage chance of failure.
---------------
In Combat?
---------------
-Gimble
I'm looking for a way to determine that my party is in combat.
I've seen all of the following, based on who the scripter is:
!CombatCounter(0)
NumCreatureLT([EVILCUTOFF],1)
OR(3)
Detect(NearestEnemyOf(Myself))
Detect([EVILCUTOFF])
Detect([GOODBUTRED])
(combined with a global flag so party members would signal each other)
probably other ones I don't recall at the moment.
Is there any particular advantage/disadvantage to one method over any other?
I want a little bit of realism in my scripts, but I don't want to necessarily go to
the trouble of tracking a global variable ( internal timers are fine: but global variables
require string searches, and I'm a little performance paranoid at the moment).
I guess, more specifically, want does CombatCounter do within the engine, and are there
any nasty side effects to using it? I wouldn't have thought so, but uScript (which overall
seems reasonably well-written) specifically avoids use of CombatCounter(), so I'm paranoid.
-Weimer
You saw CombatCounter(0) in IWD2? It doesn't appear in my version of IWD2's TRIGGER.IDS.
Similarly, ACTION.IDS is lacking StartCombatCounter (275 in IWD2 is SaveGame()) and whatnot.
The trigger may "just happen to work" even though it's not in the IDS files (there are a few
like that) but I wouldn't count on it.
The one that I see the most often is "AnyPCSeesEnemy()". IIRC, this is the one used to tell
when you should move on to the next "stage" of the Targos Palisade fight, for example.
Cirerrek
A simple little test script to tell me if AnyPCSeesEnemy() is working
Code:
------------------------------
// AnyPCSeesEnemy() Test
//
// APCSE.baf
IF
AnyPCSeesEnemy()
THEN
RESPONSE #100
FloatMessage(Myself,15897) // "Favored Enemy"
END
IF
!AnyPCSeesEnemy()
THEN
RESPONSE #100
FloatMessage(Myself,2778) // "Yes, I have - the children are safe."
END
Observed script behavior:
As long as no enemy was around, the second string was displayed. I had one PC scout ahead
and as soon as he saw and enemy, he and the other PCs stared displaying the first string.
As soon as the enemies body hit the ground, all of the PCs started to display the second
string again.
Due to the way the fog of war was at the time, I could see the goblin's display strings,
but the PCs didn't start displaying the first string until the scout had the goblin in sight.
This would lead me to believe that the trigger is limited to visual range (27?).
-------------------------------
-----------
Enemy Ally
-----------
-Briareus
For IwD2, it's:
code:
[EA.General.Race.SubRace.Class.Kit.Specifics.(Instance).(SpecialCase).ClassMask]
Just use the appropriate *.ids file for each slot. There are no ids files for
Instance or SpecialCase, nor are there any script functions to set or get their values.
-Avenger2
I think Gender and Alignment are also there somewhere, no ???
Unless SpecialCase isn't Gender, hmm. But where is Alignment ?
-Gimble
GOOD(33) --> Alignment(MyTarget,MASK_GOOD)
EVIL(37) --> Alignment(MyTarget,MASK_EVIL)
CHAOTIC(61) --> Alignment(MyTarget,MASK_CHAOTIC)
LAWFUL(59) --> Alignment(MyTarget,MASK_LAWFUL)
UNDEAD(1) --> Race(MyTarget,UNDEAD)
GOBLIN(79) --> Race(MyTarget,GOBLIN)
ELVES(15) --> Race(MyTarget,ELVES)
YUANTI(89) --> Race(MyTarget, YUANTI)
GIANT(81) --> Race(MyTarget, GIANT)
-Briareus
Check out CLASSMSK.ids. You can use any of the values in there. The value you'd want is
CLASSMASK_GROUP_CASTERS. The classmask is the 10th entry in the [object] group, so you'd have:
code:
--------------------------------------------------------------------------------
[ENEMY.0.0.0.0.0.0.0.0.CLASSMASK_GROUP_CASTERS]
--------------------------------------------------------------------------------
Would give you the nearest hostile creature that's a cleric, druid, wizard, or sorcerer
(and Bard I think as well, but there are no NPC bards so that's moot).
-Gimble
1: It seems (after some browsing), that the best way to try to find targets that won't
make their saves is through using something like:
See([ENEMY.0.0.0.0.0.0.0.0.CLASSMASK_WEAK_WILL_SAVE])
-Briareus
Have you tried using CheckStat(), CheckStatGT(), or CheckStatLT()? The values you need are in STATS.ids.
-Gimble
9 SAVEFORTITUDE
10 SAVEREFLEX
11 SAVEWILL
------------
AI Framerate
------------
-Gimble
Q: How do I know how often my script will be called?
-Briareus
A: 1/2 the value of your AI Framerate per second. So, the default AI Framerate of
30 means your script is called 15 times a second. Your script is NOT called while the
creature running the script is doing actions, though. For example, if you check to see
an enemy, then to all the commands necessary to cast a spell, during the time it takes
to process the spellcasting commands your script won't be called at all. A bad path found
through the pathfinding engine can result in your script not getting called again for quite
some time.
------
Attack
------
-Gimble
Q: What is the relationship between this interval and the time parameter provided to
AttackReevaluate()
-Briareus
A: Times used in SetGlobalTimer() and all the timers are in seconds. Intervals used in
functions like AttackReevaluate() are in AI updates. So 15 AI updates would be 1 second.
-Gimble
Q: What are the differences between Attack(), AttackOneRound(), and AttackReevaluate(),
and is any of the above preferred for any reason?
-Briareus
A: Attack() will make the creature attack the target and never stop until the target is dead.
AttackOneRound() will cause the creature to attack for one round. This one should be used
normally as it doesn't finish until the creature gets in enough attacks for one round.
AttackReevaluate allows you to specify how long (in AI Updates) to attack the target before
the action finishes. Which one you use depends on what you're trying to do. I'd recommend
using AttackOneRound() by default. I don't believe any of the scripts that shipped with the
game use AttackReevaluate().
Your scripts can get temporarily "stuck" in the Attack() and AttackOneRound() action if the
creature can't path to the target (either no path or the target continues to move away).
AttackReevaluate() gets around that because it WILL finish when the time is up, but your
creature running the script might lose out on some of the attacks it has for that round and
will have to wait for the next round.
-Gimble
Q: If I understand your prior response, the following are true:
1) The execution time of AttackOneRound() and AttackReevaluate(,105)
are the same (7 seconds), assuming no special circumstances (movements required, etc.)
-Briareus
A: Sure, as long as you'll never have to move during the attack, then yes.
-Gimble
2) Under special circumstances, AttackReevaluate() will 'time out' at the appropriate time,
but may lose any attacks not successfully completed before the timeout.
-Briareus
A: Correct.
-Gimble
3) Conversely, AttackOneRound() will guarantee one round of attacks, but will not guarantee
a completion time of 7 seconds.
A: Yup.
------
Round
------
-Gimble
Q: IIRC you said in the earlier thread that one round is 7 seconds ( thus, 15*7 = 105 ticks ).
Is that correct (just to be safe)?
-Briareus
A: Looks correct to me.
------------------
IncrementInternal
------------------
-Gimble
Q2: From the code snippets examined, I deduced that IncrementInternal has the following
arguments:
Object that has the Internal variable to be incremented
SecretFormulaX, set to zero in all examples I saw. I presume that this might be an index
offset ( if objects have multiple internal variables that can be fiddled with )
Number to increment (if positive) or decrement(if negative) the internal variable by.
-Briareus
A: You're assumption about the IncrementInternal() is correct.
-Briareus
Think of Internals as Global("var","LOCALS"), except you can specify which creature to set
the internal for. Each creature has 5 internals, numbered 0 - 4. Only player scripts set
internals, so feel free to use whatever ones you want, just be warned that if you set an
internal on a creature and save the game, that value is saved with the savegame file
*************************************************************************
Ex. - Use of IncrementInternal and Shouts
Code:
IF
ActionListEmpty()
!TimerActive(94)
AttackedBy([EVILCUTOFF],0)
THEN
RESPOSE #100
StartTimer(94,14)
Shout(5)
IncrementInternal(LastAttackerOf(Myself),0,10)
Continue()
END
-Briareus
Ex. should do the following: The shout will happen once, each time the character
is attacked by a red-circle, but not more often than once every 14 seconds.
Note:
-Briareus
What ID you want to use is up to you. I'd recommend using IDs above 100 to avoid messing
up any shipped scripts.
**************************************************************************
-----------------
StateCheck()
----------------
STATE.ids
-Caliban72
Q2: How can I tell if my character has any remaining Mirror Images left?
-Briareus
For Mirror Image, I'd use StateCheck(Object, STATE_MIRRORIMAGE).
--------------------
CheckSpellState()
--------------------
SPLSTATE.ids
-Caliban72
Q1: How do I tell if my character currently has Mage Armor (as an example) active?
Q3: How do I tell if a character has Barkskin in effect?
-Briareus
A1&3: For Mage Armor and Barkskin, use CheckSpellState(Object, ARMOR)
and CheckSpellState(Object, BARKSKIN), respectively.
-Briareus
BTW, why do you want to check for these things? If you're using the new spell casting
functions (MarkSpellAndObject, etc.), you don't need to explicitly check for spell states
as the engine does it by default (unless you set the flag to override that).
-------------------------------------
Check for Lowest HP Monster in Sight
------------------------------------
-Gimble
Q: Is there any convenient way to look for the lowest-HP monster within sight, without tons
of scripting ( since I can't seem to compare creature A's HP to creature B's HP, it makes
for a whole lot of static comparisons )?
-Briareus
A: Nope. You'll have to do a ton of IF checks. Sorry.
-------------------------------------
LastMarkedObject
------------------------------------
-Briareus
LastMarkedObject is set automagically via certain script triggers and functions.
See(), Entered(), Clicked(), LOS(), and others will set LastMarkedObject if the trigger
evaluates to TRUE.
-------------------
PartyRested()
------------------
-Gimble
Q: Does PartyRested() still work? Perhaps he could use that to reset any triggers he might
need (used heavily in BG2 scripts that I've browsed).
-Briareus
Doubtful as none of the shipping scripts use it that I know of. You can always try it out
and see if it works.
-Gimble
Oh, and just to add that PartyRested() doesn't seem to be in the new IDS files,
so it may be gone.
-Gimble
Timers and Resting
After doing some experimentation, I've come up with some - umm - unusual numbers.
By math,
60 seconds / min * 60 mins / hour * 8 hours / rest = 28,200
So, you would expect that SetGlobalTimer("MyTimer","LOCALS",28200) would kick off once
every 8 hours (presumably after each rest).
Not so.
After browsing around a bit, in GTIMES.IDS, we learn that 28,200 is actually the time
value for four complete days, and experimentation seems to bear this out.
So, if you want a timer that will go off every eight hours ( presumably after each rest ),
it appears that you really need to use SetGlobalTimer("MyTimer","LOCALS",2400), which is:
ONE_DAY (7200) / 3 = 2400
This seems kind of odd, since a timer decrements by one per second, but perhaps those
are "real" seconds. And, experimentation seems to indicate that 2400 is the appropriate
value to set the timer to be.
If that's the case (and I'm just musing now), it really seems that one real time second
is really 12 in-game seconds ( 28800 / 2400 ).
As a result, a combat round is really 12*7=84 seconds long (in game terms), leading us
closer to the original "one round = one minute" thought of 2nd Edition.
Yes, apparently I'm a geek. But I couldn't figure out why my timers weren't resetting after
resting for 8 hours, and started experimenting to find out why.
------------------
See()
------------------
-Gimble
Q: What's the second argment ( typically zero ) that is passed to See()?
-Briareus
0 = don't see dead creatures. 1 = see dead creatures. In every previous IE game, See()
would return a match even if the creature was dead. This resulted in having to do a !Dead()
check in almost every IF block. I don't know why you'd want to see dead creatures, but it's
there if you do.
------------------------------------------------------------------
Things that might make you not want to cast a spell or unable to
------------------------------------------------------------------
-Caliban72
Q: What are your uncastable conditions? I posted mine above but I totally forgot
about Insect Swarm (Plague, whatever).
-Gimble
After stealing from you (you mentioned some I hadn't considered), here's my current list:
StateCheck(Myself, STATE_INVISIBLE)
StateCheck(Myself, STATE_POISONED)
CheckSpellState(Myself, BLINK)
CheckSpellState(Myself, MISCAST_MAGIC)
CheckSpellState(Myself, FEEBLEMIND)
CheckSpellState(Myself, CANNOT_CAST)
CheckStatGT(Myself,25,SPELLFAILUREMAGE)
CheckStatGT(Myself,25,SPELLFAILUREPRIEST)
I believe that Insect Swarm sets the SPELLFAILUREMAGE value high, but I cannot guarantee it.
My personal preferences (not to cast while Invisible... as I normally only use regular
Invis when I don't want to be seen) also make a difference.
--------------------------------------------------------------
How can you set/unset feats like Expertise and Power Attack?
--------------------------------------------------------------
-Gimble
How can you set/unset feats like Expertise and Power Attack?
-Briareus
You can't. While there is a innate spell listed for power attack and expertise,
casting those will only set PA and Ex to 1. You'd have to make new spell files that
set the appropriate effect. I'm not sure if there are any fan-based spell editors out
there or not, or if those would even work with IwD2's spell format.
-------------------------------------------------------------
Druid Shapeshifting
------------------------------------------------------------
-caliban72
Is druid shapeshifting scriptable? I have been unsuccessful in my atempts at
HaveSpell(INNATE_...) and also using MSAO.
-Briareus
You won't be able to use HaveSpell() or MSAO with any player innate abilities.
Those are handled completely differently in the engine now, though there are
some INNATE_* spells for those abilities. You'll have to set your own day timers
and usage counters if you want that scripted.
-Caliban72
Chad,
On Druid shapeshifting and scripting it. I can set the shift counts and shapes possible by using
CheckStatXX(Myself,x,CLASSLEVELDRUID)
But what about the feat based shapes? Beetle, Panther, & Shambler?
How do I detect whether my char has it available or not? Determining this would enable secondary choices for shapes based on the situation.
------------------------------------------------------------
Item Scripting
-----------------------------------------------------------
-caliban72
Wandering Sky is a 2H Sword +1 available for purchase in Targos at the start of the game.
It has the ability to cast the spell "Doom" once per day. How do I script the casting of
this ability?
-Briareus
With the UseItem() function. Here's a script I wrote to use an item once per day. IIRC, the item is the ring of Mage Armor.
************************************************************
Code:
IF
!TimerActive( 1 )
IsSpellTargetValid( Myself, WIZARD_ARMOR, 0 )
THEN
RESPONSE #1
UseItem( "00Ring78", Myself )
StartTimer( 1, 20000 )
Wait( 7 )
END
Yeah, it doesn't use any of those fancy WaitAnimation() calls, but then it doesn't have to.
This is the entire script that I used for my monk while playing the game.
*********************************************************
-Caliban72
Here is my working code for iron rations:
*******************************************
IF
ActionListEmpty()
Exists([EVILCUTOFF])
HPPercentLT(Myself,100)
HasItem("00GENIR",Myself) // Iron Rations
THEN
RESPONSE #100
FloatMessage(Myself,25855)
UseItem("00GENIR",Myself)
END
****************************************
----------------------------------------
Targeting Example
---------------------------------------
-Caliban72
My targeting is pretty simplistic, even more than the supplied scripts. All my characters
hammer the spell casters first then the two front liners go after the closest, and everyone
else goes after the furthest.
I had inconsistant results with getting FarthestEnemyOf() to work for me and have defaulted
back to:
******************************************
Code:
IF
Global("TW_Caster","LOCALS",0) // have I already targeted a spell caster?
// FarthestEnemyOf doesn't seem to work
// See(FarthestEnemyOf(Myself),0)
Or(10)
See(TenthNearestEnemyOf(Myself),0)
See(NinthNearestEnemyOf(Myself),0)
See(EigthNearestEnemyOf(Myself),0)
See(SeventhNearestEnemyOf(Myself),0)
See(SixthNearestEnemyOf(Myself),0)
See(FifthNearestEnemyOf(Myself),0)
See(FourthNearestEnemyOf(Myself),0)
See(ThirdNearestEnemyOf(Myself),0)
See(SecondNearestEnemyOf(Myself),0)
See(NearestEnemyOf(Myself),0)
THEN
RESPONSE #1
SetMyTarget( LastMarkedObject )
FloatMessage(MyTarget,1077) // Ranger
Continue()
END
This works fairly well for me.
****************************************
----------------------------------
Threat Level Assessment Example
---------------------------------
-Caliban72
Thanks! I'll check it out. I'm scripting spells for Wizard, Bard, Druid, Cleric and Paladin
right now and I just added level 4 yesterday. With this much going on at once I find the
threat levels handy (adding in a little coordinated casting for trolls & summons). Mine is
also based primarily on the number of baddies, taking a sugestion from Bri I modeled it like
this:
// * THREAT-LEVEL 1: LEAST DANGER (Here there be monsters!)
// * ============================
IF
ActionListEmpty()
See([ENEMY],0) // if enemies are visible
!TimerActive(111)
THEN
RESPONSE #100
StartTimer(111, 7)
SetGlobal("TW_Threat","LOCALS",1) // the threat is at least level 1
IncrementGlobal("TW_Round","LOCALS",1)
Continue()
END
////////////////////////////////////////////////////////////////////////////////
// * THREAT-LEVEL 2: LOW DANGER
////////////////////////////////////////////////////////////////////////////////
IF
ActionListEmpty()
NumCreaturesGTMyLevel([ENEMY],0)
THEN
RESPONSE #100
SetGlobal("TW_Threat","LOCALS",2) // the threat is at least level 2
Continue()
END
IF
ActionListEmpty()
Or(5)
Exists([ENEMY.0.BUGBEAR.0])
Exists([ENEMY.0.SPIDER.0])
Exists([ENEMY.0.ETTERCAP.0])
Exists([ENEMY.0.DOPPLEGANGER.0])
Exists([ENEMY.0.DRIDER.0])
THEN
RESPONSE #100
SetGlobal("TW_Threat","LOCALS",2) // the threat is at least level 2
Continue()
END
////////////////////////////////////////////////////////////////////////////////
// * THREAT-LEVEL 3: MEDIUM DANGER
////////////////////////////////////////////////////////////////////////////////
IF
ActionListEmpty()
Or(2)
NumCreaturesGTMyLevel([ENEMY],0)
NumCreaturesGTMyLevel([ENEMY],1)
THEN
RESPONSE #100
SetGlobal("TW_Threat","LOCALS",3) // the threat is at least level 3
Continue()
END
IF
ActionListEmpty()
Or(6)
Exists([ENEMY.0.GIANT.0])
Exists([ENEMY.0.BASILISK.0])
Exists([ENEMY.0.GOLEM.0])
Exists([ENEMY.0.UMBERHULK.0])
Exists([ENEMY.0.WYVERN.0])
Exists([ENEMY.0.DJINNI.0])
THEN
RESPONSE #100
SetGlobal("TW_Threat","LOCALS",3) // the threat is at least level 3
Continue()
END
////////////////////////////////////////////////////////////////////////////////
// * THREAT-LEVEL 4: EXTREME DANGER
////////////////////////////////////////////////////////////////////////////////
IF
ActionListEmpty()
NumCreaturesGTMyLevel([ENEMY],0)
NumCreaturesGTMyLevel([ENEMY],1)
THEN
RESPONSE #100
SetGlobal("TW_Threat","LOCALS",4) // the threat is at least level 4
Continue()
END
IF
ActionListEmpty()
Or(7)
Exists([ENEMY.0.TANARI.0])
Exists([ENEMY.0.SALAMANDER.0])
Exists([ENEMY.0.MINDFLAYER.0])
Exists([ENEMY.0.DRAGON.0])
Exists([ENEMY.0.HALF_DRAGON.0])
Exists([ENEMY.0.CHIMERA.0])
Exists([ENEMY.0.BEHOLDER.0])
THEN
RESPONSE #100
SetGlobal("TW_Threat","LOCALS",4) // the threat is at least level 4
Continue()
END
Basing this evaluation on Character level alows it to flow as the characters grows stronger.
I have a debug script that floats the threat level every couple of rounds and you can see it
fall as hordes are thinned. No spells are cast during mop up, when the threat level drops to
one. It seems like at low levels with this system that THREAT=4 a LOT. Which makes sense,
since death is pretty close by for low level characters and even a few goblins can be very
dangerous. My party (F5/Rog3, P8, C8(Mask), D8, B8, W7(trans)/S1) is just outside the ice
temple in chapter 2.
------------------------
HasItemInSlot()
-----------------------
-Briareus
Looks like HasItemInSlot() doesn't work or was broken in one of the updates to 3E.
I'm not suprised as it's probably a function no one used.
---------------------------------
SetBestWeapon(O:Object*,I:Range*)
---------------------------------
-Briareus
calls EquipMostDamagingMelee() and EquipRanged() based on the range to the object.
If the object is within the range, then EquipMostDamagingMelee() is called,
otherwise EquipRanged() is called.
--------------------------------
Global & Local Variables
--------------------------------
-Caliban72
When a game is loaded (along with the assigned player scripts), what are the initial
states of all Globals referenced?
Is there a difference in the initialization based on scope ("GLOBAL" vs. "LOCALS")?
Internals are saved/ loaded with the savegame state, correct? What were the official
uses of the Internals for Player scripts that the Black Isle scripts use?
-Briareus
All globals and internals are saved with the savegame. When you reload the game,
they're the same as they were when you saved the game. There are no "temp" globals
that are not saved with the savegame.
-----------------------------
ChangeAIScript()
------------------------------
-Gimble
ChangeAIScript(S:ScriptFile*, I:Level*Scrlev)
scrlev.ids
OVERRIDE
SPECIAL_1
TEAM
SPECIAL_2
COMBAT
SPECIAL_3
MOVEMENT
Which levels can be safely overridden by user scripts?
-Briareus
NEVER use this on NPCs in the game. You will break the game.
However, for Players 1 through 6, you can use this on all the slots except Override.
The Override slot is used by the Charm-type spells.
-Gimble
Which level is the AI script at by default?
-Briareus
IIRC, the script you assign to a PC is in Special3
-Extremist
1. Which script levels are disabled if character is under stun effect?
Or in other words, are they all disabled if someone is stunned?
-Briareus
None, the engine just doesn't bother calling any scripts while stunned.
-Extremist
2. Which level contains troll's "if almost dead -> fall down" script?
-Briareus
Not sure, depends on the troll, but usually it's Special1.
----------------
PREFAB.IDS
-----------------
Nearest enemy to my leader=NearestEnemyOf(LeaderOf(Myself))
Nearest enemy to who I am protecting=NearestEnemyOf(ProtectedBy(Myself))
Nearest enemy to me=NearestEnemyOf(Myself)
Last enemy targeted by my leader=LastTargetedBy(LeaderOf(Myself))
Last enemy targeted by myself=LastTargetedBy(Myself)
Last enemy to attack my protector=LastAttackerOf(ProtectorOf(Myself))
Last enemy to attack my leader=LastAttackerOf(LeaderOf(Myself))
Last enemy to attack who I am protecting=LastAttackerOf(ProtectedBy(Myself))
Last enemy to attack me=LastAttackerOf(Myself)
Protector of the enemy leader=ProtectorOf(LeaderOf(NearestEnemyOf(Myself)))
Protector of my leader=ProtectorOf(LeaderOf(Myself))
Person I am protecting=ProtectedBy(Myself)
Person my leader is protecting=ProtectedBy(LeaderOf(Myself))
Least damaged of enemy group=LeastDamagedOf(GroupOf(NearestEnemyOf(Myself)))
Least damaged of my group=LeastDamagedOf(GroupOf(Myself))
Most damaged of enemy group=MostDamagedOf(GroupOf(NearestEnemyOf(Myself)))
Most damaged of my group=MostDamagedOf(GroupOf(Myself))
Strongest of enemy group=StrongestOf(GroupOf(NearestEnemyOf(Myself)))
Strongest of my group=StrongestOf(GroupOf(Myself))
Weakest of enemy group=WeakestOf(GroupOf(NearestEnemyOf(Myself)))
Weakest of my group=WeakestOf(GroupOf(Myself))
Enemy leader=LeaderOf(NearestEnemyOf(Myself))
My protector=ProtectorOf(Myself)
My leader=LeaderOf(Myself)
Myself=Myself
-------------------=
Nothing=Nothing()
Myself=Myself()
LeaderOf=LeaderOf()
GroupOf=GroupOf()
WeakestOf=WeakestOf()
StrongestOf=StrongestOf()
MostDamagedOf=MostDamagedOf()
LeastDamagedOf=LeastDamagedOf()
ProtectedBy=ProtectedBy()
ProtectorOf=ProtectorOf()
LastAttackerOf=LastAttackerOf()
LastTargetedBy=LastTargetedBy()
NearestEnemyOf=NearestEnemyOf()
LastCommandedBy=LastCommandedBy()
Nearest=Nearest()
------------------------
Internal()
-----------------------
-Caliban72
[Nicholas, Chad] As for the Internal, each creature has 5 internal variables (0 - 4).
They act just like IncrementGlobal "MODAL_STATE","LOCALS",2) with two exceptions:
1) They're parsed a lot faster in the engine and
2) you can set the value of another creature's internal.
Area scripts and poly scripts don't have internals.
[Nicholas, Chad] Internals only go from 0 through 4.
That makes 5 internals for each creature. If you tried to set internal 5, you'd actually
be setting internal 0. The only internal used by any of the scripts is 0, and those were
used by the player scripts I made. The only reason to avoid using 0 is if you expect to have
your scripts work with the ones that shipped with the game.
-----------------------------------
Cone spells vs. Blast Area spells
----------------------------------
-Caliban72
How can one determine that there are no allied creatures (party members or summons)
in the path of spells like Color Spray, Aganazzar's Scorcher, Shout, etc. ?
I've used code like this for area effect spells, and it works reasonably well:
//-----------------------------------------------------------------------------
// ** Check if I can use an area spell such as, fireball or stinking cloud
IF
ActionListEmpty()
NumCreatureGT([ENEMY],2)
Range(Player1,12, LESS_THAN) // see if everyone is grouped close by
Range(Player2,12, LESS_THAN)
Range(Player3,12, LESS_THAN)
Range(Player4,12, LESS_THAN)
Range(Player5,12, LESS_THAN)
Range(Player6,12, LESS_THAN)
Range(MyTarget,25, GREATER_THAN) // check that target is not to0 close
THEN
RESPONSE #1
SetGlobal("TW_AE","LOCALS",1)
Continue()
END
But this has no directional or line of sight information about where I'm about to spew
the magic.
Any ideas?
-Grog
Here's what I use; doesn't work perfect, but it seems to help.
--------------------------------------------------------------------------------
Code:
//Burning Hands
IF
See(LastAttackerOf(Myself))
HaveSpell(WIZARD_BURNING_HANDS)
Range(LastAttackerOf(Myself),5)
CheckStatLT(LastAttackerOf(Myself),26,RESISTFIRE)
!LOS(Player1,3)
!LOS(Player2,3)
!LOS(Player3,3)
!LOS(Player4,3)
!LOS(Player5,3)
!LOS(Player6,3)
THEN
RESPONSE #100
FaceObject(LastAttackerOf(Myself))
Spell(LastAttackerOf(Myself),WIZARD_BURNING_HANDS)
END
// Aganazzar's Scorcher
IF
See(NearestEnemyOf(Myself))
HaveSpell(WIZARD_AGANNAZAR_SCORCHER)
CheckStatLT(NearestEnemyOf(Myself),26,RESISTFIRE)
!LOS([Player1],7)
!LOS([Player2],7)
!LOS([Player3],7)
!LOS([Player4],7)
!LOS([Player5],7)
!LOS([Player6],7)
THEN
RESPONSE #100
FaceObject(NearestEnemyOf(Myself))
Spell(NearestEnemyOf(Myself),WIZARD_AGANNAZAR_SCOR
CHER)
END
--------------------------------------------------------------------------------
For some of the cone effect spells you may want to add a NumCreatureGT([EVILCUTOFF],X)
check in your code block
-----------
Slot.IDS
-----------
-Gimble
The Near Infinity editor reports that SLOTS.IDS has the following values:
SLOT_WEAPON0 35
SLOT_WEAPON1 36
SLOT_WEAPON7 42
However, script testing seems to indicate the values should be:
SLOT_WEAPON0 43
SLOT_WEAPON1 44
SLOT_WEAPON7 50
... as the slots between 35 and 42 are primarily inventory slots added to the interface.
I mention this to inform other scripters of the problem, and hopefully that BIS will fix the IDS file before a later patch.
A more verbose, but more accurate description for scripters:
--------------------------------------------------------------------------------
Code:
SLOT_WEAPON1_PRIMARY 43
SLOT_WEAPON1_OFFHAND 44
SLOT_WEAPON2_PRIMARY 45
SLOT_WEAPON2_OFFHAND 46
SLOT_WEAPON3_PRIMARY 47
SLOT_WEAPON3_OFFHAND 48
SLOT_WEAPON4_PRIMARY 49
SLOT_WEAPON4_OFFHAND 50
--------------------------------------------------------------------------------
------------------------------
NumCreaturesGTMyLevel( X, Y )
-----------------------------
-Gimble
Q:What's the second argument to NumCreaturesGTMyLevel( X, Y )? In other words,
what does argument Y do in the preceding sentence?
-Briareus
Whether or not to do a Hit Dice (HD) check. If false, each creature is counted as 1.
If true, each creature's amount of HD is counted towards the total. For example, two goblins
with 2 HD each. You're level 3. If the 2nd param is false, then the check will fail since
there are only 2 goblins. If the 2nd param is true, then the check is true,
since 2 HD + 2 HD = 4, which is > 3.
-----------
LOS()
----------
Caliban72
LOS has two parameters. The first is the object you're looking for, and the second is how
far as a maximum to look.
So, LOS(NearestEnemyOf(Myself),28) would spot the nearest enemy in sight range, visible
or not.
LOS(NearestEnemyOf(Myself),4) would spot the nearest enemy roughly in melee range.
And LOS(NearestEnemyOf(Myself),0) would spot the nearest enemy who was me (in effect, nobody).
Another way you can find hidden enemies is Detect(NearestEnemyOf(Myself)),
but Detect only works within a fixed range ( probably 28 ) AFAIK.
-------------------------------------
HPLost(), HPLostGT() and HPLostLT()
-------------------------------------
-Deadmeat
I've started to experiment with potion drinking and healing, and found it useful in reducing
wastage.
-----------------------------------------
Global vs. Internal when using Continue()
------------------------------------------
-Gimble
Trivia: SetGlobal("LOCALS") vs. SetInternal(Myself)
It has been discussed before that the *Internal() functions are faster than the local
variable functions because no string parsing is involved. And, usually, faster is better.
However, there is a difference between the two that is quite significant if
you use Continue().
Code:
IF
!Global("LOCALS","3",1)
THEN
RESPONSE #1
SetGlobal("LOCALS","3",1)
Continue()
END
IF
!Internal(Myself,3,1)
THEN
RESPONSE #1
SetInternal(Myself,3,1)
Continue()
END
IF
Global("LOCALS","3",1)
THEN
RESPONSE #1
FloatMessage(Myself,24088) // 1
Wait(3)
Continue()
END
IF
Internal(Myself,3,1)
THEN
RESPONSE #1
FloatMessage(Myself,24087) // 2
Wait(3)
Continue()
END
IF
True()
THEN
RESPONSE #1
SetGlobal("LOCALS","3",0)
SetInternal(Myself,3,0)
END
In the above snippet, I set the value of "3" (either internal 3 or local variable "3",
depending on which piece I'm testing ), and then check it later on in the same script run,
and float a message above my head if the condition is true.
*Global("LOCALS") works fine with this method, so I deduce that as the script is running
the locals are immediately updated during the script run (so that code further down during
the same pass will see the new value ).
*Internal(Myself), however, does not. As a result, I deduce that SetInternal() is queued up
and applied after the script run (and thus before the next pass to this script).
So, the result of this script is a recurring "1" above the character's head, but no
appearance of "2" above the character's head.
This behavior is an important one to note if you use Continue() as it can butcher your
expected code logic. Trust me.
-------------
XEquipItem()
-------------
-Japheth
If you need to equip stuff in IWDII, you can use the handy XEquipItem(S:Item*,O:Object*,I:Slot*Slots,I:EquipUnEquip*BOOLEAN) action.
Example:
XEquipItem("someitem",Myself,SLOT_WEAPON,TRUE) would equip the someitem weapon in the first weapon slot.
------------------------------
MostDamagedOf() - Object.IDS
------------------------------
-Cirerrek
My testing has shown this function to only be useful for detecting party members as
using it in a target segement with !InParty checks resulted in my party members attacking
each other.
Note: If no one is injured, it will apparently go after anything with less hitpoints
-----------------------------
SetVisualRange() - Action.IDS
----------------------------
-Cirerrek
Sets the visual range for all creatures (active? unactive?) in an area.
Not 100% sure it sets it for the party or not. I was trying to remove the lag from
my Party AI script and thought the viewing range might be causing the lag, but setting it
didn't increase the reaction time of the script. It just caused virtually all the
creatures in the area to come swarming out of the woodworks where they had been stationary
before.
-----------------------
THIEF_ALL - Object.IDS
-----------------------
-Cirerrek
THIEF_ALL apparently includes Monks