451 lines
14 KiB
Plaintext
451 lines
14 KiB
Plaintext
|
//::///////////////////////////////////////////////
|
||
|
//:: Beholder common functions
|
||
|
//:: x2_inc_behcommon
|
||
|
//:: Copyright (c) 2003 Bioware Corp.
|
||
|
//:://////////////////////////////////////////////
|
||
|
/*
|
||
|
|
||
|
Contains functions taken from the Companion and Monster AI
|
||
|
|
||
|
*/
|
||
|
//:://////////////////////////////////////////////
|
||
|
//:: Created By: Tony K
|
||
|
//:: Created On: May, 2008
|
||
|
//:://////////////////////////////////////////////
|
||
|
|
||
|
#include "x0_i0_spells"
|
||
|
|
||
|
|
||
|
// As MyPrintString, but to screen instead of log
|
||
|
void Jug_Debug2(string sString)
|
||
|
{
|
||
|
SendMessageToPC(GetFactionLeader(GetFirstPC()), sString);
|
||
|
}
|
||
|
|
||
|
|
||
|
void SetObjectArray(object oSource, string sName, int iElem, object oElem)
|
||
|
{
|
||
|
string sFull = sName+IntToString(iElem);
|
||
|
SetLocalObject(oSource,sFull,oElem);
|
||
|
}
|
||
|
|
||
|
|
||
|
object GetObjectArray(object oSource, string sName, int iElem)
|
||
|
{
|
||
|
string sFull = sName+IntToString(iElem);
|
||
|
return GetLocalObject(oSource,sFull);
|
||
|
}
|
||
|
|
||
|
|
||
|
void SetIntArray(object oSource, string sName, int iElem, int iState)
|
||
|
{
|
||
|
string sFull = sName+IntToString(iElem);
|
||
|
SetLocalInt(oSource,sFull,iState);
|
||
|
}
|
||
|
|
||
|
|
||
|
int GetIntArray(object oSource, string sName, int iElem)
|
||
|
{
|
||
|
string sFull = sName+IntToString(iElem);
|
||
|
return GetLocalInt(oSource,sFull);
|
||
|
}
|
||
|
|
||
|
|
||
|
void SetFloatArray(object oSource, string sName, int iElem, float fVal)
|
||
|
{
|
||
|
string sFull = sName+IntToString(iElem);
|
||
|
SetLocalFloat(oSource,sFull,fVal);
|
||
|
}
|
||
|
|
||
|
|
||
|
float GetFloatArray(object oSource, string sName, int iElem)
|
||
|
{
|
||
|
string sFull = sName+IntToString(iElem);
|
||
|
return GetLocalFloat(oSource,sFull);
|
||
|
}
|
||
|
|
||
|
|
||
|
// returns TRUE if a humanoid
|
||
|
int GetIsHumanoid(int nRacial)
|
||
|
{
|
||
|
return
|
||
|
(nRacial == RACIAL_TYPE_DWARF) ||
|
||
|
(nRacial == RACIAL_TYPE_ELF) ||
|
||
|
(nRacial == RACIAL_TYPE_GNOME) ||
|
||
|
(nRacial == RACIAL_TYPE_HUMANOID_GOBLINOID) ||
|
||
|
(nRacial == RACIAL_TYPE_HALFLING) ||
|
||
|
(nRacial == RACIAL_TYPE_HUMAN) ||
|
||
|
(nRacial == RACIAL_TYPE_HALFELF) ||
|
||
|
(nRacial == RACIAL_TYPE_HALFORC) ||
|
||
|
(nRacial == RACIAL_TYPE_HUMANOID_MONSTROUS) ||
|
||
|
(nRacial == RACIAL_TYPE_HUMANOID_ORC) ||
|
||
|
(nRacial == RACIAL_TYPE_HUMANOID_REPTILIAN);
|
||
|
}
|
||
|
|
||
|
|
||
|
// check if target has a disabling effect
|
||
|
int GetIsDisabled(object oTarget)
|
||
|
{
|
||
|
effect eCheck = GetFirstEffect(oTarget);
|
||
|
while(GetIsEffectValid(eCheck))
|
||
|
{
|
||
|
switch (GetEffectType(eCheck))
|
||
|
{
|
||
|
case EFFECT_TYPE_PARALYZE:
|
||
|
case EFFECT_TYPE_STUNNED:
|
||
|
case EFFECT_TYPE_FRIGHTENED:
|
||
|
case EFFECT_TYPE_SLEEP:
|
||
|
case EFFECT_TYPE_DAZED:
|
||
|
case EFFECT_TYPE_CONFUSED:
|
||
|
case EFFECT_TYPE_TURNED:
|
||
|
case EFFECT_TYPE_PETRIFY:
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
eCheck = GetNextEffect(oTarget);
|
||
|
}
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
|
||
|
// This constant somewhat matches taking a henchmen hit dice and converting to CR rating
|
||
|
const float HENCH_HITDICE_TO_CR = 0.7;
|
||
|
|
||
|
const string sThreatRating = "HenchThreatRating";
|
||
|
|
||
|
// get threat rating of target, scale by hit dice
|
||
|
float GetRawThreatRating(object oTarget)
|
||
|
{
|
||
|
int lastTestHitDice = GetLocalInt(oTarget, sThreatRating);
|
||
|
int hitDice = GetHitDice(oTarget);
|
||
|
float fThreat;
|
||
|
if (GetHitDice(oTarget) == lastTestHitDice)
|
||
|
{
|
||
|
fThreat = GetLocalFloat(oTarget, sThreatRating);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
fThreat = IntToFloat(GetHitDice(oTarget));
|
||
|
fThreat = pow(1.5, fThreat * HENCH_HITDICE_TO_CR);
|
||
|
int iAssocType = GetAssociateType(oTarget);
|
||
|
if (iAssocType == ASSOCIATE_TYPE_FAMILIAR)
|
||
|
{
|
||
|
fThreat *= 0.1;
|
||
|
}
|
||
|
else if (iAssocType != ASSOCIATE_TYPE_NONE && iAssocType != ASSOCIATE_TYPE_HENCHMAN)
|
||
|
{
|
||
|
fThreat *= 0.8;
|
||
|
}
|
||
|
if ((GetLevelByClass(CLASS_TYPE_WIZARD, oTarget) >= 5) || (GetLevelByClass(CLASS_TYPE_SORCERER, oTarget) >= 6))
|
||
|
{
|
||
|
fThreat *= 1.3;
|
||
|
}
|
||
|
else if ((GetLevelByClass(CLASS_TYPE_DRAGON, oTarget) >= 11))
|
||
|
{
|
||
|
// dragons are extra tough
|
||
|
fThreat *= 1.5;
|
||
|
}
|
||
|
if (fThreat < 0.001)
|
||
|
{
|
||
|
fThreat = 0.001;
|
||
|
}
|
||
|
SetLocalFloat(oTarget, sThreatRating, fThreat);
|
||
|
SetLocalInt(oTarget, sThreatRating, hitDice);
|
||
|
}
|
||
|
return fThreat;
|
||
|
}
|
||
|
|
||
|
|
||
|
int gHenchSpellTargetObjects;
|
||
|
|
||
|
const string BEHOLDER_SPELL_TARGET_OBJECTS = "BeholderSpellTarget";
|
||
|
|
||
|
|
||
|
// gets list of possible targets that are seen or heard
|
||
|
void HenchInitSpellTargetObjects(object oIntruder)
|
||
|
{
|
||
|
if (gHenchSpellTargetObjects != 0)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
int iMaxNumberToFind = GetAbilityScore(OBJECT_SELF, ABILITY_INTELLIGENCE) - 5;
|
||
|
if (iMaxNumberToFind > 15)
|
||
|
{
|
||
|
iMaxNumberToFind = 15;
|
||
|
}
|
||
|
else if (iMaxNumberToFind < 2)
|
||
|
{
|
||
|
iMaxNumberToFind = 2;
|
||
|
}
|
||
|
int bIntruderFound = !GetIsObjectValid(oIntruder);;
|
||
|
object oLastTarget = OBJECT_SELF;
|
||
|
int iCurSeenIndex = 1;
|
||
|
while (gHenchSpellTargetObjects <= iMaxNumberToFind)
|
||
|
{
|
||
|
object oCurSeen = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY,
|
||
|
OBJECT_SELF, iCurSeenIndex++, CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN,
|
||
|
CREATURE_TYPE_IS_ALIVE, TRUE);
|
||
|
if (!GetIsObjectValid(oCurSeen))
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
SetLocalObject(oLastTarget, BEHOLDER_SPELL_TARGET_OBJECTS, oCurSeen);
|
||
|
// Jug_Debug(GetName(OBJECT_SELF) + " adding seen target " + GetName(oCurSeen));
|
||
|
oLastTarget = oCurSeen;
|
||
|
gHenchSpellTargetObjects ++;
|
||
|
if (oCurSeen == oIntruder)
|
||
|
{
|
||
|
bIntruderFound = TRUE;
|
||
|
}
|
||
|
}
|
||
|
// limit the max number of heard targets to find
|
||
|
iMaxNumberToFind /= 2;
|
||
|
int iCurHeardIndex = 1;
|
||
|
while ((gHenchSpellTargetObjects <= iMaxNumberToFind) && (iCurHeardIndex < 3))
|
||
|
{
|
||
|
object oCurHeard = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY,
|
||
|
OBJECT_SELF, iCurHeardIndex++, CREATURE_TYPE_PERCEPTION, PERCEPTION_HEARD_AND_NOT_SEEN,
|
||
|
CREATURE_TYPE_IS_ALIVE, TRUE);
|
||
|
if (!GetIsObjectValid(oCurHeard))
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
if (LineOfSightObject(OBJECT_SELF, oCurHeard))
|
||
|
{
|
||
|
SetLocalObject(oLastTarget, BEHOLDER_SPELL_TARGET_OBJECTS, oCurHeard);
|
||
|
// Jug_Debug(GetName(OBJECT_SELF) + " adding heard target " + GetName(oCurHeard));
|
||
|
oLastTarget = oCurHeard;
|
||
|
gHenchSpellTargetObjects ++;
|
||
|
if (oCurHeard == oIntruder)
|
||
|
{
|
||
|
bIntruderFound = TRUE;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (!bIntruderFound)
|
||
|
{
|
||
|
SetLocalObject(oLastTarget, BEHOLDER_SPELL_TARGET_OBJECTS, oIntruder);
|
||
|
// Jug_Debug(GetName(OBJECT_SELF) + " adding intruder " + GetName(oIntruder));
|
||
|
oLastTarget = oIntruder;
|
||
|
}
|
||
|
DeleteLocalObject(oLastTarget, BEHOLDER_SPELL_TARGET_OBJECTS);
|
||
|
}
|
||
|
|
||
|
|
||
|
// returns chance 0.0 to 1.0 for d20 roll
|
||
|
float Getd20Chance(int limit)
|
||
|
{
|
||
|
limit += 21;
|
||
|
if (limit >= 20)
|
||
|
{
|
||
|
return 1.0;
|
||
|
}
|
||
|
if (limit <= 0)
|
||
|
{
|
||
|
return 0.0;
|
||
|
}
|
||
|
return IntToFloat(limit) / 20.0;
|
||
|
}
|
||
|
|
||
|
|
||
|
// returns chance 0.0 to 1.0 for d20 roll 1, fail 20 success
|
||
|
float Getd20ChanceLimited(int limit)
|
||
|
{
|
||
|
if (limit <= 1)
|
||
|
{
|
||
|
return 0.05;
|
||
|
}
|
||
|
if (limit >= 19)
|
||
|
{
|
||
|
return 0.95;
|
||
|
}
|
||
|
return IntToFloat(limit) / 20.0;
|
||
|
}
|
||
|
|
||
|
|
||
|
const int HENCH_BLEED_NEGHPS = -10;
|
||
|
|
||
|
// returns damage amount on target 0.0 (none) to 1.0 (lethal damage)
|
||
|
float CalculateDamageWeight(float damageAmount, object oTarget)
|
||
|
{
|
||
|
//Jug_Debug(GetName(oTarget) + " HP " + IntToString(GetCurrentHitPoints(oTarget)));
|
||
|
int currentHitPoints = GetCurrentHitPoints(oTarget);
|
||
|
if (currentHitPoints < 1)
|
||
|
{
|
||
|
// assume a bleed system is used (HENCH_BLEED_NEGHPS is a negative number)
|
||
|
currentHitPoints -= HENCH_BLEED_NEGHPS;
|
||
|
if (currentHitPoints < 1)
|
||
|
{
|
||
|
currentHitPoints = 1;
|
||
|
}
|
||
|
}
|
||
|
damageAmount /= IntToFloat(currentHitPoints);
|
||
|
if (damageAmount > 1.0)
|
||
|
{
|
||
|
damageAmount = 1.0;
|
||
|
}
|
||
|
return damageAmount;
|
||
|
}
|
||
|
|
||
|
|
||
|
struct sEnhancementLevel
|
||
|
{
|
||
|
float breach;
|
||
|
float dispel;
|
||
|
};
|
||
|
|
||
|
int giBestDispelCastingLevel;
|
||
|
|
||
|
// chance that dispel will work
|
||
|
float GetDispelChance(object oCreator)
|
||
|
{
|
||
|
if (GetIsObjectValid(oCreator))
|
||
|
{
|
||
|
int nCasterLevel = GetCasterLevel(oCreator); // this isn't always accurate (reset every spell)
|
||
|
if (nCasterLevel <= 0)
|
||
|
{
|
||
|
nCasterLevel = GetHitDice(oCreator);
|
||
|
}
|
||
|
return Getd20Chance(giBestDispelCastingLevel - nCasterLevel - 11);
|
||
|
}
|
||
|
return Getd20Chance(giBestDispelCastingLevel - 21 /* 10 - 11 */);
|
||
|
}
|
||
|
|
||
|
|
||
|
const float fMaxEnhancementWeight = 0.5;
|
||
|
|
||
|
// Jugalator Script Additions
|
||
|
// Return 1 if target is enhanced with a beneficial
|
||
|
// spell that can be dispelled (= from a spell script), 2 if the
|
||
|
// effects can be breached, 0 otherwise.
|
||
|
// TK changed to not look for magical effects only
|
||
|
struct sEnhancementLevel Jug_GetHasBeneficialEnhancement(object oTarget)
|
||
|
{
|
||
|
struct sEnhancementLevel result;
|
||
|
effect eCheck = GetFirstEffect(oTarget);
|
||
|
int lastSpellId = -1;
|
||
|
int bCheckDispel = TRUE;
|
||
|
|
||
|
while (GetIsEffectValid(eCheck))
|
||
|
{
|
||
|
int iType = GetEffectType(eCheck);
|
||
|
if ((iType != EFFECT_TYPE_VISUALEFFECT) && (GetEffectSubType(eCheck) == SUBTYPE_MAGICAL))
|
||
|
{
|
||
|
if (bCheckDispel)
|
||
|
{
|
||
|
// Found an effect applied by a spell script - check the effect type
|
||
|
switch(iType)
|
||
|
{
|
||
|
case EFFECT_TYPE_VISUALEFFECT: // this effect is very common, don't check everything
|
||
|
break;
|
||
|
case EFFECT_TYPE_REGENERATE:
|
||
|
case EFFECT_TYPE_SANCTUARY:
|
||
|
case EFFECT_TYPE_IMMUNITY:
|
||
|
case EFFECT_TYPE_INVULNERABLE:
|
||
|
case EFFECT_TYPE_HASTE:
|
||
|
case EFFECT_TYPE_ELEMENTALSHIELD:
|
||
|
case EFFECT_TYPE_SPELL_IMMUNITY:
|
||
|
case EFFECT_TYPE_SPELLLEVELABSORPTION:
|
||
|
case EFFECT_TYPE_DAMAGE_IMMUNITY_INCREASE:
|
||
|
case EFFECT_TYPE_DAMAGE_INCREASE:
|
||
|
case EFFECT_TYPE_DAMAGE_REDUCTION:
|
||
|
case EFFECT_TYPE_DAMAGE_RESISTANCE:
|
||
|
case EFFECT_TYPE_POLYMORPH:
|
||
|
case EFFECT_TYPE_ETHEREAL:
|
||
|
case EFFECT_TYPE_INVISIBILITY:
|
||
|
if (result.dispel < fMaxEnhancementWeight)
|
||
|
{
|
||
|
result.dispel += 0.5 * GetDispelChance(GetEffectCreator(eCheck));
|
||
|
if (result.dispel >= fMaxEnhancementWeight)
|
||
|
{
|
||
|
result.dispel = fMaxEnhancementWeight;
|
||
|
bCheckDispel = FALSE;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case EFFECT_TYPE_ABILITY_INCREASE:
|
||
|
case EFFECT_TYPE_AC_INCREASE:
|
||
|
case EFFECT_TYPE_ATTACK_INCREASE:
|
||
|
case EFFECT_TYPE_CONCEALMENT:
|
||
|
case EFFECT_TYPE_ENEMY_ATTACK_BONUS:
|
||
|
case EFFECT_TYPE_MOVEMENT_SPEED_INCREASE:
|
||
|
case EFFECT_TYPE_SAVING_THROW_INCREASE:
|
||
|
case EFFECT_TYPE_SEEINVISIBLE:
|
||
|
case EFFECT_TYPE_SKILL_INCREASE:
|
||
|
case EFFECT_TYPE_SPELL_RESISTANCE_INCREASE:
|
||
|
case EFFECT_TYPE_TEMPORARY_HITPOINTS:
|
||
|
case EFFECT_TYPE_TRUESEEING:
|
||
|
case EFFECT_TYPE_ULTRAVISION:
|
||
|
if (result.dispel < fMaxEnhancementWeight)
|
||
|
{
|
||
|
result.dispel += 0.1 * GetDispelChance(GetEffectCreator(eCheck));
|
||
|
if (result.dispel >= fMaxEnhancementWeight)
|
||
|
{
|
||
|
result.dispel = fMaxEnhancementWeight;
|
||
|
bCheckDispel = FALSE;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
/* case EFFECT_TYPE_PARALYZE:
|
||
|
case EFFECT_TYPE_STUNNED:
|
||
|
case EFFECT_TYPE_FRIGHTENED:
|
||
|
case EFFECT_TYPE_SLEEP:
|
||
|
case EFFECT_TYPE_DAZED:
|
||
|
case EFFECT_TYPE_CONFUSED:
|
||
|
case EFFECT_TYPE_TURNED:
|
||
|
case EFFECT_TYPE_PETRIFY:
|
||
|
case EFFECT_TYPE_CUTSCENEIMMOBILIZE:
|
||
|
case EFFECT_TYPE_MESMERIZE:
|
||
|
{
|
||
|
// if disabled don't dispel
|
||
|
struct sEnhancementLevel noResult;
|
||
|
return noResult;
|
||
|
} */
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
eCheck = GetNextEffect(oTarget);
|
||
|
}
|
||
|
|
||
|
// float targetWeight = GetThreatRating(oTarget);
|
||
|
// result.breach *= targetWeight;
|
||
|
// result.dispel *= targetWeight;
|
||
|
|
||
|
if (bCheckDispel)
|
||
|
{
|
||
|
// check if target has summons
|
||
|
object oSummon = GetAssociate(ASSOCIATE_TYPE_SUMMONED, oTarget);
|
||
|
if (GetIsObjectValid(oSummon))
|
||
|
{
|
||
|
// if (GetTag(oSummon) != "X2_S_DRGRED001" && GetTag(oSummon) != "X2_S_MUMMYWARR")
|
||
|
{
|
||
|
result.dispel += GetDispelChance(oTarget) * GetRawThreatRating(oSummon);
|
||
|
// if (result.dispel >= fMaxEnhancementWeight * targetWeight)
|
||
|
// {
|
||
|
// result.dispel = fMaxEnhancementWeight * targetWeight;
|
||
|
// }
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
|
||
|
// gets creature size adjusted for enlarge effects
|
||
|
int GetAdjustedCreatureSize(object oTarget)
|
||
|
{
|
||
|
int nSize = GetCreatureSize(oTarget);
|
||
|
return nSize;
|
||
|
}
|
||
|
|
||
|
|
||
|
const int SAVING_THROW_CHECK_FAILED = 0;
|
||
|
const int SAVING_THROW_CHECK_SUCCEEDED = 1;
|
||
|
const int SAVING_THROW_CHECK_IMMUNE = 2;
|
||
|
|
||
|
|
||
|
const int TOUCH_ATTACK_RESULT_MISS = 0;
|
||
|
const int TOUCH_ATTACK_RESULT_HIT = 1;
|
||
|
const int TOUCH_ATTACK_RESULT_CRITICAL = 2;
|