/*////////////////////////////////////////////////////////////////////////////////////////////////////
// Script Name: 0i_states_cond
//////////////////////////////////////////////////////////////////////////////////////////////////////
 Include scripts that handle states and conditions for combat.
*/////////////////////////////////////////////////////////////////////////////////////////////////////
#include "0i_main"
#include "0i_messages"
#include "0i_time"
//#include "X0_I0_COMBAT"
// Wrapper for ClearAllActions - we have added extra vars to be cleared as well.
// Note this references OBJECT_SELF!
void ai_ClearCreatureActions(int bClearCombatState = FALSE);
// Used in combat to keep track of the creatures last rounds action.
// One use is to make sure we don't use the same spell on the next round.
// 0+ is the spell that was cast, other actions use AI_LAST_ACTION_* constants.
void ai_SetLastAction(object oCreature, int nAction = AI_LAST_ACTION_NONE);
// Returns TRUE if oCreatures last rounds action is equal to nAction.
// 0+ is the spell that was cast, other actions use AI_LAST_ACTION_* constants.
int ai_CompareLastAction(object oCreature, int nAction);
// Sets the correct listen checks on oCreature.
void ai_SetListeningPatterns(object oCreature);
// Returns TRUE if oCreature is an elemental, undead, or golem i.e. non-living.
int ai_IsNonliving(int nRacialType);
// Returns TRUE if oCreature is in combat.
int ai_GetIsInCombat(object oCreature);
// Sets the time that this oCreature's current combat round started.
// Using action based combat rounds has an unfortunate side effect:
// Once you attack in melee you will continue to attack in melee do to hardcoded
// logic. This will "PUSH" your end of round back until it decides to stop attacking!
// We avoid this by setting the time and if we check for combat and 6 seconds has
// passed then we assume the current round is over, ClearAllActions, and start the next round.
void ai_SetCombatRound(object oCreature);
// Clears the current combat round timer by deleting the value.
void ai_EndCombatRound(object oCreature);
// Returns TRUE if AI_COMBAT_ROUND_IN_SECONDS has not passed since ai_SetCombatRound.
// If it returns FALSE then it will clear the current combat round timer.
int ai_IsInCombatRound(object oCreature, int nCombatRound = AI_COMBAT_ROUND_IN_SECONDS);
// Returns TRUE if oCreature is busy.
// This checks various actions to see if oCreature is busy;
// in combat, busy mode, Actions: attacking, casting spell, counterspelling,
// disabling trap, item casting spell, opening lock, resting, setting trap.
int ai_GetIsBusy(object oCreature);
// Returns a value based on the disabling effect.
// Dead = 1, Bleeding = 2, Dying = 2, Stunned = 29, Confused = 24, Paralyzed = 27
// Frightened 25, Turned = 35, Petrified = 79, Charmed = 23, Disappearappear = 75,
// Time Stop = 66, Dazed = 28, Sleep = 30.
// Returns FALSE if not Disabled.
int ai_Disabled(object oCreature);
// Set one of the AI_MODE_* bitwise constants on oAssociate to bOn.
void ai_SetAIMode(object oAssociate, int nBit, int bOn = TRUE);
// Return if nMode is set on oAssociate. Uses the AI_MODE_* bitwise constants.
int ai_GetAIMode(object oAssociate, int nBit);
// Set one of the AI_MAGIC_* bitwise constants on oAssociate to bOn.
void ai_SetMagicMode(object oAssociate, int nBit, int bOn = TRUE);
// Return if nMode is set on oAssociate. Uses the AI_MAGIC_* bitwise constants.
int ai_GetMagicMode(object oAssociate, int nBit);
// This is based off of the PC's settings for an associate and other creatures use a default.
// Set one of the AI_LOOT_* bitwise constants on oAssociate to bOn.
void ai_SetLootFilter(object oAssociate, int nBit, int bOn = TRUE);
// Return if nMode is set on oAssociate. Uses the AI_LOOT_* bitwise constants.
int ai_GetLootFilter(object oAssociate, int nBit);
// Set one of the AI_IP_* bitwise constants on oCreature to bOn.
void ai_SetItemProperty(object oCreature, string sVarname, int nBit, int bOn = TRUE);
// Return if nMode is set on oCreature. Uses the AI_IP_* bitwise constants.
int ai_GetItemProperty(object oCreature, string sVarname, int nBit);
// Returns the number of hitpoints a creature must have to not be healed.
// This is based off of the PC's settings for an associate and other creatures use a default.
int ai_GetHealersHpLimit(object oCreature, int bInCombat = TRUE);
// Returns TRUE if nCondition is within nCurrentConditions.
// nCurrentConditions is setup in ai_GetNegativeConditions.
int ai_GetHasNegativeCondition(int nCondition, int nCurrentConditions);
// Returns an integer with bitwise flags set that represent the current negative
// conditions on oCreature. ai_GetHasNegativeCondition uses this function.
int ai_GetNegativeConditions(object oCreature);
// Returns TRUE if oObject is in the line of sight of oCreature.
// If the creature is close LineOfSight doesn't work well.
int ai_GetIsInLineOfSight(object oCreature, object oObject);
// Add the specified condition flag to the behavior state of the caller
void ai_SetBehaviorState(int nCondition, int bValid = TRUE);
// Returns TRUE if the specified behavior flag is set on the caller
int ai_GetBehaviorState(int nCondition);
// Highlights the current mode for the widget passed.
void ai_HighlightWidgetMode(object oPC, object oAssociate, int nToken);
// Checks to see if the party scale is correctly adjusted.
void ai_CheckXPPartyScale(object oCreature);

