Aller au contenu

Photo

Please help me script this interface trick


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

#1
BCH

BCH
  • Members
  • 86 messages
When playing the solo game, I discovered that I could send my associates to attack a foe like so:

1. Start a melee attack on that foe from a distance, which starts me moving toward the foe.

2. Use a hotkey to order all associates to Guard Me, which starts them moving toward the foe also.

3. Click behind myself so I'm now running away from the foe.

Result: associates charge in to engage the foe for me while I use spells, engage another foe, or just plain run away.

Now, running a VPnP game, I want to create an item that will do the same thing - send associates to attack - when the item is activated and a foe is clicked on.

Unfortunately, I'm not quite sure what happens when the Guard Me command is issued through the interface.  I've looked in the Lexicon, and the best guess I've come up with is to use DetermineCombatRound().  However, using this line in a script sometimes works, often doesn't, and I can't quite figure out why:

AssignCommand(oAssociate, DetermineCombatRound(oClicked));

I'm stumped.  Ideally, I'd like the function to be able to let the standard AI decide how to attack, but when I start looking at functions like TalentMeleeAttack and TalentSpellAttack, I feel like I'm trying to rewrite DetermineCombatRound(), and I doubt that's the right thing to do.

Any ideas how I can do this?

#2
BCH

BCH
  • Members
  • 86 messages
Here's the script function, in case it helps:

void bchSicEm(object oWielder, object oClicked, int bHenchmen = FALSE, int bSummoned = FALSE, int bFamiliar = FALSE, int bAnimalCompanion = FALSE, int bDominated = FALSE){

    object oAssociate;

    string sMessage = GetName(oWielder) + " orders associates to attack " + GetName(oClicked);
    FloatingTextStringOnCreature(sMessage, oWielder);

    if (bFamiliar){
        oAssociate = GetAssociate(ASSOCIATE_TYPE_FAMILIAR, oWielder);
        AssignCommand(oAssociate, DetermineCombatRound(oClicked));
    }

    if (bAnimalCompanion){
        oAssociate = GetAssociate(ASSOCIATE_TYPE_ANIMALCOMPANION, oWielder);
        AssignCommand(oAssociate, DetermineCombatRound(oClicked));
    }

    if (bSummoned){
        oAssociate = GetAssociate(ASSOCIATE_TYPE_SUMMONED, oWielder);
        AssignCommand(oAssociate, DetermineCombatRound(oClicked));
    }

    if (bDominated){
        oAssociate = GetAssociate(ASSOCIATE_TYPE_DOMINATED, oWielder);
        AssignCommand(oAssociate, DetermineCombatRound(oClicked));
    }

    if (bHenchmen){
        oAssociate = GetAssociate(ASSOCIATE_TYPE_HENCHMAN, oWielder, 1);

        if (GetIsObjectValid(oAssociate)) {
            AssignCommand(oAssociate, DetermineCombatRound(oClicked));

            // if we have one hench, check for more
            int i;
            for (i = 2; i < 7; i++){
                oAssociate = GetAssociate(ASSOCIATE_TYPE_HENCHMAN, oWielder, i);
                AssignCommand(oAssociate, DetermineCombatRound(oClicked));
            }
        }
    }
}

Modifié par BCH, 21 septembre 2010 - 10:07 .


#3
Lightfoot8

Lightfoot8
  • Members
  • 2 535 messages

BCH wrote...

Unfortunately, I'm not quite sure what happens when the Guard Me command is issued through the interface. 


When you give any of the voice commands, the only thing the PC does is shout the command.

From there it is up to the NPC/compaion to react.  That is handled by the copmaions OnConversation event and the listening patterns it is set up to listen for.

Sorry I dont have the time to give a better answer right now.

Hope that helps.

#4
Mudeye

Mudeye
  • Members
  • 126 messages
Here's something you might try:

Set all the henchmen to guard me when you're walking around.

In your script instead of: AssignCommand(oAssociate, DetermineCombatRound(oClicked));

Put:

    AssignCommand(oAssociate,ClearAllActions());
    DelayCommand(1.0, AssignCommand(oAssociate,ActionAttack( oClicked )));

This tells the associates to attack the oClicked.  If they are already attacking then they will continue to do so.  If you want them to leave the current attack then replace the ClearAllActions line with:
AssignCommand(oAssociate,ClearAllActions(TRUE));

#5
Mudeye

Mudeye
  • Members
  • 126 messages
Another thing. There seems to be an internal function to set the state of the henchmen to defend you.
#include "x0_i0_assoc"

SetAssociateState( NW_ASC_MODE_DEFEND_MASTER, TRUE );

So that would be:
AssignCommand( oAssociate, SetAssociateState( NW_ASC_MODE_DEFEND_MASTER, TRUE ));

