Aller au contenu

Photo

2 Bugs within DeterminCombatRound


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

#1
wolfzhu

wolfzhu
  • Members
  • 12 messages
Well, I don't know where to post such topics so I just put them here. I'm on NWN1 Ver 1.69. Here are what I have found:

Bug 1:
Script: x0_i0_talent
Position:

// Try a given talent.
// This will only cast spells and feats if the targets do not already
// have the effects of those feats, and will funnel all talents
// through bkTalentFilter for a final check.
int TryTalent(talent tUse, object oTarget=OBJECT_SELF, object oCaster=OBJECT_SELF)
{
int nType = GetTypeFromTalent(tUse);
int nIndex = GetIdFromTalent(tUse);
if(nType == TALENT_TYPE_SPELL && GetHasSpellEffect(nIndex, oTarget))
{
return FALSE;
}
else if(nType == TALENT_TYPE_FEAT && GetHasFeatEffect(nIndex, oTarget))
{
return FALSE;

}
// MODIFIED February 7 2003. Implicit else, implies success.
bkTalentFilter(tUse, OBJECT_SELF);
//MyPrintString("TryTalent Successful Exit");
return TRUE;

return FALSE;
}

I don't know why the codes in red was not reviewed and there is a obvious bug here -- the last command "return FALSE" would never be executed -- in the case bkTalentFilter returns FALSE, DeterminCombatRound would regard that the NPC had performed actions in the current round, then ignore the consequent candidate talents. So we need to change code in red to:

 // MODIFIED February 7 2003. Implicit else, implies success.
if(bkTalentFilter(tUse, OBJECT_SELF))
{
//MyPrintString("TryTalent Successful Exit");
return TRUE;
}

return FALSE;


Bug 2:
Script: x0_inc_generic
Position:

 if (GetTypeFromTalent(tUse) == TALENT_TYPE_FEAT)
{
//MyPrintString("Using feat: " + IntToString(iId));
nNotValid = TRUE;
if (VerifyCombatMeleeTalent(tUse, oTarget)
&& VerifyDisarm(tUse, oTarget))
{
//MyPrintString("combat melee & disarm OK");
nNotValid = FALSE;
}
}

......

// * BK: My talent was not appropriate to use
// * will attack this round instead
if (nNotValid)
{
//MyPrintString("Invalid talent, id: " + IntToString(iId)
// + ", type: " + IntToString(GetTypeFromTalent(tUse)));

if (bJustTest == FALSE)
WrapperActionAttack(oTarget);
}
else
{
if (bJustTest == FALSE)
ActionUseTalentOnObject(tFinal, oTarget);
return TRUE;
}

The problem is, VerifyDisarm would return TRUE if tUse is not disarm feat. Therefore, if tUse is a, e.g. self-target feat, the code branch where nNotValid is FALSE would be running.  However ActionUseTalentOnObject would not be valid when tUse is a self-target feat talent.

The fix of bug2 could be add a limit to the check of disarm talent:

 if (GetTypeFromTalent(tUse) == TALENT_TYPE_FEAT)
{
//MyPrintString("Using feat: " + IntToString(iId));
nNotValid = TRUE;
if (VerifyCombatMeleeTalent(tUse, oTarget)
&& FEAT_DISARM == GetIdFromTalent(tUse)
&& VerifyDisarm(tUse, oTarget))
{
//MyPrintString("combat melee & disarm OK");
nNotValid = FALSE;
}
}


OK, above is pretty much I want to post here. Both bugs would cause an associate (henchman, summon) do nothing during a battle. Had anybody ever encountered such a problem?

Modifié par wolfzhu, 17 octobre 2011 - 12:16 .


#2
Shadooow

Shadooow
  • Members
  • 4 468 messages
No I did not, and Im not really sure whether the first "bug" is fixed correctly by you or it does something it wasnt supposed to do. The same with second. I will dig into this later as its not really easy to understand how this works and how it was probably intented to work.

For the issue you have, I believe this is caused by the horsecrap feats, that is horse menu and subfeats. Try either change these do nothing feats Category to **** or install my unofficial patch where this is contained. It helped so far everyone with this issue.

EDIT: Im not saying you are wrong. Very possibly you have found the actual cause of this issue while I found only symptoms (horse menu). So its probably related to each other. But I have to take a deeper look, these scripts are not easy to understand.

I found also many other bugs, but not all of them are included in last patch 1.70 version (5) due to not being tested properly yet.

- SpellCastAt script issues
- - uncommandable monster fights back when spell is cast at him (fixed in version 5)
- - attempt to flee from AOE can cause monster repeatedly stop attacking, move towards to target (which might not be possible if caster holds position inside or at edge of AOE) causing monster to do nothing and being flatfooted (this is being tested on my PW)
- - masses of npcs above 20, more possibly 50+ can make huge lag when an aoe spell is cast on them (or worse persistent AOE which causes total lagginess) - this is due to the each creature affected by spell shouts to all others creatures nearby to attack its attacker, which bloat exponentialy (for my PW I removed this script entirely from creatures that are often massed and killed by aoe spells, so far wasnt able to fix the cause properly)

- x0_i0_equip issues
- - function int IsOutOfAmmo(int bIAmAHenc) returns TRUE for throwing weapons which can cause creature to unequip the throwing weapons and go unarmed or use worse melee weapon (will be included in last patch version)

- x0_i0_match issues
- - function int VerifyCombatMeleeTalent(talent tUse, object oTarget) has incorrect smite use check which can lead into waste smite on neutral alignment creature (alredy fixed in 1.70 v5)
+ I added a special check to not try rapid shot feat if the current weapon is melee which was causing creature to do nothing (could happen with archer which switched to melee weapon due to distance)
+ I added a slight KD spamming reduction

- x0_i0_talent issues
- - function int TalentUseTurning() count incorrectly the power of turning (HD instead of cleric+paladin+bg levels) (already fixed in patch 1.70 v5)
- - having animal companion feat qualifies for try to turn vermin race (I disabled this in 1.70 v5 as such attempt wouldnt have effect)

Modifié par ShaDoOoW, 17 octobre 2011 - 03:04 .


#3
wolfzhu

