/////////////////////////////////////////////////////
//                                                 //
//  Invizible420's Ghost w/ visuals and Possession //
//                                                 //
//  Created 03/11/03 By: Invizible420              //
//                                                 //
//  Send Bug Reports to: Digiddy777@yahoo.com      //
//                                                 //
/////////////////////////////////////////////////////
/*
   Description:  This is the on_heartbeat script
      for a ghost.  I tried to make it fairly
      accurate to 3rd edition rules (at least as
      close as NWN will let me).

      This heartbeat script will give the ghost a
      percent chance to use Malevolence (posses)
      the pc as well as Frightening Moan.

      This script will only fire if a PC is within
      fDist (See Below) meters of the ghost and if
      a PC is in the area.
      So to not consume unnecessary CPU cycles.

      To simplify code, the moan and malevolence will
      only affect the PC once if the below ONE_SHOT
      options are TRUE.  This goes for all the ghosts,
      if set to TRUE and a PC either saves or is possessed,
      then no ghosts in the module will be able to affect
      the PC.

       Update 04.29.05:  Streamlined code into 2 scripts
       renamed them to better suit a filename format.
       ** If you are updating from an older version of
       my ghost, delete i420_ghost* files.  Then in
       Custom Creatures > Undead  --- Update all instances
       of the Ghost.

       New features:  Starting at line 66 you will find 5
       new control variables... These are effects that
       are applied when the PC is possessed and will
       last until they are unpossessed.

       Also, fixed a minor bug with the ghost using
       moan if the PC is dead.  Last, I changed the
       demo module so you can respawn if you die... oops!

*/
#include "NW_I0_GENERIC"
#include "nw_i0_plot"
#include "prc_inc_racial"

// Customizable Variables:

int MOAN_ONE_SHOT = FALSE;  // Change to TRUE to make Moan effect one shot (Default: FALSE)
int MALEVOLENCE_ONE_SHOT = FALSE;  // Change to TRUE to make Malevolence one shot (Default: FALSE)

float fDIST = 10.0;  // How far the ghost must be away from a PC (Default: 10.0m)
int POSSESS_CHANCE = 20;  // % chance last attacker will become possessed [Malevolence] (Default: 20%)
int MOAN_CHANCE = 10; // % chance last ghost will use Moan attack (Default: 10%)
float POSSESS_DUR = IntToFloat(d20(1)+10);  // Duration of possession (Default: 1d20+10)
float POSSESSED_PC_ATTACK_DIST = 15.0; // Maximum range another PC must be get attacked by another possessed PC  (Default: 15m)
int DO_DMG = TRUE; // if TRUE ghost will damage PC when possessed, if FALSE ghost will not damage PC
float DAMAGE_TIME = IntToFloat(d6(1)+3);  // This is the amount of time to wait before damaging the PC when possessed
float DAMAGE_TIME_2 = IntToFloat(d6(1)+FloatToInt(DAMAGE_TIME)+3);  // Same as above for 2nd hit
float DAMAGE_TIME_3 = IntToFloat(d6(1)+FloatToInt(DAMAGE_TIME_2)+3);  // Same as above for 3rd hit
int GHOST_DAMAGE_TYPE = DAMAGE_TYPE_NEGATIVE;  // The type of damage the ghost will do to a PC when possessed
effect GHOST_DMG_VIS = EffectVisualEffect(VFX_IMP_NEGATIVE_ENERGY); // The visual effect for the ghost possession damage
float MOAN_DIST = 10.0; // Maximum range of Frightful Moan (Default: 10m about 30 ft)
float MOAN_DUR = IntToFloat(d4(2)+3); // Duration of Moan Effect in turns (Default: 2d4 + 3 turns)

/********/
// New variables for 4.39.05 update
int PC_POSSESS_SIT = TRUE; // TRUE means the below chances will affect the PC, FALSE means they won't
int ABILITY_LOSS_CHANCE = 10; // chance the a (random) ability will be lowered for possess dur
int AC_LOSS_CHANCE = 10; // Chance the PC's AC will be decreased
int BLIND_CHANCE = 10; // Chance the ghost will close the PC's eyes (blind them)
float BLIND_DUR = POSSESS_DUR-IntToFloat(d4(1)-1); // How long to stay blind

