Amon_PRC8/_module/nss/xp_system.nss

342 lines
11 KiB
Plaintext
Raw Permalink Normal View History

2025-04-03 19:00:46 -04:00
//:: Constants for configuration
const int MAX_CHARACTER_LEVEL = 60; //:: Maximum character level
const int MAX_PARTY_SIZE = 12; //:: Maximum party size for XP split
const int LEVEL_DIFFERENCE_LIMIT = 8; //:: Max level difference within party
const int XP_REWARD_CAP = 800; //:: Max per PC reward, after split
2025-04-03 19:00:46 -04:00
const float BASE_XP_MULTIPIER = 50.0;
const float BONUS_XP_MODIFIER = 5.0;
const float MAX_CR = 80.0; //:: Maximum challenge rating cap
const float HIGH_LEVEL_PENALTY_THRESHOLD = 15.0; //:: Threshold for high-level penalty
const float XP_ADJUST_MULTIPLIER = 1.0; //:: Multiplier for XP adjustment
const float XP_DIVISOR_PC = 1.0;
const float XP_DIVISOR_DOMINATED = 0.0;
const float XP_DIVISOR_HENCHMAN = 0.5;
const float XP_DIVISOR_SUMMONED = 0.0;
const float XP_DIVISOR_ANIMALCOMPANION = 0.0;
const float XP_DIVISOR_FAMILIAR = 0.0;
const float XP_DIVISOR_UNKNOWN = 0.5;
#include "inc_utility"
int GetECLMod(object oCreature);
int GetActualLevel(object oPC);
void RewardCombatXP(object oKiller, object oVictim = OBJECT_SELF);
/*
Race LA is done entirely through this script. DO NOT set PRC_XP_USE_SIMPLE_LA
or the XP penalty will be applied twice
*/
int GetECLMod(object oCreature)
{
//:: Note this is not MyPRCGetRacialType becuase we want to include subraces too
int nRace = GetRacialType(oCreature);
int nLA = 0;
nLA = StringToInt(Get2DACache("ECL", "LA", nRace));
return nLA;
}
//:: Returns oPC's level based on XP amount, not hit dice.
int GetActualLevel(object oPC)
{
return FloatToInt(0.5 + sqrt(0.25 + ( IntToFloat(GetXP(oPC)) / 500 )));
}
void RewardCombatXP(object oKiller, object oVictim = OBJECT_SELF)
{
//:: Ensure Master gets credit for Associate kills
if (GetIsObjectValid(oKiller))
{
object oMaster = GetMaster(oKiller);
if (GetIsObjectValid(oMaster))
{
oKiller = oMaster;
}
}
//:: If killed by a trap, reward trap placer
if (GetObjectType(oKiller) == OBJECT_TYPE_TRIGGER)
oKiller = GetTrapCreator(oKiller);
//:: Sanity checks
if (oKiller == oVictim
|| !GetIsObjectValid(oKiller)
|| GetIsFriend(oKiller, oVictim))
{
return;
}
//:: Get challenge rating of oVictim
float fCR = GetChallengeRating(oVictim);
if (fCR > MAX_CR) fCR = MAX_CR;
//:: Calculate base and bonus XP
float BaseEXP = fCR * BASE_XP_MULTIPIER;
float BonusEXP = fCR + BONUS_XP_MODIFIER;
//:: Variables for party info
float PartyLevelSum = 0.0;
int NumOfParty = 0;
int nNoLeech = 0;
float PartyModifier = 0.0; //:: Tracks associate modifier for PartySize
//:: Iterate over PCs
object oPartyMember = GetFirstFactionMember(oKiller, TRUE);
while (GetIsObjectValid(oPartyMember))
{
if (GetArea(oVictim) == GetArea(oPartyMember))
{
int nTrueLevel = GetActualLevel(oPartyMember) + GetECLMod(oPartyMember);
nNoLeech = PRCMax(nNoLeech, nTrueLevel);
PartyLevelSum += nTrueLevel;
NumOfParty++;
PartyModifier += XP_DIVISOR_PC;
}
oPartyMember = GetNextFactionMember(oPartyMember, TRUE);
}
//:: Iterate over associates
oPartyMember = GetFirstFactionMember(oKiller, FALSE);
while (GetIsObjectValid(oPartyMember))
{
if (GetArea(oVictim) == GetArea(oPartyMember))
{
int nAssociateType = GetAssociateType(oPartyMember);
int nTrueLevel = GetActualLevel(oPartyMember) + GetECLMod(oPartyMember);
switch (nAssociateType)
{
case ASSOCIATE_TYPE_HENCHMAN:
PartyModifier += XP_DIVISOR_HENCHMAN;
break;
case ASSOCIATE_TYPE_ANIMALCOMPANION:
PartyModifier += XP_DIVISOR_ANIMALCOMPANION;
break;
case ASSOCIATE_TYPE_FAMILIAR:
PartyModifier += XP_DIVISOR_FAMILIAR;
break;
case ASSOCIATE_TYPE_SUMMONED:
PartyModifier += XP_DIVISOR_SUMMONED;
break;
case ASSOCIATE_TYPE_DOMINATED:
PartyModifier += XP_DIVISOR_DOMINATED;
break;
default:
PartyModifier += XP_DIVISOR_UNKNOWN;
break;
}
}
oPartyMember = GetNextFactionMember(oPartyMember, FALSE);
}
//:: Avoid division by zero
if (PartyLevelSum <= 1.0) PartyLevelSum = 1.0;
if (NumOfParty <= 1) NumOfParty = 1;
//:: Calculate average party level
float PartyAvgLvl = PartyLevelSum / NumOfParty;
//:: Adjust XP based on challenge rating and party level
float AdjustValue = ((fCR / PartyAvgLvl) + 2.0) / 3.0;
float FinalMonValue = AdjustValue > 1.0
? BaseEXP + (BonusEXP * AdjustValue * XP_ADJUST_MULTIPLIER)
: BaseEXP * AdjustValue;
//:: Apply penalty if party average level is above threshold
if (PartyAvgLvl >= HIGH_LEVEL_PENALTY_THRESHOLD)
{
FinalMonValue -= PartyAvgLvl;
}
//:: Split the XP between the party members
if (NumOfParty > MAX_PARTY_SIZE)
NumOfParty = MAX_PARTY_SIZE;
float SplitFinalEXP = FinalMonValue / NumOfParty;
if (SplitFinalEXP <= 1.0)
SplitFinalEXP = 1.0;
//:: Add modifier for associates
SplitFinalEXP += PartyModifier;
//:: Distribute XP to party members
oPartyMember = GetFirstFactionMember(oKiller, TRUE);
while (GetIsObjectValid(oPartyMember))
{
if (GetArea(oVictim) == GetArea(oPartyMember))
{
int nHD = GetActualLevel(oPartyMember) + GetECLMod(oPartyMember);
if (GetIsDead(oPartyMember))
{
SendMessageToPC(oPartyMember, "You cannot gain experience while dead.");
}
else if (nHD <= (nNoLeech - (LEVEL_DIFFERENCE_LIMIT + 1)) ||
nHD >= FloatToInt(PartyAvgLvl) + LEVEL_DIFFERENCE_LIMIT)
{
SendMessageToPC(oPartyMember, "You are too far above or below the average party level to gain experience.");
}
else
{
GiveXPToCreature(oPartyMember, PRCMin(XP_REWARD_CAP, FloatToInt(SplitFinalEXP)));
}
}
oPartyMember = GetNextFactionMember(oPartyMember, TRUE);
}
}
/* void RewardCombatXP(object oKiller, object oVictim = OBJECT_SELF)
{
//:: Ensure Master gets credit for Associate kills
if (GetIsObjectValid(oKiller))
{
object oMaster = GetMaster(oKiller);
if (GetIsObjectValid(oMaster))
{
oKiller = oMaster;
}
}
//:: If killed by a trap, reward trap placer.
if(GetObjectType(oKiller) == OBJECT_TYPE_TRIGGER)
oKiller = GetTrapCreator(oKiller);
//:: Sanity checks
if (oKiller == oVictim
|| !GetIsObjectValid(oKiller)
|| GetIsFriend(oKiller, oVictim)
|| !GetIsObjectValid(GetFirstFactionMember(oKiller, TRUE)))
{
return;
}
//:: Get challenge rating of oVictim
float fCR = GetChallengeRating(oVictim);
//:: Cap CR to max value
if (fCR > MAX_CR) fCR = MAX_CR;
//:: Calculate base and bonus XP
float BaseEXP = fCR * BASE_XP_MULTIPIER;
float BonusEXP = fCR + BONUS_XP_MODIFIER;
//:: Variables for party info
float PartyLevelSum = 0.0;
int NumOfParty = 0;
int nNoLeech = 0;
float PartyModifier = 0.0; //:: Tracks associate modifier for PartySize
//:: Iterate over party members to calculate levels, count members, and add modifiers for associates
object oPartyMember = GetFirstFactionMember(oKiller, TRUE);
while (GetIsObjectValid(oPartyMember))
{
//:: Check if the member is in the same area as the victim
if (GetArea(oVictim) == GetArea(oPartyMember))
{
int nTrueLevel = GetActualLevel(oPartyMember)+ GetECLMod(oPartyMember);
int nLeech = nTrueLevel;
nNoLeech = max(nNoLeech, nLeech); //:: Track the highest hit dice for leeching
PartyLevelSum += nTrueLevel; //:: Sum up party member levels
NumOfParty++;
//:: Handle associates party size modifier
int nAssociateType = GetAssociateType(oPartyMember);
switch (nAssociateType)
{
case 1: //:: Henchman
PartyModifier += XP_DIVISOR_HENCHMAN;
break;
case 2: //:: Animal Companion
PartyModifier += XP_DIVISOR_ANIMALCOMPANION;
break;
case 3: //:: Familiar
PartyModifier += XP_DIVISOR_FAMILIAR;
break;
case 4: //:: Summoned
PartyModifier += XP_DIVISOR_SUMMONED;
break;
case 5: //:: Dominated
PartyModifier += XP_DIVISOR_DOMINATED;
break;
default: //:: Anything else should be a PC
PartyModifier += XP_DIVISOR_PC;
break;
}
}
oPartyMember = GetNextFactionMember(oPartyMember, TRUE);
}
//:: Avoid division by zero
if (PartyLevelSum <= 1.0) PartyLevelSum = 1.0;
if (NumOfParty <= 1) NumOfParty = 1;
//:: Calculate average party level
float PartyAvgLvl = PartyLevelSum / NumOfParty;
//:: Adjust XP based on challenge rating and party level
float AdjustValue = ((fCR / PartyAvgLvl) + 2.0) / 3.0;
float FinalMonValue = AdjustValue > 1.0
? BaseEXP + (BonusEXP * AdjustValue * XP_ADJUST_MULTIPLIER)
: BaseEXP * AdjustValue;
//:: Apply penalty if party average level is above threshold
if (PartyAvgLvl >= HIGH_LEVEL_PENALTY_THRESHOLD)
{
FinalMonValue -= PartyAvgLvl;
}
//:: Split the XP between the party members
if (NumOfParty > MAX_PARTY_SIZE)
NumOfParty = MAX_PARTY_SIZE;
float SplitFinalEXP = FinalMonValue / NumOfParty;
if (SplitFinalEXP <= 1.0)
SplitFinalEXP = 1.0;
//:: Add modifier for associates: they don't receive XP but affect the XP split
SplitFinalEXP += PartyModifier;
//:: Calculate party bonus
float PartyBonus = ((FinalMonValue - SplitFinalEXP + 1.0) / 1.75)
+ (FinalMonValue + ((21.0 - PartyAvgLvl) / 3.0));
int SFEint = FloatToInt(PartyBonus);
//:: Distribute XP to party members
oPartyMember = GetFirstFactionMember(oKiller, TRUE);
while (GetIsObjectValid(oPartyMember))
{
if (GetArea(oVictim) == GetArea(oPartyMember))
{
int nHD = GetActualLevel(oPartyMember)+ GetECLMod(oPartyMember);
//:: Check conditions for giving XP
if (GetIsDead(oPartyMember))
{
SendMessageToPC(oPartyMember, "You cannot gain experience while dead.");
}
else if (nHD <= (nNoLeech - (LEVEL_DIFFERENCE_LIMIT + 1)) ||
nHD >= FloatToInt(PartyAvgLvl) + LEVEL_DIFFERENCE_LIMIT)
{
SendMessageToPC(oPartyMember, "You are too far above or below the average party level to gain experience.");
}
else
{
GiveXPToCreature(oPartyMember, min(XP_REWARD_CAP, SFEint)); //:: Give XP to valid party member
}
}
oPartyMember = GetNextFactionMember(oPartyMember, TRUE);
}
} */
void main()
{
object oVictim = OBJECT_SELF;
object oKiller = GetLastKiller();
RewardCombatXP(oKiller, oVictim);
}