Author Topic: Animal Companion AI Scripts  (Read 1276 times)

Legacy_WhiZard

  • Hero Member
  • *****
  • Posts: 2149
  • Karma: +0/-0
Animal Companion AI Scripts
« Reply #30 on: October 30, 2013, 08:46:22 pm »


               Have you tried this with a summon?  PCs do not have onDamaged scripting, so if this occurs with a summon as well, then the issue is not your animal companion AI.

If the error occured from an effect overload the display would be "Stack Overflow", not "Too many instructions."  So I am not convinced that this fluke is not a build up from stacking too many calls at once.  (A delay command can take longer to carry out if there is competition).
               
               

               
            

Legacy_MagicalMaster

  • Hero Member
  • *****
  • Posts: 2712
  • Karma: +0/-0
Animal Companion AI Scripts
« Reply #31 on: October 30, 2013, 09:42:49 pm »


               

WhiZard wrote...

If the error occured from an effect overload the display would be "StackOverflow", not "Too many instructions."  So I am not convinced that this fluke is not a build up from stacking too many calls at once.  (A delay command can take longer to carry out if there is competition).


Too many instructions means an infinite loop, right?

http://nwn.wikia.com/wiki/Loop

"Neverwinter Nights
has a safeguard in place to prevent scripts from performing infinite loops, specifically a limit on the number of instructions a single script can execute. When this limit is broken, the game ends the script prematurely and reports "TOO MANY INSTRUCTIONS" to the player(s). There are a few cases where this "TMI" error is reported for other reasons, but usually it indicates the existence of an infinite loop."

What I'm saying is that SOMEHOW that Spit2's GetObjectInShape loop becomes infinite rather than ending normally and reacquires the animal companion an infinite number of times.  Like if you looked at the loop it would be something like...

1.
Player
Animal

2.
Player
Animal

3.
Player
Animal

4.
Player
Animal
Animal
Animal
Animal
Animal
Animal
Animal
Animal
Animal
Animal
Animal
Animal
Animal
TMI cutoff

It should not trigger on the animal more than once per loop.  But sometimes it does -- and I can't figure out WHY.

I mean, at most there are 10 calls a second happening during the fight in terms of stuff I'm doing.

4 from the boss/victim HP updating
1 from Hungerer
2 from existing Acid Clouds
2 from existing Webs
1 from Siphon

That's it.  I have more calls than that per second going at the start of the module in the fight fight (10 from friendly NPCs plus potentially another 10-15 from enemy NPCs).

The problem is the Animal Companion somehow being counted an infinite number of times during the loop.

WhiZard wrote...

Have you tried this with a summon?  PCs do not have onDamaged scripting, so if this occurs with a summon as well, then the issue is not your animal companion AI.


Not yet.  Part of the problem is that it's very hard for me to reproduce it at all.  It only happens rarely and with no real pattern that I can tell except that it occurs with scripts that do a loop to select damage targets.

I can try it with a summon but I'm not even sure when I could be sure that it's only an animal companion problem -- might not have the bug with a summon for a long time or something.

But the OnDamaged scripting is irrelevant anyway -- the question is HOW the OnDamaged script was called an infinite number of times in the first place!
               
               

               


                     Modifié par MagicalMaster, 30 octobre 2013 - 10:22 .
                     
                  


            

Legacy_WhiZard

  • Hero Member
  • *****
  • Posts: 2149
  • Karma: +0/-0
Animal Companion AI Scripts
« Reply #32 on: October 30, 2013, 10:22:52 pm »


               

MagicalMaster wrote...

WhiZard wrote...

If the error occured from an effect overload the display would be "Stack
Overflow", not "Too many instructions."  So I am not convinced that
this fluke is not a build up from stacking too many calls at once.  (A
delay command can take longer to carry out if there is
competition).


Too many instructions means an infinite loop, right?

http://nwn.wikia.com/wiki/Loop

"Neverwinter Nights
has a safeguard in place to prevent scripts from performing infinite
loops, specifically a limit on the number of instructions a single
script can execute. When this limit is broken, the game ends the script
prematurely and reports "TOO MANY INSTRUCTIONS" to the player(s). There
are a few cases where this "TMI" error is reported for other reasons,
but usually it indicates the existence of an infinite loop."


That is a generalization, based on how it normally occurs in simple scripts.  TMI errors are based off of performing an instruction count (this counts instructions in the compiled code and so different NWScript commands are weighed differently).  After a certain number is reached the script and all its dependents (e.g. ExecuteScript() scripts, include file functions) are shut down.  Typically, DelayCommand() will cause a break allowing the count to restart.  However, if the functions get too complicated with too many calls, there can be problems with overlapping.

Is the infinite looping in example 4 above the product of debugging?
               
               

               
            