void ai_ClearCreatureActions(int bClearCombatState = FALSE)
{
    if(AI_DEBUG) ai_Debug("0i_states_cond", "89", GetName(OBJECT_SELF) + " is clearing actions (" +
             IntToString(bClearCombatState) + ")!");
    DeleteLocalInt(OBJECT_SELF, AI_CURRENT_ACTION_MODE);
    ClearAllActions(bClearCombatState);
}
void ai_SetLastAction(object oCreature, int nAction = AI_LAST_ACTION_NONE)
{
    SetLocalInt(oCreature, sLastActionVarname, nAction);
}
int ai_CompareLastAction(object oCreature, int nAction)
{
    // Are we checking to see if we cast a spell?
    if(nAction == AI_LAST_ACTION_CAST_SPELL &&
        GetLocalInt(oCreature, sLastActionVarname) > -1) return TRUE;
    // Check other last actions.
    return (nAction == GetLocalInt(oCreature, sLastActionVarname));
}
void ai_SetListeningPatterns(object oCreature)
{
    SetListenPattern(oCreature, AI_I_SEE_AN_ENEMY, AI_ALLY_SEES_AN_ENEMY);
    SetListenPattern(oCreature, AI_I_HEARD_AN_ENEMY, AI_ALLY_HEARD_AN_ENEMY);
    SetListenPattern(oCreature, AI_ATKED_BY_WEAPON, AI_ALLY_ATKED_BY_WEAPON);
    SetListenPattern(oCreature, AI_ATKED_BY_SPELL, AI_ALLY_ATKED_BY_SPELL);
    SetListenPattern(oCreature, AI_I_AM_WOUNDED, AI_ALLY_IS_WOUNDED);
    SetListenPattern(oCreature, AI_I_AM_DEAD, AI_ALLY_IS_DEAD);
    SetListenPattern(oCreature, AI_I_AM_DISEASED, AI_ALLY_IS_DISEASED);
    SetListenPattern(oCreature, AI_I_AM_POISONED, AI_ALLY_IS_POISONED);
    SetListenPattern(oCreature, AI_I_AM_WEAK, AI_ALLY_IS_WEAK);
    SetListening(oCreature, TRUE);
}
int ai_IsNonliving(int nRacialType)
{
    switch(nRacialType)
    {
        case RACIAL_TYPE_CONSTRUCT:
        case RACIAL_TYPE_ELEMENTAL:
        case RACIAL_TYPE_UNDEAD: return TRUE;
   }
   return FALSE;
}
int ai_GetIsInCombat(object oCreature)
{
    if(AI_DEBUG) ai_Debug("0i_states_cond", "110", GetName(oCreature) + " is in Combat: Enemy Numbers = " + IntToString(GetLocalInt(oCreature, AI_ENEMY_NUMBERS)));

    return GetLocalInt(oCreature, AI_ENEMY_NUMBERS);
}
void ai_SetCombatRound(object oCreature)
{
    SetLocalInt(oCreature, "AI_COMBAT_ROUND_START", SQLite_GetTimeStamp());
    if(AI_DEBUG) ai_Debug("0i_states_cond", "116", " ===============> " + GetName(oCreature) + " ROUND START:" + IntToString(SQLite_GetTimeStamp()) + " <===============");
}
void ai_EndCombatRound(object oCreature)
{
    if(AI_DEBUG) ai_Debug("0i_states_cond", "120", " ===============> " + GetName(oCreature) + " ROUND END:" + IntToString(SQLite_GetTimeStamp()) + " <===============");
    DeleteLocalInt(oCreature, "AI_COMBAT_ROUND_START");
}
int ai_IsInCombatRound(object oCreature, int nCombatRound = AI_COMBAT_ROUND_IN_SECONDS)
{
    int nCombatRoundStart = GetLocalInt(oCreature, "AI_COMBAT_ROUND_START");
    if(AI_DEBUG) ai_Debug("0i_states_cond", "148", " nCombatRoundStart: " + IntToString(nCombatRoundStart));
    if(!nCombatRoundStart) return FALSE;
    // New combat round calculator. If 6 seconds has passed then we are on a new round!
    int nSQLTime = SQLite_GetTimeStamp();
    int nCombatRoundTime = nSQLTime - nCombatRoundStart;
    if(AI_DEBUG) ai_Debug("0i_states_cond", "153", " SQLite_GetTimeStamp: " + IntToString(nSQLTime) +
             "(" + IntToString(nSQLTime - nCombatRoundStart) + ")");
    if(nCombatRoundTime < nCombatRound) return TRUE;
    ai_EndCombatRound(oCreature);
    return FALSE;
}
// Testing to see if we can fix some delaying in combat.
int ai_GetIsBusy(object oCreature)
{
    int nAction = GetCurrentAction(oCreature);
    if(AI_DEBUG) ai_Debug("0i_states_cond", "140", GetName(oCreature) + " Get is Busy, action: " +
             IntToString(nAction));
    switch(nAction)
    {
        case ACTION_CASTSPELL :
        case ACTION_ITEMCASTSPELL :
        case ACTION_OPENLOCK :
        case ACTION_REST :
        case ACTION_DISABLETRAP :
        case ACTION_ATTACKOBJECT :
        case ACTION_COUNTERSPELL :
        case ACTION_SETTRAP : return TRUE;
        case ACTION_WAIT :
        case ACTION_INVALID :
        {
            int nCombatWait = GetLocalInt(oCreature, AI_COMBAT_WAIT_IN_SECONDS);
            if(AI_DEBUG) ai_Debug("0i_states_cond", "153", "nCombatWait: " + IntToString(nCombatWait) +
                     " AI_AM_I_SEARCHING: " + IntToString(GetLocalInt(oCreature, AI_AM_I_SEARCHING)));
            if(nCombatWait)
            {
                if(ai_IsInCombatRound(oCreature, nCombatWait)) return TRUE;
                DeleteLocalInt(oCreature, AI_COMBAT_WAIT_IN_SECONDS);
            }
            else if(GetLocalInt(oCreature, AI_AM_I_SEARCHING)) DeleteLocalInt(oCreature, AI_AM_I_SEARCHING);
            return FALSE;
        }
        case ACTION_MOVETOPOINT :
        {
            return ai_GetIsInCombat(oCreature);
        }
    }
    return FALSE;
}
int ai_Disabled(object oCreature)
{
    if(GetIsDead(oCreature)) return 1;
    // Check for effects.
    effect eEffect = GetFirstEffect(oCreature);
    while(GetIsEffectValid(eEffect))
    {
        switch(GetEffectType(eEffect))
        {
            case EFFECT_TYPE_DOMINATED :
            {
                if(!GetCommandable(oCreature)) SetCommandable(TRUE, oCreature);
                return FALSE;
            }
            case EFFECT_TYPE_STUNNED :
            case EFFECT_TYPE_DAZED :
            case EFFECT_TYPE_SLEEP :
            case EFFECT_TYPE_CONFUSED :
            case EFFECT_TYPE_FRIGHTENED :
            case EFFECT_TYPE_PARALYZE :
            case EFFECT_TYPE_TURNED :
            case EFFECT_TYPE_CHARMED :
            case EFFECT_TYPE_PETRIFY :
            case EFFECT_TYPE_TIMESTOP :
            {
                if(AI_DEBUG) ai_Debug("0i_stats_cond", "195", GetName(oCreature) + " is disabled(" +
                         IntToString(GetEffectType(eEffect)) + ")");
                return GetEffectType(eEffect);
            }
        }
        eEffect = GetNextEffect(oCreature);
    }
    // Not Commandable is basically disabled as far as the AI is concerned.
    if(!GetCommandable(oCreature))
    {
        if(AI_DEBUG) ai_Debug("0i_stats_cond", "213", GetName(oCreature) + " is disabled(Not Commandable)!");
        return EFFECT_TYPE_PARALYZE;
    }
    if(AI_DEBUG) ai_Debug("0i_states_cond", "202", GetName(oCreature) + " is not disabled.");
    return FALSE;
}
void ai_SetAIMode(object oAssociate, int nBit, int bOn = TRUE)
{
    int nAIModes = GetLocalInt(oAssociate, sAIModeVarname);
    if(bOn) nAIModes = nAIModes | nBit;
    else nAIModes = nAIModes & ~nBit;
    SetLocalInt(oAssociate, sAIModeVarname, nAIModes);
    // Set widget to show the mode they are in.

}
int ai_GetAIMode(object oAssociate, int nBit)
{
    if(GetLocalInt(oAssociate, sAIModeVarname) & nBit) return TRUE;
    return FALSE;
}
void ai_SetMagicMode(object oAssociate, int nBit, int bOn = TRUE)
{
    int nMagicModes = GetLocalInt(oAssociate, sMagicModeVarname);
    if(bOn) nMagicModes = nMagicModes | nBit;
    else nMagicModes = nMagicModes & ~nBit;
    SetLocalInt(oAssociate, sMagicModeVarname, nMagicModes);
}
int ai_GetMagicMode(object oAssociate, int nBit)
{
    if(GetLocalInt(oAssociate, sMagicModeVarname) & nBit) return TRUE;
    return FALSE;
}
void ai_SetLootFilter(object oAssociate, int nLootBit, int bOn = TRUE)
{
    int nLootFilter = GetLocalInt(oAssociate, sLootFilterVarname);
    if(bOn) nLootFilter = nLootFilter | nLootBit;
    else nLootFilter = nLootFilter & ~nLootBit;
    SetLocalInt(oAssociate, sLootFilterVarname, nLootFilter);
}
int ai_GetLootFilter(object oAssociate, int nBit)
{
    if(GetLocalInt(oAssociate, sLootFilterVarname) & nBit) return TRUE;
    return FALSE;
}
void ai_SetItemProperty(object oCreature, string sVarname, int nBit, int bOn = TRUE)
{
    int nItemProperties = GetLocalInt(oCreature, sVarname);
    if(bOn) nItemProperties = nItemProperties | nBit;
    else nItemProperties = nItemProperties & ~nBit;
    SetLocalInt(oCreature, sVarname, nItemProperties);
}
int ai_GetItemProperty(object oCreature, string sVarname, int nBit)
{
    if(GetLocalInt(oCreature, sVarname) & nBit) return TRUE;
    return FALSE;
}
int ai_GetHealersHpLimit(object oCreature, int bInCombat = TRUE)
{
    if(bInCombat) return GetLocalInt(oCreature, AI_HEAL_IN_COMBAT_LIMIT);
    else return GetLocalInt(oCreature, AI_HEAL_OUT_OF_COMBAT_LIMIT);
}
int ai_GetHasNegativeCondition(int nCondition, int nCurrentConditions)
{
    return (nCurrentConditions & nCondition);
}
int ai_GetNegativeConditions(object oCreature)
{
    int nCondition, nEffectType;
    effect eEffect = GetFirstEffect(oCreature);
    while(GetIsEffectValid (eEffect))
    {
        // Rage and maybe other abilities might come from the oCreature!
        if(GetEffectCreator(eEffect) != oCreature)
        {
            nEffectType = GetEffectType(eEffect);
            switch(nEffectType)
            {
                case EFFECT_TYPE_DISEASE:          nCondition = nCondition | AI_CONDITION_DISEASE; break;
                case EFFECT_TYPE_POISON:           nCondition = nCondition | AI_CONDITION_POISON; break;
                case EFFECT_TYPE_CURSE:            nCondition = nCondition | AI_CONDITION_CURSE; break;
                case EFFECT_TYPE_BLINDNESS:
                case EFFECT_TYPE_DEAF:             nCondition = nCondition | AI_CONDITION_BLINDDEAF; break;
                case EFFECT_TYPE_ABILITY_DECREASE: nCondition = nCondition | AI_CONDITION_ABILITY_DRAIN; break;
                case EFFECT_TYPE_NEGATIVELEVEL:    nCondition = nCondition | AI_CONDITION_LEVEL_DRAIN; break;
                case EFFECT_TYPE_AC_DECREASE:      nCondition = nCondition | AI_CONDITION_AC_DECREASE; break;
                case EFFECT_TYPE_ATTACK_DECREASE:  nCondition = nCondition | AI_CONDITION_ATK_DECREASE; break;
                case EFFECT_TYPE_CHARMED:          nCondition = nCondition | AI_CONDITION_CHARMED; break;
                case EFFECT_TYPE_CONFUSED:         nCondition = nCondition | AI_CONDITION_CONFUSED; break;
                case EFFECT_TYPE_DAZED:            nCondition = nCondition | AI_CONDITION_DAZED; break;
                case EFFECT_TYPE_DAMAGE_DECREASE:  nCondition = nCondition | AI_CONDITION_DMG_DECREASE; break;
                case EFFECT_TYPE_DAMAGE_IMMUNITY_DECREASE: nCondition = nCondition | AI_CONDITION_DMG_I_DECREASE; break;
                case EFFECT_TYPE_DOMINATED:        nCondition = nCondition | AI_CONDITION_DOMINATED; break;
                case EFFECT_TYPE_FRIGHTENED:       nCondition = nCondition | AI_CONDITION_FRIGHTENED; break;
                case EFFECT_TYPE_PARALYZE:         nCondition = nCondition | AI_CONDITION_PARALYZE; break;
                case EFFECT_TYPE_SAVING_THROW_DECREASE: nCondition = nCondition | AI_CONDITION_SAVE_DECREASE; break;
                case EFFECT_TYPE_SKILL_DECREASE:   nCondition = nCondition | AI_CONDITION_SKILL_DECREASE; break;
                case EFFECT_TYPE_SLOW:             nCondition = nCondition | AI_CONDITION_SLOW; break;
                case EFFECT_TYPE_SPELL_RESISTANCE_DECREASE: nCondition = nCondition | AI_CONDITION_SR_DECREASE; break;
                case EFFECT_TYPE_STUNNED:          nCondition = nCondition | AI_CONDITION_STUNNED; break;
            }
        }
        eEffect = GetNextEffect(oCreature);
    }
    return nCondition;
}
int ai_GetIsInLineOfSight(object oCreature, object oObject)
{
    // Creatures can block the line of sight so when close we shouldn't check.
    if(GetDistanceBetween(oObject, oCreature) <= AI_RANGE_MELEE) return TRUE;
    return LineOfSightObject(oCreature, oObject);
}
void ai_SetBehaviorState(int nCondition, int bValid = TRUE)
{
    int nPlot = GetLocalInt(OBJECT_SELF, "NW_BEHAVIOR_MASTER");
    if(bValid)
    {
        nPlot = nPlot | nCondition;
        SetLocalInt(OBJECT_SELF, "NW_BEHAVIOR_MASTER", nPlot);
    }
    else
    {
        nPlot = nPlot & ~nCondition;
        SetLocalInt(OBJECT_SELF, "NW_BEHAVIOR_MASTER", nPlot);
    }
}
int ai_GetBehaviorState(int nCondition)
{
    int nPlot = GetLocalInt(OBJECT_SELF, "NW_BEHAVIOR_MASTER");
    if(nPlot & nCondition) return TRUE;
    return FALSE;
}
void ai_HighlightWidgetMode(object oPC, object oAssociate, int nToken)
{
    if(oPC == oAssociate) return;
    int bBool;
    bBool = ai_GetAIMode(oAssociate,AI_MODE_DEFEND_MASTER);
    NuiSetBind(oPC, nToken, "btn_cmd_guard_encouraged", JsonBool(bBool));
    bBool = ai_GetAIMode(oAssociate,AI_MODE_STAND_GROUND);
    NuiSetBind(oPC, nToken, "btn_cmd_hold_encouraged", JsonBool(bBool));
    bBool = ai_GetAIMode(oAssociate,AI_MODE_FOLLOW);
    NuiSetBind(oPC, nToken, "btn_cmd_follow_encouraged", JsonBool(bBool));
    if(!ai_GetAIMode(oAssociate, AI_MODE_DEFEND_MASTER) &&
       !ai_GetAIMode(oAssociate, AI_MODE_STAND_GROUND) &&
       !ai_GetAIMode(oAssociate, AI_MODE_FOLLOW)) bBool = TRUE;
    else bBool = FALSE;
    NuiSetBind(oPC, nToken, "btn_cmd_attack_encouraged", JsonBool(bBool));
}
void ai_CheckXPPartyScale(object oCreature)
{
    object oModule = GetModule();
    if(!GetLocalInt(oModule, AI_RULE_PARTY_SCALE)) return;
    object oMaster;
    if(!ai_GetIsCharacter(oCreature))
    {
        oMaster = GetMaster(oCreature);
        while(oMaster != OBJECT_INVALID)
        {
            if(ai_GetIsCharacter(oMaster)) break;
            oMaster = GetMaster(oMaster);
        }
        if(oMaster == OBJECT_INVALID) return;
    }
    else oMaster = oCreature;
    float fDefaultXPScale = IntToFloat(GetLocalInt(oModule, AI_BASE_PARTY_SCALE_XP));
    float fPartySize = 4.0;
    int nAssociateType, nHenchman, nHenchAssociate;
    object oHenchman;
    for(nAssociateType = 1; nAssociateType <= 5; nAssociateType++)
    {
        if(nAssociateType == ASSOCIATE_TYPE_HENCHMAN)
        {
            for(nHenchman = 1; nHenchman <= AI_MAX_HENCHMAN; nHenchman++)
            {
                oHenchman = GetAssociate(nAssociateType, oMaster, nHenchman);
                if(oHenchman != OBJECT_INVALID)
                {
                    fPartySize += 1.0;
                    for(nHenchAssociate = 2; nHenchAssociate <= 5; nHenchAssociate++)
                    {
                        if(GetAssociate(nHenchAssociate, oHenchman, 1) != OBJECT_INVALID) fPartySize += 1.0;
                    }
                }
            }
        }
        else if(GetAssociate(nAssociateType, oMaster, 1) != OBJECT_INVALID) fPartySize += 1.0;
    }
    int nXPScale = FloatToInt(fPartySize / 4.0 * fDefaultXPScale);
    //SendMessageToPC(oMaster, GetName(oMaster) + " nXPScale = (3 + fPartySize / 4.0 * fDefaultXPScale)" +
    //                IntToString(nXPScale) + " = (" + FloatToString(fPartySize, 0, 1) + " / 4.0 * " +
    //                FloatToString(fDefaultXPScale, 0, 1) + ")");
    SetModuleXPScale(nXPScale);
}

