/* * gem.h - author Keith Fulton * * 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 #include #include #include //#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 struct iCelPlLayer; struct iCelEntityList; struct iCelEntity; class ProximityList; struct iPcLinearMovement; struct iPcNPCDialog; class psServerCharManager; class EntityManager; class gemObject; class PlayerGroup; class psDatabase; class psItem; 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 // required for psNPCCommandsMessage::PerceptionType #define MAX_SPELL_CATEGORIES 4 #define BUFF_INDICATOR "+" #define DEBUFF_INDICATOR "-" class gemActor; class gemNPC; class gemItem; /** * This class holds the refs to the core factories, etc in CEL. */ class GEMSupervisor : public MessageManager, public Singleton { protected: csHash entities_by_cel_id; csHash entities_by_ps_id; int count_players; public: iObjectRegistry* object_reg; csRef pl; psDatabase *database; NPCManager *npcmanager; public: GEMSupervisor(iObjectRegistry *objreg, iCelPlLayer *player, psDatabase *db); virtual ~GEMSupervisor(); csHash& 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 CreateEntity(gemObject *obj,uint32 id); void RemoveEntity(gemObject *which,uint32 gemID); void RemoveClientFromLootables(int cnum); csPtr 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 &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, public CS::WeakReferenced { protected: bool valid; csRef entity; csRef pcmesh; ProximityList *proxlist; csString name; static GEMSupervisor *cel; csVector3 pos; float yRot; iSector *sector; bool is_alive; csString factname; csString filename; uint32 gemID; csArray 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(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(); 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 *GetObjectsInRange( float range ); // Proxlist related functions ProximityList *GetProxList() { return proxlist; }; csArray& 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() { 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( 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: csWeakRef itemdata; csString itemType; public: gemItem(csWeakRef 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 { csWeakRef 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 group; csVector3 top, bottom, offset; csVector3 last_production_pos; csWeakRef 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 logging_chat_file; /// Chat history for last CHAT_HISTORY_SIZE number of messages. csPDelArray 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 invincible; ///< cannot be attacked bool visible; ///< is visible to clients ? bool viewAllObjects; ///< can view invisible objects? csString meshcache; csPDelArray dmgHistory; csArray onAttackScripts, onDamageScripts; int FindCategorySlot(const csString & categoryName); csArray active_spell_categories; float displaceY; iSector* portalSector; void ApplyStaminaCalculations(const csVector3& velocity, float times); /// 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 pcmove; gemActor(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; } 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 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 AddAttackerHistory(gemActor * attacker, float damage, float damageRate = 0.0f, csTicks duration = 0 ); void Kill(gemActor *attacker) { DoDamage(attacker, psChar->GetHP() ); } void Resurrect(); virtual bool UpdateDR(); 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 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; csWeakRef target; csWeakRef 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 lootable_clients; public: gemNPC(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,bool saypublic=false); 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) { this->target = target; }; virtual gemObject* GetTarget() { return this->target; }; virtual void SetOwner(gemObject* owner) { if ( owner ) { this->owner = owner; this->GetCharacterData()->SetOwnerID( owner->GetCharacterData()->GetCharacterID() ); } else this->owner = NULL; }; virtual gemObject* GetOwner() { return this->owner; }; virtual void SetPosition(const csVector3& pos, float angle, iSector* sector); }; class gemPet : public gemNPC { public: gemPet(psCharacter *chardata, const char* factname,const char* filename,iSector* room, const csVector3& pos,float rotangle,int clientnum,uint32 id) : gemNPC(chardata,factname,filename,room,pos,rotangle,clientnum,id) { this->persistanceLevel = "Temporary"; }; virtual const char* GetObjectType() { return "PET"; } void SetPersistanceLevel( const char *level ) { this->persistanceLevel = level; }; const char* SetPersistanceLevel( void ) { return persistanceLevel.GetData(); }; bool IsFamiliar( void ) { return this->persistanceLevel.CompareNoCase( "Permanent" ); }; private: csString persistanceLevel; }; /** * 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: csWeakRef 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; 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.IsValid()) { dependency->UnregisterCallback(this); dependency = NULL; } } virtual void DeleteObjectCallback(iDeleteNotificationObject * object) { SetValid(false); // Prevent the Trigger from beeing called. if (dependency.IsValid()) { dependency->UnregisterCallback(this); dependency = NULL; } } }; class psResurrectEvent : public psGameEvent // psGEMEvent { protected: csWeakRef who; public: psResurrectEvent(csTicks ticks,int offsetticks,gemActor *actor) : psGameEvent(ticks,offsetticks,"psResurrectEvent") { who = actor; } void Trigger() { if (who.IsValid()) { gemActor *actor = dynamic_cast ((gemObject *) who); actor->Resurrect(); } } }; #endif