// NW_C2_DEFAULT7 MODIFIED TO USE SCREWTAPE'S DMG XP FUNCTIONS
// THE CODE BELOW REPLACES THE DEFAULT ONDEATH SCRIPT

// DMG experience rewards modification
/*
ScrewTape's script mimicking the XP Rewards from pp 36-39, DMG v3.5
 - base awards use Bioware's xptable.2da but are applied using DMG rules
*/
//:://////////////////////////////////////////////////
//:: Modifier: ScrewTape
//:: Original Modification: 02/19/04
//:: Latest Revision: 05/19/04
//:://////////////////////////////////////////////////

//:://////////////////////////////////////////////////
//:: NW_C2_DEFAULT7
/*
Default OnDeath event handler for NPCs.

  Adjusts killer's alignment if appropriate and
  alerts allies to our death.
*/
//:://////////////////////////////////////////////////
//:: Copyright (c) 2002 Floodgate Entertainment
//:: Created By: Naomi Novik
//:: Created On: 12/22/2002
//:://////////////////////////////////////////////////

// ============================================================================
// Start of modified behavior
void DMGRewardXP(object oKiller);
// End of modified behavior
// ============================================================================
// NW_C2_DEFAULT7 default behavior

#include "x2_inc_compon"
#include "x0_i0_spawncond"

void main()
{
   int nClass = GetLevelByClass(CLASS_TYPE_COMMONER);
   int nAlign = GetAlignmentGoodEvil(OBJECT_SELF);
   object oKiller = GetLastKiller();
// ============================================================================
// Start of modified behavior
   // Per discussions with Sotae, we don't want to run this script the second
   //  time we get here (since we killed ourself a second time to bypass
   //  Bioware's XP system).
   if (oKiller == OBJECT_SELF)
   {
      return;
   }
// End of modified behavior
// ============================================================================

// ============================================================================
// Start of modified behavior
      // penalize trap creators for killing commoners
      //  Thanks to *** arQon *** for this idea
      // Update - modified so any associate of the trap creator will get the
      //  alignment shift
      if (GetIsObjectValid(GetTrapCreator(oKiller)))
         AdjustAlignment(GetTrapCreator(oKiller), ALIGNMENT_EVIL, 5);
// End of modified behavior
// ============================================================================
   

// ============================================================================
// Start of modified behavior
   DMGRewardXP(oKiller);
// End of modified behavior
// ============================================================================
}
// end of NW_C2_DEFAULT7 default behavior

// ============================================================================
// Start of modified behavior

// This version allows you to look up the actual award from the 2da
//  file, xptable.2da. All other things equal, this would give you the same
//  reward as the Original Campaign. This does not take into account any of the
//  modifiers Bioware uses, like awarding extra xp for Bards and not counting
//  prestige classes towards total levels. (Make a good argument for any of
//  this and I'll consider including it.)

// Below are the tweaking modifiers, used to adjust the amount of XP awarded.

// It is not neccessary to set the module's XP slider to zero. I incorporated
//  an idea by TethyrDarknight to bypass both the slider and the double
//  message for xp. Note: you won't be able to compare this system with the
//  default Bioware awards with this implemented. If you want to do that,
//  leave the module slider at 10 (default) and comment out the next line
//  of code and uncomment the one following it.
const int B_BYPASS_BIOWARE_XP = TRUE; // recommended setting
// const int B_BYPASS_BIOWARE_XP = FALSE; // for comparison only

// N_MODULE_SLIDER - use this just as you would use a module's xp slider
//  set to 10 for normal xp - when using 2da's this will be exactly the
//  same, barring other considerations like henchmen, multiplayer, multiclass,
//  etc. If your using the DMG tables, 10 equates to 10%, which will be
//  slightly higher than the Bioware awards. 100 will match the DMG tables.
const int N_MODULE_SLIDER = 8;

// B_USE_MULTICLASS_PENALTY - this is a boolean (set to TRUE (1) or FALSE (O))
//  to decide whether or not to enforce Bioware's multi class penalty.
//  Idea taken from Bioware scripting forum post by Ima Dufus
//  FALSE would be like the DMG
//const int B_USE_MULTICLASS_PENALTY = FALSE;   // DMG accurate setting
 const int B_USE_MULTICLASS_PENALTY = TRUE; // alternate setting

