/**
 * Generic OnOpen and OnDeath handler for respawning containers.
 * To use this script:
 * <ol>
 * <li>Create a new invisible plot object to act as the respawner.  Note its
 *     resref, as we'll be using it later.</li>
 * <li>Create a container and set this script as its OnOpen and OnDeath
 *     handler.</li>
 * <li>Create a custom OnUserDefined script for the container.</li>
 * <li>In that script, #include "dmc_inc_contgen"</li>
 * <li>In that script, handle the UE_SET_RESPAWN_VARIABLES event.  In it,
 *     set the following local variables:
 *     <ul>
 *         <li>float RESPAWN_DURATION_TAG: the number of seconds to wait between
 *             respawns. (REQUIRED)</li>
 *         <li>string RESPAWNER_RESREF_TAG: the blueprint resref of the
 *             respawner blueprint created above. (REQUIRED)</li>
 *         <li>int CONTAINER_STORE_INVENTORY_TAG: TRUE if you want the
 *             container's original contents to be respawned.  Defaults to
 *             FALSE.</li>
 *     </ul>
 *     Once you've finished setting these variables, call
 *     callbackSetRespawnVariables.</li>
 * <li>In that script, optionally handle the UE_GENERATE_TREASURE event.  In it,
 *     generate treasure for the container.  You can use the treasure generation
 *     system from the original campaign (found in NW_O2_CONINCLUDE), the
 *     treasure generation system from Shadows of Undrentide (found in
 *     X0_I0_TREASURE), or your own treasure generation system.  Prior to the
 *     event being signalled, the CONTAINER_ACTOR_TAG local object is set to
 *     the object that triggered the event (whoever opened or destroyed the
 *     container).</li>
 * <li>In that script, optionally handle the UE_CONTAINER_DESTRUCTION event.  In
 *     it, perform any operations needed when the container is destroyed, such
 *     as destroying fragile treasure.  This is signalled AFTER the
 *     UE_GENERATE_TREASURE event.  Prior to the event being signalled, the
 *     CONTAINER_ACTOR_TAG local object is set to the object that triggered the
 *     event (whoever destroyed the container).</li>
 *
 * Feel free to use or modify this script freely, with the exception that this
 * paragraph must remain unchanged.  Please leave some mention of the original
 * authors in the comments of the script, and if you have an area in your module
 * where you give credit to the developers/contributors, please be sure to
 * include us.
 *
 * @author <a href="mailto:david@carr.name">David Carr</a>
 * @author <a href="http://nwvault.ign.com/Files/scripts/data/1046937481401.shtml">
 *           R.Lowe
 *         </a>
 * @version 0.3
 */

#include "dmc_inc_contgen"

/**
 * PRIVATE: Returns the value of GetLastOpenedBy() if set, otherwise the value
 * of GetLastKiller() if set, otherwise OBJECT_INVALID.
 */
object GetLastOpenerOrKiller() {
  object oLastOpenedBy = GetLastOpenedBy();
  object oLastKiller = GetLastKiller();
  if(GetIsObjectValid(oLastOpenedBy)) {
    return oLastOpenedBy;
  } else if(GetIsObjectValid(oLastKiller)){
    return oLastKiller;
  } else {
    return OBJECT_INVALID;
  }
}

/**
 * PRIVATE: Returns if the specified object was killed
 */
int ObjectWasKilled() {
  return GetIsObjectValid(GetLastKiller());
}

/**
 * PRIVATE: Alert nearby creatures when a container is disturbed.
 * Based on code in NW_O2_CONINCLUDE.
 */
void ShoutDisturbed() {
  object oContainer = OBJECT_SELF;
  location lLoc = GetLocation(oContainer);
  object oAttacker = GetLastOpenerOrKiller();
  object oTarget = GetFirstObjectInShape(SHAPE_SPHERE, RADIUS_SIZE_LARGE, lLoc,
                                         TRUE, OBJECT_TYPE_CREATURE);
  //Cycle through the targets within the spell shape until an
  //invalid object is captured.
  while(GetIsObjectValid(oTarget)) {
    if(GetFactionEqual(oTarget, oContainer)) {
      //Make anyone who is a member of my faction hostile if I am violated
      SetIsTemporaryEnemy(oAttacker, oTarget);
      AssignCommand(oTarget, ActionAttack(oAttacker));
    }
    oTarget = GetNextObjectInShape(SHAPE_SPHERE, RADIUS_SIZE_LARGE, lLoc, TRUE,
                                   OBJECT_TYPE_CREATURE);
  }
}

