// Hemophiliacs Always Bleed to Death
// By Demtrious and OldManWhistler
//
// PLEASE READ "habd_include" FOR MORE INFORMATION.
//
// OnPlayerDying event handler.

#include "habd_include"

// ****************************************************************************

// This function plays a random bleeding VoiceChat on a player.
//   oPC - the player to make play a bleed voice.
void PlayBleedVoice (object oPC);

void PlayBleedVoice (object oPC)
{
    switch (d6())
    {
        case 1: PlayVoiceChat (VOICE_CHAT_PAIN1, oPC); break;
        case 2: PlayVoiceChat (VOICE_CHAT_PAIN2, oPC); break;
        case 3: PlayVoiceChat (VOICE_CHAT_PAIN3, oPC); break;
        case 4: PlayVoiceChat (VOICE_CHAT_HEALME, oPC); break;
        case 5: PlayVoiceChat (VOICE_CHAT_NEARDEATH, oPC); break;
        case 6: PlayVoiceChat (VOICE_CHAT_HELP, oPC); break;
    }
    return;
}

// ****************************************************************************

// Heals players to 1 hp and to removes negative effects. In also calls the
// user defined bleed stabilization function.
//   oPC - the player to heal.
void HealTo1HP(object oPC);

void HealTo1HP(object oPC)
{
    object oMod = GetModule();
    string sID = GetPCPlayerName(oPC)+GetName(oPC);
    // If player is already alive then abort.
    if (GetLocalInt(oMod, HABD_PLAYER_STATE+sID) == HABD_STATE_PLAYER_ALIVE) return;
    int iNPC = GetLocalInt(OBJECT_SELF, HABD_NPC_BLEED);

    // Give the player a chance to run away
    if (HABD_POST_BLEED_INVIS_DUR > 0.0) ApplyEffectToObject(DURATION_TYPE_TEMPORARY, EffectInvisibility(INVISIBILITY_TYPE_NORMAL), oPC, HABD_POST_BLEED_INVIS_DUR);
    // Turn the plot flag off after a specific period of time.
    SetPlotFlag(oPC, FALSE);
    // Raises the player to 1 hp.
    ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectHeal(1 - (GetCurrentHitPoints(oPC))), oPC);
    SetLocalInt(oMod,HABD_PLAYER_STATE+sID, HABD_STATE_PLAYER_ALIVE); //set player state to alive
    // If this is a henchmen, then take them out of the busy state.
    if (iNPC)
    {
        HABDAssociateNotBusy();
    }

    // Keep the player from being attacked, stop nearby attackers
    AssignCommand(oPC, ApplyEffectToObject(DURATION_TYPE_TEMPORARY, EffectInvisibility(INVISIBILITY_TYPE_NORMAL), oPC, 6.0));
    // Make the player hostile again.
    SetStandardFactionReputation(STANDARD_FACTION_HOSTILE, GetLocalInt(oPC, HABD_OLD_FACTION), oPC);
    DeleteLocalInt(oPC, HABD_OLD_FACTION_SET);
    AssignCommand(oPC, ActionPlayAnimation(ANIMATION_LOOPING_PAUSE_DRUNK, 1.0, 5.0));

    // Notify the player that they were healed.
    DelayCommand(0.5, SendMessageToPC(oPC, "You have healed."));

    // Apply user defined penalties.
    AssignCommand(oPC, HABDUserDefinedBleed());

    //Give a little visual effect for flare.
    effect eVisual = EffectVisualEffect(VFX_IMP_RESTORATION);
    ApplyEffectToObject(DURATION_TYPE_INSTANT, eVisual, oPC);

    // If regeneration items were removed then reequip them.
    if (HABD_NERF_REGENERATION_ITEMS)
    {
        AssignCommand(oPC, HABDRegenerationItemsReEquip(oPC));
    }

    // Fixes the inital respawn issue with monsters not reattacking.
    //object oMonster = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, oPC, 1, CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN);
    //DelayCommand(9.0, AssignCommand(oMonster, ActionAttack(oPC)));
}

// ****************************************************************************

// Returns TRUE if the player has stabilized by gaining any HP since the last
// time they bled.
int CheckForStabilization(object oPC);

int CheckForStabilization(object oPC)
{
    object oMod = GetModule();
    string sID = GetPCPlayerName(oPC)+GetName(oPC);
    //Section deals with possiblity for healing by other players
    if (GetCurrentHitPoints(oPC) > GetLocalInt(oMod, HABD_LAST_HP+sID))  //if hitpoint have increased
    {
        DelayCommand(1.0, HealTo1HP(oPC));
        return TRUE;
    }
    return FALSE;
}