wolfzhu
  • Members
  • 12 messages

ShaDoOoW wrote...

For the issue you have, I believe this is caused by the horsecrap feats, that is horse menu and subfeats. Try either change these do nothing feats Category to **** or install my unofficial patch where this is contained. It helped so far everyone with this issue.

Not only the horsecrap feats. Actually Bug2 would halt an NPC when tUse = SUMMON_PALADIN_MOUNT, but more feats other than mount feat could also do the same, because of the same reason.

If you want to trace it, you may create a cleric-fighter-wm (20-20-20), or a 60 paladin henchman to test. The 2 bugs occur more frequently when the NPC level is very high than he has correspondingly a lower level.

Modifié par wolfzhu, 18 octobre 2011 - 12:39 .


#4
FunkySwerve

FunkySwerve
  • Members
  • 1 308 messages
There's no code in red. The forums eat color codes of more than one line if you later edit the post. It gave me fits when I was doing up the server security posting.

Funky

#5
Shadooow

Shadooow
  • Members
  • 4 468 messages

wolfzhu wrote...

ShaDoOoW wrote...

For the issue you have, I believe this is caused by the horsecrap feats, that is horse menu and subfeats. Try either change these do nothing feats Category to **** or install my unofficial patch where this is contained. It helped so far everyone with this issue.

Not only the horsecrap feats. Actually Bug2 would halt an NPC when tUse = SUMMON_PALADIN_MOUNT, but more feats other than mount feat could also do the same, because of the same reason.

paladin mount is also horse related feat, the npc will be never able to mount the summoned horse so I suggest to remove this feat category as well

If you want to trace it, you may create a cleric-fighter-wm (20-20-20),
or a 60 paladin henchman to test. The 2 bugs occur more frequently when
the NPC level is very high than he has correspondingly a lower level.

i will, thanks for tip

Modifié par ShaDoOoW, 18 octobre 2011 - 09:30 .


#6
wolfzhu

wolfzhu
  • Members
  • 12 messages
Sorry. Codes in red:

Bug1:

// MODIFIED February 7 2003. Implicit else, implies success.
bkTalentFilter(tUse, OBJECT_SELF);
//MyPrintString("TryTalent Successful Exit");
return TRUE;

return FALSE;
}

Bug2:

if (VerifyCombatMeleeTalent(tUse, oTarget)
&& VerifyDisarm(tUse, oTarget))

#7
Shadooow

Shadooow
  • Members
  • 4 468 messages
Ok I made a research and I think you are wrong at least in the "bug2".

I wasnt able to test bug1, can you tell me which feat can cause that?

The problem is, VerifyDisarm would return TRUE if tUse is not disarm feat. Therefore, if tUse is a, e.g. self-target feat, the code branch where nNotValid is FALSE would be running.  However ActionUseTalentOnObject would not be valid when tUse is a self-target feat talent.

The problem isnt in this code. The problem is that the Summon Mount category is wrong and equal to 2 (I changed this to 15 in first patch versions btw which fixes this issue) which is hostile ranged attack feat. Therefore the oTarget in this case is enemy creature instead of caster. Your "fix" will disable every combat feat except disarm. But what I want to say is that in different case this can be normally called by a self-target feat but since oTarget will be creature itself it will work fine.

While in many other cases the creature is able to use a feat/spell with personal range on creature in this case is not which means that the summon mount feat use will not be decreased and next AI round creature will try to use this feat again and again + the action itself will stop AI, so the creature will stop attacking until something triggers any other AI script.

I would be also interested to know what are those many other feats which can raise this issue. By default, 90% feats category is **** and creatures will not be able to use these feats. BUT I allowed many of them in my patch like seeker arrow, hail of arrows, assassin spell-like feats, shadow dancer feats etc. and I found out probably reason why they are disabled - the AI cant use them properly in most cases, I had to add a special code into AI in order to skip try using AA feats if the weapon in main hand is not bow as that also stopped creature attack etc...

Modifié par ShaDoOoW, 19 octobre 2011 - 02:56 .


#8
wolfzhu

wolfzhu
  • Members
  • 12 messages
Yeah, you are right, my fix would prevent other feats than disarm from being activated... Good job ShaDoOoW.

As to the first one, I would say it happens often when the npc is trying to use a buffing spell, and which spell could cause this I'm still not sure, however, the spell would make bkTalentFilter return FALSE.

#9
Shadooow

Shadooow
  • Members
  • 4 468 messages
Ok I think I understood the first issue and you are right, the creature then ignore other talents which is probably why casters like going into melee so much.

However this needs to be changed on multiple places like genericDoHarmfulRangedAttack etc and might as well need to rewrite bktalentfilter to not do anything in case of nNotValid==FALSE (the attack is overriden by new talent anyway but it should be safer).

Im not sure if I make it add into my patch as its near complete and this needs a lot of testing in order to be sure it has no side effects. Because these things are often workarounds on some issues that Bioware decided not to fix, as I said I already encounter this with feats like AA arrows.

#10
FunkySwerve

FunkySwerve
  • Members
  • 1 308 messages
I'm wondering if this is why our Drow Clerics stop attacking. It's been a longstanding issue we've not gotten around to fixing, and it's come and gone a few times with various edits. They have whips, along with the disarm feat, but they also sanctuary, which I also seem to recall causing issues at one point.

Anyway, the reason I'm posting is that I think it'd be good if we were to pinpoint the actual edits needed, so that you can fix them in your patch, and we can set forth instructions for fixing them for those not using the patch. I'm willing to help, if you don't want to undertake it on your own.

Funky

#11
Shadooow

Shadooow
  • Members
  • 4 468 messages
I just did it but since the line numbers had changed I think that these lines wont be accurate for you:

x0_i0_talent
619
316
745
776
797
815
869
953
998
1198?
1256?
1346
1355
1403
1420
1449
1634
1773
1953
2142
2266
2277

So in x0_i0_talent, look for each bktalentfilter instance and change it to

if(bktalentfilter(parameters))
return TRUE;

then in the x0_inc_generic I suggest to remove in bktalentfilter function DecreaseRemainingSpellUses calls as they shouldnt be needed anymore since the AI now get just other spell if the first found wont be cast

