// v2.2.0 - 9 New functions for handling Corpses, Drop Bag's and Tokens.
// v2.0.0 -
//::////////////////////////////////////////////////////////////////////////////
//:: FileName:  hark_i0_common
//::////////////////////////////////////////////////////////////////////////////
/*
   Common functions used across multiple include files.
*/
//::////////////////////////////////////////////////////////////////////////////
#include "hark_i0_constant"
#include "hark_i0_debug"
#include "hark_i0_hc_lib"
#include "hark_i0_match"
#include "hark_i0_store"
#include "hark_i0_text_1"
#include "hark_i0_vars"
#include "hark_i0_voice"
//::////////////////////////////////////////////////////////////////////////////


// * GENERIC STRUCT


// This structure is used to collect the number of enemies and
// allies in a situation and their respective challenge rating.
struct sSituation
{
    int ENEMY_NUM;
    int ALLY_NUM;
    int ENEMY_CR;
    int ALLY_CR;
};


// * FUNCTION PROTOTYPES


// Set the respawn point to the current location of the caller.
void SetRespawnLocation(object oTarget = OBJECT_SELF);

// Get the current respawn point for the caller.
location GetRespawnLocation(object oTarget = OBJECT_SELF);

// Set the respawn point for the target to a specific location.
void SetRespawnLocationSpecific(object oTarget, location lRespawn);

// Indicate that the PC did something friendly, use FALSE if the PC was nasty.
// This increments/decrements a variable on the PC and can be looked up to see
// how many friendly or nasty acts a player has committed to this NPC.
void SetFriendly(object oPC, int bFriendly = TRUE, object oNPC = OBJECT_SELF);

// Check how friendly/nasty the PC has been to this NPC.
int GetFriendly(object oPC, object oNPC = OBJECT_SELF);

// Returns the tag of the target with the 3-letter prefix (HS_) stripped. Useful
// for the Keyword system since the TAG should be the prefix plus the first name.
string GetTagNoPrefix(object oTarget = OBJECT_SELF);

void PersistentConversationCheck(object oPC, string sConvo = "", int bPrivate = FALSE);

// The caller will try 5 times to start a conversation with oPC.
void PersistentConversationAttempt(object oPC, string sConvo = "", int bPrivate = FALSE);

// This clears the personal reputation of oHench with all the PC's currently logged
// in. This will also clear the friendly setting oHench has with any of the PC's.
void ResetHenchmanReputation(object oHench = OBJECT_SELF);

// This makes oTarget go to the specified lDest.
// If lDest is in same area, walk (or run) there.
// If lDest is in different area, walk/run to the most appropriate door, area
// transition, or waypoint, then jump. If these fail, jump after fDelay seconds.
void TravelToLocation(location lDest, object oTarget = OBJECT_SELF, int bRun = FALSE, float fDelay = 10.0);

// Find nearest exit to target (either door or waypoint).
object GetNearestExit(object oTarget = OBJECT_SELF);

// Finds the nearest exit specified by nObjType.
object GetBestExitByType(object oTarget = OBJECT_SELF, object oTargetArea = OBJECT_INVALID, int nObjType = OBJECT_TYPE_DOOR);

// Find best exit based on desired target area.
object GetBestExit(object oTarget = OBJECT_SELF, object oTargetArea = OBJECT_INVALID);

// Count the number of enemies and allies in a given radius and their respective
// total CRs (slightly rounded, since we use integers instead of floats). This
// returns a "struct sSituation" type value. To use, do the following:
//   struct sSituation sitCurr = CountEnemiesAndAllies(20.0);
//   int nNumEnemies = sitCurr.ENEMY_NUM;
//   int nNumAllies = sitCurr.ALLY_NUM;
//   int nAllyCR = sitCurr.ALLY_CR;
//   int nEnemyCR = sitCurr.ENEMY_CR;
struct sSituation CountEnemiesAndAllies(float fRadius = 20.0, object oSource = OBJECT_SELF);

// Calculate the number of people currently attacking self.
int GetNumberOfMeleeAttackers();

// Used by Henchmen Rangers to give tracking feedback. The facing of oHench
// should be set to the facing of the PC master before calling this function.
void GetDirection(object oCritter, object oHench = OBJECT_SELF);

// Set the player state of oPC to nState. Uses the PWS_PLAYER_STATE_* constants.
void SPS(object oPC, int nState);

// Returns the current state of oPC. Uses the PWS_PLAYER_STATE_* constants.
int GPS(object oPC);

int SecondsSinceBegin();

// SetKeyword is the basic keyword setup function.  It can be used to define
// simple replies, play animations, assign scripts and waypoints, etc.
void SetKeyword(string sPattern, string sScript, object oHench = OBJECT_SELF);

// Destroy's the "copied" corpse of oHench.
void HenchRemoveCorpse(object oHench, object oCorpse);

// Empties and destroy's the Drop Bag of oHench.
void HenchRemoveDropBag(object oHench, object oDropBag);

// Destroy's the Corpse Token of oHench.
void HenchRemoveToken(object oHench, object oToken);

// Moves the Henchman's Corpse to oPC, destroying the old one.
// This should be called first, before moving the Drop Bag and Corpse Token.
void HenchMoveCorpse(object oHench, object oPC);

// Moves the Henchman's Drop Bag to oCorpse, destroying the old one.
// This should be called after HenchMoveCorpse().
void HenchMoveDropBag(object oHench);

// Moves the Henchman's Token to oDropBag, destroying the old one.
// This should be called after HenchMoveDropBag().
void HenchMoveToken(object oHench);

//
void HenchCreateCorpse(object oHench);

//
void HenchCreateDropBag(object oHench);

//
void HenchCreateToken(object oHench);


// * FUNCTION DEFINITIONS


