/*
 * spawnmanager.h by Keith Fulton <keith@paqrat.com>
 *
 * Copyright (C) 2002 Atomic Blue (info@planeshift.it, http://www.atomicblue.org) 
 *
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation (version 2 of the License)
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

#ifndef __SPAWNMANAGER_H__
#define __SPAWNMANAGER_H__

class EntityManager;
class gemObject;
class psCharacter;
class psDatabase;
class psScheduledItem;
class psSectorInfo;
struct iCelEntity;

#include <util/prb.h>
#include <csgeom/vector3.h>
#include <csutil/hash.h>
#include "util/location.h"
#include "util/gameevent.h"
#include "msgmanager.h"


/**
 * This class is used to store respawn ranges for NPCs. They are intended
 * to be gathered into a SpawnRule.
 */
class SpawnRange
{
protected:
    csRandomGen* randomgen;

    /// Unique ID used for ident and tree sorting
    int   id;

    char  type;  /// A = Area (rect) or L = Line Segment

    /// Corresponding spawn rule ID
    int   npcspawnruleid;

    /// Spawn sector name
    csString spawnsector;

private:
    /// Spawn range rectangular volume with corners (x1,y1,z1) and (x2,y2,z2)
    float x1;
    float y1;
    float z1;
    float x2;
    float y2;
    float z2;

    /// Pre-computed area to avoid overhead
    float area;

public:
    /// Ctor clears all to zero
    SpawnRange();

    /// Setup variables and compute area
    void Initialize(int idval,
                    int spawnruleid,
                    const char *type_code,
                    float rx1, float ry1, float rz1,
                    float rx2, float ry2, float rz2,
                    const char *sectorname);

    void SetID(int idval) { id = idval; };

    /// Get range's XZ area
    float GetArea() { return area; };

    /// Get spawn sector name
    const csString& GetSector() { return spawnsector; }

    /// Randomly pick a position within the range
    const csVector3 PickPos();

    bool operator==(SpawnRange& other)
    {
        return id == other.id;
    };
    bool operator<(SpawnRange& other)
    {
        return id < other.id;
    };
};

class LootEntrySet;
 
/**
 * This class is used to store respawn rules for NPCs. They are loaded
 * from the database at startup and used only in RAM thereafter.
 */
class SpawnRule
{
protected:
    
    csRandomGen* randomgen;

    /// Unique Id used for ident and tree sorting
    int   id;

    /// Minimum respawn delay in ticks (msec)
    int   minspawntime;

    /// Maximum respawn delay in ticks
    int   maxspawntime;

    /// Odds of substitute spawn triggering instead of respawn of same entity
    float substitutespawnodds;

    /// Player id to respawn if substitution spawn odds are met
    int   substituteplayer;

    /// If ranges are populated, these are ignored
    float    fixedspawnx;
    float    fixedspawny;
    float    fixedspawnz;
    float    fixedspawnrot;
    csString fixedspawnsector;

    /// Spawn ranges for the current rule
    BinaryRBTree<SpawnRange> ranges;
    
    /// Rules for generating loot when this npc is killed.
    LootEntrySet *loot;

    int dead_remain_time;

public:

    /// Ctor clears all to zero
    SpawnRule();

    /// Setup variables
    void Initialize(int idval,
                    int minspawn,
                    int maxspawn,
                    float substodds,
                    int substplayer,
                    float x,float y,float z,float angle,
                    const char *sector,
                    LootEntrySet *loot_id,
                    int dead_time);

    int  GetID() { return id; };
    void SetID(int idval) { id = idval; };

    /// Get random value between min and max spawn time, in ticks/msecs.
    int GetRespawnDelay();

    /// Determine if substitute player should be spawned.  Returns either original or substitute.
    int CheckSubstitution(int originalplayer);

    /// Pick a spot for the entity to respawn
    void DetermineSpawnLoc(psCharacter *ch, csVector3& pos, float& angle, csString& sectorname);

    /// Add a spawn range to current rule
    void AddRange(SpawnRange *range);

    /// Get the Loot Rule set to generate loot
    LootEntrySet *GetLootRules() { return loot; }

    int GetDeadRemainTime() { return dead_remain_time; }

    bool operator==(SpawnRule& other) const
    {
        return id == other.id;
    };
    bool operator<(SpawnRule& other) const
    {
        return id < other.id;
    };
};

class psItemStats;
class psCharacter;
class LootRandomizer;
/**
 * This class holds one loot possibility for a killed npc.
 * The npc_spawn_rule has a ptr to an array of these.
 */
struct LootEntry
{
    psItemStats *item;
    float probability;
    int   min_money;
    int   max_money;
    bool  randomize;
};

/**
 * This class stores an array of LootEntry and calculates
 * required loot on a newly dead mob.
 */
class LootEntrySet
{
protected:
    int id;
    csArray<LootEntry*> entries;
    float total_prob;
    LootRandomizer *lootRandomizer;

    void CreateSingleLoot(psCharacter *chr);
    void CreateMultipleLoot(psCharacter *chr);

public:
    LootEntrySet(int idx) { id=idx; total_prob=0;}
    ~LootEntrySet();

    /// This adds another item to the entries array
    void AddLootEntry( LootEntry *entry );

    /** This calculates the loot for the character, given the 
     *  current set of loot entries, and adds them to the 
     *  character as lootable inventory.
     */
    void CreateLoot( psCharacter *chr );

    /**
     * Set the randomizer to use for random loot
     */
    void SetRandomizer( LootRandomizer *rnd ) { lootRandomizer = rnd; }
};

/**
 * This class holds one loot modifier
 * The lootRandomizer contions arrays of these
 */