But if your clerics just stop attacking I think that its not this issue, this issue will send casters into melee in case that AI found talent that is useless in current situation (drown spell against constructs etc). If they stop attack, then they most probably used some feat that has bad impact on AI (horse menu) or cannot be used in certain situation (rapid shot with melee weapon) as I already pointed in my first posts.


Also, I would though about some rewrite in order to allow cast even useless spell rather than going to melee in case there is no other spell that would work. But that can be fixed on the builder side by giving the creature more damaging spells even if lower level.

Modifié par ShaDoOoW, 28 octobre 2011 - 07:44 .


#12
Shadooow

Shadooow
  • Members
  • 4 468 messages
btw funky when we are at the AI, I tried to implement your BBotD ignoring, but the code in bkacquiretarget had almost no effect, if I cast BBotD from safe distance/place where the boss couldnt reach me, he attacks BBotD and then ignore me of course

if I attacked he switched but then I ran around and since BBotD attacked him, he responded to this and fight BBotD back, have any suggestion how or rather where to script it properly? :)

Modifié par ShaDoOoW, 28 octobre 2011 - 07:55 .


#13
FunkySwerve

FunkySwerve
  • Members
  • 1 308 messages
Thanks for the fix info. Here's my include which has a modified bkAcquireTarget and ChooseNewTarget. I can't guarantee that it'll handle your situation, but it solved all our issues with BBoDs, without making them totally useless. Unfortunately, it's somewhat wired into other scripts of ours, so feel free to ask where variable x came from, or whatever. It DOES rely on NWNX for things like checking concealment, dev crit feats, etc, so I don't know how generalizable it'll be. Also, the taunt info is set in our modded taunt/bluff event, which I can also post if you want to see them. Lastly, some of the stuff in there is highly contextual to our mod, like what constitutes a 'squishy' caster, so it may not be of broad applicability.

With all of those caveats, here's the code. Some of the improvements should definitely be of some use, since the default bioware code does things like making Druids the default 'hated' class an absurd amount of the time:
object bkAcquireTarget() {
    object oLastTarget = GetAttackTarget();
    if (GetIsObjectValid(oLastTarget)                           &&
        !GetIsDead(oLastTarget)                                 &&
        !GetHasSpellEffect(SPELL_ETHEREALNESS, oLastTarget)     &&
        (!GetHasEffectOfTrueType(EFFECT_TRUETYPE_KNOCKDOWN, oLastTarget) || !Random(20))) {

        object oPriority = GetLocalObject(OBJECT_SELF, "TargetPriorityOverride");
        if (GetIsObjectValid(oPriority)) {
            return oPriority;

        } else if (GetResRef(oLastTarget) == "x2_s_bblade") {//skip bbod if boss or smart enough, unless caster with Mord
            if (GetHasSpell(SPELL_MORDENKAINENS_DISJUNCTION, OBJECT_SELF))
                return oLastTarget;

            if (GetLocalInt(OBJECT_SELF, "IgnoreBBoD") || (GetAbilityScore(OBJECT_SELF, ABILITY_INTELLIGENCE) > Random(100)))
                oLastTarget = ChooseNewTarget(oLastTarget);

        } else if (GetHasSpellEffect(HGEFFECT_TAUNT)) {

            object oEffectSource = GetLocalObject(OBJECT_SELF, "TauntSource");
            if (!GetIsObjectValid(oEffectSource))//safety check
                return oLastTarget;

            int nType = GetLocalInt(OBJECT_SELF, "TauntSkill");
            if (nType == SKILL_BLUFF) {
            /*
                Bluffed creatures switch away from bluffer 75% of the time, and never choose them as
                their new target unless there are none others available.
            */
                if ((oEffectSource == oLastTarget) && Random(4))
                    oLastTarget = ChooseNewTarget(oEffectSource);
                else
                    return oLastTarget;

            } else if (nType == SKILL_TAUNT) {
            /*
                Taunted creatures switch to attacking the taunter 75% of the time
            */
                if ((oEffectSource != oLastTarget) && Random(4))
                    return oEffectSource;
                else
                    return oLastTarget;
            } else
                return oLastTarget;

        } else
            return oLastTarget;

    } else
        oLastTarget = ChooseNewTarget();

    if (!GetIsObjectValid(oLastTarget) &&  //non-henchmen swap to ranged weapons
        !GetIsObjectValid(GetMaster(OBJECT_SELF)))
        ActionEquipMostDamagingRanged();

    return oLastTarget;  // valid or not, return it
}

int GetConcealment(object oTarget) {
    int nEff, nConceal = 0;
    effect eEff;

    for (eEff = GetFirstEffect(oTarget);
         GetIsEffectValid(eEff);
         eEff = GetNextEffect(oTarget)) {

        if (GetEffectType(eEff) == EFFECT_TYPE_CONCEALMENT) {
            if ((nEff = GetEffectInteger(eEff, 0)) > nConceal)
                nConceal = nEff;
        }
    }

    return nConceal;
}

int GetIsSquishyCaster (object oCreature) {
    if (GetLevelByclass(class_TYPE_CLERIC, oCreature) > 10)
        return TRUE;

    if (GetLevelByclass(class_TYPE_SORCERER, oCreature) > 10)
        return TRUE;

    if (GetLevelByclass(class_TYPE_WIZARD, oCreature) > 10)
        return TRUE;

    if ((GetLevelByclass(class_TYPE_DRUID, oCreature) > 10) &&
        (GetLevelByclass(class_TYPE_SHIFTER, oCreature) < 5))
        return TRUE;

    if (GetAbilityScore(oCreature, ABILITY_CHARISMA, TRUE) >= 30 &&
        (GetLevelByclass(class_TYPE_BARD, oCreature) > 10))
        return TRUE;

    return FALSE;
}

void DeleteAIVariables(object oCreature, int nTargetsConceal) {
    DeleteLocalObject(oCreature, "GEN_AI_TARGET_NEAREST");//clear fallback
    DeleteLocalObject(oCreature, "GEN_AI_TARGET_HATED");//clear hated
    if (nTargetsConceal)
        DeleteLocalObject(oCreature, "GEN_AI_TARGET_HIGH_CONCEAL");//clear highest conceal
}