//::////////////////////////////////////////////////////////////////////////////
void SetRespawnLocation(object oTarget = OBJECT_SELF)
{
  object oVars = GetObjectByTag(HS_VARS_OBJ);
  if (GetLocalInt(oVars, HS_VAR_ADV_PWH))
    { StoreLocation(oVars, HS_HEN_SPAWN_LOC + GetTag(oTarget), GetLocation(oTarget)); }
  else
    { SetLocalLocation(oVars, HS_HEN_SPAWN_LOC + GetTag(oTarget), GetLocation(oTarget)); }
}
//::////////////////////////////////////////////////////////////////////////////
location GetRespawnLocation(object oTarget = OBJECT_SELF)
{
  object oVars = GetObjectByTag(HS_VARS_OBJ);
  if (GetLocalInt(oVars, HS_VAR_ADV_PWH))
    { return RetrieveLocation(oVars, HS_HEN_SPAWN_LOC + GetTag(oTarget)); }
  else
    { return GetLocalLocation(oVars, HS_HEN_SPAWN_LOC + GetTag(oTarget)); }
}
//::////////////////////////////////////////////////////////////////////////////
void SetRespawnLocationSpecific(object oTarget, location lRespawn)
{
  object oVars = GetObjectByTag(HS_VARS_OBJ);
  if (GetLocalInt(oVars, HS_VAR_ADV_PWH))
    { StoreLocation(oVars, HS_HEN_SPAWN_LOC + GetTag(oTarget), lRespawn); }
  else
    { SetLocalLocation(oVars, HS_HEN_SPAWN_LOC + GetTag(oTarget), lRespawn); }
}
//::////////////////////////////////////////////////////////////////////////////
void SetFriendly(object oPC, int bFriendly = TRUE, object oNPC = OBJECT_SELF)
{
  int nFriendly = GetFriendly(oPC, oNPC);
  if (bFriendly)
    { nFriendly++; }
  else
    { nFriendly--; }
  SetLocalInt(oPC, GetTag(oNPC) + HS_SFX_FRIEND, nFriendly);
}
//::////////////////////////////////////////////////////////////////////////////
int GetFriendly(object oPC, object oNPC = OBJECT_SELF)
{
  return GetLocalInt(oPC, GetTag(oNPC) + HS_SFX_FRIEND);
}
//::////////////////////////////////////////////////////////////////////////////
string GetTagNoPrefix(object oTarget = OBJECT_SELF)
{
  string sTag = GetTag(oTarget);
  int nLen = GetStringLength(sTag);
  return GetStringRight(sTag, nLen-3);
}
//::////////////////////////////////////////////////////////////////////////////
void PersistentConversationCheck(object oPC, string sConvo = "", int bPrivate = FALSE)
{
  if (!IsInConversation(OBJECT_SELF))
    { PersistentConversationAttempt(oPC, sConvo, bPrivate); }
  else
    { DeleteLocalInt(OBJECT_SELF, HS_CONVO_COUNT); }
}
//::////////////////////////////////////////////////////////////////////////////
void PersistentConversationAttempt(object oPC, string sConvo = "", int bPrivate = FALSE)
{
  int nTries = GetLocalInt(OBJECT_SELF, HS_CONVO_COUNT);
  if (!IsInConversation(OBJECT_SELF) && nTries < 4)
    {
     Debug(GetName(OBJECT_SELF) + ": Trying to start conversation with: " + GetName(oPC));
     ClearAllActions();
     ActionStartConversation(oPC, sConvo, bPrivate);
     DelayCommand(HS_CONV_ATTEMPT_DELAY, PersistentConversationCheck(oPC, sConvo, bPrivate));
     SetLocalInt(OBJECT_SELF, HS_CONVO_COUNT, nTries + 1);
    }
  else
    { DeleteLocalInt(OBJECT_SELF, HS_CONVO_COUNT); }
}
//::////////////////////////////////////////////////////////////////////////////
void ResetHenchmanReputation(object oHench = OBJECT_SELF)
{
  object oPC = GetFirstPC();
  while (GetIsObjectValid(oPC))
    {
     ClearPersonalReputation(oPC, oHench);
     ClearPersonalReputation(oHench, oPC);
     SetLocalInt(oPC, GetTag(oHench) + HS_SFX_FRIEND, 0);
     oPC = GetNextPC();
    }
}
//::////////////////////////////////////////////////////////////////////////////
void TravelToLocation(location lDest, object oTarget = OBJECT_SELF, int bRun = FALSE, float fDelay = 10.0)
{
  object oDestArea = GetAreaFromLocation(lDest);
  if (oDestArea == GetArea(oTarget))
    { AssignCommand(oTarget, ActionForceMoveToLocation(lDest, bRun, fDelay)); }
  else
    {
     object oBestExit = GetBestExit(oTarget, oDestArea);
     AssignCommand(oTarget, ActionForceMoveToObject(oBestExit, bRun, 1.0, fDelay));
     int nObjType = GetObjectType(oBestExit);
     if (nObjType == OBJECT_TYPE_DOOR)
       { AssignCommand(oTarget, ActionOpenDoor(oBestExit)); }
     AssignCommand(oTarget, ActionJumpToLocation(lDest));
    }
  AssignCommand(oTarget, DelayCommand(fDelay, ClearActions(CLEAR_I0_COMMON_TravelToLocation)));
  AssignCommand(oTarget, DelayCommand(fDelay, JumpToLocation(lDest)));
}
//::////////////////////////////////////////////////////////////////////////////
object GetNearestExit(object oTarget = OBJECT_SELF)
{
  object oCurArea = GetArea(oTarget);

  // Look for a nearby Door.
  object oNearDoor = GetNearestObject(OBJECT_TYPE_DOOR, oTarget);
  if (GetArea(oNearDoor) != oCurArea)
    { oNearDoor = OBJECT_INVALID; }

  // Look for a nearby Area Transition Trigger.
  int nTrig = 1;
  object oNearTrig = GetNearestObject(OBJECT_TYPE_TRIGGER, oTarget);
  while (GetIsObjectValid(oNearTrig) && GetArea(oNearTrig) == oCurArea &&
        !GetIsObjectValid(GetTransitionTarget(oNearTrig)))
    {
     nTrig++;
     oNearTrig = GetNearestObject(OBJECT_TYPE_TRIGGER, oTarget, nTrig);
    }
  if (GetArea(oNearTrig) != oCurArea)
    { oNearTrig = OBJECT_INVALID; }

