//:: 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 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); }