//::///////////////////////////////////////////////
//:: Psionics include: Power Points
//:: psi_inc_ppoints
//::///////////////////////////////////////////////
/** @file
    Defines functions for handling power points.

    @author Ornedan
    @date   Created - 2005.11.04
*/
//:://////////////////////////////////////////////
//:://////////////////////////////////////////////

//////////////////////////////////////////////////
/*                 Constants                    */
//////////////////////////////////////////////////

// Constants are provided via psi_inc_core

//////////////////////////////////////////////////
/*             Function prototypes              */
//////////////////////////////////////////////////

/**
 * Returns the given character's current power point count.
 *
 * @param oChar           Character whose power points to examine
 * @return                The character's current power point count
 */
/*
 * @param bCountTemporary If TRUE, the returned value is the sum
 *                        of the character's real and temporary PP,
 *                        otherwise just the real PP
 */
int GetCurrentPowerPoints(object oChar/*, int bCountTemporary = TRUE*/);

/**
 * Returns the given character's maximum power point count.
 *
 * @param oChar Character whose power points to examine
 * @return      The maximum number of power points the character
 *              can normally have
 */
int GetMaximumPowerPoints(object oChar);

/**
 * Returns the current power point count as a string in format:
 * "[current] / [maximum]"
 *
 * @param oChar Character whose power points to examine
 * @return      The given character's power point data in a formatted string
 */
string GetPowerPointsAsString(object oChar);

/**
 * Sends the given character a message telling their current power point
 * count. Format:
 * "Power Points Remaining: [current] / [maximum]"
 *
 * @param oChar Character whom to inform about their power points
 */
void TellCharacterPowerPointStatus(object oChar);

/**
 * Resets current power point count to maximum power points.
 * Any temporary power points are removed.
 *
 * @param oChar Character to perform power point reseting for.
 */
void ResetPowerPoints(object oChar);

/**
 * Increases the character's current power point count by up to the given
 * amount, limited to the character's maximum for real power points unless
 * specifically allowed to exceed the maximum.
 *
 * @param oChar         Character whose power points to adjust
 * @param nGain         How many power points to add
 * @param bCanExceedMax Whether the power point total can exceed the normal
 *                      maximum as a result of this increase
 * @param bInform       If TRUE, runs TellCharacterPowerPointStatus() on oChar
 *                      after making the modification.
 */
void GainPowerPoints(object oChar, int nGain, int bCanExceedMax = FALSE, int bInform = TRUE);

/**
 * Gives the character an amount of temporary power points. Temporary power
 * points are always used first and ignore the maximum PP limit.
 *
 * @param oChar     Character whose power points to adjust
 * @param nGain     How many power points to add
 * @param fDuration How long the temporary power points will last, in seconds
 * @param bInform   If TRUE, runs TellCharacterPowerPointStatus() on oChar
 *                  after making the modification.
 */
/*
void GainTemporaryPowerPoints(object oChar, int nGain, float fDuration, int bInform = TRUE);
*/
/**
 * Decreases the character's current power point count by up to the given
 * amount, limited to not going below 0.
 * Reaching 0 PP causes loss of psionic focus.
 *
 * @param oChar   Character whose power points to adjust
 * @param nLoss   How many power points to remove
 * @param bInform If TRUE, runs TellCharacterPowerPointStatus() on oChar
 *                after making the modification.
 */
void LosePowerPoints(object oChar, int nLoss, int bInform = TRUE);

/**
 * Unconditionally sets the given character's power point count to 0.
 * This causes psionic focus loss as normal.
 *
 * @param oChar   Character whose power points to null
 * @param bInform If TRUE, runs TellCharacterPowerPointStatus() on oChar
 *                after making the modification.
 */
void LoseAllPowerPoints(object oChar, int bInform = TRUE);


//////////////////////////////////////////////////
/*                  Includes                    */
//////////////////////////////////////////////////

#include "psi_inc_core"


//////////////////////////////////////////////////
/*             Internal functions               */
//////////////////////////////////////////////////

/** Internal function
 * @param oChar Character whose feats to evaluate
 * @return      The amount of Power Points gained from Feats
 */