Modifié par Mudeye, 21 septembre 2010 - 04:05 .


#6
BCH

BCH
  • Members
  • 86 messages
Thanks!  I will try those things, and come crawling back here if I don't get them to work. :unsure:

#7
BCH

BCH
  • Members
  • 86 messages
Sorry, I need to ask for more help...

Lightfoot8 - I think I can figure out how to script a "Guard Me" shout, but that will not get me the whole effect I'm looking for.  With the Attack > Guard Me > Retreat interface trick I described, the end result is the same as saying "associates, attack that exact enemy."  That's what I can't figure out how to do.

Mudeye - Since I last posted, I've tried using ActionAttack() and WrapperActionAttack(), but those only seem to work for attacks with equipped weapons.  This is good, but it does not seem to include spells or spell-like abilities.  One of my test associates is a summoned fire beetle with Fire Bolt and Fire Stream, and it does not use these when commanded with ActionAttack or WrapperActionAttack.  However, it does use it normal combat, where it is picking it's own targets, so I know the AI is choosing those attacks somehow.

I've tried using variations on DetermineCombatRound() and ChooseTactics(), but they only work sporadically, for reasons I have not yet been able to puzzle out.  Anyone got any idea why those would not work in my script above?

My next step is adding script to decide whether the associate should use TalentSpellAttack() or something similar, but since I can't find extensive documentation on all these TalentBlahBlah combat functions, I fear I may end up on a long wild goose chase.

Again, thanks for any help anyone can offer!

#8
Mudeye

Mudeye
  • Members
  • 126 messages
Have you looked at the SetCombatCondition and SpecialTactics functions?

I believe this tells your associate to use ranged tactics:
SetCombatCondition( X0_COMBAT_FLAG_RANGED, TRUE, oAssociate );

This determines the tactics as set above to used on the target oClicked:
SpecialTactics( oClicked );

The combat conditions are:
  X0_COMBAT_FLAG_AMBUSHER
  X0_COMBAT_FLAG_COWARDLY
  X0_COMBAT_FLAG_DEFENSIVE
  X0_COMBAT_FLAG_RANGED

For simple Melee they are all set to false.

Modifié par Mudeye, 23 septembre 2010 - 05:01 .


#9
BCH

BCH
  • Members
  • 86 messages
Mudeye, I've gotten that to work with ranged equipped weapons like bows, but not with spellcasting.  Getting spellcasting to work is my current problem.

(Sorry, not trying to be difficult!  :innocent: )

Since the last time I posted, I've seen very limited success using TalentSpellAttack().  Using that in the script, associates will attack with spells sometimes.  It appears that if they are out of range for the spell or spell-like ability that the function chooses, they do nothing.

I'm still trying to find the AI that tells associates "if you are out of range of that target, move into range and cast."  If there is such a thing.

I'm also still trying to puzzle out why DetermineCombatRound() and ChooseTactics() aren't working for me.

#10
BCH

BCH
  • Members
  • 86 messages
Okay, I think I've gotten a bit closer to understanding this problem... if anyone has an idea after reading this, please let me know!

After more testing, it appears that TalentSpellAttack() does not cause my associates to do anything... until I walk them within range of whatever spell was just chosen by the AI for them to cast.

I've consistently seen the following:
 - I order my two associates to TalentSpellAttack() while 30-40 meters from the target.
 - They do nothing.  (Actually, the sorcerer puts away his crossbow and draws his sickle, then does nothing.)
 - Then I wander closer to the target.
 - At a distance of about 20 meters, both associates start casting spells.  The sorcerer starts with Protection from Evil, then Sleep, then Fireball.  The fire beetle goes straight to alternating Fire Bolt and Fire Stream.

Then I added a few lines to the script, hoping to send the associates in on their own, like so:

        if(GetDistanceBetween(oAssociate, oClicked) > 19.0 ){
            AssignCommand(oAssociate, ActionMoveToObject(oClicked, FALSE, 19.0) );
        }

As a result of this new code... no change.  The associates do not seem to respond to the ActionMoveToObject function at all.

I've tried adding SetIsTemporaryEnemy() to the script to make the associates hate the target, and to make the target hate my associates.  The target responds by attacking!  My associates still stand there like idiots.

Any ideas?  It's almost like the associates are holding the TalentSpellAttack in their action queues, but aren't willing to leave my side.

#11
Mudeye

Mudeye
  • Members
  • 126 messages
Here's a couple of thoughts.



You might try looking at the perception range of your associates. They might not be perceiving the enemy yet.



Does the location you tell them to move to conflict with the the distance they are supposed to stay from the PC?



You might want to put a clear actions before the move. If they have other stuff in their action queue it could prevent them from taking the action.

#12
Baragg

