//:://////////////////////////////////////////////
//:: Scarface's XP/GP System V2.1
//:: SF_XP
//:://////////////////////////////////////////////
/*
     All code created and written by Scarface
*/
//////////////////////////////////////////////////

//:: CONSTANTS
//:: You can adjust these constants to suit your module

// XP modifier for adjusting XP reward given - higher means more XP
// Default is 10.
// Setting this to 10 will give a similar XP amount to the default
// bioware XP engines XP Slider.
// Do NOT set this to 0
const int XP_MODIFIER = 30;

// Reward gold to players for killing creatures?
// If TRUE use the GP_REWARD_MULTIPLIER const to multiply amount of gold rewarded
// TRUE = Yes     :     FALSE = No
const int REWARD_GP = TRUE;

// This will multiply the Gold rewarded if the REWARD_GP const above is set to TRUE
// Default is 1.0 (1.0 means equal to the amount of XP given, 2.0 would mean
// double the amount of gold and 0.5 would mean half).
// It basically multiplies the XP reward (GP_REWARD_MULTIPLIER x XP = GP Reward)
const float GP_REWARD_MULTIPLIER = 16.0;

// Bonus XP/GP reward for the dealing the killing blow to the creature
// Default is 0.1 = 10%
// If the REWARD_GP const above is set to FALSE then ONLY an XP bonus is given
// * Note * Changed so this will only apply if the killer is in party
const float KILLER_XP_GP_BONUS = 0.05;

// This will give an XP/GP bonus per player in the party
// Default is 0.1 = 10% per player
const float PARTY_XP_GP_BONUS = 0.05;

// Display floating text above each party member for XP/GP rewarded
// TRUE = Yes     :     FALSE = No
const int FLOATING_TEXT = TRUE;

// Distance between each party member and the dead creature to be
// included for XP reward
// Default is 15.0 meters
// I recommend you do NOT set this lower than about 5.0 meters
// otherwise you could end up not getting any XP/GP reward
const float PARTY_DIST = 80.0;

// Party level gap for minimal XP
// If the difference between highest level party member and the lowest
// level party member is greater than this, then XP/GP rewarded will be
// the minumum possible, this helps to stop powerleveling
// Default 10  (set to 40 to turn this feature off)
const int MAX_PARTY_GAP = 21;

// XP pentalty for each summon/familiar/henchman in the party within
// the specified distance set by the PARTY_DIST const
// Default 0.2 = 20% penalty per summon/familiar/henchman)
const float SUMMON_PENALTY = 0.01;

// Do you want XP to be divided between all PC's in the party within
// the specified distance set by the PARTY_DIST consts
// If set TRUE then XP will be divided between PC's so if the XP was 200
// and there were 2 PC's they would get 100 XP each
// If set FALSE they would get 200 XP each
// Default TRUE
const int PC_DIVIDE_XP = FALSE;

// Minimum XP possible for all PC's
// Default is 5 XP
// Do NOT set this to 0
const int MIN_XP = 50;

// Check XP to consider level or not?
// Set to TRUE to check the PC's XP to consider level
// Set to FALSE to only use their current level
const int XP_CHECK = FALSE; //Default = FALSE (Use Hit Dice Not XP)