struct LootModifier
{
    csString modifier_type;
    csString name;
    csString effect;
    csString prg_evt_equip;
    csString prg_evt_unequip;
    float    probability;
    csString stat_req_modifier;
    float    cost_modifier;
    csString mesh;
    csString not_usable_with;
};

class MathScript;
class MathScriptVar;
/**
 * This class stores an array of LootModifiers and randomizes
 * loot stats.
 */
class LootRandomizer
{
protected:
    csArray<LootModifier*> prefixes;
    csArray<LootModifier*> suffixes;
    csArray<LootModifier*> adjectives;

    // precalculated max values for probability. min is always 0
    float prefix_max;
    float adjective_max;
    float suffix_max;

public:
    LootRandomizer();
    ~LootRandomizer();

    /// This adds another item to the entries array
    void AddLootModifier( LootModifier *entry );

    /// This randomizes the current loot item stats and returns the new item stats
    psItemStats* RandomizeItem( psItemStats* itemstats, float cost );
    float CalcModifierCostCap(psCharacter *chr);

protected:
    MathScript* modifierCostCalc;

private:
    void AddModifier( LootModifier *oper1, LootModifier *oper2 );
    void ApplyModifier( psItemStats* loot, LootModifier* mod );
};

class PendingLootPrompt;

/**
 *  This class is periodically called by the engine to ensure that
 *  monsters (and other NPCs) are respawned appropriately.
 */
class SpawnManager : public MessageManager
{
protected:
    psDatabase             *database;
    EntityManager          *entitymanager;
    BinaryRBTree<SpawnRule> rules;
    csHash<LootEntrySet*>   looting;
    LootRandomizer         *lootRandomizer;

#ifdef PS_EGGHUNT
    csRandomGen            *randomgen;
#endif 

    void HandleLootItem(MsgEntry *me,Client *client);
    void HandleDeathEvent(MsgEntry *me);

public:

    SpawnManager(psDatabase *db,
                 EntityManager *celserv);
    virtual ~SpawnManager();

    /** Returns the loot randomizer.
     *
     * @return Returns a reference to the current loot randomizer.
     */
    LootRandomizer* GetLootRandomizer() { return lootRandomizer; }

    /**
     * Load all rules into memory for future use.
     */
    void PreloadDatabase();

    /**
     * Load all loot categories into the manager for use in spawn rules.
     */
    void PreloadLootRules();

#if 0
    /**
     * Load NPC Waypoint paths as named linear spawn ranges.
     */
    LoadWaypointsAsSpawnRanges(iDocumentNode *topNode);
#endif

    /**
     * Load all loot mosifiers into the manager for use in spawn rules.
     */
    void PreloadLootModifiers();

    /**
     * Load all ranges for a rule.
     */
    void LoadSpawnRanges(SpawnRule *rule);

    /** Load hunt location
     *  @param sectorinfo The sector to load in. NULL means all sectors.
     */
    void LoadHuntLocations(psSectorInfo *sectorinfo = 0);

    /**
     * Called at server startup to create all creatures currently marked as
     * "living" in the database.  This will restore the server to its last
     * known NPC population if it crashes.
     *
     *  @param sectorinfo The sector we want to repopulate.
     *                    If NULL then respawn all sectors.
     */
    void RepopulateLive(psSectorInfo *sectorinfo = 0);

    /**
     * This function receives inbound net messages from the client.
     */
    void HandleMessage(MsgEntry *me,Client *client);

    /**
     * This function is called periodically by the server and will respawn
     * NPCs as appropriate.
     */
    void Respawn(csVector3& where,float rot,csString& sector,int playerID);
    
    /// Adds all items to the world.
    /** Called at the server startup to add all the items to the game. 
     * @param sectorinfo The sector to respawn the items into. If NULL it will
     *                   respawn all items in all sectors.
     */
    void RepopulateItems(psSectorInfo *sectorinfo = 0);

    /**
     * Sets the NPC as dead, plays the death animation and triggers the
     * loot generator.  Also queues the removal of the corpse.
     */
    void KillNPC(gemObject *npc,int killer_cnum);

    /**
     * Kills the specified NPC, updates the database that he is "dead"
     * and queues him for respawn according to the spawn rules.
     */
    void RemoveNPC(gemObject *obj);

#ifdef PS_EGGHUNT
    /**
     * Special logic for respawning random Easter Eggs for the Crystal Hunt.
     */
    void QueueNextHiddenCrystalSpawn();
#endif


};


/**
 * When an NPC or mob is killed in the spawn manager, its respawn event
 * is immediately created and added to the schedule to be triggered at the
 * appropriate time.
 */
class psRespawnGameEvent : public psGameEvent
{
protected:
    SpawnManager *spawnmanager;
    int ticks;
    csVector3 where;
    float     rot;
    csString  sector;
    int       playerID;

public:
    psRespawnGameEvent(SpawnManager *mgr,
               int delayticks,
               csVector3& pos,
               float angle,
               csString& sector,
               int newplayer);

    virtual void Trigger();  // Abstract event processing function
};

/**
 * When an NPC or mob is killed in the spawn manager, its respawn event
 * is immediately created and added to the schedule to be triggered at the
 * appropriate time.
 */
class psDespawnGameEvent : public psGameEvent
{
protected:
    SpawnManager *spawnmanager;
    int ticks;
    PS_ID entity;

public:
    psDespawnGameEvent(SpawnManager *mgr,
                       int delayticks,
                       gemObject *obj);

    virtual void Trigger();  // Abstract event processing function
};

class psItemSpawnEvent : public psGameEvent
{
public:
    psItemSpawnEvent (psScheduledItem* item);
    virtual ~psItemSpawnEvent();
    
    void Trigger();  // Abstract event processing function    

protected:
    psScheduledItem* schedule;
};


#endif