// ****************************************************************************

// Report the bleed count for OBJECT_SELF.
void ReportPlayerBleed();

void ReportPlayerBleed()
{
    object oPC = OBJECT_SELF;
    object oMod = GetModule();
    string sID = GetPCPlayerName(oPC)+GetName(oPC);
    int iHPs = GetCurrentHitPoints(oPC);
    int iNPC = GetLocalInt(OBJECT_SELF, HABD_NPC_BLEED);
    if (iNPC) iHPs = iHPs - 10;

    if (
        (GetLocalInt(oMod, HABD_PLAYER_STATE+sID) != HABD_STATE_PLAYER_BLEEDING) || // check if player is still bleeding
        (iHPs > 0) || // player has healed
        (iHPs <= -10) || // player is a goner, let the death script kick in
        (CheckForStabilization(oPC)) // check if player has gained any HP
        )
    {
        DeleteLocalInt(oPC, HABD_REPORT_BLEED_RUNNING);
        return;
    }
    // The delay will effect how often players are vocal about bleeding.
    DelayCommand(6.0, AssignCommand(oPC, ReportPlayerBleed()));

    // Prevent calling this function multiple times
    SetLocalInt(oPC, HABD_REPORT_BLEED_RUNNING, 1);

    DelayCommand(0.1, FloatingTextStringOnCreature(GetName(oPC)+" is bleeding to death! At "+IntToString(iHPs)+" hitpoints.", oPC));
    if (HABD_DM_NOTIFICATION_ON_BLEED) SendMessageToAllDMs(GetName(oPC)+" is bleeding to death! At "+IntToString(iHPs)+" hitpoints.");
    PlayBleedVoice(oPC);
    AssignCommand(oPC, ActionPlayAnimation(ANIMATION_LOOPING_DEAD_BACK, 1.0, 6.0));
}

// ****************************************************************************

// This function exists to fix the problem that occurs in bleeding scripts
// when the summoned familiar is being possessed by the player (sorc or wiz).
// That creates a condition where GetIsPC returns true for the familiar.
// What usually happens is that when the possessed familiar dies, the player
// is trapped in its body until the DM manually kills the player.
// While stuck in the dead familiar, the player is unable to run the unpossess
// action and the bleed count on the familiar usually does not work properly.

// This function DOES NOT kill the familiar when the player is bleeding if the player
// is not possessing the familiar. The familiar will be able to continue fighting
// for its unconcious and bleeding master.
//   oTarget - the possibly "possessed" player.
int KillPet(object oTarget, int nEffect = TRUE, int nVisualEffectId = VFX_IMP_UNSUMMON);

int KillPet(object oTarget, int nEffect = TRUE, int nVisualEffectId = VFX_IMP_UNSUMMON)
{
    // Usage: place in your bleeding script with a call that looks something like
    // if (KillPet(oPC)) return; // abort from the bleed script, oPC no longer exists

    effect eDeath = EffectDeath(FALSE, FALSE);
    effect eVis = EffectVisualEffect(nVisualEffectId);
    object oCreature = oTarget;
    if(GetIsObjectValid(oCreature))
    {
        object oMaster = GetMaster(oCreature);
        if(GetIsObjectValid(oMaster))
        {
            //Is the creature a summoned associate
            if(GetAssociate(ASSOCIATE_TYPE_FAMILIAR, oMaster) == oCreature)
            {
                //Apply the VFX and delay the destruction of the summoned monster so
                //that the script and VFX can play.
                if(nEffect)
                    DelayCommand(0.001,ApplyEffectAtLocation(DURATION_TYPE_TEMPORARY,eVis,GetLocation(oCreature),1.0f));
                SetPlotFlag(oCreature, FALSE);
                DelayCommand(0.002,FloatingTextStringOnCreature(GetName(oMaster)+" HAS LOST FAMILIAR '"+GetName(oCreature)+"'", oCreature));
                if (HABD_DM_NOTIFICATION_ON_BLEED) SendMessageToAllDMs(GetName(oMaster)+" HAS LOST FAMILIAR '"+GetName(oCreature)+"'");
                DelayCommand(0.003, ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eDeath, oCreature));
                return TRUE;
            }
        }
    }
    return FALSE;
}

// ****************************************************************************