object ChooseNewTarget(object oTargetToExclude = OBJECT_INVALID) {
    object oSource = OBJECT_SELF, oTargetToExclude2, oTargetToExclude3;
    object oEffectSource = GetLocalObject(oSource, "TauntSource");
    int nType = GetLocalInt(oSource, "TauntSkill");

    if (GetIsObjectValid(oEffectSource)) {
        if ((nType == SKILL_TAUNT) && !GetIsDead(oEffectSource) && !GetHasSpellEffect(SPELL_ETHEREALNESS, oEffectSource) && Random(4))
            return oEffectSource;

        else if ((nType == SKILL_BLUFF) && Random(4)) {
            if (oTargetToExclude == OBJECT_INVALID) //oTargetToExclude can be either a bluffer or a bbod
                oTargetToExclude = oEffectSource;
            else if (oTargetToExclude != oEffectSource)//if excluded is not bluffer, it's bbod
                oTargetToExclude2 = oEffectSource;//makes bluffer TargetToExclude2, after bbod
        }
    }

    int nNth = 1, nConceal, nHighConceal, nCrit;
    float fRange = GetLocalFloat(GetModule(), "AIRange");
    if (fRange < 1.0)
        fRange = 10.0;

    if (GetLocalInt(oSource, "X2_L_BEH_MAGIC") || GetItemIsRangedWeapon(GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oSource)))
        fRange += 20.0;

    int nTargetsCasters = GetLocalInt(oSource, "AttackCasters");

    int nTargetsConceal = (GetHasFeat(FEAT_SKILL_FOCUS_LISTEN, oSource) || GetHasFeat(FEAT_EPIC_SKILL_FOCUS_LISTEN, oSource));

    int nTargetsCritVuln = GetHasFeat(GetWeaponDevastatingCriticalFeat(GetBaseItemType(GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oSource))), oSource);

    //retrieve hated class, set if not
    int nHatedclass = GetLocalInt(oSource, "NW_L_BEHAVIOUR1") - 1;
    if (nHatedclass == -1) {
        nHatedclass = Random(10);
        SetLocalInt(oSource, "NW_L_BEHAVIOUR1", nHatedclass+1);
    }

    object oTarget;
    string sRes;

    while (GetIsObjectValid(oTarget = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY,
         oSource, nNth++,
         CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN,
         CREATURE_TYPE_DOES_NOT_HAVE_SPELL_EFFECT, SPELL_ETHEREALNESS))) {

        if (GetIsDead(oTarget)) {
            nHatedclass = Random(10);
            SetLocalInt(oSource, "NW_L_BEHAVIOUR1", nHatedclass+1);//pick a new hated class when any target is dying
            continue;
        }

        if (oTarget == oTargetToExclude || oTarget == oTargetToExclude2)//ignore these targets unless there are none others
            continue;

        sRes = GetResRef(oTarget);
        if (sRes == "x2_s_bblade") {//skip bbod if boss or smart enough, unless caster with Mord
            if (GetHasSpell(SPELL_MORDENKAINENS_DISJUNCTION, oSource)) {
                DeleteAIVariables(oSource, nTargetsConceal);
                return oTarget;
            }
            if (GetLocalInt(oSource, "IgnoreBBoD") || (GetAbilityScore(oSource, ABILITY_INTELLIGENCE) > Random(100)))
                continue;
        }

        if (!GetIsObjectValid(GetLocalObject(oSource, "GEN_AI_TARGET_NEAREST")))
            SetLocalObject(oSource, "GEN_AI_TARGET_NEAREST", oTarget);//store nearest enemy for fallback

        if ((GetLevelByclass(nHatedclass, oTarget) > 0) &&
            (!GetIsObjectValid(GetLocalObject(oSource, "GEN_AI_TARGET_HATED"))))
            SetLocalObject(oSource, "GEN_AI_TARGET_HATED", oTarget);//store nearest hated enemy

        if (GetHasEffectOfTrueType(EFFECT_TRUETYPE_KNOCKDOWN, oTarget)) {
            if (!GetIsObjectValid(oTargetToExclude3))
                oTargetToExclude3 = oTarget;//store nearest KD'd creature
            continue;
        }

        if (nTargetsCritVuln &&
            !GetIsImmune(oTarget, IMMUNITY_TYPE_CRITICAL_HIT)) {
            if (GetDistanceBetween(oSource, oTarget) <= fRange) {
                DeleteAIVariables(oSource, nTargetsConceal);
                return oTarget;//ret nearest non-crit enemy if in range
            }
            break;//end loop - if nearest crit vuln out of range, just going to attack default target
        }

        if (nTargetsCasters && GetIsSquishyCaster(oTarget)) {
            DeleteAIVariables(oSource, nTargetsConceal);
            return oTarget;//return nearest caster if they AttackCasters (ignoring range)
        }

        if (nTargetsConceal) {//find the highest conceal target in range
            nConceal = GetConcealment(oTarget);
            if (nConceal > nHighConceal) {
                if (GetDistanceBetween(oSource, oTarget) <= fRange) {
                    nHighConceal = nConceal;
                    SetLocalObject(oSource, "GEN_AI_TARGET_HIGH_CONCEAL", oTarget);//store highest conceal enemy in range
                } else break;//stop if out of range
            }
        }
    }

    if (nTargetsConceal) {
        oTarget = GetLocalObject(oSource, "GEN_AI_TARGET_HIGH_CONCEAL");
        if (GetIsObjectValid(oTarget) && Random(4)) {//target highest conceal 75% of the time
            DeleteAIVariables(oSource, nTargetsConceal);
            return oTarget;
        }
    }

    oTarget = GetLocalObject(oSource, "GEN_AI_TARGET_HATED");
    if (GetIsObjectValid(oTarget) &&  (GetDistanceBetween(oSource, oTarget) <= fRange)) {
        DeleteAIVariables(oSource, nTargetsConceal);
        return oTarget;
    }

    oTarget = GetLocalObject(oSource, "GEN_AI_TARGET_NEAREST");
    DeleteAIVariables(oSource, nTargetsConceal);

    if (!GetIsObjectValid(oTarget))
        oTarget = oTargetToExclude3;//target of last resort

    if (!GetIsObjectValid(oTarget))
        oTarget = oTargetToExclude2;//target of last resort - means kd'd get targeted before bluffers

    if (!GetIsObjectValid(oTarget))
        oTarget = oTargetToExclude;//target of last resort- means bluffers get targeted before bbods

    return oTarget;//ok if invalid
}



