Jump to content
Xyx

Large number of BG2 script findings

Recommended Posts

Here is my personal documentation, most of which is not covered in the IESDP:

 

// ----------------------------------------------------------------------------
// Documentation.
//
// Reliability of script execution:
// - Script may occasionally execute when !ActionListEmpty().
//   - Confirmed to happen during long move orders.
// - Attack() can get hung up.
//   - Not confirmed whether the script even runs at all until the target is
//	 dead.
// - AttackReevaluate() seems equivalent to Attack().
//   - The duration seems to be ignored.
// - AttackOneRound() reports ActionListEmpty() every run.
//
// ActionListEmpty():
// - Not reported during long move orders.
// - Not reported while looting containers.
//
// LastSeenBy():
// - Set by See() and Detect(), not by Exists() or Range().
//
// Invisible creatures:
// - See(), Detect(), Exists() and Range() all detect visible creatures in
//   sight radius only, with the following exceptions:
//   - Detect() will detect an invisible Myself, Player1 or [EVILCUTOFF], but
//	 not NearestEnemyOf().
//   - Exists() and Range() will detect an invisible Myself or Player1, and
//	 will detect them out of sight radius.  They will still not detect
//	 invisible enemies or visible enemies out of sight radius.
// - Note that even See(Myself) only works if you're not invisible.
//
// Communication:
// - Shout() carries throughout the area.
// - Help() carries slightly further than sight radius, probably range 40.
// - Help() sticks around for 7-12 seconds.
//
// Continue():
// - Use of Continue() anywhere will cause subsequent empty RESPONSE blocks to
//   be treated as if they, too, had Continue().
//   - Use NoAction() to stop.
// - Continue() appears to cause all instances of LastSeenBy() to be set to the
//   last value set in the current script run.
//
// NoAction():
// - NoAction() is, apparently, not actually equivalent to no action.  See
//   Continue():
// - NoAction() will not cancel modal actions.  Use ClearActions() for that.
//   Use PlayerDialogue(Myself) to un-hide immediately.
//
// Update speed:
// - LastSeenBy() is set in the current script run, but if Continue() is used,
//   it will always evaluate to the last value that gets set in the run.
// - The values of SetGlobal() and SetGlobalTimer() are set in between script
//   runs.
// - Help() is set in between script runs.
// - Heard() is set in between script runs?
// - Wait() and SmallWait() do not allow the script to update in the meantime.
//
// DRUID_ALL:
// - It is not worth checking for enemy Druids.  There are only a few of them,
//   and those come in groups.
//
// Range():
// - You can't get closer than range 8 to huge creatures like dragons.
// - Range 4 is melee range.
//   - Anyone using a ranged weapon at this range suffers -8 to hit.
//   - Anyone attacking someone using a ranged weapon in melee gains +4 to hit
//	 and damage.
//
// Melf's Minute Meteors:
// - These count as ranged weapons, so they suffer -8 to hit at point blank and
//   hand out +4 to hit and damage to opponents in melee.  They cannot be put
//   away.
// - HasItem("MELFMET",Myself) works.  HasItemEquiped(), HasItemEquipedReal()
//   and HasItemSlot() (whichever slot) do not work (for Melf's Minute Meteors, at least.)
//
// SelectWeaponAbility(SLOT_WEAPON0,1):
// - Haven't seen this produce any results.
//
// Controlled undead detection:
//	 See([ALLY.UNDEAD]) // Summoned only.
//	 See([CONTROLLED.UNDEAD]) // Controlled only.
//	 See([CHARMED.UNDEAD]) // Nothing.
//	 See([GOODBUTRED.UNDEAD]) // Nothing.
//	 See([GOODCUTOFF.UNDEAD]) // Controlled and summoned.
//	 See([NOTGOOD.UNDEAD]) // Unturned only.
//	 See([NOTEVIL.UNDEAD]) // Controlled and summoned.
//	 See([EVILCUTOFF.UNDEAD]) // Unturned only.
//	 See([EVILBUTGREEN.UNDEAD]) // Nothing.
//	 See([ENEMY.UNDEAD]) // Unturned only.
//
// Containers (Bag of Holding, Potion Case, etcetera):
// - HasItem() works for stuff inside a container.
// - GiveItem() works for stuff inside a container.
// - UseItem() does NOT work for stuff inside a container.
// - To use an item in a container, give it to yourself first.
//
// OutOfAmmo():
// - Always returns True, even with arrows in all three slots.
//
// InPartySlot():
// - Player1 is in slot 0, Player2 in slot 1, and so on.
// - NOT to be confused with marching order.
//
// Compound states:
// - State values are bit flags and can be added together to check many states
//   in one StateCheck().
// - To calculate the compound state value, simply add them together and make
//   sure the result is decimal, not octal.
// - Compound states allow for !StateCheck(<many different states>) in an OR().
// - The following works:
//	 StateCheck(Myself,4194320) // Untargetable (STATE_INVISIBLE + STATE_IMPROVEDINVISIBILITY)
// - The following (found on the web) does not work:
//   StateCheck(LastSeenBy(),2148540461) //  Compound "disabled" state
//
// Hide()
// - Hiding does not necessarily break long move orders.
//
// EquipItem()
// - Perhaps it would move an item to an appropriate slot, but it will not
//   switch weapons.
//
// Timers:
// - Names cannot be longer than 32 characters.
//
// STATE_PANIC:
// - This state seems to apply only to creatures that are affected by panic
//   inducing magic.  Creatures that fail their Morale check are not in this
//   state.
// ----------------------------------------------------------------------------

 

