Acr spells i.nss

From ALFA
Jump to: navigation, search

//////////////////////////////////////////////////////////////////////////////// // // System Name : ALFA Core Rules // Filename : acr_spells_i.nss // Version : 0.1 // Date : 2011-05-26 // Author : Ronan // // Local Variable Prefix = None // // // Dependencies external of nwscript: None // // Description // Utility functions used by ALFA's modified spells. // // Revision History // 2011-05-26 Ronan Initial version. // 2012-05-29 Foam Added ACR_GetSpellDuration. ////////////////////////////////////////////////////////////////////////////////

  1. ifndef ACR_SPELLS_I
  2. define ACR_SPELLS_I

//////////////////////////////////////////////////////////////////////////////// // Constants /////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////

const float ACR_DURATION_GLOBAL_SCALE = 1.0f; const float ACR_DURATION_10M_SCALE = 1.0f; const float ACR_DURATION_HOUR_SCALE = 13.0f / 60.0f;

const int ACR_DURATION_TYPE_STATIC = 0; const int ACR_DURATION_TYPE_PER_CL = 1;

const float ACR_DURATION_1R = 6.0f; const float ACR_DURATION_6S = 6.0f; const float ACR_DURATION_60S = 60.0f; const float ACR_DURATION_1M = 60.0f; const float ACR_DURATION_10M = 600.0f * ACR_DURATION_10M_SCALE; const float ACR_DURATION_1H = 3600.0f * ACR_DURATION_HOUR_SCALE; const float ACR_DURATION_2H = ACR_DURATION_1H * 2.0f; const float ACR_DURATION_24H = ACR_DURATION_1H * 24.0f;

const float ACR_MC_GOLD_SCALE = 0.0f;

const string ACR_SPELL_RESIST_PREFIX = "ACR_SPELL_RESIST_"; const string ACR_SPELL_VULNER_PREFIX = "ACR_SPELL_VULNER_";

const string ACR_SPELL_DESCRIPT_VAR_PREFIX = "ACR_SPELL_DESC_"; const int ACR_SPELL_DESCRIPT_ACID = 0; const int ACR_SPELL_DESCRIPT_AIR = 1; const int ACR_SPELL_DESCRIPT_CHAOTIC = 2; const int ACR_SPELL_DESCRIPT_COLD = 3; const int ACR_SPELL_DESCRIPT_DARKNESS = 4; const int ACR_SPELL_DESCRIPT_DEATH = 5; const int ACR_SPELL_DESCRIPT_EARTH = 6; const int ACR_SPELL_DESCRIPT_ELECTRICITY = 7; const int ACR_SPELL_DESCRIPT_EVIL = 8; const int ACR_SPELL_DESCRIPT_FEAR = 9; const int ACR_SPELL_DESCRIPT_FIRE = 10; const int ACR_SPELL_DESCRIPT_FORCE = 11; const int ACR_SPELL_DESCRIPT_GOOD = 12; const int ACR_SPELL_DESCRIPT_LANGUAGE_DEP = 13; const int ACR_SPELL_DESCRIPT_LAWFUL = 14; const int ACR_SPELL_DESCRIPT_LIGHT = 15; const int ACR_SPELL_DESCRIPT_MIND_AFFECTING = 16; const int ACR_SPELL_DESCRIPT_SONIC = 17; const int ACR_SPELL_DESCRIPT_WATER = 18;

const string _DM_CASTER_LEVEL = "ACR_SPELLS_DM_CL";

const int ACR_MAX_SPELLID = 3200;

const string DICT_SPELL_NAME = "DICT_SPELL_NAME"; const string DICT_SPELL_LVL = "DICT_SPELL_LVL";


//////////////////////////////////////////////////////////////////////////////// // Structures ////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////// // Global Variables //////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////// // Function Prototypes ///////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////

// Precast and post-cast events. int ACR_PrecastEvent(); int ACR_PostcastEvent();

//! Calculates the duration of a spell's effects. //! - oCaster: the spell caster for whom the duration is being calculated //! - nDurationType: the ACR_DURATION_TYPE_ constant to determine if caster //! level is involved in duration calculation //! - nDurationBase: the duration of the spell in seconds, or the number which //! is manipulated by the caster level. float ACR_GetSpellDuration( object oCaster, int nDurationType, float nDurationBase );

//! Handle and pay a material component. int ACR_PayMaterialComponentCost( object oCaster, string sItemName, int nGold, int bPayGoldIfNoItem = TRUE );

int ACR_GetCorrectCasterLevel(object oCreature, int nClass = -1);

int ACR_SpellResistanceCheck( object oCaster, object oCreature, int nSpellId = -1 );

//! Gets the common/standard spell level for a spell of Id nSpellId. int ACR_GetSpellLevel( int nSpellId );

//! Sets if the currently casting spell is a domain spell or not. void ACR_SetIsDomainSpell( int nDomain, int bValue );

void WarnWhenSpellExpires(object oCreature, int nSpellId, string sMessage, float fDuration);

void _floatMessageIfHasSpellEffect(object oCreature, int nSpellId, string sMessage, float fDuration);

//! Get spell name from ID string ACR_GetSpellName(int nSpellId);

//! Get spell innate level from ID int ACR_GetSpellInnateLevel(int nSpellId);

//! ACR's version of nw_i0_spells spellCure function. void ACR_SpellCure( object oTarget, int nDieCount, int nDieSides, int nMaxExtraDamage, int nSpellID, int vfxImpactHeal, int vfxImpactHarm );

//! ACR's version of x0_i0_spells spellsInflictTouchAttack function. void ACR_SpellsInflictTouchAttack( object oTarget, int nDieCount, int nDieSides, int nMaxExtraDamage, int nSpellID, int vfxImpactHeal, int vfxImpactHarm );

//! ACR's version of nw_i0_spells spellsHealOrHarmTarget function. void ACR_SpellsHealOrHarmTarget( object oTarget, int nDamageTotal, int vfx_impactNormalHurt, int vfx_impactUndeadHurt, int vfx_impactHeal, int nSpellID, int bIsHealingSpell=TRUE, int bHarmTouchAttack=TRUE );

//! ACR's version of nw_i0_spells GetCureDamageTotal. Handles changes to our feats. int ACR_GetCureDamageTotal( object oTarget, int nDieCount, int nDieSides, int nMaxExtraDamage, int nSpellID );

string _GetDomainVarName( int nDomain );

//! Cache Name/Lvl of Spell 2da for future use void ACR_Cache2daSpellListing();

//! Adds a persistant AC bonus effect to oTarget, such as is used in Mage Armor and Shield spells. void PersistACBonusFromSpell(object oTarget, int nSpellId, int nACType, int nACBonus, float fDurationRemaining, effect eLinkedEffects);

//!! Handles caster level check (if needed) for use of a scroll. //!! - oUser - The PC or NPC using the scroll //!! - oScroll - The scroll item //!! - nSpellID - the Spells.2da ID for the spell to be cast. //!! Returns: //!! 1 - Scroll works properly //!! 2 - Scroll use failed. int ACR_ScrollUse(object oUser, object oScroll, int nSpellID);

//! Checks if OBJECT_SELF can carry out a gaze attack on oCreature //! - oCreature: The creature to be gaze attacked int ACR_CanGazeAttack(object oCreature);

//! *** PRIVATE FUNCTIONS ***

//! Determines which class a given caster should use to activate a scroll, and whether they succeed. int _DetermineScrollAttempt(object oCaster, int nScrollID);

//! Works out how hard the caster level check would be for a given class, level, and spell level int _ScrollCastDiff(int nClass, int nUserLevel, int nSpellLevel);

//! Attempts a caster level check, with chance for mishap on failure. int _AttemptScrollUse(int nScrollID, int nSpellLevel, object oCaster, int nSpellClass);

//! Handles mishap checks for scroll use. int _ScrollMishapCheck(object oCaster, int nScrollID, int nSpellLevel, int nSpellID);

//! Handles actual scroll mishap effects. //! takes the user and the level and identity of the spell into account. void _ProcessMishap(object oCaster, int nSpellLevel, int nSpellID);

//! Check if we need to cache the spell 2da list int ACR__CheckIsReadySpell2daList();

//////////////////////////////////////////////////////////////////////////////// // Includes //////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////

  1. include "nw_i0_spells"
  2. include "x2_inc_spellhook"
  3. include "acr_i"
  4. include "acr_roll_i"
  5. include "acr_creature_i"
  6. include "acr_effects_i"

//////////////////////////////////////////////////////////////////////////////// // Function Definitions //////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////

int ACR_PrecastEvent() { // First call NWN2's precast code. if ( !X2PreSpellCastCode() ) return FALSE;

return TRUE; }