/**
 * PRIVATE: Returns true if it's okay to generate treasure now
 */
int respawnTimerHasExpired(object oContainer) {
  return !GetLocalInt(oContainer, RESPAWN_TIMER_TAG);
}

/**
 * PRIVATE: Puts a hold on treasure generation until clearRespawnTimer is called
 */
void setRespawnTimer(object oContainer) {
  SetLocalInt(oContainer, RESPAWN_TIMER_TAG, TRUE);
}

/**
 * PRIVATE: Removes the association between the spawner and a container
 */
void clearInstance(object oSpawner) {
  DeleteLocalObject(oSpawner, INSTANCE_TAG);
}

/**
 * PRIVATE: Returns the respawner object associated with a container, or
 * OBJECT_INVALID if one doesn't exist
 */
object getRespawner(object oContainer) {
  return GetLocalObject(oContainer, RESPAWNER_TAG);
}

/**
 * PRIVATE: Signals the container to set its variables, such as respawn duration
 * and whether to store the inventory.  No arguments are passed.
 */
void signalSetRespawnVariables(object oContainer) {
  SignalEvent(oContainer, EventUserDefined(UE_SET_RESPAWN_VARIABLES));
}

/**
 * PRIVATE: Signals the treasure generation event for the container.  One
 * argument is passed, the "actor" who triggered the event.
 */
void signalTreasureGeneration(object oContainer, object oActor) {
  //Set the actor on the container so that it can be accessed from user events
  SetLocalObject(oContainer, CONTAINER_ACTOR_TAG, oActor);
  SignalEvent(oContainer, EventUserDefined(UE_GENERATE_TREASURE));
}

/**
 * PRIVATE: Signals the container destruction event for the container.  One
 * argument is passed, the "actor" who triggered the event.
 */
void signalContainerDestruction(object oContainer, object oActor) {
  //Set the actor on the container so that it can be accessed from user events
  SetLocalObject(oContainer, CONTAINER_ACTOR_TAG, oActor);
  SignalEvent(oContainer, EventUserDefined(UE_CONTAINER_DESTRUCTION));
}

/**
 * PRIVATE: If a respawner for the specified container doesn't exist, create
 * one. If it's a new respawner or the instance was destroyed, update the stored
 * instance.  Then, queue a command to respawn the treasure/container.
 */
void scheduleRespawn(object oContainer) {
  if(!GetIsObjectValid(oContainer) || !GetHasInventory(oContainer)) {
    logError("invalid object passed to scheduleRespawn");
    return;
  }
  object oSpawner = getRespawner(oContainer);
  if(!GetIsObjectValid(oSpawner)) {//If there isn't a spawner already
    signalSetRespawnVariables(oContainer);
  } else {
    addRespawnToEventQueue(oSpawner);
  }
}

/**
 * PRIVATE: First, determine whether the container was opened or destroyed.
 * Then, if it has been destroyed or it is time to generate treasure, set a
 * timer for a respawn.  Then, generate the treasure if appropriate.  If the
 * chest was destroyed, potions may be shattered.  Also, alert any nearby
 * creatures.
 */
void main() {
  object oContainer = OBJECT_SELF;
  object oActor = GetLastOpenerOrKiller();
  int bWasKilled = ObjectWasKilled();
  if(!GetIsObjectValid(oActor)) {
    logError("object was neither opened nor destroyed");
    return;
  }
  if(respawnTimerHasExpired(oContainer)) {
    setRespawnTimer(oContainer);
    scheduleRespawn(oContainer);
    signalTreasureGeneration(oContainer, oActor);
  } else if(bWasKilled) {
    //clear the instance so that the respawn event knows to make a new container
    clearInstance(getRespawner(oContainer));
  }
  if(bWasKilled) {
    signalContainerDestruction(oContainer, oActor);
  }
  ShoutDisturbed();
}