/*
 * gem.h - author Keith Fulton <keith@paqrat.com>
 *
 * Copyright (C) 2003 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.
 *
 * This is the cel access class for PS.
 */

#ifndef __GEM_H__
#define __GEM_H__

#include <iutil/vfs.h>
#include <csutil/csstring.h>
#include <csutil/hash.h>

//#include "engine/celbase.h"
#include "bulkobjects/pscharacter.h"
//#include "playergroup.h"
#include "util/gameevent.h"
#include "util/consoleout.h"
#include "msgmanager.h"
//#include "groupmanager.h"

//#include <physicallayer/entity.h>

struct iCelPlLayer;
struct iCelEntityList;
struct iCelEntity;
class ProximityList;
struct iPcLinearMovement;
struct iPcNPCDialog;
class psServerCharManager;
class EntityManager;
class gemObject;
class PlayerGroup;
class psDatabase;
class psItem;
struct psItemSafe;
class csMatrix3;
struct iPcCharacterData;
class NPCManager;
class psGlyphList;
class FactionSet;
class ProgressionManager;
class psNPCDialog;
class iDeleteObjectCallback;
class psAllEntityPosMessage;
class psActionLocation;
class MathScript;
class gemItem;

#include <net/npcmessages.h>  // required for psNPCCommandsMessage::PerceptionType

#define MAX_SPELL_CATEGORIES    4
#define BUFF_INDICATOR          "+"
#define DEBUFF_INDICATOR        "-"


/**
* This class holds a shared reference to an object.
* Any other class needing to hold a ptr to a gem Object or Actor
* for >1 function should use a csRef of this and check for NULL
* before use.
*/
struct gemObjectSafe : public csRefCount
{
    gemObject *gemobj;

    gemObjectSafe()                { gemobj = NULL; }
    gemObjectSafe(gemObject *init) { gemobj = init; }
};


class gemActor;
class gemNPC;
class gemItem;

/**
* This class holds the refs to the core factories, etc in CEL.
*/
class GEMSupervisor : public MessageManager
{
protected:
    csHash<gemObject *> entities_by_cel_id;
    csHash<gemObject *> entities_by_ps_id;
    int       count_players;

public:
    iObjectRegistry*        object_reg;
    csRef<iCelPlLayer>      pl;
    psDatabase             *database;
    NPCManager             *npcmanager;

public:

    GEMSupervisor(iObjectRegistry *objreg,
        iCelPlLayer *player,
        psDatabase *db);

    virtual ~GEMSupervisor();

    csHash<gemObject *>& GetAllGEMS() { return entities_by_cel_id; }

    // Search functions
    PS_ID FindItemID(psItem *item);
    gemObject *FindObject(PS_ID cel_id);
    gemObject *FindObject(const csString& name);
    gemObject *GetObjectFromEntityList(iCelEntityList *list,size_t i);

    gemActor *FindPlayerEntity(int player_id);
    gemNPC   *FindNPCEntity(int npc_id);
    gemItem  *FindItemEntity(int item_id);

    csPtr<iCelEntity> CreateEntity(gemObject *obj,uint32 id);
    void RemoveEntity(gemObject *which,uint32 gemID);
    void RemoveClientFromLootables(int cnum);
    
    csPtr<iCelEntity> CreateProxActorList(uint32_t clientnum,iCelEntity *all_actors);

    void UpdateAllDR();
    void UpdateAllStats();

    void GetAllEntityPos(psAllEntityPosMessage& msg);
    int  CountManagedNPCs(int superclientID);
    void FillNPCList(MsgEntry *msg,int superclientID);
    void StopAllNPCs(int superclientID);

    /** Gets a list of all the 'live' entities that this player has ownership of.
      * This can be things like items in containers or work items.
      *
      * @param playerID The character owner ID we are looking for.
      * @param list The populated list of items that are active in game.
      */
    void GetPlayerObjects(unsigned int playerID, csArray<gemObject*> &list);

