Aller au contenu

Photo

Animal Companion AI Scripts


  • Veuillez vous connecter pour répondre
43 réponses à ce sujet

#1
MagicalMaster

MagicalMaster
  • Members
  • 2 000 messages
I'm having trouble altering the animal companion scripts.

Specifically, I'm editing ac5, ac6, and ac8 (Attacked, Damaged, and Disturbed) and compiling them/recompiling them/rebuilding the whole doesn't seem to make any changes.  Originally I couldn't even get a debugging string to go off at the start of each script.

Then (and I still have no idea HOW I managed to do this) the string magically started going off.  But when I then changed the string to be specific to the individual script I'm back in the same problem of it not having any effect (it's "stuck" on the original identical debugging strings).

Aka, originally, nothing would go off.

Now it will finally say (somehow): "Firing OnAttacked."

But it won't update to "Firing OnAttacked"/"Firing OnDamaged"/"Firing OnDisturbed" now.

Am I missing something obvious here?

#2
WhiZard

WhiZard
  • Members
  • 1 204 messages
Are you using new PCs or old PCs?
It could be that you are having trouble with a character file identifying scripts by means of the ID the compiler uses (not sure if this is true, but it is worth checking).

EDIT: Looking at a few character files, it does not look like there is a field for the familiar/animal companions script.  The only data I see are for a name and the type of creature.

Modifié par WhiZard, 27 octobre 2013 - 05:18 .


#3
MagicalMaster

MagicalMaster
  • Members
  • 2 000 messages
After doing some more testing, both have the same issue. Additionally, I can consistently get OnAttacked to fire (and can change what happens in OnAttacked) -- but OnAttacked appears to fire for at least OnDamaged as well (swing at the companion and hit it and one message appears when the swing starts and another when the swing hits).

Why would OnAttacked fire for OnDamaged for Animal Companions?

The reason I'm fiddling with these at all is to improve the controllability of the companions with a script I'm using. Trying to make it so that if I tell the companion to attack a certain creature that its AI doesn't make it change its mind -- setting a local int for a short time period when it is ordered to attack and then trying to make the AI script simply return if the local int is detected.

Basically a way of trying to say "I don't care what you think you want to do, just do what I just told you to do."

#4
MagicalMaster

MagicalMaster
  • Members
  • 2 000 messages
Wondering if I might be able to use SetCommandable for this...I'll probably fiddle with it a bit.

#5
WhiZard

WhiZard
  • Members
  • 1 204 messages
From the templates, ac5 appears to serve for both OnAttacked and OnDamaged for all animal companions and familiars

#6
MagicalMaster

MagicalMaster
  • Members
  • 2 000 messages
Interesting. Is there an easier/better way than trying to adjust those scripts or trying to use SetCommandable to make it so animal companions won't change their target once they get it initially? Like "I don't care if that tank with a tower shield swung at you, I told you to go for the ARCHER!"

#7
MagicalMaster

MagicalMaster
  • Members
  • 2 000 messages
So, I discovered something interesting with SetCommandable. OnSpellCast at always automatically sets Commandable to true. This means either that behavior has to be edited or another method must be used.

I decided to use another method -- basically detects whether the master has commanded the companion within the last nine seconds (round and a half) and if so the relevant AI scripts simply immediately return.

Currently have this applied to...

OnAttacked
OnHeartbeat
OnPerceived
OnSpellCastAt
OnCombatEnd
OnDialogue

I also set it up for OnDamaged but obviously this is irrelevant for the Animal Companion specifically since it uses the OnAttacked script twice.  I may be avoid to avoid returning on some of those scripts but I played it safe as it doesn't seem to hurt anything and all were firing in combat and appear to have the potential to change the behavior of the creature.

This method appears to have solved my issues -- effectively shuts the AI combat decision making down for nine seconds each time the player gives a command and allows a LOT of custom control over the pet.

This can obviously be applied to any henchmen/animal companion/familiar that isn't reliant on spells or other special abilities quite easily. Probably would be possible to script something and tell casters to cast a spell instead of attack. But thought people might find it interesting.

Modifié par MagicalMaster, 28 octobre 2013 - 06:12 .


#8
Lightfoot8

Lightfoot8
  • Members
  • 2 535 messages
Perhaps it would be simpler to just add a  SPECIAL_COMBAT_AI_SCRIP


AI Behavior Priority

Modifié par Lightfoot8, 28 octobre 2013 - 08:16 .


#9
MagicalMaster

MagicalMaster
  • Members
  • 2 000 messages
Well, effectively seems I took the four lines of code (including two lines of curly braces) that would go into the special combat AI script and placed them in the other scripts themselves. So instead of the special AI script firing and preventing the other AI scripts from firing it fires the individual scripts then stops.

Which means my question would be, what's more efficient? Is calling the special AI script less work for the computer than calling the normal scripts but simply stopping?

#10
Shadooow

Shadooow
  • Members
  • 4 465 messages

MagicalMaster wrote...