////////////////////////////////////////////////////////////////////////////////
// PC Level Max XP consts
////////////////////////////////////////////////////////////////////////////////
// You can set the maximum XP possible for each PC level for more control
// Do NOT set any of these lower than the MIN_XP const above
// Default is 600 XP max possible for all levels
// * NOTE: This is ONLY a maximum possible, PC's will still get there normal
//         XP based on the XP_MODIFIER const.
const int
LEVEL_1_MAX_XP = 200,
LEVEL_2_MAX_XP = 200,
LEVEL_3_MAX_XP = 200,
LEVEL_4_MAX_XP = 200,
LEVEL_5_MAX_XP = 200,
LEVEL_6_MAX_XP = 300,
LEVEL_7_MAX_XP = 300,
LEVEL_8_MAX_XP = 300,
LEVEL_9_MAX_XP = 300,
LEVEL_10_MAX_XP = 300,
LEVEL_11_MAX_XP = 400,
LEVEL_12_MAX_XP = 400,
LEVEL_13_MAX_XP = 400,
LEVEL_14_MAX_XP = 400,
LEVEL_15_MAX_XP = 400,
LEVEL_16_MAX_XP = 500,
LEVEL_17_MAX_XP = 500,
LEVEL_18_MAX_XP = 500,
LEVEL_19_MAX_XP = 500,
LEVEL_20_MAX_XP = 500,
LEVEL_21_MAX_XP = 600,
LEVEL_22_MAX_XP = 600,
LEVEL_23_MAX_XP = 600,
LEVEL_24_MAX_XP = 600,
LEVEL_25_MAX_XP = 600,
LEVEL_26_MAX_XP = 600,
LEVEL_27_MAX_XP = 600,
LEVEL_28_MAX_XP = 600,
LEVEL_29_MAX_XP = 600,
LEVEL_30_MAX_XP = 600,
LEVEL_31_MAX_XP = 600,
LEVEL_33_MAX_XP = 600,
LEVEL_32_MAX_XP = 600,
LEVEL_34_MAX_XP = 600,
LEVEL_35_MAX_XP = 600,
LEVEL_36_MAX_XP = 600,
LEVEL_37_MAX_XP = 600,
LEVEL_38_MAX_XP = 600,
LEVEL_39_MAX_XP = 600,
LEVEL_40_MAX_XP = 600; // No need for this really

