428 lines
15 KiB
Plaintext
428 lines
15 KiB
Plaintext
//::///////////////////////////////////////////////
|
|
//:: Associate: Heartbeat
|
|
//:: NW_CH_AC1.nss
|
|
//:: Copyright (c) 2001 Bioware Corp.
|
|
//:://////////////////////////////////////////////
|
|
/*
|
|
Move towards master or wait for him
|
|
*/
|
|
//:://////////////////////////////////////////////
|
|
//:: Created By: Preston Watamaniuk
|
|
//:: Created On: Nov 21, 2001
|
|
//:://////////////////////////////////////////////
|
|
|
|
#include "hench_i0_act"
|
|
#include "hench_i0_ai"
|
|
#include "hench_i0_assoc"
|
|
#include "x2_inc_summscale"
|
|
#include "x2_inc_spellhook"
|
|
#include "x0_inc_henai"
|
|
|
|
|
|
void TestItemProperties()
|
|
{
|
|
Jug_Debug(GetName(OBJECT_SELF) + " checking properties");
|
|
int i;
|
|
itemproperty oProp;
|
|
|
|
for (i = 0; i < NUM_INVENTORY_SLOTS; i++)
|
|
{
|
|
object oItem = GetItemInSlot(i, OBJECT_SELF);
|
|
|
|
if (GetIsObjectValid(oItem))
|
|
{
|
|
Jug_Debug("Checking item slot " + IntToString(i));
|
|
oProp = GetFirstItemProperty(oItem);
|
|
while (GetIsItemPropertyValid(oProp))
|
|
{
|
|
|
|
Jug_Debug("prop type " + IntToString(GetItemPropertyType(oProp)) + " duration " + IntToString(GetItemPropertyDurationType(oProp)) + " sub type " + IntToString(GetItemPropertySubType(oProp)) + " cost table " + IntToString(GetItemPropertyCostTable(oProp)) + " cost table value " + IntToString(GetItemPropertyCostTableValue(oProp)));
|
|
|
|
oProp = GetNextItemProperty(oItem);
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int FindCategoryBest(object oTarget, int nCategory, int nCurSpellCount)
|
|
{
|
|
// really want arrays
|
|
int spell1, spell2, spell3;
|
|
int spell1Repeat, spell2Repeat, spell3Repeat;
|
|
int spell1Feat, spell2Feat, spell3Feat;
|
|
int spellsFound;
|
|
|
|
Jug_Debug(GetName(OBJECT_SELF) + " searching category " + IntToString(nCategory));
|
|
int nTry;
|
|
|
|
while (nTry < 10)
|
|
{
|
|
talent tBest = GetCreatureTalentRandom(nCategory, oTarget);
|
|
if(!GetIsTalentValid(tBest))
|
|
{
|
|
break;
|
|
}
|
|
|
|
int nNewSpellID = GetIdFromTalent(tBest);
|
|
int nType = GetTypeFromTalent(tBest);
|
|
|
|
// Jug_Debug(GetName(OBJECT_SELF) + " test talent " + IntToString(nType) + " " + IntToString(nNewSpellID));
|
|
|
|
if (spellsFound == 0)
|
|
{
|
|
Jug_Debug(GetName(OBJECT_SELF) + " found talent " + IntToString(nType) + " " + IntToString(nNewSpellID));
|
|
spell1 = nNewSpellID;
|
|
spell1Feat = nType;
|
|
spellsFound ++;
|
|
}
|
|
else if (spellsFound == 1)
|
|
{
|
|
if (spell1 == nNewSpellID && spell1Feat == nType)
|
|
{
|
|
spell1Repeat ++;
|
|
if (spell1Repeat > 2)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Jug_Debug(GetName(OBJECT_SELF) + " found talent " + IntToString(nType) + " " + IntToString(nNewSpellID));
|
|
spell2 = nNewSpellID;
|
|
spell2Feat = nType;
|
|
spellsFound ++;
|
|
}
|
|
}
|
|
else if (spellsFound == 2)
|
|
{
|
|
if (spell1 == nNewSpellID && spell1Feat == nType)
|
|
{
|
|
spell1Repeat ++;
|
|
if (spell1Repeat > 2)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else if (spell2 == nNewSpellID && spell2Feat == nType)
|
|
{
|
|
spell2Repeat ++;
|
|
if (spell2Repeat > 2)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Jug_Debug(GetName(OBJECT_SELF) + " found talent " + IntToString(nType) + " " + IntToString(nNewSpellID));
|
|
spell3 = nNewSpellID;
|
|
spell3Feat = nType;
|
|
spellsFound ++;
|
|
}
|
|
// at most three
|
|
break;
|
|
}
|
|
nTry ++;
|
|
}
|
|
return spellsFound;
|
|
}
|
|
|
|
|
|
void TestSpells()
|
|
{
|
|
// if (!GetHasEffect(EFFECT_TYPE_ABILITY_DECREASE))
|
|
// {
|
|
// effect eDrain = EffectAbilityDecrease(ABILITY_CHARISMA, 10);
|
|
// ApplyEffectToObject(DURATION_TYPE_PERMANENT, eDrain, OBJECT_SELF);
|
|
// }
|
|
// RemoveEffects(OBJECT_SELF);
|
|
Jug_Debug(GetName(OBJECT_SELF) + " has 6 spell " + IntToString(GetHasSpell(SPELL_CHAIN_LIGHTNING)));
|
|
Jug_Debug(GetName(OBJECT_SELF) + " has 5 spell " + IntToString(GetHasSpell(SPELL_CONE_OF_COLD)));
|
|
Jug_Debug(GetName(OBJECT_SELF) + " has 4 spell " + IntToString(GetHasSpell(SPELL_ICE_STORM)));
|
|
Jug_Debug(GetName(OBJECT_SELF) + " has 3 spell " + IntToString(GetHasSpell(SPELL_FIREBALL)));
|
|
Jug_Debug(GetName(OBJECT_SELF) + " has 2 spell " + IntToString(GetHasSpell(SPELL_BULLS_STRENGTH)));
|
|
Jug_Debug(GetName(OBJECT_SELF) + " has 1 spell " + IntToString(GetHasSpell(SPELL_BURNING_HANDS)));
|
|
Jug_Debug(GetName(OBJECT_SELF) + " has 0 spell " + IntToString(GetHasSpell(SPELL_DAZE)));
|
|
}
|
|
|
|
|
|
void TestSpells2()
|
|
{
|
|
Jug_Debug(GetName(OBJECT_SELF) + " has 4 spell " + IntToString(GetHasSpell(SPELL_CURE_CRITICAL_WOUNDS)) + " check spell id " + IntToString(GetCreatureHasTalent(TalentSpell(SPELL_CURE_CRITICAL_WOUNDS))));
|
|
Jug_Debug(GetName(OBJECT_SELF) + " has 3 spell " + IntToString(GetHasSpell(SPELL_CURE_SERIOUS_WOUNDS)) + " check spell id " + IntToString(GetCreatureHasTalent(TalentSpell(SPELL_CURE_SERIOUS_WOUNDS))));
|
|
Jug_Debug(GetName(OBJECT_SELF) + " has 2 spell " + IntToString(GetHasSpell(SPELL_CURE_MODERATE_WOUNDS)) + " check spell id " + IntToString(GetCreatureHasTalent(TalentSpell(SPELL_CURE_MODERATE_WOUNDS))));
|
|
Jug_Debug(GetName(OBJECT_SELF) + " has 1 spell " + IntToString(GetHasSpell(SPELL_CURE_LIGHT_WOUNDS)) + " check spell id " + IntToString(GetCreatureHasTalent(TalentSpell(SPELL_CURE_LIGHT_WOUNDS))));
|
|
Jug_Debug(GetName(OBJECT_SELF) + " has 0 spell " + IntToString(GetHasSpell(SPELL_CURE_MINOR_WOUNDS)) + " check spell id " + IntToString(GetCreatureHasTalent(TalentSpell(SPELL_CURE_MINOR_WOUNDS))));
|
|
}
|
|
|
|
void TestSpells3()
|
|
{
|
|
Jug_Debug(GetName(OBJECT_SELF) + " has prot vs evil " + IntToString(GetHasSpell(SPELL_PROTECTION_FROM_EVIL)));
|
|
Jug_Debug(GetName(OBJECT_SELF) + " has prot vs good " + IntToString(GetHasSpell(SPELL_PROTECTION_FROM_GOOD)));
|
|
Jug_Debug(GetName(OBJECT_SELF) + " has propt vs align " + IntToString(GetHasSpell(321)));
|
|
Jug_Debug(GetName(OBJECT_SELF) + " has gate " + IntToString(GetHasSpell(SPELL_GATE)));
|
|
Jug_Debug(GetName(OBJECT_SELF) + " check spell id " + IntToString(GetCreatureHasTalent(TalentSpell(SPELL_PROTECTION_FROM_EVIL))));
|
|
Jug_Debug(GetName(OBJECT_SELF) + " check main spell id " + IntToString(GetCreatureHasTalent(TalentSpell(321))));
|
|
}
|
|
|
|
|
|
void GetBestItemSpells()
|
|
{
|
|
object oTarget = GetMaster();
|
|
if (!GetIsObjectValid(oTarget))
|
|
{
|
|
return;
|
|
}
|
|
oTarget = OBJECT_SELF;
|
|
|
|
// check if already silenced
|
|
int nAlreadySilenced = GetHasEffect(EFFECT_TYPE_SILENCE);
|
|
|
|
if (!nAlreadySilenced)
|
|
{
|
|
// ApplyEffectToObject(DURATION_TYPE_PERMANENT, EffectSilence(), oTarget);
|
|
ApplyEffectToObject(DURATION_TYPE_PERMANENT, EffectCutsceneImmobilize(), oTarget);
|
|
// ApplyEffectToObject(DURATION_TYPE_PERMANENT, EffectParalyze(), OBJECT_SELF);
|
|
}
|
|
|
|
int nStep;
|
|
for (nStep = 0; nStep <= 22; nStep++)
|
|
{
|
|
FindCategoryBest(oTarget, nStep, 0);
|
|
}
|
|
|
|
if (!nAlreadySilenced)
|
|
{
|
|
effect eSilence = GetFirstEffect(oTarget);
|
|
while(GetIsEffectValid(eSilence))
|
|
{
|
|
if(GetEffectType(eSilence) == EFFECT_TYPE_CUTSCENEIMMOBILIZE)
|
|
{
|
|
RemoveEffect(oTarget, eSilence);
|
|
// break;
|
|
}
|
|
eSilence = GetNextEffect(oTarget);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void main()
|
|
{
|
|
// GetBestItemSpells();
|
|
// if (GetIsObjectValid(GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR,PLAYER_CHAR_IS_PC, OBJECT_SELF, 1, CREATURE_TYPE_PERCEPTION, PERCEPTION_HEARD)))
|
|
// {
|
|
// Jug_Debug("*****" + GetName(OBJECT_SELF) + " heartbeat action " + IntToString(GetCurrentAction()));
|
|
// }
|
|
|
|
// if (GetIsObjectValid(GetMaster()))
|
|
// {
|
|
// TestItemProperties();
|
|
// Jug_Debug(GetName(OBJECT_SELF) + " challenge rating is " + FloatToString(GetChallengeRating(OBJECT_SELF)));
|
|
// Jug_Debug(GetName(GetMaster()) + " challenge rating is " + FloatToString(GetChallengeRating(GetMaster())));
|
|
// }
|
|
// TestSpells();
|
|
// TestSpells2();
|
|
// TestSpells3();
|
|
|
|
DeleteLocalInt(OBJECT_SELF, HENCH_AI_SCRIPT_RUN_STATE);
|
|
|
|
object oRealMaster = GetRealMaster();
|
|
// destory self if pseudo summons and master not valid
|
|
if (GetLocalInt(OBJECT_SELF, sHenchPseudoSummon))
|
|
{
|
|
oRealMaster = GetLocalObject(OBJECT_SELF, sHenchPseudoSummon);
|
|
if (!GetIsObjectValid(oRealMaster))
|
|
{
|
|
DestroyObject(OBJECT_SELF, 0.1);
|
|
ApplyEffectAtLocation(DURATION_TYPE_TEMPORARY, EffectVisualEffect(VFX_IMP_UNSUMMON), GetLocation(OBJECT_SELF));
|
|
return;
|
|
}
|
|
}
|
|
|
|
// GZ: Fallback for timing issue sometimes preventing epic summoned creatures from leveling up to their master's level.
|
|
// There is a timing issue with the GetMaster() function not returning the fof a creature
|
|
// immediately after spawn. Some code which might appear to make no sense has been added
|
|
// to the nw_ch_ac1 and x2_inc_summon files to work around this
|
|
// This code is only run at the first hearbeat
|
|
int nLevel = SSMGetSummonFailedLevelUp(OBJECT_SELF);
|
|
if (nLevel != 0)
|
|
{
|
|
int nRet;
|
|
if (nLevel == -1) // special shadowlord treatment
|
|
{
|
|
SSMScaleEpicShadowLord(OBJECT_SELF);
|
|
}
|
|
else if (nLevel == -2)
|
|
{
|
|
SSMScaleEpicFiendishServant(OBJECT_SELF);
|
|
}
|
|
else
|
|
{
|
|
nRet = SSMLevelUpCreature(OBJECT_SELF, nLevel, CLASS_TYPE_INVALID);
|
|
if (nRet == FALSE)
|
|
{
|
|
WriteTimestampedLogEntry("WARNING - nw_ch_ac1:: could not level up " + GetTag(OBJECT_SELF) + "!");
|
|
}
|
|
}
|
|
|
|
// regardless if the actual levelup worked, we give up here, because we do not
|
|
// want to run through this script more than once.
|
|
SSMSetSummonLevelUpOK(OBJECT_SELF);
|
|
}
|
|
|
|
// Check if concentration is required to maintain this creature
|
|
X2DoBreakConcentrationCheck();
|
|
|
|
// * if I am dominated, ask for some help
|
|
// TK removed SendForHelp
|
|
// if (GetHasEffect(EFFECT_TYPE_DOMINATED, OBJECT_SELF) == TRUE && GetIsEncounterCreature(OBJECT_SELF) == FALSE)
|
|
// {
|
|
// SendForHelp();
|
|
// }
|
|
|
|
// restore associate settings
|
|
HenchGetDefSettings();
|
|
|
|
if(GetAssociateState(NW_ASC_IS_BUSY))
|
|
{
|
|
return;
|
|
}
|
|
int iAmNotDoingAnything = GetIAmNotDoingAnything();
|
|
if (!iAmNotDoingAnything)
|
|
{
|
|
return;
|
|
}
|
|
if (!GetIsObjectValid(oRealMaster))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(!GetAssociateState(NW_ASC_MODE_STAND_GROUND))
|
|
{
|
|
if (HenchGetIsEnemyPerceived())
|
|
{
|
|
HenchDetermineCombatRound();
|
|
return;
|
|
}
|
|
if (GetLocalInt(OBJECT_SELF, sHenchLastHeardOrSeen))
|
|
{
|
|
// continue to move to target
|
|
MoveToLastSeenOrHeard(FALSE);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ((GetLocalObject(OBJECT_SELF,"NW_L_FORMERMASTER") != OBJECT_INVALID)
|
|
&& (GetLocalInt(OBJECT_SELF, "haveCheckedFM") != 1))
|
|
{
|
|
// Auldar: For a little OnHeartbeat efficiency, I'll set a localint so we don't
|
|
// keep checking stealth mode etc. This will be cleared in NW_CH_JOIN, as will
|
|
// the LocalObject for NW_L_FORMERMASTER.
|
|
// A little quirk with this behaviour - the ActionUseSkill's do not execute until the henchman rejoins
|
|
// however if the player re-loads, or leaves the area and returns, the henchman will no longer be in stealth etc.
|
|
// I couldn't find any way around that odd behaviour, but this works for the most part.
|
|
SetLocalInt(OBJECT_SELF, "haveCheckedFM", 1);
|
|
SetAssociateState(NW_ASC_AGGRESSIVE_SEARCH, FALSE);
|
|
SetLocalInt(OBJECT_SELF, sHenchStealthMode, 0);
|
|
SetActionMode(OBJECT_SELF, ACTION_MODE_STEALTH, FALSE);
|
|
SetActionMode(OBJECT_SELF, ACTION_MODE_DETECT, FALSE);
|
|
}
|
|
|
|
// Check to see if should re-enter stealth mode
|
|
int nStealth = GetLocalInt(GetTopAssociate(), sHenchStealthMode);
|
|
if (nStealth == 1 || nStealth == 2)
|
|
{
|
|
if(!GetActionMode(OBJECT_SELF, ACTION_MODE_STEALTH))
|
|
{
|
|
SetActionMode(OBJECT_SELF, ACTION_MODE_STEALTH, TRUE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(!GetActionMode(oRealMaster, ACTION_MODE_STEALTH))
|
|
{
|
|
SetActionMode(OBJECT_SELF, ACTION_MODE_STEALTH, FALSE);
|
|
}
|
|
}
|
|
|
|
CleanCombatVars();
|
|
|
|
if (GetLocalInt(OBJECT_SELF, henchHealCountStr))
|
|
{
|
|
ExecuteScript("hench_o0_heal", OBJECT_SELF);
|
|
return;
|
|
}
|
|
if (GetLocalInt(OBJECT_SELF, henchBuffCountStr))
|
|
{
|
|
ActionDoCommand(ActionWait(2.0));
|
|
ActionDoCommand(ExecuteScript("hench_o0_enhanc", OBJECT_SELF));
|
|
return;
|
|
}
|
|
|
|
if (HenchCheckArea())
|
|
{
|
|
return;
|
|
}
|
|
// Pausanias: Hench tends to get stuck on follow.
|
|
if (GetCurrentAction(OBJECT_SELF) == ACTION_FOLLOW)
|
|
{
|
|
if (GetDistanceToObject(oRealMaster) >= 2.2 &&
|
|
GetAssociateState(NW_ASC_DISTANCE_2_METERS)) return;
|
|
if (GetDistanceToObject(oRealMaster) >= 4.2 &&
|
|
GetAssociateState(NW_ASC_DISTANCE_4_METERS)) return;
|
|
if (GetDistanceToObject(oRealMaster) >= 6.2 &&
|
|
GetAssociateState(NW_ASC_DISTANCE_6_METERS)) return;
|
|
ClearAllActions();
|
|
}
|
|
|
|
if (GetLocalInt(OBJECT_SELF,"SwitchedToMelee") &&
|
|
GetAssociateState(NW_ASC_USE_RANGED_WEAPON))
|
|
{
|
|
ClearAllActions();
|
|
ClearWeaponStates();
|
|
HenchEquipDefaultWeapons(OBJECT_SELF, TRUE);
|
|
return;
|
|
}
|
|
|
|
int bIsScouting = GetLocalInt(OBJECT_SELF, sHenchScoutingFlag);
|
|
if (bIsScouting)
|
|
{
|
|
if (GetDistanceToObject(oRealMaster) < 6.0)
|
|
{
|
|
SpeakString(sHenchGetOutofWay);
|
|
}
|
|
object oScoutTarget = GetLocalObject(OBJECT_SELF, sHenchScoutTarget);
|
|
if (GetDistanceBetween(oScoutTarget, oRealMaster) > henchMaxScoutDistance)
|
|
{
|
|
DeleteLocalInt(OBJECT_SELF, sHenchScoutingFlag);
|
|
bIsScouting = FALSE;
|
|
}
|
|
else
|
|
{
|
|
if (CheckStealth() && !GetActionMode(OBJECT_SELF, ACTION_MODE_STEALTH))
|
|
{
|
|
SetActionMode(OBJECT_SELF, ACTION_MODE_STEALTH, TRUE);
|
|
}
|
|
ActionMoveToObject(oScoutTarget, FALSE, 1.0);
|
|
}
|
|
}
|
|
|
|
if(!bIsScouting && !GetAssociateState(NW_ASC_MODE_STAND_GROUND) &&
|
|
(GetAssociateState(NW_ASC_HAVE_MASTER) && !GetIsFighting(OBJECT_SELF) &&
|
|
GetDistanceToObject(oRealMaster) > GetFollowDistance()))
|
|
{
|
|
ClearAllActions();
|
|
ActionForceFollowObject(oRealMaster, GetFollowDistance());
|
|
}
|
|
|
|
if(GetSpawnInCondition(NW_FLAG_HEARTBEAT_EVENT))
|
|
{
|
|
SignalEvent(OBJECT_SELF, EventUserDefined(EVENT_HEARTBEAT));
|
|
}
|
|
}
|
|
|
|
|
|
|