Initial Commit
Initial Commit [v1.01]
This commit is contained in:
342
_module/nss/xp_system.nss
Normal file
342
_module/nss/xp_system.nss
Normal file
@@ -0,0 +1,342 @@
|
||||
//:: 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 = 4000; //:: Max per PC reward, after split
|
||||
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user