    /** Teleport a player to a location.
     *
     *  @param object The player to move
     *  @param x,y,z  Location to move to
     *  @param rot    The rotation to use.
     *  @param sector The sector name to move to.
     */
    void Teleport( gemObject* object, float x, float y, float z, float rot, const char* sectorname );

    void HandleMessage(MsgEntry *me,Client *client);
};

class iDeleteNotificationObject
{
public:
    virtual void RegisterCallback(iDeleteObjectCallback * receiver) = 0;
    virtual void UnregisterCallback(iDeleteObjectCallback * receiver) = 0;
};

/**
 * This class generically allows objects to be notified
 * when a gemObject is removed.  psGEMEvent uses
 * this heavily to make sure that timed events for an
 * object are not run when obsolete, but other classes
 * may use this too as appropriate.
 */
class iDeleteObjectCallback
{
public:
    virtual void DeleteObjectCallback(iDeleteNotificationObject * object)=0;
};


class gemActor;
class gemNPC;
class ClientConnectionSet;
class PublishVector;
struct iPcMesh;
struct iMeshWrapper;

/**
* A gemObject is any solid, graphical object visible in PS with normal physics
* and normal collision detection.
*/
class gemObject : public iDeleteNotificationObject
{
protected:
    bool valid;
    csRef<gemObjectSafe> self_reference;
    csRef<iCelEntity> entity;
    csRef<iPcMesh> pcmesh;
    ProximityList *proxlist;
    csString name;
    static GEMSupervisor *cel;
    csVector3 pos;
    float yRot;
    iSector *sector;
    bool is_alive;
    csString factname;
    csString filename;
    uint32 gemID;

    csArray<iDeleteObjectCallback*> receivers;

    float prox_distance_desired;
    float prox_distance_current;

    bool InitProximityList(float radius,int clientnum);

    bool InitMesh(const char *name,const char *factname,const char *filename,
        const csVector3& pos,const float rotangle,iSector* room, const char *action);

    float Matrix2YRot(const csMatrix3& mat);
    float GetAngle(float x, float y);

public:
    gemObject(GEMSupervisor *cel,const char* name, const char* factname,const char* filename,iSector* room,
        const csVector3& pos,float rotangle,int clientnum,uint32 id);

    /// This ctor is only for use in making keys for the BinaryTree
    gemObject(const char *name);

    virtual ~gemObject();

    gemObjectSafe *GetSafeReference()
    {
        /* CPrintf(CON_DEBUG,"Someone got a ref to %s refcount %d\n", name.GetData(), self_reference->GetRefCount()  ); */
        return self_reference;
    }

    uint32 GetGemID() { return gemID; }

    /// Called when a client disconnects
    virtual void Disconnect();

    virtual bool IsValid(void) { return (entity!=NULL); }

    /// Returns whether the object is alive.
    bool IsAlive() const { return is_alive; }
    void SetAlive(bool flag);

    int GetClientID();

    virtual const char* GetObjectType() { return "Object"; }
    iCelEntity *GetEntity() { return entity; }

    virtual psItem *GetItem() { return NULL; }
    virtual gemActor* GetActorPtr() { return NULL; }
    virtual gemNPC* GetNPCPtr()  { return NULL; }
    virtual psCharacter *GetCharacterData() { return NULL; }
    virtual Client* GetClient() { return NULL; }

    const char *GetName();
    void SetName(const char* n);

    void RegisterCallback(iDeleteObjectCallback * receiver) { receivers.Push(receiver); }
    void UnregisterCallback(iDeleteObjectCallback * receiver) { receivers.Delete(receiver); }

    // Mesh related functions
    iMeshWrapper *GetMeshWrapper();
    void Move(const csVector3& pos,float rotangle,iSector* room);
    bool IsNear(gemObject *obj,float radius);
    void GetPosition(csVector3& pos, float& yrot,iSector*& sector);
    void GetPosition(csVector3& pos, iSector*& sector);
    float GetAngle();
    iSector* GetSector();
    int FindAnimIndex(const char *name);