Legacy_MagicalMaster

  • Hero Member
  • *****
  • Posts: 2712
  • Karma: +0/-0
Animal Companion AI Scripts
« Reply #33 on: October 30, 2013, 10:44:44 pm »


               Yes.  Just went to get a screenshot.  Here's the code with debugging statements:

void Spit2(location lTarget, int nDur, int nDam)
{
    int nAdjust, x;
    AssignCommand(GetFirstPC(), SpeakString("Spit2 at " + IntToString(nDur) + " seconds left"));
    object oTarget = GetFirstObjectInShape(SHAPE_SPHERE, 4.0, lTarget);
    while (GetIsObjectValid(oTarget))
    {
        if (GetIsEnemy(oTarget))
        {   ++x;
            nAdjust = AdjustDamage(nDam, oTarget);

            AssignCommand(oTarget, SpeakString("Took damage from cloud, enemies found: " + IntToString(x)));
            ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDamage(nAdjust), oTarget);
        }
        oTarget = GetNextObjectInShape(SHAPE_SPHERE, 4.0, lTarget);
    }

    if (!GetIsDead(OBJECT_SELF) && nDur > 0)
    {
        nDam = (3*nDam)/2;
        if (nDam > 1000)
        {
            nDam = 1000;
        }

        DelayCommand(1.0, Spit2(lTarget, --nDur, nDam));
    }

}

Here's the image.  You can see how the bear got selected by the loop 11 times (and then died so it didn't give the TMI error) for some reason.  This is why I'm so mystified.

I also had to send him in and out of the loop like 20 times to trigger the error so it's not something that usually occurs -- which makes it even weirder.

'Posted
               
               

               


                     Modifié par MagicalMaster, 30 octobre 2013 - 10:46 .
                     
                  


            

Legacy_WhiZard

  • Hero Member
  • *****
  • Posts: 2149
  • Karma: +0/-0
Animal Companion AI Scripts
« Reply #34 on: October 30, 2013, 11:05:09 pm »


               

MagicalMaster wrote...

Yes.  Just went to get a screenshot.  Here's the code with debugging statements:


Thanks for the verification.  Do you use GetFirstObjectInShape() in your Animal Companion AI?

For example this script will cause a TMI error:


void DoScriptBreak()
{
object oBear = GetFirstObjectInShape(SHAPE_SPHERE, 1.0, GetLocation(OBJECT_SELF));
while(GetIsObjectValid(oBear))
  {
  oBear = GetNextObjectInShape(SHAPE_SPHERE, 1.0, GetLocation(OBJECT_SELF));
  }
}

void main()
{
object oFirst;
object oTarget = GetFirstObjectInShape(SHAPE_SPHERE, 5.0, GetLocation(OBJECT_SELF));
while(GetIsObjectValid(oTarget))
   {
   DoScriptBreak();
   oTarget = GetNextObjectInShape(SHAPE_SPHERE, 5.0, GetLocation(OBJECT_SELF));
   }
SendMessageToPC(OBJECT_SELF, "ScriptIsDone");
}
               
               

               
            

Legacy_MagicalMaster

  • Hero Member
  • *****
  • Posts: 2712
  • Karma: +0/-0
Animal Companion AI Scripts
« Reply #35 on: October 30, 2013, 11:12:09 pm »


               

WhiZard wrote...

Thanks for the verification.  Do you use GetFirstObjectInShape() in your Animal Companion AI?


Nope.  Here is the entirety of the code added to the AI:

    // Don't do anything if we have have been recently commanded
    if (GetLocalInt(OBJECT_SELF, "commandstatus"))
    {
        return;
    }

    AdjustLocalInt(OBJECT_SELF, "test", 1);
    SpeakString("OnAttacked: " + IntToString(GetLocalInt(OBJECT_SELF, "test")));

Obviously the last two lines are debugging.

WhiZard wrote...

For example this script will cause a TMI error:


Now why does it do that?  Shouldn't each loop only run one time if the Animal companion is the only creature in the shape being checked?

Regardless, the problem appears to be the bear getting selected an infinite number of times by the boss script -- which makes me wonder why something like Fireball doesn't have the same bug.  Something fishy here.
               
               

               
            

Legacy_WhiZard

  • Hero Member
  • *****
  • Posts: 2149
  • Karma: +0/-0
Animal Companion AI Scripts
« Reply #36 on: October 30, 2013, 11:31:37 pm »


               

MagicalMaster wrote...
Now why does it do that?  Shouldn't each loop only run one time if the Animal companion is the only creature in the shape being checked?


