/************************************************************************ * script name : pat_inc * created by : eyesolated * date : 2018/7/31 * * description : Include script for PAT * * changes : 2018/7/31 - eyesolated - Initial creation ************************************************************************/ #include "pat_cfg" #include "sql_inc" #include "nwnx_creature" #include "x0_i0_stringlib" #include "lgs_inc" #include "nwnx_deprecated" // Creates the database tables used by PAT void pat_CreateTables(); // Returns FALSE if any of the required PAT Tables is missing int pat_GetTablesExist(); // Drops all PAT Tables void pat_DropTables(); // Clears the CR Cache of all areas void pat_ClearAreaCache(); // Inserts new Areas into PAT_TABLE_AREAS and removes areas that are no longer // existing void pat_UpdateAreasTable(); // Create log entry void pat_Log(string sMessage); // Returns the ID of Role sRoleName. Returns 0 on error int pat_GetRole_ID(string sRole_Name); // Returns the Name of Role nRoleID. Returns an empty string on error string pat_GetRole_Name(int nRole_ID); // Sets the DM Setting variable on the DM void pat_SetDMSetting(object oDM, int nSetting); // Gets the DM Setting variable on the DM int pat_GetDMSetting(object oDM); // Adds an area into the PAT Database void pat_AddArea(string sResRef, string sTag, string sName, int nCR); // Adds a Role Entry to PAT void pat_AddRoleEntry(int nRole_ID, string sRole_Name, int nCR, int nAbility_STR, int nAbility_DEX, int nAbility_CON, int nAbility_WIS, int nAbility_INT, int nAbility_CHA, int nSavingThrow_Fortitude, int nSavingThrow_Reflex, int nSavingThrow_Will, int nAC, int nBaseAttackBonus, int nBaseHitPoints); // Retrieves a Role Entry from the database struct PAT_STRUCT_ROLE pat_GetRoleEntryByID(int nRole_ID, int nCR); // Retrieves a Role Entry from the database struct PAT_STRUCT_ROLE pat_GetRoleEntryByName(string sRole_Name, int nCR); // Adds a Class Setup // nRole_ID: The Role ID that will have access to this Class Setup // nClass_1: The first class to use in this setup (CLASS_TYPE_*) // nClass_2: The second class to use in this setup (CLASS_TYPE_*) // nClass_3: The third class to use in this setup (CLASS_TYPE_*) // nSpell_Source: Bitmask of PAT_SPELL_SOURCE_* the setup should use // nSpell_Category: Bitmask of PAT_SPELL_CATEGORY_* the setup should use // nFeat_PAck: The Feat Pack to use in this setup // nSkillset: The Skillset to use in this setup void pat_AddClassSetup(int nRole_ID, int nClass_1, int nClass_2, int nClass_3, int nSpell_Source, int nSpell_Category, int nFeat_Pack, int nSkillset); // Retrieves a Class Setup // nRole_ID: The Role ID to use in searching a Class Setup // nClass_1: The Class that the Setup should use in first position. -1 for "any class" // nClass_2: The Class that the Setup should use in second position. -1 for "any class" // nClass_3: The Class that the Setup should use in third position. -1 for "any class" // // Returns an empty struct if no matching Setup is found struct PAT_STRUCT_CLASS_SETUP pat_GetClassSetup(int nRole_ID, int nClass_1 = -1, int nClass_2 = -1, int nClass_3 = -1); // Adds a spell to the list of available spells // nSpell_ID: SPELL_* // nSpell_Level: The level of the spell // nSource: PAT_SPELL_SOURCE_* // nCategory: PAT_SPELL_CATEGORY_* // nTarget: PAT_SPELL_TARGET_* // nCR_Minimum: The minimum CR needed to have access to this spell void pat_AddSpell(int nSpell_ID, int nSpell_Level, int nSource, int nCategory, int nTarget, int nCR_Minimum); // Adds a feat to the list of available Feats // nFeat_ID: FEAT_* // sPAcks: The Packs that use this Feat (eg "0;" or "2;4;") // nChance_Base: The base chance of a mob taking this feat // nChance_Modifier: Added chance / CR of a mob taking this feat // nCR_Minimum: The minimum CR required to use this feat // nCR_Maximum: The maximum CR allowed to use this feat void pat_AddFeat(int nFeat_ID, string sPacks, int nChance_Base, int nChance_Modifier, int nCR_Minimum, int nCR_Maximum = PAT_CR_MAXIMUM); // Add a skill set void pat_AddSkillset(int nID, string sSkills_Extreme, string sSkills_High, string sSkills_Medium, string sSkills_Low); // Returns the ability score for nCR at nGain_Rate. See PAT_GAIN_* for available values int pat_CalculateAbilityScore(int nCR, int nGain_Rate); // Returns the Saving Throw score for nCR at nGain_Rate. See PAT_GAIN_* for available values int pat_CalculateSavingThrowValue(int nCR, int nGain_Rate); // Returns the Armor Class value for nCR at nGain_Rate. See PAT_GAIN_* for available values int pat_CalculateACValue(int nCR, int nGain_Rate); // Returns the base attack bonus for nCR at nGain_Rate. See PAT_GAIN_* for available values int pat_CalculateBaseAttackBonus(int nCR, int nGain_Rate); // Returns the Base hit points for nCR at nGain_Rate. See PAT_GAIN_* for available values int pat_CalculateBaseHitPoints(int nCR, int nGain_Rate); // Applies PAT to oCreature // If you set nCR to anything but 0, PAT will skip CR Detection and use the provided value // If you set nRole_ID to anything but PAT_ROLE_UNDEFINED, PAT will skip Role detection and use the provided value int pat_Apply(object oCreature, int nCR = 0, int nRole_ID = PAT_ROLE_UNDEFINED); //////////////////////////////////////////////////////////////////////////////// // Function implementation ////////////////////////// // internal functions object pat_GetCache() { object oCache = GetLocalObject(GetModule(), PAT_CACHE); if (!GetIsObjectValid(oCache)) { oCache = GetObjectByTag(PAT_CACHE); SetLocalObject(GetModule(), PAT_CACHE, oCache); } return oCache; } // This method selects a random specific Role if a generic Role is given int pat_Verify_Role_ID(int nRole_ID) { int nSpecific; switch (nRole_ID) { case PAT_ROLE_DPS: nSpecific = Random(7); switch (nSpecific) { case 0: nRole_ID = PAT_ROLE_DPS_MELEE_STR; break; case 1: nRole_ID = PAT_ROLE_DPS_MELEE_DEX; break; case 2: nRole_ID = PAT_ROLE_DPS_RANGE_DEX; break; case 3: nRole_ID = PAT_ROLE_DPS_RANGE_WIS; break; case 4: nRole_ID = PAT_ROLE_DPS_CASTER_INT; break; case 5: nRole_ID = PAT_ROLE_DPS_CASTER_WIS; break; case 6: nRole_ID = PAT_ROLE_DPS_CASTER_CHA; break; } break; case PAT_ROLE_DPS_MELEE: nSpecific = Random(2); switch (nSpecific) { case 0: nRole_ID = PAT_ROLE_DPS_MELEE_STR; break; case 1: nRole_ID = PAT_ROLE_DPS_MELEE_DEX; break; } break; case PAT_ROLE_DPS_RANGE: nSpecific = Random(2); switch (nSpecific) { case 0: nRole_ID = PAT_ROLE_DPS_RANGE_DEX; break; case 1: nRole_ID = PAT_ROLE_DPS_RANGE_WIS; break; } break; case PAT_ROLE_DPS_CASTER: nSpecific = Random(3); switch (nSpecific) { case 0: nRole_ID = PAT_ROLE_DPS_CASTER_INT; break; case 1: nRole_ID = PAT_ROLE_DPS_CASTER_WIS; break; case 2: nRole_ID = PAT_ROLE_DPS_CASTER_CHA; break; } break; case PAT_ROLE_HEAL: nRole_ID = PAT_ROLE_HEAL_WIS; break; case PAT_ROLE_SUPPORT: nSpecific = Random(3); switch (nSpecific) { case 0: nRole_ID = PAT_ROLE_SUPPORT_INT; break; case 1: nRole_ID = PAT_ROLE_SUPPORT_WIS; break; case 2: nRole_ID = PAT_ROLE_SUPPORT_CHA; break; } break; case PAT_ROLE_TANK: nRole_ID = PAT_ROLE_TANK_STR; break; case PAT_ROLE_NAKED: nSpecific = Random(3); switch (nSpecific) { case 0: nRole_ID = PAT_ROLE_NAKED_DPS_STR; break; case 1: nRole_ID = PAT_ROLE_NAKED_DPS_DEX; break; case 2: nRole_ID = PAT_ROLE_NAKED_TANK_STR; break; } break; case PAT_ROLE_NAKED_DPS: nSpecific = Random(2); switch (nSpecific) { case 0: nRole_ID = PAT_ROLE_NAKED_DPS_STR; break; case 1: nRole_ID = PAT_ROLE_NAKED_DPS_DEX; break; } break; case PAT_ROLE_NAKED_TANK: nRole_ID = PAT_ROLE_NAKED_TANK_STR; break; } return nRole_ID; } string pat_Verify_Role_Name(string sName) { if (sName == PAT_ROLE_NAME_DPS) return (pat_GetRole_Name(pat_Verify_Role_ID(PAT_ROLE_DPS))); else if (sName == PAT_ROLE_NAME_DPS_MELEE) return (pat_GetRole_Name(pat_Verify_Role_ID(PAT_ROLE_DPS_MELEE))); else if (sName == PAT_ROLE_NAME_DPS_CASTER) return (pat_GetRole_Name(pat_Verify_Role_ID(PAT_ROLE_DPS_CASTER))); else if (sName == PAT_ROLE_NAME_HEAL) return (pat_GetRole_Name(pat_Verify_Role_ID(PAT_ROLE_HEAL))); else if (sName == PAT_ROLE_NAME_SUPPORT) return (pat_GetRole_Name(pat_Verify_Role_ID(PAT_ROLE_SUPPORT))); else if (sName == PAT_ROLE_NAME_TANK) return (pat_GetRole_Name(pat_Verify_Role_ID(PAT_ROLE_TANK))); else if (sName == PAT_ROLE_NAME_NAKED) return (pat_GetRole_Name(pat_Verify_Role_ID(PAT_ROLE_NAKED))); else if (sName == PAT_ROLE_NAME_NAKED_DPS) return (pat_GetRole_Name(pat_Verify_Role_ID(PAT_ROLE_NAKED_DPS))); else if (sName == PAT_ROLE_NAME_NAKED_TANK) return (pat_GetRole_Name(pat_Verify_Role_ID(PAT_ROLE_NAKED_TANK))); else return sName; } void pat_ClearAreaCache() { object oArea = GetFirstArea(); while (GetIsObjectValid(oArea)) { DeleteLocalInt(oArea, PAT_VAR_CR_DB); oArea = GetNextArea(); } } void pat_UpdateAreasTable() { string sResRef; string sTag; string sName; string sResRefs = ""; string sQuery; object oArea = GetFirstArea(); int n = 0; while (GetIsObjectValid(oArea)) { n++; sResRef = GetResRef(oArea); sTag = GetTag(oArea); sName = GetName(oArea); sResRefs += "'" + sResRef + "', "; NWNX_SQL_PrepareQuery("INSERT INTO " + PAT_TABLE_AREAS + " (ResRef, Tag, Name) VALUES (?,?,?)"); NWNX_SQL_PreparedString(0, sResRef); NWNX_SQL_PreparedString(1, sTag); NWNX_SQL_PreparedString(2, sName); if (!NWNX_SQL_ExecutePreparedQuery()) { // INSERT failed, run an UPDATE for the ResRef NWNX_SQL_PrepareQuery("UPDATE " + PAT_TABLE_AREAS + " SET Tag = ?, Name = ? WHERE ResRef = ?"); NWNX_SQL_PreparedString(0, sTag); NWNX_SQL_PreparedString(1, sName); NWNX_SQL_PreparedString(2, sResRef); NWNX_SQL_ExecutePreparedQuery(); } oArea = GetNextArea(); } sResRefs = GetStringLeft(sResRefs, GetStringLength(sResRefs) - 2); sQuery = "DELETE FROM " + PAT_TABLE_AREAS + " WHERE ResRef NOT IN (" + sResRefs + ")"; NWNX_SQL_ExecuteQuery(sQuery); } void pat_Log(string sMessage) { WriteTimestampedLogEntry("PAT: " + sMessage); } int pat_GetCreatureCR(object oCreature) { // Retrieve a possibly set CR from the creature int nCR = GetLocalInt(oCreature, PAT_VAR_CR); // If no CR was set on the creature, try to find the db value for the current // area object oArea = GetArea(oCreature); if (nCR == 0) { // Check if the DM use an override int nDMSetting = GetLocalInt(oArea, PAT_VAR_DM_SETTING); DeleteLocalInt(oArea, PAT_VAR_DM_SETTING); switch (nDMSetting) { case -1: // don't use PAT at all return 0; break; case 0: // no DM Setting, use standard method break; default: // return the DMs choice of CR return nDMSetting; break; } nCR = GetLocalInt(oArea, PAT_VAR_CR_DB); // If there is no CR here, try to get it from the database if (nCR == 0) { string sQuery = "SELECT CR FROM " + PAT_TABLE_AREAS + " WHERE ResRef = '" + GetResRef(oArea) + "'"; NWNX_SQL_ExecuteQuery(sQuery); if (NWNX_SQL_ReadyToReadNextRow()) { NWNX_SQL_ReadNextRow(); nCR = StringToInt(NWNX_SQL_ReadDataInActiveRow(0)); SetLocalInt(oArea, PAT_VAR_CR_DB, nCR); } else SetLocalInt(oArea, PAT_VAR_CR_DB, -1); } if (nCR == -1) // No entry in DB or DB set to -1 nCR = 0; } // If no CR was not set on the creature / in the DB, retrieve the one on the area if (nCR == 0) nCR = GetLocalInt(oArea, PAT_VAR_CR); // If CR is still 0, return here if (nCR == 0) return nCR; int nModifier = GetLocalInt(oCreature, PAT_VAR_CR_MODIFIER); if (nModifier != 0) nCR += nModifier; if (PAT_AUTO_CR_ENABLE) { string sName; // If the creature uses our suffixes in it's name, rename them sName = GetName(oCreature); if (GetStringRight(sName, 6) == " Elite") SetName(oCreature, GetStringLeft(sName, GetStringLength(sName) - 6)); else if (GetStringRight(sName, 9) == " Champion") SetName(oCreature, GetStringLeft(sName, GetStringLength(sName) - 9)); int nElite = d100(); if (nElite <= PAT_AUTO_CR_CHANCE_CHAMPION) { sName = GetLocalString(oCreature, PAT_VAR_NAME_CHAMPION); if (sName == "") sName = GetName(oCreature) + " Champion"; SetName(oCreature, sName); nCR += PAT_AUTO_CR_MODIFIER_CHAMPION; // Add +2 to minimum loot count for this creature SetLocalInt(oCreature, CS_LGS_LOOT_MINITEMS, GetLocalInt(oCreature, CS_LGS_LOOT_MINITEMS) + PAT_AUTO_CR_BONUSLOOT_CHAMPION); } else if (nElite <= PAT_AUTO_CR_CHANCE_CHAMPION + PAT_AUTO_CR_CHANCE_ELITE) { sName = GetLocalString(oCreature, PAT_VAR_NAME_ELITE); if (sName == "") sName = GetName(oCreature) + " Elite"; SetName(oCreature, sName); nCR += PAT_AUTO_CR_MODIFIER_ELITE; // Add +1 to minimum loot count for this creature SetLocalInt(oCreature, CS_LGS_LOOT_MINITEMS, GetLocalInt(oCreature, CS_LGS_LOOT_MINITEMS) + PAT_AUTO_CR_BONUSLOOT_ELITE); } } if (nCR > PAT_CR_MAXIMUM) nCR =PAT_CR_MAXIMUM; return nCR; } int pat_GetHighestCreatureClass(object oCreature) { int nHighestLevel = 0; int nHighestClass = CLASS_TYPE_FIGHTER; // Default int nClass; int nNew; int nCurrent; for (nCurrent = 1; nCurrent <=3; nCurrent++) { nClass = GetClassByPosition(nCurrent, oCreature); // Unsupported class? These classes are not evaluated in regards to // highest class because they are not a class in the sense of representing // an archetype. switch (nClass) { case CLASS_TYPE_INVALID: continue; case CLASS_TYPE_COMMONER: case CLASS_TYPE_CONSTRUCT: case CLASS_TYPE_EYE_OF_GRUUMSH: case CLASS_TYPE_FEY: case CLASS_TYPE_GIANT: case CLASS_TYPE_HUMANOID: case CLASS_TYPE_MONSTROUS: case CLASS_TYPE_OUTSIDER: case CLASS_TYPE_SHOU_DISCIPLE: case CLASS_TYPE_UNDEAD: continue; } nNew = GetLevelByPosition(nCurrent, oCreature); if (nNew > nHighestLevel) { nHighestClass = nClass; nHighestLevel = nNew; } } return nHighestClass; } int pat_GetRandomClass(struct PAT_STRUCT_CLASS_SETUP stClassSet) { int nCount = 0; if (stClassSet.Class_3 != -1) nCount = Random(3); else if (stClassSet.Class_2 != -1) nCount = Random(2); switch (nCount) { case 0: return stClassSet.Class_1; case 1: return stClassSet.Class_2; case 2: return stClassSet.Class_3; } return stClassSet.Class_1; } int pat_GetHasRangedWeaponEquipped(object oCreature) { object oWeapon = GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oCreature); int nWeaponType = GetBaseItemType(oWeapon); switch (nWeaponType) { case BASE_ITEM_DART: case BASE_ITEM_HEAVYCROSSBOW: case BASE_ITEM_LIGHTCROSSBOW: case BASE_ITEM_LONGBOW: case BASE_ITEM_SHORTBOW: case BASE_ITEM_SHURIKEN: case BASE_ITEM_SLING: case BASE_ITEM_THROWINGAXE: return TRUE; } return FALSE; } int pat_GetCreatureRole(object oCreature) { int nRole_ID = GetLocalInt(oCreature, PAT_VAR_ROLE_ID); if (nRole_ID == 0) { string sRole_Name = GetLocalString(oCreature, PAT_VAR_ROLE_NAME); // if also no string is set, we don't know the role of this creature and // have to "guess" if (sRole_Name == "") { // Check Classes int nHighestClass = pat_GetHighestCreatureClass(oCreature); int nRandom; switch (nHighestClass) { /*************************************************************** * PC Playable Classes **************************************************************/ case CLASS_TYPE_ABERRATION: case CLASS_TYPE_ANIMAL: case CLASS_TYPE_BEAST: case CLASS_TYPE_DRAGON: case CLASS_TYPE_ELEMENTAL: case CLASS_TYPE_MAGICAL_BEAST: case CLASS_TYPE_OOZE: case CLASS_TYPE_VERMIN: if (GetAbilityScore(oCreature, ABILITY_STRENGTH, TRUE) >= GetAbilityScore(oCreature, ABILITY_DEXTERITY, TRUE)) { nRandom = Random(2); switch (nRandom) { case 0: nRole_ID = PAT_ROLE_NAKED_DPS_STR; break; case 1: nRole_ID = PAT_ROLE_NAKED_TANK_STR; break; } } else { nRole_ID = PAT_ROLE_NAKED_DPS_DEX; } break; /*************************************************************** * PC Playable Classes **************************************************************/ case CLASS_TYPE_ARCANE_ARCHER: nRole_ID = PAT_ROLE_DPS_RANGE_DEX; break; case CLASS_TYPE_ASSASSIN: nRole_ID = PAT_ROLE_DPS_MELEE_DEX; break; case CLASS_TYPE_BARBARIAN: nRole_ID = PAT_ROLE_DPS_MELEE_STR; break; case CLASS_TYPE_BARD: nRole_ID = PAT_ROLE_SUPPORT_CHA; break; case CLASS_TYPE_BLACKGUARD: nRole_ID = PAT_ROLE_TANK_STR; break; case CLASS_TYPE_CLERIC: nRandom = Random(4); switch (nRandom) { case 0: case 1: nRole_ID = PAT_ROLE_HEAL_WIS; break; case 2: nRole_ID = PAT_ROLE_DPS_CASTER_WIS; break; case 3: nRole_ID = PAT_ROLE_SUPPORT_WIS; break; } break; case CLASS_TYPE_DRUID: nRandom = Random(3); switch (nRandom) { case 0: nRole_ID = PAT_ROLE_HEAL_WIS; break; case 1: nRole_ID = PAT_ROLE_DPS_CASTER_WIS; break; case 2: nRole_ID = PAT_ROLE_SUPPORT_WIS; break; } break; case CLASS_TYPE_MONK: nRole_ID = PAT_ROLE_DPS_MELEE_DEX; break; case CLASS_TYPE_RANGER: nRole_ID = PAT_ROLE_DPS_RANGE_WIS; break; case CLASS_TYPE_ROGUE: nRole_ID = PAT_ROLE_DPS_MELEE_DEX; break; case CLASS_TYPE_SHADOWDANCER: nRole_ID = PAT_ROLE_DPS_MELEE_DEX; break; case CLASS_TYPE_SORCERER: nRandom = Random(3); switch (nRandom) { case 0: nRole_ID = PAT_ROLE_SUPPORT_CHA; break; default: nRole_ID = PAT_ROLE_DPS_CASTER_CHA; break; } break; case CLASS_TYPE_WIZARD: nRandom = Random(3); switch (nRandom) { case 0: nRole_ID = PAT_ROLE_SUPPORT_INT; break; default: nRole_ID = PAT_ROLE_DPS_CASTER_INT; break; } break; default: // Do we have a ranged weapon equipped? In this case, the mob was probably designed to be a ranged mob if (pat_GetHasRangedWeaponEquipped(oCreature)) nRole_ID = PAT_ROLE_DPS_RANGE_DEX; // Use Ability Scores to determine the correct Role else if (GetAbilityScore(oCreature, ABILITY_STRENGTH, TRUE) >= GetAbilityScore(oCreature, ABILITY_DEXTERITY, TRUE)) { nRandom = Random(10); switch (nRandom) { case 0: nRole_ID = PAT_ROLE_TANK_STR; break; default: nRole_ID = PAT_ROLE_DPS_MELEE_STR; break; } } else { nRandom = Random(4); switch (nRandom) { case 0: nRole_ID = PAT_ROLE_DPS_RANGE_DEX; break; default: nRole_ID = PAT_ROLE_DPS_MELEE_DEX; break; } } break; } } else nRole_ID = pat_GetRole_ID(sRole_Name); } // Return the verified Role ID return pat_Verify_Role_ID(nRole_ID); } void pat_RemoveAllSpells(object oCreature) { int nth; int nSpellCount; int nClass; int nSpellClass; for (nClass = 1; nClass <= 3; nClass++) { nSpellClass = GetClassByPosition(nClass, oCreature); if (nSpellClass == CLASS_TYPE_INVALID) continue; for (nth = 0; nth <= 9 ; nth++) { // Clear Known spells nSpellCount = NWNX_Creature_GetKnownSpellCount(oCreature, nSpellClass, nth); while (nSpellCount) NWNX_Creature_RemoveKnownSpell(oCreature, nSpellClass, nth, NWNX_Creature_GetKnownSpell(oCreature, nSpellClass, nth, --nSpellCount)); // Clear Memorized spells nSpellCount = NWNX_Creature_GetMemorisedSpellCountByLevel(oCreature, nSpellClass, nth); while (nSpellCount) NWNX_Creature_ClearMemorisedSpell(oCreature, nSpellClass, nth, --nSpellCount); } } } void pat_RemoveAllFeats(object oCreature) { int nFeatCount = NWNX_Creature_GetFeatCount(oCreature); while (nFeatCount) NWNX_Creature_RemoveFeat(oCreature, NWNX_Creature_GetFeatByIndex(oCreature, --nFeatCount)); } void pat_ApplyAbilities(object oCreature, struct PAT_STRUCT_ROLE stRole) { NWNX_Creature_SetRawAbilityScore(oCreature, ABILITY_STRENGTH, stRole.Ability_STR); NWNX_Creature_SetRawAbilityScore(oCreature, ABILITY_DEXTERITY, stRole.Ability_DEX); NWNX_Creature_SetRawAbilityScore(oCreature, ABILITY_CONSTITUTION, stRole.Ability_CON); NWNX_Creature_SetRawAbilityScore(oCreature, ABILITY_WISDOM, stRole.Ability_WIS); NWNX_Creature_SetRawAbilityScore(oCreature, ABILITY_INTELLIGENCE, stRole.Ability_INT); NWNX_Creature_SetRawAbilityScore(oCreature, ABILITY_CHARISMA, stRole.Ability_CHA); } void pat_ApplySavingThrows(object oCreature, struct PAT_STRUCT_ROLE stRole) { NWNX_Creature_SetBaseSavingThrow(oCreature, SAVING_THROW_FORT, stRole.SavingThrow_Fortitude); NWNX_Creature_SetBaseSavingThrow(oCreature, SAVING_THROW_REFLEX, stRole.SavingThrow_Reflex); NWNX_Creature_SetBaseSavingThrow(oCreature, SAVING_THROW_WILL, stRole.SavingThrow_Will); } void pat_ApplyArmorClass(object oCreature, struct PAT_STRUCT_ROLE stRole) { NWNX_Creature_SetBaseAC(oCreature, stRole.AC); } void pat_ApplyBaseAttackBonus(object oCreature, struct PAT_STRUCT_ROLE stRole) { NWNX_Creature_SetBaseAttackBonus(oCreature, stRole.BaseAttackBonus); } void pat_ApplyBaseHitPoints(object oCreature, struct PAT_STRUCT_ROLE stRole) { NWNX_Object_SetMaxHitPoints(oCreature, stRole.BaseHitPoints); NWNX_Object_SetCurrentHitPoints(oCreature, GetMaxHitPoints(oCreature)); } void pat_SelectSpells(object oCreature, int nCR, int nSource, int nCategory) { struct PAT_STRUCT_SPELL stResult; string sQuery; int nCastingClass = GetClassByPosition(1, oCreature); int nLevel = nCR + (nCR % 2); // Build the Query string sQuery = "(SELECT Spell_ID, Spell_Level FROM " + PAT_TABLE_SPELLS + " WHERE CR_Minimum = " + IntToString(nLevel) + " AND Source = " + IntToString(nSource) + " AND Category & " + IntToString(nCategory) + " != 0" + " ORDER BY RAND() LIMIT " + IntToString(PAT_SPELL_KNOWNSPELLS) + ")"; for (nLevel = nCR + (nCR % 2) - 1; nLevel >= 0; nLevel--) { // Select 3 spells per Level sQuery += " UNION ALL (SELECT Spell_ID, Spell_Level FROM " + PAT_TABLE_SPELLS + " WHERE CR_Minimum = " + IntToString(nLevel) + " AND Source = " + IntToString(nSource) + " AND Category & " + IntToString(nCategory) + " != 0" + " ORDER BY RAND() LIMIT " + IntToString(PAT_SPELL_KNOWNSPELLS) + ")"; } NWNX_SQL_ExecuteQuery(sQuery); int nCurrentSpellLevel = -1; int nMaxSpellSlots; int nRemainingSpellSlots = 0; int nSpellIndex = 0; int nth; int nUseSlots; int nSpellWithinLevel = 0; struct NWNX_Creature_MemorisedSpell stSpell; stSpell.ready = TRUE; stSpell.meta = 0; stSpell.domain = 0; while (NWNX_SQL_ReadyToReadNextRow()) { NWNX_SQL_ReadNextRow(); stResult.Spell_ID = StringToInt(NWNX_SQL_ReadDataInActiveRow(0)); stResult.Spell_Level = StringToInt(NWNX_SQL_ReadDataInActiveRow(1)); // New spell level, reset stats if (nCurrentSpellLevel != stResult.Spell_Level) { // Fill remaining spell slots for (nth = 0; nth < nRemainingSpellSlots; nth++) NWNX_Creature_SetMemorisedSpell(oCreature, nCastingClass, nCurrentSpellLevel, (nMaxSpellSlots - nth) - 1, NWNX_Creature_GetMemorisedSpell(oCreature, nCastingClass, nCurrentSpellLevel, Random(nMaxSpellSlots - nRemainingSpellSlots))); nCurrentSpellLevel = stResult.Spell_Level; nMaxSpellSlots = NWNX_Creature_GetMaxSpellSlots(oCreature, nCastingClass, nCurrentSpellLevel); //SendMessageToAllDMs("Max spell slots for level " + IntToString(nCurrentSpellLevel) + ": " + IntToString(nMaxSpellSlots)); nRemainingSpellSlots = nMaxSpellSlots; nSpellWithinLevel = 0; nSpellIndex = 0; } NWNX_Creature_AddKnownSpell(oCreature, nCastingClass, stResult.Spell_Level, stResult.Spell_ID); if (nRemainingSpellSlots > 0) { stSpell.id = stResult.Spell_ID; nUseSlots = FloatToInt(nRemainingSpellSlots / IntToFloat(PAT_SPELL_KNOWNSPELLS - nSpellWithinLevel)); if (nUseSlots == 0 && nUseSlots < nRemainingSpellSlots) nUseSlots = 1; for (nth = 1; nth <= nUseSlots ; nth++) { //SendMessageToAllDMs("Memorizing spell " + IntToString(stSpell.id) + " (Level " + IntToString(nCurrentSpellLevel) + ") at Index #" + IntToString(nSpellIndex)); NWNX_Creature_SetMemorisedSpell(oCreature, nCastingClass, nCurrentSpellLevel, nSpellIndex, stSpell); nSpellIndex++; nRemainingSpellSlots--; } } nSpellWithinLevel++; } NWNX_Creature_RestoreSpells(oCreature); } void pat_SelectFeats(object oCreature, int nCR, int nPack) { int nChance = d100(); struct PAT_STRUCT_FEAT stResult; string sQuery = "SELECT Feat_ID FROM " + PAT_TABLE_FEATS + " WHERE (Packs LIKE '%;0;%' OR Packs LIKE '%;" + IntToString(nPack) + ";%')" + " AND (Chance_Base + (Chance_Modifier * " + IntToString(nCR) + ")) >= " + IntToString(nChance) + " AND CR_Minimum <=" + IntToString(nCR) + " AND CR_Maximum >=" + IntToString(nCR); NWNX_SQL_ExecuteQuery(sQuery); while (NWNX_SQL_ReadyToReadNextRow()) { NWNX_SQL_ReadNextRow(); stResult.Feat_ID = StringToInt(NWNX_SQL_ReadDataInActiveRow(0)); NWNX_Creature_AddFeat(oCreature, stResult.Feat_ID); } } void pat_ApplySkillString(object oCreature, int nCR, string sSkillString, float nMultiplier) { if (sSkillString == "") return; struct sStringTokenizer tokenSkills = GetStringTokenizer(sSkillString, ";"); string sSkill; while (HasMoreTokens(tokenSkills)) { tokenSkills = AdvanceToNextToken(tokenSkills); sSkill = GetNextToken(tokenSkills); if (sSkill != "") NWNX_Creature_SetSkillRank(oCreature, StringToInt(sSkill), FloatToInt(PAT_SKILLS_BASE + nCR * nMultiplier)); } } void pat_ApplySkills(object oCreature, int nCR, int nSkillset) { struct PAT_STRUCT_SKILLSET stResult; string sQuery = "SELECT Skills_Extreme, Skills_High, Skills_Medium, Skills_Low FROM " + PAT_TABLE_SKILLSETS + " WHERE ID=" + IntToString(nSkillset); NWNX_SQL_ExecuteQuery(sQuery); if (NWNX_SQL_ReadyToReadNextRow()) { NWNX_SQL_ReadNextRow(); stResult.Skills_Extreme = NWNX_SQL_ReadDataInActiveRow(0); stResult.Skills_High = NWNX_SQL_ReadDataInActiveRow(1); stResult.Skills_Medium = NWNX_SQL_ReadDataInActiveRow(2); stResult.Skills_Low = NWNX_SQL_ReadDataInActiveRow(3); pat_ApplySkillString(oCreature, nCR, stResult.Skills_Extreme, PAT_SKILLS_EXTREME); pat_ApplySkillString(oCreature, nCR, stResult.Skills_High, PAT_SKILLS_HIGH); pat_ApplySkillString(oCreature, nCR, stResult.Skills_Medium, PAT_SKILLS_MEDIUM); pat_ApplySkillString(oCreature, nCR, stResult.Skills_Low, PAT_SKILLS_LOW); } } // public functions int pat_GetTablesExist() { int nExists_Base = NWNX_SQL_ExecuteQuery("DESCRIBE " + PAT_TABLE_BASE); int nExists_Class_Setup = NWNX_SQL_ExecuteQuery("DESCRIBE " + PAT_TABLE_CLASS_SETUP); int nExists_Spells = NWNX_SQL_ExecuteQuery("DESCRIBE " + PAT_TABLE_SPELLS); int nExists_Feats = NWNX_SQL_ExecuteQuery("DESCRIBE " + PAT_TABLE_FEATS); int nExists_Skillsets = NWNX_SQL_ExecuteQuery("DESCRIBE " + PAT_TABLE_SKILLSETS); int nExists_Areas = NWNX_SQL_ExecuteQuery("DESCRIBE " + PAT_TABLE_AREAS); return (nExists_Base && nExists_Class_Setup && nExists_Spells && nExists_Feats && nExists_Skillsets && nExists_Areas); } void pat_DropTables() { NWNX_SQL_ExecuteQuery("DROP TABLE " + PAT_TABLE_BASE); NWNX_SQL_ExecuteQuery("DROP TABLE " + PAT_TABLE_CLASS_SETUP); NWNX_SQL_ExecuteQuery("DROP TABLE " + PAT_TABLE_SPELLS); NWNX_SQL_ExecuteQuery("DROP TABLE " + PAT_TABLE_FEATS); NWNX_SQL_ExecuteQuery("DROP TABLE " + PAT_TABLE_SKILLSETS); // The areas table is not dropped by the system and needs to be dropped manually // for it to reinitialize // NWNX_SQL_ExecuteQuery("DROP TABLE " + PAT_TABLE_AREAS); } void pat_CreateTables() { // Base Table string Table_Base = PAT_TABLE_BASE + " (" + "Role_ID int NOT NULL," + "Role_Name varchar(16) NOT NULL," + "CR int NOT NULL," + "Ability_STR int NOT NULL DEFAULT 8," + "Ability_DEX int NOT NULL DEFAULT 8," + "Ability_CON int NOT NULL DEFAULT 8," + "Ability_WIS int NOT NULL DEFAULT 8," + "Ability_INT int NOT NULL DEFAULT 8," + "Ability_CHA int NOT NULL DEFAULT 8," + "SavingThrow_Fortitude int NOT NULL DEFAULT 1," + "SavingThrow_Reflex int NOT NULL DEFAULT 1," + "SavingThrow_Will int NOT NULL DEFAULT 1," + "AC int NOT NULL DEFAULT 10," + "BaseAttackBonus int NOT NULL DEFAULT 1," + "BaseHitPoints int NOT NULL DEFAULT 10" + ")"; string Table_ClassSetup = PAT_TABLE_CLASS_SETUP + " (" + "Role_ID int NOT NULL," + "Class_1 int NOT NULL DEFAULT " + IntToString(CLASS_TYPE_FIGHTER) + ", " + "Class_2 int NOT NULL DEFAULT -1, " + "Class_3 int NOT NULL DEFAULT -1, " + "Spell_Source int NOT NULL DEFAULT 0, " + "Spell_Category int NOT NULL DEFAULT 0, " + "Feat_Pack int NOT NULL DEFAULT 1, " + "Skillset int NOT NULL DEFAULT 1" + ")"; string Table_Spells = PAT_TABLE_SPELLS + " (" + "Spell_ID int NOT NULL, " + "Spell_Level int NOT NULL, " + "Source int NOT NULL, " + "Category int NOT NULL, " + "Target int NOT NULL, " + "CR_Minimum int NOT NULL DEFAULT 1" + ")"; string Table_Feats = PAT_TABLE_FEATS + " (" + "Feat_ID int NOT NULL, " + "Packs varchar(64) NOT NULL DEFAULT ';0;', " + "Chance_Base int NOT NULL DEFAULT 100, " + "Chance_Modifier int NOT NULL DEFAULT 0, " + "CR_Minimum int NOT NULL DEFAULT 1, " + "CR_Maximum int NOT NULL DEFAULT " + IntToString(PAT_CR_MAXIMUM) + ")"; string Table_Skillsets = PAT_TABLE_SKILLSETS + " (" + "ID int NOT NULL PRIMARY KEY, " + "Skills_Extreme varchar(64) DEFAULT NULL, " + "Skills_High varchar(64) DEFAULT NULL, " + "Skills_Medium varchar(64) DEFAULT NULL, " + "Skills_Low varchar(64) DEFAULT NULL" + ")"; string Table_Areas = PAT_TABLE_AREAS + " (" + "ResRef varchar(64) NOT NULL PRIMARY KEY, " + "Tag varchar(64) NULL, " + "Name varchar(64) NULL, " + "CR int NOT NULL DEFAULT -1" + ")"; NWNX_SQL_ExecuteQuery("CREATE TABLE IF NOT EXISTS " + Table_Base); NWNX_SQL_ExecuteQuery("CREATE TABLE IF NOT EXISTS " + Table_ClassSetup); NWNX_SQL_ExecuteQuery("CREATE TABLE IF NOT EXISTS " + Table_Spells); NWNX_SQL_ExecuteQuery("CREATE TABLE IF NOT EXISTS " + Table_Feats); NWNX_SQL_ExecuteQuery("CREATE TABLE IF NOT EXISTS " + Table_Skillsets); NWNX_SQL_ExecuteQuery("CREATE TABLE IF NOT EXISTS " + Table_Areas); } int pat_GetRole_ID(string sRole_Name) { // Verify role name sRole_Name = pat_Verify_Role_Name(sRole_Name); object Cache = pat_GetCache(); int nRole_ID = GetLocalInt(Cache, "RoleID_" + sRole_Name); if (nRole_ID == 0) { nRole_ID = SQLExecAndFetchInt("SELECT Role_ID FROM " + PAT_TABLE_BASE + " WHERE upper(Role_Name)='" + GetStringUpperCase(sRole_Name) + "'"); SetLocalInt(Cache, "Role_" + sRole_Name, nRole_ID); } return nRole_ID; } string pat_GetRole_Name(int nRole_ID) { // Verify role id nRole_ID = pat_Verify_Role_ID(nRole_ID); object Cache = pat_GetCache(); string sRole_Name = GetLocalString(Cache, "RoleName_" + IntToString(nRole_ID)); if (sRole_Name == "") { sRole_Name = SQLExecAndFetchString("SELECT Role_Name FROM " + PAT_TABLE_BASE + " WHERE ID=" + IntToString(nRole_ID)); SetLocalString(Cache, "RoleName_" + IntToString(nRole_ID), sRole_Name); } return sRole_Name; } void pat_SetDMSetting(object oDM, int nSetting) { SetLocalInt(oDM, PAT_VAR_DM_SETTING, nSetting); } int pat_GetDMSetting(object oDM) { return GetLocalInt(oDM, PAT_VAR_DM_SETTING); } void pat_AddArea(string sResRef, string sTag, string sName, int nCR) { NWNX_SQL_PrepareQuery("INSERT INTO " + PAT_TABLE_AREAS + " (ResRef, Tag, Name, CR) VALUES (?, ?, ?, ?)"); NWNX_SQL_PreparedString(0, sResRef); NWNX_SQL_PreparedString(1, sTag); NWNX_SQL_PreparedString(2, sName); NWNX_SQL_PreparedInt(3, nCR); NWNX_SQL_ExecutePreparedQuery(); } void pat_AddRoleEntry(int nRole_ID, string sRole_Name, int nCR, int nAbility_STR, int nAbility_DEX, int nAbility_CON, int nAbility_WIS, int nAbility_INT, int nAbility_CHA, int nSavingThrow_Fortitude, int nSavingThrow_Reflex, int nSavingThrow_Will, int nAC, int nBaseAttackBonus, int nBaseHitPoints) { NWNX_SQL_PrepareQuery("INSERT INTO " + PAT_TABLE_BASE + " (Role_ID,Role_Name,CR,Ability_STR,Ability_DEX,Ability_CON,Ability_WIS,Ability_INT,Ability_CHA,SavingThrow_Fortitude,SavingThrow_Reflex,SavingThrow_Will,AC,BaseAttackBonus,BaseHitPoints) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); NWNX_SQL_PreparedInt(0, nRole_ID); NWNX_SQL_PreparedString(1, sRole_Name); NWNX_SQL_PreparedInt(2, nCR); NWNX_SQL_PreparedInt(3, nAbility_STR); NWNX_SQL_PreparedInt(4, nAbility_DEX); NWNX_SQL_PreparedInt(5, nAbility_CON); NWNX_SQL_PreparedInt(6, nAbility_WIS); NWNX_SQL_PreparedInt(7, nAbility_INT); NWNX_SQL_PreparedInt(8, nAbility_CHA); NWNX_SQL_PreparedInt(9, nSavingThrow_Fortitude); NWNX_SQL_PreparedInt(10, nSavingThrow_Reflex); NWNX_SQL_PreparedInt(11, nSavingThrow_Will); NWNX_SQL_PreparedInt(12, nAC); NWNX_SQL_PreparedInt(13, nBaseAttackBonus); NWNX_SQL_PreparedInt(14, nBaseHitPoints); NWNX_SQL_ExecutePreparedQuery(); } void pat_AddRoleEntry_Struct(struct PAT_STRUCT_ROLE stRole) { pat_AddRoleEntry(stRole.Role_ID, stRole.Role_Name, stRole.CR,stRole.Ability_STR, stRole.Ability_DEX, stRole.Ability_CON, stRole.Ability_WIS, stRole.Ability_INT, stRole.Ability_CHA, stRole.SavingThrow_Fortitude, stRole.SavingThrow_Reflex, stRole.SavingThrow_Will, stRole.AC, stRole.BaseAttackBonus, stRole.BaseHitPoints); } void pat_AddClassSetup(int nRole_ID, int nClass_1, int nClass_2, int nClass_3, int nSpell_Source, int nSpell_Category, int nFeat_Pack, int nSkillset) { NWNX_SQL_PrepareQuery("INSERT INTO " + PAT_TABLE_CLASS_SETUP + " (Role_ID,Class_1,Class_2,Class_3,Spell_Source,Spell_Category,Feat_Pack,Skillset) VALUES (?,?,?,?,?,?,?,?)"); NWNX_SQL_PreparedInt(0, nRole_ID); NWNX_SQL_PreparedInt(1, nClass_1); NWNX_SQL_PreparedInt(2, nClass_2); NWNX_SQL_PreparedInt(3, nClass_3); NWNX_SQL_PreparedInt(4, nSpell_Source); NWNX_SQL_PreparedInt(5, nSpell_Category); NWNX_SQL_PreparedInt(6, nFeat_Pack); NWNX_SQL_PreparedInt(7, nSkillset); NWNX_SQL_ExecutePreparedQuery(); } void pat_AddSpell(int nSpell_ID, int nSpell_Level, int nSource, int nCategory, int nTarget, int nCR_Minimum) { NWNX_SQL_PrepareQuery("INSERT INTO " + PAT_TABLE_SPELLS + " (Spell_ID, Spell_Level, Source, Category, Target, CR_Minimum) VALUES (?,?,?,?,?,?)"); NWNX_SQL_PreparedInt(0, nSpell_ID); NWNX_SQL_PreparedInt(1, nSpell_Level); NWNX_SQL_PreparedInt(2, nSource); NWNX_SQL_PreparedInt(3, nCategory); NWNX_SQL_PreparedInt(4, nTarget); NWNX_SQL_PreparedInt(5, nCR_Minimum); NWNX_SQL_ExecutePreparedQuery(); } void pat_AddFeat(int nFeat_ID, string sPacks, int nChance_Base, int nChance_Modifier, int nCR_Minimum, int nCR_Maximum = PAT_CR_MAXIMUM) { NWNX_SQL_PrepareQuery("INSERT INTO " + PAT_TABLE_FEATS + " (Feat_ID, Packs, Chance_Base, Chance_Modifier, CR_Minimum, CR_Maximum) VALUES (?,?,?,?,?,?)"); NWNX_SQL_PreparedInt(0, nFeat_ID); NWNX_SQL_PreparedString(1, sPacks); NWNX_SQL_PreparedInt(2, nChance_Base); NWNX_SQL_PreparedInt(3, nChance_Modifier); NWNX_SQL_PreparedInt(4, nCR_Minimum); NWNX_SQL_PreparedInt(5, nCR_Maximum); NWNX_SQL_ExecutePreparedQuery(); } void pat_AddSkillset(int nID, string sSkills_Extreme, string sSkills_High, string sSkills_Medium, string sSkills_Low) { NWNX_SQL_PrepareQuery("INSERT INTO " + PAT_TABLE_SKILLSETS + " (ID,Skills_Extreme,Skills_High,Skills_Medium,Skills_Low) VALUES (?,?,?,?,?)"); NWNX_SQL_PreparedInt(0, nID); NWNX_SQL_PreparedString(1, sSkills_Extreme); NWNX_SQL_PreparedString(2, sSkills_High); NWNX_SQL_PreparedString(3, sSkills_Medium); NWNX_SQL_PreparedString(4, sSkills_Low); NWNX_SQL_ExecutePreparedQuery(); } struct PAT_STRUCT_ROLE pat_GetRoleEntryByID(int nRole_ID, int nCR) { struct PAT_STRUCT_ROLE stResult; string sQuery = "SELECT Role_ID, Role_Name, CR, Ability_STR, Ability_DEX, Ability_CON, Ability_WIS, Ability_INT, Ability_CHA, SavingThrow_Fortitude, SavingThrow_Reflex, SavingThrow_Will, AC, BaseHitPoints FROM " + PAT_TABLE_BASE + " WHERE Role_ID="+IntToString(pat_Verify_Role_ID(nRole_ID))+" AND CR="+IntToString(nCR); NWNX_SQL_ExecuteQuery(sQuery); if (NWNX_SQL_ReadyToReadNextRow()) { NWNX_SQL_ReadNextRow(); stResult.Role_ID = StringToInt(NWNX_SQL_ReadDataInActiveRow(0)); stResult.Role_Name = NWNX_SQL_ReadDataInActiveRow(1); stResult.CR = StringToInt(NWNX_SQL_ReadDataInActiveRow(2)); stResult.Ability_STR = StringToInt(NWNX_SQL_ReadDataInActiveRow(3)); stResult.Ability_DEX = StringToInt(NWNX_SQL_ReadDataInActiveRow(4)); stResult.Ability_CON = StringToInt(NWNX_SQL_ReadDataInActiveRow(5)); stResult.Ability_WIS = StringToInt(NWNX_SQL_ReadDataInActiveRow(6)); stResult.Ability_INT = StringToInt(NWNX_SQL_ReadDataInActiveRow(7)); stResult.Ability_CHA = StringToInt(NWNX_SQL_ReadDataInActiveRow(8)); stResult.SavingThrow_Fortitude = StringToInt(NWNX_SQL_ReadDataInActiveRow(9)); stResult.SavingThrow_Reflex = StringToInt(NWNX_SQL_ReadDataInActiveRow(10)); stResult.SavingThrow_Will = StringToInt(NWNX_SQL_ReadDataInActiveRow(11)); stResult.AC = StringToInt(NWNX_SQL_ReadDataInActiveRow(12)); stResult.BaseHitPoints = StringToInt(NWNX_SQL_ReadDataInActiveRow(13)); } return stResult; } struct PAT_STRUCT_ROLE pat_GetRoleEntryByName(string sRoleName, int nCR) { return pat_GetRoleEntryByID(pat_GetRole_ID(sRoleName), nCR); } struct PAT_STRUCT_CLASS_SETUP pat_GetClassSetup(int nRole_ID, int nClass_1 = -1, int nClass_2 = -1, int nClass_3 = -1) { struct PAT_STRUCT_CLASS_SETUP stResult; string sQuery = "SELECT Role_ID, Class_1, Class_2, Class_3, Spell_Source, Spell_Category, Feat_Pack, Skillset FROM " + PAT_TABLE_CLASS_SETUP + " WHERE Role_ID=" + IntToString(nRole_ID); if (nClass_1 != -1) sQuery += " AND Class_1=" + IntToString(nClass_1); if (nClass_2 != -1) sQuery += " AND Class_2=" + IntToString(nClass_2); if (nClass_3 != -1) sQuery += " AND Class_3=" + IntToString(nClass_3); sQuery += " ORDER BY RAND() LIMIT 1"; NWNX_SQL_ExecuteQuery(sQuery); if (NWNX_SQL_ReadyToReadNextRow()) { NWNX_SQL_ReadNextRow(); stResult.Role_ID = StringToInt(NWNX_SQL_ReadDataInActiveRow(0)); stResult.Class_1 = StringToInt(NWNX_SQL_ReadDataInActiveRow(1)); stResult.Class_2 = StringToInt(NWNX_SQL_ReadDataInActiveRow(2)); stResult.Class_3 = StringToInt(NWNX_SQL_ReadDataInActiveRow(3)); stResult.Spell_Source = StringToInt(NWNX_SQL_ReadDataInActiveRow(4)); stResult.Spell_Category = StringToInt(NWNX_SQL_ReadDataInActiveRow(5)); stResult.Feat_Pack = StringToInt(NWNX_SQL_ReadDataInActiveRow(6)); stResult.Skillset = StringToInt(NWNX_SQL_ReadDataInActiveRow(7)); } return stResult; } int pat_CalculateAbilityScore(int nCR, int nGain_Rate) { float fCR = IntToFloat(nCR); switch (nGain_Rate) { case PAT_GAIN_EXTREME: return PAT_ABILITY_BASE + FloatToInt(fCR * PAT_ABILITY_GAIN_EXTREME); case PAT_GAIN_HIGH: return PAT_ABILITY_BASE + FloatToInt(fCR * PAT_ABILITY_GAIN_HIGH); case PAT_GAIN_MEDIUM: return PAT_ABILITY_BASE + FloatToInt(fCR * PAT_ABILITY_GAIN_MEDIUM); case PAT_GAIN_LOW: return PAT_ABILITY_BASE + FloatToInt(fCR * PAT_ABILITY_GAIN_LOW); } return pat_CalculateAbilityScore(nCR, PAT_GAIN_MEDIUM); } int pat_CalculateSavingThrowValue(int nCR, int nGain_Rate) { float fCR = IntToFloat(nCR); switch (nGain_Rate) { case PAT_GAIN_EXTREME: // No extreme level for Saving Throws case PAT_GAIN_HIGH: return PAT_SAVINGTHROW_BASE + FloatToInt(fCR * PAT_SAVINGTHROW_GAIN_HIGH); case PAT_GAIN_MEDIUM: return PAT_SAVINGTHROW_BASE + FloatToInt(fCR * PAT_SAVINGTHROW_GAIN_MEDIUM); case PAT_GAIN_LOW: return PAT_SAVINGTHROW_BASE + FloatToInt(fCR * PAT_SAVINGTHROW_GAIN_LOW); } return pat_CalculateSavingThrowValue(nCR, PAT_GAIN_MEDIUM); } int pat_CalculateACValue(int nCR, int nGain_Rate) { int nResult; float fCR = IntToFloat(nCR); switch (nGain_Rate) { case PAT_GAIN_EXTREME: // No extreme level for Armor Class case PAT_GAIN_HIGH: nResult = PAT_AC_BASE + FloatToInt(fCR / PAT_AC_GAIN_HIGH + fCR * fCR / (10 * PAT_AC_GAIN_HIGH)); break; case PAT_GAIN_MEDIUM: nResult = PAT_AC_BASE + FloatToInt(fCR / PAT_AC_GAIN_MEDIUM + fCR * fCR / (10 * PAT_AC_GAIN_MEDIUM)); break; case PAT_GAIN_LOW: nResult = PAT_AC_BASE + FloatToInt(fCR / PAT_AC_GAIN_LOW + fCR * fCR / (10 * PAT_AC_GAIN_LOW)); break; } if (nResult > PAT_AC_MAXIMUM) nResult = PAT_AC_MAXIMUM; return nResult; } int pat_CalculateBaseAttackBonus(int nCR, int nGain_Rate) { float fCR = IntToFloat(nCR); switch (nGain_Rate) { case PAT_GAIN_EXTREME: // No extreme level for Base Attack Bonus case PAT_GAIN_HIGH: return PAT_BAB_BASE + FloatToInt(fCR * PAT_BAB_GAIN_HIGH); case PAT_GAIN_MEDIUM: return PAT_BAB_BASE + FloatToInt(fCR * PAT_BAB_GAIN_MEDIUM); case PAT_GAIN_LOW: return PAT_BAB_BASE + FloatToInt(fCR * PAT_BAB_GAIN_LOW); } return pat_CalculateBaseAttackBonus(nCR, PAT_GAIN_MEDIUM); } int pat_CalculateBaseHitPoints(int nCR, int nGain_Rate) { float fCR = IntToFloat(nCR); switch (nGain_Rate) { case PAT_GAIN_EXTREME: // No extreme level for Armor Class case PAT_GAIN_HIGH: return FloatToInt(PAT_HP_BASE / PAT_HP_GAIN_HIGH + fCR * (6 - PAT_HP_GAIN_HIGH) + fCR * fCR * fCR / (10 * PAT_HP_GAIN_HIGH)); case PAT_GAIN_MEDIUM: return FloatToInt(PAT_HP_BASE / PAT_HP_GAIN_MEDIUM + fCR * (6 - PAT_HP_GAIN_MEDIUM) + fCR * fCR * fCR / (10 * PAT_HP_GAIN_MEDIUM)); case PAT_GAIN_LOW: return FloatToInt(PAT_HP_BASE / PAT_HP_GAIN_LOW + fCR * (6 - PAT_HP_GAIN_LOW) + fCR * fCR * fCR / (10 * PAT_HP_GAIN_LOW)); } return pat_CalculateBaseHitPoints(nCR, PAT_GAIN_MEDIUM); } int pat_Apply(object oCreature, int nCR = 0, int nRole_ID = PAT_ROLE_UNDEFINED) { // Is PAT disabled for this creature? if (GetLocalInt(oCreature, PAT_VAR_DISABLE) != 0) return FALSE; // LogEntry pat_Log("Trying to apply to [" + GetName(oCreature) + "]"); // Do we use an override? If not, detect creature CR if (nCR == 0) nCR = pat_GetCreatureCR(oCreature); // If CR is 0, we're unable to apply PAT to this creature if (nCR == 0) return FALSE; // Retreive PAT Role of oCreature if (nRole_ID == PAT_ROLE_UNDEFINED) nRole_ID = pat_GetCreatureRole(oCreature); // If Role is 0, we're unable to apply PAT to this creature if (nRole_ID == 0) return FALSE; // Load Role struct PAT_STRUCT_ROLE stRoleEntry = pat_GetRoleEntryByID(nRole_ID, nCR); // What Level is this creature? int nLevel = GetHitDice(oCreature); // Select a random Class Setup for oCreature // If this creature has more than 1 level, it probably is NOT a PAT_Creature int nClass_1 = -1; if (nLevel > 1) nClass_1 = pat_GetHighestCreatureClass(oCreature); struct PAT_STRUCT_CLASS_SETUP stClassSetup = pat_GetClassSetup(nRole_ID, nClass_1); // If we didn't find anything, search without the Class if (stClassSetup.Role_ID == 0) stClassSetup = pat_GetClassSetup(nRole_ID); // If we still didn't find aynthing, we're unable to apply PAT to this creature if (stClassSetup.Role_ID == 0) return FALSE; // Remove all spells prior to (de)levelling pat_RemoveAllSpells(oCreature); int nth; // Delevel to 1 if (nLevel > 1) { NWNX_Creature_LevelDown(oCreature, nLevel - 1); nLevel = 1; } // Get current class setup nClass_1 = GetClassByPosition(1, oCreature); int nClass_2 = GetClassByPosition(2, oCreature); int nClass_3 = GetClassByPosition(3, oCreature); // Change class at position one // If the creature uses this class at position 2 or three, set current // class 1 as class 2/3 first if (nClass_2 == stClassSetup.Class_1) { // Only modify class if we have one NWNX_Creature_SetClassByPosition(oCreature, 1, nClass_1); nClass_2 = nClass_1; } else if (nClass_3 == stClassSetup.Class_1) { NWNX_Creature_SetClassByPosition(oCreature, 2, nClass_1); nClass_3 = nClass_1; } if (stClassSetup.Class_1 != -1) NWNX_Creature_SetClassByPosition(oCreature, 0, stClassSetup.Class_1); if (stClassSetup.Class_2 != -1) { // If the creature uses tihs class at position 3, set current class // 2 as 3 first if (nClass_3 == stClassSetup.Class_2) NWNX_Creature_SetClassByPosition(oCreature, 2, nClass_2); if (nClass_2 != CLASS_TYPE_INVALID) NWNX_Creature_SetClassByPosition(oCreature, 1, stClassSetup.Class_2); } if (stClassSetup.Class_3 != -1) { if (nClass_3 != CLASS_TYPE_INVALID) NWNX_Creature_SetClassByPosition(oCreature, 2, stClassSetup.Class_3); } // Set correct alignment for base classes switch (stClassSetup.Class_1) { case CLASS_TYPE_BARBARIAN: case CLASS_TYPE_BARD: NWNX_Creature_SetAlignmentLawChaos(oCreature, Random(70)); break; case CLASS_TYPE_DRUID: NWNX_Creature_SetAlignmentLawChaos(oCreature, Random(39) + 31); break; case CLASS_TYPE_MONK: NWNX_Creature_SetAlignmentLawChaos(oCreature, Random(31) + 70); break; case CLASS_TYPE_PALADIN: NWNX_Creature_SetAlignmentLawChaos(oCreature, Random(31) + 70); NWNX_Creature_SetAlignmentGoodEvil(oCreature, Random(31) + 70); break; } // Level Up if (nLevel < nCR) NWNX_Creature_LevelUp(oCreature, pat_GetRandomClass(stClassSetup), nCR - nLevel); // Remove all spells again after levelling pat_RemoveAllSpells(oCreature); // Replace all Feats if we are told to if (stClassSetup.Feat_Pack != -1) { pat_RemoveAllFeats(oCreature); pat_SelectFeats(oCreature, nCR, stClassSetup.Feat_Pack); } // Apply Role stats to the creature pat_ApplyAbilities(oCreature, stRoleEntry); pat_ApplySavingThrows(oCreature, stRoleEntry); pat_ApplyArmorClass(oCreature, stRoleEntry); pat_ApplyBaseAttackBonus(oCreature, stRoleEntry); pat_ApplyBaseHitPoints(oCreature, stRoleEntry); // Add spells to creature DelayCommand(0.5f, pat_SelectSpells(oCreature, nCR, stClassSetup.Spell_Source, stClassSetup.Spell_Category)); // Set skills of creature pat_ApplySkills(oCreature, nCR, stClassSetup.Skillset); // Give out some weapons that fit the role // and mark it to not be replaced if (stRoleEntry.Role_ID < PAT_ROLE_NAKED && GetLocalInt(oCreature, CS_LGS_EQUIP_DISABLE) != 1) // No equipment for mobs set to not get equipped by LGS { object oEquipment; switch (stRoleEntry.Role_ID) { case PAT_ROLE_DPS_RANGE_DEX: case PAT_ROLE_DPS_RANGE_WIS: oEquipment = lgs_CreateWeaponMelee(oCreature, nCR, -1); // Create a backup melee weapon oEquipment = lgs_CreateWeaponRanged(oCreature, nCR, INVENTORY_SLOT_RIGHTHAND, GetCreatureSize(oCreature)); // Create a ranged weapon with ammunition SetLocalInt(oEquipment, CS_LGS_EQUIP_DONTREPLACE, 1); break; case PAT_ROLE_TANK_STR: oEquipment = lgs_CreateWeaponMelee(oCreature, nCR, INVENTORY_SLOT_RIGHTHAND, CI_EGS_WEAPONSIZE_MEDIUM); // Create a melee weapon (maximum size medium) SetLocalInt(oEquipment, CS_LGS_EQUIP_DONTREPLACE, 1); oEquipment = lgs_CreateShield(oCreature, nCR); // Create a shield SetLocalInt(oEquipment, CS_LGS_EQUIP_DONTREPLACE, 1); break; default: // Everyone else gets at least a melee weapon of some description, the rest is handled via the Monster equip system of LGS oEquipment = lgs_CreateWeaponMelee(oCreature, nCR); SetLocalInt(oEquipment, CS_LGS_EQUIP_DONTREPLACE, 1); break; } // Get some suitable Body Armor oEquipment = lgs_CreateArmorBody(oCreature, nCR); SetLocalInt(oEquipment, CS_LGS_EQUIP_DONTREPLACE, 1); } else { // Improve Creature Items on Naked Roles object oCreatureItem = GetItemInSlot(INVENTORY_SLOT_CWEAPON_L, oCreature); lgs_AdditionalChance(oCreatureItem, nCR, CI_IP_EnhancementBonus, X2_IP_ADDPROP_POLICY_REPLACE_EXISTING); oCreatureItem = GetItemInSlot(INVENTORY_SLOT_CWEAPON_R, oCreature); lgs_AdditionalChance(oCreatureItem, nCR, CI_IP_EnhancementBonus, X2_IP_ADDPROP_POLICY_REPLACE_EXISTING); oCreatureItem = GetItemInSlot(INVENTORY_SLOT_CWEAPON_B, oCreature); lgs_AdditionalChance(oCreatureItem, nCR, CI_IP_EnhancementBonus, X2_IP_ADDPROP_POLICY_REPLACE_EXISTING); //oCreatureItem = GetItemInSlot(INVENTORY_SLOT_CARMOUR, oCreature); //lgs_AdditionalChance(oCreatureItem, nCR, CI_IP_ACBonus, X2_IP_ADDPROP_POLICY_REPLACE_EXISTING); //lgs_AdditionalChance(oCreatureItem, nCR, CI_IP_DamageReduction, X2_IP_ADDPROP_POLICY_REPLACE_EXISTING); } // Set the Challenge Rating variable on the monster NWNX_Creature_SetChallengeRating(oCreature, IntToFloat(nCR)); // PAT was successfully applied SetLocalInt(oCreature, PAT_VAR_APPLIED, 1); // Set AI scripts SetEventScript(oCreature, EVENT_SCRIPT_CREATURE_ON_BLOCKED_BY_DOOR, "j_ai_onblocked"); SetEventScript(oCreature, EVENT_SCRIPT_CREATURE_ON_DAMAGED, "j_ai_ondamaged"); // Don't modify cnr Skin Death if (GetEventScript(oCreature, EVENT_SCRIPT_CREATURE_ON_DEATH) != "cnr_skin_ondeath") SetEventScript(oCreature, EVENT_SCRIPT_CREATURE_ON_DEATH, "j_ai_ondeath"); else SetLocalString(oCreature, "PAT_post_cnr_skin_ondeath", "j_ai_ondeath"); SetEventScript(oCreature, EVENT_SCRIPT_CREATURE_ON_DIALOGUE, "j_ai_onconversat"); SetEventScript(oCreature, EVENT_SCRIPT_CREATURE_ON_DISTURBED, "j_ai_ondisturbed"); SetEventScript(oCreature, EVENT_SCRIPT_CREATURE_ON_END_COMBATROUND, "j_ai_oncombatrou"); SetEventScript(oCreature, EVENT_SCRIPT_CREATURE_ON_HEARTBEAT, "j_ai_onheartbeat"); SetEventScript(oCreature, EVENT_SCRIPT_CREATURE_ON_MELEE_ATTACKED, "j_ai_onphiattack"); SetEventScript(oCreature, EVENT_SCRIPT_CREATURE_ON_NOTICE, "j_ai_onpercieve"); SetEventScript(oCreature, EVENT_SCRIPT_CREATURE_ON_RESTED, "j_ai_onrest"); SetEventScript(oCreature, EVENT_SCRIPT_CREATURE_ON_SPAWN_IN, "j_ai_onspawn"); SetEventScript(oCreature, EVENT_SCRIPT_CREATURE_ON_SPELLCASTAT, "j_ai_onspellcast"); SetEventScript(oCreature, EVENT_SCRIPT_CREATURE_ON_USER_DEFINED_EVENT, "j_ai_onuserdef"); // Execute the spawn script of Jasperre's AI ExecuteScript("j_ai_onspawn", oCreature); // Log pat_Log("Applied PAT to [" + GetName(oCreature) + "] successfully"); return TRUE; }