quantumpencil Posted February 9, 2016 Share Posted February 9, 2016 Hello Modders, I am attempting to make some changes to the behavior of the bosses in both KotoR 1 and Kotor 2. The first thing I'd like to do is modify the frequency with which the bosses use force powers -- yet I'm confused about the logic governing this. I will begin with Darth Nihilus, since he seems to never use any force powers -- though it applies to the other bosses as well (Traya seems to use them sometimes but not nearly as much as the code suggest she should...) So, based on my understanding of the AI Script, this is what happens vs. Nihilus/Traya: 1: After their on_perception event fires in k_ai_master, the include method GN_DetermineCombatRound() is called. It is then called at the end of every Round. This function serves as main() for all behavior, enemy and ally alike. DetermineCombatRound() Show spoiler (hidden content - requires Javascript to show) void GN_DetermineCombatRound(object oIntruder = OBJECT_INVALID) { GN_MyPrintString(""); GN_MyPrintString("GENERIC DEBUG *************** START DETERMINE COMBAT ROUND " + GN_ReturnDebugName(OBJECT_SELF)); //RWT-OEI 10/19/04 - If this is a party member, and this party member already has //combat actions queued up, don't do anything to override those. //RWT-OEI 10/30/04 - I had to take this out. It was causing your party members to not //engage if they were in Ranged Support but had a melee weapon equipped. They'd often just //stand there and do nothing. //if ( IsObjectPartyMember(OBJECT_SELF) && GetCombatActionsPending(OBJECT_SELF)) //{ // return; //} GN_SetLastRoundData(); int nPartyAI = GetPartyAIStyle(); //Determines how the party should react to intruders int nNPC_AI = GetNPCAIStyle(OBJECT_SELF); //Determines how the individual should react in combat GN_MyPrintString("GENERIC DEBUG *************** AI STYLE = " + GN_ReturnAIStyle()); if(!GN_GetSpawnInCondition(SW_FLAG_COMMONER_BEHAVIOR) && !GN_GetSpawnInCondition(SW_FLAG_SPECTATOR_STATE) && !GN_GetSpawnInCondition(SW_FLAG_AI_OFF) //MODIFIED by Preston Watamaniuk on March 27 //Put this back in to cancel Determine Combat when user actions are present. && !GetUserActionsPending()) { if(GetPartyMemberByIndex(0) != OBJECT_SELF && !GetPlayerRestrictMode()) { if((IsObjectPartyMember(OBJECT_SELF) && !GetPlayerRestrictMode()) || !IsObjectPartyMember(OBJECT_SELF)) { if(nNPC_AI == NPC_AISTYLE_MELEE_ATTACK) { if(GetIsObjectValid(oIntruder)) { ClearAllActions(); ActionAttack(oIntruder); return; } else { object oDefault = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY); if(GetIsObjectValid(oDefault)) { ClearAllActions(); ActionAttack(oDefault); return; } } return; } //Always try and run a force field at the beginning of combat. if(GN_ActivateForceField() == TRUE) { GN_MyPrintString("GENERIC DEBUG *************** Terminating AI from Shields"); return; } //Always try to use Force Resistance at the beginning of combat. if(GN_ActivateResistances() == TRUE){return;} //P.W. (June 9) - Malak AI put into the generics if(GN_GetSpawnInCondition(SW_FLAG_MALAK_AI_ON) == TRUE) { if(GN_RunMalakAIRoutine() == TRUE){return;} } //If the boss flag is set then the creature will run the boss AI first. if(GN_GetSpawnInCondition(SW_FLAG_BOSS_AI) == TRUE) { if(GN_RunBossAIRoutine(oIntruder) == TRUE){return;} } //RWT-OEI 03/18/04 - This AI just runs around healing others if ( nNPC_AI == NPC_AISTYLE_HEALER ) { GN_RunHealerAIRoutine(oIntruder); //If this AI is active, they get no other options, so //just return after it executes. return; } //RWT-OEI 03/22/04 - This AI moves randomly between shooting if ( nNPC_AI == NPC_AISTYLE_SKIRMISH ) { //If this AI returns 1, then no other AIs should be //executed. If it returns 0, then other attacks are fine. if ( GN_RunSkirmishAIRoutine(oIntruder) ) return; } //RWT-OEI 03/23/04 - If we get here, we may be in skirmish // mode, but not moving. Therefore just attack like normal. if(nNPC_AI == NPC_AISTYLE_DEFAULT_ATTACK || nNPC_AI == NPC_AISTYLE_SKIRMISH ) { //ACTIVE if(GN_RunDefaultAIRoutine(oIntruder) == TRUE) { return; } } else if(nNPC_AI == NPC_AISTYLE_GRENADE_THROWER) { //ACTIVE if(GN_RunGrenadeAIRoutine(oIntruder) == TRUE){return;} } else if(nNPC_AI == NPC_AISTYLE_JEDI_SUPPORT) { //ACTIVE if ( GN_CheckRangeFromLeader(10.0f) == TRUE ) { return; } if(GN_RunJediSupportAIRoutine(oIntruder) == TRUE){return;} } //JAB-OEI 7/3/04 else if(nNPC_AI == NPC_AISTYLE_TURTLE) { if(GN_RunTurtleAIRoutine(oIntruder) == TRUE){return;} } else if(nNPC_AI == NPC_AISTYLE_PARTY_AGGRO) { //RWT-OEI 08/28/04 Aggressive just acts like it //did in KotOR1. //AurPostString("Aggro AI", 10, 30, 5.0f); if (GN_RunDefaultAIRoutine(oIntruder) == TRUE) { return; } } else if(nNPC_AI == NPC_AISTYLE_PARTY_DEFENSE) { //RWT-OEI 08/28/04 - Defensive acts just like //default in KotOR1, except never more than //10 meters away from the party leader at any //time. //AurPostString("Defense AI", 10, 30, 5.0f); if(GN_CheckRangeFromLeader(5.0f) == TRUE) { return; } GN_RunDefaultAIRoutine(oIntruder); return; } else if(nNPC_AI == NPC_AISTYLE_PARTY_RANGED) { //RWT-OEI 08/28/04 - Ranged Support will //stand back and use ranged weapons. If //they are attacked by someone with melee //they will switch to their alternate //weapon config if that config has //melee weapons in it. Must stay within //10 meters of party leader at any time. if (GN_CheckRangeFromLeader(10.0f) == TRUE) { return; } //AurPostString("Ranged AI", 20, 20, 5.0f); GN_RunRangedSupportAIRoutine(oIntruder); return; } else if (nNPC_AI == NPC_AISTYLE_PARTY_STATIONARY) { //RWT-OEI 08/28/04 - Just stand in one place and //shoot. Don't move under any circumstances whenever //in combat mode. This mode should probably override //movement outside of combat as well. That would //be handled elsewhere. //AurPostString("Stationary AI", 10, 30, 5.0f); //GN_RunStationaryAIRoutine(oIntruder); GN_RunRangedSupportAIRoutine(oIntruder); return; } else if (nNPC_AI == NPC_AISTYLE_PARTY_SUPPORT) { //RWT-OEI 08/28/04 - If is Jedi, then will run //default Jedi routine from KotOR1. If not, //will run Grenadier routine from KotOR1. if (GN_IsJedi(OBJECT_SELF)) { //AurPostString("JediSupport AI", 10, 30, 5.0f); GN_RunJediSupportAIRoutine(oIntruder); } else { //AurPostString("Grenadier AI", 10, 30, 5.0f); GN_RunGrenadeAIRoutine(oIntruder); } return; } else if ( nNPC_AI == NPC_AISTYLE_PARTY_REMOTE ) { //RWT-OEI 09/08/04 - First priority is to stay in range //of owner. //If we need to move as a result, don't do anything else. if (GN_CheckRangeFromOwner(2.0f) == TRUE) { return; } //Otherwise, call the default puppet AI GN_RunDefaultPuppetAIRoutine(oIntruder); return; } else if( nNPC_AI == NPC_AISTYLE_MONSTER_POWERS ) { // DJS-OEI 9/27/2004 if( GN_RunMonsterPowersAIRoutine(oIntruder) == TRUE ) { return; } else { GN_RunDefaultAIRoutine(oIntruder); } } else { //Run a default AI no matter what. RWT-OEI 08/20/04 if (GN_RunDefaultAIRoutine(oIntruder) == TRUE) { return; } } } } } if(GN_DoPostDCRChecks()) { GN_MyPrintString("GENERIC DEBUG *************** DETERMINE COMBAT ROUND END"); } GN_MyPrintString("GENERIC DEBUG *************** WARNING DETERMINE COMBAT ROUND FAILURE"); } 2: Since they use the Boss Script, the subroutine GN_RunAiBossRoutine() is called. It looks like it should always be called. This subroutine looks like this: Show spoiler (hidden content - requires Javascript to show) int GN_RunBossAIRoutine(object oIntruder = OBJECT_INVALID) { GN_MyPrintString("GENERIC DEBUG *************** Boss AI Start"); object oTarget = GN_CheckIfInjured(); if(GetIsObjectValid(oTarget)) { if(GN_TalentMasterRoutine(GEN_TALENT_HEALING, oTarget)) {return TRUE;} } if(GN_EquipAppropriateWeapon()) { GN_MyPrintString("GENERIC DEBUG *************** Switching Weapons"); } if(GN_RunBossGrenadeAI() == TRUE) {return TRUE;} else if(GN_RunBossAOEPowerRoutine() == TRUE) {return TRUE;} else if(GN_RunBossTargetedRoutine() == TRUE) {return TRUE;} GN_MyPrintString("GENERIC DEBUG *************** Boss AI: Fall Through"); return GN_RunDefaultAIRoutine(); } So this should always run one of GN_RunBossGrenadeAI(), GNRunBossAOEPowerRoutine(), or GN_RunBossTargetedRoutine() unless they all fail for some reason -- or unless he's under 50% health prompting him to spam drain. Here you will fine all three in the sequence they should run... if any of them return true, then BossAI() should stop running. Grenade Show spoiler (hidden content - requires Javascript to show) int GN_RunBossGrenadeAI() { GN_MyPrintString("GENERIC DEBUG *************** Boss AI: Grenade Function Starting"); talent tUse; object oCheck = GN_FindGrenadeTarget(); GN_MyPrintString("GENERIC DEBUG *************** Boss AI: Grenade Target = " + GN_ITS(GetIsObjectValid(oCheck))); int nDroid; if(GetIsObjectValid(oCheck)) { if(GetRacialType(oCheck) == RACIAL_TYPE_DROID) { nDroid == TRUE; } tUse = GN_GetBossCombatMove(SW_BOSS_ATTACK_TYPE_GRENADE, nDroid); if(GetIsTalentValid(tUse)) { GN_MyPrintString("GENERIC DEBUG *************** Clear 1460"); ClearAllActions(); ActionUseTalentOnObject(tUse, oCheck); GN_MyPrintString("GENERIC DEBUG *************** Boss AI: AOE Success"); return TRUE; } } GN_MyPrintString("GENERIC DEBUG *************** Boss AI: AOE Failure"); return FALSE; } AOE Show spoiler (hidden content - requires Javascript to show) int GN_RunBossAOEPowerRoutine() { GN_MyPrintString("GENERIC DEBUG *************** Boss AI: AOE Function Starting"); talent tUse; object oCheck = GN_FindAOETarget(); GN_MyPrintString("GENERIC DEBUG *************** Boss AI: AOE Target = " + GN_ITS(GetIsObjectValid(oCheck))); int nDroid; if(GetIsObjectValid(oCheck)) { if(GetRacialType(oCheck) == RACIAL_TYPE_DROID) { nDroid == TRUE; } tUse = GN_GetBossCombatMove(SW_BOSS_ATTACK_TYPE_FORCE_POWER, nDroid); if(GetIsTalentValid(tUse)) { ClearAllActions(); ActionUseTalentOnObject(tUse, oCheck); GN_MyPrintString("GENERIC DEBUG *************** Boss AI: AOE Success"); return TRUE; } } GN_MyPrintString("GENERIC DEBUG *************** Boss AI: AOE Failure"); return FALSE; } Targeted Show spoiler (hidden content - requires Javascript to show) int GN_RunBossTargetedRoutine() { GN_MyPrintString("GENERIC DEBUG *************** Boss AI: Start Targeted Action Routine"); talent tUse; object oTarget; int nDroid; int nRand = d6(); int nCnt = 1; if(nRand < 4){nRand = 1;} if(nRand == 4){nRand = 2;} if(nRand == 5){nRand = 3;} if(nRand == 6){nRand = 4;} GN_MyPrintString("GENERIC DEBUG *************** Boss AI: Get the #" + GN_ITS(nRand) + " target"); object oFind = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF, nCnt, CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN); GN_MyPrintString("GENERIC DEBUG *************** Boss AI: Initial oFind Search = " + GN_ReturnDebugName(oFind)); while(GetIsObjectValid(oFind) && nCnt <= nRand) { GN_MyPrintString("GENERIC DEBUG *************** Boss AI: Valid oFind = " + GN_ReturnDebugName(oFind) + " nCnt = " + GN_ITS(nCnt)); if(GetIsObjectValid(oFind)) { GN_MyPrintString("GENERIC DEBUG *************** Boss AI: Valid oTarget Set As = " + GN_ReturnDebugName(oFind)); oTarget = oFind; } nCnt++; oFind = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF, nCnt, CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN); } //DEBUG STATEMENTS int nX = TRUE; if(nX == TRUE) { if(GetIsTalentValid(tUse)) { if(GetTypeFromTalent(tUse) == TALENT_TYPE_FEAT) { GN_MyPrintString("GENERIC DEBUG *************** Boss AI: Talent Feat = " + GN_ITS(GetIdFromTalent(tUse))); } else if(GetTypeFromTalent(tUse) == TALENT_TYPE_FORCE) { GN_MyPrintString("GENERIC DEBUG *************** Boss AI: Talent Power = " + GN_ITS(GetIdFromTalent(tUse))); } } } if(GetIsObjectValid(oTarget)) { if(GetRacialType(oTarget) == RACIAL_TYPE_DROID) { GN_MyPrintString("GENERIC DEBUG *************** Boss AI: Get Boss Combat Move AI Droid"); nDroid = TRUE; } tUse = GN_GetBossCombatMove(SW_BOSS_ATTACK_TYPE_NPC, nDroid); tUse = GN_CheckThrowLightSaberUsage(oTarget, tUse); tUse = GN_CheckNonDroidForcePower(oTarget, tUse); //MODIFIED by Preston Watamaniuk on April 2, 2003 //Added this check to make the Droid setting was used for non-specific attacks. GN_MyPrintString("GENERIC DEBUG *************** Boss AI: Target = " + GN_ITS(GetIsObjectValid(oTarget))); GN_MyPrintString("GENERIC DEBUG *************** Boss AI: Talent = " + GN_ITS(GetIsTalentValid(tUse))); if(GetIsTalentValid(tUse) && GetIsObjectValid(oTarget)) { ClearAllActions(); ActionUseTalentOnObject(tUse, oTarget); GN_MyPrintString("GENERIC DEBUG *************** Boss AI: Targeted Power Success"); return TRUE; } } GN_MyPrintString("GENERIC DEBUG *************** Boss AI: Targeted Failure"); return FALSE; } For Darth Nihilus, then means he should Run the GrenadeAI, which will fail because Nihilus doesn't know any grenade moves. Then the AOE one should run, which should generally succeed, because he does know AOE moves. The most important function called by these is from the gensupport include, It gets the bosses next move. Here is the code Get Boss Move Helper routine: Show spoiler (hidden content - requires Javascript to show) talent GN_GetBossCombatMove(int nBossAttackType, int nDroid = FALSE) { talent tInvalid; talent tUse; int bValid = FALSE; if(nBossAttackType == SW_BOSS_ATTACK_TYPE_GRENADE || nBossAttackType == SW_BOSS_ATTACK_ANY) { tUse = GN_GetGrenadeTalent(nDroid); if(GetIsTalentValid(tUse)) { GN_MyPrintString("GENERIC DEBUG *************** Boss AI: Grenade Talent Chosen"); return tUse; } } if(nBossAttackType == SW_BOSS_ATTACK_TYPE_FORCE_POWER || nBossAttackType == SW_BOSS_ATTACK_ANY) { tUse = GN_GetAOEForcePower(nDroid); if(GetIsTalentValid(tUse)) { GN_MyPrintString("GENERIC DEBUG *************** Boss AI: AOE Force Power Talent Chosen"); return tUse; } } if(nBossAttackType == SW_BOSS_ATTACK_TYPE_NPC || nBossAttackType == SW_BOSS_ATTACK_ANY) { if(d100() > 50) { tUse = GN_GetTargetedForcePower(nDroid); if(GetIsTalentValid(tUse)) { GN_MyPrintString("GENERIC DEBUG *************** Boss AI: Targeted Force Power Talent Chosen"); return tUse; } tUse = GN_GetAOEForcePower(nDroid); if(GetIsTalentValid(tUse)) { GN_MyPrintString("GENERIC DEBUG *************** Boss AI: AOE Force Power Talent Chosen"); return tUse; } } if(GN_GetWeaponType() == 1) { GN_MyPrintString("GENERIC DEBUG *************** Boss AI: Melee Feat Talent Chosen"); tUse = GetCreatureTalentRandom(0x1104); //Only melee feats use this code } else { GN_MyPrintString("GENERIC DEBUG *************** Boss AI: Range Feat Talent Chosen"); tUse = GetCreatureTalentRandom(0x1111); //Only ranged feats use this code } if(GetIsTalentValid(tUse)) { return tUse; } } else if(nBossAttackType == SW_BOSS_ATTACK_TYPE_PC || nBossAttackType == SW_BOSS_ATTACK_ANY) { if(d100() > 70) { tUse = GN_GetTargetedForcePower(nDroid); if(GetIsTalentValid(tUse)) { return tUse; } tUse = GN_GetAOEForcePower(nDroid); if(GetIsTalentValid(tUse)) { return tUse; } } tUse = GetCreatureTalentRandom(0x1104); //Only melee feats use this code if(GetIsTalentValid(tUse)) { return tUse; } GN_MyPrintString("GENERIC DEBUG *************** Boss AI: No Feats Available"); } //Comment this out so that the boss AI handles the failure not this function. /* if(!GetIsTalentValid(tUse) && nBossAttackType != SW_BOSS_ATTACK_ANY) { tUse = GN_GetBossCombatMove(SW_BOSS_ATTACK_ANY); } */ return tUse; } However, he never uses them. Ever. All he does is randomly hit people with his lightsaber until it's time to start spamming life drain. Even if he were following the targeted AIScript because AOEPowerRoutine() failed (Because he couldn't find the right AOE target with GN_FindAOETarget(), he should still use force powers 50% of the time, since The Helper method GetBossCombatMove does a DCroll() and if it's >50 picks a force powers when called with nBossAttackType == SW_BOSS_ATTACK_TYPE_PC, But he doesn't. What the hell is wrong with this boss? have I misunderstood something? I want to make him actually use force powers (I want to do this for all enemies by giving them the Boss AI, but first I've got to make the Boss AI work...) Any idea why he's not exhibiting the expected behavior... or more accurately, why no enemy in the game seems to actually use force powers the way these functions suggest? EDIT: Added in important subroutines Link to comment Share on other sites More sharing options...
Fair Strides 2 Posted February 9, 2016 Share Posted February 9, 2016 It will be extremely helpful if you post those other Boss AI routines so we can see why they might not work. Link to comment Share on other sites More sharing options...
quantumpencil Posted February 9, 2016 Author Share Posted February 9, 2016 It will be extremely helpful if you post those other Boss AI routines so we can see why they might not work. Done, if anything else is needed I'm happy to provide. I've spent some time digging into this so I feel I have a pretty good idea of what the code *says* Link to comment Share on other sites More sharing options...
Recommended Posts
Archived
This topic is now archived and is closed to further replies.