////////////////////////////////////////////////////////////////////////////////
//:: DO NOT TOUCH ANYTHING BELOW HERE !!!!!
////////////////////////////////////////////////////////////////////////////////
// Declare the functions
int GetMaxXP(object oPC);
int CalculateXP(float fLevel, float fCR);
void GiveXP(object oKiller, int nXP, float fKillerBonus, int nDiff, int nPlayer);
int GetLevelFromXP(object oPC);
void XPDebugMessage(object oPC, float fCR, int nDiff, int nLoLevel,
int nHiLevel, float fAvLevel);
////////////////////////////////////////////////////////////////////////////////
void main()
{
    // Define major variables
    object oKiller = GetLastKiller();
    if (!GetIsPC(oKiller) && !GetIsPC(GetMaster(oKiller)))  return;
    object oParty = GetFirstFactionMember(oKiller, FALSE);
    float fCR = GetChallengeRating(OBJECT_SELF), fDist;
    int nPlayer, nSummon, nTotalLevel, nHD, nXPToGive, nHiLevel, nLoLevel = 40;

    // Calculate the amount of members oPC's Party
    while (GetIsObjectValid(oParty))
    {
        // Make sure the party member is NOT dead and are within the specified distance
        fDist = GetDistanceToObject(oParty);
        if (!GetIsDead(oParty) && fDist >= 0.0 && fDist <= PARTY_DIST)
        {
                // Party member is a player
                if(GetIsPC(oParty))
                {
                    // Number of players
                    nPlayer++;

                    // Get total level of all PC party members
                    nTotalLevel += GetLevelFromXP(oParty);

                    // GetHighest/lowest party members
                    nHD = GetLevelFromXP(oParty);
                    if (nHD > nHiLevel) nHiLevel = nHD;
                    if (nHD < nLoLevel) nLoLevel = nHD;
                }
                // Party member is a summon/familiar/henchman
                else
                {
                    nSummon++;
                }
        }
        oParty = GetNextFactionMember(oKiller, FALSE);
    }

    // This check is to stop the "DIVIDED BY ZERO" error message
    if (!nPlayer) nPlayer = 1;

    // Get average party level calculate difference between highest and lowest
    float fAvLevel = (IntToFloat(nTotalLevel) / nPlayer);
    int nDiff = abs(nHiLevel - nLoLevel);

    // Calculate XP
    int nBaseXP = CalculateXP(fAvLevel, fCR);
    int nXP = ((nBaseXP * XP_MODIFIER) / 10);

    // Lets make sure the XP reward is within consts parameters
    int nMaxXP = GetMaxXP(oKiller);

    // Calculate Penalties based on consts
    float fPenalty = (nXP *(nSummon * SUMMON_PENALTY)), fPartyBonus, fKillerBonus;

    if (PC_DIVIDE_XP)
    {
        nXPToGive = ((nXP - FloatToInt(fPenalty)) / nPlayer);
    }
    else
    {
        nXPToGive = (nXP - FloatToInt(fPenalty));
    }
    // If there is more than 1 player in the party then calculate
    // XP Bonuses based on consts
    if (nPlayer)
    {
        fPartyBonus = (nXP * (PARTY_XP_GP_BONUS * nPlayer));
        fKillerBonus = (nXPToGive * KILLER_XP_GP_BONUS);
        nXPToGive = FloatToInt(fPartyBonus) + nXPToGive;
    }
    GiveXP(oKiller, nXPToGive, fKillerBonus, nDiff, nPlayer);
    //XPDebugMessage(oKiller, fCR, nDiff, nLoLevel, nHiLevel, fAvLevel);

}
// This is my function that calculates the XP reward
int CalculateXP(float fLevel, float fCR)
{
    float fXPModifier, fDiff = fabs(fLevel - fCR), fBonus = (((0.1 * fCR) * 10) / 2);
    if (fCR >= fLevel)
    {
        if (fDiff >= 10.0) fXPModifier = 100.0;
        else if (fDiff >= 5.0 && fDiff < 10.0) fXPModifier = 70.0;
        else if (fDiff >= 4.0 && fDiff < 5.0) fXPModifier = 50.0;
        else if (fDiff >= 3.0 && fDiff < 4.0) fXPModifier = 35.0;
        else if (fDiff >= 2.0 && fDiff < 3.0) fXPModifier = 20.0;
        else if (fDiff >= 1.0 && fDiff < 2.0) fXPModifier = 10.0;
        else fXPModifier = 5.0;
    }
    else if (fCR < fLevel)
    {
        if (fDiff >= 4.0) fXPModifier = 0.25;
        else if (fDiff >= 3.0 && fDiff < 4.0) fXPModifier = 0.5;
        else if (fDiff >= 2.0 && fDiff < 3.0) fXPModifier = 1.0;
        else if (fDiff >= 1.0 && fDiff < 2.0) fXPModifier = 1.5;
        else fXPModifier = 2.0;
    }
    return FloatToInt((fXPModifier * 10) + fBonus);
}
// This is my function to give XP to each party member within
// the distance specified by the constants
void GiveXP(object oKiller, int nXPToGive, float fKillerBonus, int nDiff, int nPlayer)
{
    int nMaxXP;
    float fDist;
    // Get first party members (Only PC's)
    object oParty = GetFirstFactionMember(oKiller);
    // Loops through all party members
    while (GetIsObjectValid(oParty))
    {
        // Make sure the party member is NOT dead and are within the specified distance
        fDist = GetDistanceToObject(oParty);
        if (GetIsPC(oParty))
        {
            if (!GetIsDead(oParty) && fDist >= 0.0 && fDist <= PARTY_DIST)
            {
                // Reward the killer with bonus if specified in consts
                nMaxXP = GetMaxXP(oParty);
                if (nXPToGive > nMaxXP)
                {
                    nXPToGive = nMaxXP;
                }
                else if (nXPToGive < MIN_XP)
                {
                    nXPToGive = MIN_XP;
                }
                if (oKiller == oParty && nPlayer > 1)
                {
                    if (nDiff > MAX_PARTY_GAP)
                    {
                        FloatingTextStringOnCreature("Party level difference is too great", oParty);
                        nXPToGive = MIN_XP;
                    }
                    if (FLOATING_TEXT)
                    {
                        if (KILLER_XP_GP_BONUS > 0.0)
                        {
                            FloatingTextStringOnCreature("Killer Bonus", oParty, FALSE);
                        }
                        FloatingTextStringOnCreature(IntToString(nXPToGive + FloatToInt(fKillerBonus))+"XP", oParty, FALSE);
                    }
                    if (REWARD_GP)
                    {
                        if (FLOATING_TEXT)
                        {
                            FloatingTextStringOnCreature(IntToString(FloatToInt((IntToFloat(nXPToGive) + fKillerBonus) * GP_REWARD_MULTIPLIER))+"GP", oParty, FALSE);
                        }
                        GiveGoldToCreature(oParty, FloatToInt((IntToFloat(nXPToGive) + fKillerBonus) * GP_REWARD_MULTIPLIER));
                    }
                    GiveXPToCreature(oParty, (nXPToGive + FloatToInt(fKillerBonus)));
                }
                // Reward other party members
                else
                {
                    if (FLOATING_TEXT)
                    {
                        DelayCommand(0.1, FloatingTextStringOnCreature(IntToString(nXPToGive)+"XP", oParty, FALSE));
                    }
                    if (REWARD_GP)
                    {
                        if (FLOATING_TEXT)
                        {
                            DelayCommand(0.1, FloatingTextStringOnCreature(IntToString(FloatToInt((IntToFloat(nXPToGive) + fKillerBonus) * GP_REWARD_MULTIPLIER))+"GP", oParty, FALSE));
                        }
                        GiveGoldToCreature(oParty, FloatToInt((IntToFloat(nXPToGive) + fKillerBonus) * GP_REWARD_MULTIPLIER));
                    }
                    GiveXPToCreature(oParty, nXPToGive);
                }
            }
        }
        oParty = GetNextFactionMember(oKiller);
    }
}
// This is my function for returning the max XP for the PC's level based on the consts
int GetMaxXP(object oPC)
{
    int iMaxXP;
    switch(GetLevelFromXP(oPC))
    {
        case 1: iMaxXP = LEVEL_1_MAX_XP; break;
        case 2: iMaxXP = LEVEL_2_MAX_XP; break;
        case 3: iMaxXP = LEVEL_3_MAX_XP; break;
        case 4: iMaxXP = LEVEL_4_MAX_XP; break;
        case 5: iMaxXP = LEVEL_5_MAX_XP; break;
        case 6: iMaxXP = LEVEL_6_MAX_XP; break;
        case 7: iMaxXP = LEVEL_7_MAX_XP; break;
        case 8: iMaxXP = LEVEL_8_MAX_XP; break;
        case 9: iMaxXP = LEVEL_9_MAX_XP; break;
        case 10: iMaxXP = LEVEL_10_MAX_XP; break;
        case 11: iMaxXP = LEVEL_11_MAX_XP; break;
        case 12: iMaxXP = LEVEL_12_MAX_XP; break;
        case 13: iMaxXP = LEVEL_13_MAX_XP; break;
        case 14: iMaxXP = LEVEL_14_MAX_XP; break;
        case 15: iMaxXP = LEVEL_15_MAX_XP; break;
        case 16: iMaxXP = LEVEL_16_MAX_XP; break;
        case 17: iMaxXP = LEVEL_17_MAX_XP; break;
        case 18: iMaxXP = LEVEL_18_MAX_XP; break;
        case 19: iMaxXP = LEVEL_19_MAX_XP; break;
        case 20: iMaxXP = LEVEL_20_MAX_XP; break;
        case 21: iMaxXP = LEVEL_21_MAX_XP; break;
        case 22: iMaxXP = LEVEL_22_MAX_XP; break;
        case 23: iMaxXP = LEVEL_23_MAX_XP; break;
        case 24: iMaxXP = LEVEL_24_MAX_XP; break;
        case 25: iMaxXP = LEVEL_25_MAX_XP; break;
        case 26: iMaxXP = LEVEL_26_MAX_XP; break;
        case 27: iMaxXP = LEVEL_27_MAX_XP; break;
        case 28: iMaxXP = LEVEL_28_MAX_XP; break;
        case 29: iMaxXP = LEVEL_29_MAX_XP; break;
        case 30: iMaxXP = LEVEL_30_MAX_XP; break;
        case 31: iMaxXP = LEVEL_31_MAX_XP; break;
        case 32: iMaxXP = LEVEL_32_MAX_XP; break;
        case 33: iMaxXP = LEVEL_33_MAX_XP; break;
        case 34: iMaxXP = LEVEL_34_MAX_XP; break;
        case 35: iMaxXP = LEVEL_35_MAX_XP; break;
        case 36: iMaxXP = LEVEL_36_MAX_XP; break;
        case 37: iMaxXP = LEVEL_37_MAX_XP; break;
        case 38: iMaxXP = LEVEL_38_MAX_XP; break;
        case 39: iMaxXP = LEVEL_39_MAX_XP; break;
        case 40: iMaxXP = LEVEL_40_MAX_XP; break;
    }
    return iMaxXP;
}
// This new function will get the players level determined by XP rather than
// the players level to stop exploiting
int GetLevelFromXP(object oPC)
{
    //Changed to check HD not XP!
    int iXP;

    //If we are checking their XP to consider what level they are..
    if(XP_CHECK)
    {

    iXP = GetXP(oPC);
    if (iXP >= 780000) iXP = 40;
    else if (iXP >= 741000) iXP = 39;
    else if (iXP >= 703000) iXP = 38;
    else if (iXP >= 666000) iXP = 37;
    else if (iXP >= 630000) iXP = 36;
    else if (iXP >= 595000) iXP = 35;
    else if (iXP >= 561000) iXP = 34;
    else if (iXP >= 528000) iXP = 33;
    else if (iXP >= 496000) iXP = 32;
    else if (iXP >= 465000) iXP = 31;
    else if (iXP >= 435000) iXP = 30;
    else if (iXP >= 406000) iXP = 29;
    else if (iXP >= 378000) iXP = 28;
    else if (iXP >= 351000) iXP = 27;
    else if (iXP >= 325000) iXP = 26;
    else if (iXP >= 300000) iXP = 25;
    else if (iXP >= 276000) iXP = 24;
    else if (iXP >= 253000) iXP = 23;
    else if (iXP >= 231000) iXP = 22;
    else if (iXP >= 210000) iXP = 21;
    else if (iXP >= 190000) iXP = 20;
    else if (iXP >= 171000) iXP = 19;
    else if (iXP >= 153000) iXP = 18;
    else if (iXP >= 136000) iXP = 17;
    else if (iXP >= 120000) iXP = 16;
    else if (iXP >= 105000) iXP = 15;
    else if (iXP >= 91000) iXP = 14;
    else if (iXP >= 78000) iXP = 13;
    else if (iXP >= 66000) iXP = 12;
    else if (iXP >= 55000) iXP = 11;
    else if (iXP >= 45000) iXP = 10;
    else if (iXP >= 36000) iXP = 9;
    else if (iXP >= 28000) iXP = 8;
    else if (iXP >= 21000) iXP = 7;
    else if (iXP >= 15000) iXP = 6;
    else if (iXP >= 10000) iXP = 5;
    else if (iXP >= 6000) iXP = 4;
    else if (iXP >= 3000) iXP = 3;
    else if (iXP >= 1000) iXP = 2;
    else                  iXP = 1;
    }
    else
    {
     iXP = GetHitDice(oPC);
    }

    return iXP;
}
void XPDebugMessage(object oPC, float fCR, int nDiff, int nLoLevel, int nHiLevel, float fAvLevel)
{
    object oParty = GetFirstFactionMember(oPC);
    while (GetIsObjectValid(oParty))
    {
        SendMessageToPC(oParty, "\nDebug Info"+
                                "\nCreature CR: "+FloatToString(fCR)+
                                "\nHighest Level PC: "+IntToString(nHiLevel)+
                                "\nLowest Level PC: "+IntToString(nLoLevel)+
                                "\nLevel Difference: "+IntToString(nDiff)+
                                "\nAverage Party Level: "+FloatToString(fAvLevel));

        oParty = GetNextFactionMember(oPC);
    }
}