object oGHOST_LIMBO_WP = GetObjectByTag("GHOST_LIMBO");   // Get the Ghost Limbo Waypoint

// Text Color Stuff
object oCOLOR_OBJ = GetObjectByTag("i420_COLOR_OBJ");
string COLOR_RED = GetStringLeft(GetName(oCOLOR_OBJ),6);
string COLOR_BLUE = GetStringRight(GetName(oCOLOR_OBJ),6);




//Debugger
int DEBUGGER = FALSE;
void DB(string sDebugStr)
{
    if (DEBUGGER == TRUE) SendMessageToPC(GetFirstPC(),sDebugStr);
}


// Begin Function Declarations:
// Define Function Variables
// Returns TRUE if there is a PC in oArea else returns FALSE
int CheckAreaForPCs(object oArea);
// Returns the Nearest PC to oObject
object GetNearestPCToObject(object oObject);
// Applies Ghost Possession Effects to oTarget (1 of 4 Effects)
void ApplyGhostPossessionEffect(object oTarget,object oGhost);


int CheckAreaForPCs(object oArea)
{
  object oPC;
  int iCheck = FALSE;
  oPC = GetFirstPC();
  while (oPC != OBJECT_INVALID) {
     if (GetArea(oPC) == oArea) {
        iCheck = TRUE;
     }
     oPC = GetNextPC();
  }
  return iCheck;
}


object GetNearestPCToObject(object oObject)
{
   object oNearestPC;
   oNearestPC = GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR,PLAYER_CHAR_IS_PC,oObject);
   if (GetDistanceBetween(OBJECT_SELF,oNearestPC) <= POSSESSED_PC_ATTACK_DIST) {
      return oNearestPC;
   }
   else   {
      oNearestPC = OBJECT_INVALID;
      return oNearestPC;
   }
}

void RecreateGhostAtLocation(object oTarget,object oGhost, location lLoc)
{
    ApplyEffectToObject(DURATION_TYPE_INSTANT,EffectVisualEffect(VFX_IMP_DEATH),oTarget);
    AssignCommand(oGhost,JumpToLocation(lLoc));
    DeleteLocalLocation(oGhost,"GHOST_PC_LAST_LOC");
    ClearAllActions(FALSE);
}


void RecreateGhost(object oTarget,object oGhost)
{
    if (GetIsDead(oTarget) == FALSE && GetLocalInt(oGhost,"GHOST_PC_DEAD") != 1 &&
        oTarget != OBJECT_INVALID)
    {
        ApplyEffectToObject(DURATION_TYPE_INSTANT,EffectVisualEffect(VFX_IMP_DEATH),oTarget);
        AssignCommand(oGhost,JumpToObject(oTarget,FALSE));
    }
    else if (GetIsDead(oTarget) == TRUE || oTarget == OBJECT_INVALID &&
            GetLocalInt(oGhost, "GHOST_PC_DEAD") != 1)
    {
        RecreateGhostAtLocation(oTarget,oGhost,GetLocalLocation(oGhost,"GHOST_PC_LAST_LOC"));
        DeleteLocalLocation(oGhost,"GHOST_PC_LAST_LOC");
     }
    ClearAllActions(FALSE);
}

void DoGhostDmg(object oPC, object oGhost, int iDmgPos)
{
  if(GetIsDead(oPC) != TRUE && GetLocalInt(oGhost,"GHOST_PC_DEAD") != 1)
  {
    int DAMAGE_AMOUNT_1 = (Random(GetHitDice(oPC))+1*2)+GetAbilityModifier(ABILITY_STRENGTH,oPC); // Amount to damage the PC when possessed first hit
    int DAMAGE_AMOUNT_2 = (Random(GetHitDice(oPC))+1*2)+GetAbilityModifier(ABILITY_STRENGTH,oPC); // Amount to damage the PC when possessed second hit
    int DAMAGE_AMOUNT_3 = (Random(GetHitDice(oPC))+1*2)+GetAbilityModifier(ABILITY_STRENGTH,oPC); // Amount to damage the PC when possessed third hit

    int iDmg;
    if (iDmgPos == 1) iDmg = DAMAGE_AMOUNT_1;
    if (iDmgPos == 2) iDmg = DAMAGE_AMOUNT_2;
    if (iDmgPos == 3) iDmg = DAMAGE_AMOUNT_3;
    if (iDmg > 0)
    {
        effect eLink1 = EffectLinkEffects(GHOST_DMG_VIS,EffectDamage(iDmg,GHOST_DAMAGE_TYPE));
        ApplyEffectToObject(DURATION_TYPE_INSTANT,eLink1,oPC);
    }
    ClearAllActions(FALSE);
  }
  if (GetIsDead(oPC) == TRUE && GetLocalInt(oGhost,"GHOST_PC_DEAD") != 1)
  {
    DelayCommand(2.5,RecreateGhostAtLocation(oPC,oGhost,GetLocalLocation(oGhost,"GHOST_PC_LAST_LOC")));
    SetLocalInt(oGhost, "GHOST_PC_DEAD",1);
    DelayCommand(POSSESS_DUR+0.5,DeleteLocalInt(oGhost,"GHOST_PC_DEAD"));
  }
}