All of this is the result of actual findings, verified where possible.

 

Hope it helps!

 

EDIT: Removed HasItemSlot() comments and added disclaimer for Melf's Minute Meteors stuff.

Edited by Xyx

Share this post


Link to post
// HasItemSlot():

// - Does this EVER work?

yes it does

part of a hotkey initiated ammo pickup routine I made to use in conjunction with igi's Projectile Retrieval mod.

 

IF
 Global("ab_invfull","LOCALS",0)
 HasItemSlot(Myself,SLOT_MISC3)
 HasItemSlot(Myself,SLOT_MISC4)
 HasItemSlot(Myself,SLOT_MISC5)
 HasItemSlot(Myself,SLOT_MISC6)
 HasItemSlot(Myself,SLOT_MISC7)
 HasItemSlot(Myself,SLOT_MISC8)
 HasItemSlot(Myself,SLOT_MISC9)
 HasItemSlot(Myself,SLOT_MISC10)
 HasItemSlot(Myself,SLOT_MISC11)
 HasItemSlot(Myself,SLOT_MISC12)
 HasItemSlot(Myself,SLOT_MISC13)
 HasItemSlot(Myself,SLOT_MISC14)
 HasItemSlot(Myself,SLOT_MISC15)
 HasItemSlot(Myself,SLOT_MISC16)
 HasItemSlot(Myself,SLOT_MISC17)
 HasItemSlot(Myself,SLOT_MISC18)
THEN
RESPONSE #100
 SetGlobal("ab_invfull","LOCALS",1)
 Continue()
END
// 1 and at least one slot empty change to 0
IF
 Global("ab_invfull","LOCALS",1)
 OR(16)
  !HasItemSlot(Myself,SLOT_MISC3)
  !HasItemSlot(Myself,SLOT_MISC4)
  !HasItemSlot(Myself,SLOT_MISC5)
  !HasItemSlot(Myself,SLOT_MISC6)
  !HasItemSlot(Myself,SLOT_MISC7)
  !HasItemSlot(Myself,SLOT_MISC8)
  !HasItemSlot(Myself,SLOT_MISC9)
  !HasItemSlot(Myself,SLOT_MISC10)
  !HasItemSlot(Myself,SLOT_MISC11)
  !HasItemSlot(Myself,SLOT_MISC12)
  !HasItemSlot(Myself,SLOT_MISC13)
  !HasItemSlot(Myself,SLOT_MISC14)
  !HasItemSlot(Myself,SLOT_MISC15)
  !HasItemSlot(Myself,SLOT_MISC16)
  !HasItemSlot(Myself,SLOT_MISC17)
  !HasItemSlot(Myself,SLOT_MISC18)
THEN
RESPONSE #100
 SetGlobal("ab_invfull","LOCALS",0)
 Continue()
END

 

Share this post


Link to post

OutOfAmmo is truly buggy, probably BGEE will fix this.

I think the problem is near the attack type check (melee/projectile/bow).

Share this post


Link to post

LastHitter doesn't seem to work in BG1 - not sure if it does in BG2. Also, I wasn't aware the duration was ignored with AttackReevaluate, otherwise it would get stuck there until the target's dead (like Attack) unless a timer is used. STATE_POISON doesn't work in BG1 either, according to plainab.

Share this post


Link to post

// Melf's Minute Meteors:

// - These count as ranged weapons, so they suffer -8 to hit at point blank and

// hand out +4 to hit and damage to opponents in melee. They cannot be put

// away.

// - HasItem("MELFMET",Myself) works. HasItemEquiped(), HasItemEquipedReal()

// and HasItemSlot() (whichever slot) do not work.

 

this may only NOT work with melf's minute meteors. I have gotten HasItemEquiped() to function with other items. However in the same scenario HasItemEquipedReal() did not work. Fortunately for me HasItemEquiped() is sufficient

Share this post