  // Set the distance variables.
  float fMaxDist = 10000.0;
  float fDoorDist = fMaxDist;
  float fTrigDist = fMaxDist;
  if (GetIsObjectValid(oNearDoor))
    { fDoorDist = GetDistanceBetween(oNearDoor, oTarget); }
  if (GetIsObjectValid(oNearTrig))
    { fTrigDist = GetDistanceBetween(oNearTrig, oTarget); }

  // Return the closest exit. Return's a Waypoint if nothing else is valid.
  if (fTrigDist < fDoorDist)
    { return oNearTrig; }
  else if (fDoorDist < fTrigDist || fDoorDist < fMaxDist)
    { return oNearDoor; }
  else
    { return GetNearestObject(OBJECT_TYPE_WAYPOINT, oTarget); }
}
//::////////////////////////////////////////////////////////////////////////////
object GetBestExitByType(object oTarget = OBJECT_SELF, object oTargetArea = OBJECT_INVALID, int nObjType = OBJECT_TYPE_DOOR)
{
  int nDoor = 1;
  object oCurrentArea = GetArea(oTarget);
  object oDoor = GetNearestObject(nObjType, oTarget);
  object oNearestDoor = oDoor;
  object oDestArea = OBJECT_INVALID;
  object oBestDoor = OBJECT_INVALID;
  object oBestDestArea = OBJECT_INVALID;
  while (GetIsObjectValid(oDoor) && GetArea(oDoor) == oCurrentArea)
    {
     // If we find a door that leads to the target area, use it.
     oDestArea = GetArea(GetTransitionTarget(oDoor));
     if (oDestArea == oTargetArea) { return oDoor; }

     // If we find a door that leads to a different area, that might be good if
     // we haven't already found one that leads to the desired area, or a closer
     // door that leads to a different area.
     if (oDestArea != oCurrentArea && !GetIsObjectValid(oBestDoor))
       { oBestDoor = oDoor; }

     // Try the next door.
     nDoor++;
     oDoor = GetNearestObject(nObjType, oTarget, nDoor);
    }

  // If we found a door that leads to a different area, return that one.
  // Otherwise, return the nearest, if it's in this area.
  if (GetIsObjectValid(oBestDoor))
    { return oBestDoor; }
  else if (GetArea(oNearestDoor) == oCurrentArea)
    { return oNearestDoor; }
  else
    { return OBJECT_INVALID; }
}
//::////////////////////////////////////////////////////////////////////////////
object GetBestExit(object oTarget = OBJECT_SELF, object oTargetArea = OBJECT_INVALID)
{
  if (!GetIsObjectValid(oTargetArea)) { return GetNearestExit(oTarget); }

  // Try and find a door.
  object oBestDoor = GetBestExitByType(oTarget, oTargetArea, OBJECT_TYPE_DOOR);
  if (GetIsObjectValid(oBestDoor))
    {
     if (GetTransitionTarget(oBestDoor) == oTargetArea)
       { return oBestDoor; }
    }

  // Try and find a trigger.
  object oBestTrigger = GetBestExitByType(oTarget, oTargetArea, OBJECT_TYPE_TRIGGER);
  if (GetIsObjectValid(oBestTrigger))
    {
     if (GetTransitionTarget(oBestTrigger) == oTargetArea)
       { return oBestTrigger; }
    }

