EQ2EMu/EQ2/source/WorldServer/client.h
image 41716c32fa Group fixes/support - requires characters table update
Fix #66

- Fixed crashing of the world server when a client in a group zones
- Tracking of players group_id in the characters table (for both sanity and tracking cross zone)
- Clients zoning now properly 'rejoin' their group
- Mutex locks added to group code, this makes groups more reliably work across different zones as they run on different threads (some behavior for example was some players would not see chat and others would)
2020-08-21 23:38:15 -04:00

563 lines
19 KiB
C++

/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator 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, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator 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 EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef CLIENT_H
#define CLIENT_H
#include "../common/EQStream.h"
#include <list>
#include "../common/timer.h"
#include "Items/Items.h"
#include "zoneserver.h"
#include "Player.h"
#include "Quests.h"
using namespace std;
#define CLIENT_TIMEOUT 60000
struct TransportDestination;
struct ConversationOption;
#define MAIL_SEND_RESULT_SUCCESS 0
#define MAIL_SEND_RESULT_UNKNOWN_PLAYER 1
#define MAIL_SEND_RESULT_CANNOT_SEND_TO_PLAYER 2
#define MAIL_SEND_RESULT_GIFT_WRONG_SERVER 3 /* Cannot send gifts across worlds */
#define MAIL_SEND_RESULT_CANNOT_SEND_TO_SELF 4
#define MAIL_SEND_RESULT_MAILBOX_FULL 5
#define MAIL_SEND_RESULT_NOT_ENOUGH_COIN 6
#define MAIL_SEND_RESULT_ITEM_IN_BAG 7 /* Cannot send non-empty bags as gifts */
#define MAIL_SEND_RESULT_NOT_IN_GUILD 8
#define MAIL_SEND_RESULT_GUILD_ACCESS_DENIED 9
#define MAIL_SEND_RESULT_GIFTS_TO_GUILD 10 /* Cannot send gifts to entire guild */
#define MAIL_SEND_RESULT_EMPTY_TO_LIST 11 /* Empty recipient list */
#define MAIL_SEND_RESULT_TRIAL_PLAYERS 12 /* Cannot send mail to trial players */
#define MAIL_SEND_RESULT_MAIL_WRONG_SERVER 13 /* Cannot send mail across worlds */
#define MAIL_SEND_RESULT_UNKNOWN_ERROR 14
#define MAIL_TYPE_REGULAR 0
#define MAIL_TYPE_SPAM 1
#define MAIL_TYPE_GM 2
struct QueuedQuest{
Quest* quest;
int32 step;
bool display_quest_helper;
};
struct BuyBackItem{
int32 item_id;
int32 unique_id;
int16 quantity;
int32 price;
bool save_needed;
};
struct MacroData{
string name;
string text;
int16 icon;
};
struct Mail {
int32 mail_id;
int32 player_to_id;
string player_from;
string subject;
string mail_body;
int8 already_read;
int8 mail_type;
int32 coin_copper;
int32 coin_silver;
int32 coin_gold;
int32 coin_plat;
int16 stack;
int32 postage_cost;
int32 attachment_cost;
int32 char_item_id;
int32 time_sent;
int32 expire_time;
int8 save_needed;
};
struct MailWindow {
int32 coin_copper;
int32 coin_silver;
int32 coin_gold;
int32 coin_plat;
int32 char_item_id;
};
struct PendingGuildInvite {
Guild* guild;
Player* invited_by;
};
struct PendingResurrection {
Spawn* caster;
Timer* expire_timer;
string spell_name;
string heal_name;
bool active;
float hp_perc;
float mp_perc;
float range;
int8 crit_mod;
bool no_calcs;
int32 subspell;
bool crit;
bool should_delete;
int32 spell_visual;
};
#define PAPERDOLL_TYPE_FULL 0
#define PAPERDOLL_TYPE_HEAD 1
struct IncomingPaperdollImage {
uchar* image_bytes;
int32 current_size_bytes;
int8 image_num_packets;
int8 last_received_packet_index;
int8 image_type;
};
class Client {
public:
Client(EQStream* ieqs);
~Client();
bool Process(bool zone_process = false);
void Disconnect(bool send_disconnect = true);
void SetConnected(bool val){ connected = val; }
bool IsConnected(){ return connected; }
bool IsReadyForSpawns(){ return ready_for_spawns; }
bool IsReadyForUpdates() { return ready_for_updates; }
bool IsZoning(){ return client_zoning; }
void SetReadyForUpdates();
void SetReadyForSpawns(bool val);
void QueuePacket(EQ2Packet* app, bool attemptedCombine=false);
void SendLoginInfo();
void SimpleMessage(int8 color, const char* message);
void Message(int8 type, const char* message, ...);
void SendSpellUpdate(Spell* spell);
void Zone(ZoneServer* new_zone, bool set_coords = true);
void Zone(const char* new_zone, bool set_coords = true);
void Zone(int32 zoneid, bool set_coords = true);
void Zone(int32 instanceid, bool set_coords = true, bool byInstanceID=false);
void SendZoneInfo();
void SendZoneSpawns();
void HandleVerbRequest(EQApplicationPacket* app);
void SendCharInfo();
void SendLoginDeniedBadVersion();
void SendCharPOVGhost();
void SendPlayerDeathWindow();
float DistanceFrom(Client* client);
void SendDefaultGroupOptions();
bool HandleLootItem(Spawn* entity, int32 item_id);
bool HandleLootItem(Spawn* entity, Item* item);
void HandleLoot(EQApplicationPacket* app);
void HandleSkillInfoRequest(EQApplicationPacket* app);
void HandleExamineInfoRequest(EQApplicationPacket* app);
void HandleQuickbarUpdateRequest(EQApplicationPacket* app);
void SendPopupMessage(int8 unknown, const char* text, const char* type, float size, int8 red, int8 green, int8 blue);
void PopulateSkillMap();
void ChangeLevel(int16 old_level, int16 new_level);
void ChangeTSLevel(int16 old_level, int16 new_level);
bool Summon(const char* search_name);
bool TryZoneInstance(int32 zoneID, bool zone_coords_valid=false);
bool GotoSpawn(const char* search_name, bool forceTarget=false);
void DisplayDeadWindow();
void HandlePlayerRevive(int32 point_id);
void Bank(Spawn* banker, bool cancel = false);
void BankWithdrawal(int64 amount);
void BankDeposit(int64 amount);
Spawn* GetBanker();
void SetBanker(Spawn* in_banker);
bool AddItem(int32 item_id, int16 quantity = 0);
bool AddItem(Item* item);
bool AddItemToBank(int32 item_id, int16 quantity = 0);
bool AddItemToBank(Item* item);
bool RemoveItem(Item *item, int16 quantity);
void ProcessTeleport(Spawn* spawn, vector<TransportDestination*>* destinations, int32 transport_id = 0);
void ProcessTeleportLocation(EQApplicationPacket* app);
void UpdateCharacterInstances();
void SetLastSavedTimeStamp(int32 unixts) { last_saved_timestamp = unixts; }
int32 GetLastSavedTimeStamp() { return last_saved_timestamp; }
bool CheckZoneAccess(const char* zoneName);
ZoneServer* GetCurrentZone();
void SetCurrentZoneByInstanceID(int32 id, int32 zoneid);
//void SetCurrentZoneByInstanceID(instanceid, zoneid);
void SetCurrentZone(int32 id);
void SetCurrentZone(ZoneServer* zone) {
current_zone = zone;
player->SetZone(zone);
}
Player* GetPlayer(){ return player; }
EQStream* getConnection(){ return eqs; }
inline int32 GetIP() { return ip; }
inline int16 GetPort() { return port; }
inline int32 WaitingForBootup() { return pwaitingforbootup; }
inline int32 GetCharacterID() { return character_id; }
inline int32 GetAccountID() { return account_id; }
inline const char* GetAccountName() { return account_name; }
inline sint16 GetAdminStatus() { return admin_status; }
inline int16 GetVersion() { return version; }
void SetNameCRC(int32 val){ name_crc = val; }
int32 GetNameCRC(){ return name_crc; }
void SetVersion(int16 new_version){ version = new_version; }
void SetAccountID(int32 in_accountid) { account_id = in_accountid; }
void SetCharacterID(int32 in_characterid) { character_id = in_characterid; }
void SetAdminStatus(sint16 in_status) { admin_status = in_status; }
void DetermineCharacterUpdates ( );
void UpdateTimeStampFlag ( int8 flagType )
{
if(! (timestamp_flag & flagType ) )
timestamp_flag |= flagType;
}
int8 GetTimeStampFlag ( ) { return timestamp_flag; }
bool UpdateQuickbarNeeded();
void Save();
bool remove_from_list;
void CloseLoot();
void SendPendingLoot(int32 total_coins, Spawn* entity);
void Loot(int32 total_coins, vector<Item*>* items, Spawn* entity);
void Loot(Spawn* entity, bool attemptDisarm=true);
void OpenChest(Spawn* entity, bool attemptDisarm=true);
void CastGroupOrSelf(Entity* source, uint32 spellID, uint32 spellTier=1, float restrictiveRadius=0.0f);
void CheckPlayerQuestsKillUpdate(Spawn* spawn);
void CheckPlayerQuestsChatUpdate(Spawn* spawn);
void CheckPlayerQuestsItemUpdate(Item* item);
void CheckPlayerQuestsSpellUpdate(Spell* spell);
void CheckPlayerQuestsLocationUpdate();
void AddPendingQuest(Quest* quest);
void AcceptQuest(int32 id);
Quest* GetPendingQuest(int32 id);
void RemovePendingQuest(Quest* quest);
void SetPlayerQuest(Quest* quest, map<int32, int32>* progress);
void AddPlayerQuest(Quest* quest, bool call_accepted = true, bool send_packets = true);
void RemovePlayerQuest(int32 id, bool send_update = true, bool delete_quest = true);
void SendQuestJournal(bool all_quests = false, Client* client = 0);
void SendQuestUpdate(Quest* quest);
void SendQuestFailure(Quest* quest);
void SendQuestUpdateStep(Quest* quest, int32 step, bool display_quest_helper = true);
void SendQuestUpdateStepImmediately(Quest* quest, int32 step, bool display_quest_helper = true);
void DisplayQuestComplete(Quest* quest);
void DisplayRandomizeFeatures(int32 features);
void AcceptQuestReward(Quest* quest, int32 item_id);
Quest* GetPendingQuestAcceptance(int32 item_id);
Quest* GetActiveQuest(int32 quest_id);
void DisplayConversation(int32 conversation_id, int32 spawn_id, vector<ConversationOption>* conversations, const char* text, const char* mp3, int32 key1, int32 key2);
void DisplayConversation(Item* item, vector<ConversationOption>* conversations, const char* text, int8 type, const char* mp3 = 0, int32 key1 = 0, int32 key2 = 0);
void DisplayConversation(Entity* npc, int8 type, vector<ConversationOption>* conversations, const char* text, const char* mp3 = 0, int32 key1 = 0, int32 key2 = 0);
void CloseDialog(int32 conversation_id);
int32 GetConversationID(Spawn* spawn, Item* item);
void CombineSpawns(float radius, Spawn* spawn);
void AddCombineSpawn(Spawn* spawn);
void RemoveCombineSpawn(Spawn* spawn);
void SaveCombineSpawns(const char* name = 0);
Spawn* GetCombineSpawn();
bool ShouldTarget();
void TargetSpawn(Spawn* spawn);
void ReloadQuests();
int32 GetCurrentQuestID(){ return current_quest_id; }
void SetLuaDebugClient(bool val);
void SetMerchantTransaction(Spawn* spawn);
Spawn* GetMerchantTransaction();
void SetMailTransaction(Spawn* spawn);
Spawn* GetMailTransaction();
void PlaySound(const char* name);
void SendBuyMerchantList(bool sell = false);
void SendSellMerchantList(bool sell = false);
void SendBuyBackList(bool sell = false);
void SendRepairList();
void ShowLottoWindow();
void PlayLotto(int32 price, int32 ticket_item_id);
void SendGuildCreateWindow();
float CalculateBuyMultiplier(int32 merchant_id);
float CalculateSellMultiplier(int32 merchant_id);
void BuyItem(int32 item_id, int16 quantity);
void SellItem(int32 item_id, int16 quantity, int32 unique_id = 0);
void BuyBack(int32 item_id, int16 quantity);
void RepairItem(int32 item_id);
void RepairAllItems();
void AddBuyBack(int32 unique_id, int32 item_id, int16 quantity, int32 price, bool save_needed = true);
deque<BuyBackItem*>* GetBuyBacks();
vector<Item*>* GetRepairableItems();
void SendMailList();
void DisplayMailMessage(int32 mail_id);
void HandleSentMail(EQApplicationPacket* app);
void DeleteMail(int32 mail_id, bool from_database = false);
bool AddMailCoin(int32 copper, int32 silver = 0, int32 gold = 0, int32 plat = 0);
bool RemoveMailCoin(int32 copper, int32 silver = 0, int32 gold = 0, int32 plat = 0);
void TakeMailAttachments(int32 mail_id);
void CancelSendMail();
bool GateAllowed();
bool BindAllowed();
bool Bind();
bool Gate();
void SendChatRelationship(int8 type, const char* name);
void SendFriendList();
void SendIgnoreList();
void SendNewSpells(int8 class_id);
string GetCoinMessage(int32 total_coins);
void SetItemSearch(vector<Item*>* items);
vector<Item*>* GetSearchItems();
void SearchStore(int32 page);
void SetPlayer(Player* new_player){
player = new_player;
}
void AddPendingQuestReward(Quest* quest);
void AddPendingQuestUpdate(int32 quest_id, int32 step_id, int32 progress = 0xFFFFFFFF);
void ProcessQuestUpdates();
void AddWaypoint(const char* waypoint_name, int8 waypoint_category, int32 spawn_id);
void BeginWaypoint(const char* waypoint_name, float x, float y, float z);
void InspectPlayer(Player* player_to_inspect);
void SetPendingGuildInvite(Guild* guild, Player* invited_by = 0);
PendingGuildInvite* GetPendingGuildInvite() {return &pending_guild_invite;}
void ShowClaimWindow();
void ShowGuildSearchWindow();
void CheckQuestQueue();
void ShowDressingRoom(Item *item, sint32 crc);
void SendCollectionList();
bool SendCollectionsForItem(Item *item);
void HandleCollectionAddItem(int32 collection_id, Item *item);
void DisplayCollectionComplete(Collection *collection);
void HandInCollections();
void AcceptCollectionRewards(Collection *collection, int32 selectable_item_id = 0);
void SendRecipeList();
void SendTitleUpdate();
void SendUpdateTitles(sint16 prefix, sint16 suffix);
void SendLanguagesUpdate(int32 id);
void SendAchievementsList();
void SendAchievementUpdate(bool first_login = false);
///<summary>Send the pet options window to the client</summary>
///<param name="type">Type of pet, 1 = combat 0 = non combat</param>
void SendPetOptionsWindow(const char* pet_name, int8 type = 1);
void SendBiography();
bool IsCrafting();
void SetRecipeListSent(bool val) {m_recipeListSent = val; }
bool GetRecipeListSent() { return m_recipeListSent; }
void ShowRecipeBook();
PendingResurrection* GetCurrentRez();
void SendResurrectionWindow();
void AcceptResurrection();
Mutex m_resurrect;
Mutex* GetResurrectMutex();
void SetPendingLastName(string last_name);
void RemovePendingLastName();
string* GetPendingLastName();
void SendLastNameConfirmation();
void SetInitialSpawnsSent(bool val) { initial_spawns_sent = val; }
bool GetInitialSpawnsSent() { return initial_spawns_sent; }
void SendQuestJournalUpdate(Quest* quest);
void AddQuestTimer(int32 quest_id);
void RemoveQuestTimer(int32 quest_id);
void SetPendingFlightPath(int32 val) { pending_flight_path = val; }
int32 GetPendingFlightPath() { return pending_flight_path; }
void EndAutoMount();
bool GetOnAutoMount() { return on_auto_mount; }
bool IsCurrentTransmuteID(int32 trans_id);
void SetTransmuteID(int32 trans_id);
int32 GetTransmuteID();
enum ServerSpawnPlacementMode { DEFAULT, OPEN_HEADING, CLOSE_HEADING };
void SetSpawnPlacementMode(ServerSpawnPlacementMode mode) { spawnPlacementMode = mode; }
ServerSpawnPlacementMode GetSpawnPlacementMode() { return spawnPlacementMode; }
bool HandleNewLogin(int32 account_id, int32 access_code);
void SendSpawnChanges(set<Spawn*>& spawns);
void MakeSpawnChangePacket(map<int32, SpawnData> info_changes, map<int32, SpawnData> pos_changes, map<int32, SpawnData> vis_changes, int32 info_size, int32 pos_size, int32 vis_size);
bool IsZonedIn() { return connected_to_zone; }
void SendHailCommand(Spawn* target);
void SendDefaultCommand(Spawn* spawn, const char* command, float distance);
void SetTempPlacementSpawn(Spawn* tmp) { tempPlacementSpawn = tmp; }
Spawn* GetTempPlacementSpawn() { return tempPlacementSpawn; }
void SetPlacementUniqueItemID(int32 id) { placement_unique_item_id = id; }
int32 GetPlacementUniqueItemID() { return placement_unique_item_id; }
void SetHasOwnerOrEditAccess(bool val) { hasOwnerOrEditAccess = val; }
bool HasOwnerOrEditAccess() { return hasOwnerOrEditAccess; }
bool HandleHouseEntityCommands(Spawn* spawn, int32 spawnid, string command);
// find an appropriate spawn to use for the house object, save spawn location/entry data to DB
bool PopulateHouseSpawn(PacketStruct* place_object);
// finalize the spawn-in of the object in world, remove the item from player inventory, set the spawned in object item id (for future pickup)
bool PopulateHouseSpawnFinalize();
void SendMoveObjectMode(Spawn* spawn, uint8 placementMode, float unknown2_3=0.0f);
void SendFlightAutoMount(int32 path_id, int16 mount_id = 0, int8 mount_red_color = 0xFF, int8 mount_green_color = 0xFF, int8 mount_blue_color=0xFF);
void SendShowBook(Spawn* sender, string title, int8 num_pages, ...);
void SendShowBook(Spawn* sender, string title, vector<Item::BookPage*> pages);
void SetTemporaryTransportID(int32 id) { temporary_transport_id = id; }
int32 GetTemporaryTransportID() { return temporary_transport_id; }
void SetRejoinGroupID(int32 id) { rejoin_group_id = id; }
private:
void SavePlayerImages();
void SkillChanged(Skill* skill, int16 previous_value, int16 new_value);
void GiveQuestReward(Quest* quest);
void SetStepComplete(int32 quest_id, int32 step);
void AddStepProgress(int32 quest_id, int32 step, int32 progress);
map<int32, map<int32, int32> > quest_pending_updates;
vector<QueuedQuest*> quest_queue;
vector<Quest*> quest_pending_reward;
volatile bool quest_updates;
Mutex MQuestPendingUpdates;
Mutex MQuestQueue;
Mutex MDeletePlayer;
vector<Item*>* search_items;
Spawn* transport_spawn;
Mutex MBuyBack;
deque<BuyBackItem*> buy_back_items;
Spawn* merchant_transaction;
Spawn* mail_transaction;
Mutex MPendingQuestAccept;
vector<Quest*> pending_quest_accept;
bool lua_debug;
bool should_target;
Spawn* combine_spawn;
int8 num_active_failures;
int32 next_conversation_id;
map<int32, Spawn*> conversation_spawns;
map<int32, Item*> conversation_items;
map<int32, map<int8, string> > conversation_map;
int32 current_quest_id;
Spawn* banker;
map<int32, bool> sent_spell_details;
map<int32, bool> sent_item_details;
Player* player;
int16 version;
int8 timestamp_flag;
int32 ip;
int16 port;
int32 account_id;
int32 character_id;
sint16 admin_status; // -2 Banned, -1 Suspended, 0 User, etc.
char account_name[64];
char zone_name[64];
int32 zoneID;
int32 instanceID;
Timer* autobootup_timeout;
int32 pwaitingforbootup;
int32 last_update_time;
int32 last_saved_timestamp;
Timer* CLE_keepalive_timer;
Timer* connect;
Timer* camp_timer;
Timer* disconnect_timer;
bool connected;
bool ready_for_spawns;
bool ready_for_updates;
bool seencharsel;
bool connected_to_zone;
bool client_zoning;
bool firstlogin;
bool new_client_login;
Timer pos_update;
Timer quest_pos_timer;
Timer lua_debug_timer;
bool player_pos_changed;
bool HandlePacket(EQApplicationPacket *app);
EQStream* eqs;
bool quickbar_changed;
ZoneServer* current_zone;
int32 name_crc;
MailWindow mail_window;
PendingGuildInvite pending_guild_invite;
PendingResurrection current_rez;
string* pending_last_name;
IncomingPaperdollImage incoming_paperdoll;
int32 transmuteID;
bool m_recipeListSent;
bool initial_spawns_sent;
bool should_load_spells;
// int32 = quest id
vector<int32> quest_timers;
Mutex MQuestTimers;
int32 pending_flight_path;
ServerSpawnPlacementMode spawnPlacementMode;
bool on_auto_mount;
bool EntityCommandPrecheck(Spawn* spawn, const char* command);
bool delayedLogin;
int32 delayedAccountID;
int32 delayedAccessKey;
Timer delayTimer;
Spawn* tempPlacementSpawn;
int32 placement_unique_item_id;
bool hasOwnerOrEditAccess;
int32 temporary_transport_id;
int32 rejoin_group_id;
};
class ClientList {
public:
ClientList();
~ClientList();
bool ContainsStream(EQStream* eqs);
void Add(Client* client);
Client* Get(int32 ip, int16 port);
Client* FindByAccountID(int32 account_id);
Client* FindByName(char* charname);
void Remove(Client* client, bool delete_data = false);
void RemoveConnection(EQStream* eqs);
void Process();
int32 Count();
void ReloadQuests();
void CheckPlayersInvisStatus(Client* owner);
void RemovePlayerFromInvisHistory(int32 spawnID);
private:
Mutex MClients;
list<Client*> client_list;
};
#endif