// Per Trickster's request, I included an option to count the players' other
//  associates, summoned/familiar/companion/dominated. In doing so, I realized
//  I wasn't adhering to the DMG's notes regarding "creatures that enemies
//  summon or otherwise add to their forces with magic powers. An ememy's
//  ability to summon or add these creatures is a part of the enemy's CR
//  already." pp 37, just below the steps to determine the XP award. So I
//  included that in the option. These two options seemed to be tied together,
//  that is, if I count the party's associate types when determining total
//  party members to divide experience by, I should also award XP for enemies'
//  associates and on the other hand, if I don't count the party's associates
//  (except henchmen), I shouldn't award XP for enemies' associates.
const int B_COUNT_ASSOCIATES = FALSE; // DMG Accurate
// const int B_COUNT_ASSOCIATES = TRUE; // alternate setting

// And why not allow the same option for henchmen
const int B_COUNT_HENCHMEN = TRUE; // recommended setting
// const int B_COUNT_HENCHMEN = FALSE; // alternate setting

// B_AWARD_DEAD_PLAYERS
//  Allow dead/dying players to receive awards (per DMG, pp 41 'DEATH AND
//  EXPERIENCE POINTS') but leave the option to disable.
// const int B_AWARD_DEAD_PLAYERS = TRUE; // DMG accurate setting
const int B_AWARD_DEAD_PLAYERS = FALSE; // alternate setting

///////////////////////////////////////////////////////////////////////////////
// GetXPFrom2DATable, inputs: level and CR, outputs: experience points
int GetXPFrom2DATable(int nLevel, float fCR);

///////////////////////////////////////////////////////////////////////////////
// IsPlayerEligible, input: player object, output: TRUE or FALSE
int IsPlayerEligible(object oPlayer)
{
   if (B_AWARD_DEAD_PLAYERS)
      return GetArea(GetObjectByTag("NW_DEATH_TEMPLE")) == GetArea(oPlayer) ||
         GetDistanceBetween(OBJECT_SELF, oPlayer) < 50.0f;

   return GetArea(OBJECT_SELF) == GetArea(oPlayer) &&
      GetIsInCombat(oPlayer) && GetCurrentHitPoints(oPlayer) > 0;
}

///////////////////////////////////////////////////////////////////////////////
// GetHitDiceByXP
//  Solve for hd from xp = hd * (hd - 1) * 500
//   hd = 1/50 * (sqrt(5) * sqrt(xp + 125) + 25)
int GetHitDiceByXP(float fXP)
{
   return FloatToInt(0.02 * (sqrt(5.0f) * sqrt(fXP + 125.0f) + 25.0f));
}

