PRC8/nwn/nwnprc/trunk/include/inc_ecl.nss
Jaysyn904 6ec137a24e Updated AMS marker feats
Updated AMS marker feats.  Removed arcane & divine marker feats.  Updated Dread Necromancer for epic progression. Updated weapon baseitem models.  Updated new weapons for crafting & npc equip.
 Updated prefix.  Updated release archive.
2024-02-11 14:01:05 -05:00

371 lines
13 KiB
Plaintext

/** @file
* ECL handling.
*
* @author Primogenitor
*
* @todo Primo, could you document this one? More details to header and comment function prototypes
*/
//////////////////////////////////////////////////
/* Function prototypes */
//////////////////////////////////////////////////
// returns oTarget's LA value, including their race and template(s) LA
int GetTotalLA(object oTarget);
// returns oTarget's level adjusted by their LA
int GetECL(object oTarget);
void GiveXPReward(object oCreature, int nXP, int bIsPC = TRUE);
void GiveXPRewardToParty(object oKiller, object oDead, int nCR = 0);
//////////////////////////////////////////////////
/* Include section */
//////////////////////////////////////////////////
//#include "inc_utility"
//#include "prc_inc_template"
#include "inc_npc"
//////////////////////////////////////////////////
/* Function definitions */
//////////////////////////////////////////////////
int GetTotalLA(object oTarget)
{
int nLA;
int nRace = GetRacialType(oTarget);
if(GetPRCSwitch(PRC_XP_USE_SIMPLE_LA))
nLA += StringToInt(Get2DACache("ECL", "LA", nRace));
if(GetPRCSwitch(PRC_XP_INCLUDE_RACIAL_HIT_DIE_IN_LA))
nLA += StringToInt(Get2DACache("ECL", "RaceHD", nRace));
nLA += GetPersistantLocalInt(oTarget, "template_LA");
nLA -= GetPersistantLocalInt(oTarget, "LA_Buyoff");
return nLA;
}
int GetECL(object oTarget)
{
int nLevel;
// we need to use a derivation of the base xp formular to compute the
// pc level based on total XP.
//
// base XP formula (x = pc level, t = total xp):
//
// t = x * (x-1) * 500
//
// need to use some base math..
// transform for pq formula use (remove brackets with x inside and zero right side)
//
// x^2 - x - (t / 500) = 0
//
// use pq formula to solve it [ x^2 + px + q = 0, p = -1, q = -(t/500) ]...
//
// that's our new formula to get the level based on total xp:
// level = 0.5 + sqrt(0.25 + (t/500))
//
if(GetPRCSwitch(PRC_ECL_USES_XP_NOT_HD) && GetIsPC(oTarget))
nLevel = FloatToInt(0.5 + sqrt(0.25 + ( IntToFloat(GetXP(oTarget)) / 500 )));
else
nLevel = GetHitDice(oTarget);
nLevel += GetTotalLA(oTarget);
return nLevel;
}
int CheckDistance(object oDead, object oTest)
{
if(GetPRCSwitch(PRC_XP_MUST_BE_IN_AREA))
{
if(GetArea(oDead) != GetArea(oTest))
return FALSE;
if(GetDistanceBetween(oDead, oTest) > IntToFloat(GetPRCSwitch(PRC_XP_MAX_PHYSICAL_DISTANCE)))
return FALSE;
}
return TRUE;
}
float GetGroupBonusModifier(int nPartySize)
{
return 1 + ((nPartySize-1) * IntToFloat(GetPRCSwitch(PRC_XP_GROUP_BONUS))/100.0);
}
void GiveXPRewardToParty(object oKiller, object oDead, int nCR = 0)
{
//get prc switches
int bSPAM = GetPRCSwitch(PRC_XP_DISABLE_SPAM);
float fPRC_XP_DIVISOR_PC = IntToFloat(GetPRCSwitch(PRC_XP_PC_PARTY_COUNT_x100))/100.0;
float fPRC_XP_DIVISOR_HENCHMAN = IntToFloat(GetPRCSwitch(PRC_XP_HENCHMAN_PARTY_COUNT_x100))/100.0;
float fPRC_XP_DIVISOR_ANIMALCOMPANION = IntToFloat(GetPRCSwitch(PRC_XP_ANIMALCOMPANION_PARTY_COUNT_x100))/100.0;
float fPRC_XP_DIVISOR_FAMILIAR = IntToFloat(GetPRCSwitch(PRC_XP_FAMILIAR_PARTY_COUNT_x100))/100.0;
float fPRC_XP_DIVISOR_DOMINATED = IntToFloat(GetPRCSwitch(PRC_XP_DOMINATED_PARTY_COUNT_x100))/100.0;
float fPRC_XP_DIVISOR_SUMMONED = IntToFloat(GetPRCSwitch(PRC_XP_SUMMONED_PARTY_COUNT_x100))/100.0;
float fPRC_XP_DIVISOR_UNKNOWN = IntToFloat(GetPRCSwitch(PRC_XP_UNKNOWN_PARTY_COUNT_x100))/100.0;
//number of PCs in the party, average party level
int nPartySize, nAvgLevel;
//xp divisor
float fDivisor;
//get some basic group data like average PC level , PC group size, and XP divisor
object oTest = GetFirstFactionMember(oKiller, FALSE);
while(GetIsObjectValid(oTest))
{
if(CheckDistance(oDead, oTest))
{
if(GetIsPC(oTest))
{
nPartySize++;
nAvgLevel += GetECL(oTest);
fDivisor += fPRC_XP_DIVISOR_PC;
}
else
{
switch(GetAssociateTypeNPC(oTest))
{
case ASSOCIATE_TYPE_HENCHMAN: fDivisor += fPRC_XP_DIVISOR_HENCHMAN; break;
case ASSOCIATE_TYPE_ANIMALCOMPANION: fDivisor += fPRC_XP_DIVISOR_ANIMALCOMPANION; break;
case ASSOCIATE_TYPE_FAMILIAR: fDivisor += fPRC_XP_DIVISOR_FAMILIAR; break;
case ASSOCIATE_TYPE_DOMINATED: fDivisor += fPRC_XP_DIVISOR_DOMINATED; break;
case ASSOCIATE_TYPE_SUMMONED: fDivisor += fPRC_XP_DIVISOR_SUMMONED; break;
default: fDivisor += fPRC_XP_DIVISOR_UNKNOWN; break;
}
}
}
else if(!bSPAM && GetIsPC(oTest))
SendMessageToPC(oTest, "You are too far away from the combat to gain any experience.");
oTest = GetNextFactionMember(oKiller, FALSE);
}
//in case something weird is happenening
if(fDivisor == 0.0f)
return;
//calculate average partylevel
nAvgLevel /= nPartySize;
int nBaseXP;
if(!nCR) nCR = GetPRCSwitch(PRC_XP_USE_ECL_NOT_CR) ? GetECL(oDead) : FloatToInt(GetChallengeRating(oDead));
if(nCR < 1) nCR = 1;
if(GetPRCSwitch(PRC_XP_USE_BIOWARE_XPTABLE))
{
if(nCR > 40) nCR = 40;
if(nAvgLevel > 40) nAvgLevel = 40;
nBaseXP = StringToInt(Get2DACache("xptable", "C"+IntToString(nCR), nAvgLevel-1));
}
else
{
if(nCR > 70) nCR = 70;
if(nAvgLevel > 60) nAvgLevel = 60;
nBaseXP = StringToInt(Get2DACache("dmgxp", IntToString(nCR), nAvgLevel-1));
}
//average xp per party member
int nXPAward = FloatToInt(IntToFloat(nBaseXP)/fDivisor);
//now the module slider
nXPAward = FloatToInt(IntToFloat(nXPAward) * IntToFloat(GetPRCSwitch(PRC_XP_SLIDER_x100))/100.0);
//xp = 0, quit
if(!nXPAward)
return;
//group bonus
nXPAward = FloatToInt(IntToFloat(nXPAward) * GetGroupBonusModifier(nPartySize));
int nKillerLevel = GetECL(oKiller);
//calculate xp for each party member individually
oTest = GetFirstFactionMember(oKiller, FALSE);
float fPCAdjust;
int nMbrLevel;
while(GetIsObjectValid(oTest))
{
if(CheckDistance(oDead, oTest))
{
if(GetIsPC(oTest))
{
nMbrLevel = GetECL(oTest);
if(abs(nMbrLevel - nKillerLevel) <= GetPRCSwitch(PRC_XP_MAX_LEVEL_DIFF))
{
//now the individual slider
fPCAdjust = IntToFloat(GetLocalInt(oTest, PRC_XP_SLIDER_x100))/100.0;
if(fPCAdjust == 0.0) fPCAdjust = 1.0;
nXPAward = FloatToInt(IntToFloat(nXPAward) * fPCAdjust);
GiveXPReward(oTest, nXPAward);
}
else if(!bSPAM)
SendMessageToPC(oTest, "You are too high level to gain any experience.");
}
else
GiveXPReward(oTest, nXPAward, FALSE);
}
oTest = GetNextFactionMember(oKiller, FALSE);
}
}
void GiveXPReward(object oCreature, int nXP, int bIsPC = TRUE)
{
//actually give the XP
if(bIsPC)
{
if(GetPRCSwitch(PRC_XP_USE_SETXP))
SetXP(oCreature, GetXP(oCreature)+nXP);
else
GiveXPToCreature(oCreature, nXP);
}
else if(GetPRCSwitch(PRC_XP_GIVE_XP_TO_NPCS))
SetLocalInt(oCreature, "NPC_XP", GetLocalInt(oCreature, "NPC_XP")+nXP);
}
//::///////////////////////////////////////////////
//:: Effective Character Level Experience Script
//:: ecl_exp
//:: Copyright (c) 2004 Theo Brinkman
//:://////////////////////////////////////////////
/*
Call ApplyECLToXP() from applicable heartbeat script(s)
to cause experience to be adjusted according to ECL.
*/
//:://////////////////////////////////////////////
//:: Created By: Theo Brinkman
//:: Last Updated On: 2004-07-28
//:://////////////////////////////////////////////
// CONSTANTS
const string sLEVEL_ADJUSTMENT = "ecl_LevelAdjustment";
const string sXP_AT_LAST_HEARTBEAT = "ecl_LastExperience";
int GetXPForLevel(int nLevel)
{
return nLevel*(nLevel-1)*500;
}
void ApplyECLToXP(object oPC)
{
//abort if simple LA is disabled
if(!GetPRCSwitch(PRC_XP_USE_SIMPLE_LA))
return;
//abort if it's not valid, still loading, or a PC
if(!GetIsObjectValid(oPC) || GetLocalInt(oPC, "PRC_ECL_Delay") || !GetIsPC(oPC))
return;
// Abort if they are registering as a cohort
if(GetLocalInt(oPC, "OriginalXP") || GetPersistantLocalInt(oPC, "RegisteringAsCohort"))
return;
// Let them make it to level 3 in peace
if(GetTag(GetModule()) == "Prelude")
return;
// And start HotU in peace
if(GetTag(GetArea(oPC)) == "q2a_yprooms")
return;
// Abort if they were just relevelled
if(GetLocalInt(oPC, "RelevelXP"))
{
DeleteLocalInt(oPC, "RelevelXP");
return;
}
//this is done first because leadership uses it too
int iCurXP = GetXP(oPC);
//if (DEBUG) DoDebug("ApplyECLToXP - iCurXP "+IntToString(iCurXP));
int iLastXP = GetPersistantLocalInt(oPC, sXP_AT_LAST_HEARTBEAT);
//if (DEBUG) DoDebug("ApplyECLToXP - iLastXP "+IntToString(iLastXP));
if(iCurXP > iLastXP)
{
//if (DEBUG) DoDebug("ApplyECLToXP - gained XP");
int iLvlAdj = GetTotalLA(oPC);
if(iLvlAdj)
{
//if (DEBUG) DoDebug("ApplyECLToXP - have LA");
int iPCLvl = GetHitDice(oPC);
// Get XP Ratio (multiply new XP by this to see what to subtract)
float fRealXPToLevel = IntToFloat(GetXPForLevel(iPCLvl+1));
float fECLXPToLevel = IntToFloat(GetXPForLevel(iPCLvl+1+iLvlAdj));
float fXPRatio = 1.0 - (fRealXPToLevel/fECLXPToLevel);
//At this point the ratio is based on total XP
//This is not correct, it should be based on the XP required to reach
//the next level.
//fRealXPToLevel = IntToFloat(iPCLvl*1000);
//fECLXPToLevel = IntToFloat((iPCLvl+iLvlAdj)*1000);
//fXPRatio = 1.0 - (fRealXPToLevel/fECLXPToLevel);
float fXPDif = IntToFloat(iCurXP - iLastXP);
int iXPDif = FloatToInt(fXPDif * fXPRatio);
int newXP = iCurXP - iXPDif;
SendMessageToPC(oPC, "XP gained since last heartbeat "+IntToString(FloatToInt(fXPDif)));
SendMessageToPC(oPC, "Real XP to level: "+IntToString(FloatToInt(fRealXPToLevel)));
SendMessageToPC(oPC, "ECL XP to level: "+IntToString(FloatToInt(fECLXPToLevel)));
SendMessageToPC(oPC, "Level Adjustment +"+IntToString(iLvlAdj)+". Reducing XP by " + IntToString(iXPDif));
SetXP(oPC, newXP);
}
}
iCurXP = GetXP(oPC);
SetPersistantLocalInt(oPC, sXP_AT_LAST_HEARTBEAT, iCurXP);
}
int GetBuyoffCost(object oPC)
{
int nECL = GetECL(oPC);
int nXP = (nECL-1) * 1000;
return nXP;
}
void BuyoffLevel(object oPC)
{
int nECL = GetECL(oPC);
int nXP = (nECL-1) * 1000;
SetXP(oPC, GetXP(oPC)-nXP);
int nBuyoff = GetPersistantLocalInt(oPC, "LA_Buyoff");
SetPersistantLocalInt(oPC, "LA_Buyoff", nBuyoff+1);
}
int GetCanBuyoffLA(object oPC)
{
int nReturn = FALSE;
int nBuyoff = GetPersistantLocalInt(oPC, "LA_Buyoff");
int nChar = GetHitDice(oPC);
int nLA = StringToInt(Get2DACache("ECL", "LA", GetRacialType(oPC))) + GetPersistantLocalInt(oPC, "template_LA");
int nCheck = nLA - nBuyoff;
if (DEBUG) DoDebug("PRE-LA nBuyoff "+IntToString(nBuyoff)+" nChar "+IntToString(nChar)+" nLA "+IntToString(nLA)+" nCheck "+IntToString(nCheck));
if (0 >= nCheck) // no LA
return FALSE;
if (!nBuyoff) // Not purchased anything yet
{
if (nChar >= StringToInt(Get2DACache("la_buyoff", "1st", nLA)))
nReturn = TRUE;
}
if (nBuyoff == 1) // Purchased first already
{
if (nChar >= StringToInt(Get2DACache("la_buyoff", "2nd", nLA)))
nReturn = TRUE;
}
if (nBuyoff == 2) // Purchased second already
{
if (nChar >= StringToInt(Get2DACache("la_buyoff", "3rd", nLA)))
nReturn = TRUE;
}
if (nBuyoff == 3) // Purchased third already
{
if (nChar >= StringToInt(Get2DACache("la_buyoff", "4th", nLA)))
nReturn = TRUE;
}
if (nBuyoff == 4) // Purchased fourth already
{
if (nChar >= StringToInt(Get2DACache("la_buyoff", "5th", nLA)))
nReturn = TRUE;
}
if (nBuyoff == 5) // Purchased fifth already
{
if (nChar >= StringToInt(Get2DACache("la_buyoff", "6th", nLA)))
nReturn = TRUE;
}
if (DEBUG) DoDebug("nReturn "+IntToString(nReturn)+" nBuyoff "+IntToString(nBuyoff)+" nChar "+IntToString(nChar)+" nLA "+IntToString(nLA));
return nReturn;
}