Baragg
  • Members
  • 271 messages
I have not opened this up and looked it over but doing so may help you better understand what you can do.



http://nwvault.ign.c....Detail&id=1600

#13
Baragg

Baragg
  • Members
  • 271 messages
Forget what I posted above, I opened that up and found that it is very involved.

#14
Baragg

Baragg
  • Members
  • 271 messages
I remember reading something about this on the old forums, I believe, but could be wrong, the idea grew up to be a series of if statements where it read out like:



if(the caster has XXXspell)

{

cast it

}

if(the caster has XXXspell)

{

cast it

}



So on and so forth with buffs being the first, unless of course you are going to check the closeness of enemies and want to cast spells to freeze them in place first, then buff, then blast.

#15
BCH

BCH
  • Members
  • 86 messages

Mudeye wrote...

Here's a couple of thoughts.

You might try looking at the perception range of your associates. They might not be perceiving the enemy yet.

Does the location you tell them to move to conflict with the the distance they are supposed to stay from the PC?

You might want to put a clear actions before the move. If they have other stuff in their action queue it could prevent them from taking the action.


I'm pretty sure the associates can see their targets... I haven't changed their perception ranges, and they do have good ranks in Spot and Listen.  In some cases, I've used enemies that started off Neutral rather than Hostile, so it was possible to walk my associates right past the target before ordering the attack.

Technically, yes, they would have to leave my side to attack the target, so that does conflict with their set following distances.  However, when they decide to attack on their own, they have no problems chasing after enemies.  They just don't respond to the ActionMoveToObject() function when I add it to the manual attack script.

I did try ClearAllActions() and ClearAllActions(TRUE) in the script.  Sorry, I should have specified that.  Still no success on my part.

Baragg wrote...
I remember reading something about this on the old forums, I believe,
but could be wrong, the idea grew up to be a series of if statements
where it read out like: [snip]


Yeah, that's basically how ChooseTactics() reads.  Unfortunately, I can't find the line or lines in ChooseTactics() that is keeping the associates from doing what I want them to do.

Thanks, though.  I appreciate the help.

#16
Mudeye

Mudeye
  • Members
  • 126 messages
Could you post the exact script you are using?

#17
BCH

BCH
  • Members
  • 86 messages
Here ya go.  This appears to work fine for physical attacks with equipped weapons (melee, ranged, or creature weapons). 

All the problems seem to occur in the "attack with magic" parts.  Associates do nothing if they are out of range of the spell or ability chosen by the script, until they are walked within range of the enemy, at which point they start casting.

#include "NW_I0_GENERIC"

// runs a function that returns an integer and discards the return value
void bchFunctionIgnoreInt(int FunctionThatReturnsInt){ }




void bchGetEm(object oAssociate, object oClicked, object oWielder, int bCast = FALSE){


    //--------------------------------------------------------------------------
    // This (setting associates to Stand Your Ground = No, Guard Me = No)
    //  seems to be necessary.  Without this, TalentSpellAttack and ActionAttack
    //  don't seem to work except randomly, occasionally.
    // Not sure if other settings would work as well.
    // Maybe they just need *something* set?

    SetAssociateState(NW_ASC_MODE_STAND_GROUND, FALSE);
    SetAssociateState(NW_ASC_MODE_DEFEND_MASTER, FALSE);
    //--------------------------------------------------------------------------

    if(bCast){  // this associate should attack with magic

        // make the associate hostile to the target (so they are more willing to attack?)
        //SetIsTemporaryEnemy(oClicked, oAssociate);

        // make the target hostile to the associate,
        //  so their spells will affect them,
        //  even if they start out neutral or friendly
        SetIsTemporaryEnemy(oAssociate, oClicked);

        if(GetDistanceBetween(oAssociate, oClicked) > 19.0 ){
            AssignCommand(oAssociate, ClearAllActions() );
            AssignCommand(oAssociate, ActionMoveToObject(oClicked, FALSE, 19.0)  );
        }

        // hopefully, this will work for all spell-like abilities as well as spells
        //  currently, it only seems to work when an enemy is within range of their spells
        AssignCommand(oAssociate, bchFunctionIgnoreInt(TalentSpellAttack(oClicked) ) );

    }else{

        // this works for equipped weapons (ranged or melee)
        AssignCommand(oAssociate, WrapperActionAttack(oClicked) );
    }
}