///////////////////////////////////////////////////////////////////////////////
// DMGRewardXP, inputs: the killing object, outputs: none (returned)
void DMGRewardXP(object oKiller)
{
   // We'll start by declaring (and sometimes getting) our variables
   //  we received the killer as an argument (don't need to declare)
   int    nPartyMembers = 0;
   int    nIndex = 0;
   int    nLevel = 0;
   int    nXP = 0;
   int    nHoldXP = 0;
   int    nAssociateType = 0;
   float  fTotalXP = 0.0f;
   float  fScale = 1.0f;
   float  fCR = GetChallengeRating(OBJECT_SELF);
   object oMaster = GetMaster(oKiller);
   object oMember;

   // Update 5/19/04 - make sure we check the actual master in case we have a
   //  henchman or other associate who also has an associate (for example,
   //  henchman is a wizard and has a familiar or has summoned a creature).
   // Update 07/12/04 thanks to ***RodneyOrpheus*** fixed nasty bug here
   while (GetIsObjectValid(oMaster))
   {
      oKiller = oMaster;
      oMaster = GetMaster(oKiller);
   }

   // This gets rid of the double experience awards (will also override
   //  module's XP slider, so it doesn't need to be set to zero)
   //  Thanks to *** TethyrDarknight *** for this idea
   if (B_BYPASS_BIOWARE_XP)
   {
      if (GetIsPC(oKiller) || GetIsPC(GetTrapCreator(oKiller)))
      {
         ApplyEffectToObject(DURATION_TYPE_INSTANT,
            EffectResurrection(), OBJECT_SELF);
         ApplyEffectToObject(DURATION_TYPE_INSTANT,
            EffectDamage((GetCurrentHitPoints() * 2)), OBJECT_SELF);
      }
   }

   // Sanity check - should only happen if creature is invalid
   //  No point in calculating any more if CR is less than or = to 0
   //  or if the script's module slider is set to 0
   if (fCR <= 0.0 || N_MODULE_SLIDER == 0)
      return;

   // Before we start calculating XP, let's check to see if we are a controlled
   //  type* of someone else. * summoned/familiar/companion/dominated
   // Update 8/5/04 - thanks ***Belial Prime*** - we were hitting this check on
   //  DM controlled enemies, where we should still be awarding xp.
   //  Used GetAssociateType instead of GetMaster.
   if (B_COUNT_ASSOCIATES == FALSE &&
       GetAssociateType(OBJECT_SELF) != ASSOCIATE_TYPE_NONE)
      return;

   // This will also ensure we award XP for traps.
   //  Thanks to *** Sotae *** for this idea
   if (GetIsObjectValid(GetTrapCreator(oKiller)))
      oKiller = GetTrapCreator(oKiller);

   // First let's determine the number of members in the party. The values of
   //  B_COUNT_HENCHMEN and B_COUNT_ASSOCIATES will determine whether or not
   //  we count henchman and other associates when determining the total number
   //  of party members to divide the Total XP award by. Although the DMG
   //  doesn't specify for players, the DMG does specify for that for enemies,
   //  we don't count xp separately for their creatures added to their forces.
   //  Since henchman can play such a big role, I recommend counting them.
   //  Note: I don't award XP to associates, I just use them in determining
   //  the factor to divide the total XP if specified.
   oMember = GetFirstFactionMember(oKiller); // this only returns PC's
   while (GetIsObjectValid(oMember))
   {
      // sanity check - first let's see if the PC member is in the same area as
      //  the creature just killed  and if the member was in combat. This way,
      //  if the member's in the area, but not in combat - we won't include
      //  him. It should be noted, there's a delay after an enemy is killed and
      //  before a PC is no longer in combat. (Try resting immediately after
      //  you've killed an enemy, and also note the music). This delay is
      //  sufficient for us to count XP. Handy! Note: this delay does NOT occur
      //  if the player is dead, so dead members will NOT be awarded XP.
      if (IsPlayerEligible(oMember))
      {
         // add the PC him/herself
         nPartyMembers++;

         // We'll treat henchmen separately from the other associates, as,
         //  since version 1.59, you can have more than one henchman.
         if (B_COUNT_HENCHMEN)
         {
            // Loop through the available henchmen and see if he's got 'em
            //  Update 08/05/04 - fixed off by one error in the comparison
            for (nIndex = 1; nIndex <= GetMaxHenchmen(); nIndex++)
            {
               if (GetIsObjectValid(GetHenchman(oMember, nIndex)))
                  nPartyMembers++; // increment by number of henchmen
            }
         }

         // According to the lexicon, henchman is the only associate type
         //  that the PC's can have more than one of, so we'll make a simple
         //  check once for each type. I looked in the nwscript.nss file, and
         //  the associate types range from 0 - 5 (where 0 is none, 1 is
         //  henchmen and 2 - 5 are the one's we are interested in)
         if (B_COUNT_ASSOCIATES)
         {
            // loop through each associate type, not including henchmen
            for (nAssociateType  = ASSOCIATE_TYPE_ANIMALCOMPANION;
                 nAssociateType <= ASSOCIATE_TYPE_DOMINATED; nAssociateType++)
            {
               if (GetIsObjectValid(GetAssociate(nAssociateType, oMember)))
                  nPartyMembers++; // increment by number of associates
            }
         }

      } // end if (IsPlayerEligible(oMember))

      // get the next guy in the PC's party
      oMember = GetNextFactionMember(oMember);
   } // end while (GetIsObjectValid(oMember))

   // Sanity check - this shouldn't happen, but in case it does...
   //  this will also prevent a divide by zero below
   if (nPartyMembers < 1)
      return;

   // According to the DMG, there are 6 steps. The 6th step is a loop, so
   //  we'll start with that. (why didn't we start yet - because we needed to
   //  know the total party members first)
   oMember = GetFirstFactionMember(oKiller);
   while (GetIsObjectValid(oMember))
   {
      // same check as before
      if (IsPlayerEligible(oMember))
      {
         // Step 1. determine character level
         // Update 08/05/04 - thanks ***Belial Prime*** We want to get level
         //  by xp, to discourage pcs from waiting to level
         nLevel = GetHitDiceByXP(IntToFloat(GetXP(oMember)));

         // Step 2. get the monster's fCR
         // fCR = GetChallengeRating(OBJECT_SELF); // we've already done this

         // Step 3. consult the table and adjust using N_MODULE_SLIDER
         // Update 08/05/04 - go back to using rounding instead of truncation
         fScale = IntToFloat(N_MODULE_SLIDER) / 10.0f;
         fTotalXP = IntToFloat(GetXPFrom2DATable(nLevel, fCR)) * fScale;

         // Step 4. divide the XP by the number of party members
         //  we did the sanity check above, so we can't get DIV/0
         nXP = FloatToInt((fTotalXP / IntToFloat(nPartyMembers)) + 0.5f);

         // Step 5 is taken care of by calling this script when each
         //  monster's OnDeath script gets called (this one)

         // Award the XP to the PC - always award at least 1 xp so we know the
         //  awards are working
         if (nXP <= 0)
            nXP = 1;

         // use the standard bioware penalty for multi-class characters
         if (B_USE_MULTICLASS_PENALTY)
            GiveXPToCreature(oMember, nXP);

         // or use get and set to bypass the multi-class penalty
         //  Thanks to *** Ima Dufus *** for this idea
         else
         {
            // we'll get the current xp
            nHoldXP = GetXP(oMember);

            // add them to our newly calculated xp
            nXP += nHoldXP;

            // and re-assign the total,
            //  thus bypassing the multi-class penalty
            SetXP(oMember, nXP);
         }

      } // end if (IsPlayerEligible(oMember))

      // Step 6. get the next guy
      oMember = GetNextFactionMember(oMember);
   } // end while (GetIsObjectValid(oMember))
} // end DMGRewardXP