int _GetFeatBonusPP(object oChar){
    int nBonusPP = 0;

    // Normal feats
    if(GetHasFeat(FEAT_WILD_TALENT, oChar))
        nBonusPP += 2;

    int i, nPsiTalents;
    for(i = FEAT_PSIONIC_TALENT_1; i <= FEAT_PSIONIC_TALENT_10; i++)
        if(GetHasFeat(i, oChar)) nPsiTalents++;

    nBonusPP += nPsiTalents * (2 + nPsiTalents + 1) / 2;

    // Epic feats
    int nImpManifestations;
    for(i = FEAT_IMPROVED_MANIFESTATION_1; i <= FEAT_IMPROVED_MANIFESTATION_10; i++)
        if(GetHasFeat(i, oChar)) nImpManifestations++;

    nBonusPP += nImpManifestations * (18 + nImpManifestations);

    // Racial boni
    if(GetHasFeat(FEAT_NATPSIONIC_1, oChar))
        nBonusPP += 1;
    if(GetHasFeat(FEAT_NATPSIONIC_2, oChar))
        nBonusPP += 2;
    if(GetHasFeat(FEAT_NATPSIONIC_3, oChar))
        nBonusPP += 3;
        
    if(GetRacialType(oChar) == RACIAL_TYPE_KALASHTAR)
        nBonusPP += GetHitDice(oChar);
        
    if(GetHasFeat(FEAT_ABERRANT_WARPED_MIND, oChar))
        nBonusPP += GetAberrantFeatCount(oChar);        

    return nBonusPP;
}

/** Internal function
 * @param oChar          Character whose ability modifier to evaluate
 * @param nFirstPsiClass The CLASS_TYPE_* of the character's first psionic class
 * @return               The amount of Bonus Power Points gained from Abilities
 */
int _GetModifierPP (object oChar, int nFirstPsiClass)
{
    int nPP = 0;
    int nBonus;
    int nPsion   = GetLevelByClass(CLASS_TYPE_PSION, oChar)
                 + (nFirstPsiClass == CLASS_TYPE_PSION ? GetPsionicPRCLevels(oChar) : 0);
    int nPsychic = GetLevelByClass(CLASS_TYPE_PSYWAR, oChar)
                 + (nFirstPsiClass == CLASS_TYPE_PSYWAR ? GetPsionicPRCLevels(oChar) : 0);
    int nRogue   = GetLevelByClass(CLASS_TYPE_PSYCHIC_ROGUE, oChar)
                 + (nFirstPsiClass == CLASS_TYPE_PSYCHIC_ROGUE ? GetPsionicPRCLevels(oChar) : 0);                 
    int nWilder  = GetLevelByClass(CLASS_TYPE_WILDER, oChar)
                 + (nFirstPsiClass == CLASS_TYPE_WILDER ? GetPsionicPRCLevels(oChar) : 0);
    int nZuoken  = GetLevelByClass(CLASS_TYPE_FIST_OF_ZUOKEN, oChar)
                 + (nFirstPsiClass == CLASS_TYPE_FIST_OF_ZUOKEN ? GetPsionicPRCLevels(oChar) : 0);
    int nWarmind = GetLevelByClass(CLASS_TYPE_WARMIND, oChar)
                 + (nFirstPsiClass == CLASS_TYPE_WARMIND ? GetPsionicPRCLevels(oChar) : 0);

    if(nPsion > 0)
    {
        if(nPsion > 20) nPsion = 20;
        nBonus = (nPsion * GetAbilityModifier(ABILITY_INTELLIGENCE, oChar)) / 2;
        nPP += nBonus;
    }
    if(nPsychic > 0)
    {
        if(nPsychic > 20) nPsychic = 20;
        nBonus = (nPsychic * GetAbilityModifier(ABILITY_WISDOM, oChar)) / 2;
        nPP += nBonus;
    }
    if(nRogue > 0)
    {
        if(nRogue > 20) nRogue = 20;
        nBonus = (nRogue * GetAbilityModifier(ABILITY_INTELLIGENCE, oChar)) / 2;
        nPP += nBonus;
    }    
    if(nWilder > 0)
    {
        if(nWilder > 20) nWilder = 20;
        nBonus = (nWilder * GetAbilityModifier(ABILITY_CHARISMA, oChar)) / 2;
        nPP += nBonus;
    }
    if(nZuoken > 0)
    {
        if(nZuoken > 10) nZuoken = 10;
        nBonus = (nZuoken * GetAbilityModifier(ABILITY_WISDOM, oChar)) / 2;
        nPP += nBonus;
    }
    if(nWarmind > 0)
    {
        if(nWarmind > 10) nWarmind = 10;
        nBonus = (nWarmind * GetAbilityModifier(ABILITY_WISDOM, oChar)) / 2;
        nPP += nBonus;
    }

    return nPP;
}

