Hey NWN community!
I'm struggling with the AI for my enemy NPCs and was hoping one of you lot could shed some light into this for me.
Basically I have a wall with archers that fires at incoming enemies. The enemies are to destroy the wall's gate, which works somewhat. My problem here is the fact that once one of the enemies gets attacked from the archers they will interrupt their priority to attack the gate and instead focus on the archers, which by the way are on top of the wall so the enemies can't really reach them.
I've been trying to modify various scripts on the enemy NPCs such as OnPhysicalAttacked, OnDamaged etc so they will ignore their attacker and instead focus on the gate since that is a priority for them. But still they insist on trying to attack the archers on the wall which is driving me nuts.
Any ideas here?
Thanks in advance!
AI Behavior Priority
Débuté par
Greyjoy
, juil. 23 2011 05:05
#1
Posté 23 juillet 2011 - 05:05
#2
Posté 23 juillet 2011 - 06:39
It's possible it's the calls from the OnConversation that are still doing it. They might not be responding to themselves being attacked, but the silent shouts from the others that are, and are responding to that.
#3
Posté 23 juillet 2011 - 06:54
I do not see a reason here to mess with any or the default events. All you need to do is set up a special AI for your archers. This is easy to do.
First you need to set a Local string on your archers, you can do this either in the toolset or in there onspawn script.
From the OnSpawn Script you would add something like this.
SetLocalString(OBJECT_SELF,"X2_SPECIAL_COMBAT_AI_SCRIPT", "Name of AI script" );
In the toolset you would just add the X2_SPECIAL_COMBAT_AI_SCRIPT string name and give it the value of the AI script name.
After that you just need to write the script for your special AI. I see it looking something like this.
void main()
{
object oWall =GetNearestObjectByTag("Tag Of Wall");
if (oWall == OBJECT_INVALID) return;
ActionAttack(oWall);
SetLocalInt(OBJECT_SELF,"X2_SPECIAL_COMBAT_AI_SCRIPT_OK",TRUE);
}
Clean and simple.
The only line in it the need a little more explanation is the:
SetLocalInt(OBJECT_SELF,"X2_SPECIAL_COMBAT_AI_SCRIPT_OK",TRUE);
When this local is set to true in the script it prevents the rest of the AI from running. so there will be no fear of your archers changing targets to something else.
Once your wall is destroyed, the script will no longer set that local to true and the regular AI will be able to take back over.
First you need to set a Local string on your archers, you can do this either in the toolset or in there onspawn script.
From the OnSpawn Script you would add something like this.
SetLocalString(OBJECT_SELF,"X2_SPECIAL_COMBAT_AI_SCRIPT", "Name of AI script" );
In the toolset you would just add the X2_SPECIAL_COMBAT_AI_SCRIPT string name and give it the value of the AI script name.
After that you just need to write the script for your special AI. I see it looking something like this.
void main()
{
object oWall =GetNearestObjectByTag("Tag Of Wall");
if (oWall == OBJECT_INVALID) return;
ActionAttack(oWall);
SetLocalInt(OBJECT_SELF,"X2_SPECIAL_COMBAT_AI_SCRIPT_OK",TRUE);
}
Clean and simple.
The only line in it the need a little more explanation is the:
SetLocalInt(OBJECT_SELF,"X2_SPECIAL_COMBAT_AI_SCRIPT_OK",TRUE);
When this local is set to true in the script it prevents the rest of the AI from running. so there will be no fear of your archers changing targets to something else.
Once your wall is destroyed, the script will no longer set that local to true and the regular AI will be able to take back over.
#4
Posté 23 juillet 2011 - 07:00
We altered targeting behavior by modifying the target selection functions called by DetermineCombatRound. We included this script in x0_inc_generic to do so:
As you can see, it allows us to fine-tune target selection. There may be an easier way that's equally effective, but I'm unaware of it if so.
[EDIT] Use Lightfoot's way, it's far simpler than making deep ai edits.
Funky
object bkAcquireTarget() {
object oLastTarget = GetAttackTarget();
if (GetIsObjectValid(oLastTarget) &&
!GetIsDead(oLastTarget) &&
!GetHasSpellEffect(SPELL_ETHEREALNESS, oLastTarget)) {
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;
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 (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 = oTargetToExclude2;//target of last resort
if (!GetIsObjectValid(oTarget))
oTarget = oTargetToExclude;//target of last resort- means bluffers get targeted before bbods
return oTarget;//ok if invalid
}
As you can see, it allows us to fine-tune target selection. There may be an easier way that's equally effective, but I'm unaware of it if so.
[EDIT] Use Lightfoot's way, it's far simpler than making deep ai edits.
Funky
Modifié par FunkySwerve, 23 juillet 2011 - 07:01 .
#5
Posté 24 juillet 2011 - 12:50
Lightfoot8 that's a brilliant idea. I can just force the enemies to attack the gate and disable the rest of the AI. Once the gate is destroyed their regular/default AI kicks in and all is good.
Thanks a bunch!
Thanks a bunch!
#6
Posté 08 octobre 2011 - 02:06
I used LightFoot8's script as the starting point for a Wand of Targets script which lets you tell a henchman who to attack. See the Utilities link in my sig for the item.
#7
Posté 08 octobre 2011 - 02:53
I really like the BBotD code, would you mind if I take that idea for my project?FunkySwerve wrote...
...





Retour en haut