If I've already shared that code and you're having problems with it specifically, I would just change this section:


        } else if (GetResRef(oLastTarget) == "x2_s_bblade") {//skip bbod if boss or smart enough, unless caster with Mord
            if (GetHasSpell(SPELL_MORDENKAINENS_DISJUNCTION, OBJECT_SELF))
                return oLastTarget;

            if (GetLocalInt(OBJECT_SELF, "IgnoreBBoD") || (GetAbilityScore(OBJECT_SELF, ABILITY_INTELLIGENCE) > Random(100)))
                oLastTarget = ChooseNewTarget(oLastTarget);

        } 

So that it ALWAYS chooses a new target instead of checking intelligence (just remove that second if clause and make  oLastTarget = ChooseNewTarget(oLastTarget); fire for all BBoD targets. These target checks fire very frequently - even when the ai was set to ignore kd'd targets 90% of the time, they would still get whacked somewhat frequently - moving it up to 95% helped quite a bit (the !Random(20) in bkAcquire's first if clause). Another possibility is just upping fRange, if distance is the issue (it's where it is because I was concerned about overhead). Anyway, I know this targeting code works, since we use it, though it may misbehave in scenarios we're not seeing on the server.

Funky

Modifié par FunkySwerve, 28 octobre 2011 - 09:36 .


#14
FunkySwerve

FunkySwerve
  • Members
  • 1 308 messages
Oh, one other warning. I would playtest this include to make sure it doesn't result in TMIs. I don't think it will, but we run at much higher than normal TMI limits, so I have no way of knowing. No performance issues to speak of, at least - I made it as fast as was reasonable.

Funky

#15
Shadooow

Shadooow
  • Members
  • 4 468 messages
i already seen this code in other topic and re-scripted it myself and it didnt worked and it cant by default because there is an target switch in OnAttack script, to make it work and ignore BBotD there must be some code added either to this event or into DetermineCombatRound (which is where I did it) and run bkAcquireTarget if the oIntruder is BBotD, that way it works.

Anyway to the previous issue with bkTalentFilter, I tracked down an possible TMI that could probably happen in some very special case. For this reason last bkTalentFilter in function TalentMeleeAttack (x0_i0_talent, line 1635 (at lest in my version)) must stay either default or you have to rescript the code in order to make sure that when bkTalentFilter will be FALSE the function TalentMeleeAttack returns TRUE anyway!

#16
Failed.Bard

Failed.Bard
  • Members
  • 774 messages
You might have to make a wrapper function for GetNearestCreature to more easily allow checking for plot flag in the targetting routine.
The changes withing the vanilla OnPysicalAttacked and OnDamaged routines are easier, just put an exception in so that if the attacker/damager is plot flagged don't switch targets.

#17
FunkySwerve

FunkySwerve
  • Members
  • 1 308 messages

ShaDoOoW wrote...

i already seen this code in other topic and re-scripted it myself and it didnt worked and it cant by default because there is an target switch in OnAttack script, to make it work and ignore BBotD there must be some code added either to this event or into DetermineCombatRound (which is where I did it) and run bkAcquireTarget if the oIntruder is BBotD, that way it works.

It works just fine, as I've said. You need to include it in nw_i0_generic and x0_inc_generic, which contain, among other things, subfunctions of DCR and DCR iteself, which uses those functions. It replaces the following:

// This function returns the target for this combat round.
// Normally, this will be the same target as the last round.
// The only time this changes is if the target is gone/killed
// or they are in dying mode.
object bkAcquireTarget()
{
    object oLastTarget = GetAttackTarget();

    // * for now no 'target switching' other
    // * than what occurs in the OnDamaged and OnPerceived events
    // * (I may roll their functionality into this function
    if (GetIsObjectValid(oLastTarget) == TRUE
        && !GetAssociateState(NW_ASC_MODE_DYING, oLastTarget))
    {
        return oLastTarget;
    } else {
        oLastTarget = ChooseNewTarget();
    }

    // * If no valid target it means no enemies are nearby, resume normal behavior
    if (! GetIsObjectValid(oLastTarget)) {
        // * henchmen should only equip weapons based on what you tell them
        if (GetIsObjectValid(GetMaster(OBJECT_SELF)) == FALSE) {
            // * if no ranged weapon this function should
            // * automatically be melee weapon
            ActionEquipMostDamagingRanged();
        }
    }

    // valid or not, return it
    return oLastTarget;
}


// Choose a new nearby target. Target must be an enemy, perceived,
// and not in dying mode. If possible, we first target members of
// a class we hate.
object ChooseNewTarget(object oTargetToExclude=OBJECT_INVALID)
{
    int nHatedclass = GetLocalInt(OBJECT_SELF, "NW_L_BEHAVIOUR1") - 1;

    // * if the object has no hated class, then assign it
    // * a random one.
    // * NOTE: classes are off-by-one
    if (nHatedclass == -1)
    {
        bkSetupBehavior(1);
        nHatedclass = GetLocalInt(OBJECT_SELF, "NW_L_BEHAVIOUR1") - 1;
    }

    //MyPrintString("I hate " + IntToString(nHatedclass));

    // * First try to attack the class you hate the most
    object oTarget = GetNearestPerceivedEnemy(OBJECT_SELF, 1,
                                              CREATURE_TYPE_class,
                                              nHatedclass);

    if (GetIsObjectValid(oTarget) && !GetAssociateState(NW_ASC_MODE_DYING, oTarget))
        return oTarget;

    // If we didn't find one with the criteria, look
    // for a nearby one
    // * Keep looking until we find a perceived target that
    // * isn't in dying mode
    oTarget = GetNearestPerceivedEnemy();
    int nNth = 1;
    while (GetIsObjectValid(oTarget)
           && GetAssociateState(NW_ASC_MODE_DYING, oTarget))
    {
        nNth++;
        oTarget = GetNearestPerceivedEnemy(OBJECT_SELF, nNth);
    }

    return oTarget;
}

Past that, just recompile all your scripts using DCR and it should work just fine. DCR is called from default5, so I don't see why you would single out that event.

Funky

Modifié par FunkySwerve, 29 octobre 2011 - 07:57 .


#18
Failed.Bard

Failed.Bard
  • Members
  • 774 messages

while (GetIsObjectValid(oTarget = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY,
oSource, nNth++,
CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN,
CREATURE_TYPE_DOES_NOT_HAVE_SPELL_EFFECT, SPELL_ETHEREALNESS)))