void DelayAction(object oTarget, int iSit)
{
    // Check which situation(s) the PC has and apply the effect
    if (iSit == 1)
    {
        int ABILITY_LOSS_AMT = Random(GetHitDice(oTarget))+1; // amount of ability loss
        int iAbility;
        switch(d6(1))
        {
            case 1: iAbility = ABILITY_CHARISMA; break;
            case 2: iAbility = ABILITY_CONSTITUTION; break;
            case 3: iAbility = ABILITY_DEXTERITY; break;
            case 4: iAbility = ABILITY_INTELLIGENCE; break;
            case 5: iAbility = ABILITY_STRENGTH; break;
            case 6: iAbility = ABILITY_WISDOM; break;
        }
        ApplyEffectToObject(DURATION_TYPE_TEMPORARY,EffectVisualEffect(VFX_DUR_MIND_AFFECTING_DISABLED),oTarget,POSSESS_DUR-3.5);
        ApplyEffectToObject(DURATION_TYPE_TEMPORARY,EffectAbilityDecrease(iAbility,ABILITY_LOSS_AMT),oTarget,POSSESS_DUR-3.5);
    }
    if (iSit == 2)
    {
        int AC_LOSS_AMT = Random(GetHitDice(oTarget))+1; // amount of Ac loss
        ApplyEffectToObject(DURATION_TYPE_TEMPORARY,EffectVisualEffect(VFX_DUR_MIND_AFFECTING_NEGATIVE),oTarget,POSSESS_DUR-5.0);
        ApplyEffectToObject(DURATION_TYPE_TEMPORARY,EffectACDecrease(AC_LOSS_AMT),oTarget,POSSESS_DUR-5.0);
    }
    if (iSit == 3)
    {
        ApplyEffectToObject(DURATION_TYPE_TEMPORARY,EffectVisualEffect(VFX_DUR_BLIND),oTarget,BLIND_DUR-6.0);
        ApplyEffectToObject(DURATION_TYPE_TEMPORARY,EffectBlindness(),oTarget,BLIND_DUR-6.0);
    }
}

void GhostEffects2(object oTarget,object oGhost)
{

    DB("POSSESS_DUR = "+FloatToString(POSSESS_DUR)); // DEBUG LINE


   ApplyEffectToObject(DURATION_TYPE_INSTANT,EffectVisualEffect(VFX_DUR_MIND_AFFECTING_DOMINATED),oTarget);
   ApplyEffectToObject(DURATION_TYPE_INSTANT,EffectVisualEffect(VFX_IMP_DEATH),oTarget);
   ApplyEffectToObject(DURATION_TYPE_INSTANT,EffectVisualEffect(VFX_FNF_SUMMON_UNDEAD),oTarget);


   // Sets an int so the PC can't be dominated multiple times simoltaneously, then deletes the int after the ghost effect
   SetLocalInt(oTarget,"PC_IS_DOMINATED",1);
   DelayCommand(POSSESS_DUR,DeleteLocalInt(oTarget,"PC_IS_DOMINATED"));

   // Dominate the oTarget
   ApplyEffectToObject(DURATION_TYPE_TEMPORARY,EffectCutsceneDominated(),oTarget,POSSESS_DUR);

   // Do the NEW ossessed situations
   if (PC_POSSESS_SIT == TRUE)
   {
      // Check to see if PC will be affected by a possessed situation
      if (d100(1) < ABILITY_LOSS_CHANCE) DelayCommand(2.5,DelayAction(oTarget,1));
      if (d100(1) < AC_LOSS_CHANCE) DelayCommand(4.0,DelayAction(oTarget,2));
      if (d100(1) < BLIND_CHANCE) DelayCommand(5.5,DelayAction(oTarget,3));
   }

   if(DO_DMG == TRUE) {
      DelayCommand(DAMAGE_TIME,DoGhostDmg(oTarget, oGhost, 1));
      if (FloatToInt(POSSESS_DUR)-1 > FloatToInt(DAMAGE_TIME_2)) DelayCommand(DAMAGE_TIME_2,DoGhostDmg(oTarget, oGhost, 2));
      if (FloatToInt(POSSESS_DUR)-1 > FloatToInt(DAMAGE_TIME_3)) DelayCommand(DAMAGE_TIME_3,DoGhostDmg(oTarget, oGhost, 3));
   }
   DelayCommand(POSSESS_DUR,RecreateGhost(oTarget,oGhost));
}