    csArray<gemObject*> *GetObjectsInRange( float range );

    // Proxlist related functions
    ProximityList *GetProxList() { return proxlist; };
    csArray<PublishDestination>& GetMulticastClients();

    /** Generates proxlist if needed (or forced)
      * Then removes entities of nearby objects at clients, if needed */
    void UpdateProxList( bool force = false);
    void RemoveFromAllProx();

    float RangeTo(gemObject *obj, bool ignoreY = false);

    virtual bool IsUpdateReq (csVector3 const &pos,csVector3 const &oldPos)
        { return (pos-oldPos).SquaredNorm() >= DEF_UPDATE_DIST*DEF_UPDATE_DIST; }

    /** This value indicates the range that this entity would become visible
     *  to other entities if no other modifiers were taken into consideration. */
    virtual float GetBaseAdvertiseRange() { return DEF_PROX_DIST; };

    virtual void SendBehaviorMessage(const csString & str, gemObject *obj);
    virtual csString GetDefaultBehavior(const csString & dfltBehaviors);

    // Networking functions
    virtual void Broadcast(int clientnum, bool control = false );
    virtual void Send( int clientnum, bool control /*=false */, bool include_superclients) {}
    virtual void SendGroupMessage(MsgEntry *me) { };

    // Overridden functions in child classes
    virtual PSCHARACTER_MODE GetMode() { return PSCHARACTER_MODE_UNKNOWN; }
    virtual void SetMode(PSCHARACTER_MODE mode) { }
    virtual int GetPlayerID() { return 0; }
    virtual int GetGuildID() { return 0; }
    virtual psGuildInfo* GetGuild() { return 0; }
    virtual bool UpdateDR(csTicks ticks) { return false; }
    virtual void BroadcastTargetStatDR(ClientConnectionSet *clients) { }
    virtual void SendTargetStatDR(Client *client) { }
    virtual psNPCDialog *GetNPCDialogPtr() { return 0; }
    virtual void GetLastSuperclientPos(csVector3& pos) { }
    virtual void SetLastSuperclientPos(csVector3& pos) { }
    virtual void AddLootableClient(int cnum) { }
    virtual void RemoveLootableClient(int cnum) { }
    virtual bool IsLootableClient(int cnum) { return false; }
    virtual Client *GetRandomLootClient(int range) { return NULL; }
    virtual int  GetSuperclientID() { return 0; }
    virtual void SetSuperclientID(int id) { }

    virtual bool GetVisibility() { return true; }
    virtual bool SeesObject(gemObject * object, float range) { return false; }
};

/*
* Any PS Object with which a player may have interaction (i.e. clickable).
*/
class gemActiveObject : public gemObject
{
public:
    gemActiveObject( const char *name );
    gemActiveObject(GEMSupervisor *cel,
                    const char* name,
                    const char* factname,
                    const char* filename,
                    iSector* room,
                    const csVector3& pos,
                    float rotangle,
                    int clientnum,uint32 id);

    virtual const char* GetObjectType() { return "Active object"; }

    virtual void Broadcast(int clientnum, bool control = false );
    virtual void Send( int clientnum, bool control=false ) { }
    
    virtual void SendBehaviorMessage(const csString & str, gemObject *obj);
    virtual csString GetDefaultBehavior(const csString & dfltBehaviors);

    //@@@ should probably add tests for other actions (usable? examinable?)
    // default "interaction" objects are not pick-uppable and cannot be locked
    virtual bool IsPickable() { return false; }
    virtual bool IsLockable() { return false; }
    virtual bool IsContainer() { return false; }
};

class gemItem : public gemActiveObject
{
private:
    csRef<psItemSafe> itemdata;
    csString itemType;

public:
    gemItem(GEMSupervisor *cel,
        psItemSafe *item,
        const char* factname,
        const char* filename,
        iSector* room,
        const csVector3& pos,
        float rotangle,
        int clientnum,uint32 id);