Well, effectively seems I took the four lines of code (including two lines of curly braces) that would go into the special combat AI script and placed them in the other scripts themselves. So instead of the special AI script firing and preventing the other AI scripts from firing it fires the individual scripts then stops.

Which means my question would be, what's more efficient? Is calling the special AI script less work for the computer than calling the normal scripts but simply stopping?

special AI is more efficient if you are ok with short answer, explaining why could take hours...

#11
MagicalMaster

MagicalMaster
  • Members
  • 2 000 messages
I'm okay with it. I've just been told that, for example, a psuedo-heartbeat takes up much more resources than a standard heartbeat and I didn't know if a similar issue would apply here.

Assuming there are no other issues, is there any downside to using that special AI?

#12
MagicalMaster

MagicalMaster
  • Members
  • 2 000 messages
I tried this and had issues. Specifically, the animal companion, even once the AI was supposed the reactivate, would not seek out new targets (would respond if attacked).

Here's the code I used for the new script:

void main()
{
        // Don't do anything if we have have been recently commanded
        if (GetLocalInt(OBJECT_SELF, "commandstatus"))
        {
                SetLocalInt(OBJECT_SELF,"X2_SPECIAL_COMBAT_AI_SCRIPT_OK",TRUE);
        }
}

Modifié par MagicalMaster, 29 octobre 2013 - 12:15 .


#13
Lightfoot8

Lightfoot8
  • Members
  • 2 535 messages
keep inmind that the script is ran at the top of the core combat function DetermineCombatRound

it basicly stops the core AI when X2_SPECIAL_COMBAT_AI_SCRIPT_OK is set. So within the script you need to tell the creature what to do if the flag is set.

void main()
{
// Don't do anything if we have have been recently commanded
if (GetLocalInt(OBJECT_SELF, "commandstatus"))
{
ActoinAttack( ??);
SetLocalInt(OBJECT_SELF,"X2_SPECIAL_COMBAT_AI_SCRIPT_OK",TRUE);
}
}
Allong with whatever checks you want to use to make sure the Target is still valid.

I Have many times just called DetermineCombatRound again with a direct attack target. In doing that though you will need to place controls so that the functiont does not just keep calling itself and creating an endless loop.

#14
MagicalMaster

MagicalMaster
  • Members
  • 2 000 messages
The creature has already BEEN told what to do -- all I'm trying to do is prevent it from changing its mind when the player does not want it to.

But it seemed to suppress the AI even when commandstatus was 0 and thus the normal AI should have run.

#15
Lightfoot8

Lightfoot8
  • Members
  • 2 535 messages
ok, Just to make sure we are on the same page.

The script above is not on any EVENT.
it is connected to the creature by setting a local on the creature. i.e.
SetLocalString(OBJECT_SELF,"X2_SPECIAL_COMBAT_AI_SCRIPT", "Name of AI script" );

Is that how you have it set up?

AND for the record the function DetermineCombatRound  runs at least once every combat round.   Many time it runs more then once.

Modifié par Lightfoot8, 29 octobre 2013 - 04:55 .


#16
MagicalMaster

MagicalMaster
  • Members
  • 2 000 messages
Yes, in the Animal Companion summon script I put

SetLocalString(oAnimal,"X2_SPECIAL_COMBAT_AI_SCRIPT", "sh_commandcheck" );

(oAnimal has been verified to be the correct target)

and then "sh_commandcheck" is a new script that is

void main()
{
         // Don't do anything if we have have been recently commanded
         if (GetLocalInt(OBJECT_SELF, "commandstatus"))
         {
                 SetLocalInt(OBJECT_SELF,"X2_SPECIAL_COMBAT_AI_SCRIPT_OK",TRUE);
         }
}

With the idea that as long as commandstatus is considered TRUE that the rest of the AI would be suppressed.

Modifié par MagicalMaster, 29 octobre 2013 - 08:15 .


#17
WhiZard

WhiZard
  • Members
  • 1 204 messages
I'm not sure what ShaDoOoW is seeing as more efficient in using the AI script. Your original idea to suppress the AI by disabling event calls if certain criteria is met decreases overhead so long as DetermineCombatRound() isn't called elsewhere (besides from any PC initiated commands). Waiting for the special AI to trigger means more exposure to scripted checks and changing tasks. On the other hand, DetermineCombatRound() does do a good job at clearing the action queue, however, this is not always enforced if it is cut off with special AI scripts.

#18
MagicalMaster

MagicalMaster
  • Members
  • 2 000 messages

WhiZard wrote...

Waiting for the special AI to trigger means more exposure to scripted checks and changing tasks. On the other hand, DetermineCombatRound() does do a good job at clearing the action queue, however, this is not always enforced if it is cut off with special AI scripts.


Yeah, my code literally looks something like this...
// On Attacked/Perceived/Whatever

int main()
{
    if (Busy)
    {
        return;
    }

     <Actual AI stuff for the script goes here>
}

Also, whenever my command script gives a new order it clears all actions first to prevent any kind of action queue pile-up.  And then the AI proceeds normally after a set time passes (9 seconds by default) without the PC giving an order.

#19
Lightfoot8