int ACR_PostcastEvent() { // Delete temp description variables. ACR_SetIsDomainSpell( ACR_SPELL_DESCRIPT_ACID, FALSE ); ACR_SetIsDomainSpell( ACR_SPELL_DESCRIPT_AIR, FALSE ); ACR_SetIsDomainSpell( ACR_SPELL_DESCRIPT_CHAOTIC, FALSE ); ACR_SetIsDomainSpell( ACR_SPELL_DESCRIPT_COLD, FALSE ); ACR_SetIsDomainSpell( ACR_SPELL_DESCRIPT_DARKNESS, FALSE ); ACR_SetIsDomainSpell( ACR_SPELL_DESCRIPT_DEATH, FALSE ); ACR_SetIsDomainSpell( ACR_SPELL_DESCRIPT_EARTH, FALSE ); ACR_SetIsDomainSpell( ACR_SPELL_DESCRIPT_ELECTRICITY, FALSE ); ACR_SetIsDomainSpell( ACR_SPELL_DESCRIPT_EVIL, FALSE ); ACR_SetIsDomainSpell( ACR_SPELL_DESCRIPT_FEAR, FALSE ); ACR_SetIsDomainSpell( ACR_SPELL_DESCRIPT_FIRE, FALSE ); ACR_SetIsDomainSpell( ACR_SPELL_DESCRIPT_FORCE, FALSE ); ACR_SetIsDomainSpell( ACR_SPELL_DESCRIPT_GOOD, FALSE ); ACR_SetIsDomainSpell( ACR_SPELL_DESCRIPT_LANGUAGE_DEP, FALSE ); ACR_SetIsDomainSpell( ACR_SPELL_DESCRIPT_LAWFUL, FALSE ); ACR_SetIsDomainSpell( ACR_SPELL_DESCRIPT_LIGHT, FALSE ); ACR_SetIsDomainSpell( ACR_SPELL_DESCRIPT_MIND_AFFECTING, FALSE ); ACR_SetIsDomainSpell( ACR_SPELL_DESCRIPT_SONIC, FALSE ); ACR_SetIsDomainSpell( ACR_SPELL_DESCRIPT_WATER, FALSE );

return TRUE; }

float ACR_GetSpellDuration( object oCaster, int nDurationType, float nDurationBase ) { // Get base duration. float nDuration = nDurationBase;

// Adjust to caster level if not static. if ( nDurationType == ACR_DURATION_TYPE_PER_CL ) { nDuration = nDuration * ACR_GetCorrectCasterLevel( oCaster ); }

// Check for extended metamagic. if ( GetMetaMagicFeat() & METAMAGIC_EXTEND ) { nDuration = nDuration * 2; }

// Check for persistent metamagic. if ( GetMetaMagicFeat() & METAMAGIC_PERSISTENT ) { nDuration = ACR_DURATION_24H; }

// Apply global duration scaling. nDuration = nDuration * ACR_DURATION_GLOBAL_SCALE;

return nDuration; }

// Called to determine if a spell touch attack succeeds on a target. // Returns TOUCH_ATTACK_RESULT_MISS, TOUCH_ATTACK_RESULT_HIT, or TOUCH_ATTACK_RESULT_CRITICAL. int ACR_SpellTouchAttackRanged(object oTarget, int bDisplayFeedback=TRUE, int bCanSneakAttack=FALSE) { return TouchAttackRanged(oTarget, bDisplayFeedback); }

int ACR_GetSpellSneakAttackDamage(object oCaster, object oTarget) { // Get damage. int nDamage = ACR_GetSneakAttackDamage( oCaster ); if ( nDamage <= 0 ) { return 0; }

// Bail if we can't sneak attack the target. if ( !ACR_GetCanSneakAttack( oCaster, oTarget ) ) { return 0; }

FloatingTextStringOnCreature("<C=gray>*Sneak Attack!*</C>", oCaster, FALSE, 1.5f); return nDamage; }

int ACR_PayMaterialComponentCost( object oCaster, string sItemName, int nGold, int bPayGoldIfNoItem = TRUE ) { // Dealing with an item or gold? if ( sItemName == "" ) { // Scale our gold by our constant. nGold = FloatToInt( IntToFloat( nGold ) * ACR_MC_GOLD_SCALE ); if ( nGold <= 0 ) return TRUE;

// Return false if we don't have enough gold. if ( GetGold( oCaster ) < nGold ) { return FALSE; }

// Remove the gold. AssignCommand( oCaster, TakeGoldFromCreature( nGold, oCaster, TRUE, FALSE ) ); SendMessageToPC( oCaster, "You have paid " + IntToString( nGold ) + "gp in material components." ); } else { // We currently don't support item material components. return ACR_PayMaterialComponentCost( oCaster, "", nGold, TRUE ); } return TRUE; }

int _GetClassCL(int nClass, int nLevel) {

if(nLevel < 1) return 0;

switch(nClass) { case CLASS_TYPE_BARD: case CLASS_TYPE_CLERIC: case CLASS_TYPE_DRUID: case CLASS_TYPE_FAVORED_SOUL: case CLASS_TYPE_SORCERER: case CLASS_TYPE_SPIRIT_SHAMAN: case CLASS_TYPE_WIZARD: case CLASS_LOREMASTER: case CLASS_MYSTICTHEURGE: case CLASS_TYPE_STORMLORD: case CLASS_TYPE_DOOMGUIDE: case CLASS_TYPE_ARCANE_SCHOLAR: case CLASS_TYPE_ARCANETRICKSTER: case CLASS_TYPE_RED_WIZARD: return nLevel;

case CLASS_TYPE_PALADIN: case CLASS_TYPE_RANGER: return (nLevel < 4) ? 0 : nLevel / 2;

case CLASS_BLADESINGER: return 1 + nLevel / 2;

case CLASS_TYPE_WARPRIEST: return nLevel / 2;

case CLASS_TYPE_HARPER: case CLASS_TYPE_ELDRITCH_KNIGHT: return nLevel - 1;

case CLASS_TYPE_SACREDFIST: return (nLevel + 1) * 3 / 4; } return 0; }

int _GetClassCLContribution(object oCreature, int nCasterClass, int nClass) { if(nClass == nCasterClass) // This is the same class we're casting from. return _GetClassCL(nClass, GetLevelByClass(nClass, oCreature)); else { // This is not the same class we're casting from. // Check to see if its mapped to our class. string sFeatMap2DA = Get2DAString("classes", "BonusCasterFeatByClassMap", nClass); if(sFeatMap2DA == "****" || sFeatMap2DA == "") return 0; else { int nFeat = StringToInt(Get2DAString(sFeatMap2DA, "SpellcasterFeat", nCasterClass)); // Lets assume nFeat == 0 an error, since feat 0 is alertness. if(!nFeat) nFeat = -1; return GetHasFeat(nFeat, oCreature) * _GetClassCL(nClass, GetLevelByClass(nClass, oCreature)); } } }

int ACR_GetCorrectCasterLevel(object oCreature, int nClass = -1) {

if(GetSpellCastItem() != OBJECT_INVALID) return GetCasterLevel(oCreature);

// If no class is provided, get the last caster class. if ( nClass == -1 ) nClass = GetLastSpellCastClass();

if(GetIsDM(oCreature)) { int nCL = GetLocalInt(oCreature, _DM_CASTER_LEVEL); return nCL ? nCL : 10; }

int i = 1; int nCL; for(; i<5; i++) { int nCurClass = GetClassByPosition(i, oCreature); if(nCurClass != CLASS_TYPE_INVALID) nCL += _GetClassCLContribution(oCreature, nClass, nCurClass); }

int nPS = StringToInt(Get2DAString("classes", "FEATPracticedSpellcaster", nClass)); if(nPS && GetHasFeat(nPS, oCreature)) { int nHd = GetHitDice(oCreature); nCL += 4; if(nCL > nHd) nCL = nHd; }

return nCL; }

int ACR_SpellResistanceCheck( object oCaster, object oCreature, int nSpellId = -1 ) { if ( nSpellId < 0 ) nSpellId = GetSpellId();

// Check for a specific immunity. if ( GetLocalInt( oCreature, ACR_SPELL_RESIST_PREFIX + IntToString( nSpellId ) ) ) return TRUE;

// Check for a specific vulnerability. if ( GetLocalInt( oCreature, ACR_SPELL_VULNER_PREFIX + IntToString( nSpellId ) ) ) return FALSE;

// Otherwise we just wrap MyResistSpell. return MyResistSpell( oCaster, oCreature ); }

int ACR_GetSpellLevel( int nSpellId ) {

   ClearScriptParams();
   AddScriptParameterObject(OBJECT_INVALID);
   AddScriptParameterInt(ACR_ITEMS_CSHARP_SPELL_LEVEL);
   AddScriptParameterInt(nSpellId);
   return ExecuteScriptEnhanced("ACR_Items", OBJECT_SELF, TRUE);

}

void ACR_SetIsDomainSpell( int nDomain, int nValue ) { string sVarName = _GetDomainVarName( nDomain );

// Are we deleting? if ( nValue = FALSE ) { DeleteLocalInt( OBJECT_SELF, sVarName ); return; }

SetLocalInt( OBJECT_SELF, sVarName, nValue ); }

int ACR_GetIsDomainSpell( int nDomain ) { return ( GetLocalInt( OBJECT_SELF, _GetDomainVarName( nDomain ) ) != FALSE ); }

int ACR_GetHighestCasterLevel(object oCreature) { int nCL;

int level = ACR_GetCorrectCasterLevel(oCreature, CLASS_TYPE_CLERIC); nCL = (level > nCL) ? level : nCL;

level = ACR_GetCorrectCasterLevel(oCreature, CLASS_TYPE_DRUID); nCL = (level > nCL) ? level : nCL;

level = ACR_GetCorrectCasterLevel(oCreature, CLASS_TYPE_FAVORED_SOUL); nCL = (level > nCL) ? level : nCL;

level = ACR_GetCorrectCasterLevel(oCreature, CLASS_TYPE_SPIRIT_SHAMAN); nCL = (level > nCL) ? level : nCL;

level = ACR_GetCorrectCasterLevel(oCreature, CLASS_TYPE_BARD); nCL = (level > nCL) ? level : nCL;

level = ACR_GetCorrectCasterLevel(oCreature, CLASS_TYPE_SORCERER); nCL = (level > nCL) ? level : nCL;

level = ACR_GetCorrectCasterLevel(oCreature, CLASS_TYPE_WIZARD); nCL = (level > nCL) ? level : nCL;

level = ACR_GetCorrectCasterLevel(oCreature, CLASS_TYPE_PALADIN); nCL = (level > nCL) ? level : nCL;

level = ACR_GetCorrectCasterLevel(oCreature, CLASS_TYPE_RANGER); nCL = (level > nCL) ? level : nCL;

return nCL; }

void WarnWhenSpellExpires(object oCreature, int nSpellId, string sMessage, float fDuration) { DelayCommand(fDuration - 12.0, _floatMessageIfHasSpellEffect(oCreature, nSpellId, sMessage, 12.0)); }

void _floatMessageIfHasSpellEffect(object oCreature, int nSpellId, string sMessage, float fDuration) { if(GetHasSpellEffect(nSpellId, oCreature)) FloatingTextStringOnCreature(sMessage, oCreature, TRUE, fDuration); }

void ACR_Cache2daSpellListing() { ACR_Cache2daListing(SPELLS_2DA, SPELLS_NAME_COL, DICT_SPELL_NAME, ACR_MAX_SPELLID); ACR_Cache2daListing(SPELLS_2DA, SPELLS_INNATE_LEVEL_COL, DICT_SPELL_LVL, ACR_MAX_SPELLID); }

int ACR__CheckIsReadySpell2daList() { if (ACR_CheckIsReady2daList(DICT_SPELL_NAME)) return 1;

if (!ACR_CheckIsStarted2daList(DICT_SPELL_NAME)) ACR_Cache2daSpellListing();

return 0; }

string ACR_GetSpellName(int spell_id) { string str, strref;

str = IntToString(spell_id);

if (!ACR__CheckIsReadySpell2daList()) { return "error"; }

strref = ACR_DictionaryGet(DICT_SPELL_NAME, str); return GetStringByStrRef(StringToInt(strref)); }

int ACR_GetSpellInnateLevel(int spell_id) { string str;

str = IntToString(spell_id);

if (!ACR__CheckIsReadySpell2daList()) { return -1; }

return StringToInt(ACR_DictionaryGet(DICT_SPELL_LVL, str)); }

string ACR_GetSpellList(object oCreature, int nMemorized=0) { int id, lvl, num, len, old_lvl; string name, str, msg, dict_id = "TMP";

if (!ACR__CheckIsReadySpell2daList()) { return "Please try again shortly. Spell list not yet ready."; }

// Iterate through all possible spells to determine those present for (str = ACR_DictionaryIterateFirst(DICT_SPELL_NAME); str != ""; str = ACR_DictionaryIterateNext(DICT_SPELL_NAME)) { id = StringToInt(str);

num = (nMemorized ? GetHasSpell(id, oCreature) : GetSpellKnown(oCreature, id));

if (num) { lvl = ACR_GetSpellInnateLevel(id); name = ACR_GetSpellName(id);

// Handle sorting (reverse order from highest level spell to lowest) lvl = 10 - lvl;

if (lvl < 10) { str = "0" + IntToString(lvl); } else str = "10";

str += " " + name;

ACR_DictionarySet(dict_id, str, IntToString(num)); } }

old_lvl = -1; msg = "";

// Iterate through spells present and format for (str = ACR_DictionaryIterateFirst(dict_id); str != ""; str = ACR_DictionaryIterateNext(dict_id)) { len = GetStringLength(str); name = GetStringRight(str, len-3);

// Shift level back to correct value lvl = 10-StringToInt(GetStringLeft(str, 2));

num = StringToInt(ACR_DictionaryGet(dict_id, str));

// Display spell level if new level if (old_lvl != lvl) {

// Remove trailing comma len = GetStringLength(msg); msg = GetStringLeft(msg, len-2);

msg += "\nLevel " + IntToString(lvl) + ": "; old_lvl = lvl; }

// Display number if > 1 if (num > 1) { msg += IntToString(num) + " "; }

msg += name + ", "; }

ACR_DictionaryClear(dict_id);

// Remove trailing comma if (old_lvl != -1) { len = GetStringLength(msg); msg = GetStringLeft(msg, len-2); }

return msg; }