I'm curious about that part of your script, Funky.  Doesn't NWN remove a creature from your visibility list if they have etherealness cast and you don't have the counter for it?
  It seems just looking at it, as if you're needlessly checking the spell effects, potentially of every creature in the area, if the etherealness effect is already handled under the visibility check for PERCEPTION_SEEN.

#19
Shadooow

Shadooow
  • Members
  • 4 468 messages
Funky, I did that exactly and then I went playtesting. I had an creature that couldnt see me (i was outside of his visual range) and then I cast BBoD into his vision range, this creature then attacked BBoD and doesnt changed target even after I came closer. Then I tried to come closer in order this npc would attack me and then I casted BBoD into his path, the BBoD then attacked him and since I was farther than BBoD creature switched target in OnAttack script to BBoD and then bkAcquireTarget WASNT called at all!

I tracked down every bits and then I rewritten it. I took the BBoD part from bkAcquireTarget and put it into DetermineCombatRound where I checked if oIntruder (object passed into function which if valid has greater priority than new target from bkAcquireTarget) is BBoD and in case if it is && creature is smart enough, then she try to dispell it immediately or if no such talent possible, then it switch target if there is a no-BBoD target in his vision range. That worked just fine and without problem.

Failed.Bard wrote...

while (GetIsObjectValid(oTarget = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY,
oSource, nNth++,
CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN,
CREATURE_TYPE_DOES_NOT_HAVE_SPELL_EFFECT, SPELL_ETHEREALNESS)))


I'm curious about that part of your script, Funky.  Doesn't NWN remove a creature from your visibility list if they have etherealness cast and you don't have the counter for it?
  It seems just looking at it, as if you're needlessly checking the spell effects, potentially of every creature in the area, if the etherealness effect is already handled under the visibility check for PERCEPTION_SEEN.

It does but HG might use some plugin that reenables it etc. A safety check is never wrong even almost unused. This two extra parameters have no effect on CPU/TMI anything. If you will go into default AI you will see lots of things like this, while I could also rewriten them, I rather kept them in order to make the changes I did easier to compare with default version.

#20
FunkySwerve

FunkySwerve
  • Members
  • 1 308 messages

Failed.Bard wrote...

while (GetIsObjectValid(oTarget = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY,
oSource, nNth++,
CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN,
CREATURE_TYPE_DOES_NOT_HAVE_SPELL_EFFECT, SPELL_ETHEREALNESS)))


I'm curious about that part of your script, Funky.  Doesn't NWN remove a creature from your visibility list if they have etherealness cast and you don't have the counter for it?
  It seems just looking at it, as if you're needlessly checking the spell effects, potentially of every creature in the area, if the etherealness effect is already handled under the visibility check for PERCEPTION_SEEN.


It's to remedy bugs in behavior caused by engine-level errors, which would cause creatures to be attacked inappropriately - see, e.g., this bug report. The vision code is some of the buggiest - it's actually home to one of the few remaining crash bugs in nwserver. There's also an issue with attacks of opportunity against Sanc'd targets, though I don't remember if they're at all related.

Funky

#21
FunkySwerve

FunkySwerve
  • Members
  • 1 308 messages

ShaDoOoW wrote...

Funky, I did that exactly and then I went playtesting. I had an creature that couldnt see me (i was outside of his visual range) and then I cast BBoD into his vision range, this creature then attacked BBoD and doesnt changed target even after I came closer. Then I tried to come closer in order this npc would attack me and then I casted BBoD into his path, the BBoD then attacked him and since I was farther than BBoD creature switched target in OnAttack script to BBoD and then bkAcquireTarget WASNT called at all!


You're right about it not working when oIntruder is passed into DCR. I guess we haven't noticed any issues from that due to style of play (not doing what you did in your test) and pre-existing code in events where that happens. Here, for example, is our dialogue script:

#include "nw_i0_generic"

void main () {
    object oSelf = OBJECT_SELF;

    if (GetSpawnInCondition(NW_FLAG_ON_DIALOGUE_EVENT))
        SignalEvent(oSelf, EventUserDefined(EVENT_DIALOGUE));

    int nUptime = GetLocalInt(GetModule(), "uptime");
    int nEvtime = GetLocalInt(oSelf, "AI_Uptime_Dialogue");

    if (nUptime > 0 && nUptime == nEvtime)
        return;

    SetLocalInt(oSelf, "AI_Uptime_Dialogue", nUptime);
    DelayCommand(1.0, SetLocalInt(oSelf, "AI_Uptime_Dialogue", -1));

    object oSpeaker = GetLastSpeaker();

    if (!GetIsPC(oSpeaker) && GetListenPatternNumber() == 1) {
        int bDCR = FALSE;
        object oTarget, oHostile = GetLastHostileActor(oSpeaker);

        if (GetIsObjectValid(oHostile)) {
            int bIgnoreBBoD = Random(4);

            oTarget = GetAttemptedAttackTarget();
            if (!GetIsObjectValid(oTarget) || (bIgnoreBBoD && GetResRef(oTarget) == "x2_s_bblade")) {
                oTarget = GetAttemptedSpellTarget();

                if (!GetIsObjectValid(oTarget) || (bIgnoreBBoD && GetResRef(oTarget) == "x2_s_bblade")) {
                    bDCR    = TRUE;
                    oTarget = GetLastHostileActor(oSelf);

                    if (!GetIsObjectValid(oTarget) || (bIgnoreBBoD && GetResRef(oTarget) == "x2_s_bblade"))
                        SetLastHostileActor(oSelf, oHostile);
                }
            }
        } else {
            if (!GetIsObjectValid(GetAttemptedAttackTarget()) &&
                !GetIsObjectValid(GetAttemptedSpellTarget()))
                bDCR = TRUE;
        }

        if (bDCR)
            DetermineCombatRound(oHostile);
    }
}