Lightfoot8
  • Members
  • 2 535 messages
 AS far as the sp.AI script.   I think I would try something like this.   


#include "x2_inc_switches"
#include "nw_i0_generic"
void main()
{
    if ( GetCreatureOverrideAIScriptTarget()== OBJECT_INVALID)
    {
        string sScript =GetLocalString(OBJECT_SELF,CREATURE_VAR_CUSTOM_AISCRIPT);
        SetCreatureOverrideAIScript(OBJECT_SELF,"");
        DetermineCombatRound(GetAttackTarget());
        SetCreatureOverrideAIScript(OBJECT_SELF,sScript);
        SetCreatureOverrideAIScriptFinished();
    }
}

To answer the question on efficiency, I believe you method is more efficient. I myself however like everything going through a single combat function. But it does not sound like you are blocking the combat AI anyway. You are just blocking the other events from firing.

#20
MagicalMaster

MagicalMaster
  • Members
  • 2 000 messages
Yeah, I'm blocking events that fire the combat AI, in effect. Basically telling the mobs being controlled "Do NOT think for yourselves."

Also, any ideas on what might seem to randomly cause this or good ways to debug it?  This never happens without an animal companion but I *sometimes* see it with one -- it doesn't even happen every fight.

Posted Image

Trying to check exactly when AC5 (OnAttacked/OnDamaged) is firing to see if it gets triggered a bunch in a split second or something but figured I could ask here as well.

Modifié par MagicalMaster, 30 octobre 2013 - 01:13 .


#21
MagicalMaster

MagicalMaster
  • Members
  • 2 000 messages
Okay...it appears somehow AC5 is getting triggered like 50 times simultaneously or something.  Pet entered an acid cloud of boss and instantly claimed AC5 was triggered like 15 times (cloud only ticks once per second) and I got the instructions error.

Here's the code running for the acid cloud ("sh_blank" is simply blank and that's what the persistent AoE scripts are designated so there's no persistent AoE scripts running):
void Spit2(location lTarget, int nDur, int nDam)
{
    int nAdjust;

    object oTarget = GetFirstObjectInShape(SHAPE_SPHERE, 4.0, lTarget);
    while (GetIsObjectValid(oTarget))
    {
        if (GetIsEnemy(oTarget))
        {
            nAdjust = AdjustDamage(nDam, oTarget);

            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));
    }

}

Seems pretty basic -- loop through a 4.0 meter circle, apply damage if it's an enemy, adjust the damage for the next tick, then call itself again as long there is duration remaining.

Modifié par MagicalMaster, 30 octobre 2013 - 01:52 .


#22
MagicalMaster

MagicalMaster
  • Members
  • 2 000 messages
Okay.  So I added some debugging strings (again) to try to get to the root of this (and have been unsuccessful so far).

Ths is the acid cloud code.
void Spit2(location lTarget, int nDur, int nDam)
{
    int nAdjust;

    object oTarget = GetFirstObjectInShape(SHAPE_SPHERE, 4.0, lTarget);
    while (GetIsObjectValid(oTarget))
    {
        if (GetIsEnemy(oTarget))
        {
            nAdjust = AdjustDamage(nDam, oTarget);

            AssignCommand(oTarget, SpeakString("Took damage from cloud"));
            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));
    }

}

This is the code at the top of AC5:
void main()
{
    // 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")));

So, in essence, every time the creature gets attacked or damage it says "On Attacked: x" where "x" is the number of times this event has occcurred.

It took me several tries to reproduce this bug and I'm still not sure what's triggering it -- doesn't happen every time.Posted Image

Posted Image

You can see how the cloud damaged the bear 14 times *instantly* before the AssignCommand could even make the bear say it took damage form the cloud (then it overloaded).

But how in the world does that loop hit ANYTHING 14+ times instantly?

Modifié par MagicalMaster, 30 octobre 2013 - 04:42 .


#23
WhiZard

WhiZard
  • Members
  • 1 204 messages
I have seen this phenoma happen many times with standard creatures. I have had wall of flame, cause up to six applications of damage to a creature that was trying to attack me, as his AI made him retreat slightly back each time he hit the AoE and took damage. Do a check in the AoE scripts to see how many times the OnEnter and OnExit scripts are running.

#24
MagicalMaster

MagicalMaster
  • Members
  • 2 000 messages
There *are* no OnEnter or OnExit scripts -- they're simply blank (using the conveniently named "sh_blank" script). Literally just
int main (void)
{
        return;
}

The only thing actually doing damage is the script I posted which does an AoE check each second -- so even if the creature enters and leaves the area the area is only checked every second.  I'll try putting a debug string in blank when I can to see if anything is weird but the only script DOING anything is the one posted.

Modifié par MagicalMaster, 30 octobre 2013 - 03:41 .


#25
MagicalMaster

MagicalMaster
  • Members
  • 2 000 messages
Yeah, it's definitely not sh_blank -- that's only being called a few times and exactly when expected.

Half thinking I should just set a local int on the first loop and then remove it with a second loop to see if that helps.

But I don't see why I should *need* to do this.