    virtual const char* GetObjectType() { return itemType.GetData(); }
    virtual psItem *GetItem();

    virtual float GetBaseAdvertiseRange();

    virtual void Broadcast(int clientnum, bool control = false );
    virtual void Send( int clientnum, bool control=false, bool super_clients = false );

    virtual void SetPosition(const csVector3& pos,float angle, iSector* sector);

    virtual bool IsPickable();
    virtual bool IsLockable();
    virtual bool IsContainer();

    virtual bool GetVisibility();
};

class gemActionLocation : public gemActiveObject
{
private:
    psActionLocation *action;
    bool visible;

public:
    gemActionLocation( GEMSupervisor *cel, psActionLocation *action, iSector *isec, int clientnum, uint32 id );

    virtual const char* GetObjectType() { return "ActionLocation"; }
    virtual psActionLocation *GetAction() { return action; }
    
    virtual float GetBaseAdvertiseRange();
    virtual bool SeesObject(gemObject * object, float range);

    virtual void Broadcast(int clientnum, bool control = false );
    virtual void Send( int clientnum, bool control = false, bool super_clients = false );

    virtual bool GetVisibility() { return visible; };
    virtual void SetVisibility(bool vis) { visible = vis; };
};

/*
    Struct for damage history
*/
struct DamageHistory
{
    csRef<gemObjectSafe> attacker_ref;
    float damage;
    float damageRate;
    int hp;
    unsigned int timestamp;
};

/*
* Any semi-autonomous object, either a player or an NPC.
*/
class gemActor : public gemObject
{
protected:
    psCharacter *psChar;
    FactionSet *factions;
    int playerID;
    csRef<PlayerGroup> group;

    csVector3 top, bottom, offset;
    csVector3 last_production_pos;

    csRef<SafeClientRef> clientRef;

    uint8_t DRcounter;  /// increments in loop to prevent out of order packet overwrites of better data
    csTicks lastDR;
    bool moving;

    /** Production Start Pos is used to record the place where people started digging. */
    csVector3 productionStartPos;

    csVector3 last_sent_superclient_pos;

    struct DRstate
    {
        csVector3 pos;
        iSector* sector;
        float yrot;
        float vel_y;
    } valid_location;
    DRstate newvalid_location;
    DRstate last_location;

    // To be used for the /report command.
    // NumReports says how many /report commands that haven't expired yet were applied on our object.
    // Each /report command increments this variable. After some time, it is decremented back.
    int numReports;
    csRef<iFile> logging_chat_file;
    /// Chat history for last CHAT_HISTORY_SIZE number of messages.
    csPDelArray<csString> chatHistory;
    int reportTargetId;

    bool InitLinMove(const csVector3& pos,float angle, iSector* sector);
    bool InitCharData(Client* c);

    /// What commands the actor can access. Normally only for a player controlled object.
    int securityLevel;

    bool isFalling;         ///< is this object falling down ?
    float fallBeginning;    ///< y-coordinate of fall beginning

    bool visible;           ///< is visible to clients ?
    bool viewAllObjects;    ///< can view invisible objects?

    csString meshcache;

    csArray<DamageHistory> dmgHistory;
    csArray<csString> onAttackScripts, onDamageScripts;

    int FindCategorySlot(const csString & categoryName);
    csArray<csString> active_spell_categories;

    float displaceY;
    iSector* portalSector;

    void ApplyStaminaCalculations(const csVector3& velocity, float times);

    bool invincible;        // cannot be attacked

    /// Set initial attributes for GMs
    void SetGMDefaults();

    uint8_t movementMode; ///< Actor movement mode from DB table movement_modes
    bool isAllowedToMove; ///< Is a movement lockout in effect?
    bool atRest;          ///< Is this character stationary or moving?

public:
    csRef<iPcLinearMovement> pcmove;

    gemActor(GEMSupervisor *cel,psCharacter *chardata, const char* factname,const char* filename,iSector* room,
        const csVector3& pos,float rotangle,int clientnum,uint32 id);