///////////////////////////////////////////////////////////////////////////////
// GetXPFrom2DATable, inputs: level and CR, outputs: experience points
int GetXPFrom2DATable(int nLevel, float fCR)
{
   // The 2da table goes from CR C0 to C40 and from level 0 - level 39
   //  (off by one). Reading from the 2da table in a loop is bad because it is
   //  slow - so what this function does is check a local int on the module
   //  For this to work properly, the 2da read in OnModuleLoad must be
   //  successful. In multiplayer, even with less than 8 enemies, with 4
   //  players, doing the reads here was entirely unreliable. In single player,
   //  I was able to get it to choke with mass kills (wilting, even greater
   //  cleave). Reading the entire table in OnModuleLoad has presented NO
   //  errors in more than 25 hours of testing with various modules in
   //  multiplayer and single player.
   int nCR = FloatToInt(fCR);
   int nXP = 0;

   // Update 08/07/04 thanks to ***RodneyOrpheus***
   //  make sure we hanlde CR > 40
   if (nCR > 40)
   {
      // The pattern from level 27 through 32 looks like this
      //  400   425   450   475   500   525   550   575   600
      //  this simply extends it out to level 40, based on CR - HD
      //  once it's over 8, everyone will receive  600
      nXP = 400 + (25 * (nCR - nLevel));
      if (nXP > 600)
         nXP = 600;
   }
   // otherwise, use the ints stored read in from the 2da table during
   //  OnModuleLoad
   else
   {
      // This is the string name we're going to use to retrieve each entry stored,
      //  for example, 2DA_XP_10v10 is where we'd store level 10 vs CR 10
      string sXpPerLvl = "2DA_XP_" + IntToString(nLevel) + "v" + IntToString(nCR);
      nXP = GetLocalInt(GetModule(), sXpPerLvl);
   }

   // We should only ever get an error if the reads were unsuccessful in
   //  OnModuleLoad. Spam warning message around.
   if (nXP <= 0 || nXP > 600)
   {
      string sError = "WARNING! Error during xp read - check xptable.2da reads"
         + " in OnModuleLoad.";
      SendMessageToPC(GetFirstPC(), sError);
      SendMessageToAllDMs(sError);
      PrintString(sError);
   }

   // otherwise, return the xp
   return nXP;
}

// End of modified behavior
// ============================================================================

// END OF NW_C2_DEFAULT7