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 .