    virtual ~gemActor();

    virtual const char* GetObjectType() { return "Actor"; }
    virtual gemActor* GetActorPtr() { return this; }
    virtual psCharacter *GetCharacterData() { return psChar; }
    virtual Client* GetClient();
    
    virtual int GetPlayerID() { return playerID; }
    void SetFamiliar(gemActor *familiar)
    { 
        if (clientRef && clientRef->clientptr)
            clientRef->clientptr->SetFamiliar(familiar);
        else
            Error2("Actor %s attempted to SetFamiliar on null client.\n",GetName() );
    }

    bool SetupCharData();

    void SetTextureParts(const char *parts);
    void SetEquipment(const char *equip);
    
    void SetMode(PSCHARACTER_MODE mode) { psChar->SetMode(mode); }
    PSCHARACTER_MODE GetMode() { return psChar->GetMode(); }
    bool IsAllowedToMove() { return isAllowedToMove; }  ///< Covers sitting, death, and out-of-stamina
    void SetAllowedToMove(bool newvalue);

    void SetMovementMode(uint8_t mode);

    void SetSecurityLevel(int level) { securityLevel = level; }

    // Last Production Pos is used to require people to move around while /digging
    void SetLastProductionPos(csVector3& pos) { last_production_pos = pos; }
    void GetLastProductionPos(csVector3& pos) { pos = last_production_pos; }

    /** Returns the place where the player last started digging.
      * @return The location where the player last started digging. */
    const csVector3& GetProductionStartPos(void) const { return productionStartPos; }
    /** Sets the place where the player started digging.
      * @param pos The location where the player started digging. */
    void SetProductionStartPos(const csVector3& pos) { productionStartPos = pos; }

    // To be used for the /report command.
    void AddChatReport(gemActor *target);
    void RemoveChatReport();
    bool IsLoggingChat() const { return numReports > 0; }
    int GetReportTargetId() const { return reportTargetId; }

    /**
     * @brief Adds the chat message to the history and optionally to the log file
     * @param[in] who The name of the character who sent this message
     * @param[in] msg The chat message
     * @return Returns true if the message was written to the log file
     */
    bool LogMessage(const char *who, const psChatMessage &msg);

    void UpdateStats();
    void ProcessStamina(const csVector3& velocity, bool force=false);
    virtual float DrainMana(float adjust, bool absolute);

    void SetPosition(const csVector3& pos,float angle, iSector* sector);

    void UpdateValidLocation(const csVector3& pos, float vel_y, float yrot, iSector* sector, bool force = false);

    bool SetDRData(psDRMessage& drmsg);
    void MulticastDRUpdate(MsgEntry *resend = NULL);

    float GetRelativeFaction(gemActor *speaker);
    FactionSet *GetFactions() { return factions; }

    csPtr<PlayerGroup> GetGroup();
    void SetGroup(PlayerGroup *group);
    bool InGroup() const;
    bool IsGroupedWith(gemActor *other) const;
    int GetGroupID();
    void RemoveFromGroup();

    const char *GetGuildName();
    psGuildInfo *GetGuild() { return psChar->GetGuild(); }
    psGuildLevel *GetGuildLevel() { return psChar->GetGuildLevel(); }

    void DoDamage(gemActor *attacker, float damage, float damageRate = 0.0f, csTicks duration=0);
    void Kill(gemActor *attacker) { DoDamage(attacker, psChar->GetHP() ); }
    void Resurrect();

    virtual bool UpdateDR(csTicks ticks);
    virtual void GetLastSuperclientPos(csVector3& pos) { pos = last_sent_superclient_pos; }
    virtual void SetLastSuperclientPos(csVector3& pos) { last_sent_superclient_pos = pos; }

    virtual void BroadcastTargetStatDR(ClientConnectionSet *clients);
    virtual void SendTargetStatDR(Client *client);
    virtual void SendGroupStats();

    void SetAction(const char *anim);

