//:://///////////////////////////////////////////// //:: Shadowcasting include: Mysteries Known //:: shd_inc_mystknwn //:://///////////////////////////////////////////// /** @file Defines functions for adding & removing Mysteries known. Data stored: - For each Path list -- Total number of Mysteries known -- A modifier value to maximum Mysteries known on this list to account for feats and classes that add Mysteries -- An array related to Mysteries the knowledge of which is not dependent on character level --- Each array entry specifies the spells.2da row of the known Mysteries's class-specific entry -- For each character level on which Mysteries have been gained from this list --- An array of Mysteries gained on this level ---- Each array entry specifies the spells.2da row of the known Mysteries's class-specific entry @author Stratovarius @date Created - 2019.02.06 */ //::////////////////////////////////////////////// //::////////////////////////////////////////////// ////////////////////////////////////////////////// /* Constants */ ////////////////////////////////////////////////// const int MYSTERY_LIST_SHADOWCASTER = 136; const int MYSTERY_LIST_SHADOWSMITH = 98; /// Special Mystery list. Mysteries gained via other sources. const int MYSTERY_LIST_MISC = CLASS_TYPE_INVALID;//-1; const string _MYSTERY_LIST_NAME_BASE = "PRC_MysteryList_"; const string _MYSTERY_LIST_PATH = "PRC_PathTotal_"; const string _MYSTERY_LIST_TOTAL_KNOWN = "_TotalKnown"; const string _MYSTERY_LIST_MODIFIER = "_KnownModifier"; const string _MYSTERY_LIST_MISC_ARRAY = "_MysteriesKnownMiscArray"; const string _MYSTERY_LIST_LEVEL_ARRAY = "_MysteriesKnownLevelArray_"; const string _MYSTERY_LIST_GENERAL_ARRAY = "_MysteriesKnownGeneralArray"; ////////////////////////////////////////////////// /* Function prototypes */ ////////////////////////////////////////////////// /** * Gives the creature the control feats for the given Mystery and marks the Mystery * in a Mysteries known array. * If the Mystery's data is already stored in one of the Mysteries known arrays for * the list or adding the Mystery's data to the array fails, the function aborts. * * @param oShadow The creature to gain the Mystery * @param nList The list the Mystery comes from. One of MYSTERY_LIST_* * @param n2daRow The 2da row in the lists's 2da file that specifies the Mystery. * @param bLevelDependent If this is TRUE, the Mystery is tied to a certain level and can * be lost via level loss. If FALSE, the Mystery is not dependent * of a level and cannot be lost via level loss. * @param nLevelToTieTo If bLevelDependent is TRUE, this specifies the level the Mystery * is gained on. Otherwise, it's ignored. * The default value (-1) means that the current level of oShadow * will be used. * * @return TRUE if the Mystery was successfully stored and control feats added. * FALSE otherwise. */ int AddMysteryKnown(object oShadow, int nList, int n2daRow, int bLevelDependent = FALSE, int nLevelToTieTo = -1); /** * Removes all Mysteries gained from each list on the given level. * * @param oShadow The creature whose Mysteries to remove * @param nLevel The level to clear */ void RemoveMysteriesKnownOnLevel(object oShadow, int nLevel); /** * Gets the value of the Mysteries known modifier, which is a value that is added * to the 2da-specified maximum Mysteries known to determine the actual maximum. * * @param oShadow The creature whose modifier to get * @param nList The list the maximum Mysteries known from which the modifier * modifies. One of MYSTERY_LIST_* */ int GetKnownMysteriesModifier(object oShadow, int nList); /** * Sets the value of the Mysteries known modifier, which is a value that is added * to the 2da-specified maximum Mysteries known to determine the actual maximum. * * @param oShadow The creature whose modifier to set * @param nList The list the maximum Mysteries known from which the modifier * modifies. One of MYSTERY_LIST_* */ void SetKnownMysteriesModifier(object oShadow, int nList, int nNewValue); /** * Gets the number of Mysteries a character possesses * * @param oShadow The creature whose Mysteries to check * @param nList The list to check. One of MYSTERY_LIST_* * * @return The number of Mysteries known oShadow has from nList */ int GetMysteryCount(object oShadow, int nList); /** * Gets the number of Mysteries a character character possesses from a * specific path * * @param oShadow The creature whose Mysteries to check * @param nPath The path to check. One of PATH_* * * @return The number of Mysteries known oShadow has from nPath */ int GetMysteryCountByPath(object oShadow, int nPath); /** * Gets the maximum number of Mysteries a character may possess from a given list * at this time. Calculated based on class levels, feats and a misceallenous * modifier. * * @param oShadow Character to determine maximum Mysteries for * @param nList MYSTERY_LIST_* of the list to determine maximum Mysteries for * * @return Maximum number of Mysteries that oShadow may know from the given list. */ int GetMaxMysteryCount(object oShadow, int nList); /** * Determines whether a character has a given Mystery, gained via some Mystery list. * * @param nMystery Mystery_* of the Mystery to test * @param oShadow Character to test for the possession of the Mystery * @return TRUE if the character has the Mystery, FALSE otherwise */ int GetHasMystery(int nMystery, object oShadow = OBJECT_SELF); /** * Gets the highest Mystery level a character may learn at this time. * * @param oShadow Character to determine maximum Mystery level for * @param nClass Class to check against * @param nType 1 = apprentice, 2 = initiate, 3 = master * * @return Maxmimum mystery level */ int GetMaxMysteryLevelLearnable(object oShadow, int nClass, int nType); /** * Gets the total number of completed paths for the character * * @param oShadow Character to check */ int GetCompletedPaths(object oShadow); /** * Adds the feat as a bonus feat gained from having completed a path. Aborts if the feat * was one the character already had * * @param oShadow Character to check * @param nFeat Feat to add */ void AddPathBonusFeat(object oShadow, int nFeat); /** * Returns how many Path Bonus feats the character has * * @param oShadow Character to check */ int GetPathBonusFeats(object oShadow); /** * Reapplies any missing Path Bonus feats the character has chosen and doesn't have * * @param oShadow Character to check */ void ReapplyPathBonusFeat(object oShadow); /** * Returns the IP_CONST version of the feat. * Only works on Bonus Path feats * * @param nFeat Feat to itemproperty */ int PathFeatToIPFeat(int nFeat); ////////////////////////////////////////////////// /* Includes */ ////////////////////////////////////////////////// ////////////////////////////////////////////////// /* Internal functions */ ////////////////////////////////////////////////// void _MysteryRecurseRemoveArray(object oShadow, string sArrayName, string sMystFile, int nArraySize, int nCurIndex) { if(DEBUG) DoDebug("_MysteryRecurseRemoveArray():\n" + "oShadow = " + DebugObject2Str(oShadow) + "\n" + "sArrayName = '" + sArrayName + "'\n" + "sMystFile = '" + sMystFile + "'\n" + "nArraySize = " + IntToString(nArraySize) + "\n" + "nCurIndex = " + IntToString(nCurIndex) + "\n" ); // Determine whether we've already parsed the whole array or not if(nCurIndex >= nArraySize) { if(DEBUG) DoDebug("_MysteryRecurseRemoveArray(): Running itemproperty removal loop."); // Loop over itemproperties on the skin and remove each match object oSkin = GetPCSkin(oShadow); itemproperty ipTest = GetFirstItemProperty(oSkin); while(GetIsItemPropertyValid(ipTest)) { // Check if the itemproperty is a bonus feat that has been marked for removal if(GetItemPropertyType(ipTest) == ITEM_PROPERTY_BONUS_FEAT && GetLocalInt(oShadow, "PRC_MystFeatRemovalMarker_" + IntToString(GetItemPropertySubType(ipTest))) ) { if(DEBUG) DoDebug("_MysteryRecurseRemoveArray(): Removing bonus feat itemproperty:\n" + DebugIProp2Str(ipTest)); // If so, remove it RemoveItemProperty(oSkin, ipTest); DeleteLocalInt(oShadow, "PRC_MystFeatRemovalMarker_" + IntToString(GetItemPropertySubType(ipTest))); } ipTest = GetNextItemProperty(oSkin); } } // Still parsing the array else { int nMystID = GetPowerfileIndexFromSpellID(persistant_array_get_int(oShadow, sArrayName, nCurIndex)); // Set the marker string sName = "PRC_MystFeatRemovalMarker_" + Get2DACache(sMystFile, "IPFeatID", nMystID); if(DEBUG) DoDebug("_MysteryRecurseRemoveArray(): Recursing through array, marker set:\n" + sName); SetLocalInt(oShadow, sName, TRUE); string sPathArray = _MYSTERY_LIST_PATH + "_" + Get2DACache(sMystFile, "Path", nMystID); SetPersistantLocalInt(oShadow, sPathArray, GetPersistantLocalInt(oShadow, sPathArray) - 1); // Recurse to next array index _MysteryRecurseRemoveArray(oShadow, sArrayName, sMystFile, nArraySize, nCurIndex + 1); } } void _RemoveMysteryArray(object oShadow, int nList, int nLevel) { if(DEBUG) DoDebug("_RemoveMysteryArray():\n" + "oShadow = " + DebugObject2Str(oShadow) + "\n" + "nList = " + IntToString(nList) + "\n" ); string sBase = _MYSTERY_LIST_NAME_BASE + IntToString(nList); string sArray = sBase + _MYSTERY_LIST_LEVEL_ARRAY + IntToString(nLevel); int nSize = persistant_array_get_size(oShadow, sArray); // Reduce the total by the array size SetPersistantLocalInt(oShadow, sBase + _MYSTERY_LIST_TOTAL_KNOWN, GetPersistantLocalInt(oShadow, sBase + _MYSTERY_LIST_TOTAL_KNOWN) - nSize ); // Remove each Mystery in the array _MysteryRecurseRemoveArray(oShadow, sArray, GetAMSDefinitionFileName(nList), nSize, 0); // Remove the array itself persistant_array_delete(oShadow, sArray); } ////////////////////////////////////////////////// /* Function definitions */ ////////////////////////////////////////////////// int AddMysteryKnown(object oShadow, int nList, int n2daRow, int bLevelDependent = FALSE, int nLevelToTieTo = -1) { string sBase = _MYSTERY_LIST_NAME_BASE + IntToString(nList); string sArray = sBase; string sPowerFile = GetAMSDefinitionFileName(/*PowerListToClassType(*/nList/*)*/); string sTestArray; int i, j, nSize, bReturn; // Get the spells.2da row corresponding to the cls_psipw_*.2da row int nSpells2daRow = StringToInt(Get2DACache(sPowerFile, "SpellID", n2daRow)); // Determine the array name. if(bLevelDependent) { // If no level is specified, default to the creature's current level if(nLevelToTieTo == -1) nLevelToTieTo = GetHitDice(oShadow); sArray += _MYSTERY_LIST_LEVEL_ARRAY + IntToString(nLevelToTieTo); } else { sArray += _MYSTERY_LIST_GENERAL_ARRAY; } // Make sure the power isn't already in an array. If it is, abort and return FALSE // Loop over each level array and check that it isn't there. for(i = 1; i <= GetHitDice(oShadow); i++) { sTestArray = sBase + _MYSTERY_LIST_LEVEL_ARRAY + IntToString(i); if(persistant_array_exists(oShadow, sTestArray)) { nSize = persistant_array_get_size(oShadow, sTestArray); for(j = 0; j < nSize; j++) if(persistant_array_get_int(oShadow, sArray, j) == nSpells2daRow) return FALSE; } } // Check the non-level-dependent array sTestArray = sBase + _MYSTERY_LIST_GENERAL_ARRAY; if(persistant_array_exists(oShadow, sTestArray)) { nSize = persistant_array_get_size(oShadow, sTestArray); for(j = 0; j < nSize; j++) if(persistant_array_get_int(oShadow, sArray, j) == nSpells2daRow) return FALSE; } // All checks are made, now start adding the new power // Create the array if it doesn't exist yet if(!persistant_array_exists(oShadow, sArray)) persistant_array_create(oShadow, sArray); // Store the power in the array if(persistant_array_set_int(oShadow, sArray, persistant_array_get_size(oShadow, sArray), nSpells2daRow) != SDL_SUCCESS) { if(DEBUG) DoDebug("shd_inc_mystknwn: AddPowerKnown(): ERROR: Unable to add power to known array\n" + "oShadow = " + DebugObject2Str(oShadow) + "\n" + "nList = " + IntToString(nList) + "\n" + "n2daRow = " + IntToString(n2daRow) + "\n" + "bLevelDependent = " + DebugBool2String(bLevelDependent) + "\n" + "nLevelToTieTo = " + IntToString(nLevelToTieTo) + "\n" + "nSpells2daRow = " + IntToString(nSpells2daRow) + "\n" ); return FALSE; } // Increment Mysteries known total SetPersistantLocalInt(oShadow, sBase + _MYSTERY_LIST_TOTAL_KNOWN, GetPersistantLocalInt(oShadow, sBase + _MYSTERY_LIST_TOTAL_KNOWN) + 1 ); // Increment Mysteries known by path string sPathArray = _MYSTERY_LIST_PATH + "_" + Get2DACache(sPowerFile, "Path", n2daRow); SetPersistantLocalInt(oShadow, sPathArray, GetPersistantLocalInt(oShadow, sPathArray) + 1); // Give the power's control feats object oSkin = GetPCSkin(oShadow); string sPowerFeatIP = Get2DACache(sPowerFile, "IPFeatID", n2daRow); itemproperty ipFeat = PRCItemPropertyBonusFeat(StringToInt(sPowerFeatIP)); IPSafeAddItemProperty(oSkin, ipFeat, 0.0f, X2_IP_ADDPROP_POLICY_KEEP_EXISTING, FALSE, FALSE); // Second power feat, if any /*sPowerFeatIP = Get2DACache(sPowerFile, "IPFeatID2", n2daRow); if(sPowerFeatIP != "") { ipFeat = PRCItemPropertyBonusFeat(StringToInt(sPowerFeatIP)); IPSafeAddItemProperty(oSkin, ipFeat, 0.0f, X2_IP_ADDPROP_POLICY_KEEP_EXISTING, FALSE, FALSE); }*/ return TRUE; } void RemoveMysteriesKnownOnLevel(object oShadow, int nLevel) { if(DEBUG) DoDebug("shd_inc_mystknwn: RemoveMysteryKnownOnLevel():\n" + "oShadow = " + DebugObject2Str(oShadow) + "\n" + "nLevel = " + IntToString(nLevel) + "\n" ); string sPostFix = _MYSTERY_LIST_LEVEL_ARRAY + IntToString(nLevel); // For each Mystery list, determine if an array exists for this level. if(persistant_array_exists(oShadow, _MYSTERY_LIST_NAME_BASE + IntToString(MYSTERY_LIST_SHADOWCASTER) + sPostFix)) // If one does exist, clear it _RemoveMysteryArray(oShadow, MYSTERY_LIST_SHADOWCASTER, nLevel); if(persistant_array_exists(oShadow, _MYSTERY_LIST_NAME_BASE + IntToString(MYSTERY_LIST_SHADOWSMITH) + sPostFix)) _RemoveMysteryArray(oShadow, MYSTERY_LIST_SHADOWSMITH, nLevel); if(persistant_array_exists(oShadow, _MYSTERY_LIST_NAME_BASE + IntToString(MYSTERY_LIST_MISC) + sPostFix)) _RemoveMysteryArray(oShadow, MYSTERY_LIST_MISC, nLevel); } int GetKnownMysteriesModifier(object oShadow, int nList) { return GetPersistantLocalInt(oShadow, _MYSTERY_LIST_NAME_BASE + IntToString(nList) + _MYSTERY_LIST_MODIFIER); } void SetKnownMysteriesModifier(object oShadow, int nList, int nNewValue) { SetPersistantLocalInt(oShadow, _MYSTERY_LIST_NAME_BASE + IntToString(nList) + _MYSTERY_LIST_MODIFIER, nNewValue); } int GetMysteryCount(object oShadow, int nList) { return GetPersistantLocalInt(oShadow, _MYSTERY_LIST_NAME_BASE + IntToString(nList) + _MYSTERY_LIST_TOTAL_KNOWN); } int GetMysteryCountByPath(object oShadow, int nPath) { return GetPersistantLocalInt(oShadow, _MYSTERY_LIST_PATH + "_" + IntToString(nPath)); } int GetMaxMysteryCount(object oShadow, int nList) { int nMaxMysteries = 0; if(nList == MYSTERY_LIST_MISC) { if(DEBUG) DoDebug("GetMaxMysteryCount(): ERROR: Using unfinished power list!"); } else { int nLevel = GetLevelByClass(nList, oShadow); if (nList == GetPrimaryShadowMagicClass(oShadow)) nLevel += GetShadowMagicPRCLevels(oShadow); string sFile = GetAMSKnownFileName(nList); nMaxMysteries = StringToInt(Get2DACache(sFile, "Mysteries", nLevel - 1)); // Calculate feats // Add in the custom modifier nMaxMysteries += GetKnownMysteriesModifier(oShadow, nList); if(DEBUG) DoDebug("GetMaxMysteryCount(): " + IntToString(nList) + " Mysteries: " + IntToString(nMaxMysteries)); } return nMaxMysteries; } int GetHasMystery(int nMystery, object oShadow = OBJECT_SELF) { if((GetLevelByClass(CLASS_TYPE_SHADOWCASTER, oShadow) && GetHasFeat(GetClassFeatFromPower(nMystery, CLASS_TYPE_SHADOWCASTER), oShadow) ) || (GetLevelByClass(CLASS_TYPE_SHADOWSMITH, oShadow) && GetHasFeat(GetClassFeatFromPower(nMystery, CLASS_TYPE_SHADOWSMITH), oShadow) ) // add new classes here ) return TRUE; return FALSE; } string DebugListKnownMysteries(object oShadow) { string sReturn = "Mysteries known by " + DebugObject2Str(oShadow) + ":\n"; int i, j, k, numPowerLists = 6; int nPowerList, nSize; string sTemp, sArray, sArrayBase, sPowerFile; // Loop over all power lists for(i = 1; i <= numPowerLists; i++) { // Some padding sReturn += " "; // Get the power list for this loop switch(i) { case 1: nPowerList = MYSTERY_LIST_SHADOWCASTER; sReturn += "SHADOWCASTER"; break; case 2: nPowerList = MYSTERY_LIST_SHADOWSMITH; sReturn += "SHADOWSMITH"; break; // This should always be last case 6: nPowerList = MYSTERY_LIST_MISC; sReturn += "Misceallenous"; break; } sReturn += " Mysteries known:\n"; // Determine if the character has any Mysteries from this list sPowerFile = GetAMSDefinitionFileName(nPowerList); sArrayBase = _MYSTERY_LIST_NAME_BASE + IntToString(nPowerList); // Loop over levels for(j = 1; j <= GetHitDice(oShadow); j++) { sArray = sArrayBase + _MYSTERY_LIST_LEVEL_ARRAY + IntToString(j); if(persistant_array_exists(oShadow, sArray)) { sReturn += " Gained on level " + IntToString(j) + ":\n"; nSize = persistant_array_get_size(oShadow, sArray); for(k = 0; k < nSize; k++) sReturn += " " + GetStringByStrRef(StringToInt(Get2DACache(sPowerFile, "Name", GetPowerfileIndexFromSpellID(persistant_array_get_int(oShadow, sArray, k)) ) ) ) + "\n"; } } // Non-leveldependent Mysteries sArray = sArrayBase + _MYSTERY_LIST_GENERAL_ARRAY; if(persistant_array_exists(oShadow, sArray)) { sReturn += " Non-leveldependent:\n"; nSize = persistant_array_get_size(oShadow, sArray); for(k = 0; k < nSize; k++) sReturn += " " + GetStringByStrRef(StringToInt(Get2DACache(sPowerFile, "Name", GetPowerfileIndexFromSpellID(persistant_array_get_int(oShadow, sArray, k)) ) ) ) + "\n"; } } return sReturn; } int GetMaxMysteryLevelLearnable(object oShadow, int nClass, int nType) { if(DEBUG) DoDebug("GetMaxMysteryLevelLearnable nType: " + IntToString(nType)); // Rules Quote: // Within a category—Apprentice, Initiate, Master—you must have at least two mysteries of any given level // before you can take any mysteries of the next higher level. For instance, you must have two 1st-level // mysteries before you can take any 2nds, and at least two 2nds before you can take any 3rds. int nMaxLrn, i, nMystLevel, nCount1, nCount2; string sFeatID; int nLevel = GetLevelByClass(nClass, oShadow); if (nClass == GetPrimaryShadowMagicClass(oShadow)) nLevel += GetShadowMagicPRCLevels(oShadow); if(DEBUG) DoDebug("GetMaxMysteryLevelLearnable nClass: " + IntToString(nClass)); string sPowerFile = GetAMSDefinitionFileName(nClass); if(DEBUG) DoDebug("GetMaxMysteryLevelLearnable sPowerFile: " + sPowerFile); if (nType == 3 && nLevel >= 13 && nClass == CLASS_TYPE_SHADOWCASTER) // Master mysteries, must be minimum 13th level { for(i = 0; i < GetPRCSwitch(FILE_END_CLASS_POWER) ; i++) { sFeatID = Get2DACache(sPowerFile, "FeatID", i); if(sFeatID != "" && GetHasFeat(StringToInt(sFeatID), oShadow)) // Make sure it's not a blank mystery and that the PC has it { nMystLevel = StringToInt(Get2DACache(sPowerFile, "Level", i)); if (nMystLevel == 7) nCount1++; else if (nMystLevel == 8) nCount2++; // Don't need to check for 9th because 10th don't exist } } //DoDebug("GetMaxMysteryLevelLearnable nCount1: " + IntToString(nCount1)); //DoDebug("GetMaxMysteryLevelLearnable nCount2: " + IntToString(nCount2)); if (nCount2 > 1) // Need to know at least two nMaxLrn = 9; else if (nCount1 > 1) // Need to know at least two nMaxLrn = 8; else // At 13th level you get access to 7ths regardless nMaxLrn = 7; } else if (nType == 2 && nLevel >= 7 && nClass == CLASS_TYPE_SHADOWCASTER) // Initiate mysteries { for(i = 0; i < GetPRCSwitch(FILE_END_CLASS_POWER) ; i++) { sFeatID = Get2DACache(sPowerFile, "FeatID", i); if(sFeatID != "" && GetHasFeat(StringToInt(sFeatID), oShadow)) // Make sure it's not a blank mystery and that the PC has it { nMystLevel = StringToInt(Get2DACache(sPowerFile, "Level", i)); if (nMystLevel == 4) nCount1++; else if (nMystLevel == 5) nCount2++; // Don't need to check for 6th because 7th is triggered by level } } //DoDebug("GetMaxMysteryLevelLearnable nCount1: " + IntToString(nCount1)); //DoDebug("GetMaxMysteryLevelLearnable nCount2: " + IntToString(nCount2)); if (nCount2 > 1) // Need to know at least two nMaxLrn = 6; else if (nCount1 > 1) // Need to know at least two nMaxLrn = 5; else // At 7th level you get access to 4ths regardless nMaxLrn = 4; } else // Apprentice mysteries { for(i = 0; i < GetPRCSwitch(FILE_END_CLASS_POWER) ; i++) { sFeatID = Get2DACache(sPowerFile, "FeatID", i); if(sFeatID != "" && GetHasFeat(StringToInt(sFeatID), oShadow)) // Make sure it's not a blank mystery and that the PC has it { nMystLevel = StringToInt(Get2DACache(sPowerFile, "Level", i)); if (nMystLevel == 1) nCount1++; else if (nMystLevel == 2) nCount2++; // Don't need to check for 3rd because 4th is triggered by level } } //DoDebug("GetMaxMysteryLevelLearnable nCount1: " + IntToString(nCount1)); //DoDebug("GetMaxMysteryLevelLearnable nCount2: " + IntToString(nCount2)); if (nCount2 > 1) // Need to know at least two nMaxLrn = 3; else if (nCount1 > 1) // Need to know at least two nMaxLrn = 2; else // At 1st level you get access to 1st regardless nMaxLrn = 1; } //DoDebug("GetMaxMysteryLevelLearnable nMaxLrn: " + IntToString(nMaxLrn)); return nMaxLrn; } int GetCompletedPaths(object oShadow) { int i, nReturn, nPath; for(i = 0; i < 24; i++) { //if(DEBUG) DoDebug("GetCompletedPaths(): i " + IntToString(i)); nPath = GetMysteryCountByPath(oShadow, i); //if(DEBUG) DoDebug("GetCompletedPaths(): nPath " + IntToString(nPath)); if(nPath == 3) // Completed Path nReturn++; //Number of completed paths } //if(DEBUG) DoDebug("GetCompletedPaths(): nReturn " + IntToString(nReturn)); // Return the total return nReturn; } void AddPathBonusFeat(object oShadow, int nFeat) { if (GetHasFeat(nFeat, oShadow)) return; // No point adding something they've got int nCount, i; for(i = 1; i < 7 ; i++) { if (GetPersistantLocalInt(oShadow, "PathBonus"+IntToString(i)) > 0) // Make sure to only fill empty ones nCount++; } SetPersistantLocalInt(oShadow, "PathBonus"+IntToString(nCount + 1), nFeat); // Give the power's control feats object oSkin = GetPCSkin(oShadow); int nIPFeat = PathFeatToIPFeat(nFeat); itemproperty ipFeat = PRCItemPropertyBonusFeat(nIPFeat); IPSafeAddItemProperty(oSkin, ipFeat, 0.0f, X2_IP_ADDPROP_POLICY_KEEP_EXISTING, FALSE, FALSE); } int GetPathBonusFeats(object oShadow) { int nCount, i; for(i = 1; i < 7 ; i++) { if (GetPersistantLocalInt(oShadow, "PathBonus"+IntToString(i)) > 0) // Count filled ones nCount++; } return nCount; } void ReapplyPathBonusFeat(object oShadow) { int nFeat, i; object oSkin = GetPCSkin(oShadow); for(i = 1; i < 7 ; i++) { nFeat = GetPersistantLocalInt(oShadow, "PathBonus"+IntToString(i)); if (nFeat > 0 && !GetHasFeat(nFeat, oShadow))// Only apply if it's a valid feat they don't have { int nIPFeat = PathFeatToIPFeat(nFeat); itemproperty ipFeat = PRCItemPropertyBonusFeat(nIPFeat); IPSafeAddItemProperty(oSkin, ipFeat, 0.0f, X2_IP_ADDPROP_POLICY_KEEP_EXISTING, FALSE, FALSE); } } } int PathFeatToIPFeat(int nFeat) { int nIPFeat = -1; switch(nFeat) { case FEAT_PATH_FOCUS_CLOAK_SHADOWS : nIPFeat = IP_CONST_FEAT_PATH_FOCUS_CLOAK_SHADOWS ; break; case FEAT_PATH_FOCUS_DARK_TERRAIN : nIPFeat = IP_CONST_FEAT_PATH_FOCUS_DARK_TERRAIN ; break; case FEAT_PATH_FOCUS_EBON_WHISPERS : nIPFeat = IP_CONST_FEAT_PATH_FOCUS_EBON_WHISPERS ; break; case FEAT_PATH_FOCUS_EYES_DARKNESS : nIPFeat = IP_CONST_FEAT_PATH_FOCUS_EYES_DARKNESS ; break; case FEAT_PATH_FOCUS_SHUTTERS_CLOUDS : nIPFeat = IP_CONST_FEAT_PATH_FOCUS_SHUTTERS_CLOUDS ; break; case FEAT_PATH_FOCUS_TOUCH_TWILIGHT : nIPFeat = IP_CONST_FEAT_PATH_FOCUS_TOUCH_TWILIGHT ; break; case FEAT_PATH_FOCUS_UMBRAL_MIND : nIPFeat = IP_CONST_FEAT_PATH_FOCUS_UMBRAL_MIND ; break; case FEAT_PATH_FOCUS_BLACK_MAGIC : nIPFeat = IP_CONST_FEAT_PATH_FOCUS_BLACK_MAGIC ; break; case FEAT_PATH_FOCUS_BODY_SOUL : nIPFeat = IP_CONST_FEAT_PATH_FOCUS_BODY_SOUL ; break; case FEAT_PATH_FOCUS_DARK_REFLECTIONS : nIPFeat = IP_CONST_FEAT_PATH_FOCUS_DARK_REFLECTIONS ; break; case FEAT_PATH_FOCUS_EBON_ROADS : nIPFeat = IP_CONST_FEAT_PATH_FOCUS_EBON_ROADS ; break; case FEAT_PATH_FOCUS_ELEMENTAL_SHADOWS : nIPFeat = IP_CONST_FEAT_PATH_FOCUS_ELEMENTAL_SHADOWS ; break; case FEAT_PATH_FOCUS_UNBINDING_SHADE : nIPFeat = IP_CONST_FEAT_PATH_FOCUS_UNBINDING_SHADE ; break; case FEAT_PATH_FOCUS_VEIL_SHADOWS : nIPFeat = IP_CONST_FEAT_PATH_FOCUS_VEIL_SHADOWS ; break; case FEAT_PATH_FOCUS_BREATH_TWILIGHT : nIPFeat = IP_CONST_FEAT_PATH_FOCUS_BREATH_TWILIGHT ; break; case FEAT_PATH_FOCUS_DARK_METAMORPHOSIS : nIPFeat = IP_CONST_FEAT_PATH_FOCUS_DARK_METAMORPHOSIS ; break; case FEAT_PATH_FOCUS_EBON_WALLS : nIPFeat = IP_CONST_FEAT_PATH_FOCUS_EBON_WALLS ; break; case FEAT_PATH_FOCUS_EYES_NIGHT_SKY : nIPFeat = IP_CONST_FEAT_PATH_FOCUS_EYES_NIGHT_SKY ; break; case FEAT_PATH_FOCUS_HEART_SOUL : nIPFeat = IP_CONST_FEAT_PATH_FOCUS_HEART_SOUL ; break; case FEAT_PATH_FOCUS_SHADOW_CALLING : nIPFeat = IP_CONST_FEAT_PATH_FOCUS_SHADOW_CALLING ; break; case FEAT_GREATER_PATH_FOCUS_CLOAK_SHADOWS : nIPFeat = IP_CONST_FEAT_GREATER_PATH_FOCUS_CLOAK_SHADOWS ; break; case FEAT_GREATER_PATH_FOCUS_DARK_TERRAIN : nIPFeat = IP_CONST_FEAT_GREATER_PATH_FOCUS_DARK_TERRAIN ; break; case FEAT_GREATER_PATH_FOCUS_EBON_WHISPERS : nIPFeat = IP_CONST_FEAT_GREATER_PATH_FOCUS_EBON_WHISPERS ; break; case FEAT_GREATER_PATH_FOCUS_EYES_DARKNESS : nIPFeat = IP_CONST_FEAT_GREATER_PATH_FOCUS_EYES_DARKNESS ; break; case FEAT_GREATER_PATH_FOCUS_SHUTTERS_CLOUDS : nIPFeat = IP_CONST_FEAT_GREATER_PATH_FOCUS_SHUTTERS_CLOUDS ; break; case FEAT_GREATER_PATH_FOCUS_TOUCH_TWILIGHT : nIPFeat = IP_CONST_FEAT_GREATER_PATH_FOCUS_TOUCH_TWILIGHT ; break; case FEAT_GREATER_PATH_FOCUS_UMBRAL_MIND : nIPFeat = IP_CONST_FEAT_GREATER_PATH_FOCUS_UMBRAL_MIND ; break; case FEAT_GREATER_PATH_FOCUS_BLACK_MAGIC : nIPFeat = IP_CONST_FEAT_GREATER_PATH_FOCUS_BLACK_MAGIC ; break; case FEAT_GREATER_PATH_FOCUS_BODY_SOUL : nIPFeat = IP_CONST_FEAT_GREATER_PATH_FOCUS_BODY_SOUL ; break; case FEAT_GREATER_PATH_FOCUS_DARK_REFLECTIONS : nIPFeat = IP_CONST_FEAT_GREATER_PATH_FOCUS_DARK_REFLECTIONS ; break; case FEAT_GREATER_PATH_FOCUS_EBON_ROADS : nIPFeat = IP_CONST_FEAT_GREATER_PATH_FOCUS_EBON_ROADS ; break; case FEAT_GREATER_PATH_FOCUS_ELEMENTAL_SHADOWS : nIPFeat = IP_CONST_FEAT_GREATER_PATH_FOCUS_ELEMENTAL_SHADOWS ; break; case FEAT_GREATER_PATH_FOCUS_UNBINDING_SHADE : nIPFeat = IP_CONST_FEAT_GREATER_PATH_FOCUS_UNBINDING_SHADE ; break; case FEAT_GREATER_PATH_FOCUS_VEIL_SHADOWS : nIPFeat = IP_CONST_FEAT_GREATER_PATH_FOCUS_VEIL_SHADOWS ; break; case FEAT_GREATER_PATH_FOCUS_BREATH_TWILIGHT : nIPFeat = IP_CONST_FEAT_GREATER_PATH_FOCUS_BREATH_TWILIGHT ; break; case FEAT_GREATER_PATH_FOCUS_DARK_METAMORPHOSIS: nIPFeat = IP_CONST_FEAT_GREATER_PATH_FOCUS_DARK_METAMORPHOSIS; break; case FEAT_GREATER_PATH_FOCUS_EBON_WALLS : nIPFeat = IP_CONST_FEAT_GREATER_PATH_FOCUS_EBON_WALLS ; break; case FEAT_GREATER_PATH_FOCUS_EYES_NIGHT_SKY : nIPFeat = IP_CONST_FEAT_GREATER_PATH_FOCUS_EYES_NIGHT_SKY ; break; case FEAT_GREATER_PATH_FOCUS_HEART_SOUL : nIPFeat = IP_CONST_FEAT_GREATER_PATH_FOCUS_HEART_SOUL ; break; case FEAT_GREATER_PATH_FOCUS_SHADOW_CALLING : nIPFeat = IP_CONST_FEAT_GREATER_PATH_FOCUS_SHADOW_CALLING ; break; case FEAT_NOCTURNAL_CASTER_CLOAK_SHADOWS : nIPFeat = IP_CONST_FEAT_NOCTURNAL_CASTER_CLOAK_SHADOWS ; break; case FEAT_NOCTURNAL_CASTER_DARK_TERRAIN : nIPFeat = IP_CONST_FEAT_NOCTURNAL_CASTER_DARK_TERRAIN ; break; case FEAT_NOCTURNAL_CASTER_EBON_WHISPERS : nIPFeat = IP_CONST_FEAT_NOCTURNAL_CASTER_EBON_WHISPERS ; break; case FEAT_NOCTURNAL_CASTER_EYES_DARKNESS : nIPFeat = IP_CONST_FEAT_NOCTURNAL_CASTER_EYES_DARKNESS ; break; case FEAT_NOCTURNAL_CASTER_SHUTTERS_CLOUDS : nIPFeat = IP_CONST_FEAT_NOCTURNAL_CASTER_SHUTTERS_CLOUDS ; break; case FEAT_NOCTURNAL_CASTER_TOUCH_TWILIGHT : nIPFeat = IP_CONST_FEAT_NOCTURNAL_CASTER_TOUCH_TWILIGHT ; break; case FEAT_NOCTURNAL_CASTER_UMBRAL_MIND : nIPFeat = IP_CONST_FEAT_NOCTURNAL_CASTER_UMBRAL_MIND ; break; case FEAT_NOCTURNAL_CASTER_BLACK_MAGIC : nIPFeat = IP_CONST_FEAT_NOCTURNAL_CASTER_BLACK_MAGIC ; break; case FEAT_NOCTURNAL_CASTER_BODY_SOUL : nIPFeat = IP_CONST_FEAT_NOCTURNAL_CASTER_BODY_SOUL ; break; case FEAT_NOCTURNAL_CASTER_DARK_REFLECTIONS : nIPFeat = IP_CONST_FEAT_NOCTURNAL_CASTER_DARK_REFLECTIONS ; break; case FEAT_NOCTURNAL_CASTER_EBON_ROADS : nIPFeat = IP_CONST_FEAT_NOCTURNAL_CASTER_EBON_ROADS ; break; case FEAT_NOCTURNAL_CASTER_ELEMENTAL_SHADOWS : nIPFeat = IP_CONST_FEAT_NOCTURNAL_CASTER_ELEMENTAL_SHADOWS ; break; case FEAT_NOCTURNAL_CASTER_UNBINDING_SHADE : nIPFeat = IP_CONST_FEAT_NOCTURNAL_CASTER_UNBINDING_SHADE ; break; case FEAT_NOCTURNAL_CASTER_VEIL_SHADOWS : nIPFeat = IP_CONST_FEAT_NOCTURNAL_CASTER_VEIL_SHADOWS ; break; case FEAT_NOCTURNAL_CASTER_BREATH_TWILIGHT : nIPFeat = IP_CONST_FEAT_NOCTURNAL_CASTER_BREATH_TWILIGHT ; break; case FEAT_NOCTURNAL_CASTER_DARK_METAMORPHOSIS : nIPFeat = IP_CONST_FEAT_NOCTURNAL_CASTER_DARK_METAMORPHOSIS ; break; case FEAT_NOCTURNAL_CASTER_EBON_WALLS : nIPFeat = IP_CONST_FEAT_NOCTURNAL_CASTER_EBON_WALLS ; break; case FEAT_NOCTURNAL_CASTER_EYES_NIGHT_SKY : nIPFeat = IP_CONST_FEAT_NOCTURNAL_CASTER_EYES_NIGHT_SKY ; break; case FEAT_NOCTURNAL_CASTER_HEART_SOUL : nIPFeat = IP_CONST_FEAT_NOCTURNAL_CASTER_HEART_SOUL ; break; case FEAT_NOCTURNAL_CASTER_SHADOW_CALLING : nIPFeat = IP_CONST_FEAT_NOCTURNAL_CASTER_SHADOW_CALLING ; break; case FEAT_FAV_MYST_BENDPERSPECTIVE : nIPFeat = IP_CONST_FEAT_FAV_MYST_BENDPERSPECTIVE ; break; case FEAT_FAV_MYST_CARPETSHADOW : nIPFeat = IP_CONST_FEAT_FAV_MYST_CARPETSHADOW ; break; case FEAT_FAV_MYST_DUSKANDDAWN : nIPFeat = IP_CONST_FEAT_FAV_MYST_DUSKANDDAWN ; break; case FEAT_FAV_MYST_LIFEFADES : nIPFeat = IP_CONST_FEAT_FAV_MYST_LIFEFADES ; break; case FEAT_FAV_MYST_MESMERIZINGSHADE : nIPFeat = IP_CONST_FEAT_FAV_MYST_MESMERIZINGSHADE ; break; case FEAT_FAV_MYST_STEELSHADOWS : nIPFeat = IP_CONST_FEAT_FAV_MYST_STEELSHADOWS ; break; case FEAT_FAV_MYST_VOICEOFSHADOW : nIPFeat = IP_CONST_FEAT_FAV_MYST_VOICEOFSHADOW ; break; case FEAT_FAV_MYST_BLACKFIRE : nIPFeat = IP_CONST_FEAT_FAV_MYST_BLACKFIRE ; break; case FEAT_FAV_MYST_CONGRESSSHADOWS : nIPFeat = IP_CONST_FEAT_FAV_MYST_CONGRESSSHADOWS ; break; case FEAT_FAV_MYST_FLESHFAILS : nIPFeat = IP_CONST_FEAT_FAV_MYST_FLESHFAILS ; break; case FEAT_FAV_MYST_PIERCINGSIGHT : nIPFeat = IP_CONST_FEAT_FAV_MYST_PIERCINGSIGHT ; break; case FEAT_FAV_MYST_SHADOWSKIN : nIPFeat = IP_CONST_FEAT_FAV_MYST_SHADOWSKIN ; break; case FEAT_FAV_MYST_SIGHTECLIPSED : nIPFeat = IP_CONST_FEAT_FAV_MYST_SIGHTECLIPSED ; break; case FEAT_FAV_MYST_THOUGHTSSHADOW : nIPFeat = IP_CONST_FEAT_FAV_MYST_THOUGHTSSHADOW ; break; case FEAT_FAV_MYST_AFRAIDOFTHEDARK : nIPFeat = IP_CONST_FEAT_FAV_MYST_AFRAIDOFTHEDARK ; break; case FEAT_FAV_MYST_CLINGINGDARKNESS : nIPFeat = IP_CONST_FEAT_FAV_MYST_CLINGINGDARKNESS ; break; case FEAT_FAV_MYST_DANCINGSHADOWS : nIPFeat = IP_CONST_FEAT_FAV_MYST_DANCINGSHADOWS ; break; case FEAT_FAV_MYST_FLICKER : nIPFeat = IP_CONST_FEAT_FAV_MYST_FLICKER ; break; case FEAT_FAV_MYST_KILLINGSHADOWS : nIPFeat = IP_CONST_FEAT_FAV_MYST_KILLINGSHADOWS ; break; case FEAT_FAV_MYST_SHARPSHADOWS : nIPFeat = IP_CONST_FEAT_FAV_MYST_SHARPSHADOWS ; break; case FEAT_FAV_MYST_UMBRALTOUCH : nIPFeat = IP_CONST_FEAT_FAV_MYST_UMBRALTOUCH ; break; case FEAT_FAV_MYST_AURAOFSHADE : nIPFeat = IP_CONST_FEAT_FAV_MYST_AURAOFSHADE ; break; case FEAT_FAV_MYST_BOLSTER : nIPFeat = IP_CONST_FEAT_FAV_MYST_BOLSTER ; break; case FEAT_FAV_MYST_SHADOWEVOCATION : nIPFeat = IP_CONST_FEAT_FAV_MYST_SHADOWEVOCATION ; break; case FEAT_FAV_MYST_SHADOWVISION : nIPFeat = IP_CONST_FEAT_FAV_MYST_SHADOWVISION ; break; case FEAT_FAV_MYST_SHADOWSFADE : nIPFeat = IP_CONST_FEAT_FAV_MYST_SHADOWSFADE ; break; case FEAT_FAV_MYST_STEPINTOSHADOW : nIPFeat = IP_CONST_FEAT_FAV_MYST_STEPINTOSHADOW ; break; case FEAT_FAV_MYST_WARPSPELL : nIPFeat = IP_CONST_FEAT_FAV_MYST_WARPSPELL ; break; case FEAT_FAV_MYST_CURTAINSHADOWS : nIPFeat = IP_CONST_FEAT_FAV_MYST_CURTAINSHADOWS ; break; case FEAT_FAV_MYST_DARKAIR : nIPFeat = IP_CONST_FEAT_FAV_MYST_DARKAIR ; break; case FEAT_FAV_MYST_ECHOSPELL : nIPFeat = IP_CONST_FEAT_FAV_MYST_ECHOSPELL ; break; case FEAT_FAV_MYST_FEIGNLIFE : nIPFeat = IP_CONST_FEAT_FAV_MYST_FEIGNLIFE ; break; case FEAT_FAV_MYST_LANGUOR : nIPFeat = IP_CONST_FEAT_FAV_MYST_LANGUOR ; break; case FEAT_FAV_MYST_PASSINTOSHADOW : nIPFeat = IP_CONST_FEAT_FAV_MYST_PASSINTOSHADOW ; break; case FEAT_FAV_MYST_UNRAVELDWEOMER : nIPFeat = IP_CONST_FEAT_FAV_MYST_UNRAVELDWEOMER ; break; case FEAT_FAV_MYST_FLOODSHADOWS : nIPFeat = IP_CONST_FEAT_FAV_MYST_FLOODSHADOWS ; break; case FEAT_FAV_MYST_GREATERSHADOWEVOCATION : nIPFeat = IP_CONST_FEAT_FAV_MYST_GREATERSHADOWEVOCATION ; break; case FEAT_FAV_MYST_SHADOWINVESTITURE : nIPFeat = IP_CONST_FEAT_FAV_MYST_SHADOWINVESTITURE ; break; case FEAT_FAV_MYST_SHADOWSTORM : nIPFeat = IP_CONST_FEAT_FAV_MYST_SHADOWSTORM ; break; case FEAT_FAV_MYST_SHADOWSFADE_GREATER : nIPFeat = IP_CONST_FEAT_FAV_MYST_SHADOWSFADE_GREATER ; break; case FEAT_FAV_MYST_UNVEIL : nIPFeat = IP_CONST_FEAT_FAV_MYST_UNVEIL ; break; case FEAT_FAV_MYST_VOYAGESHADOW : nIPFeat = IP_CONST_FEAT_FAV_MYST_VOYAGESHADOW ; break; case FEAT_FAV_MYST_DARKSOUL : nIPFeat = IP_CONST_FEAT_FAV_MYST_DARKSOUL ; break; case FEAT_FAV_MYST_EPHEMERALIMAGE : nIPFeat = IP_CONST_FEAT_FAV_MYST_EPHEMERALIMAGE ; break; case FEAT_FAV_MYST_LIFEFADESGREATER : nIPFeat = IP_CONST_FEAT_FAV_MYST_LIFEFADESGREATER ; break; case FEAT_FAV_MYST_PRISONNIGHT : nIPFeat = IP_CONST_FEAT_FAV_MYST_PRISONNIGHT ; break; case FEAT_FAV_MYST_UMBRALSERVANT : nIPFeat = IP_CONST_FEAT_FAV_MYST_UMBRALSERVANT ; break; case FEAT_FAV_MYST_TRUTHREVEALED : nIPFeat = IP_CONST_FEAT_FAV_MYST_TRUTHREVEALED ; break; case FEAT_FAV_MYST_FARSIGHT : nIPFeat = IP_CONST_FEAT_FAV_MYST_FARSIGHT ; break; case FEAT_FAV_MYST_GRFLESHFAILS : nIPFeat = IP_CONST_FEAT_FAV_MYST_GRFLESHFAILS ; break; case FEAT_FAV_MYST_SHADOWPLAGUE : nIPFeat = IP_CONST_FEAT_FAV_MYST_SHADOWPLAGUE ; break; case FEAT_FAV_MYST_SOULPUPPET : nIPFeat = IP_CONST_FEAT_FAV_MYST_SOULPUPPET ; break; case FEAT_FAV_MYST_TOMBNIGHT : nIPFeat = IP_CONST_FEAT_FAV_MYST_TOMBNIGHT ; break; case FEAT_FAV_MYST_UMBRALBODY : nIPFeat = IP_CONST_FEAT_FAV_MYST_UMBRALBODY ; break; case FEAT_FAV_MYST_ARMYSHADOW : nIPFeat = IP_CONST_FEAT_FAV_MYST_ARMYSHADOW ; break; case FEAT_FAV_MYST_CONSUMEESSENCE : nIPFeat = IP_CONST_FEAT_FAV_MYST_CONSUMEESSENCE ; break; case FEAT_FAV_MYST_EPHEMERALSTORM : nIPFeat = IP_CONST_FEAT_FAV_MYST_EPHEMERALSTORM ; break; case FEAT_FAV_MYST_REFLECTIONS : nIPFeat = IP_CONST_FEAT_FAV_MYST_REFLECTIONS ; break; case FEAT_FAV_MYST_SHADOWSURGE : nIPFeat = IP_CONST_FEAT_FAV_MYST_SHADOWSURGE ; break; case FEAT_FAV_MYST_SHADOWTIME : nIPFeat = IP_CONST_FEAT_FAV_MYST_SHADOWTIME ; break; case FEAT_SHADOW_CAST : nIPFeat = IP_CONST_FEAT_SHADOW_CAST ; break; case FEAT_EMPOWER_MYSTERY : nIPFeat = IP_CONST_FEAT_EMPOWER_MYSTERY ; break; case FEAT_EXTEND_MYSTERY : nIPFeat = IP_CONST_FEAT_EXTEND_MYSTERY ; break; case FEAT_MAXIMIZE_MYSTERY : nIPFeat = IP_CONST_FEAT_MAXIMIZE_MYSTERY ; break; case FEAT_QUICKEN_MYSTERY : nIPFeat = IP_CONST_FEAT_QUICKEN_MYSTERY ; break; case FEAT_STILL_MYSTERY : nIPFeat = IP_CONST_FEAT_STILL_MYSTERY ; break; } return nIPFeat; }