  // Return the best or nearest exit.
  if (GetIsObjectValid(oBestDoor))
    { return oBestDoor; }
  else if (GetIsObjectValid(oBestTrigger))
    { return oBestTrigger; }
  else
    { return GetNearestExit(oTarget); }
}
//::////////////////////////////////////////////////////////////////////////////
struct sSituation CountEnemiesAndAllies(float fRadius = 20.0, object oSource = OBJECT_SELF)
{
  struct sSituation sitCurrent;
  sitCurrent.ENEMY_NUM = 0;
  sitCurrent.ALLY_NUM = 0;
  sitCurrent.ENEMY_CR = 0;
  sitCurrent.ALLY_CR = 0;
  location lSource = GetLocation(oSource);
  object oTarget = GetFirstObjectInShape(SHAPE_SPHERE, fRadius, lSource, TRUE);
  while(GetIsObjectValid(oTarget))
    {
     if (GetObjectSeen(oTarget))
       {
        if (GetIsEnemy(oTarget))
          {
           if (GetIsPC(oTarget) == TRUE)
             { sitCurrent.ENEMY_CR += GetHitDice(oTarget); }
           else
             { sitCurrent.ENEMY_CR += FloatToInt(GetChallengeRating(oTarget)); }
           sitCurrent.ENEMY_NUM++;
          }
        else if (GetIsFriend(oTarget))
          {
           if (GetIsPC(oTarget) == TRUE)
             { sitCurrent.ALLY_CR += GetHitDice(oTarget); }
           else
             { sitCurrent.ALLY_CR += FloatToInt(GetChallengeRating(oTarget)); }
           sitCurrent.ALLY_NUM++;
          }
       }
     oTarget = GetNextObjectInShape(SHAPE_SPHERE, fRadius, lSource, TRUE);
    }
  return sitCurrent;
}
//::////////////////////////////////////////////////////////////////////////////
int GetNumberOfMeleeAttackers()
{
  int nCnt = 0;
  object oTarget = GetFirstObjectInShape(SHAPE_SPHERE, 5.0, GetLocation(OBJECT_SELF), TRUE);
  while (GetIsObjectValid(oTarget))
    {
     if (GetIsEnemy(oTarget))
       { nCnt++; }
     oTarget = GetNextObjectInShape(SHAPE_SPHERE, 5.0, GetLocation(OBJECT_SELF), TRUE);
    }
  return nCnt;
}
//::////////////////////////////////////////////////////////////////////////////
void GetDirection(object oCritter, object oHench = OBJECT_SELF)
{
  // Determine the actual facing.
  float fFacing = GetFacing(oHench);
  if (fFacing >= 360.0) { fFacing = 720.0 - fFacing; }
  if (fFacing < 0.0) { fFacing += (360.0); }
  int nFacing = FloatToInt(fFacing);

  // Set the string for the direction of the creature.
  string sDirection = "";
  if (nFacing >= 359 && nFacing <= 2) { sDirection = "E"; }
  else if (nFacing >=   3 && nFacing <=  45) { sDirection = "ENE"; }
  else if (nFacing >=  46 && nFacing <=  87) { sDirection = "NNE"; }
  else if (nFacing >=  88 && nFacing <=  92) { sDirection = "N"; }
  else if (nFacing >=  93 && nFacing <= 135) { sDirection = "NNW"; }
  else if (nFacing >= 136 && nFacing <= 177) { sDirection = "WNW"; }
  else if (nFacing >= 178 && nFacing <= 182) { sDirection = "W"; }
  else if (nFacing >= 183 && nFacing <= 225) { sDirection = "WSW"; }
  else if (nFacing >= 226 && nFacing <= 267) { sDirection = "SSW"; }
  else if (nFacing >= 268 && nFacing <= 272) { sDirection = "S"; }
  else if (nFacing >= 273 && nFacing <= 315) { sDirection = "SSE"; }
  else if (nFacing >= 316 && nFacing <= 358) { sDirection = "ESE"; }

  // Set the string for the race of the creature.
  string sRace;
  int nRace = GetRacialType(oCritter);
  if (nRace == RACIAL_TYPE_ABERRATION) { sRace = "An aberration"; }
  else if (nRace == RACIAL_TYPE_ALL) { sRace = "A creature"; }
  else if (nRace == RACIAL_TYPE_ANIMAL) { sRace = "An animal"; }
  else if (nRace == RACIAL_TYPE_BEAST) { sRace = "A beast"; }
  else if (nRace == RACIAL_TYPE_CONSTRUCT) { sRace = "A construct"; }
  else if (nRace == RACIAL_TYPE_DRAGON) { sRace = "A dragon"; }
  else if (nRace == RACIAL_TYPE_DWARF) { sRace = "A dwarf"; }
  else if (nRace == RACIAL_TYPE_ELEMENTAL) { sRace = "An elemental"; }
  else if (nRace == RACIAL_TYPE_ELF) { sRace = "An elf"; }
  else if (nRace == RACIAL_TYPE_FEY) { sRace = "A fey"; }
  else if (nRace == RACIAL_TYPE_GIANT) { sRace = "A giant"; }
  else if (nRace == RACIAL_TYPE_GNOME) { sRace = "A gnome"; }
  else if (nRace == RACIAL_TYPE_HALFELF) { sRace = "A half-elf"; }
  else if (nRace == RACIAL_TYPE_HALFLING) { sRace = "A halfling"; }
  else if (nRace == RACIAL_TYPE_HALFORC) { sRace = "A half-orc"; }
  else if (nRace == RACIAL_TYPE_HUMAN) { sRace = "A human"; }
  else if (nRace == RACIAL_TYPE_HUMANOID_GOBLINOID) { sRace = "A goblinoid creature"; }
  else if (nRace == RACIAL_TYPE_HUMANOID_MONSTROUS) { sRace = "A monstrous creature"; }
  else if (nRace == RACIAL_TYPE_HUMANOID_ORC) { sRace = "An orcish creature"; }
  else if (nRace == RACIAL_TYPE_HUMANOID_REPTILIAN) { sRace = "A reptilianlike creature"; }
  else if (nRace == RACIAL_TYPE_INVALID) { sRace = "An odd thing"; }
  else if (nRace == RACIAL_TYPE_MAGICAL_BEAST) { sRace = "A very unusual creature"; }
  else if (nRace == RACIAL_TYPE_OUTSIDER) { sRace = "An outsider"; }
  else if (nRace == RACIAL_TYPE_SHAPECHANGER) { sRace = "A possible shapechanger"; }
  else if (nRace == RACIAL_TYPE_UNDEAD) { sRace = "An undead"; }
  else if (nRace == RACIAL_TYPE_VERMIN) { sRace = "A vermin"; }

  // Give feedback based on direction and race.
  SpeakString(sRace + " is to the " + sDirection + ".");
}
//::////////////////////////////////////////////////////////////////////////////
void SPS(object oPC, int nState)
{
  string sID = GetName(oPC) + GetPCPlayerName(oPC);
  if (GetLocalInt(GetModule(), "PERSIST"))
    { SetCampaignInt("HCRPC" + sID, "PlayerState", nState); }
  else
    { StoreInt(GetModule(), "PlayerState" + sID, nState); }
}
//::////////////////////////////////////////////////////////////////////////////
int GPS(object oPC)
{
  string sID = GetName(oPC) + GetPCPlayerName(oPC);
  if (GetLocalInt(GetModule(), "PERSIST"))
    { return GetCampaignInt("HCRPC" + sID, "PlayerState"); }
  else
    { return RetrieveInt(GetModule(), "PlayerState" + sID); }
}
//::////////////////////////////////////////////////////////////////////////////
int SecondsSinceBegin()
{
  object oMod = GetModule();
  int nConv = FloatToInt(HoursToSeconds(1));
  float fStartHour = IntToFloat(RetrieveInt(oMod, "HourStart"));
  float fStartDay = IntToFloat(RetrieveInt(oMod, "DayStart"));
  float fStartMonth = IntToFloat(RetrieveInt(oMod, "MonthStart"));
  float fStartYear = IntToFloat(RetrieveInt(oMod, "YearStart"));
  float fCurDay = IntToFloat(GetCalendarDay());
  float fCurMonth = IntToFloat(GetCalendarMonth());
  float fCurYear = IntToFloat(GetCalendarYear());
  float fCurHour = IntToFloat(GetTimeHour());
  float fCurMin = IntToFloat(GetTimeMinute());
  float fCurSec = IntToFloat(GetTimeSecond());
  float fElapsed = 0.000;
  if (fCurYear == fStartYear)
    {
     if (fCurMonth == fStartMonth)
       {
        if (fCurDay == fStartDay)
          { fElapsed += (fCurHour - fStartHour); }
        else
          {
           if (fCurHour > fStartHour)
             {
              fElapsed += 24.0 * (fCurDay - fStartDay);
              fElapsed += fCurHour - fStartHour;
             }
           else
             {
              fElapsed += 24.0 * (fCurDay - fStartDay - 1.0);
              fElapsed += 24.0 - fStartHour + fCurHour;
             }
          }
       }
     else
       {
        if (fCurDay > fStartDay)
          {
           fElapsed += 28.0 * 24.0 * (fCurMonth - fStartMonth);
           fElapsed += 24.0 * (fCurDay - fStartDay);
          }
        else
          {
           fElapsed += 28.0 * 24.0 * (fCurMonth - fStartMonth - 1.0);
           fElapsed += 24.0 * (28.0 - fStartDay + fCurDay);
          }
        if (fCurHour > fStartHour)
          { fElapsed += fCurHour - fStartHour + 2.0; }
        else
          { fElapsed += -24.0 + fStartHour + fCurHour; }
       }
    }
  else
    {
     if (fCurMonth > fStartMonth)
       {
        fElapsed += 12.0 * 28.0 * 24.0 * (fCurYear - fStartYear);
        fElapsed += 28.0 * 24.0 * (fCurMonth - fStartMonth);
       }
     else
       {
        fElapsed += 12.0 * 28.0 * 24.0 * (fCurYear - fStartYear - 1.0);
        fElapsed += 28.0 * 24.0 * (12.0 - fStartMonth + fCurMonth);
       }
     if (fCurDay > fStartDay)
       { fElapsed += 24.0 * (fCurDay - fStartDay); }
     else
       { fElapsed += 24.0 * (28.0 - fStartDay + fCurDay); }
     if (fCurHour > fStartHour)
       { fElapsed += fCurHour - fStartHour + 2.0; }
     else
       { fElapsed += -24.0 + fStartHour + fCurHour; }
    }
  fElapsed = IntToFloat(nConv) * fElapsed;
  fElapsed += fCurMin * 60.0;
  fElapsed += fCurSec;
  return FloatToInt(fElapsed);
}
//::////////////////////////////////////////////////////////////////////////////
void SetKeyword(string sPattern, string sScript, object oHench = OBJECT_SELF)
{
  if (sPattern == "" || sScript == "" || !GetIsObjectValid(oHench)) { return; }

  // Set the pattern number.
  int nPattern = GetLocalInt(oHench, HS_KCS_PATTERN);
  if (nPattern == 0)
    { nPattern = 5000; }
  else
    { nPattern++; }

  // Set the listen pattern, keyword and script to execute.
  SetListenPattern(oHench, sPattern, nPattern);
  string sPattern = IntToString(nPattern);
  SetLocalString(oHench, HS_KCS_SCRIPT + sPattern, sScript);
  SetLocalInt(oHench, HS_KCS_PATTERN, nPattern);
}
//::////////////////////////////////////////////////////////////////////////////
void HenchRemoveCorpse(object oHench, object oCorpse)
{
  if (!GetIsObjectValid(oHench) || !GetIsObjectValid(oCorpse)) { return; }
  ExecuteScript(sDestroyScript, oCorpse);
  DeleteLocalObject(oHench, HS_HEN_D_CORPSE);
}
//::////////////////////////////////////////////////////////////////////////////
void HenchRemoveDropBag(object oHench, object oDropBag)
{
  if (!GetIsObjectValid(oHench) || !GetIsObjectValid(oDropBag)) { return; }

  object oNew;
  object oItem = GetFirstItemInInventory(oDropBag);
  while (GetIsObjectValid(oItem))
    {
     if (!GetLocalInt(oItem, "Destroy") && !GetIsNoDrop(oItem))
       {
        if (GetHasInventory(oItem) &&
            GetIsObjectValid(GetFirstItemInInventory(oItem)))
          {
           // Pack has stuff in it so empty it first.
           object oPack = GetFirstItemInInventory(oItem);
           while (GetIsObjectValid(oPack))
             {
              if (!GetLocalInt(oPack, "Destroy"))
                {
                 oNew = CopyItem(oPack, oHench);
                 if (GetIsObjectValid(oNew))
                   {
                    SetLocalInt(oPack, "Destroy", TRUE);
                    DestroyObject(oPack);
                   }
                }
              else
                {
                 SetLocalInt(oPack, "Destroy", TRUE);
                 DestroyObject(oPack);
                }
              oPack = GetNextItemInInventory(oItem);
             }

           // Pack is empty now, so move it.
           oNew = CopyItem(oItem, oHench);
           if (GetIsObjectValid(oNew))
             {
              SetLocalInt(oItem, "Destroy", TRUE);
              DestroyObject(oItem);
             }
          }
        else
          {
           object oNew = CopyItem(oItem, oHench);
           if (GetIsObjectValid(oNew))
             {
              SetLocalInt(oItem, "Destroy", TRUE);
              DestroyObject(oItem);
             }
          }
       }
     else
       {
        SetLocalInt(oItem, "Destroy", TRUE);
        DestroyObject(oItem);
       }
     oItem = GetNextItemInInventory(oDropBag);
    }
  DestroyObject(oDropBag, 2.0);
  DeleteLocalObject(oHench, HS_HEN_DROP_BAG);
  // Long delay needed so it wont interfere with persistent convo attempt.
  DelayCommand(10.0, ExecuteScript(sReEquipScript, oHench));
}
//::////////////////////////////////////////////////////////////////////////////
void HenchRemoveToken(object oHench, object oToken)
{
  SetLocalInt(oToken, "Destroy", 1);
  DestroyObject(oToken);
  DeleteLocalObject(oHench, HS_HEN_CORP_TOK);
}
//::////////////////////////////////////////////////////////////////////////////
void HenchMoveCorpse(object oHench, object oPC)
{
  if (!GetIsObjectValid(oHench) || !GetIsObjectValid(oPC)) { return; }

  // Determine the location.
  float fFac = GetFacing(oPC);
  float fDif = 45.0 + IntToFloat(d20());
  fFac = fFac + fDif;
  if (fFac > 360.0)
    { fFac = 720.0 - fFac; }
  if (fFac < 0.0f)
    { fFac = 360.0 + fFac; }
  float fNewX;
  float fNewY;
  float fNewZ;
  float fDis = -0.8;
  location lLocation = GetLocation(oPC);
  vector vVec = GetPositionFromLocation(lLocation);
  if ((fFac > 0.0) && (fFac < 90.0))
    {
     fNewX = vVec.x + ((cos(fFac))*fDis);
     fNewY = vVec.y + ((sin(fFac))*fDis);
     fNewZ = vVec.z;
    }
  else if ((fFac > 90.0) && (fFac < 180.0))
    {
     fNewX = vVec.x - ((cos(180.0 - fFac))*fDis);
     fNewY = vVec.y + ((sin(180.0 - fFac))*fDis);
     fNewZ = vVec.z;
    }
  else if ((fFac > 180.0) && (fFac < 270.0))
    {
     fNewX = vVec.x - ((cos(fFac - 180.0))*fDis);
     fNewY = vVec.y - ((sin(fFac - 180.0))*fDis);
     fNewZ = vVec.z;
    }
  else if ((fFac > 270.0) && (fFac < 360.0))
    {
     fNewX = vVec.x + ((cos(360.0 - fFac))*fDis);
     fNewY = vVec.y - ((sin(360.0 - fFac))*fDis);
     fNewZ = vVec.z;
    }
  else if (fFac == 0.0)
    {
     fNewX = vVec.x + fDis;
     fNewY = vVec.y;
     fNewZ = vVec.z;
    }
  else if (fFac == 90.0)
    {
     fNewX = vVec.x + 0.2;
     fNewY = vVec.y + fDis;
     fNewZ = vVec.z;
    }
  else if (fFac == 180.0)
    {
     fNewX = vVec.x - fDis;
     fNewY = vVec.y + 0.2;
     fNewZ = vVec.z;
    }
  else if (fFac == 270.0)
    {
     fNewX = vVec.x + 0.2;
     fNewY = vVec.y - fDis;
     fNewZ = vVec.z;
    }
  object oArea = GetArea(oPC);
  vector vNewFinal = Vector(fNewX, fNewY, fNewZ);
  location lCorpse = Location(oArea, vNewFinal, fFac);

  // Now that we have our location, create a new Corpse or move it.
  int i;
  effect eDam;
  object oNew;
  object oItem;
  object oCorpse = GetLocalObject(oHench, HS_HEN_D_CORPSE);
  if (!GetIsObjectValid(oCorpse))
    {
     oNew = CopyObject(oHench, lCorpse);
     eDam = EffectDamage(GetCurrentHitPoints(oNew) + 10);
     SetPlotFlag(oNew, FALSE);
     SetImmortal(oNew, FALSE);
     SetLocalInt(oNew, HS_SELF_DESTROY, TRUE);
     AssignCommand(oNew, SetIsDestroyable(FALSE, TRUE, TRUE));
     // If not dead, kill the copy.
     if (!GetIsDead(oNew))
       { ApplyEffectToObject(DURATION_TYPE_INSTANT, eDam, oNew); }
     // Clear everything but equipped armor, for decency.
     for (i = 0; i < NUM_INVENTORY_SLOTS; i++)
       {
        oItem = GetItemInSlot(i, oNew);
        if (GetIsObjectValid(oItem) && i != INVENTORY_SLOT_CHEST)
          { DestroyObject(oItem); }
       }
     oItem = GetFirstItemInInventory(oNew);
     while (GetIsObjectValid(oItem))
       {
        DestroyObject(oItem);
        oItem = GetNextItemInInventory(oNew);
       }
     SetLocalObject(oHench, HS_HEN_D_CORPSE, oNew);
     SetLocalObject(oNew, "Owner", oHench);
    }
  else
    {
     oNew = CopyObject(oCorpse, lCorpse);
     SetLocalObject(oHench, HS_HEN_D_CORPSE, oNew);
     SetLocalObject(oNew, "Owner", oHench);
     ExecuteScript(sDestroyScript, oCorpse);
    }
}
//::////////////////////////////////////////////////////////////////////////////
void HenchMoveDropBag(object oHench)
{
  object oCorpse = GetLocalObject(oHench, HS_HEN_D_CORPSE);
  if (!GetIsObjectValid(oHench) || !GetIsObjectValid(oCorpse)) { return; }

  // Determine the location.
  float fFac = GetFacing(oCorpse);
  float fDif = 45.0 + IntToFloat(d20());
  fFac = fFac + fDif;
  if (fFac > 360.0)
    { fFac = 720.0 - fFac; }
  if (fFac < 0.0f)
    { fFac = 360.0 + fFac; }
  float fNewX;
  float fNewY;
  float fNewZ;
  float fDis = -0.8;
  location lLocation = GetLocation(oCorpse);
  vector vVec = GetPositionFromLocation(lLocation);
  if ((fFac > 0.0) && (fFac < 90.0))
    {
     fNewX = vVec.x + ((cos(fFac))*fDis);
     fNewY = vVec.y + ((sin(fFac))*fDis);
     fNewZ = vVec.z;
    }
  else if ((fFac > 90.0) && (fFac < 180.0))
    {
     fNewX = vVec.x - ((cos(180.0 - fFac))*fDis);
     fNewY = vVec.y + ((sin(180.0 - fFac))*fDis);
     fNewZ = vVec.z;
    }
  else if ((fFac > 180.0) && (fFac < 270.0))
    {
     fNewX = vVec.x - ((cos(fFac - 180.0))*fDis);
     fNewY = vVec.y - ((sin(fFac - 180.0))*fDis);
     fNewZ = vVec.z;
    }
  else if ((fFac > 270.0) && (fFac < 360.0))
    {
     fNewX = vVec.x + ((cos(360.0 - fFac))*fDis);
     fNewY = vVec.y - ((sin(360.0 - fFac))*fDis);
     fNewZ = vVec.z;
    }
  else if (fFac == 0.0)
    {
     fNewX = vVec.x + fDis;
     fNewY = vVec.y;
     fNewZ = vVec.z;
    }
  else if (fFac == 90.0)
    {
     fNewX = vVec.x + 0.2;
     fNewY = vVec.y + fDis;
     fNewZ = vVec.z;
    }
  else if (fFac == 180.0)
    {
     fNewX = vVec.x - fDis;
     fNewY = vVec.y + 0.2;
     fNewZ = vVec.z;
    }
  else if (fFac == 270.0)
    {
     fNewX = vVec.x + 0.2;
     fNewY = vVec.y - fDis;
     fNewZ = vVec.z;
    }
  object oArea = GetArea(oCorpse);
  vector vNewFinal = Vector(fNewX, fNewY, fNewZ);
  location lBagLoc = Location(oArea, vNewFinal, fFac);

  // Now that we have our location, create a new Drop Bag or move it.
  object oNew;
  object oItem;
  object oNewItem;
  object oBag = GetLocalObject(oHench, HS_HEN_DROP_BAG);
  if (!GetIsObjectValid(oBag))
    {
     // No previous Drop Bag. Create a new one.
     oNew = CreateObject(OBJECT_TYPE_PLACEABLE, HS_DROP_BAG, lBagLoc);
     SetLocalObject(oHench, HS_HEN_DROP_BAG, oNew);
     SetLocalObject(oNew, "Owner", oHench);
    }
  else
    {
     // Previous Bag still exists. Create a new one, transfer the contents and
     // then destroy the old one.
     oNew  = CreateObject(OBJECT_TYPE_PLACEABLE, HS_DROP_BAG, lBagLoc);
     oItem = GetFirstItemInInventory(oBag);
     while (GetIsObjectValid(oItem))
       {
        oNewItem = CopyItem(oItem, oNew);
        if (GetIsObjectValid(oNewItem)) { DestroyObject(oItem); }
        oItem = GetNextItemInInventory(oBag);
       }
     SetLocalObject(oHench, HS_HEN_DROP_BAG, oNew);
     SetLocalObject(oNew, "Owner", oHench);
     DestroyObject(oBag, 2.0);
    }
}
//::////////////////////////////////////////////////////////////////////////////
void HenchMoveToken(object oHench)
{
  object oDropBag = GetLocalObject(oHench, HS_HEN_DROP_BAG);
  if (!GetIsObjectValid(oHench) || !GetIsObjectValid(oDropBag)) { return; }

  object oNew;
  object oTok = GetLocalObject(oHench, HS_HEN_CORP_TOK);
  if (!GetIsObjectValid(oTok))
    {
     oNew = CreateItemOnObject(HS_CORP_TOK, oDropBag);
     SetLocalObject(oHench, HS_HEN_CORP_TOK, oNew);
     SetLocalObject(oNew, "Owner", oHench);
    }
  else
    {
     oNew = CreateItemOnObject(HS_CORP_TOK, oDropBag);
     SetLocalObject(oHench, HS_HEN_CORP_TOK, oNew);
     SetLocalObject(oNew, "Owner", oHench);
     DestroyObject(oTok);
    }
}
//::////////////////////////////////////////////////////////////////////////////
void HenchCreateCorpse(object oHench)
{
  if (!GetIsObjectValid(oHench)) { return; }

  // Determine the location.
  float fFac = GetFacing(oHench);
  float fDif = 45.0 + IntToFloat(d20());
  fFac = fFac + fDif;
  if (fFac > 360.0)
    { fFac = 720.0 - fFac; }
  if (fFac < 0.0f)
    { fFac = 360.0 + fFac; }
  float fNewX;
  float fNewY;
  float fNewZ;
  float fDis = -0.8;
  location lLocation = GetLocation(oHench);
  vector vVec = GetPositionFromLocation(lLocation);
  if ((fFac > 0.0) && (fFac < 90.0))
    {
     fNewX = vVec.x + ((cos(fFac))*fDis);
     fNewY = vVec.y + ((sin(fFac))*fDis);
     fNewZ = vVec.z;
    }
  else if ((fFac > 90.0) && (fFac < 180.0))
    {
     fNewX = vVec.x - ((cos(180.0 - fFac))*fDis);
     fNewY = vVec.y + ((sin(180.0 - fFac))*fDis);
     fNewZ = vVec.z;
    }
  else if ((fFac > 180.0) && (fFac < 270.0))
    {
     fNewX = vVec.x - ((cos(fFac - 180.0))*fDis);
     fNewY = vVec.y - ((sin(fFac - 180.0))*fDis);
     fNewZ = vVec.z;
    }
  else if ((fFac > 270.0) && (fFac < 360.0))
    {
     fNewX = vVec.x + ((cos(360.0 - fFac))*fDis);
     fNewY = vVec.y - ((sin(360.0 - fFac))*fDis);
     fNewZ = vVec.z;
    }
  else if (fFac == 0.0)
    {
     fNewX = vVec.x + fDis;
     fNewY = vVec.y;
     fNewZ = vVec.z;
    }
  else if (fFac == 90.0)
    {
     fNewX = vVec.x + 0.2;
     fNewY = vVec.y + fDis;
     fNewZ = vVec.z;
    }
  else if (fFac == 180.0)
    {
     fNewX = vVec.x - fDis;
     fNewY = vVec.y + 0.2;
     fNewZ = vVec.z;
    }
  else if (fFac == 270.0)
    {
     fNewX = vVec.x + 0.2;
     fNewY = vVec.y - fDis;
     fNewZ = vVec.z;
    }
  object oArea = GetArea(oHench);
  vector vNewFinal = Vector(fNewX, fNewY, fNewZ);
  location lCorpse = Location(oArea, vNewFinal, fFac);

  // Now that we have our location, create a Corpse.
  int i;
  effect eDam;
  object oNew;
  object oItem;
  object oCorpse = GetLocalObject(oHench, HS_HEN_D_CORPSE);
  if (!GetIsObjectValid(oCorpse))
    {
     oNew = CopyObject(oHench, lCorpse);
     eDam = EffectDamage(GetCurrentHitPoints(oNew) + 10);
     SetPlotFlag(oNew, FALSE);
     SetImmortal(oNew, FALSE);
     SetLocalInt(oNew, HS_SELF_DESTROY, TRUE);
     AssignCommand(oNew, SetIsDestroyable(FALSE, TRUE, TRUE));
     // If not dead, kill the copy.
     if (!GetIsDead(oNew))
       { ApplyEffectToObject(DURATION_TYPE_INSTANT, eDam, oNew); }
     // Clear everything but equipped armor, for decency.
     for (i = 0; i < NUM_INVENTORY_SLOTS; i++)
       {
        oItem = GetItemInSlot(i, oNew);
        if (GetIsObjectValid(oItem) && i != INVENTORY_SLOT_CHEST)
          { DestroyObject(oItem); }
       }
     oItem = GetFirstItemInInventory(oNew);
     while (GetIsObjectValid(oItem))
       {
        DestroyObject(oItem);
        oItem = GetNextItemInInventory(oNew);
       }
     SetLocalObject(oHench, HS_HEN_D_CORPSE, oNew);
     SetLocalObject(oNew, "Owner", oHench);
    }
  else
    {
     oNew = CopyObject(oCorpse, lCorpse);
     SetLocalObject(oHench, HS_HEN_D_CORPSE, oNew);
     SetLocalObject(oNew, "Owner", oHench);
     ExecuteScript(sDestroyScript, oCorpse);
    }
}
//::////////////////////////////////////////////////////////////////////////////
void HenchCreateDropBag(object oHench)
{
  if (!GetIsObjectValid(oHench)) { return; }

  // Determine the location.
  float fFac = GetFacing(oHench);
  float fDif = 45.0 + IntToFloat(d20());
  fFac = fFac + fDif;
  if (fFac > 360.0)
    { fFac = 720.0 - fFac; }
  if (fFac < 0.0f)
    { fFac = 360.0 + fFac; }
  float fNewX;
  float fNewY;
  float fNewZ;
  float fDis = -0.8;
  location lLocation = GetLocation(oHench);
  vector vVec = GetPositionFromLocation(lLocation);
  if ((fFac > 0.0) && (fFac < 90.0))
    {
     fNewX = vVec.x + ((cos(fFac))*fDis);
     fNewY = vVec.y + ((sin(fFac))*fDis);
     fNewZ = vVec.z;
    }
  else if ((fFac > 90.0) && (fFac < 180.0))
    {
     fNewX = vVec.x - ((cos(180.0 - fFac))*fDis);
     fNewY = vVec.y + ((sin(180.0 - fFac))*fDis);
     fNewZ = vVec.z;
    }
  else if ((fFac > 180.0) && (fFac < 270.0))
    {
     fNewX = vVec.x - ((cos(fFac - 180.0))*fDis);
     fNewY = vVec.y - ((sin(fFac - 180.0))*fDis);
     fNewZ = vVec.z;
    }
  else if ((fFac > 270.0) && (fFac < 360.0))
    {
     fNewX = vVec.x + ((cos(360.0 - fFac))*fDis);
     fNewY = vVec.y - ((sin(360.0 - fFac))*fDis);
     fNewZ = vVec.z;
    }
  else if (fFac == 0.0)
    {
     fNewX = vVec.x + fDis;
     fNewY = vVec.y;
     fNewZ = vVec.z;
    }
  else if (fFac == 90.0)
    {
     fNewX = vVec.x + 0.2;
     fNewY = vVec.y + fDis;
     fNewZ = vVec.z;
    }
  else if (fFac == 180.0)
    {
     fNewX = vVec.x - fDis;
     fNewY = vVec.y + 0.2;
     fNewZ = vVec.z;
    }
  else if (fFac == 270.0)
    {
     fNewX = vVec.x + 0.2;
     fNewY = vVec.y - fDis;
     fNewZ = vVec.z;
    }
  object oArea = GetArea(oHench);
  vector vNewFinal = Vector(fNewX, fNewY, fNewZ);
  location lBagLoc = Location(oArea, vNewFinal, fFac);

  // Now that we have our location, create a new Drop Bag or move it.
  object oNew;
  object oItem;
  object oNewItem;
  object oBag = GetLocalObject(oHench, HS_HEN_DROP_BAG);
  if (!GetIsObjectValid(oBag))
    {
     oNew = CreateObject(OBJECT_TYPE_PLACEABLE, HS_DROP_BAG, lBagLoc);
     SetLocalObject(oHench, HS_HEN_DROP_BAG, oNew);
     SetLocalObject(oNew, "Owner", oHench);
    }
  else
    {
     oNew = CreateObject(OBJECT_TYPE_PLACEABLE, HS_DROP_BAG, lBagLoc);
     oItem = GetFirstItemInInventory(oBag);
     while (GetIsObjectValid(oItem))
       {
        oNewItem = CopyItem(oItem, oNew);
        if (GetIsObjectValid(oNewItem)) { DestroyObject(oItem); }
        oItem = GetNextItemInInventory(oBag);
       }
     SetLocalObject(oHench, HS_HEN_DROP_BAG, oNew);
     SetLocalObject(oNew, "Owner", oHench);
     DestroyObject(oBag, 2.0);
    }
}
//::////////////////////////////////////////////////////////////////////////////
void HenchCreateToken(object oHench)
{
  object oBag = GetLocalObject(oHench, HS_HEN_DROP_BAG);
  if (!GetIsObjectValid(oHench) || !GetIsObjectValid(oBag)) { return; }

  object oNew;
  object oTok = GetLocalObject(oHench, HS_HEN_CORP_TOK);
  if (!GetIsObjectValid(oTok))
    {
     oNew = CreateItemOnObject(HS_CORP_TOK, oBag);
     SetLocalObject(oHench, HS_HEN_CORP_TOK, oNew);
     SetLocalObject(oNew, "Owner", oHench);
    }
  else
    {
     oNew = CreateItemOnObject(HS_CORP_TOK, oBag);
     SetLocalObject(oHench, HS_HEN_CORP_TOK, oNew);
     SetLocalObject(oNew, "Owner", oHench);
     DestroyObject(oTok);
    }
}
//::////////////////////////////////////////////////////////////////////////////
//void main(){}