And, searching by that resref, we have a LOT of pre-existing bbod-handling code:


abo_endcombat (245):     if (GetLocalInt(oSelf, "NoBBoD") && GetResRef(oPC) == "x2_s_bblade") {
aby_boss_heart (426):                     if (GetIsObjectValid(oTarget = GetNearestObjectByTag("x2_s_bblade", oSelf)))
aby_boss_heart (487):                     if (GetIsObjectValid(oTarget = GetNearestObjectByTag("x2_s_bblade", oSelf)))
aby_boss_heart (527):                     if (GetIsObjectValid(oTarget = GetNearestObjectByTag("x2_s_bblade", oSelf)))
aby_boss_heart (589):                     if (GetIsObjectValid(oTarget = GetNearestObjectByTag("x2_s_bblade", oSelf)))
aby_boss_heart (720):                     if (GetIsObjectValid(oTarget = GetNearestObjectByTag("x2_s_bblade", oSelf)))
aby_endcombat (147):     } else if (GetResRef(oTarget) == "x2_s_bblade") {
aby_endcombat (874):     if (GetLocalInt(oSelf, "NoBBoD") && GetResRef(oPC) == "x2_s_bblade")
aby_endcombat (3394):                 if (GetResRef(oPC) == "x2_s_bblade") {
aby_enter (1683):     if (GetIsDM(oPC) || !(GetIsPC(oPC) || GetIsPC(GetMaster(oPC))) || GetResRef(oPC) == "x2_s_bblade")
aby_onhit (206):                 if (GetResRef(oTarget) == "x2_s_bblade")
dul_endcombat (178):     if (GetLocalInt(oSelf, "NoBBoD") && GetResRef(oPC) == "x2_s_bblade")
ele_endcombat (105):     if (GetLocalInt(oSelf, "NoBBoD") && GetResRef(oPC) == "x2_s_bblade")
ely_endcombat (156):             if (GetResRef(oPC) == "x2_s_bblade") {
fey_endcombat (82):     if (GetLocalInt(oSelf, "NoBBoD") && GetResRef(oPC) == "x2_s_bblade")
fky_3endcmb_hell (366):                 if (GetResRef(oPC) == "x2_s_bblade")
fky_3endcmb_hell (439):                 if (GetResRef(oPC) == "x2_s_bblade")
fky_3endcmb_hell (501):                 if (GetResRef(oPC) == "x2_s_bblade")
fky_3endcmb_hell (621):                 if (GetResRef(oPC) == "x2_s_bblade") {
fky_3endcmb_hell (1544):                 if (GetResRef(oPC) == "x2_s_bblade") {
fky_ai_4dialogue (28):             if (!GetIsObjectValid(oTarget) || (bIgnoreBBoD && GetResRef(oTarget) == "x2_s_bblade")) {
fky_ai_4dialogue (31):                 if (!GetIsObjectValid(oTarget) || (bIgnoreBBoD && GetResRef(oTarget) == "x2_s_bblade")) {
fky_ai_4dialogue (35):                     if (!GetIsObjectValid(oTarget) || (bIgnoreBBoD && GetResRef(oTarget) == "x2_s_bblade")) 
fky_ai_target (13):         } else if (GetResRef(oLastTarget) == "x2_s_bblade") {//skip bbod if boss or smart enough, unless caster with Mord
fky_ai_target (162):         if (sRes == "x2_s_bblade") {//skip bbod if boss or smart enough, unless caster with Mord
gen_endcombat (185):     if (GetLocalInt(ei.self, "NoBBoD") && GetResRef(ei.opp) == "x2_s_bblade")
had_enter (274):     if (GetIsDM(oPC) || GetLocalInt(oPC, "PlotSummons") || GetResRef(oPC) == "x2_s_bblade")
had_enter (298):     if (GetIsDM(oPC) || !(GetIsPC(oPC) || GetIsPC(GetMaster(oPC))) || GetResRef(oPC) == "x2_s_bblade")
hellenter (96):         if (GetResRef(oPC) == "x2_s_bblade") {
hellenter (131):     if (GetIsDM(oPC) || !(GetIsPC(oPC) || GetIsPC(GetMaster(oPC))) || GetResRef(oPC) == "x2_s_bblade")
hell_boss_spawn (18):     AssignCommand(oArea, DelayCommand(6.0, DestroyObjectsByTag(oNear, "x2_s_bblade")));
hg_area_exit (154):     if (GetResRef(oPC) != "x2_s_bblade" && !GetLocalInt(oPC, "FKY_CHAT_INVULN") && !GetLocalInt(oPC, "PlotSummons"))
hg_attack_inc (769):         if (GetResRef(oTarget) == "x2_s_bblade") {
hiv_endcombat (167):     if (GetLocalInt(oSelf, "NoBBoD") && GetResRef(oPC) == "x2_s_bblade")
loc_endcombat (112):     if (GetLocalInt(oSelf, "NoBBoD") && GetResRef(oPC) == "x2_s_bblade")
loc_endcombat (403):             } else if (GetResRef(oPC) == "x2_s_bblade") {
mol_endcombat (153):     if (GetLocalInt(oSelf, "NoBBoD") && GetResRef(oPC) == "x2_s_bblade") {
toy_endcombat (124):     if (GetLocalInt(oSelf, "NoBBoD") && GetResRef(oPC) == "x2_s_bblade")
toy_onhit (17):             if (GetResRef(oTarget) == "x2_s_bblade")
x2_inc_spellhook (223):         if(GetTag(oAssoc) == "x2_s_bblade") // black blade of disaster
x2_s0_blckblde (10):     if (GetResRef(oSummon) == "x2_s_bblade") {
x2_s0_blckblde (35):         effect eSummon = EffectSummonCreature("x2_s_bblade");

Nothing in onattacked, though. I guess that scenario just isn't coming up much. Surveying the default ai scripts, it looks like it's occurences are limited to onspellcastat, ondialogue, and ondamaged.

nw_c2_default2 (39):            DetermineCombatRound();
nw_c2_default2 (58):                     DetermineCombatRound();
nw_c2_default3 (23):        DetermineCombatRound();
nw_c2_default3 (28):     }else{DetermineCombatRound();}
nw_c2_default5 (39):                             DetermineCombatRound();
nw_c2_default6 (72):                         DetermineCombatRound();
nw_c2_default6 (79):                                 ActionDoCommand(DetermineCombatRound());
nw_c2_default6 (102):                         DetermineCombatRound(oAttacker);
nw_c2_default8 (28):             DetermineCombatRound(oTarget);
nw_c2_default9 (64):     DetermineCombatRound();
nw_c2_defaultb (21):                 DetermineCombatRound(oCaster);
And, surveying my ai, which is mostly based on the default (with the x2 revisions), it comes up even less:
fky_3endcmb_hell (2368):         DetermineCombatRound();
fky_ai_2percept (27):         DetermineCombatRound();
fky_ai_4dialogue (46):             DetermineCombatRound(oHostile);
fky_ai_5attacked (15):         DetermineCombatRound();
fky_ai_5spellat (18):         DetermineCombatRound();
fky_ai_6ondamage (380):         DetermineCombatRound(oDamager);
fky_ai_6ondamage (385):                 ActionDoCommand(DetermineCombatRound());
fky_ai_9spawn (37):     DetermineCombatRound();
though my ai includes mw_c2_default8 (ondisturbed), which does pass oIntruder.

I have to wonder if simply removing the check for oIntruder's validity and running bkAcq all the time isn't the best solution. Here's my onspellcastat (replacing defaultb) , by way of comparison:

#include "nw_i0_generic"

void main() {
    object oSelf = OBJECT_SELF;

    if (GetSpawnInCondition(NW_FLAG_SPELL_CAST_AT_EVENT))
        SignalEvent(oSelf, EventUserDefined(EVENT_SPELL_CAST_AT));

    if (!GetLastSpellHarmful())
        return;

    if (GetIsObjectValid(GetAttemptedAttackTarget()) || GetIsObjectValid(GetAttemptedSpellTarget()))
        return;

    object oHostile = GetLastSpellCaster();

    if (GetArea(oHostile) == GetArea(oSelf)) {
        DetermineCombatRound();

        if (!GetIsObjectValid(GetLastHostileActor(oSelf)))
            SetLastHostileActor(oSelf, oHostile);
        SpeakString("NW_I_WAS_ATTACKED", TALKVOLUME_SILENT_TALK);
    }
}
}

Thoughts? I ran your test with spawing out of sight and got the same result, by the way, save that the creature switched to me as soon as I attacked it with a sling, despite being much further away than the BBoD. The script events on the creature in question:

OnBlocked=nw_c2_defaulte
OnDamaged=fky_ai_6ondamage
OnDeath=fky_ai_7death
OnConversation=fky_ai_4dialogue
OnDisturbed=nw_c2_default8
OnCombatRoundEnd=fky_3endcmb_hell
OnHeartbeat=fky_1heart_hellb
OnPhysicalAttacked=fky_ai_5attacked
OnPerception=fky_ai_2percept
OnRested=nw_c2_defaulta
OnSpawn=fky_ai_9spawn
OnSpellCast=fky_ai_5spellat
OnUserDefined=


Kinda tempted to throw in debug to see just how often DCR fires in various scenarios.

Funky

Modifié par FunkySwerve, 29 octobre 2011 - 05:56 .


#22
Shadooow

Shadooow
  • Members
  • 4 468 messages
Yea when I attacked, creature tried to switch me but since BBoD attacked again in the same time or few seconds later creature stayed. Except that what Im trying to handle is a tactic used on my PW where you summon BBoD outside of corner, lure hard creatures to attack it, then you tell BBoD to come to you into your corner, creatures come with it. And then you tell BBoD to stop and you can easily avoid fighting and run away. And unlock door and continue further inside dungeon.

Or at least not allowing to use BBoD to acquire some time to heal/resurrect another player etc in situations like very tough boss who just killed tank.

Modifié par ShaDoOoW, 29 octobre 2011 - 09:25 .


#23
WhiZard

WhiZard
  • Members
  • 1 204 messages

ShaDoOoW wrote...
And unlock door and continue further inside dungeon.

This part doesn't require AI scripting.  You can simply cause the door (I assume it is plot) to relock itself and send a message to the players (like "You will need to kill the creatures in this area before proceeding onward") if the guardian creatures are still alive.

#24
Shadooow

Shadooow
  • Members
  • 4 468 messages
I dont want them to kill guardians.

#25
Shadooow

Shadooow
  • Members
  • 4 468 messages

Failed.Bard wrote...

while (GetIsObjectValid(oTarget = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY,
oSource, nNth++,
CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN,
CREATURE_TYPE_DOES_NOT_HAVE_SPELL_EFFECT, SPELL_ETHEREALNESS)))


I'm curious about that part of your script, Funky.  Doesn't NWN remove a creature from your visibility list if they have etherealness cast and you don't have the counter for it?
  It seems just looking at it, as if you're needlessly checking the spell effects, potentially of every creature in the area, if the etherealness effect is already handled under the visibility check for PERCEPTION_SEEN.

BTW, I reliazed this later but then I implemented it into my patch and latest test confirmed it works.

This code actually fixes inv+GS bug thought only for monsters as player will be able to attack you anyway. Also sometimes the GS works only for the monsters who are around when you cast it. This is also fixed by this snippet.

To resurrect the thread, I have found at least three new issues with invisibility code within AI.
1) once the invisibility is cast, creature cast defensively although that her target might get see insivisibility/true seeing already
2) casters with HIPS don't cast spells and attacks melee
3) creature which entered invisibility tries to heal herself no matter that she is missing only 1% of hitpoints and she may waste a fullheal trying to cure this loss