A GetFirst/GetNext...  loop never ends.  It stores the last valid entry until it is continued.  So if your script progress beyond a loop (often by stopping when the command returns OBJECT_INVALID, although the last valid object is still remembered)  it will then continue from that point when continuing in the same loop type.  In my example, what destroyed the script was an object between 1.0 and 5.0 meters from the PC.  The innerloop kept on resetting the loop to the closest object and then only progressed to 1.0 meters out, letting the outer loop always get the first object beyond 1.0 meters.
               
               

               
            

Legacy_MagicalMaster

  • Hero Member
  • *****
  • Posts: 2712
  • Karma: +0/-0
Animal Companion AI Scripts
« Reply #37 on: October 31, 2013, 05:40:16 am »


               Interesting.

However, I'm not seeing a way that could occur in my situation given that I'm not using any loop inside a loop, let alone two loops of the same type.  Have any idea what could be occurring in my case?
               
               

               
            

Legacy_WhiZard

  • Hero Member
  • *****
  • Posts: 2149
  • Karma: +0/-0
Animal Companion AI Scripts
« Reply #38 on: October 31, 2013, 02:45:10 pm »


               

MagicalMaster wrote...

Interesting.

However, I'm not seeing a way that could occur in my situation given that I'm not using any loop inside a loop, let alone two loops of the same type.  Have any idea what could be occurring in my case?


What I am thinking is that your damage is calling the OnDamagedEvent to be executed before continuing in the script (thus inside the loop).  The default BioWare AI does not use GetFirstObjectInShape() in its AI (an exception is made for a check made for casting the knock spell if the creature knows that spell, but this should only be triggered by command, or the master failure to open a lock, not anything that would appear in a loop).  Instead the BioWare AI uses the GetNearest... commands so as not to leave any loop open (yes, it does cycle through inventory with GetFirst/NextItemInInventory() so this method is not perfect).  If you have any modifications of the standard AI they may not necessarily take open loops into account.
               
               

               


                     Modifié par WhiZard, 31 octobre 2013 - 02:46 .
                     
                  


            

Legacy_WhiZard

  • Hero Member
  • *****
  • Posts: 2149
  • Karma: +0/-0
Animal Companion AI Scripts
« Reply #39 on: October 31, 2013, 03:58:32 pm »


               I have confirmed the OnDamaged behavior.  If you damage a creature within a loop, then their OnDamaged event is also within that loop, and if the damaging loop never finishes, there will be a TMI error on both the OnDamaged event for the creature and on script initiating the damage.
               
               

               


                     Modifié par WhiZard, 31 octobre 2013 - 03:59 .
                     
                  


            

Legacy_MagicalMaster

  • Hero Member
  • *****
  • Posts: 2712
  • Karma: +0/-0
Animal Companion AI Scripts
« Reply #40 on: October 31, 2013, 04:59:17 pm »


               I haven't modified the standard AI in any way besides that "return if commanded" bit at the top.

I'm trying to figure out how what I'm doing is any different than hitting a creature with a Fireball -- why am I not seeing an infinite loop on Fireball at times?
               
               

               
            

Legacy_WhiZard

  • Hero Member
  • *****
  • Posts: 2149
  • Karma: +0/-0
Animal Companion AI Scripts
« Reply #41 on: October 31, 2013, 06:08:56 pm »


               

MagicalMaster wrote...

I haven't modified the standard AI in any way besides that "return if commanded" bit at the top.

I'm trying to figure out how what I'm doing is any different than hitting a creature with a Fireball -- why am I not seeing an infinite loop on Fireball at times?


No haks or overrides.  Not the answer I was looking for.  I'll continue to check the default AI, however the shout system may have hard-coded interference as well.  If this is a default AI problem it is surprising it hasn't been reported before given the number of instances of hitting an ally with fireball.
               
               

               
            

Legacy_MagicalMaster

  • Hero Member
  • *****
  • Posts: 2712
  • Karma: +0/-0
Animal Companion AI Scripts
« Reply #42 on: October 31, 2013, 07:05:54 pm »


               Y'know, here's an idea that just occurred to me:

I'm not delaying the damage.

I think all of Bioware's spells DelayCommand the damage -- which would mean the loop getting the targets finishes before the damage itself starts occurring.  If I DelayCommanded my damage by 0.05 or 0.1 or something then maybe that would let the loop finding targets finish before any OnDamaged events are called.

I'll give it a shot as soon as I can.
               
               

               
            

Legacy_MagicalMaster

  • Hero Member
  • *****
  • Posts: 2712
  • Karma: +0/-0
Animal Companion AI Scripts
« Reply #43 on: October 31, 2013, 09:42:16 pm »


               I've done some testing and it *seems* like it fixed the bug -- can't get it to trigger again despite my best efforts.  Going to do some more testing but the answer seems to be that you need to DelayCommand if you're going to apply an effect that causes another script to trigger *while in a loop.*