// Applies -1 HP to the player and checks for stabilization.
//   fBleedTimer - the time duration between bleeding -1 HP.
void BleedToDeath(float fBleedTimer)
{
    object oMod = GetModule();
    object oPC = OBJECT_SELF;
    string sID = GetPCPlayerName(oPC)+GetName(oPC);
    int iNPC = GetLocalInt(OBJECT_SELF, HABD_NPC_BLEED);
    if (HABD_DEBUG) SpeakString("DEBUG: HABD OnBleed, "+GetName(oPC)+", HP: "+IntToString(GetCurrentHitPoints(oPC))+", master: "+GetName(GetMaster(oPC))+", state:"+HABDGetPlayerStateName(oPC), TALKVOLUME_SHOUT);

    int iPlayerState = GetLocalInt(oMod, HABD_PLAYER_STATE+sID);
    if (iPlayerState != HABD_STATE_PLAYER_BLEEDING) return;
    if (CheckForStabilization(oPC)) return;

    // if you get here - you are dying and have not been healed
    // so you need to roll to see if you stablize
    int nSavingRoll = d10();

    // Death can be disabled before a certain level by "faking" the stabilization
    // check. Do not let the players know that death is disabled because it will
    // only encourage them to be idiots.
    if ((HABD_NO_DEATH_UNTIL_LEVEL) && (GetHitDice(oPC) < HABD_NO_DEATH_UNTIL_LEVEL))
    {
        switch (GetCurrentHitPoints(oPC))
        {
            case 10:
            case -1: nSavingRoll = nSavingRoll + 2; break;
            case 9:
            case -2: nSavingRoll = nSavingRoll + 3; break;
            case 8:
            case -3: nSavingRoll = nSavingRoll + 4; break;
            case 7:
            case -4: nSavingRoll = nSavingRoll + 5; break;
            case 6:
            case -5: nSavingRoll = nSavingRoll + 6; break;
            case 5:
            case -6: nSavingRoll = nSavingRoll + 7; break;
            case 4:
            case -7: nSavingRoll = nSavingRoll + 8; break;
            case 3:
            case -8: nSavingRoll = nSavingRoll + 9; break;
            case 2:
            case -9:
            default: nSavingRoll = nSavingRoll + 10; break;
        }
    }

    if (nSavingRoll > 9)                                                    //set to 9 for 3E - lower for easier stabilization
    {
        DelayCommand(1.0, HealTo1HP(oPC));                                   //call heal subroutine
        // Always make it look like they rolled a 10 to stabilize
        SendMessageToPC(oPC,"Saving Roll to stop bleeding (at "+IntToString(GetCurrentHitPoints(oPC))+") = 10");
        FloatingTextStringOnCreature(GetName(oPC)+" has self-stabilized.", oPC);
        DelayCommand(6.0, SendMessageToPC(oPC, "In a life or death effort you have survived, alive but barely."));
        return;
    }
    //if you get here, you have not been healed and did not successfully stabilize
    else
    {
        // Most important, keep the bleeding chain going.
        DelayCommand(fBleedTimer, AssignCommand(oPC, BleedToDeath(fBleedTimer)));
        SendMessageToPC(oPC,"Saving Roll to stop bleeding (at "+IntToString(GetCurrentHitPoints(oPC))+") = " +IntToString(nSavingRoll));
        SetPlotFlag(oPC, FALSE);
        ApplyEffectToObject(DURATION_TYPE_PERMANENT, EffectDamage(1,DAMAGE_TYPE_MAGICAL,DAMAGE_POWER_PLUS_FIVE), oPC);
        SetPlotFlag(oPC, TRUE);
        // Update local variable with hitpoints for healing option.
        SetLocalInt(oMod,HABD_LAST_HP+sID, GetCurrentHitPoints(oPC));

        // if this is true then the player has died.
        if (GetCurrentHitPoints(oPC) <= -10)
        {
            SendMessageToPC(oPC,"You have died.");
            // Ensure that plot is not still set.
            SetPlotFlag(oPC, FALSE);
            // Set up the hostile faction again.
            SetStandardFactionReputation(STANDARD_FACTION_HOSTILE, GetLocalInt(oPC, HABD_OLD_FACTION), oPC);
            DeleteLocalInt(oPC, HABD_OLD_FACTION_SET);
            // Set playerstate to dead not dying
            SetLocalInt(oMod,HABD_PLAYER_STATE+sID, HABD_STATE_PLAYER_DEAD);
            // OnPlayerDead script will be called after this.
            // BleedToDeath will be called one more time, but it will instantly
            // abort because the player is not in the bleeding state.
            return;
        }
    }
}

// ****************************************************************************