    virtual void Broadcast(int clientnum, bool control = false );
    virtual void Send( int clientnum, bool control /*=false */, bool include_superclients );
    virtual void SendGroupMessage(MsgEntry *me);

    /// Used by chat manager to identify an npc you are near if you talk to one
    gemObject *FindNearbyActorName(const char *name);

    virtual void SendBehaviorMessage(const csString & str, gemObject *obj);
    virtual csString GetDefaultBehavior(const csString & dfltBehaviors);

    /** Called when the object began falling - 'fallBeginning' tells where the fall started
        displaceY is the displacement that needs to be added to due to passing through
        warping portals
        portalSector is the final sector of the player after passing through the warping
        portals.
    */
    void FallBegan(float fallBeginning, float displaceY, iSector* portalSector);
    /** Called when the object has fallen down - sets its falling status to false */
    float FallEnded(float height);
    /** Checks if the object is falling */
    bool IsFalling() { return isFalling; }

    virtual bool GetVisibility() { return visible; }
    virtual void SetVisibility(bool visible);
    virtual bool SeesObject(gemObject * object, float range);

    virtual bool GetInvincibility() { return invincible; }
    virtual void SetInvincibility(bool invincible);
    
    /// Flag to determine of this player can see all objects
    bool GetViewAllObjects() { return viewAllObjects; }
    void SetViewAllObjects(bool v);

    void StopMoving(bool worldVel = false);

    /// Moves player to his spawn position
    bool MoveToSpawnPos();
    bool GetSpawnPos(csVector3& pos, float& yrot, iSector*& sector);

    /// Restores actor to his last valid position
    bool MoveToValidPos();
    void GetValidPos(csVector3& pos, float& yrot, iSector*& sector);

    /// Get the last reported location this actor was at
    void GetLastLocation(csVector3& pos, float& vel_y, float& yrot, iSector*& sector);
    void MoveToLastPos(); 

    DamageHistory* GetDamageHistory(int pos) { return &dmgHistory.Get(pos); }
    size_t GetDamageHistoryCount() { return dmgHistory.Length(); }
    void ClearDamageHistory() { dmgHistory.Empty(); }

    int  AttachAttackScript(const csString & scriptName);
    void DetachAttackScript(int scriptID);
    int  AttachDamageScript(const csString & scriptName);
    void DetachDamageScript(int scriptID);
    void InvokeAttackScripts(gemActor *target);
    void InvokeDamageScripts(gemActor *attacker);

    int  AddSpellCategory(const csString & categoryName);
    void RemoveSpellCategory(const csString & categoryName);
    bool IsSpellCategoryActive(const csString & categoryName);
    csArray<csString> GetActiveSpellCategories() { return active_spell_categories;};

    /** These flags are for GM/debug abilities */
    bool nevertired;        // infinite stamina
    bool safefall;          // no fall damage
    bool questtester;       // no quest lockouts

    bool SetMesh(const char* meshname);
    bool ResetMesh() { return SetMesh(meshcache); }
    
    bool GetFiniteInventory() { return GetCharacterData()->Inventory().GetDoRestrictions(); }
    void SetFiniteInventory(bool v) { GetCharacterData()->Inventory().SetDoRestrictions(v); }
};

class gemNPC : public gemActor
{
protected:
    psNPCDialog *npcdialog;
    int superClientID;
    csRef<gemObjectSafe>  target;
    csRef<gemObjectSafe>  owner;

    csTicks nextVeryShortRangeAvail; /// When can npc respond to very short range prox trigger again
    csTicks nextShortRangeAvail;     /// When can npc respond to short range prox trigger again
    csTicks nextLongRangeAvail;      /// When can npc respond to long range prox trigger again

    /// Array of client id's allowed to loot this char
    csArray<int> lootable_clients;

public:
    gemNPC(GEMSupervisor *cel,psCharacter *chardata, const char* factname,const char* filename,iSector* room,
        const csVector3& pos,float rotangle,int clientnum,uint32 id);