Link to post
Fortunately for me HasItemEquiped() is sufficient
Outside of BG1 I take it, because you don't even have HasItemEquiped there (and thus need to use HasItem anyhow).

Share this post


Link to post
Fortunately for me HasItemEquiped() is sufficient
Outside of BG1 I take it, because you don't even have HasItemEquiped there (and thus need to use HasItem anyhow).

yes in BGT to get short bow prof with igi's Learn Thru Use mod

Share this post


Link to post

I think Xyx meant it doesn't work for the magical item slot (where Melf's etc.-type items are created). It probably works for most other slots.

Share this post


Link to post

I think Xyx meant it doesn't work for the magical item slot (where Melf's etc.-type items are created). It probably works for most other slots.

I am sure that is what he meant as it was tagged under Melf's Meteor. However, I didn't want someone coming along reading his notes and thinking that none of them work at all. So we qualify the statement by showing that HasItemEquiped() can work in other situations.

Share this post


Link to post

I doubt AttackReevaluate is buggy.

 

In-game testing indicates that it is.

 

Specifically, opponents often get stuck in an attack loop (even with a very small script) and don't carry out valid spellcasting blocks placed above the AttackReevaluate() block.

Share this post


Link to post
// HasItemSlot():

// - Does this EVER work?

yes it does

Indeed, my mistake. I assumed it was HasItemSlot("MELFMET",SLOT_MISC0) instead of HasItemSlot(Myself,SLOT_MISC0), so naturally I couldn't get it to produce any results. I'll edit the original post.

 

I doubt AttackReevaluate is buggy.

I ran a few more tests and now even AttackOneRound() seems buggy at times. Specifically, consider the following script:

 

// Cure Light Wounds
IF
ActionListEmpty()
// Can cast?
!GlobalTimerNotExpired("XyxAlacrityTimer","LOCALS")
THEN
RESPONSE #100
	DisplayStringHead(Myself,6620) // "Cure Light Wounds"
	SetGlobalTimer("XyxAlacrityTimer","LOCALS",6)
	SpellNoDec(Myself,CLERIC_CURE_LIGHT_WOUNDS)
END

IF
ActionListEmpty()
See(NearestEnemyOf())
THEN
RESPONSE #100
	DisplayStringHead(Myself,4666) // "Attack"
	Attack(NearestEnemyOf())
END

It's supposed to cast Cure Light Wounds every six seconds and attack in between. As it is, it'll cast Cure Light Wounds exactly once and then get hung up on attacking. It may even switch to another enemy if the original target dies without ever casting Cure Light Wounds again.

 

However, remove the ActionListEmpty() triggers or change NearestEnemyOf() to Player1 and suddenly it casts Cure Light Wounds every round like clockwork.

Share this post


Link to post

But you have Attack in that second block, not AttackOneRound...

 

There was a recent post about issues with Attack* commands here with some testing. I've just been using a timer with Attack rather than AttackOneRound or AttackReevaluate... seems to work better. Also, here Avenger suggested "AttackReevaluate(target, 150) is the same as Attack." That would suggest that Attack should reevaluate roughly every 2 rounds, but I don't remember seeing that.

Share this post


Link to post

But you have Attack in that second block, not AttackOneRound...

Ah, yes, a copy/paste error. I tried both. sorry for the confusion.

 

Attack() may still be interrupted. It's just more likely to get stuck.

 

I could find no difference between Attack() and AttackReevaluate(somevalue), but that is no proof that there is no difference. The problem with these things is that everyone seems to have different results because of varying circumstances. In my experience, whether or not a script gets stuck seems to depend entirely on the script and its circumstances, not on random chance.

Share this post


Link to post

NoAction really doesn't stop modal actions (this is likely my fault).

But, DisplayStringHead does!

This is why i reported it falsely, i always use displaystringhead for debugging (hotkeys are not reliable enough).

 

AttackReevaluate works.

Try this script:

IF
 HotKey(B)
THEN
 RESPONSE #100
   SetGlobal("attacktype","global",1)
END

IF
 HotKey(D)
THEN
 RESPONSE #100
   SetGlobal("attacktype","global",2)
END

IF
 HotKey(A)
THEN
 RESPONSE #100
   SetGlobal("attacktype","global",0)
   DisplayStringHead(Myself,1)  // ~No, I'm sorry, none of them sound familiar.~
END

IF
 Global("attacktype","global",1)
 See([PC])
THEN
 RESPONSE #100
   AttackReevaluate(LastSeenBy(Myself),1)
END

IF
 Global("attacktype","global",2)
 See([PC])
THEN
 RESPONSE #100
   Attack(LastSeenBy(Myself))
END

 

You will find that you can easily ditch your attacker (by another nearer pc), if it is using AttackReevaluate.

Edited by Avenger

Share this post


Link to post
Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • Create New...