// Commands some or all associates to attack oClicked
// First five switches control which associates receive the command.
// bCast == TRUE causes a spell or magic ability attack,
// bCast == FALSE causes a physical attack.
void bchSicEm(object oWielder, object oClicked, int bHenchmen = FALSE,
                int bSummoned = FALSE, int bFamiliar = FALSE,
                int bAnimalCompanion = FALSE, int bDominated = FALSE, int bCast = FALSE){

    object oAssociate;

    string sMessage = GetName(oWielder) + " orders associates to attack " + GetName(oClicked);
    FloatingTextStringOnCreature(sMessage, oWielder);
    SendMessageToAllDMs(sMessage);

    if (bFamiliar){
        oAssociate = GetAssociate(ASSOCIATE_TYPE_FAMILIAR, oWielder);
        if(GetIsObjectValid(oAssociate)){
            bchGetEm(oAssociate, oClicked, oWielder, bCast);
        }
    }

    if (bAnimalCompanion){
        oAssociate = GetAssociate(ASSOCIATE_TYPE_ANIMALCOMPANION, oWielder);
        if(GetIsObjectValid(oAssociate)){
            bchGetEm(oAssociate, oClicked, oWielder, bCast);
        }
    }

    if (bSummoned){
        oAssociate = GetAssociate(ASSOCIATE_TYPE_SUMMONED, oWielder);
        if(GetIsObjectValid(oAssociate)){
            bchGetEm(oAssociate, oClicked, oWielder, bCast);
        }
    }

    if (bDominated){
        oAssociate = GetAssociate(ASSOCIATE_TYPE_DOMINATED, oWielder);
        if(GetIsObjectValid(oAssociate)){
            bchGetEm(oAssociate, oClicked, oWielder, bCast);
        }
    }

    if (bHenchmen){
        oAssociate = GetAssociate(ASSOCIATE_TYPE_HENCHMAN, oWielder, 1);

        if (GetIsObjectValid(oAssociate)) {
            bchGetEm(oAssociate, oClicked, oWielder, bCast);

            // if we have one hench, check for more
            int i;
            for (i = 2; i < 7; i++){
                oAssociate = GetAssociate(ASSOCIATE_TYPE_HENCHMAN, oWielder, i);
                if(GetIsObjectValid(oAssociate)){
                    bchGetEm(oAssociate, oClicked, oWielder, bCast);
                }
            }
        }
    }
}


Modifié par BCH, 30 septembre 2010 - 03:22 .


#18
Mudeye

Mudeye
  • Members
  • 126 messages
You said:
      if(GetDistanceBetween(oAssociate, oClicked) > 19.0 ){
               AssignCommand(oAssociate, ClearAllActions() );
               AssignCommand(oAssociate, ActionMoveToObject(oClicked, FALSE, 19.0)  );
           }

           // hopefully, this will work for all spell-like abilities as well as spells
           //  currently, it only seems to work when an enemy is within range of their spells
           AssignCommand(oAssociate, bchFunctionIgnoreInt(TalentSpellAttack(oClicked) ) );


This line looks questionable to me:
AssignCommand(oAssociate, bchFunctionIgnoreInt(TalentSpellAttack(oClicked) ) );

I'm not sure that it captures the TalentSpellAttack(oClicked) call.  It might be trying to call TalentSpellAttack immediately and pass the result into  bchFunctionIgnoreInt which is then called later.  That would also explain why it only works if you are already within spell range.  If you are too far away, then the immediate call to TalentSpellAttack doesn't do anything since it actually happens before the move command.



How about trying these two things.
----------------------------
1) make a new function:
void MyCast( object target )
{
    SpeakString("Casting Spell");
    ActionCastSpellAtObject(SPELL_MAGIC_MISSILE, target, METAMAGIC_ANY, TRUE );
}

Replace the line:
AssignCommand(oAssociate, bchFunctionIgnoreInt(TalentSpellAttack(oClicked) ) );
with
AssignCommand(oAssociate, MyCast(oClicked) );

See if that runs.  Hopefully the speak and the spell will get cast.
--------------------------------------
2) If the first one works then make a new function:

#include "x0_i0_talent"

void MySpellAttack( object target )
{
   TalentSpellAttack(target);
}

Replace the line:
AssignCommand(oAssociate, bchFunctionIgnoreInt(TalentSpellAttack(oClicked) ) );
with
AssignCommand(oAssociate, MySpellAttack(oClicked) );

Modifié par Mudeye, 30 septembre 2010 - 04:21 .


#19
BCH

BCH
  • Members
  • 86 messages
Sorry for the delay... real life getting in the way.

Mudeye, I think you were right about that recommendation, and I think that I was not successfully putting TalentSpellAttack() into the associates action queues.

Unfortunately, it seems that there's a lot more to my problem.  The more I test, the more I think that my attempts are being thwarted by the associate AI that are already running, but I don't know how to properly hook into that AI to give the right commands.

Thanks for all your help, though.  I'll just have to keep reading through Bioware's code until I find out the proper way to do this.  There must be something I'm missing in DetermineCombatRound(), as that seems to be Bioware's go-to function for making attacks happen.