void ACR_SpellCure( object oTarget, int nDieCount, int nDieSides, int nMaxExtraDamage, int nSpellID, int vfxImpactHeal, int vfxImpactHarm ) { int nDamageTotal = ACR_GetCureDamageTotal( oTarget, nDieCount, nDieSides, nMaxExtraDamage, nSpellID );

	int bIsHealingSpell = TRUE;

int bHarmTouchAttack = TRUE; ACR_SpellsHealOrHarmTarget( oTarget, nDamageTotal, vfxImpactHarm, vfxImpactHarm, vfxImpactHeal, nSpellID, bIsHealingSpell, bHarmTouchAttack ); }

void ACR_SpellsInflictTouchAttack( object oTarget, int nDieCount, int nDieSides, int nMaxExtraDamage, int nSpellID, int vfxImpactHeal, int vfxImpactHarm ) { int nMetaMagic = GetMetaMagicFeat();

// Calculate damage. int nDamage = ACR_Roll( nDieCount, nDieSides );

// Handle metamagics. if ( nMetaMagic == METAMAGIC_MAXIMIZE ) { nDamage = ( nDieCount * nDieSides ); } else if ( nMetaMagic == METAMAGIC_EMPOWER ) { nDamage += ( nDamage / 2 ); }

// Get caster level bonus damage. int nExtraDamage = GetCasterLevel(OBJECT_SELF); if ( nSpellID == SPELLABILITY_BG_INFLICT_SERIOUS_WOUNDS ) nExtraDamage = GetLevelByClass( CLASS_TYPE_BLACKGUARD ); if ( nSpellID == SPELLABILITY_BG_INFLICT_CRITICAL_WOUNDS ) nExtraDamage = GetLevelByClass( CLASS_TYPE_BLACKGUARD ); if ( nExtraDamage > nMaxExtraDamage ) nExtraDamage = nMaxExtraDamage; nDamage += nExtraDamage;

// Do the harming. int bIsHealingSpell = FALSE; int bHarmTouchAttack = TRUE; ACR_SpellsHealOrHarmTarget( oTarget, nDamage, vfxImpactHarm, vfxImpactHarm, vfxImpactHeal, nSpellID, bIsHealingSpell, bHarmTouchAttack ); }

void ACR_SpellsHealOrHarmTarget( object oTarget, int nDamageTotal, int vfx_impactNormalHurt, int vfx_impactUndeadHurt, int vfx_impactHeal, int nSpellID, int bIsHealingSpell=TRUE, int bHarmTouchAttack=TRUE ) { int bIsHarmful = FALSE;

// Immune to healing effects? if ( GetLocalInt( oTarget, VAR_IMMUNE_TO_HEAL ) == TRUE ) { return; }

// Apply effects. if ( !ACR_GetIsUndead( oTarget ) ) { if ( bIsHealingSpell ) { DoHealing( oTarget, nDamageTotal, vfx_impactHeal ); } else { DoHarming( oTarget, nDamageTotal, DAMAGE_TYPE_NEGATIVE, vfx_impactNormalHurt, bHarmTouchAttack ); bIsHarmful = TRUE; } } else { if ( bIsHealingSpell ) { DoHarming( oTarget, nDamageTotal, DAMAGE_TYPE_POSITIVE, vfx_impactUndeadHurt, bHarmTouchAttack ); bIsHarmful = TRUE; } else { DoHealing( oTarget, nDamageTotal, vfx_impactHeal ); } } }

int ACR_GetCureDamageTotal( object oTarget, int nDieCount, int nDieSides, int nMaxExtraDamage, int nSpellID ) { // Get base data. object oCaster = OBJECT_SELF; int nCasterClass = GetLastSpellCastClass(); int nCasterLevel = ACR_GetCorrectCasterLevel( oCaster, nCasterClass ); int nMetaMagic = GetMetaMagicFeat();

// Cure spells are treated one level higher if we're a healing domain cleric. if ( nCasterClass == CLASS_TYPE_CLERIC && GetHasFeat( FEAT_HEALING_DOMAIN_POWER, oCaster ) ) { nCasterLevel++; }

// Get the damage. int nDamage = ACR_Roll( nDieCount, nDieSides );

// Apply metamagic. if ( nMetaMagic == METAMAGIC_MAXIMIZE ) { nDamage = ( nDieCount * nDieSides ); } else if ( nMetaMagic == METAMAGIC_EMPOWER ) { nDamage += ( nDamage / 2 ); }

// Add the caster level as bonus, to a maximum. int nExtraDamage = nCasterLevel; if ( nExtraDamage > nMaxExtraDamage ) nExtraDamage = nMaxExtraDamage; nDamage += nExtraDamage;

// Handle the Augment Healing feat. if ( GetHasFeat( FEAT_AUGMENT_HEALING ) && !GetIsObjectValid( GetSpellCastItem() ) ) { int nSpellLvl = GetSpellLevel( nSpellID ); nDamage += ( 2 * nSpellLvl ); }

return nDamage; }

string _GetDomainVarName( int nDomain ) { return ACR_SPELL_DESCRIPT_VAR_PREFIX + IntToString( nDomain ); }

int _GetPersistentACBonusTarget(object oTarget, int nACType, int nACBonus) { // Find the base and enhancement AC bonuses. // Base should not stack with the effect. // Enhancement should. int nEnhancement = 0; int nBaseAC = 0; if(nACType == AC_ARMOUR_ENCHANTMENT_BONUS) { object oArmor = GetItemInSlot(INVENTORY_SLOT_CHEST, oTarget); nBaseAC = GetBaseArmorClass(oArmor); nEnhancement = GetACEnhancementBonus(oArmor); /* Not sure what to do with bracers... object oBracers = GetItemInSlot(INVENTORY_SLOT_ARMS, oTarget); if(GetBaseItemType(oBracers) == BASE_ITEM_BRACER) { int nBracersEnhancement = GetACEnhancementBonus(oBracers); if(nBracersEnhancement > nEnhancement) nEnhancement = nBracersEnhancement; }*/ } else if(nACType = AC_SHIELD_ENCHANTMENT_BONUS) { object oShield = GetItemInSlot(INVENTORY_SLOT_LEFTHAND, oTarget); int nType = GetBaseItemType(oShield); if(nType == BASE_ITEM_TOWERSHIELD || nType == BASE_ITEM_LARGESHIELD || nType == BASE_ITEM_SMALLSHIELD) { nEnhancement = GetACEnhancementBonus(oShield); nBaseAC = GetBaseArmorClass(oShield); } }

int nResult = nACBonus + nEnhancement - nBaseAC; if(nResult < 0) nResult = 0; return nResult; }

void _AddPersistentACBonusEffects(object oTarget, int nSpellId, int nACType, int nBonus, float fDurationRemaining, effect eLinkedEffects) {

if(nBonus <= 0) { // If there's no AC bonus needed, persist this effect as something else so the spell won't be ended. if(GetEffectType(eLinkedEffects) != EFFECT_TYPE_INVALIDEFFECT) ApplyEffectFromSpell(nSpellId, DURATION_TYPE_TEMPORARY, eLinkedEffects, oTarget, fDurationRemaining); else ApplyTrackingEffectFromSpell(nSpellId, DURATION_TYPE_TEMPORARY, oTarget, fDurationRemaining); } else { effect newEffect = EffectACIncrease(nBonus, nACType); SetEffectSpellId(newEffect, nSpellId); if(GetEffectType(eLinkedEffects) != EFFECT_TYPE_INVALIDEFFECT) newEffect = EffectLinkEffects(eLinkedEffects, newEffect);

ApplyEffectFromSpell(nSpellId, DURATION_TYPE_TEMPORARY, newEffect, oTarget, fDurationRemaining); } }

void _PersistACBonusFromSpell(object oTarget, int nSpellId, int nACType, int nACBonus, int nLastACBonus, float fDurationRemaining, effect eLinkedEffects, int bIsPC) {

if(bIsPC && !GetIsObjectValid(oTarget)) { // If the PC has logged out, skip this heartbeat. DelayCommand(6.0, _PersistACBonusFromSpell(oTarget, nSpellId, nACType, nACBonus, nLastACBonus, fDurationRemaining - 6.0, eLinkedEffects, bIsPC)); return; }

int nSource = ACR_EFFECT_SOURCE_SPELL_OFFSET + nSpellId;

if(CountTrackingEffectsFor(oTarget, nSource)) { // Spell is still active. See if we need to change the AC effects. int nNeededACBonus = _GetPersistentACBonusTarget(oTarget, nACType, nACBonus); if(nNeededACBonus != nLastACBonus) { // Change AC effects. RemoveAllEffectsFromSource(oTarget, nSource); _AddPersistentACBonusEffects(oTarget, nSpellId, nACType, nNeededACBonus, fDurationRemaining, eLinkedEffects); } if(fDurationRemaining > 0.0) DelayCommand(6.0, _PersistACBonusFromSpell(oTarget, nSpellId, nACType, nACBonus, nNeededACBonus, fDurationRemaining - 6.0, eLinkedEffects, bIsPC)); } }

void PersistACBonusFromSpell(object oTarget, int nSpellId, int nACType, int nACBonus, float fDurationRemaining, effect eLinkedEffects) { SetEffectSpellId(eLinkedEffects, nSpellId); int nTarget = _GetPersistentACBonusTarget(oTarget, nACType, nACBonus); _AddPersistentACBonusEffects(oTarget, nSpellId, nACType, nTarget, fDurationRemaining, eLinkedEffects); DelayCommand(6.0, _PersistACBonusFromSpell(oTarget, nSpellId, nACType, nACBonus, nTarget, fDurationRemaining - 6.0, eLinkedEffects, GetIsPC(oTarget) && !GetIsDMPossessed(oTarget))); }


int ACR_ScrollUse(object oUser, object oScroll, int nSpellID) {

  int nResultType = _DetermineScrollAttempt(oUser, nSpellID);
  
  if ( nResultType < 0) {
      // then there was a mishap. do not run the spell script, the scroll is consumed.

SetModuleOverrideSpellScriptFinished(); return FALSE;

  } else if (nResultType == 0) {
  	   // the user failed to activate the scroll, replace it and end.
      SetPlotFlag(oScroll, TRUE);

DelayCommand(2.0, SetPlotFlag(oScroll, FALSE)); SetModuleOverrideSpellScriptFinished();

      return FALSE;
  } 
  // otherwise, scroll use was successful.

return TRUE; }


int ACR_CanGazeAttack(object oCreature) {

   effect eEffect = GetFirstEffect(oCreature);
   if(!GetObjectSeen(OBJECT_SELF, oCreature)) return FALSE;
   if(!GetObjectSeen(oCreature, OBJECT_SELF)) return FALSE;
   while(GetIsEffectValid(eEffect))
   {
       if(GetEffectType(eEffect) == EFFECT_TYPE_BLINDNESS) return FALSE;
       if(GetEffectSpellId(eEffect) == SPELL_BLINDSIGHT) return FALSE;
       eEffect = GetNextEffect(oCreature);
   }
   return TRUE;

}

//////////////////////////////////////////////////////////////////////////////// // *** BEGIN PRIVATE FUNCTIONS *** ////////////////////////////////////////////////////////////////////////////////

int _DetermineScrollAttempt(object oCaster, int nScrollID) {

   int nSpellClass = 0;

int nSpellDiff = 99; int nTestDiff = 99; int nSpellLevel = 99; int nTestSpellLevel = 99; int bAttributeFail = FALSE; int bHasUMD = FALSE; int nUMDRanks = GetSkillRank(SKILL_USE_MAGIC_DEVICE, oCaster); // track how good our class/level combo is: 0 - nothing // 1 - need to use UMD for class /and/ an attribute UMD check // 2 - class is OK, but attribute is too low, UMD check to simulate it // 3 - need to use UMD for class (attribute OK) // 4 - valid caster class and stat int nAttemptLevel = 0; string sTest = "default"; string sClass = "";

// need to know if the PC has Use Magic Device skill, so it can be used if needed. if (GetSkillRank(SKILL_USE_MAGIC_DEVICE, oCaster, TRUE) >=1) { bHasUMD = TRUE; } // start with checking for bard levels int nPCCasterLevel = ACR_GetCorrectCasterLevel(oCaster, CLASS_TYPE_BARD); if ((nPCCasterLevel > 0) || bHasUMD) { sTest = Get2DAString("spells", "Bard", nScrollID); // check to see if it is on the bard list

       if (sTest != "") {	

nTestSpellLevel = StringToInt(sTest); //SendMessageToPC(oCaster, "Bard scroll- level "+sTest); nSpellClass = CLASS_TYPE_BARD; nSpellLevel = nTestSpellLevel; sClass = "Bard"; //SendMessageToPC(oCaster, "Bard scroll- level "+sTest); if (GetAbilityScore(oCaster, ABILITY_CHARISMA) >= (nSpellLevel + 10)) { // caster has high enough attribute, calculate difficulty nSpellDiff = _ScrollCastDiff(CLASS_TYPE_BARD, nPCCasterLevel, nSpellLevel); if (nPCCasterLevel == 0) { // need the UMD check, for this, but stat is OK nAttemptLevel = 3; } else { nAttemptLevel = 4; } } else { bAttributeFail = TRUE; if (bHasUMD) { // if the PC had UMD, the attribute failure isn't final. Track info. nSpellDiff = _ScrollCastDiff(CLASS_TYPE_BARD, nPCCasterLevel, nSpellLevel); if (nPCCasterLevel == 0) { // doesn't qualify by attribute or class nAttemptLevel = 1; } else { // has the class, but not the attribute nAttemptLevel = 2; } } } } } // check cleric levels next nPCCasterLevel = ACR_GetCorrectCasterLevel(oCaster, CLASS_TYPE_CLERIC); if ((nPCCasterLevel > 0) || bHasUMD) { // is the spell on the cleric spell list? sTest = Get2DAString("spells", "Cleric", nScrollID);

       if (sTest != "") {	

nTestSpellLevel = StringToInt(sTest); //SendMessageToPC(oCaster, "Cleric scroll- level "+sTest); if (GetAbilityScore(oCaster, ABILITY_WISDOM) >= (nTestSpellLevel + 10)) { // caster has high enough attribute // now, need to decide if we are better off using cleric or bard levels. nTestDiff = _ScrollCastDiff(CLASS_TYPE_CLERIC, nPCCasterLevel, nTestSpellLevel); if ((nPCCasterLevel == 0) && (nAttemptLevel < 4)) { if (ACR_GetMinSpellCL(nTestSpellLevel, CLASS_TYPE_CLERIC) < ACR_GetMinSpellCL(nSpellLevel, nSpellClass)) { nSpellLevel = nTestSpellLevel; nSpellClass = CLASS_TYPE_CLERIC; sClass = "Cleric"; nSpellDiff = nTestDiff; bAttributeFail = FALSE; nAttemptLevel = 3; } } else if (nTestDiff < nSpellDiff) { // cleric levels have a better chance to activate. nSpellLevel = nTestSpellLevel; nSpellClass = CLASS_TYPE_CLERIC; sClass = "Cleric"; nSpellDiff = nTestDiff; bAttributeFail = FALSE; nAttemptLevel = 4; } } else if ((nAttemptLevel <= 1) && bHasUMD) { // if nAttemptLevel is not set yet, or requires both checks, then // we need to consider this class as an alternative. if ((nTestSpellLevel < nSpellLevel) && (nPCCasterLevel == 0)) { // PC will need a UMD class check, and an attribute check, but this class is more favorable. nSpellLevel = nTestSpellLevel; nSpellClass = CLASS_TYPE_CLERIC; sClass = "Cleric"; nSpellDiff = nTestDiff; bAttributeFail = TRUE; nAttemptLevel = 1; } } else if ((nAttemptLevel <= 2) && bHasUMD) { // The PC has the class, but needs the attribute. Use this class if it's better than the alternative. if ((nTestSpellLevel < nSpellLevel) && (nPCCasterLevel == 0)) { // PC will need a UMD class check, and an attribute check, but this class is more favorable. nSpellLevel = nTestSpellLevel; nSpellClass = CLASS_TYPE_CLERIC; sClass = "Cleric"; nSpellDiff = nTestDiff; bAttributeFail = TRUE; nAttemptLevel = 2; }

} else if (nAttemptLevel == 0) { bAttributeFail = TRUE; } } } // next, check for druid levels. nPCCasterLevel = ACR_GetCorrectCasterLevel(oCaster, CLASS_TYPE_DRUID); if ((nPCCasterLevel > 0) || bHasUMD) { sTest = Get2DAString("spells", "Druid", nScrollID); // spell on the druid list?

       if (sTest != "") {	

nTestSpellLevel = StringToInt(sTest); //SendMessageToPC(oCaster, "Druid scroll- level "+sTest); if (GetAbilityScore(oCaster, ABILITY_WISDOM) >= (nTestSpellLevel + 10)) { // caster has high enough attribute // now, need to decide if we are better off using cleric or bard levels. nTestDiff = _ScrollCastDiff(CLASS_TYPE_DRUID, nPCCasterLevel, nTestSpellLevel); if ((nPCCasterLevel == 0) && (nAttemptLevel < 4)) { if (ACR_GetMinSpellCL(nTestSpellLevel, CLASS_TYPE_DRUID) < ACR_GetMinSpellCL(nSpellLevel, nSpellClass)) { nSpellLevel = nTestSpellLevel; nSpellClass = CLASS_TYPE_DRUID; sClass = "Druid"; nSpellDiff = nTestDiff; bAttributeFail = FALSE; nAttemptLevel = 3; } } else if (nTestDiff < nSpellDiff) { // then we're better off using druid levels to activate. nSpellLevel = nTestSpellLevel; nSpellClass = CLASS_TYPE_DRUID; sClass = "Druid"; nSpellDiff = nTestDiff; bAttributeFail = FALSE; nAttemptLevel = 4; } } else if ((nAttemptLevel <= 1) && bHasUMD) { // if nAttemptLevel is not set yet, or requires both checks, then // we need to consider this class as an alternative. if ((nTestSpellLevel < nSpellLevel) && (nPCCasterLevel == 0)) { // PC will need a UMD class check, and an attribute check, but this class is more favorable. nSpellLevel = nTestSpellLevel; nSpellClass = CLASS_TYPE_DRUID; sClass = "Druid"; nSpellDiff = nTestDiff; bAttributeFail = TRUE; nAttemptLevel = 1; } } else if ((nAttemptLevel <= 2) && bHasUMD) { // The PC has the class, but needs the attribute. Use this class if it's better than the alternative. if ((nTestSpellLevel < nSpellLevel) && (nPCCasterLevel == 0)) { // PC will need a UMD class check, and an attribute check, but this class is more favorable. nSpellLevel = nTestSpellLevel; nSpellClass = CLASS_TYPE_DRUID; sClass = "Druid"; nSpellDiff = nTestDiff; bAttributeFail = TRUE; nAttemptLevel = 2; } } else if (nAttemptLevel == 0) { // only count this as an attribute fail if no other class works. bAttributeFail = TRUE; } } } // now checking Paladin levels. nPCCasterLevel = ACR_GetCorrectCasterLevel(oCaster, CLASS_TYPE_PALADIN); // Paladins only cast from 4th onward, but still have CL 0 for scrolls. if ((nPCCasterLevel > 0) || bHasUMD) { sTest = Get2DAString("spells", "Paladin", nScrollID);

       if (sTest != "") {	

nTestSpellLevel = StringToInt(sTest); //SendMessageToPC(oCaster, "Paladin scroll- level "+sTest); if (GetAbilityScore(oCaster, ABILITY_WISDOM) >= (nTestSpellLevel + 10)) { // caster has high enough attribute nTestDiff = _ScrollCastDiff(CLASS_TYPE_PALADIN, nPCCasterLevel, nTestSpellLevel); if (nPCCasterLevel == 0) { if ((ACR_GetMinSpellCL(nTestSpellLevel, CLASS_TYPE_PALADIN) < ACR_GetMinSpellCL(nSpellLevel, nSpellClass)) && (nAttemptLevel < 4)) { nSpellLevel = nTestSpellLevel; nSpellClass = CLASS_TYPE_PALADIN; sClass = "Paladin"; nSpellDiff = nTestDiff; bAttributeFail = FALSE; nAttemptLevel = 3; } } else if (nTestDiff < nSpellDiff) { // then we're better off using paladin levels to activate. nSpellLevel = nTestSpellLevel; nSpellClass = CLASS_TYPE_PALADIN; sClass = "Paladin"; nSpellDiff = nTestDiff; bAttributeFail = FALSE; nAttemptLevel = 4; } } else if ((nAttemptLevel <= 1) && bHasUMD) { // if nAttemptLevel is not set yet, or requires both checks, then // we need to consider this class as an alternative. if ((nTestSpellLevel < nSpellLevel) && (nPCCasterLevel == 0)) { // PC will need a UMD class check, and an attribute check, but this class is more favorable. nSpellLevel = nTestSpellLevel; nSpellClass = CLASS_TYPE_PALADIN; sClass = "Paladin"; nSpellDiff = nTestDiff; bAttributeFail = TRUE; nAttemptLevel = 1; } } else if ((nAttemptLevel <= 2) && bHasUMD) { // The PC has the class, but needs the attribute. Use this class if it's better than the alternative. if ((nTestSpellLevel < nSpellLevel) && (nPCCasterLevel == 0)) { // PC will need a UMD class check, and an attribute check, but this class is more favorable. nSpellLevel = nTestSpellLevel; nSpellClass = CLASS_TYPE_PALADIN; sClass = "Paladin"; nSpellDiff = nTestDiff; bAttributeFail = TRUE; nAttemptLevel = 2; } } else if (nAttemptLevel == 0) { // only count this as an attribute fail if no other class works. bAttributeFail = TRUE; } } } // check for ranger levels. nPCCasterLevel = ACR_GetCorrectCasterLevel(oCaster, CLASS_TYPE_RANGER); // Rangers only cast from 4th onward, but still have CL 0 for scrolls. if ((nPCCasterLevel > 0) || bHasUMD) { sTest = Get2DAString("spells", "Ranger", nScrollID);

       if (sTest != "") {	

nTestSpellLevel = StringToInt(sTest); //SendMessageToPC(oCaster, "Ranger scroll- level "+sTest); if (GetAbilityScore(oCaster, ABILITY_WISDOM) >= (nTestSpellLevel + 10)) { // caster has high enough attribute nTestDiff = _ScrollCastDiff(CLASS_TYPE_RANGER, nPCCasterLevel, nTestSpellLevel); if (nPCCasterLevel == 0) { if ((ACR_GetMinSpellCL(nTestSpellLevel, CLASS_TYPE_RANGER) < ACR_GetMinSpellCL(nSpellLevel, nSpellClass)) && (nAttemptLevel < 4)) { nSpellLevel = nTestSpellLevel; nSpellClass = CLASS_TYPE_RANGER; sClass = "Ranger"; nSpellDiff = nTestDiff; bAttributeFail = FALSE; nAttemptLevel = 3; } } else if (nTestDiff < nSpellDiff) { // then we are better off casting as a ranger. nSpellLevel = nTestSpellLevel; nSpellClass = CLASS_TYPE_RANGER; sClass = "Ranger"; nSpellDiff = nTestDiff; bAttributeFail = FALSE; nAttemptLevel = 4; } } else if ((nAttemptLevel <= 1) && bHasUMD) { // if nAttemptLevel is not set yet, or requires both checks, then // we need to consider this class as an alternative. if ((nTestSpellLevel < nSpellLevel) && (nPCCasterLevel == 0)) { // PC will need a UMD class check, and an attribute check, but this class is more favorable. nSpellLevel = nTestSpellLevel; nSpellClass = CLASS_TYPE_RANGER; sClass = "Ranger"; nSpellDiff = nTestDiff; bAttributeFail = TRUE; nAttemptLevel = 1; } } else if ((nAttemptLevel <= 2) && bHasUMD) { // The PC has the class, but needs the attribute. Use this class if it's better than the alternative. if ((nTestSpellLevel < nSpellLevel) && (nPCCasterLevel == 0)) { // PC will need a UMD class check, and an attribute check, but this class is more favorable. nSpellLevel = nTestSpellLevel; nSpellClass = CLASS_TYPE_RANGER; sClass = "Ranger"; nSpellDiff = nTestDiff; bAttributeFail = TRUE; nAttemptLevel = 2; } } else if (nAttemptLevel == 0) { // only count this as an attribute fail if no other class works. bAttributeFail = TRUE; } } } // check Wizard levels next nPCCasterLevel = ACR_GetCorrectCasterLevel(oCaster, CLASS_TYPE_WIZARD); if ((nPCCasterLevel > 0) || bHasUMD) { // is the spell on the wizard spell list? sTest = Get2DAString("Spells", "Wiz_Sorc", nScrollID);

       if (sTest != "") {	

nTestSpellLevel = StringToInt(sTest); //SendMessageToPC(oCaster, "Wizard scroll- level "+sTest); if (GetAbilityScore(oCaster, ABILITY_INTELLIGENCE) >= (nTestSpellLevel + 10)) { // caster has high enough attribute // now, need to decide if we are better off using cleric or bard levels. nTestDiff = _ScrollCastDiff(CLASS_TYPE_WIZARD, nPCCasterLevel, nTestSpellLevel); if (nPCCasterLevel == 0) { if ((ACR_GetMinSpellCL(nTestSpellLevel, CLASS_TYPE_WIZARD) < ACR_GetMinSpellCL(nSpellLevel, nSpellClass)) && (nAttemptLevel < 4)) { nSpellLevel = nTestSpellLevel; nSpellClass = CLASS_TYPE_WIZARD; sClass = "Wizard"; nSpellDiff = nTestDiff; bAttributeFail = FALSE; nAttemptLevel = 3; } } else if (nTestDiff < nSpellDiff) { // cleric levels have a better chance to activate. nSpellLevel = nTestSpellLevel; nSpellClass = CLASS_TYPE_WIZARD; sClass = "Wizard"; nSpellDiff = nTestDiff; bAttributeFail = FALSE;

					nAttemptLevel = 4;

} } else if ((nAttemptLevel <= 1) && bHasUMD) { // if nAttemptLevel is not set yet, or requires both checks, then // we need to consider this class as an alternative. if ((nTestSpellLevel < nSpellLevel) && (nPCCasterLevel == 0)) { // PC will need a UMD class check, and an attribute check, but this class is more favorable. nSpellLevel = nTestSpellLevel; nSpellClass = CLASS_TYPE_WIZARD; sClass = "Wizard"; nSpellDiff = nTestDiff; bAttributeFail = TRUE; nAttemptLevel = 1; } } else if ((nAttemptLevel <= 2) && bHasUMD) { // The PC has the class, but needs the attribute. Use this class if it's better than the alternative. if ((nTestSpellLevel < nSpellLevel) && (nPCCasterLevel == 0)) { // PC will need a UMD class check, and an attribute check, but this class is more favorable. nSpellLevel = nTestSpellLevel; nSpellClass = CLASS_TYPE_WIZARD; sClass = "Wizard"; nSpellDiff = nTestDiff; bAttributeFail = TRUE; nAttemptLevel = 2; } } else if (nAttemptLevel == 0) { // only count this as an attribute fail if no other class works. bAttributeFail = TRUE; } } } // check spirit shaman levels // -Spirit shamans draw from the druid list, using wisdom- so most UMD cases are better // done via druid class simulation, as it has a faster progression. // only UMD check I forsee here is a SS using a scroll they don't have the attribute for. nPCCasterLevel = ACR_GetCorrectCasterLevel(oCaster, CLASS_TYPE_SPIRIT_SHAMAN); if (nPCCasterLevel > 0) { // is the spell on the cleric spell list? sTest = Get2DAString("spells", "Druid", nScrollID);

       if (sTest != "") {	

nTestSpellLevel = StringToInt(sTest); //SendMessageToPC(oCaster, "Spirit Shaman scroll- level "+sTest); if (GetAbilityScore(oCaster, ABILITY_WISDOM) >= (nTestSpellLevel + 10)) { // caster has high enough attribute // now, need to decide if we are better off using cleric or bard levels. nTestDiff = _ScrollCastDiff(CLASS_TYPE_SPIRIT_SHAMAN, nPCCasterLevel, nTestSpellLevel); if (nPCCasterLevel == 0) { if ((ACR_GetMinSpellCL(nTestSpellLevel, CLASS_TYPE_SPIRIT_SHAMAN) < ACR_GetMinSpellCL(nSpellLevel, nSpellClass)) && (nAttemptLevel < 4)) { nSpellLevel = nTestSpellLevel; nSpellClass = CLASS_TYPE_SPIRIT_SHAMAN; sClass = "Spitit Shaman"; nSpellDiff = nTestDiff; bAttributeFail = FALSE; nAttemptLevel = 3; } } else if (nTestDiff < nSpellDiff) { // cleric levels have a better chance to activate. nSpellLevel = nTestSpellLevel; nSpellClass = CLASS_TYPE_SPIRIT_SHAMAN; sClass = "Spirit Shaman"; nSpellDiff = nTestDiff; bAttributeFail = FALSE; } } else if ((nAttemptLevel <= 2) && bHasUMD) { // The PC has the class, but needs the attribute. Use this class if it's better than the alternative. if ((nTestSpellLevel < nSpellLevel) && (nPCCasterLevel == 0)) { // PC will need a UMD class check, and an attribute check, but this class is more favorable. nSpellLevel = nTestSpellLevel; nSpellClass = CLASS_TYPE_SPIRIT_SHAMAN; sClass = "Spirit Shaman"; nSpellDiff = nTestDiff; bAttributeFail = TRUE; nAttemptLevel = 2; } } else if (nAttemptLevel == 0) { // only count this as an attribute fail if no other class works. bAttributeFail = TRUE; } } } // check Favored Soul levels next // Favored Souls draw from the cleric spell list, but by Charisma- so a high CHA, low // WIS UMD user is almost always going to be be better off emulating a FS. nPCCasterLevel = ACR_GetCorrectCasterLevel(oCaster, CLASS_TYPE_FAVORED_SOUL); if ((nPCCasterLevel > 0) || bHasUMD) { // is the spell on the cleric spell list? sTest = Get2DAString("spells", "Cleric", nScrollID);

       if (sTest != "") {	

nTestSpellLevel = StringToInt(sTest); //SendMessageToPC(oCaster, "Favored Soul scroll- level "+sTest); if (GetAbilityScore(oCaster, ABILITY_CHARISMA) >= (nTestSpellLevel + 10)) { // caster has high enough attribute // now, need to decide if we are better off using cleric or bard levels. nTestDiff = _ScrollCastDiff(CLASS_TYPE_FAVORED_SOUL, nPCCasterLevel, nTestSpellLevel); if (nPCCasterLevel == 0) { if ((ACR_GetMinSpellCL(nTestSpellLevel, CLASS_TYPE_FAVORED_SOUL) < ACR_GetMinSpellCL(nSpellLevel, nSpellClass)) && (nAttemptLevel < 4)) { nSpellLevel = nTestSpellLevel; nSpellClass = CLASS_TYPE_FAVORED_SOUL; sClass = "Favored Soul"; nSpellDiff = nTestDiff; bAttributeFail = FALSE; nAttemptLevel = 3; } } else if (nTestDiff < nSpellDiff) { // cleric levels have a better chance to activate. nSpellLevel = nTestSpellLevel; nSpellClass = CLASS_TYPE_FAVORED_SOUL; sClass = "Favored Soul"; nSpellDiff = nTestDiff; bAttributeFail = FALSE; nAttemptLevel = 4; } } else if ((nAttemptLevel <= 1) && bHasUMD) { // if nAttemptLevel is not set yet, or requires both checks, then // we need to consider this class as an alternative. if ((nTestSpellLevel < nSpellLevel) && (nPCCasterLevel == 0)) { // PC will need a UMD class check, and an attribute check, but this class is more favorable. nSpellLevel = nTestSpellLevel; nSpellClass = CLASS_TYPE_FAVORED_SOUL; sClass = "Favored Soul"; nSpellDiff = nTestDiff; bAttributeFail = TRUE; nAttemptLevel = 1; } } else if ((nAttemptLevel <= 2) && bHasUMD) { // The PC has the class, but needs the attribute. Use this class if it's better than the alternative. if ((nTestSpellLevel < nSpellLevel) && (nPCCasterLevel == 0)) { // PC will need a UMD class check, and an attribute check, but this class is more favorable. nSpellLevel = nTestSpellLevel; nSpellClass = CLASS_TYPE_FAVORED_SOUL; sClass = "Favored Soul"; nSpellDiff = nTestDiff; bAttributeFail = TRUE; nAttemptLevel = 2; } } else if (nAttemptLevel == 0) { // only count this as an attribute fail if no other class works. bAttributeFail = TRUE; } } } // finally, check sorceror levels // Wiz spell list, but uses CHA as the stat, so higher CHA UMD users will want to test. nPCCasterLevel = ACR_GetCorrectCasterLevel(oCaster, CLASS_TYPE_SORCERER); if (nPCCasterLevel > 0) { // is the spell on the cleric spell list? sTest = Get2DAString("spells", "Wiz_Sorc", nScrollID);

       if (sTest != "") {	

nTestSpellLevel = StringToInt(sTest); //SendMessageToPC(oCaster, "Sorceror scroll- level "+sTest); if (GetAbilityScore(oCaster, ABILITY_CHARISMA) >= (nTestSpellLevel + 10)) { // caster has high enough attribute // now, need to decide if we are better off using cleric or bard levels. nTestDiff = _ScrollCastDiff(CLASS_TYPE_SORCERER, nPCCasterLevel, nTestSpellLevel); if (nPCCasterLevel == 0) { if ((ACR_GetMinSpellCL(nTestSpellLevel, CLASS_TYPE_SORCERER) < ACR_GetMinSpellCL(nSpellLevel, nSpellClass)) && (nAttemptLevel < 4)) { nSpellLevel = nTestSpellLevel; nSpellClass = CLASS_TYPE_SORCERER; sClass = "Sorcerer"; nSpellDiff = nTestDiff; bAttributeFail = FALSE; nAttemptLevel = 3; } } else if (nTestDiff < nSpellDiff) { // cleric levels have a better chance to activate. nSpellLevel = nTestSpellLevel; nSpellClass = CLASS_TYPE_SORCERER; sClass = "Sorcerer"; nSpellDiff = nTestDiff; bAttributeFail = FALSE; nAttemptLevel = 4; } } else if ((nAttemptLevel <= 1) && bHasUMD) { // if nAttemptLevel is not set yet, or requires both checks, then // we need to consider this class as an alternative. if ((nTestSpellLevel < nSpellLevel) && (nPCCasterLevel == 0)) { // PC will need a UMD class check, and an attribute check, but this class is more favorable. nSpellLevel = nTestSpellLevel; nSpellClass = CLASS_TYPE_SORCERER; sClass = "Sorcerer"; nSpellDiff = nTestDiff; bAttributeFail = TRUE; nAttemptLevel = 1; } } else if ((nAttemptLevel <= 2) && bHasUMD) { // The PC has the class, but needs the attribute. Use this class if it's better than the alternative. if ((nTestSpellLevel < nSpellLevel) && (nPCCasterLevel == 0)) { // PC will need a UMD class check, and an attribute check, but this class is more favorable. nSpellLevel = nTestSpellLevel; nSpellClass = CLASS_TYPE_SORCERER; sClass = "Sorcerer"; nSpellDiff = nTestDiff; bAttributeFail = TRUE; nAttemptLevel = 2; } } else if (nAttemptLevel == 0) { // only count this as an attribute fail if no other class works. bAttributeFail = TRUE; } } } if (bAttributeFail) { if (bHasUMD) { // PC doesn't qualify to cast the spell by any possessed or simulated classes. // They are allowed an attempt to simulate the required attribute. int nAttributeDC = 15 + 10 + nSpellLevel; int nRoll = d20(1); if (GetSkillRank(SKILL_SPELLCRAFT, oCaster, TRUE) >= 5) { // synergy bonus for scroll use nAttributeDC = nAttributeDC - 2; SendMessageToPC(oCaster, "Spellcraft Synergy Applied: DC reduced to "+IntToString(nAttributeDC)); } if (GetSkillRank(SKILL_DECIPHER_SCRIPT, oCaster, TRUE) >= 5) { // synergy bonus for scroll use nAttributeDC = nAttributeDC - 2; SendMessageToPC(oCaster, "Decipher Script Synergy Applied: DC reduced to "+IntToString(nAttributeDC)); } if ((nRoll + nUMDRanks) >= nAttributeDC) { // don't return here, even though it's successful- caster level check and/or UMD class check may still be needed. SendMessageToPC(oCaster, "Use Magic Device Roll to emulate ability score: "+IntToString(nRoll)+"+"+IntToString(nUMDRanks)+"="+IntToString(nRoll+nUMDRanks)+" vs. DC "+IntToString(nAttributeDC)+": Success!"); bAttributeFail = FALSE; } else { SendMessageToPC(oCaster, "Use Magic Device Roll to emulate ability score: "+IntToString(nRoll)+"+"+IntToString(nUMDRanks)+"="+IntToString(nRoll+nUMDRanks)+" vs. DC "+IntToString(nAttributeDC)+": Failure!"); return 0; } } else { // without UMD, the attempt will fail. SendMessageToPC(oCaster, "Your attributes are not high enough to activate this scroll."); return 0; } }

if (nSpellDiff <= 1) { // the user can activate the scroll without a check, due to level and attributes. return 1;

} else if (nAttemptLevel == 0) { // no class was found to allow you to activate, fail. SendMessageToPC(oCaster, "You cannot activate this scroll."); return 0;

} else if ((GetLevelByClass(nSpellClass, oCaster) == 0) && bHasUMD) { // caster does not have appropriate levels to attempt the spell, must make UMD check.

       int nUMDDC = 20 + _ScrollCastDiff(nSpellClass, 0, nSpellLevel) - 1;

int nRoll2 = d20(1); SendMessageToPC(oCaster, "Attempting to activate by emulating a "+sClass); if (GetSkillRank(SKILL_SPELLCRAFT, oCaster, TRUE) >= 5) { // synergy bonus for scroll use nUMDDC = nUMDDC - 2; SendMessageToPC(oCaster, "Spellcraft Synergy Applied: DC reduced to "+IntToString(nUMDDC)); } if (GetSkillRank(SKILL_DECIPHER_SCRIPT, oCaster, TRUE) >= 5) { // synergy bonus for scroll use nUMDDC = nUMDDC - 2; SendMessageToPC(oCaster, "Decipher Script Synergy Applied: DC reduced to "+IntToString(nUMDDC)); } if ((nRoll2 + nUMDRanks) >= nUMDDC) { SendMessageToPC(oCaster, "Use Magic Device Roll to activate scroll: "+IntToString(nRoll2)+"+"+IntToString(nUMDRanks)+"="+IntToString(nRoll2+nUMDRanks)+" vs. DC "+IntToString(nUMDDC)+": Success!"); return 1; } else if ((nRoll2+ nUMDRanks) <= (nUMDDC-10)) { // missed it by a mile, auto-mishap as per PhB p.85 SendMessageToPC(oCaster, "Use Magic Device Roll to activate scroll: "+IntToString(nRoll2)+"+"+IntToString(nUMDRanks)+"="+IntToString(nRoll2+nUMDRanks)+" vs. DC "+IntToString(nUMDDC)+": Critical Failure!"); _ProcessMishap(oCaster, nSpellLevel, nScrollID); return -1; } else { SendMessageToPC(oCaster, "Use Magic Device Roll to activate scroll: "+IntToString(nRoll2)+"+"+IntToString(nUMDRanks)+"="+IntToString(nRoll2+nUMDRanks)+" vs. DC "+IntToString(nUMDDC)+": Failure!"); if (_ScrollMishapCheck(oCaster, nScrollID, nSpellLevel, nScrollID)) {

               return -1;

} else { return 0; } } } else { // have to run a caster level check, with chance of failure. SendMessageToPC(oCaster, "Attempting to activate scroll using "+sClass+" level(s)."); return _AttemptScrollUse(nScrollID, nSpellLevel, oCaster, nSpellClass); } }

int _ScrollCastDiff(int nClass, int nUserLevel, int nSpellLevel) {

if (nSpellLevel == 0) { // special handling for 0th level spells, as they don't work well for the math. if (nUserLevel >= 1) { // Caster has the right class, return <=1 return 0; } else { // Caster has no appropriate levels to cast it, return > 1. return 2; } }

   if (nClass == CLASS_TYPE_BARD) {

if (nSpellLevel < 3) { return (nSpellLevel*2 - nUserLevel +1); } else { return ((nSpellLevel*3 -2) - nUserLevel +1); }

// clerics, druids, and wizards all gain spell level access at the same rate } else if ((nClass == CLASS_TYPE_CLERIC) || (nClass == CLASS_TYPE_DRUID) || (nClass == CLASS_TYPE_WIZARD)) { return ((nSpellLevel*2 - 1) - nUserLevel +1);

// sorcerors, spirit shamans, and favored souls all use a staggered spell level advancement } else if ((nClass == CLASS_TYPE_SORCERER) || (nClass == CLASS_TYPE_FAVORED_SOUL) || (nClass == CLASS_TYPE_SPIRIT_SHAMAN)) { if (nSpellLevel < 2) { return (1 - nUserLevel +1); } else {

           return (nSpellLevel*2 - nUserLevel);

}

// Paladins and Rangers use the same delayed spell level table } else if ((nClass == CLASS_TYPE_PALADIN) || (nClass == CLASS_TYPE_RANGER)) { if (nUserLevel < 4) { nUserLevel = 0; } if (nSpellLevel < 3) { return ((nSpellLevel*4 - nUserLevel+1)/2 +1); } else if (nSpellLevel == 3) { return ((6 - nUserLevel/2)); } else { // spell is 4th level return ((7 - nUserLevel/2)+1); } } else { return 99; } }

int _AttemptScrollUse(int nScrollID, int nSpellLevel, object oCaster, int nSpellClass) {

   int nRoll = d20(1);

if (nRoll == 1) { SendMessageToPC(oCaster, "Caster Level check: Critical failure!"); if (_ScrollMishapCheck(oCaster, nScrollID, nSpellLevel, nScrollID)) {

           return -1;

} else { return 0; } } int nScrollDC = 1 + ACR_GetMinSpellCL(nSpellLevel, nSpellClass); int nCL = ACR_GetCasterLevel(oCaster, nSpellClass); if (nRoll+nCL >= nScrollDC) {

       SendMessageToPC(oCaster, "Caster Level check: "+IntToString(nRoll)+"+"+IntToString(nCL)+"="+IntToString(nCL+nRoll)+" vs. DC "+IntToString(nScrollDC)+": Success! "); 

return 1; } else { SendMessageToPC(oCaster, "Caster Level check: "+IntToString(nRoll)+"+"+IntToString(nCL)+"="+IntToString(nCL+nRoll)+" vs. DC "+IntToString(nScrollDC)+": Failure! "); if (_ScrollMishapCheck(oCaster, nScrollID, nSpellLevel, nScrollID)) {

           return -1;

} else { return 0; } } }

int _ScrollMishapCheck(object oCaster, int nScrollID, int nSpellLevel, int nSpellID) {

   int nCheck = d20(1);

int nWisBonus = GetAbilityModifier(ABILITY_WISDOM, oCaster); int nTotal = nCheck + nWisBonus; SendMessageToPC(oCaster, "Rolling to avoid mishap: "+IntToString(nCheck)+"+"+IntToString(nWisBonus)+"= "+IntToString(nTotal)+", vs. DC 5"); if ((nTotal < 5) || (nCheck == 1)) { // Scroll mishap!

       _ProcessMishap(oCaster, nSpellLevel, nSpellID);

return TRUE; } else { SendMessageToPC(oCaster, "Mishap averted!"); }

   return FALSE;

}


void _ProcessMishap(object oCaster, int nSpellLevel, int nSpellID) {

SendMessageToPC(oCaster, "Failure! The spell goes awry..."); int nMishapType = d4(1); object oTarget = GetSpellTargetObject(); effect eMishap; if (nMishapType == 1) { // raw damage backfire, d6 / spell level magical damage to caster eMishap = EffectDamage(d6(nSpellLevel), DAMAGE_TYPE_MAGICAL, DAMAGE_POWER_NORMAL, TRUE); eMishap = EffectLinkEffects(eMishap, EffectVisualEffect(VFX_IMP_MAGBLUE)); ApplyEffectToObject(DURATION_TYPE_INSTANT, eMishap, oCaster);

} else if (nMishapType == 2) { // target switch SendMessageToPC(oCaster, "The spell seems to have been misdirected!"); if (oTarget == OBJECT_INVALID) { // spell is aimed at a location, misdirect it vector vLoc = GetPositionFromLocation(GetSpellTargetLocation()); vector vNew = Vector(vLoc.x+(ACR_RandomFloat(-12.0, 12.0)), vLoc.y+(ACR_RandomFloat(-12.0, 12.0)), vLoc.z); location lNew = Location(GetAreaFromLocation(GetSpellTargetLocation()), vNew, 0.0); AssignCommand(oCaster, ActionCastSpellAtLocation(nSpellID, lNew, METAMAGIC_NONE, TRUE, PROJECTILE_PATH_TYPE_DEFAULT, TRUE)); } else { // spell was aimed at a creature or object- switch target if (GetIsEnemy(oTarget, oCaster)) { // aiming for an enemy? Redirect to caster AssignCommand(oCaster, ActionCastSpellAtObject(nSpellID, oCaster, METAMAGIC_NONE, TRUE, 0, PROJECTILE_PATH_TYPE_DEFAULT, TRUE)); } else if (oTarget == oCaster) { // caster aiming at him/herself? Aim elsewhere. object oNewTarget = GetNearestCreature(CREATURE_TYPE_IS_ALIVE, CREATURE_ALIVE_BOTH, oCaster, d6(1)); AssignCommand(oCaster, ActionCastSpellAtObject(nSpellID, oNewTarget, METAMAGIC_NONE, TRUE, 0, PROJECTILE_PATH_TYPE_DEFAULT, TRUE)); } }

} else if (nMishapType == 3) { // delayed effect int nDelay = d12(1); SendMessageToPC(oCaster, "The spell seems to have no effect... for now."); DelayCommand(HoursToSeconds(nDelay), AssignCommand(oTarget, ActionCastSpellAtObject(nSpellID, oTarget, METAMAGIC_NONE, TRUE, 0, PROJECTILE_PATH_TYPE_DEFAULT, TRUE)));

} else if (nMishapType <= 4) { // innocuous effect, scroll user int nDuration = d10(2); switch (d10(1)) { case 1: eMishap = EffectVisualEffect(VFX_DUR_INVOCATION_TENACIOUS_PLAGUE); break; case 2: eMishap = EffectVisualEffect(VFX_DUR_SHINING_SHIELD); break; case 3: eMishap = EffectVisualEffect(VFX_DUR_SOOTHING_LIGHT); break; case 4: eMishap = EffectVisualEffect(VFX_DUR_SACRED_FLAMES); break; case 5: eMishap = EffectVisualEffect(VFX_DUR_SHADOW_CLOAK); break; case 6: eMishap = EffectVisualEffect(VFX_DUR_STUN); break; case 7: eMishap = EffectVisualEffect(VFX_DUR_FIRE); break; case 8: eMishap = EffectVisualEffect(VFX_INVOCATION_BRIMSTONE_CHAIN2); break; case 9: eMishap = EffectVisualEffect(VFX_DUR_BLUR); break; default: eMishap = EffectVisualEffect(VFX_INVOCATION_ELDRITCH_CHAIN); break; } ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eMishap, oCaster, (IntToFloat(nDuration)*8.57)); }

}