Aschbourne_PRC8/_module/nss/g_common.nss
GetOffMyYarn 69879d6957 Areas and Fixes
Added CCOH and missing areas
Changed some areas to be craftable,
Fixed some on death issues,
Fixed the Gaurd
2024-08-30 11:38:44 -04:00

3823 lines
193 KiB
Plaintext

////////////////////////////////////////////////////////////////////////////////
// Common Functions Library
// -TheEngine Jan 5/2003
// www.zeromassengine.com
////////////////////////////////////////////////////////////////////////////////
// NOTES for future revisions
//
////////////////////////////////////////////////////////////////////////////////
// Food System v1.00
// -TheEngine Nov 9/2002
// www.zeromassengine.com
// NOTES
// Standard usage is 120 units for 1 day of rations (5 units per game-hour)
// Therefore, 6 units of supplies is deducted every 24 seconds under the STANDARD conditions
//
// *** DEC 19 - Cut the food consumption rate by 40% - it was just eating resources too fast.
// Default Heartbeat for system is now 60 seconds instead of 24 seconds.
// - Added code to FoodSys_HeartBeat() so if a PC is dead (0 HP), he will not
// use food.
// *** JAN 5, 2003 - Integrated Foodsystem scripts into Common scripts library file
//
// STANDARD CONDITIONS
// o 120 Supply units = 1 day of rations
// o 6 food units for every 4 rounds (24 seconds), totalling 120 units for 1 game day
// Since there are 480 rounds in 1 game day, there are 20 x 24-second periods in a game day.
// 20 x 6 = 120 supply units.
// o SEASON = Fall; Time of Day = Day; CON-mod = 0; STR-mod = 0; Terrain = GRASSLAND; Modifier = NORMAL;
// Ambient Temp = 25 C; No Clothing.
//
// TIME Notes
// 1 round = 6 seconds, 1 turn = 60 seconds
// 2 real minutes = 1 game hour
// 48 rm's = 1 game day
//
// CONSIDERATIONS
// - Season
// - Time of Day
// - PC Constitution
// - PC Strength
// - Terrain Type
// - Ambient Temperature
// - PC Clothing/Equipment/
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Includes ////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
#include "NW_O2_CONINCLUDE"
#include "NW_I0_GENERIC"
////////////////////////////////////////////////////////////////////////////////
// Constants ///////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
int SEASON_SUMMER = 1; int SEASON_FALL = 2; // JUN - AUG then SEP - NOV
int SEASON_WINTER = 3; int SEASON_SPRING = 4; // DEC - FEB then MAR - MAY
// Time of Day Constants
int TOD_DUSK = 1; int TOD_DAWN = 2;
int TOD_DAY = 3; int TOD_NIGHT = 4;
// Healing
int HEAL_EFFECTS_NONE = 0; // Healing Function Constants
int HEAL_EFFECTS_BASIC = 1; // Effects to be removed will be AC, Attack, and Damage Decrease
int HEAL_EFFECTS_SENSORY = 2; // Saving throws, Curse, Blindness and Deafness
int HEAL_EFFECTS_CONSTITUTION = 4; // Constitution impairments. Damage Immunity Decrease, Disease, Poison and Paralysis
int HEAL_EFFECTS_ENERGY_DRAIN = 8; // Negative Levels are lifted.
int HEAL_EFFECTS_ADVANCED = 16; // Ability decrease, Skill decrease and Spell Resistance Decrease
int HEAL_EFFECTS_ALL = 31; // Remove ALL effects
// Blessing
int HEAL_BLESS_DURATION_LOW = 16; // 1 minutes blessing duration
int HEAL_BLESS_DURATION_MEDIUM = 32; // 3 minutes
int HEAL_BLESS_DURATION_HIGH = 64; // 6 minutes
int HEAL_BLESS_NONE = 0; // Blessing 'packages'
int HEAL_BLESS_BASIC = 1; // +1 AC/Attack/Saves
int HEAL_BLESS_AVERAGE = 2; // +2 AC/Attack/Saves
int HEAL_BLESS_HIGH = 4; // +3 AC/Attack/Saves
int HEAL_BLESS_EXTRA_DAMAGE = 128; // Optional Blessing Effects
int HEAL_BLESS_DAMAGE_REDUCTION = 256; int HEAL_BLESS_DAMAGE_RESIST_ACID = 512;
int HEAL_BLESS_DAMAGE_RESIST_COLD = 1024; int HEAL_BLESS_DAMAGE_RESIST_FIRE = 2048;
int HEAL_BLESS_HASTE = 4096; int HEAL_BLESS_HIT_POINTS = 8192;
int HEAL_BLESS_REGENERATE = 16384; int HEAL_BLESS_SPELL_ABSORPTION_ONE = 32768;
int HEAL_BLESS_SPELL_ABSORPTION_TWO = 65536; int HEAL_BLESS_SPELL_ABSORPTION_THREE = 131072;
// Experience Values
int XP_ATTO = 1; int XP_FEMTO = 2;
int XP_PICO = 3; int XP_NANO = 4;
int XP_MICRO = 5; int XP_MILLI = 6;
int XP_CENTI = 7; int XP_DECI = 9;
int XP_DECA = 10; int XP_HECTO = 12;
int XP_KILO = 13; int XP_MEGA = 15;
int XP_GIGA = 17; int XP_TERA = 20;
int XP_PETA = 25; int XP_EXA = 30;
int XP_ULTRA = 40;
// Item Category Values
int MAX_AMMO_LOW = 12; int MAX_AMMO_MEDIUM = 16;
int MAX_AMMO_HIGH = 22; int MAX_ARMOR_LOW = 87;
int MAX_ARMOR_MEDIUM = 4; int MAX_ARMOR_HIGH = 5;
int MAX_BOOKS_LOW = 7; int MAX_BOOKS_MEDIUM = 6;
int MAX_BOOKS_HIGH = 1; int MAX_CLOTHING_LOW = 4;
int MAX_CLOTHING_MEDIUM = 5; int MAX_CLOTHING_HIGH = 9;
int MAX_JEWELRY_LOW = 5; int MAX_JEWELRY_MEDIUM = 6;
int MAX_JEWELRY_HIGH = 11; int MAX_MISCELLANEOUS = 10;
int MAX_ALCHEM_NOTES = 14; int MAX_POISONS = 53;
int MAX_POTIONS_LOW = 28; int MAX_POTIONS_MEDIUM = 10;
int MAX_POTIONS_HIGH = 18; int MAX_NWN_POTIONS = 23;
int MAX_REAGENTS_LOW = 30; int MAX_REAGENTS_MEDIUM = 12;
int MAX_REAGENTS_HIGH = 28; int MAX_RECIPES_LOW = 30;
int MAX_RECIPES_MEDIUM = 12; int MAX_RECIPES_HIGH = 28;
int MAX_SCROLLS_LOW = 11; int MAX_SCROLLS_MEDIUM = 2;
int MAX_SCROLLS_HIGH = 1; int MAX_WEAPONS_LOW = 99;
int MAX_WEAPONS_MEDIUM = 34; int MAX_WEAPONS_HIGH = 14;
// Object Quality Identifiers
float QUALITY_HIGH = 37.0; float QUALITY_LOW = 19.0;
float QUALITY_MEDIUM = 26.0;
// Object Types
int OBJECT_TYPE_BARREL = 1; int OBJECT_TYPE_BOOKS = 2;
int OBJECT_TYPE_CHEST = 3; int OBJECT_TYPE_CRATE = 4;
int OBJECT_TYPE_GOLD_PILE = 5; int OBJECT_TYPE_LOOTBAG = 6;
int OBJECT_TYPE_MISCELLANEOUS = 7; int OBJECT_TYPE_WIZARD_CABINET = 8;
// Exchange Rates
float EXCHANGE_RATE_COPPER = 100.0;
float EXCHANGE_RATE_SILVER = 10.0;
float EXCHANGE_RATE_ELECTRUM = 2.0;
float EXCHANGE_RATE_GOLD = 1.0;
float EXCHANGE_RATE_PLATINUM = 0.2;
// Food System
int DEBUG_STATE = FALSE; // Switch to FALSE if Debug information is not wanted
int TERRAIN_MODIFIER_INFRA = 0;
int TERRAIN_MODIFIER_EASY = 1;
int TERRAIN_MODIFIER_NORMAL = 2;
int TERRAIN_MODIFIER_HARD = 3;
int TERRAIN_MODIFIER_ULTRA = 4;
int TERRAIN_TYPE_FOREST = 1;
int TERRAIN_TYPE_GRASSLAND = 2;
int TERRAIN_TYPE_CITY = 3;
int TERRAIN_TYPE_CAVERN = 4;
int TERRAIN_TYPE_HILLS = 5;
int TERRAIN_TYPE_DUNGEON = 6;
////////////////////////////////////////////////////////////////////////////////
// Structures //////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Functions List //////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void ActionForceMoveToRandomLocation(object oSubject, float fXDist, float fYDist, int iRun=TRUE);
void AI_Movement_FlockTypeA(int iFlockRoll, float fDist=1.0, object oObject=OBJECT_SELF);
void AI_Movement_FlockTypeB(float fMinDist=1.5, float fMaxDist=2.2, int iMoveToGroup=TRUE, object oObject=OBJECT_SELF);
void Area_CheckForPlayers(object oArea, float fFrequency=18.0);
string Array_AddElement(string strElement, string strArray);
string Array_GetElement(int iElement, string strArray);
int Array_GetTotalElements(string strArray);
void Array_ObjectAddElement(object oObject, string strArrayName, string strElement);
string Array_RemoveElement(int iElement, string strArray);
int BlockCodeExecution(object oTarget, string strBlockFlag, int iReset=FALSE);
int BlockMultiActivation(string strBlockFlag, object oTarget, float fBlockPeriod, string strMessage="Nothing happened!");
void CreateItem(object oPC, string strItemTag, int iGiveBack);
object CreateObjectAtDistance(int nObjectType, object oTarget, string strResRef, float fDist);
void Debug_Report(string strMessage, object oSelf=OBJECT_SELF);
void Doors_ActionCloseDoor(object oDoor);
void Doors_ActionSealDoor(object oDoor, int iNoPC=TRUE);
void Doors_BashOpenCheck(object oObject=OBJECT_SELF);
void Doors_DelayClose(object oDoor, float fDelay);
void Doors_DelaySeal(object oDoor, float fDelay);
int Doors_DoBashDamage(object oDoor);
void Doors_FakeDoorSpeak(object oDoor=OBJECT_SELF);
string Effect_AbsorbAbility(object oAbsorber, object oVictim, int iAbility);
string Effect_AbsorbHitPoints(object oAbsorber, object oVictim);
string Effect_AbsorbRandomAbility(object oAbsorber, object oVictim);
string Effect_AbsorbRandomSkill(object oAbsorber, object oVictim);
string Effect_AbsorbSpellMemory(object oAbsorber, object oVictim);
void Effect_AbsorptionVFX(object oAbsorber, object oVictim);
void Effect_DischargeAbsorbedSpell(object oCaster, int iAbortOnUnknown=FALSE);
void Effect_DischargeMemorizedSpell(object oCaster, int iSpellID);
effect Effect_SetAbilityScore(int iValue, object oTarget, int iAbility);
void Encounter_NightEncounterOnEnter(object oEncounter=OBJECT_SELF);
void Encounter_NightEncounterOnExit(object oEncounter=OBJECT_SELF);
int Experience_GetXPForDC(int iDC);
int Experience_RewardByLevel(int iLvls, int iBaseXP);
void Experience_RewardNearestByDC(int iDC);
void Fire_BurnCreature(object oVictim, int iDamage, int iDamageType=DAMAGE_TYPE_FIRE, int iDC=21);
void Fire_DoBurnCheck(int iDamage, int iDamageType=DAMAGE_TYPE_FIRE, int iDC=21, object oFire=OBJECT_SELF, float fDelay=1.0);
void Fire_DoRandomMotion(int iHungry=FALSE, object oFire=OBJECT_SELF, float fMaxXRange=4.0, float fMaxYRange=4.0, float fXRange=0.15, float fYRange=0.15);
void Fire_Explosion(location lCenter, int iMaxDamage=30, int iMinDamage=6, float fRadius=10.0);
void FoodSys_ClearMessages(object oPC);
void FoodSys_CommentOnWeather(object oPC, float fApparentTemp);
void FoodSys_Drunk_CheckVomit(object oPC, int iMOD, int iBloodAlcohol);
void FoodSys_Drunk_EffectDrunk(object oTarget);
void FoodSys_Drunk_LowerBloodAlcohol(object oTarget);
void FoodSys_Drunk_PlaySoundFemale(object oPC);
void FoodSys_Drunk_PlaySoundMale(object oPC);
void FoodSys_Drunk_SpeakWarning(object oPC);
void FoodSys_Drunk_VomitEffect(object oPC, int iMOD);
int FoodSys_GetActiveState(object oArea);
int FoodSys_GetAmbientTemperature(object oArea);
int FoodSys_GetConAdjPCClothing(object oPC);
int FoodSys_GetConAdjSeason(); // Consumption Adjustment for Season
int FoodSys_GetConAdjTerrainModifier(object oArea);
int FoodSys_GetConAdjTerrainType(object oArea);
int FoodSys_GetConAdjTOD();
int FoodSys_GetFoodUse();
int FoodSys_GetHeatRetension(object oPC);
int FoodSys_GetMinimumFoodRate(object oArea);
int FoodSys_GetStarvationLimit(object oPC);
int FoodSys_GetStarveCounter(object oPC);
int FoodSys_GetTemperatureFlux(object oArea);
int FoodSys_GetTerrainModifier(object oArea);
int FoodSys_GetTerrainType(object oArea);
void FoodSys_HeartBeat(object oPC, float fHeartBeat);
void FoodSys_Initialize(object oTarget, float fHeartBeat=60.0, int iBaseFoodUse=6);
void FoodSys_SendMessage(object oPC, string strMessageID, string strMessage);
void FoodSys_SetFoodUse(int iBaseFoodUse);
void FoodSys_SetStarveCounter(object oPC, int iValue=0);
void FoodSys_SetTerrainType(int iTerrainType=3, int iAmbientTemperature=25, int iTerrainModifier=2, int iTemperatureFlux=10, int iMinFoodRate=1, int iActive=TRUE);
void FoodSys_ShowTerrainVars(object oPC);
int FoodSys_TakeSupplies(object oPC, int iQuantity);
int GetHighestLevel(object oSubject);
int GetLevel(object oSubject);
int GetLowestLevel(object oSubject);
int GetMiddleLevel(object oSubject);
int GetNumberOfNegativeEffects(object oTarget);
int GetSeason();
string GetSeasonString();
int GetTOD();
string GetTODString();
void Heal_BeginHealing(object oPC, int iRemoveEffects = 31, int iAddBlessings = 0, int iGiveHenchPotions = TRUE, string strPotionTag = "NW_IT_MPOTION003", int iHealHench = TRUE, int iHealFamil = TRUE, int iHealOthers = TRUE, int iMinHP = 0, int iMaxHP = 0);
void Heal_BlessingEffect(object oPC, int iAddBlessings);
void Heal_RestoreEffect(object oTarget, int iRemoveEffects, int iMinHP, int iMaxHP, int iDoHeal=TRUE);
int Inventory_CountItem(object oTarget, string strItemTag);
void Inventory_DestroyAllItems(object oTarget=OBJECT_SELF);
int Inventory_ItemCount(object oTarget);
void Inventory_RemoveItem(object oTarget, string strItemTag);
int Inventory_RemoveItemNumber(object oTarget, string strItemTag, int iTake=1);
void Inventory_RemoveStackedItemQuantity(object oTarget, string strItemTag, int iRemoveNum);
void Lightning_DamageCreatures(location lStrike, int iDifficulty=15, float fRange=10.0, int iMaxDamage=26, int iMinDamage=6);
void Lightning_DivineFury(object oObject, float fRadius, int iTotalStrikes, int iDoesDamage=FALSE, float fDelayWindow=14.0);
void Lightning_DoStrike(int iLightning, object oSelf);
void Lightning_Initialize(object oSelf, int iXSize, int iYSize, int iLightningDC=15, float fLightningRange=6.0, int iLightningRandFreq=20, int iLightningConstFreq=9);
location Location_AddVectors(vector vCurrentPosition, float fVectorX, float fVectorY, float fVectorZ, object oObject=OBJECT_SELF);
location Location_GetLocationNearObject(object oObject, float fXRange=1.0, float fYRange=1.0);
location Location_GetLocationNearWaypoint(string strObjectTag, float fXRange=1.0, float fYRange=1.0);
location Location_GetRandomLocation(object oArea, int iXSize, int iYSize, int iXMargin=0, int iYMargin=0, int iHeight=0);
float Math_FitFloatToBoundaries(float fValue, float fPoint, float fRange);
float Math_RandomFloat(float fMaxValue);
float Math_RandomFloatSign();
int Math_RandomIntSign();
void Object_AddProperty(int iEffect, int iParamA=1, int iParamB=4, int iParamC=1, object oObject=OBJECT_SELF);
void Object_CheckDamageVsResistances(int iDown, int iSpecDam, int iDReduction, float fDur, int iResistances, object oSelf=OBJECT_SELF);
int Object_CheckInventoryForItem(string strResource, object oObject);
int Object_CountSameAtLocation(object oObject, location lCenter, float fSize=5.0, int iShape=SHAPE_SPHERE);
void Object_CreateObject(int nObjectType, string sTemplate, location lLocation, int bUseAppearAnimation=FALSE);
void Object_EnableRegenerators(object oObject=OBJECT_SELF);
void Object_ForceEndCombat(object oAttacker);
void Object_InitializeInventory(string strItemTag, int iStack=4, object oObject=OBJECT_SELF);
int Object_IsClassAndRace(int iClassType, object oObject=OBJECT_SELF, int iExclusive=FALSE, string strName="");
int Object_IsPCNear(object oObject=OBJECT_SELF, float fDist=15.0);
int Object_IsPrey(object oObject=OBJECT_SELF);
object Object_GetClosestSameInLocationShape(location lCenter, object oObject=OBJECT_SELF, float fSize=5.0, int iShape=SHAPE_SPHERE);
vector Object_GetDensityCenter(location lCenter, object oObject=OBJECT_SELF, float fSize=5.0, int iShape=SHAPE_SPHERE);
int Object_GetDifficulty(object oObject, int iReturnRaw=FALSE);
object Object_GetNearestSame(object oObject=OBJECT_SELF, float fDistance=15.0);
int Object_GetType(object oObject);
void Object_GiveUniqueAbilities(object oObject=OBJECT_SELF);
void Object_MakeAnimalsCommon(object oObject=OBJECT_SELF);
void Object_RespawnContents(string strResource, int iStack=1, object oObject=OBJECT_SELF);
void Object_RespawnSpecialInventory(object oObject, string strInventoryArray);
int Object_SelectMemorizedSpell(object oCaster);
void Object_SpawnMonsterFromPC(object oPlayer);
string Object_StoreSpecialInventory(object oObject=OBJECT_SELF);
void Placeable_Altar_ShrineOfPtah(object oCloser, object oObject=OBJECT_SELF);
int Placeable_Lever_GetState(object oLever);
int Placeable_Lever_SwitchState(object oLever);
void Player_HeartBeat_Raise(object oPC, float fHeartBeat=6.0, int iReviveDC = 25);
void Player_HeartBeat_Revive(object oPC, float fHeartBeat=6.0, int iReviveDC = 25);
void Player_RestoreToLife(object oPC);
string Recall_GetDefaultPortalTag();
string Recall_GetPortalTag(object oArea=OBJECT_SELF);
void Recall_InitializeArea(string strRecallPortal, string strDefaultPortal="NULL", object oArea=OBJECT_SELF);
int Recall_IsDefaultPortalActive();
int Recall_IsDefaultPortalBlocked(object oArea=OBJECT_SELF);
int Recall_IsAreaActive(object oArea=OBJECT_SELF);
int Recall_IsRecall();
void Recall_SetAreaBlocked(int iBlockValue=TRUE, object oArea=OBJECT_SELF);
void Recall_SetDefaultPortal(string strDefaultPortal);
void Respawn_ByResrefWithDelay(object oObject, float fDelay, string strInventoryArray="");
void Respawn_DoRespawn(int iType, string strResRef, location lLoc, string strInventoryArray="");
int RollChance(int iPercent);
int Spell_IsOffensive(int iSpellID);
string String_AddDigits(string strItemTag, int iValue, int iDigits=2);
string String_GetAbilityText(int iAbility);
int String_IsSubString(string strOriginal, string strFindThis);
void Sunlight_CheckForSunriseInArea(object oArea, float fHeartBeat=18.0);
void Sunlight_CheckForSunlightOnUndead(object oUndead);
void Sunlight_DamageUndead(object oUndead, int iMaxDamage=12, int iMinDamage=6, float fFrequency=2.0, int iRepeats=20);
void Sunlight_DestroyUndead(object oUndead);
void Sunlight_DestroyUndeadInArea(object oArea);
void TorchLight_Heartbeat(object oPC, float fHeartBeat, float fBurnLimit, float fBurnRate);
void TorchLight_Initialize(object oPC, float fHeartBeat=6.0, float fBurnLimit=2880.0, float fBurnRate=6.0);
int TorchLight_IsHoldingTorch(object oPC);
int Treasure_Death_CreateMeat(int iProb=45, int iMax=3);
int Treasure_Death_DecideOnBodyParts(int nPercentChance, int nQuant, string strTemplate, object oMonster);
void Treasure_Death_DecideOnMeat(object oMonster);
void Treasure_Death_PlaceBodyParts(object oMonster=OBJECT_SELF);
int Treasure_ExchangeFundsForGold(object oPC, object oItem, float fExchangeRate);
void Treasure_GenerateBook(object oContainer, object oLastOpener, int iQuality);
void Treasure_GenerateCustomTreasure(object oContainer, int iWealth=0);
object Treasure_GenerateMiscItem(object oContainer);
void Treasure_GenerateNWNBook(object oContainer, object oLastOpener);
object Treasure_GeneratePoisonPotion(object oContainer);
void Treasure_GeneratePotion(object oContainer, object oAdventurer, int iQuality);
void Treasure_GenerateReagent(object oContainer, int iQuality);
void Treasure_GenerateTreasure(object oObject, object oLastOpener, int iQuality, int iType);
void Treasure_GenerateValuable(object oContainer, object oAdventurer, int iQuality);
void Treasure_GenerateWizardSupplies(object oContainer, object oAdventurer, int iQuality);
void Treasure_GoldForClassAdjust(object oTarget);
int Treasure_RollCoins(object oPC, object oItem, float fExchangeRate);
void Treasure_StockContainerByDCAndRespawn(object oObject);
void Weapon_FireBallista(object oWeapon, object oPC, int iPower=6);
////////////////////////////////////////////////////////////////////////////////
// Function Definitions ////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Forces the the action subject to run to a random location, the distance of which is
// specified by the fXDist and fYDist parameters. The object will not move if the location
// generated is invalid or can't be reached.
void ActionForceMoveToRandomLocation(object oSubject, float fXDist, float fYDist, int iRun=TRUE) {
vector vCurrentPosition = GetPosition(oSubject);
int iDMOD = GetAbilityModifier(ABILITY_DEXTERITY, oSubject);
int iCMOD = GetAbilityModifier(ABILITY_CONSTITUTION, oSubject);
float fRunDistanceX = fXDist + IntToFloat(Random(iDMOD)) + IntToFloat(Random(iCMOD));
float fRunDistanceY = fYDist + IntToFloat(Random(iDMOD)) + IntToFloat(Random(iCMOD));
float fDirectionX = 1.0;
float fDirectionY = 1.0;
if (Random(100) < 50) fDirectionX = -1.0;
if (Random(100) < 50) fDirectionY = -1.0;
float fVX = vCurrentPosition.x + (fDirectionX * fRunDistanceX);
float fVY = vCurrentPosition.y + (fDirectionY * fRunDistanceY);
vector vNewPosition = Vector(fVX, fVY, vCurrentPosition.z);
location lNewLocation = Location(GetArea(oSubject), vNewPosition, IntToFloat(Random(180)));
AssignCommand(oSubject, ClearAllActions());
AssignCommand(oSubject, ActionForceMoveToLocation(lNewLocation, iRun));
}
// Flocking algorithm that clusters creatures of the same species. Cohesion of the cluster is
// determined by the Charisma and Wisdom of the animals involved.
void AI_Movement_FlockTypeA(int iFlockRoll, float fDist=1.0, object oObject=OBJECT_SELF) {
// see if we flock to others
object oSame = Object_GetNearestSame();
if (GetIsObjectValid(oSame)) {
float fDist = GetDistanceToObject(oSame);
int iABILS = GetAbilityScore(OBJECT_SELF, ABILITY_CHARISMA) + GetAbilityScore(OBJECT_SELF, ABILITY_WISDOM);
if (iFlockRoll <= iABILS) {
ActionForceMoveToObject(oSame, FALSE, fDist);
}
}
}
// Keeps creatures a minimum of fMinDist apart while moving them to the highest concentration of nearby
// similair creatures
void AI_Movement_FlockTypeB(float fMinDist=1.5, float fMaxDist=2.2, int iMoveToGroup=TRUE, object oObject=OBJECT_SELF) {
// Keep a minimum distance from similair objects
object oNearest = Object_GetNearestSame();
if (GetDistanceBetween(oNearest, oObject) < fMinDist) {
ActionMoveAwayFromObject(oNearest, FALSE, fMaxDist);
return;
}
// Move toward the highest concentration of similair objects
vector vPosition = Object_GetDensityCenter(GetLocation(oObject), oObject, 15.0);
location lNewLocation = Location(GetArea(oObject), vPosition, IntToFloat(Random(180)));
ActionForceMoveToLocation(lNewLocation, Random(2));
return;
}
// This function is called from the Master OnEnter file for a given area. It schedules itself
// to run every 18 seconds (by default) and examines the area for PC's who are NOT DM's - and
// sets a variable ("iPlayerPresent") on the area to TRUE if PC's were found. Once a PC is found,
// the function aborts searching through objects to save some effort.
void Area_CheckForPlayers(object oArea, float fFrequency=18.0) {
int iPlayerPresent = FALSE;
object oObject = GetFirstObjectInArea(oArea);
// Search for a PC, exclude the DM
while(GetIsObjectValid(oObject)) {
if (GetIsPC(oObject) && !GetIsDM(oObject)) {
iPlayerPresent = TRUE;
break;
}
oObject = GetNextObjectInArea(oArea);
}
// Set a variable on the area indicating if a Player was found
SetLocalInt(oArea, "iPlayerPresent", iPlayerPresent);
// Continue checking
AssignCommand(oArea, DelayCommand(fFrequency, Area_CheckForPlayers(oArea, fFrequency)));
}
// Add an element!
string Array_AddElement(string strElement, string strArray) {
string strWork = strArray + strElement + "|";
return strWork;
}
// Access the element number passed
string Array_GetElement(int iElement, string strArray) {
int iDelimeter;
int iLength;
string strWork;
int iIndex;
// Locate element
for (iIndex=0; iIndex<iElement; iIndex++) {
iDelimeter = FindSubString(strArray, "|");
iLength = GetStringLength(strArray);
strWork = GetSubString(strArray, iDelimeter+1, iLength-iDelimeter+1);
strArray = strWork;
}
// Shave off data passed data we want
iDelimeter = FindSubString(strArray, "|");
return(GetStringLeft(strArray, iDelimeter));
}
// Counts the number of individual strings in the array by their delimiters '|'
int Array_GetTotalElements(string strArray) {
int iDelimeter;
int iLength;
string strWork;
int iCount=0;
while (GetStringLength(strArray) > 0) {
iCount++;
iDelimeter = FindSubString(strArray, "|");
iLength = GetStringLength(strArray);
strWork = GetSubString(strArray, iDelimeter+1, iLength-iDelimeter+1);
strArray = strWork;
}
return iCount;
}
void Array_ObjectAddElement(object oObject, string strArrayName, string strElement) {
string strArray = GetLocalString(oObject, strArrayName);
strArray = Array_AddElement(strElement, strArray);
SetLocalString(oObject, strArrayName, strArray);
}
string Array_RemoveElement(int iElement, string strArray) {
int iIndex;
string strFinished;
int iTotalElements = Array_GetTotalElements(strArray);
// Copy all elements up to the element we want
for (iIndex=0; iIndex<iElement; iIndex++) strFinished += Array_GetElement(iIndex, strArray) + "|";
// Copy all remaining elements, skipping the one we're removing
for (iIndex=iElement+1; iIndex<iTotalElements; iIndex++) strFinished += Array_GetElement(iIndex, strArray) + "|";
// Return the result
return strFinished;
}
// This function is used to ensure that some piece of code is executed only once by testing
// a flag. If the code has been run, the return value is TRUE.
// The block condition can later be reset by calling the function again with the last
// parameter as TRUE.
int BlockCodeExecution(object oTarget, string strBlockFlag, int iReset=FALSE) {
// Check too see if we're clearing the blocking flag on the target object
if (iReset == TRUE) {
SetLocalInt(oTarget, strBlockFlag, FALSE);
return FALSE;
}
// Check too see if the block flag has already been set. Return TRUE if it has been.
if (GetLocalInt(oTarget, strBlockFlag)) return TRUE;
// If the block flag was not set, then set it and return FALSE.
SetLocalInt(oTarget, strBlockFlag, TRUE);
return FALSE;
}
// Used to Block a section of code from running if it's already been activated and is still active. Good
// for potions who's abilities you don't want a PC to be able to accumulate. After fBlockPeriod expires,
// the code can run again.
int BlockMultiActivation(string strBlockFlag, object oTarget, float fBlockPeriod, string strMessage="Nothing happened!") {
if (GetLocalInt(oTarget, strBlockFlag)) { FloatingTextStringOnCreature(strMessage, oTarget); return TRUE; }
SetLocalInt(oTarget, strBlockFlag, TRUE);
AssignCommand(oTarget, DelayCommand(fBlockPeriod, SetLocalInt(oTarget, strBlockFlag, FALSE)));
return FALSE;
}
// Used in combination with a DelayCommand() call to create an item on an object
// after a brief wait.
void CreateItem(object oPC, string strItemTag, int iGiveBack) {
CreateItemOnObject(strItemTag, oPC, iGiveBack);
}
// Creates an object relative to the location of another object (such as a PC)
object CreateObjectAtDistance(int nObjectType, object oTarget, string strResRef, float fDist) {
float fFacing = GetFacing(oTarget); // Get the angle the PC is facing
if (fFacing >= 360.0) fFacing = 720.0 - fFacing; // Correct for the GetFacing bug
if (fFacing < 0.0) fFacing += (360.0);
vector vCreate = AngleToVector(fFacing); // Create a vector from the angle the PC is facing
vCreate = VectorNormalize(vCreate); // Normalize the vector to a unit vector
vector vPCPos = GetPosition(oTarget); // Get the vector position of the PC
vCreate.x = (vCreate.x * fDist) + vPCPos.x; // Create the vector position for the new object
vCreate.y = (vCreate.y * fDist) + vPCPos.y;
vCreate.z = vPCPos.z;
location lCreate = Location(GetArea(oTarget), vCreate, fFacing); // Create a location for the new object
object oReturn = CreateObject(nObjectType, strResRef, lCreate); // Create the object
return oReturn;
}
// Output a string to the nearest PC of the object executing the function
void Debug_Report(string strMessage, object oSelf=OBJECT_SELF) {
// NOT sure if it works!!!
/* string strReport = GetTag(oSelf);
strReport += ": " + strMessage;
object oPC = GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR, PLAYER_CHAR_IS_PC);
SendMessageToPC(oPC, strReport);
*/
}
// Performs the close door action, but only if no PC is nearby.
void Doors_ActionCloseDoor(object oDoor) {
if (!Object_IsPCNear(oDoor)) ActionCloseDoor(oDoor);
else AssignCommand(oDoor, DelayCommand(20.0, Doors_ActionCloseDoor(oDoor)));
}
// Performs the seal door action, but only if no PC is nearby (when iNoPC is TRUE). If iNoPC is
// FALSE, then the door will close automatically.
void Doors_ActionSealDoor(object oDoor, int iNoPC=TRUE) {
if (iNoPC) {
if (!Object_IsPCNear(oDoor)) {
ActionCloseDoor(oDoor);
SetLocked(oDoor, TRUE);
}
else AssignCommand(oDoor, DelayCommand(30.0, Doors_ActionCloseDoor(oDoor)));
}
else {
ActionCloseDoor(oDoor);
SetLocked(oDoor, TRUE);
}
}
// Checks too see if damage done to the door causes it too open. Uses the objects
// will save to determine the number of 'Hitpoints' of damage that can be taken before
// opening.
void Doors_BashOpenCheck(object oDoor=OBJECT_SELF) {
int iFortitude = GetFortitudeSavingThrow(oDoor); // It gets a save for half damage
int iHardness = GetReflexSavingThrow(oDoor); // Use the Reflex Save as the 'Hardness' of the door!
int iOpenDamage = GetWillSavingThrow(oDoor); // Max damage door can take before opening
int iDoorDamage = GetLocalInt(oDoor, "iDoorDamage");
int iBash = Doors_DoBashDamage(oDoor) - iHardness;
if (iBash < 0) iBash = 0;
object oAttacker = GetLastAttacker(oDoor);
// Check if the door only takes half damage
int iDC = GetHighestLevel(oAttacker) + GetAbilityScore(oAttacker, ABILITY_STRENGTH) + GetAbilityModifier(ABILITY_STRENGTH, oAttacker);
int iRoll = d20() + iFortitude;
if (iRoll >= iDC) iBash /= 2; // Success
int iTotalDamage = iDoorDamage + iBash;
// Float the damage on the PC
string strBashText = "I did " + IntToString(iBash) + " damage!";
FloatingTextStringOnCreature(strBashText, oAttacker);
// Check if the door actually opens
if (iTotalDamage >= iOpenDamage) {
// Effects
effect eFX2 = EffectVisualEffect(VFX_FNF_SCREEN_BUMP);
effect eFX3 = EffectVisualEffect(VFX_COM_SPARKS_PARRY);
int iSound = Random(3) + 1;
string strSound = "as_cv_woodbreak" + IntToString(iSound);
PlaySound(strSound);
DelayCommand(0.3, ApplyEffectToObject(DURATION_TYPE_INSTANT, eFX3, oDoor));
DelayCommand(0.5, ApplyEffectToObject(DURATION_TYPE_INSTANT, eFX2, oDoor));
// Stop the PC from attacking
Object_ForceEndCombat(oAttacker);
// Open the door
ActionUnlockObject(oDoor);
ActionOpenDoor(oDoor);
iTotalDamage = 0; // Reset damage counter
// Set the door to close and lock again after a delay
Doors_DelaySeal(oDoor, 600.0 + IntToFloat(Random(300))); // 10.0
}
SetLocalInt(oDoor, "iDoorDamage", iTotalDamage);
}
// Closes a door object on a delay
void Doors_DelayClose(object oDoor, float fDelay) {
DelayCommand(fDelay, Doors_ActionCloseDoor(oDoor));
}
// Closes and Locks a door object on a delay
void Doors_DelaySeal(object oDoor, float fDelay) {
DelayCommand(fDelay, Doors_ActionSealDoor(oDoor));
}
// Calculate an amount of damage that the Attack of the Door is likely to do
int Doors_DoBashDamage(object oDoor){
object oAttacker = GetLastAttacker();
object oWeapon = GetLastWeaponUsed(oAttacker);
int iLVL = GetLevel(oAttacker);
int iSTR = GetAbilityScore(oAttacker, ABILITY_STRENGTH);
int iSTRMod = GetAbilityModifier(ABILITY_STRENGTH, oAttacker);
int iBaseItem = GetBaseItemType(oWeapon);
int iDamage;
int iDamageModifier;
if (iLVL < 9) iDamageModifier = 1;
else if (iLVL < 16) iDamageModifier = 2;
else if (iLVL < 21) iDamageModifier = 3;
if (iBaseItem == BASE_ITEM_BASTARDSWORD) iDamage = d12(iDamageModifier) + Random(iSTR);
else if (iBaseItem == BASE_ITEM_BATTLEAXE) iDamage = d10(iDamageModifier) + Random(iSTR);
else if (iBaseItem == BASE_ITEM_DOUBLEAXE) iDamage = d6(2+iDamageModifier) + Random(iSTR);
else if (iBaseItem == BASE_ITEM_GREATAXE) iDamage = d6(2+iDamageModifier) + Random(iSTR);
else if (iBaseItem == BASE_ITEM_GREATSWORD) iDamage = d6(2+iDamageModifier) + Random(iSTR);
else if (iBaseItem == BASE_ITEM_HALBERD) iDamage = d12(iDamageModifier) + Random(iSTR);
else if (iBaseItem == BASE_ITEM_HEAVYFLAIL) iDamage = d10(iDamageModifier) + Random(iSTR);
else if (iBaseItem == BASE_ITEM_WARHAMMER) iDamage = d10(iDamageModifier) + Random(iSTR);
else if (iBaseItem == BASE_ITEM_LONGBOW) iDamage = d6(iDamageModifier);
else if (iBaseItem == BASE_ITEM_LIGHTCROSSBOW) iDamage = d4(iDamageModifier);
else if (iBaseItem == BASE_ITEM_SHORTBOW) iDamage = d4(iDamageModifier);
else if (iBaseItem == BASE_ITEM_HEAVYCROSSBOW) iDamage = d6(iDamageModifier);
else if (iBaseItem == BASE_ITEM_SHURIKEN) iDamage = d3(iDamageModifier);
else if (iBaseItem == BASE_ITEM_INVALID || iBaseItem == BASE_ITEM_GLOVES || iBaseItem == BASE_ITEM_BRACER) iDamage = d4();
else iDamage = d4(iDamageModifier) + Random(iSTR);
// Calculate final damage
iDamage += iSTRMod;
return(iDamage);
}
// This function
void Doors_FakeDoorSpeak(object oDoor=OBJECT_SELF){
int iRand = Random(7);
string strFakeSpeak;
if (iRand == 0) strFakeSpeak = "The door appears jammed - perhaps there's another way in.";
if (iRand == 1) strFakeSpeak = "The door is stuck.";
if (iRand == 2) strFakeSpeak = "The door seems blocked from the other side.";
if (iRand == 3) strFakeSpeak = "It just will not budge!";
if (iRand == 4) strFakeSpeak = "Something tightly blocks the doorway from the other side.";
if (iRand == 5) strFakeSpeak = "The door appears barred from within.";
if (iRand == 6) strFakeSpeak = "The door is heavily damaged and will not open.";
if (iRand == 7) strFakeSpeak = "A baracade has been constructed on the inside. There doesn't appear to be anyway in...";
AssignCommand(oDoor, SpeakString(strFakeSpeak));
}
string Effect_AbsorbAbility(object oAbsorber, object oVictim, int iAbility) {
string strAbility = String_GetAbilityText(iAbility);
string strReturn;
// How much?
int iDrain = d3();
// Only do the drain if the ability selected can support it...
int iScore = GetAbilityScore(oVictim, iAbility);
int iDiff = iScore - 3;
if (iDiff == 0) return "";
if (iDrain > iDiff) iDrain = iDiff;
// Absorb the Ability
effect eDrain = SupernaturalEffect(EffectAbilityDecrease(iAbility, iDrain));
effect eGain = SupernaturalEffect(EffectAbilityIncrease(iAbility, iDrain));
ApplyEffectToObject(DURATION_TYPE_PERMANENT, eDrain, oVictim);
ApplyEffectToObject(DURATION_TYPE_PERMANENT, eGain, oAbsorber);
Effect_AbsorptionVFX(oAbsorber, oVictim);
// Return string indicating result
strReturn = "absorbed " + IntToString(iDrain) + " points of " + strAbility + " from " + GetName(oVictim);
return strReturn;
}
string Effect_AbsorbHitPoints(object oAbsorber, object oVictim) {
// How many hitpoints?
int iCHP = GetCurrentHitPoints(oVictim);
int iHP = d3(GetHitDice(oVictim));
if (iHP >= iCHP) iHP = iCHP / 2; // Only take MAX 50% of the PC's HP!!!
if (iHP < 1) return "";
// Absorb the Hitpoints
effect eDrain = EffectDamage(iHP, DAMAGE_TYPE_MAGICAL, DAMAGE_POWER_PLUS_FIVE);
effect eGain = EffectTemporaryHitpoints(iHP);
ApplyEffectToObject(DURATION_TYPE_PERMANENT, eDrain, oVictim);
ApplyEffectToObject(DURATION_TYPE_PERMANENT, eGain, oAbsorber);
Effect_AbsorptionVFX(oAbsorber, oVictim);
// Return string indicating result
string strReturn = "absorbed " + IntToString(iHP) + " points of life from " + GetName(oVictim);
return strReturn;
}
string Effect_AbsorbRandomAbility(object oAbsorber, object oVictim) {
// What ability?
int iAbility;
int iRand = Random(6);
string strAbility;
string strReturn;
if (iRand == 0) strReturn = Effect_AbsorbAbility(oAbsorber, oVictim, ABILITY_CHARISMA);
if (iRand == 1) strReturn = Effect_AbsorbAbility(oAbsorber, oVictim, ABILITY_CONSTITUTION);
if (iRand == 2) strReturn = Effect_AbsorbAbility(oAbsorber, oVictim, ABILITY_DEXTERITY);
if (iRand == 3) strReturn = Effect_AbsorbAbility(oAbsorber, oVictim, ABILITY_INTELLIGENCE);
if (iRand == 4) strReturn = Effect_AbsorbAbility(oAbsorber, oVictim, ABILITY_STRENGTH);
if (iRand == 5) strReturn = Effect_AbsorbAbility(oAbsorber, oVictim, ABILITY_WISDOM);
return strReturn;
}
string Effect_AbsorbRandomSavingThrow(object oAbsorber, object oVictim) {
// What saving throw?
int iRand = Random(19);
int iSaveType;
string strSaveType;
if (iRand == 0) { strSaveType = "save vs acids"; iSaveType = SAVING_THROW_TYPE_ACID; }
if (iRand == 1) { strSaveType = "save vs chaos"; iSaveType = SAVING_THROW_TYPE_CHAOS; }
if (iRand == 2) { strSaveType = "save vs cold"; iSaveType = SAVING_THROW_TYPE_COLD; }
if (iRand == 3) { strSaveType = "save vs death"; iSaveType = SAVING_THROW_TYPE_DEATH; }
if (iRand == 4) { strSaveType = "save vs diseases"; iSaveType = SAVING_THROW_TYPE_DISEASE; }
if (iRand == 5) { strSaveType = "save vs divine"; iSaveType = SAVING_THROW_TYPE_DIVINE; }
if (iRand == 6) { strSaveType = "save vs electricity"; iSaveType = SAVING_THROW_TYPE_ELECTRICITY; }
if (iRand == 7) { strSaveType = "save vs evils"; iSaveType = SAVING_THROW_TYPE_EVIL; }
if (iRand == 8) { strSaveType = "save vs fear"; iSaveType = SAVING_THROW_TYPE_FEAR; }
if (iRand == 9) { strSaveType = "save vs fire"; iSaveType = SAVING_THROW_TYPE_FIRE; }
if (iRand == 10) { strSaveType = "save vs good"; iSaveType = SAVING_THROW_TYPE_GOOD; }
if (iRand == 11) { strSaveType = "save vs law"; iSaveType = SAVING_THROW_TYPE_LAW; }
if (iRand == 12) { strSaveType = "save vs mind spells"; iSaveType = SAVING_THROW_TYPE_MIND_SPELLS; }
if (iRand == 13) { strSaveType = "save vs negative"; iSaveType = SAVING_THROW_TYPE_NEGATIVE; }
if (iRand == 14) { strSaveType = "save vs poisons"; iSaveType = SAVING_THROW_TYPE_POISON; }
if (iRand == 15) { strSaveType = "save vs positive"; iSaveType = SAVING_THROW_TYPE_POSITIVE; }
if (iRand == 16) { strSaveType = "save vs sonics"; iSaveType = SAVING_THROW_TYPE_SONIC; }
if (iRand == 17) { strSaveType = "save vs spells"; iSaveType = SAVING_THROW_TYPE_SPELL; }
if (iRand == 18) { strSaveType = "save vs traps"; iSaveType = SAVING_THROW_TYPE_TRAP; }
// Absorb the save
int iDrain = d3();
effect eDrain = SupernaturalEffect(EffectSavingThrowDecrease(SAVING_THROW_ALL, iDrain, iSaveType));
effect eGain = SupernaturalEffect(EffectSavingThrowIncrease(SAVING_THROW_ALL, iDrain, iSaveType));
ApplyEffectToObject(DURATION_TYPE_PERMANENT, eDrain, oVictim);
ApplyEffectToObject(DURATION_TYPE_PERMANENT, eGain, oAbsorber);
Effect_AbsorptionVFX(oAbsorber, oVictim);
// Return string indicating result
string strReturn = "absorbed " + IntToString(iDrain) + " points of " + strSaveType + " from " + GetName(oVictim);
return strReturn;
}
string Effect_AbsorbRandomSkill(object oAbsorber, object oVictim) {
// What Skill? (Attempt the drain whether the PC has the skill or not)
int iSkill;
int iRand = Random(20);
string strSkill;
if (iRand == 0) { iSkill = SKILL_ANIMAL_EMPATHY; strSkill = "Animal Empathy"; }
if (iRand == 1) { iSkill = SKILL_CONCENTRATION; strSkill = "Concentration"; }
if (iRand == 2) { iSkill = SKILL_DISABLE_TRAP; strSkill = "Disabling Traps"; }
if (iRand == 3) { iSkill = SKILL_DISCIPLINE; strSkill = "Discipline"; }
if (iRand == 4) { iSkill = SKILL_HEAL; strSkill = "Healing"; }
if (iRand == 5) { iSkill = SKILL_HIDE; strSkill = "Hiding"; }
if (iRand == 6) { iSkill = SKILL_LISTEN; strSkill = "Listening"; }
if (iRand == 7) { iSkill = SKILL_LORE; strSkill = "Lore"; }
if (iRand == 8) { iSkill = SKILL_MOVE_SILENTLY; strSkill = "Moving Silently"; }
if (iRand == 9) { iSkill = SKILL_OPEN_LOCK; strSkill = "Opening Locks"; }
if (iRand == 10) { iSkill = SKILL_PARRY; strSkill = "Parry"; }
if (iRand == 11) { iSkill = SKILL_PERFORM; strSkill = "Performing"; }
if (iRand == 12) { iSkill = SKILL_PERSUADE; strSkill = "Persuading"; }
if (iRand == 13) { iSkill = SKILL_PICK_POCKET; strSkill = "Picking Pockets"; }
if (iRand == 14) { iSkill = SKILL_SEARCH; strSkill = "Searching"; }
if (iRand == 15) { iSkill = SKILL_SET_TRAP; strSkill = "Setting Traps"; }
if (iRand == 16) { iSkill = SKILL_SPELLCRAFT; strSkill = "Spellcrafting"; }
if (iRand == 17) { iSkill = SKILL_SPOT; strSkill = "Spotting"; }
if (iRand == 18) { iSkill = SKILL_TAUNT; strSkill = "Taunting"; }
if (iRand == 19) { iSkill = SKILL_USE_MAGIC_DEVICE; strSkill = "Using Magical Devices"; }
// Abort if the PC doesn't have the selected skill
if (GetSkillRank(iSkill, oVictim) < 1) return "";
// How much?
int iDrain = d2(3);
// Proceed with the absorption!!!!!
effect eDrain = SupernaturalEffect(EffectSkillDecrease(iSkill, iDrain));
effect eGain = SupernaturalEffect(EffectSkillIncrease(iSkill, iDrain));
ApplyEffectToObject(DURATION_TYPE_PERMANENT, eDrain, oVictim);
ApplyEffectToObject(DURATION_TYPE_PERMANENT, eGain, oAbsorber);
Effect_AbsorptionVFX(oAbsorber, oVictim);
// Return string indicating result
string strReturn = "absorbed " + IntToString(iDrain) + " points of " + strSkill + " from " + GetName(oVictim);
return strReturn;
}
// Allows the creature to 'take' memorized spells from PC's. Places the IDs for stolen spells
// into an array for later access.
string Effect_AbsorbSpellMemory(object oAbsorber, object oVictim) {
// Select a spell that the PC has
int iSpellID = Object_SelectMemorizedSpell(oVictim);
// Cause the PC to discharge it
Effect_DischargeMemorizedSpell(oVictim, iSpellID);
// Add the Spell ID to memory array for later access
Array_ObjectAddElement(oAbsorber, "strAbsorbedSpells", IntToString(iSpellID));
// Do the FX
Effect_AbsorptionVFX(oAbsorber, oVictim);
// Return string indicating result
string strReturn = "absorbed spell memories from " + GetName(oVictim);
return strReturn;
}
void Effect_AbsorptionVFX(object oAbsorber, object oVictim) {
effect eVFX1 = EffectVisualEffect(VFX_IMP_HEAD_ELECTRICITY);
effect eVFX2 = EffectVisualEffect(VFX_COM_HIT_ELECTRICAL);
ApplyEffectToObject(DURATION_TYPE_INSTANT, eVFX1, oAbsorber);
ApplyEffectToObject(DURATION_TYPE_INSTANT, eVFX2, oVictim);
}
// Causes the target object to randomly select and cast a spell that they've absorbed.
void Effect_DischargeAbsorbedSpell(object oCaster, int iAbortOnUnknown=FALSE) {
// Select a spell to cast
string strAbsorbedSpells = GetLocalString(oCaster, "strAbsorbedSpells");
int iTotalAbsorbed = Array_GetTotalElements(strAbsorbedSpells);
int iElement = Random(iTotalAbsorbed);
int iSpellID = StringToInt(Array_GetElement(iElement, strAbsorbedSpells));
strAbsorbedSpells = Array_RemoveElement(iElement, strAbsorbedSpells); // Remove the element
SetLocalString(oCaster, "strAbsorbedSpells", strAbsorbedSpells);
// Determine if the spell is defensive or offensive
int iSpellType = Spell_IsOffensive(iSpellID); // Returns TRUE if the spell is used to attack with
if ((iSpellType == -1) && iAbortOnUnknown) {
return; // Could not identify the spell, just skip it and return.
}
// If spell was offensive then get last attack target and FIRE! Otherwise, cast at self.
if (iSpellType) { // Offensive
object oLastAttacker = GetLastAttacker(oCaster);
AssignCommand(oCaster, ClearAllActions(TRUE));
AssignCommand(oCaster, ActionCastSpellAtLocation(iSpellID, GetLocation(oLastAttacker), METAMAGIC_ANY, TRUE, PROJECTILE_PATH_TYPE_DEFAULT, TRUE));
}
else { // Defensive
AssignCommand(oCaster, ClearAllActions(TRUE));
AssignCommand(oCaster, ActionCastSpellAtLocation(iSpellID, GetLocation(oCaster), METAMAGIC_ANY, TRUE, PROJECTILE_PATH_TYPE_DEFAULT, TRUE));
}
}
// Causes the target of the function to cast the spell specified, effectively discharging it.
void Effect_DischargeMemorizedSpell(object oCaster, int iSpellID) {
// Get a location away from the caster...
location lLoc = GetLocation(oCaster);
vector vPos = GetPositionFromLocation(lLoc);
float fX = vPos.x;
float fY = vPos.y;
fX += Math_RandomFloatSign() * (5.0 + Math_RandomFloat(8.0));
fY += Math_RandomFloatSign() * (5.0 + Math_RandomFloat(8.0));
vPos.x = fX;
vPos.y = fY;
lLoc = Location(GetArea(oCaster), vPos, 0.0);
// Cast the spell off
AssignCommand(oCaster, ActionCastSpellAtLocation(iSpellID, lLoc, METAMAGIC_ANY, FALSE, PROJECTILE_PATH_TYPE_DEFAULT, TRUE));
}
// Used to set an ability score to a particular value, rather than boosting it up X points.
effect Effect_SetAbilityScore(int iValue, object oTarget, int iAbility) {
int iMOD = iValue - GetAbilityScore(oTarget, iAbility);
if (iMOD < 0) { iMOD = 0; FloatingTextStringOnCreature("Nothing happened!!!", oTarget); }
effect eReal = EffectAbilityIncrease(iAbility, iMOD);
return(eReal);
}
// Placed in the master encounters OnEnter script for an encounter if that encounter
// is meant to be active only at night. Be sure the encounter tag is UNIQUE and that
// the counter is initially set to inactive (advanced tab) and Continuous.
void Encounter_NightEncounterOnEnter(object oEncounter=OBJECT_SELF) {
object oPC = GetEnteringObject();
int nEntries = GetLocalInt(oEncounter, "nEntries");
int nResetValue = GetLocalInt(oEncounter, "nResetValue");
if (GetIsPC(oPC) && GetIsNight()) {
if (nEntries == 0) {
SetEncounterActive(TRUE);
nResetValue = 2 + Random(2);
SetLocalInt(oEncounter, "nResetValue", nResetValue);
}
if (nEntries == nResetValue) nEntries = -1;
SetLocalInt(oEncounter, "nEntries", nEntries);
}
}
// Placed in the master encounters OnExit script for an encounter if that encounter
// is meant to be active only at night. Be sure the encounter tag is UNIQUE and that
// the counter is initially set to inactive (advanced tab) and Continuous.
void Encounter_NightEncounterOnExit(object oEncounter=OBJECT_SELF) {
object oPC = GetExitingObject();
int nEntries = GetLocalInt(oEncounter, "nEntries");
if (GetIsPC(oPC)) {
nEntries++;
SetLocalInt(oEncounter, "nEntries", nEntries);
SetEncounterActive(FALSE);
}
}
// Returns an XP_* constant based on the magnitude of the DC value that was passed in
int Experience_GetXPForDC(int iDC) {
if (iDC >= 0 && iDC < 4) return (XP_ATTO);
if (iDC >= 4 && iDC < 6) return (XP_FEMTO);
if (iDC >= 6 && iDC < 8) return (XP_PICO);
if (iDC >= 8 && iDC < 11) return (XP_NANO);
if (iDC >= 11 && iDC < 13) return (XP_MICRO);
if (iDC >= 13 && iDC < 15) return (XP_MILLI);
if (iDC >= 15 && iDC < 16) return (XP_CENTI);
if (iDC >= 16 && iDC < 17) return (XP_DECI);
if (iDC >= 18 && iDC < 20) return (XP_DECA);
if (iDC >= 20 && iDC < 22) return (XP_HECTO);
if (iDC >= 22 && iDC < 25) return (XP_KILO);
if (iDC >= 25 && iDC < 28) return (XP_MEGA);
if (iDC >= 28 && iDC < 32) return (XP_GIGA);
if (iDC >= 32) return (XP_TERA);
return 1;
}
// Lower level characters get more XP; returns the XP value for a PC of a certain
// level trying to disarm a trap of a certain difficulty
int Experience_RewardByLevel(int iLvls, int iBaseXP) {
int iReward = 15 - iLvls;
if (iReward <= 0) return iBaseXP;
else iReward *= iBaseXP;
if (iReward >= (iBaseXP * 3)) iReward = iBaseXP * 3;
return (iReward);
}
// Calls Experience_RewardByLevel to determine the amount of XP (from the iXPValue) to award
void Experience_RewardNearestByDC(int iDC) {
object oTarget = GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR, PLAYER_CHAR_IS_PC);
int iDCXPValue = Experience_GetXPForDC(iDC);
int iLevels = GetLevel(oTarget);
GiveXPToCreature(oTarget, Experience_RewardByLevel(iLevels, iDC));
}
// Burns the creature specified with the damage & damage type passed. Applies a saving throw for half damage.
void Fire_BurnCreature(object oVictim, int iDamage, int iDamageType=DAMAGE_TYPE_FIRE, int iDC=21) {
// Do a saving throw
int iFortSave = GetFortitudeSavingThrow(oVictim) + d20();
if (iFortSave > iDC) {
SendMessageToPC(oVictim, "Save (roll: " + IntToString(iFortSave) + ") vs. Flame (" + IntToString(iDC) + ") *success*");
iDamage /= 2; // Half Damage
}
else SendMessageToPC(oVictim, "Save (roll: " + IntToString(iFortSave) + ") vs. Flame (" + IntToString(iDC) + ") *failure*");
// Play audio
if (GetIsPC(oVictim)) {
if (!BlockMultiActivation("iBurnScream", oVictim, 7.0, "")) {
if (GENDER_FEMALE == GetGender(oVictim)) AssignCommand(oVictim, PlaySound("as_pl_screamf1"));
if (GENDER_MALE == GetGender(oVictim)) AssignCommand(oVictim, PlaySound("as_pl_screamm1"));
}
}
// Apply Damage
effect eFX = EffectDamage(iDamage, iDamageType, DAMAGE_POWER_ENERGY);
ApplyEffectToObject(DURATION_TYPE_INSTANT, eFX, oVictim);
// Output text
FloatingTextStringOnCreature("Ouch!!", oVictim);
}
// Checks too see if any creatures are too close and applies fire damage if so. Recurses into itself
// to continue checking.
void Fire_DoBurnCheck(int iDamage, int iDamageType=DAMAGE_TYPE_FIRE, int iDC=21, object oFire=OBJECT_SELF, float fDelay=1.0) {
// Check for nearby creatures
object oCreature = GetFirstObjectInShape(SHAPE_SPHERE, 0.50, GetLocation(oFire));
while (GetIsObjectValid(oCreature)) {
// Burn the creature
Fire_BurnCreature(oCreature, iDamage, iDamageType, iDC);
// Get next creature
oCreature = GetNextObjectInShape(SHAPE_SPHERE, 0.50, GetLocation(oFire));
}
// Recurse
AssignCommand(oFire, DelayCommand(fDelay, Fire_DoBurnCheck(iDamage, iDamageType, iDC, oFire)));
}
// Moves the flame. Don't move too far from the closest "FireBowl" audio object. If iHungry is
// true, the flame will chase after nearby creatures.
void Fire_DoRandomMotion(int iHungry=FALSE, object oFire=OBJECT_SELF, float fMaxXRange=4.0, float fMaxYRange=4.0, float fXRange=0.20, float fYRange=0.20) {
float fXPos;
float fYPos;
object oAudio = GetNearestObjectByTag("FireBowl");
vector vLimit = GetPosition(oAudio);
vector vPosition = GetPosition(oFire);
if (!GetIsObjectValid(oAudio)) vLimit = vPosition;
// Is the flame hungry?
if (iHungry) {
// Calculate new fire position that's closer to creature
float fSiteRange = sqrt(pow(fMaxXRange, 2.0) + pow(fMaxYRange, 2.0));
object oCreature = GetFirstObjectInShape(SHAPE_SPHERE, fSiteRange, GetLocation(oFire));
vector vCreature = GetPosition(oCreature);
if (vCreature.x >= vPosition.x) fXPos = vPosition.x + Math_RandomFloat(fXRange);
else if (vCreature.x <= vPosition.x) fXPos = vPosition.x - Math_RandomFloat(fXRange);
if (vCreature.y >= vPosition.y) fYPos = vPosition.y + Math_RandomFloat(fYRange);
else if (vCreature.y <= vPosition.y) fYPos = vPosition.y - Math_RandomFloat(fYRange);
}
else {
// Calculate new fire position
fXPos = vPosition.x + (Math_RandomFloatSign() * Math_RandomFloat(fXRange));
fYPos = vPosition.y + (Math_RandomFloatSign() * Math_RandomFloat(fYRange));
}
// Make sure fXPos is within bounds
fXPos = Math_FitFloatToBoundaries(fXPos, vLimit.x, fMaxXRange);
fYPos = Math_FitFloatToBoundaries(fYPos, vLimit.y, fMaxYRange);
// Create another flame
string strResRef = GetResRef(oFire);
vector vNewPosition = Vector(fXPos, fYPos, vPosition.z);
location lNewLocation = Location(GetArea(oFire), vNewPosition, Math_RandomFloat(180.0));
CreateObject(OBJECT_TYPE_PLACEABLE, strResRef, lNewLocation);
DestroyObject(oFire, Math_RandomFloat(3.0));
}
// Creates an area with explosive damage in it - all objects inside the sphere of effect take fire damage,
// prorated from the center.
void Fire_Explosion(location lCenter, int iMaxDamage=30, int iMinDamage=6, float fRadius=10.0) {
if (fRadius <= 0.0) fRadius = 1.0;
iMaxDamage -= iMinDamage;
if (iMaxDamage <= 1) iMaxDamage=1;
object oObject = GetFirstObjectInShape(SHAPE_SPHERE, fRadius, lCenter);
effect eFX1 = EffectVisualEffect(VFX_IMP_FLAME_M);
effect eFX2 = EffectVisualEffect(VFX_FNF_FIREBALL);
ApplyEffectAtLocation(DURATION_TYPE_INSTANT, eFX2, lCenter);
while (GetIsObjectValid(oObject)) {
location lLoc = GetLocation(oObject);
float fDistance = GetDistanceBetweenLocations(lCenter, lLoc);
int iTotalDamage = iMinDamage + FloatToInt((IntToFloat(Random(iMaxDamage)) * (fRadius - fDistance)/fRadius));
effect eDamage = EffectDamage(iTotalDamage, DAMAGE_TYPE_FIRE);
ApplyEffectToObject(DURATION_TYPE_INSTANT, eDamage, oObject);
ApplyEffectToObject(DURATION_TYPE_INSTANT, eFX1, oObject);
oObject = GetNextObjectInShape(SHAPE_SPHERE, fRadius, lCenter);
}
}
// Clears off the state of all messages that have been sent (PC has eaten some food)
void FoodSys_ClearMessages(object oPC) {
SetLocalInt(oPC, "iStarveMsg01", FALSE);
SetLocalInt(oPC, "iStarveMsg02", FALSE);
SetLocalInt(oPC, "iStarveMsg03", FALSE);
SetLocalInt(oPC, "iStarveMsg04", FALSE);
}
// Outputs some floating text above the PC that indicates what the PC thinks of the current weather.
// If PC has been standing in the same spot for a while, output the info then block until he's standing at a new spot.
// If PC has been moving about, have a small chance for output.
void FoodSys_CommentOnWeather(object oPC, float fApparentTemp) {
}
// Called from FoodSys_Drunk_EffectDrunk() to determine if the booze-hound can hold
// their liqour...
void FoodSys_Drunk_CheckVomit(object oActivator, int iMOD, int iBloodAlcohol) {
// Calculate whether oActivator's Blood Alcohol is too high
int bVomit = FALSE;
int iVomit;
int iAdj;
if (iMOD >= 0) { // For + CON
iAdj = iMOD * iMOD;
iVomit = Random(iBloodAlcohol) - (iMOD * Random(iMOD));
if (iVomit > (20 + (iMOD * (iMOD-1)) + (FloatToInt(IntToFloat(iMOD) * 3.35)))) bVomit = TRUE;
}
else { // For - CON
iAdj = -1 * iMOD * iMOD;
iVomit = Random(iBloodAlcohol) + (iMOD * Random(iMOD));
if (iVomit > (20 - (iMOD * (iMOD)) + (FloatToInt(IntToFloat(iMOD) * 3.35)))) bVomit = TRUE;
}
// oActivator can't hold their booze!
if (bVomit) {
// Say something pre-event?
if (Random(100) > 49) FoodSys_Drunk_SpeakWarning(oActivator);
// Vomit effects
AssignCommand(oActivator, ClearAllActions());
AssignCommand(oActivator, FoodSys_Drunk_VomitEffect(oActivator, iMOD));
// Remove some supplies (they barfed them up, afterall!)
Inventory_RemoveStackedItemQuantity(oActivator, "supplies", Random(40 - iAdj) + d2(10));
}
}
// Entry function for the FoodSys_Drunk_* functions. Called from the activator function(s)
// for alcohol (such as Ale, Wine, etc.)
void FoodSys_Drunk_EffectDrunk(object oTarget) {
// Check for immunity
if (GetLocalInt(oTarget, "iAlcohol_Immune")) return;
// Proceed normally
int iMOD = GetAbilityModifier(ABILITY_CONSTITUTION, oTarget);
int iAdj = iMOD * iMOD;
if (iMOD < 0) iAdj *= -1;
int iBloodAlcohol = GetLocalInt(oTarget, "iBloodAlcohol");
if (iBloodAlcohol <= 0) AssignCommand(oTarget, DelayCommand(12.0, FoodSys_Drunk_LowerBloodAlcohol(oTarget)));
int iInc = d12() + 5 - iAdj;
if (iInc < 4) iInc = 4;
iBloodAlcohol += iInc;
SetLocalInt(oTarget, "iBloodAlcohol", iBloodAlcohol);
// Do effects
effect eFXA;
effect eFXB;
effect eFXC;
if (iBloodAlcohol > (40 + iAdj)) { // CON/WIS/INT -1
eFXA = EffectAbilityDecrease(ABILITY_CONSTITUTION, 1);
eFXB = EffectAbilityDecrease(ABILITY_WISDOM, 1);
eFXC = EffectAbilityDecrease(ABILITY_INTELLIGENCE, 1);
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFXA, oTarget, IntToFloat(d20(2)-iMOD));
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFXB, oTarget, IntToFloat(d20(2)-iMOD));
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFXC, oTarget, IntToFloat(d20(2)-iMOD));
}
if (iBloodAlcohol > (45 + iAdj)) { // WIS-1 / CHA-2 / CONC - 1
eFXA = EffectSkillDecrease(SKILL_CONCENTRATION, 1);
eFXB = EffectAbilityDecrease(ABILITY_WISDOM, 1);
eFXC = EffectAbilityDecrease(ABILITY_CHARISMA, 2);
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFXA, oTarget, IntToFloat(d20(3)-iMOD));
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFXB, oTarget, IntToFloat(d20(3)-iMOD));
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFXC, oTarget, IntToFloat(d20(3)-iMOD));
}
if (iBloodAlcohol > (55 + iAdj)) { // DEX -1 / WIS -2/ CHA -2
eFXA = EffectAbilityDecrease(ABILITY_DEXTERITY, 1);
eFXB = EffectAbilityDecrease(ABILITY_WISDOM, 2);
eFXC = EffectAbilityDecrease(ABILITY_CHARISMA, 2);
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFXA, oTarget, IntToFloat(d20(3)-iMOD));
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFXB, oTarget, IntToFloat(d20(3)-iMOD));
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFXC, oTarget, IntToFloat(d20(3)-iMOD));
}
if (iBloodAlcohol > (60 + iAdj)) { // Blindness
eFXB = EffectSkillDecrease(SKILL_CONCENTRATION, 2);
eFXC = EffectSkillDecrease(SKILL_DISCIPLINE, 2);
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFXB, oTarget, IntToFloat(d20(4)-iMOD));
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFXC, oTarget, IntToFloat(d20(4)-iMOD));
}
if (iBloodAlcohol > (67 + iAdj)) {
eFXB = EffectSkillDecrease(SKILL_CONCENTRATION, 4);
eFXC = EffectSkillDecrease(SKILL_DISCIPLINE, 4);
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFXB, oTarget, IntToFloat(d20(4)-iMOD));
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFXC, oTarget, IntToFloat(d20(4)-iMOD));
}
// Check to see if PC is gonna POP
FoodSys_Drunk_CheckVomit(oTarget, iMOD, iBloodAlcohol);
}
// Once the target object begins drinking alcohol, his Blood Alcohol level is raised. This function
// is called and then recurses back into itself on a delay (determined by the CON of the target object)
// until the Blood Alcohol level is 0 or less.
void FoodSys_Drunk_LowerBloodAlcohol(object oTarget) {
int iBloodAlcohol = GetLocalInt(oTarget, "iBloodAlcohol");
int iMOD = GetAbilityModifier(ABILITY_CONSTITUTION, oTarget);
if (iMOD <= 0) iMOD = 1;
if (iMOD > 14) iMOD = 14;
iBloodAlcohol -= iMOD;
SetLocalInt(oTarget, "iBloodAlcohol", iBloodAlcohol);
if (iBloodAlcohol > 0) AssignCommand(oTarget, DelayCommand(14.0 - IntToFloat(iMOD), FoodSys_Drunk_LowerBloodAlcohol(oTarget)));
}
// Randomly plays back one of the basic vomit sounds for females.
void FoodSys_Drunk_PlaySoundFemale(object oPC) {
int iRandom = Random(3);
string strSFX;
if (iRandom == 0) strSFX = "f_vomit000";
if (iRandom == 1) strSFX = "f_vomit001";
if (iRandom == 2) strSFX = "f_vomit002";
AssignCommand(oPC, PlaySound(strSFX));
}
// Randomly plays back one of the basic vomit sounds for males.
void FoodSys_Drunk_PlaySoundMale(object oPC) {
int iRandom = Random(3);
string strSFX;
if (iRandom == 0) strSFX = "m_vomit000";
if (iRandom == 1) strSFX = "m_vomit001";
if (iRandom == 2) strSFX = "m_vomit002";
AssignCommand(oPC, PlaySound(strSFX));
}
// If the FoodSystem determines that the target object is going to vomit, it can call this function
// to have the target say something prior to vomitting.
void FoodSys_Drunk_SpeakWarning(object oTarget) {
int iRandom = Random(4);
string strSay;
if (iRandom == 0) strSay = "Don't feel so good...";
if (iRandom == 1) strSay = "*BRACK*";
if (iRandom == 2) strSay = "AWE CRIPES!";
if (iRandom == 3) strSay = "Gonna PUKE!!!";
FloatingTextStringOnCreature(strSay, oTarget);
}
// This function is responsible for creating the actual vomit FX (including audio based on sex).
void FoodSys_Drunk_VomitEffect(object oPC, int iMOD) {
// Create some barf
location lLoc = GetLocation(oPC);
effect eVFXA = EffectVisualEffect(VFX_COM_BLOOD_REG_GREEN);
effect eVFXB = EffectVisualEffect(VFX_COM_CHUNK_YELLOW_SMALL);
effect eFX;
eFX = EffectKnockdown();
ApplyEffectAtLocation(DURATION_TYPE_TEMPORARY, eVFXA, lLoc, 16.0);
ApplyEffectAtLocation(DURATION_TYPE_TEMPORARY, eVFXB, lLoc, 16.0);
float fDrunkAnimDur = 8.0 - IntToFloat(iMOD);
AssignCommand(oPC, DelayCommand(1.8, PlayAnimation(ANIMATION_LOOPING_PAUSE_DRUNK, 1.0, fDrunkAnimDur)));
AssignCommand(oPC, DelayCommand((1.8+fDrunkAnimDur), ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFX, oPC, IntToFloat(d12() - iMOD))));
// Play audio
if (GetGender(oPC) == GENDER_FEMALE) FoodSys_Drunk_PlaySoundFemale(oPC);
else FoodSys_Drunk_PlaySoundMale(oPC);
// Apply effects from puking!
if (iMOD >=0 ) {
int iPenalty = 8 - iMOD;
if (iPenalty < 0) iPenalty = 1;
eFX = EffectAbilityDecrease(ABILITY_CONSTITUTION, iPenalty);
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFX, oPC, IntToFloat(d100(5)));
}
else {
eFX = EffectAbilityDecrease(ABILITY_CONSTITUTION, (-1 * iMOD));
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFX, oPC, IntToFloat(d100(8)));
}
}
// Accessor function for the Active state of the area
int FoodSys_GetActiveState(object oArea) {
return(GetLocalInt(oArea, "ACTIVE_STATE"));
}
// Accessor function for ambient temperature variable
int FoodSys_GetAmbientTemperature(object oArea) {
return(GetLocalInt(oArea, "AMBIENT_TEMP"));
}
// Takes into account the amount of good/harm the PC's equipment is doing. Although Season and Time of Day
// were incorporated previously, they are also considered here in relation to the clothing worn as 'influences'.
int FoodSys_GetConAdjPCClothing(object oPC) {
// Get Area Ambient Temperature; Modify with Season and TOD info.
int iSeason = GetSeason();
int iTOD = GetTOD();
object oArea = GetArea(oPC);
int iTempFlux = FoodSys_GetTemperatureFlux(oArea);
int iAmbTemp = FoodSys_GetAmbientTemperature(oArea);
// Modify the Temperature with the Season
if (iSeason == SEASON_WINTER) iAmbTemp *= -1; // Invert the ambient temperature for winter
if ((iSeason == SEASON_SPRING) || (iSeason == SEASON_FALL)) iAmbTemp -= FloatToInt(IntToFloat(iAmbTemp) / IntToFloat(iTempFlux)); // Decrease the ambient temperature for Spring and Fall
// Modify the Temperature with the Flux
int iFlux = Random(iTempFlux);
if ((iSeason == SEASON_FALL) || (iSeason == SEASON_SPRING)) {
iFlux /= 2;
if (Random(100) < 50) iFlux *= -1; // Decrease the temperature from the seasonal ambient
iAmbTemp += iFlux;
}
// Modify the Temperature with the Windshield
// Not yet implimented
// Modify the Temperature with the TOD
if (iTOD == TOD_NIGHT) iAmbTemp -= Random(iFlux/2);
if (iTOD == TOD_DAY) iAmbTemp += Random(iFlux/2);
if ((iTOD == TOD_DUSK) || (iTOD == TOD_DAWN)) {
if (Random(100) < 50) iAmbTemp -= Random(iFlux/3);
else iAmbTemp += Random(iFlux/3);
}
// Generate a Heat Retension percentage for the PC based on his clothing
float fHeatRetension = IntToFloat(FoodSys_GetHeatRetension(oPC));
// What is the apparent temperature for the PC?
float fAmbTemp = IntToFloat(iAmbTemp);
float fApparentTemp = fAmbTemp;
float fAdjust;
float fSign = 1.0;
if (iAmbTemp < 0) fSign = -1.0;
float fHRFactor = fHeatRetension - 50; // The PC's Heat Retension factor (0 heat loss and heat gain is at 50%, tan(0) = 0 which is why I correct the value with -50, to get a ZERO heat gain/loss)
float fWarmingFactor = fAmbTemp - 25; // Represents the players ability to heat themselves as temperature drops (becomes less and less with lower temps). ZERO point for this is 25C.
if (fWarmingFactor >= 90.0) fWarmingFactor = 89.0; // Ensure the values are not illegal for the TAN function
if (fWarmingFactor <= -90.0) fWarmingFactor = -89.0;
if (fHRFactor >= 90.0) fHRFactor = 89.0;
if (fHRFactor <= -90.0) fHRFactor = -89.0;
fApparentTemp += fSign * fAmbTemp * (tan(fWarmingFactor) + tan(fHRFactor));
// Return an adjustment for the PC's food consumption rate
int iExtraFoodNeeded = 0;
if (fApparentTemp <= -25.0) iExtraFoodNeeded = 7;
if (fApparentTemp >= -25.0 && fApparentTemp <= -12.0) iExtraFoodNeeded = 4;
if (fApparentTemp >= -11.0 && fApparentTemp <= -5.0) iExtraFoodNeeded = 2;
if (fApparentTemp >= -4.0 && fApparentTemp <= 7.0) iExtraFoodNeeded = -1;
if (fApparentTemp >= 8.0 && fApparentTemp <= 25.0) iExtraFoodNeeded = -2;
if (fApparentTemp >= 25.0 && fApparentTemp <= 32.0) iExtraFoodNeeded = 1;
if (fApparentTemp >= 33.0) iExtraFoodNeeded = 2;
// Let the PC see what his character thinks of the current temperature (low random chance if moving
// about, high random chance if PC has been standing still for some time)
FoodSys_CommentOnWeather(oPC, fApparentTemp);
// Finish up
if (DEBUG_STATE) SendMessageToPC(oPC, "Current Temperature is at: " + FloatToString(fAmbTemp) + " C, Apparent temperature is at: " + FloatToString(fApparentTemp) + " C");
return(iExtraFoodNeeded);
}
// Returns the ration consumption adjustment factor for the current season
int FoodSys_GetConAdjSeason() {
int iMonth = GetCalendarMonth();
if ((iMonth >= 1 && iMonth <= 2) || iMonth==12) return 3; // December - February (WINTER)
if (iMonth >= 3 && iMonth <= 5) return 1; // March - May (SPRING)
if (iMonth >= 6 && iMonth <= 8) return 2; // June - August (SUMMER)
if (iMonth >= 9 && iMonth <= 11) return 0; // September - November (FALL)
return 0;
}
// Returns the ration consumption adjustment factor for the difficult modifier of the current terrain
int FoodSys_GetConAdjTerrainModifier(object oArea) {
int iTerrainModifier = FoodSys_GetTerrainModifier(oArea);
if (iTerrainModifier == TERRAIN_MODIFIER_INFRA) return -3;
if (iTerrainModifier == TERRAIN_MODIFIER_EASY) return -2;
if (iTerrainModifier == TERRAIN_MODIFIER_NORMAL) return 0;
if (iTerrainModifier == TERRAIN_MODIFIER_HARD) return 1;
if (iTerrainModifier == TERRAIN_MODIFIER_ULTRA) return 2;
return 0;
}
// Returns the ration consumption adjustment factor for the current type of terrain
int FoodSys_GetConAdjTerrainType(object oArea) {
int iTerrainType = FoodSys_GetTerrainType(oArea);
if (iTerrainType == TERRAIN_TYPE_DUNGEON) return 2;
if (iTerrainType == TERRAIN_TYPE_FOREST) return 3;
if (iTerrainType == TERRAIN_TYPE_GRASSLAND) return 0;
if (iTerrainType == TERRAIN_TYPE_CITY) return 1;
if (iTerrainType == TERRAIN_TYPE_CAVERN) return 2;
if (iTerrainType == TERRAIN_TYPE_HILLS) return 3;
return 0;
}
// Returns the ration consumption adjustment factor for the current time of day
int FoodSys_GetConAdjTOD() {
if (GetIsDawn()) return 1;
if (GetIsDay()) return 0;
if (GetIsDusk()) return 1;
if (GetIsNight()) return 2;
return 0;
}
// Accessor function for the basic food usage value (food used every 24 seconds, or in a HB)
int FoodSys_GetFoodUse() {
return(GetLocalInt(GetModule(), "BASE_FOOD_USAGE"));
}
// Examines the PC to determine what articles of clothing he's wearing and how well he will therefore
// retain heat. Can be a good thing, can be a bad thing! A return value of 50% is the ideal level for
// maintaining a constant temperature.
int FoodSys_GetHeatRetension(object oPC) {
int iHeatRetension = 0;
// Look for Summer-specific Heat dissipation items (have no effect during winter)
// Is it Summer?
if (SEASON_SUMMER == GetSeason()) {
// Not implimented yet
}
// Look for Winter-specific Heat Retension items (large shield, etc.) (have no effect during summer)
// Is it winter?
if (SEASON_WINTER == GetSeason()) {
// Not implimented yet
}
// Assess the remaining items for year round heat retention (HR = 65% if all slots are full)
if (GetIsObjectValid(GetItemInSlot(INVENTORY_SLOT_ARMS, oPC))) iHeatRetension += 7;
if (GetIsObjectValid(GetItemInSlot(INVENTORY_SLOT_BELT, oPC))) iHeatRetension += 1;
if (GetIsObjectValid(GetItemInSlot(INVENTORY_SLOT_BOOTS, oPC))) iHeatRetension += 7;
if ((GetIsObjectValid(GetItemInSlot(INVENTORY_SLOT_CARMOUR, oPC))) || (GetIsObjectValid(GetItemInSlot(INVENTORY_SLOT_CHEST, oPC)))) iHeatRetension += 25; if (GetIsObjectValid(GetItemInSlot(INVENTORY_SLOT_CLOAK, oPC))) iHeatRetension += 14;
if (GetIsObjectValid(GetItemInSlot(INVENTORY_SLOT_HEAD, oPC))) iHeatRetension += 10;
if (GetIsObjectValid(GetItemInSlot(INVENTORY_SLOT_LEFTHAND, oPC))) iHeatRetension += 4;
if (GetIsObjectValid(GetItemInSlot(INVENTORY_SLOT_NECK, oPC))) iHeatRetension += 7;
if (GetIsObjectValid(GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oPC))) iHeatRetension += 4;
return(iHeatRetension);
}
// Accessor function for the minimum food rate consumption variable
int FoodSys_GetMinimumFoodRate(object oArea) {
return(GetLocalInt(oArea, "MIN_FOOD_RATE"));
}
// Calculates how long the PC can last without food
int FoodSys_GetStarvationLimit(object oPC) {
// Based on PC's CON and STR
int iCON = GetAbilityModifier(ABILITY_CONSTITUTION, oPC);
int iSTR = GetAbilityModifier(ABILITY_STRENGTH, oPC);
// Calculate the limit
return(iCON + (iSTR / 2));
}
// Get the PC's Current starvation level
int FoodSys_GetStarveCounter(object oPC) {
return(GetLocalInt(oPC, "iStarveCounter"));
}
// Accessor function for the temperature flux variable
int FoodSys_GetTemperatureFlux(object oArea) {
return(GetLocalInt(oArea, "TEMP_FLUX"));
}
// Accessor function for terrain modifier variable
int FoodSys_GetTerrainModifier(object oArea) {
return(GetLocalInt(oArea, "TERRAIN_MOD"));
}
// Accessor function for terrain type variable
int FoodSys_GetTerrainType(object oArea) {
return(GetLocalInt(oArea, "TERRAIN_TYPE"));
}
// Main function for maintaining the FoodSystem_* on all PC's - launched via FoodSystem_Initialize().
void FoodSys_HeartBeat(object oPC, float fHeartBeat) {
// Exempt from Hunger? Immune to Food Use, Dead, or Close to Dead...
if ((GetLocalInt(oPC, "iFoodSys_Immune") == TRUE) || (GetCurrentHitPoints(oPC) <= 3)) {
if (DEBUG_STATE) SendMessageToPC(oPC, "PC is immune to hunger...");
AssignCommand(oPC, DelayCommand(fHeartBeat, FoodSys_HeartBeat(oPC, fHeartBeat))); // Make sure the Food System Heart Beat continues to fire
return;
}
// Is the weather system in the current area active?
object oArea = GetArea(oPC);
if (!FoodSys_GetActiveState(oArea)) {
if (DEBUG_STATE) SendMessageToPC(oPC, "Food system not active in this area.");
AssignCommand(oPC, DelayCommand(fHeartBeat, FoodSys_HeartBeat(oPC, fHeartBeat))); // Make sure the Food System Heart Beat continues to fire
return;
}
// Get Consumption Adjustment factor for Season
int iConsumpAdj = FoodSys_GetFoodUse();
iConsumpAdj += FoodSys_GetConAdjSeason();
// Get Consumption Adjustment factor for TOD
iConsumpAdj += FoodSys_GetConAdjTOD();
// Get Consumption Adjustment factor for Area Terrain Type
iConsumpAdj += FoodSys_GetConAdjTerrainType(oArea);
// Get Consumption Adjustment factor for Area Terrain Modifier
iConsumpAdj += FoodSys_GetConAdjTerrainModifier(oArea);
// Consumption Adjustment factor for PC's clothing
iConsumpAdj += FoodSys_GetConAdjPCClothing(oPC);
// Constitution adjustment factor
iConsumpAdj -= GetAbilityModifier(ABILITY_CONSTITUTION, oPC);
// Strength adjustment factor
iConsumpAdj -= (GetAbilityModifier(ABILITY_STRENGTH, oPC) / 2);
// Make sure food consumed is NOT less than the Minimum Food Rate consumption for every HeartBeat period
// (This is the least amount of food that MUST be consumed each food_sys heartbeat, regardless of bonusses)
int iMinFoodRate = FoodSys_GetMinimumFoodRate(oArea);
if (iConsumpAdj < iMinFoodRate) iConsumpAdj = iMinFoodRate;
if (DEBUG_STATE) SendMessageToPC(oPC, "Final Food Usage: " + IntToString(iConsumpAdj));
// Check starvation counter.
int iStarveCounter = FoodSys_GetStarveCounter(oPC);
int iStarveLimit = FoodSys_GetStarvationLimit(oPC);
// Has PC reached starvation levels of hunger yet?
if (iStarveCounter > iStarveLimit) { // YES! U GONNA DIE NOW SENOR!
// How high is the counter? Output a message indicating PC's severity!
effect eFX1;
effect eFX2;
effect eFX3;
effect eFX4;
effect eFX5;
int iPenalty = iStarveCounter - iStarveLimit;
int iSeason = GetSeason();
if (iStarveCounter >= iStarveLimit+1) { // -(STR/CON/DEX - 1; CONC & DISC - 5;)
FoodSys_SendMessage(oPC, "iStarveMsg01", "I need food... badly!");
eFX1 = EffectAbilityDecrease(ABILITY_STRENGTH, iPenalty);
// eFX2 = EffectAbilityDecrease(ABILITY_CONSTITUTION, iPenalty);
eFX3 = EffectAbilityDecrease(ABILITY_DEXTERITY, iPenalty);
eFX4 = EffectSkillDecrease(SKILL_CONCENTRATION, iPenalty);
eFX5 = EffectSkillDecrease(SKILL_DISCIPLINE, iPenalty);
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFX1, oPC, fHeartBeat);
// ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFX2, oPC, fHeartBeat);
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFX3, oPC, fHeartBeat);
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFX4, oPC, fHeartBeat);
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFX5, oPC, fHeartBeat);
}
if (iStarveCounter >= iStarveLimit+2) { // - Slow
FoodSys_SendMessage(oPC, "iStarveMsg02", "I'm starving!! Getting weaker... must find food soon...");
eFX1 = EffectSlow();
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFX1, oPC, fHeartBeat);
}
if (iStarveCounter >= iStarveLimit+3) { // - Reduced Damage; Reduced AC; both increase on how far over the Starve limit we are
FoodSys_SendMessage(oPC, "iStarveMsg03", "I'm starving to death!! ...must find food or shelter soon...");
eFX1 = EffectDamageDecrease(iPenalty);
eFX2 = EffectACDecrease(iPenalty);
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFX1, oPC, fHeartBeat);
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFX2, oPC, fHeartBeat);
}
if (iStarveCounter >= iStarveLimit+4) { // - Hit points reduced based on how far over Starve limit we are
if (18 > d20()+GetFortitudeSavingThrow(oPC)) {
SendMessageToPC(oPC, "Saving throw vs. Primary effects of starvation failed!");
FoodSys_SendMessage(oPC, "iStarveMsg04", "...can't keep going...");
eFX1 = EffectSavingThrowDecrease(SAVING_THROW_ALL, 1);
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFX1, oPC, IntToFloat(Random(FloatToInt(fHeartBeat))));
}
}
if (iStarveCounter >= iStarveLimit+5) { // - %Chance Dazed; %Chance Knockdown; %Chance Stunned;
if (iPenalty+10 > d20()+GetFortitudeSavingThrow(oPC)) { // PC becomes diseased
SendMessageToPC(oPC, "Saving throw vs. Primary effects of starvation failed!");
int iRandom = Random(3);
string strFXMsg;
if (iRandom == 0) { eFX1 = EffectDazed(); strFXMsg = "...can't think straight..."; }
if (iRandom == 1) { eFX1 = EffectKnockdown(); strFXMsg = "...dizzy... loosing my balance..."; }
if (iRandom == 2) { eFX1 = EffectStunned(); strFXMsg = "... who... where am i???..."; }
FloatingTextStringOnCreature(strFXMsg, oPC);
SendMessageToPC(oPC, strFXMsg);
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFX1, oPC, IntToFloat(Random(FloatToInt(fHeartBeat))));
}
}
if (iStarveCounter >= iStarveLimit+8) { // - %Chance Blindness; %Chance Deafness; %Chance Confused
if (iPenalty+10 > d20()+GetFortitudeSavingThrow(oPC)) { // PC becomes diseased
SendMessageToPC(oPC, "Saving throw vs. Tertiary effects of starvation failed!");
int iRandom = Random(3);
string strFXMsg;
if (iRandom == 0) { eFX1 = EffectBlindness(); strFXMsg = "...my vision has blacked out from hunger!"; }
if (iRandom == 1) { eFX1 = EffectDeaf(); strFXMsg = "...i can't hear anything but a buzzing sound from the hunger!"; }
if (iRandom == 2) { eFX1 = EffectConfused(); strFXMsg = "...where? where am i?? so... hungry..."; }
FloatingTextStringOnCreature(strFXMsg, oPC);
SendMessageToPC(oPC, strFXMsg);
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFX1, oPC, IntToFloat(Random(FloatToInt(fHeartBeat))+iPenalty));
}
}
if (iStarveCounter >= iStarveLimit+10) { // - %Chance of Disease
if (d100()-GetAbilityModifier(ABILITY_CONSTITUTION, oPC) >= 95-iPenalty) {
if (iPenalty+12 > d20()+GetFortitudeSavingThrow(oPC)) { // PC becomes diseased
SendMessageToPC(oPC, "Saving throw vs. Disease Vulnerability effects of starvation failed!");
string strFXMsg;
int iRandom = Random(9);
if (iRandom == 0) { eFX1 = EffectDisease(DISEASE_BURROW_MAGGOTS); strFXMsg = "... i'm so weak i've contracted burrowing maggots..."; }
if (iRandom == 1) { eFX1 = EffectDisease(DISEASE_CACKLE_FEVER); strFXMsg = "... i'm so weak i've contracted cackle fever..."; }
if (iRandom == 2) { eFX1 = EffectDisease(DISEASE_DEMON_FEVER); strFXMsg = "... i'm so weak i've contracted demon fever..."; }
if (iRandom == 3) { eFX1 = EffectDisease(DISEASE_DEVIL_CHILLS); strFXMsg = "... i'm so weak i've contracted devil chills..."; }
if (iRandom == 4) { eFX1 = EffectDisease(DISEASE_DREAD_BLISTERS); strFXMsg = "... i'm so weak i've contracted dread blisters..."; }
if (iRandom == 5) { eFX1 = EffectDisease(DISEASE_FILTH_FEVER); strFXMsg = "... i'm so weak i've contracted filth fever..."; }
if (iRandom == 6) { eFX1 = EffectDisease(DISEASE_MINDFIRE); strFXMsg = "... i'm so weak i've contracted mindfire..."; }
if (iRandom == 7) { eFX1 = EffectDisease(DISEASE_RED_ACHE); strFXMsg = "... i'm so weak i've contracted the red ache..."; }
if (iRandom == 8) { eFX1 = EffectDisease(DISEASE_SHAKES); strFXMsg = "... i'm so weak i've contracted the shakes..."; }
FloatingTextStringOnCreature(strFXMsg, oPC);
SendMessageToPC(oPC, strFXMsg);
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFX1, oPC, IntToFloat(Random(FloatToInt(fHeartBeat)) * (1+Random(FloatToInt(fHeartBeat)))));
}
}
}
}
// How many supply rations are needed? Does the PC have them?
if (FoodSys_TakeSupplies(oPC, iConsumpAdj)) {
// Yes, do a debit. Clear starvation counter to ZERO.
SetLocalInt(oPC, "iStarveMsg00", FALSE);
FoodSys_ClearMessages(oPC);
FoodSys_SetStarveCounter(oPC); // Reset the starvation counter to ZERO
}
else {
// No, indicate PC has run out of food. Start counter.
FoodSys_SetStarveCounter(oPC, ++iStarveCounter); // Increment starvation level
int iStarveMsg00 = GetLocalInt(oPC, "iStarveMsg00");
if (!iStarveMsg00) {
FloatingTextStringOnCreature("I've run out of food!!", oPC);
SetLocalInt(oPC, "iStarveMsg00", TRUE);
}
}
// Make sure the Food System Heart Beat continues to fire
AssignCommand(oPC, DelayCommand(fHeartBeat, FoodSys_HeartBeat(oPC, fHeartBeat)));
// Output info for debugging
FoodSys_ShowTerrainVars(oPC);
}
// Placed in the modules OnClientEnter and used on PC's entering the game. Used to
// activate the food system for all PC's entering the world.
void FoodSys_Initialize(object oTarget, float fHeartBeat=60.0, int iBaseFoodUse=6) {
FoodSys_SetFoodUse(iBaseFoodUse);
AssignCommand(oTarget, FoodSys_HeartBeat(oTarget, fHeartBeat));
}
// Sends the message but ensures it is only seend ONCE by the PC until he has eaten again...
void FoodSys_SendMessage(object oPC, string strMessageID, string strMessage) {
if (GetLocalInt(oPC, strMessageID)) return;
SetLocalInt(oPC, strMessageID, TRUE);
FloatingTextStringOnCreature(strMessage, oPC);
}
// Sets the minimum food requirement per heartbeat of the system
void FoodSys_SetFoodUse(int iBaseFoodUse) {
SetLocalInt(GetModule(), "BASE_FOOD_USAGE", iBaseFoodUse);
}
// Set the value contained within the PC's starvation counter
void FoodSys_SetStarveCounter(object oPC, int iValue=0) {
SetLocalInt(oPC, "iStarveCounter", iValue);
}
// Used in an Area's OnEnter callback event to set how much food the area requires from travellers.
// Can also be called in an areas OnHeartBeat - though not recommended. Will only fire once for each
// area that calls it.
// *Default params are TERRAIN_TYPE_CITY, TEMP=25C, TERRAIN_MODIFIER_NORMAL, MIN_FOOD_RATE=1
void FoodSys_SetTerrainType(int iTerrainType=3, int iAmbientTemperature=25, int iTerrainModifier=2, int iTemperatureFlux=10, int iMinFoodRate=1, int iActive=TRUE) {
// Be sure Terrain Type is valid
if ((iTerrainType != TERRAIN_TYPE_DUNGEON) &&
(iTerrainType != TERRAIN_TYPE_FOREST) &&
(iTerrainType != TERRAIN_TYPE_GRASSLAND) &&
(iTerrainType != TERRAIN_TYPE_CITY) &&
(iTerrainType != TERRAIN_TYPE_CAVERN) &&
(iTerrainType != TERRAIN_TYPE_HILLS)) iTerrainType = TERRAIN_TYPE_CITY;
// Be sure Terrain Difficulty Modifier is valid
if ((iTerrainModifier != TERRAIN_MODIFIER_INFRA) &&
(iTerrainModifier != TERRAIN_MODIFIER_EASY) &&
(iTerrainModifier != TERRAIN_MODIFIER_NORMAL) &&
(iTerrainModifier != TERRAIN_MODIFIER_HARD) &&
(iTerrainModifier != TERRAIN_MODIFIER_ULTRA)) iTerrainModifier = TERRAIN_MODIFIER_NORMAL;
// Set the variables
SetLocalInt(OBJECT_SELF, "TERRAIN_TYPE", iTerrainType);
SetLocalInt(OBJECT_SELF, "TERRAIN_MOD", iTerrainModifier);
SetLocalInt(OBJECT_SELF, "AMBIENT_TEMP", iAmbientTemperature);
SetLocalInt(OBJECT_SELF, "MIN_FOOD_RATE", iMinFoodRate); // The least amount of food that MUST be consumed each food_sys heartbeat, regardless of bonusses
SetLocalInt(OBJECT_SELF, "TEMP_FLUX", iTemperatureFlux);
SetLocalInt(OBJECT_SELF, "ACTIVE_STATE", iActive);
}
// Sends the current terrain variables to a PC
void FoodSys_ShowTerrainVars(object oPC) {
int iTerrainType = FoodSys_GetTerrainType(GetArea(oPC));
int iTerrainModifier = FoodSys_GetTerrainModifier(GetArea(oPC));
int iAmbientTemperature = FoodSys_GetAmbientTemperature(GetArea(oPC));
// Terrain Type
string strTerrainType;
if (iTerrainType == TERRAIN_TYPE_DUNGEON) strTerrainType = "Dungeon";
if (iTerrainType == TERRAIN_TYPE_FOREST) strTerrainType = "Forest";
if (iTerrainType == TERRAIN_TYPE_GRASSLAND) strTerrainType = "Grassland";
if (iTerrainType == TERRAIN_TYPE_CITY) strTerrainType = "City";
if (iTerrainType == TERRAIN_TYPE_CAVERN) strTerrainType = "Cavern/Mine";
if (iTerrainType == TERRAIN_TYPE_HILLS) strTerrainType = "Hills";
string strTerrainModifier;
// Terrain Modifier
if (iTerrainModifier == TERRAIN_MODIFIER_INFRA) strTerrainModifier = "Infra";
if (iTerrainModifier == TERRAIN_MODIFIER_EASY) strTerrainModifier = "Easy";
if (iTerrainModifier == TERRAIN_MODIFIER_NORMAL) strTerrainModifier = "Normal";
if (iTerrainModifier == TERRAIN_MODIFIER_HARD) strTerrainModifier = "Hard";
if (iTerrainModifier == TERRAIN_MODIFIER_ULTRA) strTerrainModifier = "Ultra";
// Season
string strSeason = GetSeasonString();
string strTOD = GetTODString();
string strTempFlux = IntToString(FoodSys_GetTemperatureFlux(GetArea(oPC)));
// Active state
int iActiveState = FoodSys_GetActiveState(GetArea(oPC));
// Output the values
if (DEBUG_STATE) SendMessageToPC(oPC, strTerrainType + ", " + strTerrainModifier + ", " + IntToString(iAmbientTemperature) + " C " + "+/- " + strTempFlux + ", " + strTOD + ", " + strSeason + ", active state is " + IntToString(iActiveState));
}
// Debit the PC for the supplies he consumes this 'HeartBeat'
int FoodSys_TakeSupplies(object oPC, int iQuantity) {
int iPCHasEnough = FALSE;
// Count the PC's supplies
object oItem = GetFirstItemInInventory(oPC);
int iTotalSupplies = 0;
while (GetIsObjectValid(oItem)) {
if (GetTag(oItem) == "supplies") iTotalSupplies += GetNumStackedItems(oItem);
oItem = GetNextItemInInventory(oPC);
}
// Does he have enough?
if (iTotalSupplies >= iQuantity) {
iPCHasEnough = TRUE; // Yes!
Inventory_RemoveStackedItemQuantity(oPC, "supplies", iQuantity);
}
else Inventory_RemoveStackedItemQuantity(oPC, "supplies", iTotalSupplies);
return iPCHasEnough;
}
// Returns the subjects highest class level
int GetHighestLevel(object oSubject) {
int iIndex;
int iHighest = 0;
int iTemp;
for (iIndex=1; iIndex<=3; iIndex++) {
iTemp = GetLevelByPosition(iIndex, oSubject);
if (iTemp > iHighest) iHighest = iTemp;
}
return iHighest;
}
// Returns the PC's combined level from all classes (MAX 3 classes)
int GetLevel(object oSubject) {
int iIndex;
int iLevels = 0;
for (iIndex=1; iIndex<=3; iIndex++) iLevels += GetLevelByPosition(iIndex, oSubject);
return(iLevels);
}
// Returns the subjects lowest class level
int GetLowestLevel(object oSubject) {
int iIndex;
int iLowest = 500;
int iTemp;
for (iIndex=1; iIndex<=3; iIndex++) {
iTemp = GetLevelByPosition(iIndex, oSubject);
if (iTemp < iLowest) iLowest = iTemp;
}
return iLowest;
}
// Returns the subjects middle class level. Only for PC's with 3 classes - otherwise returns a 0.
int GetMiddleLevel(object oSubject) {
return 0;
}
// Returns the total number of negative effects that the target object has on it
int GetNumberOfNegativeEffects(object oTarget) {
int iTotalEffects = 0;
effect eBad = GetFirstEffect(oTarget);
while(GetIsEffectValid(eBad)) {
if ((GetEffectType(eBad) == EFFECT_TYPE_AC_DECREASE ||
GetEffectType(eBad) == EFFECT_TYPE_ATTACK_DECREASE ||
GetEffectType(eBad) == EFFECT_TYPE_DAMAGE_DECREASE ||
GetEffectType(eBad) == EFFECT_TYPE_SAVING_THROW_DECREASE ||
GetEffectType(eBad) == EFFECT_TYPE_CURSE ||
GetEffectType(eBad) == EFFECT_TYPE_BLINDNESS ||
GetEffectType(eBad) == EFFECT_TYPE_DEAF ||
GetEffectType(eBad) == EFFECT_TYPE_DAMAGE_IMMUNITY_DECREASE ||
GetEffectType(eBad) == EFFECT_TYPE_DISEASE ||
GetEffectType(eBad) == EFFECT_TYPE_POISON ||
GetEffectType(eBad) == EFFECT_TYPE_PARALYZE ||
GetEffectType(eBad) == EFFECT_TYPE_NEGATIVELEVEL ||
GetEffectType(eBad) == EFFECT_TYPE_ABILITY_DECREASE ||
GetEffectType(eBad) == EFFECT_TYPE_SPELL_RESISTANCE_DECREASE ||
GetEffectType(eBad) == EFFECT_TYPE_SKILL_DECREASE)) iTotalEffects++;
eBad = GetNextEffect(oTarget);
}
return iTotalEffects;
}
// Accessor function the current season
int GetSeason() {
int iMonth = GetCalendarMonth();
if ((iMonth >= 1 && iMonth <= 2) || iMonth==12) return SEASON_WINTER;
if (iMonth >= 3 && iMonth <= 5) return SEASON_SPRING;
if (iMonth >= 6 && iMonth <= 8) return SEASON_SUMMER;
if (iMonth >= 9 && iMonth <= 11) return SEASON_FALL;
return(0);
}
// Accessor function for the current season text
string GetSeasonString() {
int iMonth = GetCalendarMonth();
if ((iMonth >= 1 && iMonth <= 2) || iMonth==12) return "Winter";
if (iMonth >= 3 && iMonth <= 5) return "Spring";
if (iMonth >= 6 && iMonth <= 8) return "Summer";
if (iMonth >= 9 && iMonth <= 11) return "Fall";
return("Unknown");
}
// Accessor function for the Time of Day
int GetTOD() {
if (GetIsDawn()) return TOD_DAWN;
if (GetIsDay()) return TOD_DAY;
if (GetIsDusk()) return TOD_DUSK;
if (GetIsNight()) return TOD_NIGHT;
return(0);
}
// Accessor function for the current TOD string
string GetTODString() {
if (GetIsDawn()) return "Dawn";
if (GetIsDay()) return "Day";
if (GetIsDusk()) return "Dusk";
if (GetIsNight()) return "Night";
return("Unknown");
}
// Called by an object that is capable of Healing another object (a Priest healing a PC and all his minions).
// iRemoveEffects dictates what Negative Effects will be removed from the targets.
// iAddBlessings allows the caller to add a set of blessings to the targets.
void Heal_BeginHealing(object oPC, int iRemoveEffects = 31, int iAddBlessings = FALSE, int iGiveHenchPotions = TRUE, string strPotionTag = "NW_IT_MPOTION003", int iHealHench = TRUE, int iHealFamil = TRUE, int iHealOthers = TRUE, int iMinHP = 0, int iMaxHP = 0) {
object oHenchman = GetAssociate(ASSOCIATE_TYPE_HENCHMAN,oPC);
object oAnimal = GetAssociate(ASSOCIATE_TYPE_ANIMALCOMPANION,oPC);
object oFamiliar = GetAssociate(ASSOCIATE_TYPE_FAMILIAR,oPC);
object oDominated = GetAssociate(ASSOCIATE_TYPE_DOMINATED,oPC);
object oSummoned = GetAssociate(ASSOCIATE_TYPE_SUMMONED,oPC);
ActionPauseConversation();
// Bless PC if needed
if (iAddBlessings != 0) Heal_BlessingEffect(oPC, iAddBlessings);
// Do restorations
ActionCastFakeSpellAtObject(SPELL_GREATER_RESTORATION, OBJECT_SELF);
ActionDoCommand(Heal_RestoreEffect(oPC, iRemoveEffects, iMinHP, iMaxHP));
if (!GetIsObjectValid(GetItemPossessedBy(oHenchman, strPotionTag)) && iGiveHenchPotions) CreateItemOnObject(strPotionTag, oHenchman, 3);
if(GetIsObjectValid(oHenchman) && iHealHench) ActionDoCommand(Heal_RestoreEffect(oHenchman, iRemoveEffects, iMinHP, iMaxHP));
if(GetIsObjectValid(oAnimal) && iHealOthers) ActionDoCommand(Heal_RestoreEffect(oAnimal, iRemoveEffects, iMinHP, iMaxHP));
if(GetIsObjectValid(oFamiliar) && iHealFamil) ActionDoCommand(Heal_RestoreEffect(oFamiliar, iRemoveEffects, iMinHP, iMaxHP));
if(GetIsObjectValid(oDominated) && iHealOthers) ActionDoCommand(Heal_RestoreEffect(oDominated, iRemoveEffects, iMinHP, iMaxHP));
if(GetIsObjectValid(oSummoned) && iHealOthers) ActionDoCommand(Heal_RestoreEffect(oSummoned, iRemoveEffects, iMinHP, iMaxHP));
ActionResumeConversation();
}
// Only the PC may have these (as oppossed to Henchmen, etc.)!!!
// Blocks PC from obtaining multiple blessings while previous ones are still in effect!!
// iAddBlessings *must* contain ONE of the HEAL_BLESS_DURATION_* values, and should have
// one of the 'packages' and perhaps one (or more) of the Blessing Options available. Made available
// through a conversation, these effects can be charged for or specified with a good degree of variety.
void Heal_BlessingEffect(object oPC, int iAddBlessings) {
if (GetLocalInt(oPC, "iBlessingActive") == 1) {
FloatingTextStringOnCreature("Blessing fizzled!", oPC);
return; // PC has already been blessed!
}
float fDuration;
effect eACBonus;
effect eATBonus;
effect eSTBonus;
effect eOptional;
// Get duration
if ((iAddBlessings & 16) == 16) fDuration = 60.0; // 1 minute
if ((iAddBlessings & 32) == 32) fDuration = 180.0; // 3 minutes
if ((iAddBlessings & 64) == 64) fDuration = 360.0; // 6 minutes
// Basic blessing
if ((iAddBlessings & 1) == 1) {
eACBonus = EffectACIncrease(1);
eATBonus = EffectAttackIncrease(1);
eSTBonus = EffectSavingThrowIncrease(SAVING_THROW_TYPE_ALL, 1);
}
// Average blessing
if ((iAddBlessings & 2) == 2) {
eACBonus = EffectACIncrease(2);
eATBonus = EffectAttackIncrease(2);
eSTBonus = EffectSavingThrowIncrease(SAVING_THROW_TYPE_ALL, 2);
}
// High blessing
if ((iAddBlessings & 4) == 4) {
eACBonus = EffectACIncrease(3);
eATBonus = EffectAttackIncrease(3);
eSTBonus = EffectSavingThrowIncrease(SAVING_THROW_TYPE_ALL, 3);
}
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eACBonus, oPC, fDuration);
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eATBonus, oPC, fDuration);
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eSTBonus, oPC, fDuration);
// 128 = Optional Damage Increase effect (random amount)
if ((iAddBlessings & 128) == 128) {
eOptional = EffectDamageIncrease(d12());
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eOptional, oPC, fDuration);
}
// 256 = Optional Damage Reduction effect (random)
if ((iAddBlessings & 256) == 256) {
eOptional = EffectDamageReduction(d10(), DAMAGE_POWER_PLUS_ONE, d20(10));
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eOptional, oPC, fDuration);
}
// 512 = Optional Damage Resistance to ACID effect
if ((iAddBlessings & 512) == 512) {
eOptional = EffectDamageResistance(DAMAGE_TYPE_ACID, d10());
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eOptional, oPC, fDuration);
}
// 1024 = Optional Damage Resistance to COLD effect
if ((iAddBlessings & 1024) == 1024) {
eOptional = EffectDamageResistance(DAMAGE_TYPE_COLD, d10());
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eOptional, oPC, fDuration);
}
// 2048 = Optional Damage Resistance to FIRE effect
if ((iAddBlessings & 2048) == 2048) {
eOptional = EffectDamageResistance(DAMAGE_TYPE_FIRE, d10());
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eOptional, oPC, fDuration);
}
// 4096 = Optional Haste Effect
if ((iAddBlessings & 4096) == 4096) {
eOptional = EffectHaste();
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eOptional, oPC, fDuration);
}
// 8192 = Optional Temporary Hit Points (random percent of Target's max HP's)
if ((iAddBlessings & 8192) == 8192) {
eOptional = EffectTemporaryHitpoints(Random(GetMaxHitPoints(oPC)));
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eOptional, oPC, fDuration);
}
// 16384 = Optional Regeneration effect
if ((iAddBlessings & 16384) == 16384) {
eOptional = EffectRegenerate(d4(), 3.0);
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eOptional, oPC, fDuration);
}
// 32768 = Optional Level 1 Spell Absorption Effect
if ((iAddBlessings & 32768) == 32768) {
eOptional = EffectSpellLevelAbsorption(1, d6(3));
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eOptional, oPC, fDuration);
}
// 65536 = Optional Level 2 Spell Absorption Effect
if ((iAddBlessings & 65536) == 65536) {
eOptional = EffectSpellLevelAbsorption(2, d8(4));
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eOptional, oPC, fDuration);
}
// 131072 = Optional Level 3 Spell Absorption Effect
if ((iAddBlessings & 131072) == 131072) {
eOptional = EffectSpellLevelAbsorption(3, d8(5));
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eOptional, oPC, fDuration);
}
// Finish up
SetLocalInt(oPC, "iBlessingActive", 1);
AssignCommand(oPC, DelayCommand(fDuration, SetLocalInt(oPC, "iBlessingActive", 0)));
effect eVisual = EffectVisualEffect(VFX_IMP_RESTORATION_GREATER);
ApplyEffectToObject(DURATION_TYPE_INSTANT, eVisual, oPC);
}
// Called from Heal_BeginHealing() function. Does the actual 'healing' involved on the PC and his
// If all HP's should be healed, make iMaxHP = 0. If NO HP's should be healed, make iDoHeal = FALSE
void Heal_RestoreEffect(object oTarget, int iRemoveEffects, int iMinHP, int iMaxHP, int iDoHeal=TRUE) {
// Search for negative effects
effect eBad;
// Remove basic effects
if ((iRemoveEffects & 1) == 1) {
eBad = GetFirstEffect(oTarget);
while(GetIsEffectValid(eBad)) {
if ((GetEffectType(eBad) == EFFECT_TYPE_AC_DECREASE ||
GetEffectType(eBad) == EFFECT_TYPE_ATTACK_DECREASE ||
GetEffectType(eBad) == EFFECT_TYPE_DAMAGE_DECREASE)) RemoveEffect(oTarget, eBad);
eBad = GetNextEffect(oTarget);
}
}
// Remove sensory impairments
if ((iRemoveEffects & 2) == 2) {
eBad = GetFirstEffect(oTarget);
while(GetIsEffectValid(eBad)) {
if ((GetEffectType(eBad) == EFFECT_TYPE_SAVING_THROW_DECREASE ||
GetEffectType(eBad) == EFFECT_TYPE_CURSE ||
GetEffectType(eBad) == EFFECT_TYPE_BLINDNESS ||
GetEffectType(eBad) == EFFECT_TYPE_DEAF)) RemoveEffect(oTarget, eBad);
eBad = GetNextEffect(oTarget);
}
}
// Remove constitution impairments
if ((iRemoveEffects & 4) == 4) {
eBad = GetFirstEffect(oTarget);
while(GetIsEffectValid(eBad)) {
if ((GetEffectType(eBad) == EFFECT_TYPE_DAMAGE_IMMUNITY_DECREASE ||
GetEffectType(eBad) == EFFECT_TYPE_DISEASE ||
GetEffectType(eBad) == EFFECT_TYPE_POISON ||
GetEffectType(eBad) == EFFECT_TYPE_PARALYZE)) RemoveEffect(oTarget, eBad);
eBad = GetNextEffect(oTarget);
}
}
// Remove Level Drains
if ((iRemoveEffects & 8) == 8) {
eBad = GetFirstEffect(oTarget);
while(GetIsEffectValid(eBad)) {
if ((GetEffectType(eBad) == EFFECT_TYPE_NEGATIVELEVEL)) RemoveEffect(oTarget, eBad);
eBad = GetNextEffect(oTarget);
}
}
// Remove Advanced impairments
if ((iRemoveEffects & 16) == 16) {
eBad = GetFirstEffect(oTarget);
while(GetIsEffectValid(eBad)) {
if ((GetEffectType(eBad) == EFFECT_TYPE_ABILITY_DECREASE ||
GetEffectType(eBad) == EFFECT_TYPE_SPELL_RESISTANCE_DECREASE ||
GetEffectType(eBad) == EFFECT_TYPE_SKILL_DECREASE)) RemoveEffect(oTarget, eBad);
eBad = GetNextEffect(oTarget);
}
}
// Heal the target object
if (iDoHeal) {
if(GetRacialType(oTarget) != RACIAL_TYPE_UNDEAD) {
// Determine amount to heal
int nHeal;
if (iMaxHP == 0) nHeal = GetMaxHitPoints(oTarget) - GetCurrentHitPoints(oTarget);
else nHeal = iMinHP + Random(iMaxHP);
// Apply the Healing effect
effect eHeal = EffectHeal(nHeal);
if (nHeal > 0) ApplyEffectToObject(DURATION_TYPE_INSTANT, eHeal, oTarget);
}
}
effect eVisual = EffectVisualEffect(VFX_IMP_RESTORATION_GREATER);
ApplyEffectToObject(DURATION_TYPE_INSTANT, eVisual, oTarget);
}
// Searches the targets inventory for the item with the passed tag and returns the total count for that
// item (including stacked items).
int Inventory_CountItem(object oTarget, string strItemTag) {
int iCount = 0;
object oItem = GetFirstItemInInventory(oTarget);
while (GetIsObjectValid(oItem)) {
if (strItemTag == GetTag(oItem)) iCount += GetNumStackedItems(oItem);
oItem = GetNextItemInInventory(oTarget);
}
return iCount;
}
// Deletes all items in the inventory of the container
void Inventory_DestroyAllItems(object oTarget=OBJECT_SELF) {
object oItem = GetFirstItemInInventory(oTarget);
while (GetIsObjectValid(oItem)) {
DestroyObject(oItem);
oItem = GetNextItemInInventory(oTarget);
}
}
// Returns the total number of items in the containers inventory
int Inventory_ItemCount(object oTarget) {
int iCount = 0;
object oItem = GetFirstItemInInventory(oTarget);
while (GetIsObjectValid(oItem)) {
iCount++;
oItem = GetNextItemInInventory(oTarget);
}
return iCount;
}
// Removes ALL instances of strItemTag from the Targets inventory!!!
void Inventory_RemoveItem(object oTarget, string strItemTag) {
object oItem = GetFirstItemInInventory(oTarget);
while (GetIsObjectValid(oItem)) {
if (strItemTag == GetTag(oItem)) DestroyObject(oItem);
oItem = GetNextItemInInventory(oTarget);
}
}
// Removes iTake instances of strItemTag from the Targets inventory. Should only be used on
// unstackable items as it will take the entire stack!!
int Inventory_RemoveItemNumber(object oTarget, string strItemTag, int iTake=1) {
int iCount=0;
object oItem = GetFirstItemInInventory(oTarget);
while (GetIsObjectValid(oItem)) {
if (strItemTag == GetTag(oItem)) { iCount++; DestroyObject(oItem); }
if (iCount >= iTake) return(iCount);
oItem = GetNextItemInInventory(oTarget);
}
return(iCount);
}
// Used on items that are stacked to take -some- items from the stack, but not
// all.
void Inventory_RemoveStackedItemQuantity(object oTarget, string strItemTag, int iRemoveNum) {
object oItem = GetFirstItemInInventory(oTarget);
int iCount = 0;
int iTotalEmpties = 0;
while ((iCount <= iRemoveNum) && GetIsObjectValid(oItem)) {
if (GetTag(oItem) == strItemTag) {
int iStackSize = GetNumStackedItems(oItem);
iCount += iStackSize;
if (iCount <= iRemoveNum) DestroyObject(oItem);
else {
int iGiveBack = iCount - iRemoveNum;
AssignCommand(oTarget, DelayCommand(1.0, CreateItem(oTarget, strItemTag, iGiveBack)));
DestroyObject(oItem);
}
}
oItem = GetNextItemInInventory(oTarget);
}
}
// Hurls down a flurry of lightning strikes!
void Lightning_DivineFury(object oObject, float fRadius, int iTotalStrikes, int iDoesDamage=FALSE, float fDelayWindow=14.0) {
effect eStrike = EffectVisualEffect(VFX_IMP_LIGHTNING_M);
int iIndex=0;
for (iIndex=0; iIndex < iTotalStrikes; iIndex++) {
// Get a location near the effect center
location lLoc = Location_GetLocationNearObject(oObject, fRadius, fRadius);
float fDelay = 1.0 + Math_RandomFloat(fDelayWindow);
AssignCommand(GetArea(oObject), DelayCommand(fDelay, ApplyEffectAtLocation(DURATION_TYPE_INSTANT, eStrike, lLoc)));
if (iDoesDamage) Lightning_DamageCreatures(lLoc, 12, 3.5);
}
}
// Damage all creatures within range of the strike (if they fail a save)
void Lightning_DamageCreatures(location lStrike, int iDifficulty=15, float fRange=10.0, int iMaxDamage=26, int iMinDamage=6) { // Get the effects
int iDamage = iMinDamage + Random(iMaxDamage - iMinDamage);
effect eREALA = EffectDamage(iDamage, DAMAGE_TYPE_ELECTRICAL);
effect eREALB = EffectKnockdown();
effect eREALC = EffectDeaf();
effect eREALD = EffectACDecrease(12);
// Begin searching for nearby objects that can take damage
object oObject = GetFirstObjectInShape(SHAPE_SPHERE, fRange, lStrike);
while (oObject != OBJECT_INVALID) {
// Only PC's get saving throws... because I like them =)
if (GetIsPC(oObject)) {
int iFortSave = GetFortitudeSavingThrow(oObject) + d20();
if (iFortSave >= iDifficulty) {
SendMessageToPC(oObject, "Lightning Strike! Save (roll: " + IntToString(iFortSave) + ") vs. Fortitude (" + IntToString(iDifficulty) + ") *success*");
// If the PC was too close, takes partial damage anyway
if (GetDistanceBetweenLocations(lStrike, GetLocation(oObject)) <= 1.0) {
SendMessageToPC(oObject, "Too close to the lightning bolt!!!");
eREALA = EffectDamage(d6(3), DAMAGE_TYPE_ELECTRICAL);
ApplyEffectToObject(DURATION_TYPE_INSTANT, eREALA, oObject);
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eREALB, oObject, IntToFloat(d6()));
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eREALC, oObject, IntToFloat(d20()));
if (!GetIsPC(oObject)) ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eREALD, oObject, IntToFloat(d10()));
}
oObject = GetNextObjectInShape(SHAPE_SPHERE, fRange, lStrike);
continue;
}
SendMessageToPC(oObject, "Save (roll: " + IntToString(iFortSave) + ") vs. Fortitude (" + IntToString(iDifficulty) + ") *failure*");
}
// Apply effects and damage to creatures
ApplyEffectToObject(DURATION_TYPE_INSTANT, eREALA, oObject);
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eREALB, oObject, IntToFloat(d10()+d4()));
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eREALC, oObject, IntToFloat(d10()+d4()));
oObject = GetNextObjectInShape(SHAPE_SPHERE, fRange, lStrike);
}
}
// Creates a lightning strike in the target area at a random location. Recurses back into itself!
// If any creatures are nearby, they must make a saving throw or suffer effects.
void Lightning_DoStrike(int iLightning, object oSelf) {
// Get the effect
effect eVFX = EffectVisualEffect(iLightning);
float fRange = GetLocalFloat(oSelf, "fLightningRange");
int iDifficulty = GetLocalInt(oSelf, "iLightningDC");
int iAreaSizeX = GetLocalInt(oSelf, "XSIZE"); // X-Size of the area
int iAreaSizeY = GetLocalInt(oSelf, "YSIZE"); // Y-Size of the area
int iRandmFrequency = GetLocalInt(oSelf, "iLightningRandFreq"); // Interval between strikes, randomized.
int iConstFrequency = GetLocalInt(oSelf, "iLightningConstFreq"); // Interval between strikes - this is the minimum period.
// Generate a random location to do a lightning strike
location lStrike = Location_GetRandomLocation(GetArea(OBJECT_SELF), iAreaSizeX, iAreaSizeY);
// Do a lightning strike visual effect and prepare additional effects if Saving Throws for nearby creatures are failed
ApplyEffectAtLocation(DURATION_TYPE_INSTANT, eVFX, lStrike);
// Check for nearby creatures
Lightning_DamageCreatures(lStrike);
// Do another lightning strike
DelayCommand(IntToFloat(Random(iRandmFrequency) + iConstFrequency), Lightning_DoStrike(iLightning, oSelf)); // Strikes
}
// Entry function for the perpetual Lightning Strike system. The X and Y parameters must be the
// size of the area the lightning will be striking inside of - that information can be found
// in the Toolset. Place in the OnEnter of an Area with a Code-Block command.
void Lightning_Initialize(object oSelf, int iXSize, int iYSize, int iLightningDC=15, float fLightningRange=6.0, int iLightningRandFreq=20, int iLightningConstFreq=9) {
SetLocalInt(oSelf, "XSIZE", iXSize);
SetLocalInt(oSelf, "YSIZE", iYSize);
SetLocalInt(oSelf, "iLightningDC", iLightningDC);
SetLocalFloat(oSelf, "fLightningRange", fLightningRange);
SetLocalInt(oSelf, "iLightningRandFreq", iLightningRandFreq);
SetLocalInt(oSelf, "iLightningConstFreq", iLightningRandFreq);
Lightning_DoStrike(VFX_IMP_LIGHTNING_M, oSelf);
}
// Adds the vector data passed to the position passed and returns a location
location Location_AddVectors(vector vCurrentPosition, float fVectorX, float fVectorY, float fVectorZ, object oObject=OBJECT_SELF) {
float fVX = vCurrentPosition.x + fVectorX;
float fVY = vCurrentPosition.y + fVectorY;
float fVZ = vCurrentPosition.z + fVectorZ;
vector vNewPosition = Vector(fVX, fVY, fVZ);
return(Location(GetArea(oObject), vNewPosition, IntToFloat(Random(180))));
}
// Uses the X and Y ranges passed to determine a random point near the waypoint supplied.
location Location_GetLocationNearObject(object oObject, float fXRange=1.0, float fYRange=1.0) {
// Get the position of the Waypoint
vector vCenter = GetPosition(oObject);
// Generate a new location nearby
float fNewX = vCenter.x + (Math_RandomFloatSign() * Math_RandomFloat(fXRange));
float fNewY = vCenter.y + (Math_RandomFloatSign() * Math_RandomFloat(fYRange));
vector vNewPosition = Vector(fNewX, fNewY, vCenter.z);
return(Location(GetArea(oObject), vNewPosition, IntToFloat(Random(180))));
}
// Uses the X and Y ranges passed to determine a random point near the waypoint supplied.
location Location_GetLocationNearWaypoint(string strWaypointTag, float fXRange=1.0, float fYRange=1.0) {
// Get the position of the Waypoint
object oWaypoint = GetObjectByTag(strWaypointTag);
vector vCenter = GetPosition(oWaypoint);
// Generate a new location nearby
float fNewX = vCenter.x + (Math_RandomFloatSign() * Math_RandomFloat(fXRange));
float fNewY = vCenter.y + (Math_RandomFloatSign() * Math_RandomFloat(fYRange));
vector vNewPosition = Vector(fNewX, fNewY, vCenter.z);
return(Location(GetArea(oWaypoint), vNewPosition, IntToFloat(Random(180))));
}
// Returns a random location within the range of 0->iXSize and 0->iYSize
location Location_GetRandomLocation(object oArea, int iXSize, int iYSize, int iXMargin=0, int iYMargin=0, int iHeight=0) {
float fX = IntToFloat(Random(iXSize - iXMargin) + iXMargin);
float fY = IntToFloat(Random(iYSize - iYMargin) + iYMargin);
float fZ = IntToFloat(iHeight);
vector vStrike = Vector(fX, fY, fZ);
location lStrike = Location(oArea, vStrike, IntToFloat(Random(180)));
return lStrike;
}
// Ensures that fValue is within range of (fPoint +/- fRange); if not, it will return a corrected
// value that is.
float Math_FitFloatToBoundaries(float fValue, float fPoint, float fRange) {
// Check the value is in bounds
if (fValue > fPoint + fRange) fValue = (fPoint + fRange) - Math_RandomFloat(0.5);
if (fValue < fPoint - fRange) fValue = (fPoint - fRange) + Math_RandomFloat(0.5);
// Return final value
return fValue;
}
// Computes a random float value, including the decimal portion (2-digits only)
float Math_RandomFloat(float fMaxValue) {
float fWhole = fabs(fMaxValue);
int iWhole = Random(FloatToInt(fWhole));
int iDecimal = FloatToInt(100.0 * (fMaxValue - fWhole));
if (iDecimal == 0) iDecimal = 100;
float fDecimal = IntToFloat(Random(iDecimal)) / 100.0;
float fFinal = IntToFloat(iWhole) + fDecimal;
return fFinal;
}
// Returns a -1.0 or 1.0 which can be multiplied to another value to randomly determine the sign of the result.
float Math_RandomFloatSign() {
if (d100() > 50) return 1.0;
return -1.0;
}
// Returns a -1 or 1 which can be multiplied to another value to randomly determine the sign of the result.
int Math_RandomIntSign() {
if (d100() > 50) return 1;
return -1;
}
// Use this (called typically from OnSpawn) function to add a regular effect to an object permanently (a
// minor distinction I make between an effect and a property is that properties are perm.)
void Object_AddProperty(int iEffect, int iParamA=1, int iParamB=4, int iParamC=1, object oObject=OBJECT_SELF) {
effect eFX;
if (iEffect == EFFECT_TYPE_REGENERATE) eFX = EffectRegenerate(iParamA, IntToFloat(iParamB));
if (iEffect == EFFECT_TYPE_TEMPORARY_HITPOINTS) eFX = EffectTemporaryHitpoints(iParamA);
if ((iEffect == EFFECT_TYPE_ABILITY_INCREASE || iEffect == EFFECT_TYPE_ABILITY_DECREASE) && iParamB > 0) eFX = EffectAbilityIncrease(iParamA, iParamB);
if ((iEffect == EFFECT_TYPE_ABILITY_INCREASE || iEffect == EFFECT_TYPE_ABILITY_DECREASE) && iParamB < 0) eFX = EffectAbilityDecrease(iParamA, abs(iParamB));
if (iEffect == EFFECT_TYPE_TRUESEEING) eFX = EffectTrueSeeing();
ApplyEffectToObject(DURATION_TYPE_PERMANENT, eFX, oObject);
}
// Object_CheckDamageVsResistances() - Called from the obects 'On Damaged' slot. This function processes the results of a successful attack on a monster versus
// the creatures resistances, which are passed in the params. Determines if the
// (and how much) damage the creature takes and if it can be destroyed by the attack.
// 1) Minimum HP's to allow going down
// 2) Amount of Special Damage needed to effect creature. Will only count if creature is 'down'.
// 3) When creature is down, only massive damage will effect it. Damage done by PC's is REDUCED by this amount.
// 4) Period that creature will be down for
// 5) Bitwise AND specifying ABSENT resistances as follows:
// 1 DAMAGE_TYPE_ACID
// 2 DAMAGE_TYPE_BLUDGEONING
// 4 DAMAGE_TYPE_COLD
// 8 DAMAGE_TYPE_DIVINE
// 16 DAMAGE_TYPE_ELECTRICAL
// 32 DAMAGE_TYPE_FIRE
// 64 DAMAGE_TYPE_MAGICAL
// 128 DAMAGE_TYPE_NEGATIVE
// 256 DAMAGE_TYPE_PIERCING
// 512 DAMAGE_TYPE_POSITIVE
// 1024 DAMAGE_TYPE_SLASHING
// 2048 DAMAGE_TYPE_SONIC
// Specifying '41' indicates the creature IS NOT IMMUNE to Acid, Divine, or Fire damage, but is resistant
// to all other types of damage.
// 6) The final parameter passed is the object ID of the calling object.
//
void Object_CheckDamageVsResistances(int iDown, int iSpecDam, int iDReduction, float fDur, int iResistances, object oSelf=OBJECT_SELF) {
// Test object for a knock-down
if ((GetCurrentHitPoints(oSelf) <= iDown) && (!GetLocalInt(oSelf, "iDown"))) { // The creature is near death, put him down
ClearAllActions();
SetLocalInt(oSelf , "iDown", TRUE); // Set flag on creature to avoid this code again until object gets up
// Apply resistances
if ((iResistances & 1) == FALSE) ApplyEffectToObject(DURATION_TYPE_TEMPORARY, EffectDamageResistance(DAMAGE_TYPE_ACID, iDReduction), oSelf, fDur);
if ((iResistances & 2) == FALSE) ApplyEffectToObject(DURATION_TYPE_TEMPORARY, EffectDamageResistance(DAMAGE_TYPE_BLUDGEONING, iDReduction), oSelf, fDur);
if ((iResistances & 4) == FALSE) ApplyEffectToObject(DURATION_TYPE_TEMPORARY, EffectDamageResistance(DAMAGE_TYPE_COLD, iDReduction), oSelf, fDur);
if ((iResistances & 8) == FALSE) ApplyEffectToObject(DURATION_TYPE_TEMPORARY, EffectDamageResistance(DAMAGE_TYPE_DIVINE, iDReduction), oSelf, fDur);
if ((iResistances & 16) == FALSE) ApplyEffectToObject(DURATION_TYPE_TEMPORARY, EffectDamageResistance(DAMAGE_TYPE_ELECTRICAL, iDReduction), oSelf, fDur);
if ((iResistances & 32) == FALSE) ApplyEffectToObject(DURATION_TYPE_TEMPORARY, EffectDamageResistance(DAMAGE_TYPE_FIRE, iDReduction), oSelf, fDur);
if ((iResistances & 64) == FALSE) ApplyEffectToObject(DURATION_TYPE_TEMPORARY, EffectDamageResistance(DAMAGE_TYPE_MAGICAL, iDReduction), oSelf, fDur);
if ((iResistances & 128) == FALSE) ApplyEffectToObject(DURATION_TYPE_TEMPORARY, EffectDamageResistance(DAMAGE_TYPE_NEGATIVE, iDReduction), oSelf, fDur);
if ((iResistances & 256) == FALSE) ApplyEffectToObject(DURATION_TYPE_TEMPORARY, EffectDamageResistance(DAMAGE_TYPE_PIERCING, iDReduction), oSelf, fDur);
if ((iResistances & 512) == FALSE) ApplyEffectToObject(DURATION_TYPE_TEMPORARY, EffectDamageResistance(DAMAGE_TYPE_POSITIVE, iDReduction), oSelf, fDur);
if ((iResistances & 1024) == FALSE) ApplyEffectToObject(DURATION_TYPE_TEMPORARY, EffectDamageResistance(DAMAGE_TYPE_SLASHING, iDReduction), oSelf, fDur);
if ((iResistances & 2048) == FALSE) ApplyEffectToObject(DURATION_TYPE_TEMPORARY, EffectDamageResistance(DAMAGE_TYPE_SONIC, iDReduction), oSelf, fDur);
// Apply Knockdown FX
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, EffectKnockdown(), oSelf, fDur);
// Set delay for Knockdown to be lifted
DelayCommand(fDur, ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectHeal(GetMaxHitPoints()), oSelf, fDur));
DelayCommand(fDur, SetLocalInt(oSelf, "knee", 0));
}
// If knocked down, we can apply special damages (those creature is NOT resistant too) and kill the critter
if ((GetLocalInt(oSelf, "iDown") == 1)) {
location lLoc = GetLocation(oSelf);
// ON ACID DAMAGE Spew the object
if (GetDamageDealtByType(DAMAGE_TYPE_ACID) >= iSpecDam) {
ClearAllActions();
DelayCommand(0.3, ApplyEffectAtLocation(DURATION_TYPE_TEMPORARY, EffectVisualEffect(VFX_COM_CHUNK_GREEN_SMALL), lLoc,5.0));
DelayCommand(0.4, ApplyEffectAtLocation(DURATION_TYPE_TEMPORARY, EffectVisualEffect(VFX_COM_BLOOD_LRG_GREEN), lLoc,5.0));
DelayCommand(1.0, ApplyEffectAtLocation(DURATION_TYPE_TEMPORARY, EffectVisualEffect(VFX_COM_HIT_ACID), lLoc,5.0));
DelayCommand(0.1, ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDeath(TRUE), oSelf));
}
// ON BLUDGEON DAMAGE Smash the object
if (GetDamageDealtByType(DAMAGE_TYPE_BLUDGEONING) >= iSpecDam) {
ClearAllActions();
DelayCommand(0.3, ApplyEffectAtLocation(DURATION_TYPE_TEMPORARY, EffectVisualEffect(VFX_COM_BLOOD_LRG_RED), lLoc,5.0));
DelayCommand(0.4, ApplyEffectAtLocation(DURATION_TYPE_TEMPORARY, EffectVisualEffect(VFX_COM_CHUNK_BONE_MEDIUM), lLoc,5.0));
DelayCommand(2.5, ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDeath(TRUE), oSelf));
}
// ON COLD DAMAGE Freeze the object
if (GetDamageDealtByType(DAMAGE_TYPE_COLD) >= iSpecDam) {
ClearAllActions();
DelayCommand(0.3, ApplyEffectAtLocation(DURATION_TYPE_TEMPORARY, EffectVisualEffect(VFX_COM_CHUNK_RED_SMALL), lLoc,5.0));
DelayCommand(0.4, ApplyEffectAtLocation(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_FROST_L), lLoc));
DelayCommand(2.5, ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDeath(TRUE), oSelf));
}
// ON DIVINE DAMAGE Strike the object
if (GetDamageDealtByType(DAMAGE_TYPE_DIVINE) >= iSpecDam) {
ClearAllActions();
DelayCommand(0.3, ApplyEffectAtLocation(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_DIVINE_STRIKE_FIRE), lLoc));
DelayCommand(0.4, ApplyEffectAtLocation(DURATION_TYPE_TEMPORARY, EffectVisualEffect(VFX_COM_BLOOD_LRG_RED), lLoc, 8.0));
DelayCommand(0.8, ApplyEffectAtLocation(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_DIVINE_STRIKE_HOLY), lLoc));
DelayCommand(2.5, ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDeath(TRUE), oSelf));
}
// ON ELECTRICAL DAMAGE Fry the object
if (GetDamageDealtByType(DAMAGE_TYPE_ELECTRICAL) >= iSpecDam) {
ClearAllActions();
DelayCommand(0.3, ApplyEffectAtLocation(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_COM_HIT_ELECTRICAL), lLoc));
DelayCommand(2.5, ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDeath(TRUE), oSelf));
}
// ON FIRE DAMAGE Immolate the object
if (GetDamageDealtByType(DAMAGE_TYPE_FIRE) >= iSpecDam) {
ClearAllActions();
DelayCommand(1.5, ApplyEffectAtLocation(DURATION_TYPE_TEMPORARY, EffectVisualEffect(VFX_IMP_FLAME_M), lLoc, 5.5));
DelayCommand(2.5, ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDeath(TRUE), oSelf));
}
// ON MAGICAL DAMAGE Fireworks on the object
if (GetDamageDealtByType(DAMAGE_TYPE_MAGICAL) >= iSpecDam) {
ClearAllActions();
DelayCommand(1.5, ApplyEffectAtLocation(DURATION_TYPE_TEMPORARY, EffectVisualEffect(VFX_COM_BLOOD_REG_GREEN), lLoc, 4.5));
DelayCommand(1.5, ApplyEffectAtLocation(DURATION_TYPE_TEMPORARY, EffectVisualEffect(VFX_COM_BLOOD_CRT_RED), lLoc, 5.5));
DelayCommand(1.5, ApplyEffectAtLocation(DURATION_TYPE_TEMPORARY, EffectVisualEffect(VFX_COM_CHUNK_BONE_MEDIUM), lLoc, 6.0));
DelayCommand(2.5, ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDeath(TRUE), oSelf));
}
// ON NEGATIVE DAMAGE Neg effect
if (GetDamageDealtByType(DAMAGE_TYPE_NEGATIVE) >= iSpecDam) {
ClearAllActions();
DelayCommand(1.5, ApplyEffectAtLocation(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_COM_HIT_NEGATIVE), lLoc));
DelayCommand(1.8, ApplyEffectAtLocation(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_NEGATIVE_ENERGY), lLoc));
DelayCommand(2.5, ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDeath(TRUE), oSelf));
}
// ON PIERCE DAMAGE Do some gore
if (GetDamageDealtByType(DAMAGE_TYPE_PIERCING) >= iSpecDam) {
ClearAllActions();
DelayCommand(1.5, ApplyEffectAtLocation(DURATION_TYPE_TEMPORARY, EffectVisualEffect(VFX_COM_CHUNK_RED_SMALL), lLoc, 5.5));
DelayCommand(2.5, ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDeath(TRUE), oSelf));
}
// ON POSITIVE DAMAGE Pos effect
if (GetDamageDealtByType(DAMAGE_TYPE_POSITIVE) >= iSpecDam) {
ClearAllActions();
DelayCommand(1.5, ApplyEffectAtLocation(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_COM_HIT_NEGATIVE), lLoc));
DelayCommand(1.8, ApplyEffectAtLocation(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_NEGATIVE_ENERGY), lLoc));
DelayCommand(2.5, ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDeath(TRUE), oSelf));
}
// ON SLASH DAMAGE Slash it up
if (GetDamageDealtByType(DAMAGE_TYPE_PIERCING) >= iSpecDam) {
ClearAllActions();
DelayCommand(1.5, ApplyEffectAtLocation(DURATION_TYPE_TEMPORARY, EffectVisualEffect(VFX_COM_CHUNK_RED_SMALL), lLoc, 5.5));
DelayCommand(2.5, ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDeath(TRUE), oSelf));
}
// ON SONIC
if (GetDamageDealtByType(DAMAGE_TYPE_PIERCING) >= iSpecDam) {
ClearAllActions();
DelayCommand(1.5, ApplyEffectAtLocation(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_HEAD_SONIC), lLoc));
DelayCommand(2.5, ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDeath(TRUE), oSelf));
}
}
}
// Search the object's inventory and return true if the item passed was found inside. False otherwise...
int Object_CheckInventoryForItem(string strResource, object oObject) {
object oItem = GetFirstItemInInventory();
string strItemTag;
while (GetIsObjectValid(oItem)) {
strItemTag = GetTag(oItem);
if (strItemTag == strResource) return (TRUE);
oItem = GetNextItemInInventory();
}
return (FALSE);
}
// Counts the number of objects that are of the same type as the caller from a specified center point
int Object_CountSameAtLocation(object oObject, location lCenter, float fSize=5.0, int iShape=SHAPE_SPHERE) {
string strName = GetName(oObject);
int iCount = 0;
// Search for similair objects
object oFound = GetFirstObjectInShape(iShape, fSize, lCenter, TRUE, OBJECT_TYPE_CREATURE);
while (GetIsObjectValid(oFound)) {
if (strName == GetName(oFound)) iCount++;
oFound = GetNextObjectInShape(iShape, fSize, lCenter, TRUE, OBJECT_TYPE_CREATURE);
}
// Return count
return iCount;
}
// Create an object (similair to CreateObject()) - useful for creating objects with DelayCommand().
void Object_CreateObject(int nObjectType, string sTemplate, location lLocation, int bUseAppearAnimation=FALSE) {
CreateObject(nObjectType, sTemplate, lLocation, bUseAppearAnimation);
}
// Called (usually from an OnSpawn) to determine if a creature is a regenerating monster
void Object_EnableRegenerators(object oObject=OBJECT_SELF) {
float fCR = GetChallengeRating(oObject);
// Check for undead
if (Object_IsClassAndRace(CLASS_TYPE_UNDEAD, oObject)) {
if (fCR < 4.0) Object_AddProperty(EFFECT_TYPE_REGENERATE);
if (fCR >= 4.0 && fCR < 6.0) Object_AddProperty(EFFECT_TYPE_REGENERATE, 1, 3);
if (fCR == 6.0) Object_AddProperty(EFFECT_TYPE_REGENERATE, 1, 2);
if (fCR >= 7.0 && fCR < 10.0) Object_AddProperty(EFFECT_TYPE_REGENERATE, 2, 3);
if (fCR >= 10.0) Object_AddProperty(EFFECT_TYPE_REGENERATE, 2, 2);
}
// Check for trolls
if (Object_IsClassAndRace(CLASS_TYPE_GIANT, OBJECT_SELF, FALSE, "troll")) {
if (fCR <= 7.0) Object_AddProperty(EFFECT_TYPE_REGENERATE, 1, 2);
else if (fCR < 9.0) Object_AddProperty(EFFECT_TYPE_REGENERATE, 2, 3);
else if (fCR >= 9.0) Object_AddProperty(EFFECT_TYPE_REGENERATE, 3, 3);
}
// Check for lycanthropes
if (Object_IsClassAndRace(CLASS_TYPE_SHAPECHANGER, oObject, FALSE, "were")) {
if (fCR < 8.0) Object_AddProperty(EFFECT_TYPE_REGENERATE);
if (fCR >= 8.0 && fCR < 14.0) Object_AddProperty(EFFECT_TYPE_REGENERATE, 1, 3);
if (fCR >= 14.0) Object_AddProperty(EFFECT_TYPE_REGENERATE, 2, 4);
}
// Check for regular animals
if (Object_IsClassAndRace(CLASS_TYPE_ANIMAL, oObject, TRUE)) Object_AddProperty(EFFECT_TYPE_REGENERATE, 1, 8);
}
// Causes the object calling the function to stop combat
void Object_ForceEndCombat(object oAttacker) {
AssignCommand(oAttacker, ClearAllActions());
AssignCommand(oAttacker, SurrenderToEnemies());
ClearPersonalReputation(oAttacker);
}
// Places iStack instances of strItemTag onto the object if the function has NOT already been called for
// the object
void Object_InitializeInventory(string strItemTag, int iStack=4, object oObject=OBJECT_SELF) {
if (GetLocalInt(oObject, "iInventoryInit")) return;
SetLocalInt(oObject, "iInventoryInit", TRUE);
int i;
for (i=0; i<iStack; i++) CreateItemOnObject(strItemTag);
}
// Intended for animals only - determines if the creature is prey or predator. Returns TRUE if it's a prey
// item.
int Object_IsPrey(object oObject=OBJECT_SELF) {
string strAnimalName = GetStringLowerCase(GetName(oObject));
if (String_IsSubString(strAnimalName, "chicken") ||
String_IsSubString(strAnimalName, "raven") ||
String_IsSubString(strAnimalName, "boar") ||
String_IsSubString(strAnimalName, "cow") ||
String_IsSubString(strAnimalName, "deer") ||
String_IsSubString(strAnimalName, "ox") ||
String_IsSubString(strAnimalName, "stag")) return TRUE; // Prey
return FALSE; // Predator
}
// Checks too see if the object passed in has levels in iClassType; if the race of a class is needed
// then pass strName (eg. "hill" for CLASS_TYPE_GIANT, to return whether or not the object is a Hill
// Giant) - note that strName must be part of the object's name as specified in the Toolset.
// If the creature can have NO other classes than iClassType, pass iExclusive as TRUE.
int Object_IsClassAndRace(int iClassType, object oObject=OBJECT_SELF, int iExclusive=FALSE, string strName="") {
// Check too see if they have any levels in the given class
int iLevel = GetLevelByClass(iClassType, oObject);
if (!iExclusive) {
if (iLevel > 0) {
// If no Race is needed, return the level
if (GetStringLength(strName) == 0) return iLevel;
else {
// Race is needed, so try and get a match from the objects given name.
string strLName = GetStringLowerCase(strName);
string strOName = GetStringLowerCase(GetName(oObject));
if (String_IsSubString(strOName, strLName)) return iLevel;
}
}
}
else { // iExclusive was TRUE, so creature can have ONLY levels in iClassType
int iAllLevels = GetLevel(oObject);
if (iAllLevels == iLevel) { // All Levels are in iClassType
// If no Race is needed, return the level
if (GetStringLength(strName) == 0) return iLevel;
else {
// Race is needed, so try and get a match from the objects given name.
string strLName = GetStringLowerCase(strName);
string strOName = GetStringLowerCase(GetName(oObject));
if (String_IsSubString(strOName, strLName)) return iLevel;
}
}
}
return FALSE;
}
// Checks to see if a PC is near the calling object, returns TRUE if a PC is within fDist.
int Object_IsPCNear(object oObject=OBJECT_SELF, float fDist=20.0) {
object oPC = GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR, PLAYER_CHAR_IS_PC);
float fDistance = GetDistanceBetween(oObject, oPC);
if (fDistance < fDist) return TRUE;
return FALSE;
}
// Returns the object inside the shape centered at lCenter that is the same species as the caller and is
// closest than any other candidates.
object Object_GetClosestSameInLocationShape(location lCenter, object oObject=OBJECT_SELF, float fSize=5.0, int iShape=SHAPE_SPHERE) {
string strName = GetName(oObject);
object oTemp;
float fTemp;
float fDistance = 1000.0; // Arbitrarily high (no meaning)
// Search for similair objects
object oFound = GetFirstObjectInShape(iShape, fSize, lCenter, TRUE, OBJECT_TYPE_CREATURE);
while (GetIsObjectValid(oFound)) {
if (strName == GetName(oFound)) {
fTemp = GetDistanceBetween(oObject, oFound);
if (fTemp < fDistance) { fDistance = fTemp; oTemp = oFound; }
}
oFound = GetNextObjectInShape(iShape, fSize, lCenter, TRUE, OBJECT_TYPE_CREATURE);
}
// Return nearest object
return oTemp;
}
vector Object_GetDensityCenter(location lCenter, object oObject=OBJECT_SELF, float fSize=5.0, int iShape=SHAPE_SPHERE) {
vector vAverage;
vector vFinal;
vector vFound;
float fCount = 0.0;
// Search for similair objects
object oFound = GetFirstObjectInShape(iShape, fSize, lCenter, TRUE, OBJECT_TYPE_CREATURE);
string strName = GetName(oObject);
while (GetIsObjectValid(oFound)) {
if (strName == GetName(oFound)) {
fCount += 1.0;
vFound = GetPosition(oFound);
vAverage.x += vFound.x;
vAverage.y += vFound.y;
vAverage.z += vFound.z;
}
oFound = GetNextObjectInShape(iShape, fSize, lCenter, TRUE, OBJECT_TYPE_CREATURE);
}
vFinal.x = vAverage.x / fCount;
vFinal.y = vAverage.y / fCount;
vFinal.z = vAverage.z / fCount;
return vFinal;
}
// Examines all the DC's for the object and returns a difficulty (a.k.a 'quality') rating.
// Pass with iReturnRaw as TRUE and get the sum of all the object's DC's instead of a QUALITY_* constant.
int Object_GetDifficulty(object oObject, int iReturnRaw=FALSE) {
int iTotal = GetLockUnlockDC(oObject);
iTotal += GetTrapDetectDC(oObject);
iTotal += GetTrapDisarmDC(oObject);
if (iReturnRaw) return iTotal;
if (iTotal < 19) return FloatToInt(QUALITY_LOW);
if (iTotal >= 19 && iTotal < 28) return FloatToInt(QUALITY_MEDIUM);
return FloatToInt(QUALITY_HIGH);
}
// Returns the first object found, inside a circle of fDistance meters that is
// of the same species as the object passed in.
object Object_GetNearestSame(object oObject=OBJECT_SELF, float fDistance=5.0) {
location lCenter = GetLocation(oObject);
object oFound;
object oNearest = GetFirstObjectInShape(SHAPE_SPHERE, fDistance, lCenter, TRUE, OBJECT_TYPE_CREATURE);
while (GetIsObjectValid(oNearest)) {
if ((GetName(oObject) == GetName(oNearest)) && (oObject != oNearest)) { oFound = oNearest; break; }
oNearest = GetNextObjectInShape(SHAPE_SPHERE, fDistance, lCenter, TRUE, OBJECT_TYPE_CREATURE);
}
return oFound;
}
// Returns one of the OBJECT_TYPE_* constants for the object that was passed
int Object_GetType(object oObject) {
string strTag = GetStringLowerCase(GetName(oObject));
if (String_IsSubString(strTag, "barrel")) return OBJECT_TYPE_BARREL;
if (String_IsSubString(strTag, "book")) return OBJECT_TYPE_BOOKS;
if (String_IsSubString(strTag, "chest")) return OBJECT_TYPE_CHEST;
if (String_IsSubString(strTag, "crate") || String_IsSubString(strTag, "box")) return OBJECT_TYPE_CRATE;
if (String_IsSubString(strTag, "treasure")) return OBJECT_TYPE_GOLD_PILE;
if (String_IsSubString(strTag, "loot")) return OBJECT_TYPE_LOOTBAG;
if (String_IsSubString(strTag, "supply")) return OBJECT_TYPE_WIZARD_CABINET;
return(OBJECT_TYPE_MISCELLANEOUS);
}
// Changes the creatures stats so that they are unique to the individual; changes are not large
// enough to constitute differentiation to a 'new species'.
void Object_GiveUniqueAbilities(object oObject=OBJECT_SELF) {
// All creatures receive a MOD from their basic stats
Object_AddProperty(EFFECT_TYPE_ABILITY_INCREASE, ABILITY_STRENGTH, Math_RandomIntSign() * d2());
Object_AddProperty(EFFECT_TYPE_ABILITY_INCREASE, ABILITY_CONSTITUTION, Math_RandomIntSign() * d2());
Object_AddProperty(EFFECT_TYPE_ABILITY_INCREASE, ABILITY_INTELLIGENCE, Math_RandomIntSign() * d2());
Object_AddProperty(EFFECT_TYPE_ABILITY_INCREASE, ABILITY_CHARISMA, Math_RandomIntSign() * d2());
Object_AddProperty(EFFECT_TYPE_ABILITY_INCREASE, ABILITY_WISDOM, Math_RandomIntSign() * d2());
Object_AddProperty(EFFECT_TYPE_ABILITY_INCREASE, ABILITY_DEXTERITY, Math_RandomIntSign() * d2());
Object_AddProperty(EFFECT_TYPE_TEMPORARY_HITPOINTS, d8(GetHitDice(OBJECT_SELF))); // Give critters unique hit points
}
// Usually called from the OnSpawn of a creature in order to change its faction to common (if it's an
// animal)
void Object_MakeAnimalsCommon(object oObject=OBJECT_SELF) {
// Check to be sure the creature passed in was an animal
if (!Object_IsClassAndRace(CLASS_TYPE_ANIMAL, oObject, TRUE)) return;
////////////////////////////////////////////////////////////////////////////
// Creatures that are exempt from faction changes (remain hostile) /////////
////////////////////////////////////////////////////////////////////////////
// All dire creatures are hostile
string strCritterName = GetStringLowerCase(GetName(oObject));
if (String_IsSubString(strCritterName, "dire") ||
String_IsSubString(strCritterName, "rat") ||
String_IsSubString(strCritterName, "badger")) return;
// Check if the animal was a non-prey item. If so, change to Defender.
string strAnimalName = GetName(oObject);
if (!Object_IsPrey(oObject)) ChangeToStandardFaction(oObject, STANDARD_FACTION_DEFENDER); // No need to worry about 'prey' item factions since they're common by default
}
// Respawns the passed item onto the passed object but only if the object's inventory does not already
// have an instance of the item.
void Object_RespawnContents(string strResource, int iStack=1, object oObject=OBJECT_SELF) {
if (Object_CheckInventoryForItem(strResource, oObject)) return; // If object already has special item in it, don't create a new one
int i;
for (i=0; i<iStack; i++) CreateItemOnObject(strResource, oObject);
}
// Creates the items contained in strInventoryArray on the object passed
void Object_RespawnSpecialInventory(object oObject, string strInventoryArray) {
int iElements = Array_GetTotalElements(strInventoryArray);
int iIndex;
for (iIndex=0; iIndex<iElements; iIndex+=2) {
string strItemTag = Array_GetElement(iIndex, strInventoryArray);
string strStack = Array_GetElement(iIndex+1, strInventoryArray);
CreateItemOnObject(strItemTag, oObject, StringToInt(strStack));
}
}
// Very dirty method of getting a Spell ID from the PC... don't try this at home, kids.
// Returns a -1 if the list was exhausted without finding a matching spell!
int Object_SelectMemorizedSpell(object oCaster) {
int iIndex;
for (iIndex=0; iIndex<1000; iIndex++) {
if (GetHasSpell(iIndex, oCaster)) return iIndex;
}
return -1;
}
// Creates a monster at the location of the PC's death - called from "nw_o0_death"
void Object_SpawnMonsterFromPC(object oPlayer) {
object oCreature = GetLastHostileActor(oPlayer);
if (GetIsObjectValid(oCreature)) {
// If PC was killed by an undead - create another one!
if (Object_IsClassAndRace(CLASS_TYPE_UNDEAD, oCreature)) {
location lLoc = GetLocation(oPlayer);
string strResRef = GetResRef(oCreature);
float fDelay = IntToFloat(d6());
effect eFX = EffectVisualEffect(VFX_IMP_RAISE_DEAD);
AssignCommand(GetArea(oPlayer), DelayCommand(fDelay, ApplyEffectAtLocation(DURATION_TYPE_INSTANT, eFX, lLoc)));
AssignCommand(GetArea(oPlayer), DelayCommand(fDelay, Object_CreateObject(OBJECT_TYPE_CREATURE, strResRef, lLoc)));
}
}
}
// If any items are in the container, this function will store them. Upon Respawn of the object,
// the items can be recreated.
string Object_StoreSpecialInventory(object oObject=OBJECT_SELF) {
// Count and store the tags of the items in the object's inventory (presumably these
// were present -before- treasure was generated.
int iCount=0;
object oItem = GetFirstItemInInventory(oObject);
string strInventoryArray="";
while (GetIsObjectValid(oItem)) {
string strItemTag = GetTag(oItem);
int iStackSize = GetNumStackedItems(oItem);
strInventoryArray = Array_AddElement(strItemTag, strInventoryArray);
strInventoryArray = Array_AddElement(IntToString(iStackSize), strInventoryArray);
oItem = GetNextItemInInventory(oObject);
iCount++;
}
// Store the Array
SetLocalString(oObject, "strInventoryArray", strInventoryArray);
return(strInventoryArray);
}
// Shrine of Ptah code, able to regenerate the charges on items with appropriate sacrafice.
// Should be in the OnClose slot for a placeable object with inventory. Works for just one
// item in inventory.
void Placeable_Altar_ShrineOfPtah(object oCloser, object oObject=OBJECT_SELF) {
// Any items in container?
if (!Inventory_ItemCount(oObject)) return;
// Get the tag of the last non-gem item in container
object oItem = GetFirstItemInInventory(oObject);
object oAntique;
string strItemTag;
int iOffering = 0;
float fGold;
while (GetIsObjectValid(oItem)) {
if (GetBaseItemType(oItem) != BASE_ITEM_GEM) {
oAntique = oItem;
strItemTag = GetTag(oItem);
fGold = IntToFloat(GetGoldPieceValue(oItem)); // Determine what the object is worth
}
else iOffering += GetGoldPieceValue(oItem); // Total the value of the gem-offering
oItem = GetNextItemInInventory(oObject);
}
int iLen = GetStringLength(strItemTag);
if (iLen == 0 || iLen == -1) return;
// Is it stacked? If so, do nothing.
if (GetNumStackedItems(oItem) > 1) return;
// Check if the offering is accepted
float fLevel = IntToFloat(GetLevel(oCloser));
fGold *= 0.15 + (0.01 * fLevel) + (0.005 * fLevel);
float fOffering = IntToFloat(iOffering);
if (fGold > fOffering) return;
// Exit if the Shrine has been used successfully recently
float fDelay = 1200.0 + Math_RandomFloat(1200.0);
if (BlockMultiActivation(GetTag(oObject), oObject, fDelay)) return;
// Take the offering and restore the item!
Inventory_DestroyAllItems(oObject);
object oNew = CreateItemOnObject(strItemTag, oObject);
SetIdentified(oNew, TRUE);
// Do lightning effect!
Lightning_DivineFury(oObject, 10.0, 20);
// Do the normal FX!
effect eVFXC = EffectVisualEffect(VFX_IMP_HOLY_AID); // For shrine
effect eVFXE = EffectVisualEffect(VFX_FNF_SCREEN_SHAKE);
effect eVFXD = EffectVisualEffect(VFX_FNF_HOWL_ODD); // For shrine
ApplyEffectAtLocation(DURATION_TYPE_INSTANT, eVFXE, GetLocation(oObject));
ApplyEffectToObject(DURATION_TYPE_INSTANT, eVFXC, oObject);
ApplyEffectToObject(DURATION_TYPE_INSTANT, eVFXD, oObject);
}
// Gets the state of the lever specified
int Placeable_Lever_GetState(object oLever) {
return(GetLocalInt(oLever, "iState"));
}
// Switches the state of the lever that was pulled, stores the state variable
// on it and returns that state to the caller. BE SURE to set the default state
// of the Lever to DEACTIVATED from the toolset!!!
int Placeable_Lever_SwitchState(object oLever) {
int iState = GetLocalInt(oLever, "iState");
if (iState == TRUE) {
iState = FALSE;
PlayAnimation(ANIMATION_PLACEABLE_DEACTIVATE);
AssignCommand(oLever, SpeakString("Off"));
}
else {
iState = TRUE;
PlayAnimation(ANIMATION_PLACEABLE_ACTIVATE);
AssignCommand(oLever, SpeakString("On"));
}
// Store the lever's state
SetLocalInt(oLever, "iState", iState);
return iState;
}
// Allows the player a chance to revive if a temple is not in range
void Player_HeartBeat_Raise(object oPC, float fHeartBeat=6.0, int iReviveDC = 25) {
int iFortSave = GetFortitudeSavingThrow(oPC) + d20();
int iWillSave = GetWillSavingThrow(oPC) + d20();
int iDeathDC = 23;
int iWillDC = 24;
if (GetLevel(oPC) < 5) { iDeathDC -= 8; iWillDC -= 7; }
else if (GetLevel(oPC) < 10) { iDeathDC -= 4; iWillDC -= 3; }
else if (GetLevel(oPC) < 15) { iDeathDC -= 2; iWillDC -=1; }
if (iFortSave >= iDeathDC) {
SendMessageToPC(oPC, "Save (roll: " + IntToString(iFortSave) + ") vs. Death (" + IntToString(iDeathDC) + ") *success*");
if (iWillSave >= iWillDC) {
SendMessageToPC(oPC, "Save (roll: " + IntToString(iWillSave) + ") vs. Will (" + IntToString(iWillDC) + ") *success*");
Player_RestoreToLife(oPC);
return;
}
else SendMessageToPC(oPC, "Save (roll: " + IntToString(iWillSave) + ") vs. Will (" + IntToString(iWillDC) + ") *failure*");
}
else SendMessageToPC(oPC, "Save (roll: " + IntToString(iFortSave) + ") vs. Death (" + IntToString(iDeathDC) + ") *failure*");
AssignCommand(oPC, DelayCommand(fHeartBeat, Player_HeartBeat_Revive(oPC)));
}
// Called from the modules OnPlayerDeath event. In case the PC is out of range of a Temple and cannot
// respawn, he should have the chance of reviving in time
void Player_HeartBeat_Revive(object oPC, float fHeartBeat=6.0, int iReviveDC = 25) {
// If PC is alive, stop checking his saving throws!!!!
if (!GetIsDead(oPC)) return;
// Adjust the DC for PC's level
int iLevel = GetLevel(oPC);
if (iLevel < 5) iReviveDC -= 10;
else if (iLevel < 10) iReviveDC -= 5;
else if (iLevel < 15) iReviveDC -= 2;
// Make the saving throw for life!
int iSave = GetFortitudeSavingThrow(oPC) + d20();
if (iSave > iReviveDC) {
SendMessageToPC(oPC, "Save (roll: " + IntToString(iSave) + ") vs. Fortitude (" + IntToString(iReviveDC) + ") *success*");
FloatingTextStringOnCreature("Your heart has begun to pump again... faintly...", oPC);
effect eFX = EffectRegenerate(d4(), 1.0);
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFX, oPC, fHeartBeat);
AssignCommand(oPC, DelayCommand(fHeartBeat, Player_HeartBeat_Raise(oPC, fHeartBeat, iReviveDC)));
}
else AssignCommand(oPC, DelayCommand(fHeartBeat, Player_HeartBeat_Revive(oPC, fHeartBeat, iReviveDC)));
}
// Raise the PC
void Player_RestoreToLife(object oPC) {
FloatingTextStringOnCreature("The fates are with you! Your heart refuses to quit and you regain consciousness!", oPC);
effect eFX = EffectResurrection();
ApplyEffectToObject(DURATION_TYPE_INSTANT, eFX, oPC);
eFX = EffectHeal(d12());
AssignCommand(GetArea(oPC), DelayCommand(2.0, ApplyEffectToObject(DURATION_TYPE_INSTANT, eFX, oPC)));
}
// Returns the default portal tag for the module
string Recall_GetDefaultPortalTag() {
return(GetLocalString(GetModule(), "strDefaultPortal"));
}
// Called from Recall_IsRecall() in order to determine the location to recall a PC to for an area
string Recall_GetPortalTag(object oArea=OBJECT_SELF) {
string strRecallPortal = GetLocalString(oArea, "strRecallPortal");
if (GetStringLength(strRecallPortal) < 1) {
// Check if a default portal is available
if (Recall_IsDefaultPortalActive()) {
if (!Recall_IsDefaultPortalBlocked()) { // Check too see if area is blocking the default portal
strRecallPortal = Recall_GetDefaultPortalTag();
}
}
}
return(strRecallPortal);
}
// Called in the OnEnter event for an area to set the location tag that will be used for recalls
void Recall_InitializeArea(string strRecallPortal, string strDefaultPortal="NULL", object oArea=OBJECT_SELF) {
// Check to make sure that the portal tag string passed in was valid
object oPortal = GetObjectByTag(strRecallPortal);
if (!GetIsObjectValid(oPortal)) return;
// Set the recall variables for the area if all is good
SetLocalInt(oArea, "iRecallActive", TRUE);
SetLocalString(oArea, "strRecallPortal", strRecallPortal);
// If a default portal was passed in, set it for the module
if (strDefaultPortal != "NULL") Recall_SetDefaultPortal(strDefaultPortal);
}
// Called to determine if the recall stone is active for the given area
int Recall_IsAreaActive(object oArea=OBJECT_SELF) {
if (GetLocalInt(oArea, "iRecallActive")) {
return TRUE; // Yes, area was initialized
}
if (Recall_IsDefaultPortalActive() && !Recall_IsDefaultPortalBlocked(oArea)) return TRUE; // Yes, area was initialized with a default portal and is not blocked
return FALSE; // No, area not initialized
}
// Checks too see if there is a default portal
int Recall_IsDefaultPortalActive() {
return(GetLocalInt(GetModule(), "iDefaultActive"));
}
// If the default portal is active for the module, an area can see if it's blocked (PCs are not permitted to
// transport out) by calling this function.
int Recall_IsDefaultPortalBlocked(object oArea=OBJECT_SELF) {
return(GetLocalInt(oArea, "iBlockValue"));
}
// Called from the modules OnActivateItem script to determine if the item used was a Recall Stone
// Hope you kept it from the original campaign, fucka! When adding a portal to an area, be sure to
// use the CUSTOM | VISUAL EFFECTS | Portal placeable and to give it a UNIQUE name!
int Recall_IsRecall() {
if (GetTag(GetItemActivated()) == "NW_IT_RECALL") {
object oPC = GetItemActivator();
object oArea = GetArea(oPC);
// Check the area's Stone global too see if player can transport from the area
if (!Recall_IsAreaActive(oArea)) {
// No idea what the following is, so just leave it... couldn't find the 'string table'
AssignCommand(GetItemActivator(), ActionSpeakStringByStrRef(10611));
return TRUE;
}
// Continue with transport
string strPortal = Recall_GetPortalTag(oArea);
object oPortal = GetObjectByTag(strPortal);
if (GetIsObjectValid(oPortal) == TRUE) {
// Set the last used location flag and value
SetLocalInt(GetItemActivator(), "NW_L_USED_RECALL", 1);
SetLocalLocation(GetItemActivator(), "NW_L_LOC_RECALL", GetLocation(GetItemActivator()));
// Carry out the transport command
AssignCommand(oPC, ClearAllActions());
AssignCommand(oPC, PlaySound("as_mg_telepout1"));
AssignCommand(oPC, JumpToLocation(GetLocation(oPortal)));
AssignCommand(oPC, ActionDoCommand(ClearAllActions()));
return TRUE;
}
else { // Error string, no temple is nearby!
AssignCommand(GetItemActivator(), ActionSpeakStringByStrRef(10614));
return TRUE;
}
} // end If (GetTag())
return FALSE;
}
// Called once from an area's OnEnter slot to set whether the Stone of Recall can be used in that area.
// Normally, this is used to block retrieval to the Default Recall Portal for the module, set with
// Recall_SetDefaultPortal().
void Recall_SetAreaBlocked(int iBlockValue=TRUE, object oArea=OBJECT_SELF) {
SetLocalInt(oArea, "iBlockValue", iBlockValue);
}
// Called from a modules OnModuleLoad or once from the OnClientEnter events to allow one portal
// to be associated with all areas of the module. The portal assigned to an area with Recall_InitializeArea()
// takes precedence over the default portal.
void Recall_SetDefaultPortal(string strDefaultPortal) {
SetLocalString(GetModule(), "strDefaultPortal", strDefaultPortal);
SetLocalInt(GetModule(), "iDefaultActive", TRUE);
}
// Adds iValue to the Item Tag passed in - assumes there are suppossed to be 3
// digits trailing!!!!
string String_AddDigits(string strItemTag, int iValue, int iDigits=2) {
string strDigits = IntToString(iValue);
int iIndex;
for (iIndex=GetStringLength(strDigits); iIndex<iDigits; iIndex++) strItemTag += "0";
strItemTag += strDigits;
return(strItemTag);
}
// Returns the text string naming an ability score
string String_GetAbilityText(int iAbility) {
string strAbility;
if (iAbility == ABILITY_CHARISMA) strAbility = "Charisma";
if (iAbility == ABILITY_CONSTITUTION) strAbility = "Constitution";
if (iAbility == ABILITY_DEXTERITY) strAbility = "Dexterity";
if (iAbility == ABILITY_INTELLIGENCE) strAbility = "Intelligence";
if (iAbility == ABILITY_STRENGTH) strAbility = "Strength";
if (iAbility == ABILITY_WISDOM) strAbility = "Wisdom";
return(strAbility);
}
// Returns TRUE if the string 'strFindThis' was found in 'strOriginal'.
int String_IsSubString(string strOriginal, string strFindThis) {
if (-1 != FindSubString(strOriginal, strFindThis)) return TRUE;
return FALSE;
}
// Called to have the Area respawn an object. The final parameter is optional - if it's "",
// no action will be taken. If it contains an array of item tags with stack sizes, the items will
// be created on the new object.
void Respawn_ByResrefWithDelay(object oObject, float fDelay, string strInventoryArray="") {
string strResRef = GetResRef(oObject);
int iType = GetObjectType(oObject);
location lLoc = GetLocation(oObject);
object oArea = GetArea(oObject);
AssignCommand(oArea, DestroyObject(oObject, fDelay-1.0));
AssignCommand(oArea, DelayCommand(fDelay, Respawn_DoRespawn(iType, strResRef, lLoc, strInventoryArray)));
}
// Create an object in the area
void Respawn_DoRespawn(int iType, string strResRef, location lLoc, string strInventoryArray="") {
object oObject = CreateObject(iType, strResRef, lLoc);
Object_RespawnSpecialInventory(oObject, strInventoryArray);
}
// Rolls a 'die' against the chance passed in and returns 'TRUE' if the roll was LESS than iPercent.
int RollChance(int iPercent) {
int iRand = Random(100);
if (iRand <= iPercent) return TRUE;
return FALSE;
}
// Function Version 1.00 - 12/31/02
// Returns TRUE if the spell is offensive (can be used to attack an enemy), FALSE if it
// is defensive and -1 if it was not found in the list.
// *DOES NOT* cover all spells in the game yet!
int Spell_IsOffensive(int iSpellID) {
if (iSpellID == SPELL_ACID_FOG) return TRUE;
if (iSpellID == SPELL_AID) return FALSE;
if (iSpellID == SPELL_BARKSKIN) return FALSE;
if (iSpellID == SPELL_BESTOW_CURSE) return TRUE;
if (iSpellID == SPELL_BLESS) return FALSE;
if (iSpellID == SPELL_BLINDNESS_AND_DEAFNESS) return TRUE;
if (iSpellID == SPELL_BULLS_STRENGTH) return FALSE;
if (iSpellID == SPELL_BURNING_HANDS) return TRUE;
if (iSpellID == SPELL_CALL_LIGHTNING) return TRUE;
if (iSpellID == SPELL_CHAIN_LIGHTNING) return TRUE;
if (iSpellID == SPELL_CHARM_PERSON) return TRUE;
if (iSpellID == SPELL_CLARITY) return FALSE;
if (iSpellID == SPELL_CLOUDKILL) return TRUE;
if (iSpellID == SPELL_COLOR_SPRAY) return TRUE;
if (iSpellID == SPELL_CONE_OF_COLD) return TRUE;
if (iSpellID == SPELL_CONFUSION) return TRUE;
if (iSpellID == SPELL_CREATE_GREATER_UNDEAD) return FALSE;
if (iSpellID == SPELL_CREATE_UNDEAD) return FALSE;
if (iSpellID == SPELL_CREEPING_DOOM) return FALSE;
if (iSpellID == SPELL_CURE_CRITICAL_WOUNDS) return FALSE;
if (iSpellID == SPELL_CURE_LIGHT_WOUNDS) return FALSE;
if (iSpellID == SPELL_CURE_MINOR_WOUNDS) return FALSE;
if (iSpellID == SPELL_CURE_MODERATE_WOUNDS) return FALSE;
if (iSpellID == SPELL_CURE_SERIOUS_WOUNDS) return FALSE;
if (iSpellID == SPELL_DARKNESS) return TRUE;
if (iSpellID == SPELL_DARKVISION) return FALSE;
if (iSpellID == SPELL_DAZE) return TRUE;
if (iSpellID == SPELL_DEATH_WARD) return FALSE;
if (iSpellID == SPELL_DISMISSAL) return TRUE;
if (iSpellID == SPELL_DISPEL_MAGIC) return TRUE;
if (iSpellID == SPELL_DIVINE_POWER) return FALSE;
if (iSpellID == SPELL_DOMINATE_PERSON) return TRUE;
if (iSpellID == SPELL_DOOM) return TRUE;
if (iSpellID == SPELL_ENDURANCE) return FALSE;
if (iSpellID == SPELL_ENDURE_ELEMENTS) return FALSE;
if (iSpellID == SPELL_ENERGY_BUFFER) return FALSE;
if (iSpellID == SPELL_ENERGY_DRAIN) return TRUE;
if (iSpellID == SPELL_ENTANGLE) return TRUE;
if (iSpellID == SPELL_FEAR) return TRUE;
if (iSpellID == SPELL_FEEBLEMIND) return TRUE;
if (iSpellID == SPELL_FINGER_OF_DEATH) return TRUE;
if (iSpellID == SPELL_FIRE_STORM) return TRUE;
if (iSpellID == SPELL_FIREBALL) return TRUE;
if (iSpellID == SPELL_FLAME_ARROW) return TRUE;
if (iSpellID == SPELL_FLAME_LASH) return TRUE;
if (iSpellID == SPELL_FLAME_STRIKE) return TRUE;
if (iSpellID == SPELL_FOXS_CUNNING) return FALSE;
if (iSpellID == SPELL_FREEDOM_OF_MOVEMENT) return FALSE;
if (iSpellID == SPELL_GHOUL_TOUCH) return TRUE;
if (iSpellID == SPELL_GHOSTLY_VISAGE) return FALSE;
if (iSpellID == SPELL_GLOBE_OF_INVULNERABILITY) return FALSE;
if (iSpellID == SPELL_GREASE) return FALSE;
if (iSpellID == SPELL_GREATER_STONESKIN) return FALSE;
if (iSpellID == SPELL_GREATER_BULLS_STRENGTH) return FALSE;
if (iSpellID == SPELL_GREATER_CATS_GRACE) return FALSE;
if (iSpellID == SPELL_GREATER_DISPELLING) return TRUE;
if (iSpellID == SPELL_GREATER_ENDURANCE) return FALSE;
if (iSpellID == SPELL_GREATER_FOXS_CUNNING) return FALSE;
if (iSpellID == SPELL_GREATER_MAGIC_WEAPON) return FALSE;
if (iSpellID == SPELL_GREATER_OWLS_WISDOM) return FALSE;
if (iSpellID == SPELL_HARM) return TRUE;
if (iSpellID == SPELL_HASTE) return FALSE;
if (iSpellID == SPELL_HEAL) return FALSE;
if (iSpellID == SPELL_HORRID_WILTING) return TRUE;
if (iSpellID == SPELL_ICE_STORM) return TRUE;
if (iSpellID == SPELL_IMPLOSION) return TRUE;
if (iSpellID == SPELL_IMPROVED_INVISIBILITY) return TRUE;
if (iSpellID == SPELL_INCENDIARY_CLOUD) return TRUE;
if (iSpellID == SPELL_INVISIBILITY) return FALSE;
if (iSpellID == SPELL_INVISIBILITY_PURGE) return TRUE;
if (iSpellID == SPELL_LESSER_DISPEL) return TRUE;
if (iSpellID == SPELL_LESSER_RESTORATION) return FALSE;
if (iSpellID == SPELL_LESSER_SPELL_BREACH) return TRUE;
if (iSpellID == SPELL_LIGHT) return TRUE;
if (iSpellID == SPELL_LIGHTNING_BOLT) return TRUE;
if (iSpellID == SPELL_MAGE_ARMOR) return FALSE;
if (iSpellID == SPELL_MAGIC_MISSILE) return TRUE;
if (iSpellID == SPELL_MASS_CHARM) return TRUE;
if (iSpellID == SPELL_MASS_HASTE) return FALSE;
if (iSpellID == SPELL_MASS_HEAL) return FALSE;
if (iSpellID == SPELL_MELFS_ACID_ARROW) return TRUE;
if (iSpellID == SPELL_METEOR_SWARM) return TRUE;
if (iSpellID == SPELL_MIND_BLANK) return TRUE;
if (iSpellID == SPELL_MIND_FOG) return TRUE;
if (iSpellID == SPELL_MINOR_GLOBE_OF_INVULNERABILITY) return FALSE;
if (iSpellID == SPELL_MORDENKAINENS_DISJUNCTION) return TRUE;
if (iSpellID == SPELL_MORDENKAINENS_SWORD) return FALSE;
if (iSpellID == SPELL_NEGATIVE_ENERGY_BURST) return TRUE;
if (iSpellID == SPELL_NEGATIVE_ENERGY_PROTECTION) return FALSE;
if (iSpellID == SPELL_NEGATIVE_ENERGY_RAY) return TRUE;
if (iSpellID == SPELL_NEUTRALIZE_POISON) return FALSE;
if (iSpellID == SPELL_OWLS_WISDOM) return FALSE;
if (iSpellID == SPELL_PHANTASMAL_KILLER) return FALSE;
if (iSpellID == SPELL_POISON) return TRUE;
if (iSpellID == SPELL_POWER_WORD_KILL) return TRUE;
if (iSpellID == SPELL_POWER_WORD_STUN) return TRUE;
if (iSpellID == SPELL_PRAYER) return FALSE;
if (iSpellID == SPELL_PREMONITION) return FALSE;
if (iSpellID == SPELL_PRISMATIC_SPRAY) return TRUE;
if (iSpellID == SPELL_RAY_OF_ENFEEBLEMENT) return TRUE;
if (iSpellID == SPELL_RAY_OF_FROST) return TRUE;
if (iSpellID == SPELL_REGENERATE) return FALSE;
if (iSpellID == SPELL_RESIST_ELEMENTS) return FALSE;
if (iSpellID == SPELL_RESISTANCE) return FALSE;
if (iSpellID == SPELL_RESTORATION) return FALSE;
if (iSpellID == SPELL_SANCTUARY) return FALSE;
if (iSpellID == SPELL_SCARE) return FALSE;
if (iSpellID == SPELL_SHADOW_SHIELD) return FALSE;
if (iSpellID == SPELL_SILENCE) return FALSE;
if (iSpellID == SPELL_SLAY_LIVING) return TRUE;
if (iSpellID == SPELL_SLEEP) return TRUE;
if (iSpellID == SPELL_SLOW) return TRUE;
if (iSpellID == SPELL_SOUND_BURST) return TRUE;
if (iSpellID == SPELL_SPELL_RESISTANCE) return FALSE;
if (iSpellID == SPELL_SPHERE_OF_CHAOS) return FALSE;
if (iSpellID == SPELL_STINKING_CLOUD) return TRUE;
if (iSpellID == SPELL_STONESKIN) return FALSE;
if (iSpellID == SPELL_STORM_OF_VENGEANCE) return TRUE;
if (iSpellID == SPELL_SUMMON_CREATURE_I) return FALSE;
if (iSpellID == SPELL_SUMMON_CREATURE_II) return FALSE;
if (iSpellID == SPELL_SUMMON_CREATURE_III) return FALSE;
if (iSpellID == SPELL_SUMMON_CREATURE_IV) return FALSE;
if (iSpellID == SPELL_SUMMON_CREATURE_IX) return FALSE;
if (iSpellID == SPELL_SUMMON_CREATURE_V) return FALSE;
if (iSpellID == SPELL_SUMMON_CREATURE_VI) return FALSE;
if (iSpellID == SPELL_SUMMON_CREATURE_VII) return FALSE;
if (iSpellID == SPELL_SUMMON_CREATURE_VIII) return FALSE;
if (iSpellID == SPELL_SUNBEAM) return TRUE;
if (iSpellID == SPELL_TIME_STOP) return FALSE;
if (iSpellID == SPELL_TRUE_SEEING) return FALSE;
if (iSpellID == SPELL_VIRTUE) return FALSE;
if (iSpellID == SPELL_WAIL_OF_THE_BANSHEE) return TRUE;
if (iSpellID == SPELL_WALL_OF_FIRE) return TRUE;
if (iSpellID == SPELL_WAR_CRY) return FALSE;
if (iSpellID == SPELL_WEB) return TRUE;
if (iSpellID == SPELL_WEIRD) return TRUE;
if (iSpellID == SPELL_WORD_OF_FAITH) return FALSE;
return -1;
}
void Sunlight_CheckForSunriseInArea(object oArea, float fHeartBeat=12.0) {
// Set an integer on the area to indicate to other Sunlight checking scripts
// that the area calling this function has sunlight periods.
SetLocalInt(oArea, "iHasSunlight", TRUE);
// FALSE for Night and Dusk, TRUE for Day and Dawn.
int iLastLight = GetLocalInt(oArea, "iLastLight");
int iCurrentLight;
if (GetIsDay() || GetIsDawn()) iCurrentLight = TRUE;
else iCurrentLight = FALSE;
// The sun has come up, destroy undead
if (iLastLight == FALSE && iCurrentLight == TRUE) Sunlight_DestroyUndeadInArea(oArea);
// Save the current light condition to the last light condition
SetLocalInt(oArea, "iLastLight", iCurrentLight);
// Schedule another check
AssignCommand(oArea, DelayCommand(fHeartBeat, Sunlight_CheckForSunriseInArea(oArea, fHeartBeat)));
}
void Sunlight_CheckForSunlightOnUndead(object oUndead) {
// Do not continue if the area does not receive sunlight
if (!GetLocalInt(GetArea(oUndead), "iHasSunlight")) return;
// Only apply sunlight effects to undead during dawn and day periods
if (Object_IsClassAndRace(CLASS_TYPE_UNDEAD, oUndead)) {
if (GetIsDawn()) Sunlight_DamageUndead(oUndead);
else if (GetIsDay()) AssignCommand(GetArea(oUndead), DelayCommand(6.0 + IntToFloat(d10()), Sunlight_DestroyUndead(oUndead)));
}
}
void Sunlight_DamageUndead(object oUndead, int iMaxDamage=12, int iMinDamage=6, float fFrequency=2.0, int iRepeats=20) {
iMaxDamage -= iMinDamage;
if (iMaxDamage <= 1) iMaxDamage=1;
int iTotalDamage = iMinDamage + Random(iMaxDamage);
effect eVFX = EffectVisualEffect(VFX_IMP_FLAME_M);
effect eDamage = EffectDamage(iTotalDamage, DAMAGE_TYPE_DIVINE, DAMAGE_POWER_PLUS_FIVE);
ApplyEffectToObject(DURATION_TYPE_INSTANT, eVFX, oUndead);
ApplyEffectToObject(DURATION_TYPE_INSTANT, eDamage, oUndead);
iRepeats-=1;
if (!GetIsDead(oUndead) && iRepeats > 0) AssignCommand(GetArea(oUndead), DelayCommand(fFrequency, Sunlight_DamageUndead(oUndead, iMaxDamage, iMinDamage, fFrequency, iRepeats)));
}
void Sunlight_DestroyUndead(object oUndead) {
effect eVFX = EffectVisualEffect(VFX_IMP_FLAME_M);
ApplyEffectToObject(DURATION_TYPE_INSTANT, eVFX, oUndead);
DestroyObject(oUndead, 0.5);
}
// Called from Sunlight_CheckLight() to kill all Undead in the area that was passed. Any undead found
// will explode into flames!!!!
void Sunlight_DestroyUndeadInArea(object oArea) {
object oObject = GetFirstObjectInArea(oArea);
effect eVFX = EffectVisualEffect(VFX_IMP_FLAME_M);
// Find a zombie!
while (GetIsObjectValid(oObject)) {
if (Object_IsClassAndRace(CLASS_TYPE_UNDEAD, oObject)) AssignCommand(oArea, DelayCommand(6.0 + IntToFloat(d10()), Sunlight_DestroyUndead(oObject)));
oObject = GetNextObjectInArea(oArea);
}
}
// Calculates the fuel left remaining on the torch. Does not stop executing until
// the PC leaves the game!
void TorchLight_Heartbeat(object oPC, float fHeartBeat, float fBurnLimit, float fBurnRate) {
int iHoldingTorchNow = TorchLight_IsHoldingTorch(oPC);
float fTorchFuelRemaining = GetLocalFloat(oPC, "fTorchFuelRemaining");
// If torch is depleted, destroy it and reset the Fuel value to the BurnLimit
if (fTorchFuelRemaining <= 1.0) {
object oTorch = GetItemInSlot(INVENTORY_SLOT_LEFTHAND, oPC);
string strItemTag = GetTag(oTorch);
SetLocalFloat(oPC, "fTorchFuelRemaining", fBurnLimit);
if (strItemTag == "NW_IT_TORCH001") {
DestroyObject(oTorch, 1.5);
FloatingTextStringOnCreature("My torch has run out of fuel...", oPC);
}
}
// Check to see if the PC is using a torch...
if (iHoldingTorchNow) {
fTorchFuelRemaining -= fBurnRate; // Consume some fuel for the torch
SetLocalFloat(oPC, "fTorchFuelRemaining", fTorchFuelRemaining);
}
// Check what the PC's status is again in fHeartBeat seconds
AssignCommand(oPC, DelayCommand(fHeartBeat, TorchLight_Heartbeat(oPC, fHeartBeat, fBurnLimit, fBurnRate)));
}
// Initializes the Torch Light System on the PC that has just entered the game.
// Care should be taken to ensure the object specified is, in fact, a PC. The burn limit is
// set to 2880, which is 48 minutes Real time given that the burn rate stays at 6.0. 48 minutes
// Real time is about equal to 1 day in the game. This is a *generous* setting...
void TorchLight_Initialize(object oPC, float fHeartBeat=6.0, float fBurnLimit=2880.0, float fBurnRate=6.0) {
AssignCommand(oPC, DelayCommand(fHeartBeat, TorchLight_Heartbeat(oPC, fHeartBeat, fBurnLimit, fBurnRate)));
}
// Returns TRUE if the PC is holding his torch, FALSE otherwise.
int TorchLight_IsHoldingTorch(object oPC) {
object oItem = GetItemInSlot(INVENTORY_SLOT_LEFTHAND, oPC);
string strItemTag = GetTag(oItem);
if (strItemTag == "NW_IT_TORCH001") return TRUE;
return FALSE;
}
// Places the custom meat item on the body once it's dead
int Treasure_Death_CreateMeat(int iProb=45, int iMax=3) {
if (Random(100) <= iProb) return (Random(iMax)+1);
return (0);
}
// Returns TRUE if the item was placed, false if not
int Treasure_Death_DecideOnBodyParts(int nPercentChance, int nQuant, string strTemplate, object oMonster) {
if (nQuant == 0) nQuant = 1;
int nRand;
int nResult = FALSE;
int nIndex;
for (nIndex=0; nIndex<nQuant; nIndex++) {
nRand = Random(100);
if (nRand <= nPercentChance) { // Place Item
object oReagent = CreateItemOnObject(strTemplate, oMonster, nQuant);
SetIdentified(oReagent, TRUE);
nResult = TRUE;
}
}
return nResult;
}
void Treasure_Death_DecideOnMeat(object oMonster) {
int iChance = 4 * GetHitDice(oMonster);
int iStack = Random(GetHitDice(oMonster)+1);
int iIndex;
for (iIndex=0; iIndex<iStack; iIndex++) {
if (d100() < iChance) CreateItemOnObject("cu_food000", oMonster, 1);
}
}
// This function decides what (if any) body parts to place on the corpse given what was killed
void Treasure_Death_PlaceBodyParts(object oMonster=OBJECT_SELF) {
// Prevent this code from running on a creature more than once
if (GetLocalInt(oMonster, "NW_DO_ONCE")) return;
SetLocalInt(oMonster, "NW_DO_ONCE", TRUE);
// Get the name of the creature
string strName = GetStringLowerCase(GetName(oMonster));
// Animals
if (Object_IsClassAndRace(CLASS_TYPE_ANIMAL, oMonster, TRUE)) Treasure_Death_DecideOnMeat(oMonster);
// Balor Demons
if (Object_IsClassAndRace(CLASS_TYPE_OUTSIDER, oMonster, FALSE, "balor")) Treasure_Death_DecideOnBodyParts(25, 2, "cu_reag200", oMonster);
// Bodak
if (Object_IsClassAndRace(CLASS_TYPE_UNDEAD, oMonster, FALSE, "bodak")) {
Treasure_Death_DecideOnBodyParts(30, 1, "cu_reag100", oMonster); // head?
Treasure_Death_DecideOnBodyParts(20, 1, "NW_IT_MSMLMISC06", oMonster); // tooth?
}
// Beetles
if (Object_IsClassAndRace(CLASS_TYPE_VERMIN, oMonster, FALSE, "weevil") ||
Object_IsClassAndRace(CLASS_TYPE_VERMIN, oMonster, FALSE, "beetle") ||
Object_IsClassAndRace(CLASS_TYPE_VERMIN, oMonster, FALSE, "hive") ||
Object_IsClassAndRace(CLASS_TYPE_VERMIN, oMonster, FALSE, "earwig"))
Treasure_Death_DecideOnBodyParts(30, 1, "cu_reag002", oMonster); // Beetle Carapace?
// Brain Weevil
if (Object_IsClassAndRace(CLASS_TYPE_VERMIN, oMonster, FALSE, "weevil"))
Treasure_Death_DecideOnBodyParts(15, 1, "cu_reag201", oMonster);
// Bear parts
if (Object_IsClassAndRace(CLASS_TYPE_ANIMAL, oMonster, TRUE, "bear")) {
// Dire Bear?
if (String_IsSubString(strName, "dire")) Treasure_Death_DecideOnBodyParts(30, 2, "cu_reag102", oMonster);
}
// Ettercaps
if (String_IsSubString(strName, "ettercap")) Treasure_Death_DecideOnBodyParts(15, 1, "cu_reag204", oMonster); // Spine and brain?
// Falcons
if (String_IsSubString(strName, "raptor")) {
Treasure_Death_DecideOnBodyParts(35, 1, "cu_reag104", oMonster); // Falcon egg?
Treasure_Death_DecideOnBodyParts(90, 4, "cu_reag004", oMonster); // Feather?
}
// Giants
if (Object_IsClassAndRace(CLASS_TYPE_GIANT, oMonster, FALSE, "giant")) {
Treasure_Death_DecideOnBodyParts(35, 1, "cu_reag310", oMonster); // Heart?
if (String_IsSubString(strName, "frost")) Treasure_Death_DecideOnBodyParts(15, 1, "cu_reag309", oMonster); // // Essence of Frost Giant?
}
// Goblins
if (String_IsSubString(strName, "gob")) {
Treasure_Death_DecideOnBodyParts(20, 1, "cu_reag105", oMonster); // Heart?
Treasure_Death_DecideOnBodyParts(35, 2, "cu_reag106", oMonster); // Kidneys?
}
// Hell-Fiend
if (String_IsSubString(strName, "fnd")) Treasure_Death_DecideOnBodyParts(45, 2, "cu_reag205", oMonster); // Eyeball?
// Iron Golem
if (String_IsSubString(strName, "goliron")) Treasure_Death_DecideOnBodyParts(70, 1, "cu_reag206", oMonster); // Spinal cluster?
// Lich
if (String_IsSubString(strName, "lich")) Treasure_Death_DecideOnBodyParts(20, 1, "cu_reag207", oMonster); // Lich bones
// Minotaur
if (String_IsSubString(strName, "minotaur") || String_IsSubString(strName, "minwiz") || String_IsSubString(strName, "minchief")) {
Treasure_Death_DecideOnBodyParts(35, 2, "cu_reag313", oMonster); // Eyeballs?
Treasure_Death_DecideOnBodyParts(60, 1, "cu_reag314", oMonster); // Heart?
Treasure_Death_DecideOnBodyParts(50, 1, "cu_reag315", oMonster); // Tongue?
}
// Nymph
if (String_IsSubString(strName, "nymph")) Treasure_Death_DecideOnBodyParts(90, 1, "cu_reag208", oMonster); // Lock of Hair?
// Orcs
if (String_IsSubString(strName, "orc")) {
Treasure_Death_DecideOnBodyParts(25, 2, "cu_reag316", oMonster); // Ears?
Treasure_Death_DecideOnBodyParts(15, 2, "cu_reag317", oMonster); // Eyeballs?
Treasure_Death_DecideOnBodyParts(35, 1, "cu_reag318", oMonster); // Head?
}
// Succubus
if (String_IsSubString(strName, "sucubus")) Treasure_Death_DecideOnBodyParts(30, 2, "cu_reag320", oMonster); // Ears?
// Tigers
if (String_IsSubString(strName, "tiger")) Treasure_Death_DecideOnBodyParts(35, 2, "cu_reag321", oMonster);
// Trolls
if (String_IsSubString(strName, "troll")) {
Treasure_Death_DecideOnBodyParts(50, 1, "cu_reag322", OBJECT_SELF);
Treasure_Death_DecideOnBodyParts(75, 1, "cu_reag323", OBJECT_SELF);
}
// Vampires
if (String_IsSubString(strName, "vampire")) {
Treasure_Death_DecideOnBodyParts(45, 2, "cu_reag325", oMonster); // Teeth?
Treasure_Death_DecideOnBodyParts(40, 1, "cu_reag324", oMonster); // Heart?
}
// Water Elementals
if (String_IsSubString(strName, "water")) Treasure_Death_DecideOnBodyParts(15, 2, "cu_reag326", oMonster);
// Wererats
if (String_IsSubString(strName, "wererat")) Treasure_Death_DecideOnBodyParts(35, 1, "cu_reag319", oMonster);
// Werewolves
if (String_IsSubString(strName, "werewolf")) {
Treasure_Death_DecideOnBodyParts(65, 1, "cu_reag312", oMonster); // Lock of hair?
Treasure_Death_DecideOnBodyParts(45, 4, "cu_reag024", oMonster); // Teeth?
}
// Wolves
if (String_IsSubString(strName, "wolf") && (String_IsSubString(strName, "werewolf") == -1)) {
Treasure_Death_DecideOnBodyParts(75, 1, "cu_reag212", oMonster); // Skin?
Treasure_Death_DecideOnBodyParts(45, 4, "cu_reag024", oMonster); // Teeth?
}
// Zombies
if (String_IsSubString(strName, "zomb")) Treasure_Death_DecideOnBodyParts(35, 2, "cu_reag111", oMonster); // Kidnies???
}
// Creates the amount in 'copper' for the quantity of the funds available.
int Treasure_ExchangeFundsForGold(object oPC, object oItem, float fExchangeRate) {
AssignCommand(oPC, PlaySound("it_coins"));
int iGoldValue = Treasure_RollCoins(oPC, oItem, fExchangeRate);
DestroyObject(oItem);
GiveGoldToCreature(oPC, iGoldValue);
return(iGoldValue);
}
void Treasure_GenerateBook(object oContainer, object oLastOpener, int iQuality) {
string strFinal;
string strItemTag;
int iMAX;
// Determine the type of book
int iType = d100();
if (iType <= 40) { strItemTag = "cu_note"; iMAX = MAX_ALCHEM_NOTES; }
if (iType > 40 && iType <= 70) strItemTag = "cu_recip";
if (iType > 70 && iType <= 90) strItemTag = "cu_book";
if (iType > 90) strItemTag = "cu_scroll";
// Determine the Level of the item
if (iQuality == TREASURE_LOW) {
if (strItemTag == "cu_recip") iMAX = MAX_RECIPES_LOW;
if (strItemTag == "cu_book") iMAX = MAX_BOOKS_LOW;
if (strItemTag == "cu_scroll") iMAX = MAX_SCROLLS_LOW;
strItemTag += "0";
strFinal = String_AddDigits(strItemTag, Random(iMAX));
}
if (iQuality == TREASURE_MEDIUM) {
if (strItemTag == "cu_recip") iMAX = MAX_RECIPES_MEDIUM;
if (strItemTag == "cu_book") iMAX = MAX_BOOKS_MEDIUM;
if (strItemTag == "cu_scroll") iMAX = MAX_SCROLLS_MEDIUM;
strItemTag += "1";
strFinal = String_AddDigits(strItemTag, Random(iMAX));
}
if (iQuality == TREASURE_HIGH) {
if (strItemTag == "cu_recip") iMAX = MAX_RECIPES_HIGH;
if (strItemTag == "cu_book") iMAX = MAX_BOOKS_HIGH;
if (strItemTag == "cu_scroll") iMAX = MAX_SCROLLS_HIGH;
strItemTag += "2";
strFinal = String_AddDigits(strItemTag, Random(iMAX));
}
CreateItemOnObject(strFinal, oContainer);
}
void Treasure_GenerateCustomTreasure(object oContainer, int iWealth=0) { // Default treasure is from Low, Med, or High
int iRand;
string strItemTag;
int iStack;
int iProb = d12(); // Probability of a certain category of item to come up
if (iWealth == TREASURE_LOW) iProb = 1; // Treasure is low category
if (iWealth == TREASURE_MEDIUM) iProb = 8; // Treasure is medium category
if (iWealth == TREASURE_HIGH) iProb = 10; // Treasure is high category
// Select an item category
iRand = Random(15); // Purposely high!!!
if (iProb <= 6) {
// Potions
if (iRand == 0) { iStack = Random(3); strItemTag = String_AddDigits("cu_potion0", Random(MAX_POTIONS_LOW)); }
// Scrolls
if (iRand == 1) { iStack = Random(2); strItemTag = String_AddDigits("cu_scroll0", Random(MAX_SCROLLS_LOW)); }
// Reagents
if (iRand == 2) { iStack = Random(5); strItemTag = String_AddDigits("cu_reag0", Random(MAX_REAGENTS_LOW)); }
// Ammunitions
if (iRand == 3) { iStack = Random(50); strItemTag = String_AddDigits("cu_ammo0", Random(MAX_AMMO_LOW)); }
// Armors
if (iRand == 4) { iStack = Random(2); strItemTag = String_AddDigits("cu_armor0", Random(MAX_ARMOR_LOW)); }
// Books
if (iRand == 5) { iStack = Random(2); strItemTag = String_AddDigits("cu_book0", Random(MAX_BOOKS_LOW)); }
// Clothing
if (iRand == 6) { iStack = Random(2); strItemTag = String_AddDigits("cu_cloth0", Random(MAX_CLOTHING_LOW)); }
// Jewelry
if (iRand == 7) { iStack = Random(2); strItemTag = String_AddDigits("cu_jewel0", Random(MAX_JEWELRY_LOW)); }
// Notes
if (iRand == 8) { iStack = Random(2); strItemTag = String_AddDigits("cu_note0", Random(MAX_ALCHEM_NOTES-7)); }
// Recipes
if (iRand == 9) { iStack = Random(3); strItemTag = String_AddDigits("cu_recip0", Random(MAX_RECIPES_LOW)); }
// Weapons
if (iRand == 10) { iStack = Random(2); strItemTag = String_AddDigits("cu_weap0", Random(MAX_WEAPONS_LOW)); }
}
else if (iProb <= 9) {
// Potions
if (iRand == 0) { iStack = Random(2); strItemTag = String_AddDigits("cu_potion1", Random(MAX_POTIONS_MEDIUM)); }
// Scrolls
if (iRand == 1) { iStack = Random(2); strItemTag = String_AddDigits("cu_scroll1", Random(MAX_SCROLLS_MEDIUM)); }
// Reagents
if (iRand == 2) { iStack = Random(3); strItemTag = String_AddDigits("cu_reag1", Random(MAX_REAGENTS_MEDIUM)); }
// Ammunitions
if (iRand == 3) { iStack = Random(15); strItemTag = String_AddDigits("cu_ammo1", Random(MAX_AMMO_MEDIUM)); }
// Armors
if (iRand == 4) { iStack = Random(2); strItemTag = String_AddDigits("cu_armor1", Random(MAX_ARMOR_MEDIUM)); }
// Books
if (iRand == 5) { iStack = Random(2); strItemTag = String_AddDigits("cu_book1", Random(MAX_BOOKS_MEDIUM)); }
// Clothing
if (iRand == 6) { iStack = Random(2); strItemTag = String_AddDigits("cu_cloth1", Random(MAX_CLOTHING_MEDIUM)); }
// Jewelry
if (iRand == 7) { iStack = Random(2); strItemTag = String_AddDigits("cu_jewel1", Random(MAX_JEWELRY_MEDIUM)); }
// Notes
if (iRand == 8) { iStack = Random(2); strItemTag = String_AddDigits("cu_note1", Random(MAX_ALCHEM_NOTES-4)); }
// Recipes
if (iRand == 9) { iStack = Random(2); strItemTag = String_AddDigits("cu_recip1", Random(MAX_RECIPES_MEDIUM)); }
// Weapons
if (iRand == 10) { iStack = Random(2); strItemTag = String_AddDigits("cu_weap1", Random(MAX_WEAPONS_MEDIUM)); }
// Misc Items
if (iRand == 11) Treasure_GenerateMiscItem(oContainer);
// Generate Poison
if (iRand == 12) Treasure_GeneratePoisonPotion(oContainer);
}
else {
// Potions
if (iRand == 0) { iStack = Random(2); strItemTag = String_AddDigits("cu_potion2", Random(MAX_POTIONS_HIGH)); }
// Scrolls
if (iRand == 1) { iStack = Random(2); strItemTag = String_AddDigits("cu_scroll2", Random(MAX_SCROLLS_HIGH)); }
// Reagents
if (iRand == 2) { iStack = Random(2); strItemTag = String_AddDigits("cu_reag2", Random(MAX_REAGENTS_HIGH)); }
// Ammunitions
if (iRand == 3) { iStack = Random(8); strItemTag = String_AddDigits("cu_ammo2", Random(MAX_AMMO_HIGH)); }
// Armors
if (iRand == 4) { iStack = Random(2); strItemTag = String_AddDigits("cu_armor2", Random(MAX_ARMOR_HIGH)); }
// Books
if (iRand == 5) { iStack = Random(2); strItemTag = String_AddDigits("cu_book2", Random(MAX_BOOKS_HIGH)); }
// Clothing
if (iRand == 6) { iStack = Random(2); strItemTag = String_AddDigits("cu_cloth2", Random(MAX_CLOTHING_HIGH)); }
// Jewelry
if (iRand == 7) { iStack = Random(2); strItemTag = String_AddDigits("cu_jewel2", Random(MAX_JEWELRY_HIGH)); }
// Notes
if (iRand == 8) { iStack = Random(3); strItemTag = String_AddDigits("cu_note2", Random(MAX_ALCHEM_NOTES)); }
// Recipes
if (iRand == 9) { iStack = Random(2); strItemTag = String_AddDigits("cu_recip2", Random(MAX_RECIPES_HIGH)); }
// Weapons
if (iRand == 10) { iStack = Random(2); strItemTag = String_AddDigits("cu_weap2", Random(MAX_WEAPONS_HIGH)); }
// Misc Items
if (iRand == 11) Treasure_GenerateMiscItem(oContainer);
}
// Create the object in inventory
// NOTE! iStack -can- be ZERO (by design), which will cause the creation to fail!
CreateItemOnObject(strItemTag, oContainer, iStack);
}
// Create a custom-misc item on the container
object Treasure_GenerateMiscItem(object oContainer) {
int iRand = Random(MAX_MISCELLANEOUS);
string strItemTag = "INVALID";
int iStack = 0;
if (iRand == 0) if (RollChance(6)) { strItemTag = "AlchemistsEquipment"; iStack = 1; }
if (iRand == 1) if (RollChance(8)) { strItemTag = "ScribesKit"; iStack = 1; }
if (iRand == 2) if (RollChance(85)) { strItemTag = "BlankParchment"; iStack = d8(); }
if (iRand == 3) if (RollChance(3)) { strItemTag = "BucknardsEverfullPurse"; iStack = 1; }
if (iRand == 4) if (RollChance(90)) { strItemTag = "EmptyVile"; iStack = d10(); }
if (iRand == 5) if (RollChance(12)) { strItemTag = "FigurineofWonderousPower"; iStack = 1; }
if (iRand == 6) if (RollChance(12)) { strItemTag = "figurineofwon002"; iStack = 1; }
if (iRand == 7) if (RollChance(12)) { strItemTag = "figurineofwonder"; iStack = 1; }
if (iRand == 8) if (RollChance(60)) { strItemTag = "EmptyBottle"; iStack = d10(); }
if (iRand == 8) if (RollChance(5)) { strItemTag = "ThievesTools"; iStack = 1; }
return(CreateItemOnObject(strItemTag, oContainer, iStack));
}
// Creates a book item (book or scroll) from the stock NWN items
void Treasure_GenerateNWNBook(object oContainer, object oLastOpener) {
if (d100() < 60) CreateBook(oContainer);
else CreateArcaneScroll(oContainer, oLastOpener);
}
// Create a Poison in the container
object Treasure_GeneratePoisonPotion(object oContainer) {
string strPoison;
object oPoison;
strPoison = String_AddDigits("cu_potion0", Random(MAX_POISONS));
if (60 <= Random(100)) oPoison = CreateItemOnObject(strPoison, oContainer, d4()); // 40% chance of creating a poison potion to make them fairly rare
return (oPoison);
}
// Create a custom potion in the container
void Treasure_GeneratePotion(object oContainer, object oAdventurer, int iQuality) {
string strItemTag;
if (iQuality == TREASURE_LOW) strItemTag = String_AddDigits("cu_potion0", Random(MAX_POTIONS_LOW));
if (iQuality == TREASURE_MEDIUM) strItemTag = String_AddDigits("cu_potion1", Random(MAX_POTIONS_MEDIUM));
if (iQuality == TREASURE_HIGH) strItemTag = String_AddDigits("cu_potion2", Random(MAX_POTIONS_HIGH));
CreateItemOnObject(strItemTag, oContainer);
}
// Create a reagent item in the container
void Treasure_GenerateReagent(object oContainer, int iQuality) {
string strReagent = "cu_reag";
if (iQuality == TREASURE_LOW) { strReagent += "0"; strReagent = String_AddDigits(strReagent, Random(MAX_REAGENTS_LOW)); };
if (iQuality == TREASURE_MEDIUM) { strReagent += "1"; strReagent = String_AddDigits(strReagent, Random(MAX_REAGENTS_MEDIUM)); };
if (iQuality == TREASURE_HIGH) { strReagent += "2"; strReagent = String_AddDigits(strReagent, Random(MAX_REAGENTS_HIGH)); };
CreateItemOnObject(strReagent, oContainer);
}
// Generate Custom and Standard treasure on the object with the information passed
void Treasure_GenerateTreasure(object oObject, object oLastOpener, int iQuality, int iType) {
// Determine difficulty of the object relative to the PC's level
int iIndex=0;
int iPCLevel = GetLevel(oLastOpener);
int iChance = iPCLevel * 3;
float iQAdj = IntToFloat(iQuality) * ((100.0 - IntToFloat(iPCLevel))/100.0);
if (iType == OBJECT_TYPE_BARREL) if (d100() < (80 - iPCLevel)) GenerateLowTreasure(oLastOpener, oObject);
if (iType == OBJECT_TYPE_BOOKS) {
if (d100() >= 70 - iPCLevel) Treasure_GenerateNWNBook(oObject, oLastOpener);
if (iQAdj <= QUALITY_LOW) Treasure_GenerateBook(oObject, oLastOpener, TREASURE_LOW);
if (iQAdj > QUALITY_LOW && iQAdj <= QUALITY_MEDIUM) Treasure_GenerateBook(oObject, oLastOpener, TREASURE_MEDIUM);
if (iQAdj > QUALITY_MEDIUM) Treasure_GenerateBook(oObject, oLastOpener, TREASURE_HIGH);
}
else if (iType == OBJECT_TYPE_GOLD_PILE) {
if (iQAdj <= QUALITY_LOW) Treasure_GenerateValuable(oObject, oLastOpener, TREASURE_LOW);
if (iQAdj > QUALITY_LOW && iQAdj <= QUALITY_MEDIUM) for (iIndex=0; iIndex<d3(); iIndex++) if (d100() < iChance) Treasure_GenerateValuable(oObject, oLastOpener, TREASURE_MEDIUM);
if (iQAdj > QUALITY_MEDIUM) for (iIndex=0; iIndex<d6(); iIndex++) if (d100() < iChance) Treasure_GenerateValuable(oObject, oLastOpener, TREASURE_HIGH);
}
else if (iType == OBJECT_TYPE_LOOTBAG) {
if (iQAdj <= QUALITY_LOW) CreateGold(oObject, oLastOpener, TREASURE_LOW);
if (iQAdj > QUALITY_LOW && iQAdj <= QUALITY_MEDIUM) for (iIndex=0; iIndex<d4(); iIndex++) if (d100() < iChance) CreateGold(oObject, oLastOpener, TREASURE_MEDIUM);
if (iQAdj > QUALITY_MEDIUM) for (iIndex=0; iIndex<d8(); iIndex++) if (d100() < iChance) CreateGold(oObject, oLastOpener, TREASURE_HIGH);
}
else if (iType == OBJECT_TYPE_WIZARD_CABINET) {
if (iQAdj <= QUALITY_LOW) Treasure_GenerateWizardSupplies(oObject, oLastOpener, TREASURE_LOW);
if (iQAdj > QUALITY_LOW && iQAdj <= QUALITY_MEDIUM) for (iIndex=0; iIndex<d4(3); iIndex++) if (d100() < iChance) Treasure_GenerateWizardSupplies(oObject, oLastOpener, TREASURE_MEDIUM);
if (iQAdj > QUALITY_MEDIUM) for (iIndex=0; iIndex<d8(2); iIndex++) if (d100() < iChance) Treasure_GenerateWizardSupplies(oObject, oLastOpener, TREASURE_HIGH);
}
else { // Generate Generic Treasure
int iStack;
int iMod;
if (iQAdj <= QUALITY_LOW) { GenerateLowTreasure(oLastOpener, oObject); iStack = 1; iMod = TREASURE_LOW; }
if (iQAdj > QUALITY_LOW && iQAdj <= QUALITY_MEDIUM) { GenerateMediumTreasure(oLastOpener, oObject); iStack = d3(); iMod = TREASURE_MEDIUM; }
if (iQAdj > QUALITY_MEDIUM) { GenerateHighTreasure(oLastOpener, oObject); iStack = d6(); iMod = TREASURE_HIGH; }
// Include additional treasure?
int iTreasure = Random(13) + 1;
int i=0;
for (i=0; i<iStack; i++) {
if (d100() < iChance) {
switch (iTreasure) {
case 1: if (RollChance(70)) CreateJunk(oObject); // fall through
case 2: if (RollChance(30)) Treasure_GenerateNWNBook(oObject, oLastOpener); // fall through
case 3: if (RollChance(50)) CreatePotion(oObject, oLastOpener, iMod);
break;
case 4: if (RollChance(30)) CreateArcaneScroll(oObject, oLastOpener);
break;
case 5: if (RollChance(40)) Treasure_GenerateReagent(oObject, iMod);
break;
case 6: if (RollChance(60)) CreateGold(oObject, oLastOpener, iMod);
break;
case 7: if (RollChance(30)) CreateGem(oObject, oLastOpener, iMod);
break;
case 8: if (RollChance(20)) Treasure_GeneratePotion(oObject, oLastOpener, iMod);
break;
case 9: if (RollChance(25)) CreateJewel(oObject, oLastOpener, iMod);
break;
case 10: if (RollChance(25)) Treasure_GenerateBook(oObject, oLastOpener, iMod);
break;
case 11: if (RollChance(35)) Treasure_GenerateCustomTreasure(oObject, iMod);
break;
case 12: if (RollChance(10)) Treasure_GenerateMiscItem(oObject);
break;
case 13: if (RollChance(5)) Treasure_GeneratePoisonPotion(oObject);
break;
}
}
}
}
}
// Create Junk, Gold, Gems or Jewels in the container
void Treasure_GenerateValuable(object oContainer, object oAdventurer, int iQuality) {
int iType = d100();
int iLevel = GetLevel(oAdventurer);
if (iQuality == TREASURE_LOW) iType -= Random(iLevel*2) + iLevel;
if (iQuality == TREASURE_HIGH) iType += Random(iLevel);
if (iType < 40) CreateJunk(oContainer);
if (iType >= 40 && iType < 70) CreateGold(oContainer, oAdventurer, iQuality);
if (iType >= 70 && iType < 95) CreateGem(oContainer, oAdventurer, iQuality);
if (iType >= 95) CreateJewel(oContainer, oAdventurer, iQuality);
}
// Create Reagents, Books, Potions, Gems or Jewels in the container
void Treasure_GenerateWizardSupplies(object oContainer, object oAdventurer, int iQuality) {
int iType = d100();
int iIndex;
int iMaxReag;
if (iQuality == TREASURE_LOW) iMaxReag = d6();
if (iQuality == TREASURE_MEDIUM) iMaxReag = d12();
if (iQuality == TREASURE_HIGH) iMaxReag = d20();
if (iType < 80) for (iIndex=0; iIndex<iMaxReag; iIndex++) Treasure_GenerateReagent(oContainer, iQuality);
if (iType >= 80 && iType < 82) Treasure_GenerateNWNBook(oContainer, oAdventurer);
if (iType >= 82 && iType < 90) Treasure_GenerateBook(oContainer, oAdventurer, iQuality);
if (iType >= 90 && iType < 93) CreatePotion(oContainer, oAdventurer, iQuality);
if (iType >= 93 && iType < 96) Treasure_GeneratePotion(oContainer, oAdventurer, iQuality);
if (iType >= 96 && iType < 98) CreateGem(oContainer, oAdventurer, iQuality);
if (iType >= 98) CreateJewel(oContainer, oAdventurer, iQuality);
}
// When a character enters the module and is low-level, his gold should be converted
// to copper with consideration given to his character class. Called from the modules
// OnClientEnter() event slot.
void Treasure_GoldForClassAdjust(object oTarget) {
// Convert gold to copper if at a low level
int iLevel = GetLevel(oTarget);
int iGold = GetGold(oTarget);
if (iGold > 100) return;
if (iLevel > 2) return;
// Award more gold depending on class
int iMaxClassValue;
if (GetLevelByClass(CLASS_TYPE_BARBARIAN, oTarget)) iMaxClassValue = 200;
else if (GetLevelByClass(CLASS_TYPE_BARD, oTarget)) iMaxClassValue = 400;
else if (GetLevelByClass(CLASS_TYPE_CLERIC, oTarget)) iMaxClassValue = 500;
else if (GetLevelByClass(CLASS_TYPE_DRUID, oTarget)) iMaxClassValue = 200;
else if (GetLevelByClass(CLASS_TYPE_FIGHTER, oTarget)) iMaxClassValue = 750;
else if (GetLevelByClass(CLASS_TYPE_MONK, oTarget)) iMaxClassValue = 100;
else if (GetLevelByClass(CLASS_TYPE_PALADIN, oTarget)) iMaxClassValue = 150;
else if (GetLevelByClass(CLASS_TYPE_RANGER, oTarget)) iMaxClassValue = 100;
else if (GetLevelByClass(CLASS_TYPE_ROGUE, oTarget)) iMaxClassValue = 500;
else if (GetLevelByClass(CLASS_TYPE_SORCERER, oTarget)) iMaxClassValue = 200;
else if (GetLevelByClass(CLASS_TYPE_WIZARD, oTarget)) iMaxClassValue = 750;
else iMaxClassValue = 550;
// Give the new gold amount in copper
int iCopper = iGold * 25 + Random(iMaxClassValue); // ~ 1/4 of the Gold -> Copper exchange rate
AssignCommand(oTarget, GiveGoldToCreature(oTarget, iCopper));
}
// Returns the equivalent value of the passed coin in gold. Outputs the amount of 'change'
// PC has on his person.
int Treasure_RollCoins(object oPC, object oItem, float fExchangeRate) {
int iStack = GetNumStackedItems(oItem);
if (iStack > 1) iStack++;
// Get the PC's change for this coin type
string strCoinTag = GetStringLowerCase(GetTag(oItem));
float fCoins = IntToFloat(GetLocalInt(oPC, strCoinTag));
// Add the current change to the new change
fCoins += IntToFloat(iStack);
// Convert to gold with exchange rate
int iGoldCoins = 0;
while (fCoins >= fExchangeRate) {
iGoldCoins++;
fCoins -= fExchangeRate;
}
// Store remaining change
SetLocalInt(oPC, strCoinTag, FloatToInt(fCoins));
// Output remaining change
string strCoinName;
if (strCoinTag == "cu_gold001") strCoinName = "copper";
if (strCoinTag == "cu_gold002") strCoinName = "silver";
if (strCoinTag == "cu_gold003") strCoinName = "electrum";
if (strCoinTag == "cu_gold004") strCoinName = "gold";
if (strCoinTag == "cu_gold005") strCoinName = "platinum";
SendMessageToPC(oPC, "Your " + strCoinName + " was worth " + IntToString(iGoldCoins) + " gold pieces. You have " + IntToString(FloatToInt(fCoins)) + " " + strCoinName + " pieces remaining in change.");
// Return the amount that was exchanged to gold
return iGoldCoins;
}
// Entry function for treasure generating library. Place in the OnOpen() of a container.
void Treasure_StockContainerByDCAndRespawn(object oObject) {
// Run code only once
if (BlockCodeExecution(oObject, "NW_DO_ONCE")) return;
// Store the tags of any items in the chest so they can be recalled later if need
string strInventoryArray = Object_StoreSpecialInventory();
// Determine information needed to create treasure
object oLastOpener = GetLastOpener(); // PC's level
int iContainerQuality = Object_GetDifficulty(oObject, TRUE); // Difficulty of container
int iContainerType = Object_GetType(oObject); // The type of the container
// Place the treasure
Treasure_GenerateTreasure(oObject, oLastOpener, iContainerQuality, iContainerType);
// Respawn the chest on a delay
float fRespawnDelay = 1200.0 + IntToFloat(iContainerQuality*40);
Respawn_ByResrefWithDelay(oObject, fRespawnDelay, strInventoryArray);
// Finish up
ShoutDisturbed();
}
// The OnUse code for a Ballista that can fire fireballs, level 6
void Weapon_FireBallista(object oWeapon, object oPC, int iPower=6) {
// Find nearest hostile within range
object oCreature = GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR, PLAYER_CHAR_NOT_PC);
if (GetIsObjectValid(oCreature)) {
if (60 < GetStandardFactionReputation(STANDARD_FACTION_HOSTILE, oCreature)) {
float iDist = GetDistanceToObject(oCreature);
if (iDist < 35.0) {
if (BlockMultiActivation("iFired", oWeapon, 2.0, "Waiting for reload!!")) return;
ActionCastSpellAtObject(SPELL_FIREBALL, oCreature, METAMAGIC_ANY, TRUE, iPower);
}
}
}
else AssignCommand(oPC, SpeakString("No enemies in range!"));
}