    virtual ~gemNPC();

    virtual const char* GetObjectType() { return "NPC"; }
    virtual gemNPC* GetNPCPtr() { return this; }
    virtual psNPCDialog *GetNPCDialogPtr() { return npcdialog; }
    virtual Client* GetClient() { return NULL; }

    virtual int GetSuperclientID() { return superClientID; }
    virtual void SetSuperclientID(int id) { superClientID=id; }

    void SetupDialog(int NPCID);
    void ReactToPlayerApproach(psNPCCommandsMessage::PerceptionType type,gemActor *player);

    virtual void AddLootableClient(int cnum);
    virtual void RemoveLootableClient(int cnum);
    bool IsLootableClient(int cnum);
    Client *GetRandomLootClient(int range);
    bool AdjustMoneyLootClients(const psMoney &m);

    /// Used to allow a NPC to communicate to its environment
    /// void NPCTalk(const csString & text);

    void Say(const char *strsay,Client *who);
    virtual void Send( int clientnum, bool control /*=false */, bool include_superclients );
    virtual void Broadcast(int clientnum, bool control = false );

    virtual void SendBehaviorMessage(const csString & str, gemObject *obj);
    virtual csString GetDefaultBehavior(const csString & dfltBehaviors);

    virtual void SetTarget(gemObject* target)
    {
        if (target)
            this->target = target->GetSafeReference();
        else
            this->target = NULL;
    };
    virtual gemObject* GetTarget()
    {
        if (this->target)
            return this->target->gemobj;
        else
            return NULL;
    };

    virtual void SetOwner(gemObject* owner)
    {
        if ( owner )
        {
            this->owner = owner->GetSafeReference();
            this->GetCharacterData()->SetOwnerID( owner->GetCharacterData()->GetCharacterID() );
        }
        else
            this->owner = NULL;
    };
    virtual gemObject* GetOwner()
    {
        if(this->owner)
            return this->owner->gemobj;
        else
            return NULL;
    };

    virtual void SetPosition(const csVector3& pos, float angle, iSector* sector);
};


/**
 * This class automatically implements timed events which depend
 * on the existence and validity of a gemObject of any kind.  It
 * will make sure this event is cancelled and skipped when the
 * gemObject is deleted before the event is fired.
 */
class psGEMEvent : public psGameEvent, public iDeleteObjectCallback
{
public:
    csRef<gemObjectSafe> dependency;

    psGEMEvent(csTicks ticks,int offsetticks,gemObject *depends, const char* newType)
        : psGameEvent(ticks,offsetticks,newType)
    {
        dependency = NULL;

        // Register for disconnect events
        if ( depends )
        {
            dependency = depends->GetSafeReference();
            depends->RegisterCallback(this);
        }
    }

    virtual ~psGEMEvent()
    {
        // If DeleteObjectCallback() has not been called normal operation
        // this object have to unregister to prevent the
        // object from calling DeleteObjectCallback() later when destroyed.
        if (dependency && dependency->gemobj)
        {
            dependency->gemobj->UnregisterCallback(this);
            dependency = NULL;
        }
    }

    virtual void DeleteObjectCallback(iDeleteNotificationObject * object)
    {
        SetValid(false); // Prevent the Trigger from beeing called.
        
        if (dependency && dependency->gemobj)
        {
            dependency->gemobj->UnregisterCallback(this);
            dependency = NULL;
        }
    }
};

class psResurrectEvent : public psGameEvent // psGEMEvent
{
protected:
    csRef<gemObjectSafe> who;

public:
    psResurrectEvent(csTicks ticks,int offsetticks,gemActor *actor)
        : psGameEvent(ticks,offsetticks,"psResurrectEvent")
    {
        who = actor->GetSafeReference();
    }

    void Trigger()
    {
        if (who->gemobj)
        {
            gemActor *actor = dynamic_cast<gemActor*> (who->gemobj);
            actor->Resurrect();
        }
    }
};


#endif