// OnPlayerDying event handler.
void main()
{
    object oMod = GetModule();
    object oPC = GetLastPlayerDying();
    int iNPC = GetLocalInt(OBJECT_SELF, HABD_NPC_BLEED);
    if (iNPC == 1) oPC = OBJECT_SELF;
    string sID = GetPCPlayerName(oPC)+GetName(oPC);

    // If an NPC is running this script, then set up its master. The master was
    // automatically wiped out when the henchman died.
    if (iNPC)
    {
        if (!GetIsObjectValid(GetAssociate(ASSOCIATE_TYPE_HENCHMAN, GetLocalObject(OBJECT_SELF, HABD_NPC_MASTER))))
            AddHenchman(GetLocalObject(OBJECT_SELF, HABD_NPC_MASTER), oPC);
    }

    if (HABD_DEBUG) SpeakString("DEBUG: HABD OnDying, "+GetName(oPC)+", HP: "+IntToString(GetCurrentHitPoints(oPC))+", master: "+GetName(GetMaster(oPC))+", state:"+HABDGetPlayerStateName(oPC), TALKVOLUME_SHOUT);

    // Check if bleeding is running on DM or DM possessed, then abort.
    if(GetIsDM(oPC) || GetIsDM(GetMaster(oPC))) return;

    // whistler: if this is a player in a possessed familiar, then just kill it.
    // Familiar penalties will kick in when familiar dies.
    if (KillPet(oPC)) return;

    int iState = GetLocalInt(oMod,HABD_PLAYER_STATE+sID);
    if ((iState == HABD_STATE_PLAYER_DEAD) || (iState == HABD_STATE_RESPAWNED_GHOST)) return;

    // Most important, issue the commands to start the bleeding chain.
    float fBleedTimer = HABDGetBleedTimer(oPC);
    AssignCommand(oPC, DelayCommand(fBleedTimer, BleedToDeath(fBleedTimer)));
    if (GetLocalInt(oPC, HABD_REPORT_BLEED_RUNNING) == 0) DelayCommand(6.0, AssignCommand(oPC, ReportPlayerBleed()));

    int iHPs = GetCurrentHitPoints(oPC);
    SetPlotFlag(oPC, TRUE);
    // Force friendly to hostile faction.
    if (!GetLocalInt(oPC, HABD_OLD_FACTION_SET))
    {
        SetLocalInt(oPC, HABD_OLD_FACTION, GetStandardFactionReputation(STANDARD_FACTION_HOSTILE, oPC));
        SetLocalInt(oPC, HABD_OLD_FACTION_SET, 1);
    }
    SetStandardFactionReputation(STANDARD_FACTION_HOSTILE, 100, oPC);
    // Keep the player from being attacked, stop nearby attackers
    AssignCommand(oPC, ApplyEffectToObject(DURATION_TYPE_TEMPORARY, EffectInvisibility(INVISIBILITY_TYPE_NORMAL), oPC, 6.0));

    // Allow a good chance for healing - will limit HP to -5 on a bleed level hit.
    if (
        (iHPs<-5) &&
        (iState == HABD_STATE_PLAYER_ALIVE)
        )
    {
        int nHeal = -5 - iHPs;   //should heal player to -5
        ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectHeal(nHeal), oPC);
    }

    // Set the state variables.
    iHPs = GetCurrentHitPoints(oPC);
    SetLocalInt(oMod,HABD_PLAYER_STATE+sID, HABD_STATE_PLAYER_BLEEDING);
    SetLocalInt(oMod,HABD_LAST_HP+sID, GetCurrentHitPoints(oPC));

    // Check if we are re-entering this state from persistence.
    if (GetLocalInt(oPC, HABD_PERSISTANT_REAPPLY) != 1)
    {
        // Increment the counters.
        SetLocalInt(oMod, HABD_CURRENT_BLEED_COUNT+sID, GetLocalInt(oMod, HABD_CURRENT_BLEED_COUNT+sID) + 1);
        SetLocalInt(oMod, HABD_BLEED_COUNT+sID, GetLocalInt(oMod, HABD_BLEED_COUNT+sID) + 1);
    } else {
        DeleteLocalInt(oPC, HABD_PERSISTANT_REAPPLY);
    }

    // Nerf regeneration items.
    if (HABD_NERF_REGENERATION_ITEMS)
    {
        AssignCommand(oPC, HABDRegenerationItemsUnequip(oPC));
    }

    // Notify that bleeding has started.
    if (iNPC) iHPs = iHPs - 10;
    string sMsg = GetName(oPC)+" is bleeding to death! At "+IntToString(iHPs)+" hitpoints. Will die in "+FloatToString((10 + iHPs)*fBleedTimer, 3, 0)+" seconds.";
    if (HABD_DM_NOTIFICATION_ON_BLEED) SendMessageToAllDMs(sMsg);
    FloatingTextStringOnCreature(sMsg, oPC);
}
