Added CCOH and missing areas Changed some areas to be craftable, Fixed some on death issues, Fixed the Gaurd
3823 lines
193 KiB
Plaintext
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!"));
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|