/** Internal function
 * @param oChar          Character whose classes to evaluate
 * @param nClass         The CLASS_TYPE_* of the specific class to evaluate
 * @param nFirstPsiClass The CLASS_TYPE_* of the character's first psionic class
 * @return               The amount of Power Points gained from levels in the
 *                       given class
 */
int _GetPPForClass (object oChar, int nClass, int nFirstPsiClass)
{
    int nPP;
    int nLevel = GetLevelByClass(nClass, oChar)
               + (nFirstPsiClass == nClass ? GetPsionicPRCLevels(oChar) : 0);
    string sPsiFile = GetAMSKnownFileName(nClass);
    nPP = StringToInt(Get2DACache(sPsiFile, "PowerPoints", nLevel - 1)); // Index from 0

    return nPP;
}


//////////////////////////////////////////////////
/*             Function definitions             */
//////////////////////////////////////////////////

int GetCurrentPowerPoints(object oChar/*, int bCountTemporary = TRUE*/)
{
    int nRealPP =      GetLocalInt(oChar, POWER_POINT_VARNAME);
    //int nTemporaryPP = 0; ///@todo If necessary

    return nRealPP; //+ nTemporaryPP;
}

int GetMaximumPowerPoints(object oChar)
{
    int nMaxPP;

    // The character's first psionic class is considered to be the one that +ML PrCs add to
    int nFirstPsiClass = GetPrimaryPsionicClass(oChar);

    nMaxPP += _GetPPForClass(oChar, CLASS_TYPE_PSION, nFirstPsiClass);
    nMaxPP += _GetPPForClass(oChar, CLASS_TYPE_WILDER, nFirstPsiClass);
    nMaxPP += _GetPPForClass(oChar, CLASS_TYPE_PSYWAR, nFirstPsiClass);
    nMaxPP += _GetPPForClass(oChar, CLASS_TYPE_PSYCHIC_ROGUE, nFirstPsiClass);
    nMaxPP += _GetPPForClass(oChar, CLASS_TYPE_FIST_OF_ZUOKEN, nFirstPsiClass);
    nMaxPP += _GetPPForClass(oChar, CLASS_TYPE_WARMIND, nFirstPsiClass);

    nMaxPP += _GetModifierPP(oChar, nFirstPsiClass);

    nMaxPP += _GetFeatBonusPP(oChar);

    return nMaxPP;
}

string GetPowerPointsAsString(object oChar)
{
    return IntToString(GetCurrentPowerPoints(oChar)) + " / " + IntToString(GetMaximumPowerPoints(oChar));
}

void TellCharacterPowerPointStatus(object oChar)
{
    FloatingTextStringOnCreature(GetStringByStrRef(16824181) + " " + GetPowerPointsAsString(oChar), // "Power Points Remaining:"
                                 oChar, FALSE);
}

void ResetPowerPoints(object oChar)
{
    SetLocalInt(oChar, POWER_POINT_VARNAME, GetMaximumPowerPoints(oChar));
}

void GainPowerPoints(object oChar, int nGain, int bCanExceedMax = FALSE, int bInform = TRUE)
{
    int nCurPP = GetCurrentPowerPoints(oChar/*, FALSE*/);
        nCurPP += nGain;

    if(!bCanExceedMax)
    {
        int nMaxPP = GetMaximumPowerPoints(oChar);
        if(nCurPP > nMaxPP)
            nCurPP = nMaxPP;
    }

    SetLocalInt(oChar, POWER_POINT_VARNAME, nCurPP);

    if(bInform)
        TellCharacterPowerPointStatus(oChar);
}

/*
void GainTemporaryPowerPoints(object oChar, int nGain, float fDuration, int bInform = TRUE)
{
}
*/

void LosePowerPoints(object oChar, int nLoss, int bInform = TRUE)
{
    int nCurPP = GetCurrentPowerPoints(oChar/*, FALSE*/);
        nCurPP -= nLoss;
    if(nCurPP < 0)
        nCurPP = 0;

    SetLocalInt(oChar, POWER_POINT_VARNAME, nCurPP);

    if(GetCurrentPowerPoints(oChar) == 0)
        LosePsionicFocus(oChar);

    if(bInform)
        TellCharacterPowerPointStatus(oChar);
}

void LoseAllPowerPoints(object oChar, int bInform = TRUE)
{
    SetLocalInt(oChar, POWER_POINT_VARNAME, 0);

    LosePsionicFocus(oChar);

    if(bInform)
        TellCharacterPowerPointStatus(oChar);
}

// Test main
//void main(){}