void fFace(object oPC, object oGhost)
{
   float fFacing = GetFacing(oPC);
   AssignCommand(oGhost,SetFacing(fFacing));
}

void ApplyGhostPossessionEffect(object oTarget,object oGhost)
{
   // Apply effect to make ghost look like it leaps into the PC and disappears
   ApplyEffectToObject(DURATION_TYPE_INSTANT,EffectVisualEffect(VFX_FNF_SUMMON_UNDEAD),OBJECT_SELF);
   AssignCommand(oGhost,ClearAllActions());
   AssignCommand(oGhost,JumpToObject(oTarget,FALSE));

   DelayCommand(0.4,AssignCommand(oGhost,JumpToObject(oGHOST_LIMBO_WP,FALSE)));

   DelayCommand(0.5,fFace(oTarget,oGhost));

   GhostEffects2(oTarget,oGhost);
}


void main()
{
  switch(GetUserDefinedEventNumber())
  {
  case 1001:
   // This insures that the script is only fired if a PC is
   // within the area.
   if (CheckAreaForPCs(GetArea(OBJECT_SELF)) == TRUE) {

      object oNearestPC = GetNearestPC();

      if ( GetIsPC(oNearestPC) == TRUE &&
           GetDistanceBetween(OBJECT_SELF,GetNearestPC()) <= fDIST &&
           GetLocalInt(oNearestPC,"i420_GHOST_MALEVOLENCE") != 1 &&
           GetLocalInt(oNearestPC,"PC_IS_DOMINATED") != 1 &&
           GetIsDead(oNearestPC) != TRUE)
       {
         // Roll a % die against the POSSESS_CHANCE
         if (d100(1) <= POSSESS_CHANCE) {

            // Get DC and Will Saves
            int DC = GetAbilityModifier(ABILITY_CHARISMA,OBJECT_SELF)+15;
            int Will = GetWillSavingThrow(oNearestPC);
            int id20 = d20(1);
            int SavingThrow = Will+id20;
            // If Save fails
            if (DC > SavingThrow ) {
               //SettheLastlocationofPC
               SetLocalLocation(OBJECT_SELF,"GHOST_PC_LAST_LOC",GetLocation(oNearestPC));

               // Send Message to PC
               SendMessageToPC(oNearestPC,COLOR_RED+"Ghost uses Malevolence!");
               SendMessageToPC(oNearestPC,COLOR_BLUE+GetName(oNearestPC)+" : Will Save vs. Malevolence : *failure* : ("+IntToString(id20)+" + "+IntToString(Will)+" = "+IntToString(SavingThrow)+" vs. DC: "+IntToString(DC)+")");
               // Apply the 1st Ghost Effects
               ApplyGhostPossessionEffect(oNearestPC,OBJECT_SELF);
            }
            // If Save succeeds
            else  {
               // Send Message to PC
               SendMessageToPC(oNearestPC,COLOR_RED+"Ghost uses Malevolence!");
               SendMessageToPC(oNearestPC,COLOR_BLUE+GetName(oNearestPC)+" : Will Save vs. Malevolence : *success* : ("+IntToString(id20)+" + "+IntToString(Will)+" = "+IntToString(SavingThrow)+" vs. DC: "+IntToString(DC)+")");
               // Set the int that ghosts can not affect the PC
               if (MALEVOLENCE_ONE_SHOT == TRUE) {
                  SetLocalInt(oNearestPC,"i420_GHOST_MALEVOLENCE",1);
               }
            }
         }
      }
      // Roll a % die against the MOAN_CHANCE
      if (d100(1) <= MOAN_CHANCE && GetLocalInt(oNearestPC,"PC_IS_MOANED") != 1 &&
           GetLocalInt(oNearestPC,"i420_GHOST_MALEVOLENCE") != 1 )  {
         PlayAnimation(ANIMATION_FIREFORGET_TAUNT);
         DelayCommand(0.75,ApplyEffectToObject(DURATION_TYPE_INSTANT,EffectVisualEffect(VFX_IMP_SILENCE),OBJECT_SELF));
         // Loop through all the Objects in a MOAN_DIST m radius
         object oObjects;
         oObjects = GetFirstObjectInShape(SHAPE_SPHERE,MOAN_DIST,GetLocation(OBJECT_SELF),TRUE,OBJECT_TYPE_CREATURE);
         while (oObjects != OBJECT_INVALID) {
            if ((MyPRCGetRacialType(oObjects) != RACIAL_TYPE_UNDEAD) &&
                (GetLocalInt(oNearestPC,"i420_GHOST_MOAN") != 1) &&
                GetIsDead(oNearestPC) != TRUE) {
               int iDC = GetAbilityModifier(ABILITY_CHARISMA,OBJECT_SELF)+15;
               int iWill = GetWillSavingThrow(oNearestPC);
               int D20 = d20(1);
               int iSavingThrow = iWill+D20;
               // If Save fails
               if (iDC > iSavingThrow ) {
                   // Sets an int to make sure PC can't be moaned multiple times simoltaneously, deletes aftet moan effect
                   SetLocalInt(oNearestPC,"PC_IS_MOANED",1);
                   DelayCommand(MOAN_DUR,DeleteLocalInt(oNearestPC,"PC_IS_MOANED"));

                   SendMessageToPC(oNearestPC,COLOR_RED+"Ghost uses Frightful Moan!");
                   SendMessageToPC(oNearestPC,COLOR_BLUE+GetName(oNearestPC)+" : Will Save vs. Fear : *failure* : ("+IntToString(D20)+" + "+IntToString(iWill)+" = "+IntToString(iSavingThrow)+" vs. DC: "+IntToString(iDC)+")");
                   effect eVis = EffectVisualEffect(VFX_DUR_MIND_AFFECTING_FEAR);
                   effect eLink = EffectLinkEffects(eVis, EffectFrightened());
                   AssignCommand(oNearestPC,ClearAllActions(FALSE));
                   AssignCommand(oNearestPC,ActionMoveAwayFromObject(OBJECT_SELF,TRUE));
                   ApplyEffectToObject(DURATION_TYPE_TEMPORARY,eLink,oObjects,MOAN_DUR);
               }
               else {
                  SendMessageToPC(oNearestPC,COLOR_RED+"Ghost uses Frightful Moan!");
                  // Send Message to PC
                  SendMessageToPC(oNearestPC,COLOR_BLUE+GetName(oNearestPC)+" : Will Save vs. Fear : *success* : ("+IntToString(D20)+" + "+IntToString(iWill)+" = "+IntToString(iSavingThrow)+" vs. DC: "+IntToString(iDC)+")");
                  // Set the int that ghosts can not affect the PC
                  if (MOAN_ONE_SHOT == TRUE) {
                  SetLocalInt(oNearestPC,"i420_GHOST_MOAN",1);
                  }
               }
            }
         oObjects = GetNextObjectInShape(SHAPE_SPHERE,MOAN_DIST,GetLocation(OBJECT_SELF),TRUE,OBJECT_TYPE_CREATURE);
         }
      }
   }
   break;
   case 1007:
       object oPlayer = GetLastPlayerDied();
        SetIsTemporaryEnemy(GetLastDamager(),oPlayer);
        DelayCommand(5.0, PopUpDeathGUIPanel(oPlayer,1,1,0,"To test what ghost does after PC dies, press the 'Wait for help' button. (Must be in Multi-player to see the button!)"));
   break;
 }
}