diff --git a/source/LoginServer/Character.cpp b/source/LoginServer/Character.cpp new file mode 100644 index 0000000..67423d8 --- /dev/null +++ b/source/LoginServer/Character.cpp @@ -0,0 +1,20 @@ +/* + 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 . +*/ +#include "Character.h" \ No newline at end of file diff --git a/source/LoginServer/Character.h b/source/LoginServer/Character.h new file mode 100644 index 0000000..a0e89a8 --- /dev/null +++ b/source/LoginServer/Character.h @@ -0,0 +1,25 @@ +/* + 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 . +*/ +#ifndef _EQ2_CHARACTER_ +#define _EQ2_CHARACTER_ +class Character{ + +}; +#endif diff --git a/source/LoginServer/EQ2 Login.sln b/source/LoginServer/EQ2 Login.sln new file mode 100644 index 0000000..48169c7 --- /dev/null +++ b/source/LoginServer/EQ2 Login.sln @@ -0,0 +1,25 @@ +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EQ2 Login", "Login.vcxproj", "{BE2C1914-FCCC-4F65-A7DD-105142B36104}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + EQ2Login|Win32 = EQ2Login|Win32 + MiniLogin Release|Win32 = MiniLogin Release|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BE2C1914-FCCC-4F65-A7DD-105142B36104}.Debug|Win32.ActiveCfg = EQ2Login|Win32 + {BE2C1914-FCCC-4F65-A7DD-105142B36104}.Debug|Win32.Build.0 = EQ2Login|Win32 + {BE2C1914-FCCC-4F65-A7DD-105142B36104}.EQ2Login|Win32.ActiveCfg = EQ2Login|Win32 + {BE2C1914-FCCC-4F65-A7DD-105142B36104}.EQ2Login|Win32.Build.0 = EQ2Login|Win32 + {BE2C1914-FCCC-4F65-A7DD-105142B36104}.MiniLogin Release|Win32.ActiveCfg = EQ2Login|Win32 + {BE2C1914-FCCC-4F65-A7DD-105142B36104}.MiniLogin Release|Win32.Build.0 = EQ2Login|Win32 + {BE2C1914-FCCC-4F65-A7DD-105142B36104}.Release|Win32.ActiveCfg = EQ2Login|Win32 + {BE2C1914-FCCC-4F65-A7DD-105142B36104}.Release|Win32.Build.0 = EQ2Login|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/source/LoginServer/EQ2 Login.suo b/source/LoginServer/EQ2 Login.suo new file mode 100644 index 0000000..d090a9e Binary files /dev/null and b/source/LoginServer/EQ2 Login.suo differ diff --git a/source/LoginServer/LWorld.cpp b/source/LoginServer/LWorld.cpp new file mode 100644 index 0000000..aa80ad4 --- /dev/null +++ b/source/LoginServer/LWorld.cpp @@ -0,0 +1,1546 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#include "../common/debug.h" +#include +#include + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#else +#include +#include +#include +#include +#include +#include + +#include "../common/unix.h" + +#define SOCKET_ERROR -1 +#define INVALID_SOCKET -1 + +extern int errno; +#endif + +#include "../common/servertalk.h" +#include "LWorld.h" +#include "net.h" +#include "client.h" +#include "../common/packet_dump.h" +#include "login_opcodes.h" +#include "login_structs.h" +#include "LoginDatabase.h" +#include "PacketHeaders.h" +#include "../common/ConfigReader.h" + +#ifdef WIN32 +#define snprintf _snprintf +#define vsnprintf _vsnprintf +#define strncasecmp _strnicmp +#define strcasecmp _stricmp +#endif + +extern ClientList client_list; +extern NetConnection net; +extern LWorldList world_list; +extern LoginDatabase database; +extern ConfigReader configReader; +extern volatile bool RunLoops; + +#include "../common/Log.h" +using namespace std; +LWorld::LWorld(TCPConnection* in_con, bool in_OutgoingLoginUplink, int32 iIP, int16 iPort, bool iNeverKick) { + Link = in_con; + RemoteID = 0; + LinkWorldID = 0; + if (iIP) + ip = iIP; + else + ip = in_con->GetrIP(); + + struct in_addr in; + in.s_addr = in_con->GetrIP(); + char* ipadd = inet_ntoa(in); + if(ipadd) + strncpy(IPAddr,ipadd,64); + + if (iPort) + port = iPort; + else + port = in_con->GetrPort(); + ID = 0; + pClientPort = 0; + memset(account, 0, sizeof(account)); + memset(address, 0, sizeof(address)); + memset(worldname, 0, sizeof(worldname)); + status = 0; + accountid = 0; + admin_id = 0; + IsInit = false; + kicked = false; + pNeverKick = iNeverKick; + pPlaceholder = false; + pshowdown = false; + pConnected = in_con->Connected(); + pReconnectTimer = 0; + pStatsTimer = NULL; + isAuthenticated = false; + + if (in_OutgoingLoginUplink) { + pClientPort = port; + ptype = Login; + OutgoingUplink = true; + if (net.GetLoginMode() == Mesh) { + pReconnectTimer = new Timer(INTERSERVER_TIMER); + pReconnectTimer->Trigger(); + } + } + else { + ptype = UnknownW; + OutgoingUplink = false; + } + + in.s_addr = GetIP(); + strcpy(address, inet_ntoa(in)); + isaddressip = true; + + num_players = 0; + num_zones = 0; +} + +LWorld::LWorld(int32 in_accountid, char* in_accountname, char* in_worldname, int32 in_admin_id) { + pPlaceholder = true; + Link = 0; + ip = 0; + port = 0; + ID = 0; + strcpy(IPAddr,""); + pClientPort = 0; + memset(account, 0, sizeof(account)); + memset(address, 0, sizeof(address)); + memset(worldname, 0, sizeof(worldname)); + status = 0; + accountid = in_accountid; + admin_id = in_admin_id; + IsInit = false; + kicked = false; + pNeverKick = false; + pshowdown = true; + RemoteID = 0; + LinkWorldID = 0; + OutgoingUplink = false; + pReconnectTimer = 0; + pConnected = false; + + pStatsTimer = NULL; + + ptype = World; + strcpy(account, in_accountname); + strcpy(worldname, in_worldname); + + strcpy(address, "none"); + + isaddressip = true; + + num_players = 0; + num_zones = 0; +} + +LWorld::LWorld(TCPConnection* in_RemoteLink, int32 in_ip, int32 in_RemoteID, int32 in_accountid, char* in_accountname, char* in_worldname, char* in_address, sint32 in_status, int32 in_adminid, bool in_showdown, int8 in_authlevel, bool in_placeholder, int32 iLinkWorldID) { + Link = in_RemoteLink; + RemoteID = in_RemoteID; + LinkWorldID = iLinkWorldID; + ip = in_ip; + + struct in_addr in; + if(in_RemoteLink) + in.s_addr = in_RemoteLink->GetrIP(); + else if (in_ip) + in.s_addr = in_ip; + char* ipadd = inet_ntoa(in); + if(ipadd) + strncpy(IPAddr,ipadd,64); + + port = 0; + ID = 0; + pClientPort = 0; + memset(account, 0, sizeof(account)); + memset(address, 0, sizeof(address)); + memset(worldname, 0, sizeof(worldname)); + status = in_status; + accountid = in_accountid; + admin_id = in_adminid; + IsInit = true; + kicked = false; + pNeverKick = false; + pPlaceholder = in_placeholder; + pshowdown = in_showdown; + OutgoingUplink = false; + pReconnectTimer = 0; + pConnected = true; + pStatsTimer = NULL; + + ptype = World; + strcpy(account, in_accountname); + strcpy(worldname, in_worldname); + + strcpy(address, in_address); + + isaddressip = false; + + num_players = 0; + num_zones = 0; +} + +LWorld::~LWorld() { + + safe_delete ( pStatsTimer ); + num_zones = 0; + num_players = 0; + database.UpdateWorldServerStats(this, -4); + + if (ptype == World && RemoteID == 0) { + if (net.GetLoginMode() != Mesh || (!pPlaceholder && IsInit)) { + ServerPacket* pack = new ServerPacket(ServerOP_WorldListRemove, sizeof(int32)); + *((int32*) pack->pBuffer) = GetID(); + world_list.SendPacketLogin(pack); + delete pack; + } + } + + if (Link != 0 && RemoteID == 0) { + world_list.RemoveByLink(Link, 0, this); + if (OutgoingUplink) + delete Link; + else + Link->Free(); + } + Link = 0; + safe_delete(pReconnectTimer); + + world_list.RemoveByID ( this->GetID ( ) ); +} + +bool LWorld::Process() { + bool ret = true; + if (Link == 0) + return true; + if (Link->Connected()) { + if (!pConnected) { + pConnected = true; + } + } + else { + pConnected = false; + if (pReconnectTimer) { + if (pReconnectTimer->Check() && Link->ConnectReady()) { + pReconnectTimer->Start(pReconnectTimer->GetTimerTime() + (rand()%15000), false); + } + return true; + } + return false; + } + if (RemoteID != 0) + return true; + + if(pStatsTimer && pStatsTimer->Check()) + { + if(isAuthenticated && (database.IsServerAccountDisabled(account) || database.IsIPBanned(IPAddr))) + { + this->Kick(ERROR_BADPASSWORD); + return false; + } + + database.UpdateWorldServerStats(this, GetStatus()); + } + + ServerPacket* pack = 0; + while (ret && (pack = Link->PopPacket())) { + + // this stops connections from sending invalid packets without first authenticating + // with the login server to show it is a legit server + if(!isAuthenticated && pack->opcode != ServerOP_LSInfo) + { + Kick("This connection has not authenticated."); + break; + } + + switch(pack->opcode) { + case 0: + break; + case ServerOP_KeepAlive: { + // ignore this + break; + } + case ServerOP_LSFatalError: { + net.Uplink_WrongVersion = true; + ret = false; + kicked = true; + break; + } + case ServerOP_CharacterCreate: { + WorldCharNameFilterResponse_Struct* wcnfr = (WorldCharNameFilterResponse_Struct*) pack->pBuffer; + + Client* client = client_list.FindByLSID(wcnfr->account_id); + if(!client){ + if(wcnfr->account_id == 0){ + client_list.FindByCreateRequest(); + } + break; + } + if(wcnfr->response == 1) + { + client->CharacterApproved(GetID(),wcnfr->char_id); + } + else + { + client->CharacterRejected(wcnfr->response); + } + break; + } + case ServerOP_UsertoWorldReq: { + UsertoWorldRequest_Struct* ustwr = (UsertoWorldRequest_Struct*) pack->pBuffer; + if (ustwr->ToID) { + LWorld* world = world_list.FindByID(ustwr->ToID); + if (!world) { + break; + } + if (this->GetType() != Login) { + break; + } + ustwr->FromID = this->GetID(); + world->SendPacket(pack); + } + break; + } + case ServerOP_UsertoWorldResp: { + if (pack->size != sizeof(UsertoWorldResponse_Struct)) + break; + + UsertoWorldResponse_Struct* seps = (UsertoWorldResponse_Struct*) pack->pBuffer; + if (seps->ToID) { + LWorld* world = world_list.FindByID(seps->ToID); + + if (this->GetType() != Login) { + break; + } + if (world) { + seps->ToID = world->GetRemoteID(); + world->SendPacket(pack); + } + } + else { + Client* client = 0; + client = client_list.FindByLSID(seps->lsaccountid); + if(client == 0) + break; + if(this->GetID() != seps->worldid && this->GetType() != Login) + break; + + client->WorldResponse(GetID(),seps->response, seps->ip_address, seps->port, seps->access_key); + } + break; + } + case ServerOP_CharTimeStamp: { // This is being sent to synch a new timestamp on the login server + if(pack->size != sizeof(CharacterTimeStamp_Struct)) + break; + + CharacterTimeStamp_Struct* cts = (CharacterTimeStamp_Struct*) pack->pBuffer; + + if(!database.UpdateCharacterTimeStamp(cts->account_id,cts->char_id,cts->unix_timestamp,GetAccountID())) + printf("TimeStamp update error with character id %i of account id %i on server %i\n",cts->char_id,cts->account_id,GetAccountID()); + + //Todo: Synch with the other login servers + + break; + } + case ServerOP_GetTableData:{ + Kick("This is not an update server."); + break; + } + case ServerOP_GetTableQuery:{ + Kick("This is not an update server."); + break; + } + case ServerOP_GetLatestTables:{ + Kick("This is not an update server."); + break; + } + case ServerOP_ZoneUpdate:{ + if(pack->size > CHARZONESTRUCT_MAXSIZE) + break; + CharZoneUpdate_Struct* czu = (CharZoneUpdate_Struct*)pack->pBuffer; + database.UpdateCharacterZone(czu->account_id, czu->char_id, czu->zone_id, GetAccountID()); + break; + } + case ServerOP_RaceUpdate: { + + if(pack->size != sizeof(RaceUpdate_Struct)) + break; + + RaceUpdate_Struct* ru = (RaceUpdate_Struct*) pack->pBuffer; + database.UpdateCharacterRace(ru->account_id , ru->char_id , ru->model_type , ru->race , this->GetAccountID ( )); + break; + } + case ServerOP_BasicCharUpdate: { + if(pack->size != sizeof(CharDataUpdate_Struct)) + break; + + CharDataUpdate_Struct* cdu = (CharDataUpdate_Struct*) pack->pBuffer; + + switch(cdu->update_field) + { + case LEVEL_UPDATE_FLAG: + { + database.UpdateCharacterLevel(cdu->account_id,cdu->char_id,cdu->update_data,this->GetAccountID()); + break; + } + case CLASS_UPDATE_FLAG: + { + database.UpdateCharacterClass(cdu->account_id,cdu->char_id,cdu->update_data,this->GetAccountID()); + break; + } + case GENDER_UPDATE_FLAG: + { + database.UpdateCharacterGender(cdu->account_id,cdu->char_id,cdu->update_data,this->GetAccountID()); + break; + } + case DELETE_UPDATE_FLAG: + { + if(cdu->update_field == 1) + database.DeleteCharacter(cdu->account_id,cdu->char_id,this->GetAccountID()); + break; + } + } + break; + } + case ServerOP_LSInfo: { + if (pack->size != sizeof(ServerLSInfo_Struct)) { + this->Kick(ERROR_BADVERSION); + ret = false; + //struct in_addr in; + //in.s_addr = GetIP(); + } + else { + ServerLSInfo_Struct* lsi = (ServerLSInfo_Struct*) pack->pBuffer; + if (strcmp(lsi->protocolversion, EQEMU_PROTOCOL_VERSION) != 0 || !database.CheckVersion(lsi->serverversion)) { + cout << "ERROR - KICK BAD VERSION: Got versions: protocol: '" << lsi->protocolversion << "', database version: " << lsi->serverversion << endl; + cout << "To allow all world server versions to login, run query on your login database (alternatively replacing * with the database version if preferred): insert into login_versions set version = '*';" << endl; + this->Kick(ERROR_BADVERSION); + ret = false; + //struct in_addr in; + //in.s_addr = GetIP(); + } + else if (!SetupWorld(lsi->name, lsi->address, lsi->account, lsi->password, lsi->serverversion)) { + this->Kick(ERROR_BADPASSWORD); + ret = false; + //struct in_addr in; + //in.s_addr = GetIP(); + } + else{ + isAuthenticated = true; + devel_server = (lsi->servertype == 4); + } + } + break; + } + case ServerOP_LSStatus: { + ServerLSStatus_Struct* lss = (ServerLSStatus_Struct*) pack->pBuffer; + + if(lss->num_players > 5000 || lss->num_zones > 500) { + this->Kick("Your server has exceeded a number of players and/or zone limit."); + ret = false; + break; + } + UpdateStatus(lss->status, lss->num_players, lss->num_zones, lss->world_max_level); + break; + } + case ServerOP_SystemwideMessage: { + if (this->GetType() == Login) { + // no looping plz =p + //world_list.SendPacket(pack, this); + } + else if (this->GetType() == Chat) { + world_list.SendPacket(pack); + } + else { + } + break; + } + case ServerOP_ListWorlds: { + if (pack->size <= 1 || pack->pBuffer[pack->size - 1] != 0) { + break; + } + world_list.SendWorldStatus(this, (char*) pack->pBuffer); + break; + } + case ServerOP_WorldListUpdate: { + break; + } + case ServerOP_WorldListRemove: { + if (this->GetType() != Login) { + // cout << "Error: ServerOP_WorldListRemove from a non-login connection? WTF!" << endl; + break; + } + if (pack->size != sizeof(int32)) { + // cout << "Wrong size on ServerOP_WorldListRemove. Got: " << pack->size << ", Expected: " << sizeof(int32) << endl; + break; + } + cout << "Got world remove for remote #" << *((int32*) pack->pBuffer) << endl; + if ((*((int32*) pack->pBuffer)) > 0) { + LWorld* world = world_list.FindByLink(this->GetLink(), *((int32*) pack->pBuffer)); + if (world && world->GetRemoteID() != 0) { + *((int32*) pack->pBuffer) = world->GetID(); + if (net.GetLoginMode() != Mesh) + world_list.SendPacketLogin(pack, this); + world_list.RemoveByID(*((int32*) pack->pBuffer)); + } + } + else { + // cout << "Error: ServerOP_WorldListRemove: ID = 0? ops!" << endl; + } + break; + } + case ServerOP_TriggerWorldListRefresh: { + world_list.UpdateWorldList(); + if (net.GetLoginMode() != Mesh) + world_list.SendPacketLogin(pack, this); + break; + } + case ServerOP_ZoneUpdates:{ + pack->Inflate(); + ZoneUpdateList_Struct* updates = 0; + if(pack->size >= sizeof(ZoneUpdateList_Struct) && ((ZoneUpdateList_Struct*)pack->pBuffer)->total_updates <= MAX_UPDATE_COUNT){ + updates = (ZoneUpdateList_Struct*)pack->pBuffer; + ZoneUpdate_Struct* zone = 0; + int32 pos = sizeof(ZoneUpdateList_Struct); + sint16 num_updates = 0; + map zone_updates; + LoginZoneUpdate update; + while(pos < pack->size && num_updates < updates->total_updates){ + zone = (ZoneUpdate_Struct*)(pack->pBuffer+pos); + if((pos + zone->zone_name_length + zone->zone_desc_length + sizeof(ZoneUpdate_Struct)) <= pack->size){ + update.name = string(zone->data, zone->zone_name_length); + update.description = string(zone->data + zone->zone_name_length, zone->zone_desc_length); + pos += sizeof(ZoneUpdate_Struct) + zone->zone_name_length + zone->zone_desc_length; + num_updates++; + zone_updates[zone->zone_id] = update; + } + else + break; + } + if(zone_updates.size() == updates->total_updates) + world_list.AddServerZoneUpdates(this, zone_updates); + else + cout << "Error processing zone updates for server: " << GetAccount() << endl; + } + else + Kick("Possible Hacking Attempt"); + break; + } + + case ServerOP_LoginEquipment: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode %04X (%i): ServerOP_LoginEquipment", pack->opcode, pack->opcode); + + pack->Inflate(); + EquipmentUpdateList_Struct* updates = 0; + if(pack->size >= sizeof(EquipmentUpdateList_Struct) && ((EquipmentUpdateList_Struct*)pack->pBuffer)->total_updates <= MAX_LOGIN_APPEARANCE_COUNT){ + updates = (EquipmentUpdateList_Struct*)pack->pBuffer; + EquipmentUpdate_Struct* equip = 0; + int32 pos = sizeof(EquipmentUpdateList_Struct); + sint16 num_updates = 0; + map equip_updates; + LoginEquipmentUpdate update; + while(pos < pack->size && num_updates < updates->total_updates){ + equip = (EquipmentUpdate_Struct*)(pack->pBuffer+pos); + update.world_char_id = equip->world_char_id; + update.equip_type = equip->equip_type; + update.red = equip->red; + update.green = equip->green; + update.blue = equip->blue; + update.highlight_red = equip->highlight_red; + update.highlight_green = equip->highlight_green; + update.highlight_blue = equip->highlight_blue; + update.slot = equip->slot; + pos += sizeof(EquipmentUpdate_Struct); + num_updates++; + equip_updates[equip->id] = update; // JohnAdams: I think I need item_appearances.id from World here? + } + + LogWrite(LOGIN__DEBUG, 1, "Login", "Processing %i Login Appearance Updates...", num_updates); + if(equip_updates.size() == updates->total_updates) + { + world_list.AddServerEquipmentUpdates(this, equip_updates); + } + else + { + LogWrite(LOGIN__ERROR, 0, "Login", "Error processing login appearance updates for server: %s\n\t%s, function %s, line %i", GetAccount(), __FILE__, __FUNCTION__, __LINE__); + } + } + else + { + LogWrite(LOGIN__ERROR, 0, "Login", "World ID '%i', Possible Hacking Attempt (func: %s, line: %i", GetAccountID(), __FUNCTION__, __LINE__); + Kick("Possible Hacking Attempt"); + } + break; + } + case ServerOP_BugReport:{ + if(pack->size == sizeof(BugReport)){ + BugReport* report = (BugReport*)pack->pBuffer; + database.SaveBugReport(GetAccountID(), report->category, report->subcategory, report->causes_crash, report->reproducible, report->summary, report->description, report->version, report->player, report->account_id, report->spawn_name, report->spawn_id, report->zone_id); + } + break; + } + case ServerOP_EncapPacket: { + if (this->GetType() != Login) { + // cout << "Error: ServerOP_EncapPacket from a non-login connection? WTF!" << endl; + break; + } + ServerEncapPacket_Struct* seps = (ServerEncapPacket_Struct*) pack->pBuffer; + if (seps->ToID == 0xFFFFFFFF) { // Broadcast + ServerPacket* inpack = new ServerPacket(seps->opcode); + inpack->size = seps->size; + // Little trick here to save a memcpy, be careful if you change this any + inpack->pBuffer = seps->data; + world_list.SendPacketLocal(inpack, this); + inpack->pBuffer = 0; + delete inpack; + } + else { + LWorld* world = world_list.FindByID(seps->ToID); + if (world) { + ServerPacket* inpack = new ServerPacket(seps->opcode); + inpack->size = seps->size; + // Little trick here to save a memcpy, be careful if you change this any + inpack->pBuffer = seps->data; + world->SendPacket(inpack); + inpack->pBuffer = 0; + delete inpack; + } + } + if (net.GetLoginMode() != Mesh) + world_list.SendPacketLogin(pack, this); + break; + } + default: + { + cout << "Unknown LoginSOPcode: 0x" << hex << (int)pack->opcode << dec; + cout << " size:" << pack->size << " from " << GetAccount() << endl; + DumpPacket(pack->pBuffer, pack->size); + //Kick("Possible Hacking Attempt"); + break; + } + } + delete pack; + } + return ret; +} + +void LWorld::SendPacket(ServerPacket* pack) { + if (Link == 0) + return; + if (RemoteID) { + ServerPacket* outpack = new ServerPacket(ServerOP_EncapPacket, sizeof(ServerEncapPacket_Struct) + pack->size); + ServerEncapPacket_Struct* seps = (ServerEncapPacket_Struct*) outpack->pBuffer; + seps->ToID = RemoteID; + seps->opcode = pack->opcode; + seps->size = pack->size; + memcpy(seps->data, pack->pBuffer, pack->size); + Link->SendPacket(outpack); + delete outpack; + } + else { + Link->SendPacket(pack); + } +} + +void LWorld::Message(const char* to, const char* message, ...) { + va_list argptr; + char buffer[256]; + + va_start(argptr, message); + vsnprintf(buffer, 256, message, argptr); + va_end(argptr); + + ServerPacket* pack = new ServerPacket(ServerOP_EmoteMessage, sizeof(ServerEmoteMessage_Struct) + strlen(buffer) + 1); + ServerEmoteMessage_Struct* sem = (ServerEmoteMessage_Struct*) pack->pBuffer; + strcpy(sem->to, to); + strcpy(sem->message, buffer); + SendPacket(pack); + delete pack; +} + +void LWorld::Kick(const char* message, bool iSetKickedFlag) { + if (iSetKickedFlag) + kicked = true; + if (message) { + ServerPacket* pack = new ServerPacket(ServerOP_LSFatalError, strlen(message) + 1); + strcpy((char*) pack->pBuffer, message); + SendPacket(pack); + delete pack; + } + if (Link && GetRemoteID() == 0) + Link->Disconnect(); +} +bool LWorld::CheckServerName(const char* name) { + if (strlen(name) < 10) + return false; + for (size_t i=0; i= 'a' && name[i] <= 'z') || (name[i] >= 'A' && name[i] <= 'Z') || (name[i] >= '0' && name[i] <= '9') || name[i] == ' ' || name[i] == '\'' || name[i] == '-' || name[i] == '(' || name[i] == ')' || name[i] == '[' || name[i] == ']' || name[i] == '/' || name[i] == '.' || name[i] == ',' || name[i] == '_' || name[i] == '+' || name[i] == '=' || name[i] == ':' || name[i] == '~')) + return false; + } + return true; +} +bool LWorld::SetupWorld(char* in_worldname, char* in_worldaddress, char* in_account, char* in_password, char* in_version) { + if (strlen(in_worldaddress) > 3) { + isaddressip = false; + strcpy(address, in_worldaddress); + } + if (strlen(in_worldname) > 3) { + char tmpAccount[30]; + memcpy(tmpAccount, in_account, 29); + tmpAccount[29] = '\0'; + + int32 id = database.CheckServerAccount(tmpAccount, in_password); + + if(id == 0) + return false; + if(database.IsServerAccountDisabled(tmpAccount) || database.IsIPBanned(IPAddr) || (isaddressip && database.IsIPBanned(address))) + return false; + + LWorld* world = world_list.FindByID(id); + if(world) + world->Kick("Ghost Kick!"); + + ID = id; + accountid = id; + strncpy(account,tmpAccount,30); + char* name = database.GetServerAccountName(id); + if(name) + snprintf(worldname, (sizeof(worldname)) - 1, "%s", name); + else{ //failed to get account + account[0] = 0; + IsInit = false; + this->Kick ( "Could not load server information." ); + return false; + } + //world_list.KickGhostIP(GetIP(), this); + IsInit = true; + ptype = World; + world_list.SendWorldChanged(id, true); + } + else { + // name too short + account[0] = 0; + IsInit = false; + return false; + } + + database.UpdateWorldVersion(GetAccountID(), in_version); + pStatsTimer = new Timer ( 60000 ); + pStatsTimer->Start ( 60000 ); + + return true; +} +void LWorldList::SendWorldChanged(int32 server_id, bool sendtoallclients, Client* sendto){ + EQ2Packet* outapp = new EQ2Packet(OP_WorldStatusChangeMsg, 0, sizeof(LS_WorldStatusChanged)); + LS_WorldStatusChanged* world_changed = (LS_WorldStatusChanged*)outapp->pBuffer; + + world_changed->server_id = server_id; + LWorld* world = world_list.FindByID(server_id); + if(!world || world->ShowDown()) + world_changed->up = 0; + else + world_changed->up = 1; + if(sendtoallclients || sendto == 0) + client_list.SendPacketToAllClients(outapp); + else + sendto->QueuePacket(outapp); + world_list.SetUpdateServerList(true); +} +void LWorld::UpdateWorldList(LWorld* to) { + world_list.SetUpdateServerList( true ); +} + +void LWorld::ChangeToPlaceholder() { + ip = 0; + status = -1; + pPlaceholder = true; + if (Link != 0 && RemoteID == 0) { + Link->Disconnect(); + } + UpdateWorldList(); +} + +void LWorld::SetRemoteInfo(int32 in_ip, int32 in_accountid, char* in_account, char* in_name, char* in_address, int32 in_status, int32 in_adminid, sint32 in_players, sint32 in_zones) { + ip = in_ip; + accountid = in_accountid; +// strcpy(account, in_account); + strcpy(worldname, in_name); + strcpy(address, in_address); + status = in_status; + admin_id = in_adminid; + num_players = in_players; + num_zones = in_zones; +} + + +LWorldList::LWorldList() { + server_update_thread = true; + NextID = 1; + tcplistener = new TCPServer(net.GetPort(), true); + if (net.GetLoginMode() == Slave) + OutLink = new TCPConnection(true); + else + OutLink = 0; + + UpdateServerList = true; + #ifdef WIN32 + _beginthread(ServerUpdateLoop, 0, this); + #else + pthread_t thread; + pthread_create(&thread, NULL, &ServerUpdateLoop, this); + #endif +} + +LWorldList::~LWorldList() { + server_update_thread = false; + while(!server_update_thread){ + Sleep(100); + } + safe_delete(tcplistener); + safe_delete(OutLink); +} + +void LWorldList::Shutdown() { + LinkedListIterator iterator(list); + + iterator.Reset(); + while(iterator.MoreElements()) + { + iterator.RemoveCurrent ( ); + } + + safe_delete(tcplistener); +} + +void LWorldList::Add(LWorld* worldserver) { + LWorld* worldExist = FindByID(worldserver->GetID ( ) ); + if( worldExist ) + { + worldExist->Kick(); + MWorldMap.writelock(); + worldmap.erase(worldExist->GetID()); + MWorldMap.releasewritelock(); + safe_delete(worldExist); + } + MWorldMap.writelock(); + worldmap[worldserver->GetID()] = worldserver; + MWorldMap.releasewritelock(); + database.ResetWorldServerStatsConnectedTime(worldserver); + database.UpdateWorldIPAddress(worldserver->GetID(), worldserver->GetIP()); +} + +void LWorldList::AddInitiateWorld ( LWorld* world ) +{ + list.Insert ( world ); +} + +void LWorldList::KickGhostIP(int32 ip, LWorld* NotMe, int16 iClientPort) { + if (ip == 0) + return; + + map::iterator map_list; + MWorldMap.readlock(); + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++ ) { + LWorld* world = map_list->second; + if (!world->IsKicked() && world->GetIP() == ip && world != NotMe) { + if ((iClientPort == 0 && world->GetType() == World) || (iClientPort != 0 && world->GetClientPort() == iClientPort)) { + struct in_addr in; + in.s_addr = world->GetIP(); + // cout << "Removing GhostIP LWorld(" << world->GetID() << ") from ip: " << inet_ntoa(in) << " port: " << (int16)(world->GetPort()); + if (!world->Connected()) + { + // cout << " (it wasnt connected)"; + // cout << endl; + if (NotMe) { + in.s_addr = NotMe->GetIP(); + cout << "NotMe(" << NotMe->GetID() << ") = " << inet_ntoa(in) << ":" << NotMe->GetPort() << " (" << NotMe->GetClientPort() << ")" << endl; + } + world->Kick("Ghost IP kick"); + } + } + } + } + MWorldMap.releasereadlock(); +} + +void LWorldList::KickGhost(ConType in_type, int32 in_accountid, LWorld* ButNotMe) { + map::iterator map_list; + MWorldMap.readlock(); + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++ ) { + LWorld* world = map_list->second; + if (!world->IsKicked() && world->GetType() == in_type && world != ButNotMe && (in_accountid == 0 || world->GetAccountID() == in_accountid)) { + if (world->GetIP() != 0) { + //struct in_addr in; + //in.s_addr = world->GetIP(); + // cout << "Removing GhostAcc LWorld(" << world->GetID() << ") from ip: " << inet_ntoa(in) << " port: " << (int16)(world->GetPort()) << endl; + } + if (world->GetType() == Login && world->IsOutgoingUplink()) { + world->Kick("Ghost Acc Kick", false); + // cout << "softkick" << endl; + } + else + world->Kick("Ghost Acc Kick"); + } + } + MWorldMap.releasereadlock(); +} + +void LWorldList::UpdateWorldStats(){ + map::iterator map_list; + MWorldMap.readlock(); + for(map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + LWorld* world = map_list->second; + if(world && world->GetAccountID() > 0) + database.UpdateWorldServerStats(world, world->GetStatus()); + } + MWorldMap.releasereadlock(); +} + +void LWorldList::Process() { + TCPConnection* newtcp = 0; + LWorld* newworld = 0; + + LinkedListIterator iterator(list); + + iterator.Reset(); + while(iterator.MoreElements()) + { + if(iterator.GetData( )->GetID ( ) > 0 ) + { + LWorld* world = iterator.GetData ( ); + iterator.RemoveCurrent ( false ); + Add( world ); + } + else + { + if(! iterator.GetData ( )->Process ( ) ) + iterator.RemoveCurrent ( ); + else + iterator.Advance(); + } + } + + while ((newtcp = tcplistener->NewQueuePop())) { + newworld = new LWorld(newtcp); + newworld->SetID(0); + AddInitiateWorld(newworld); + struct in_addr in; + in.s_addr = newtcp->GetrIP(); + LogWrite(LOGIN__INFO, 0, "Login", "New Server connection: %s port %i", inet_ntoa(in), ntohs(newtcp->GetrPort())); + net.numservers++; + net.UpdateWindowTitle(); + world_list.UpdateWorldList(); + } + map::iterator map_list; + for( map_list = worldmap.begin(); map_list != worldmap.end(); ) { + LWorld* world = map_list->second; + + int32 account_id = world->GetAccountID(); + + if (world->IsKicked() && !world->IsNeverKick()) { + map_list++; + worldmap.erase ( account_id ); + net.numservers--; + net.UpdateWindowTitle(); + safe_delete ( world ); + continue; + } + else if (!world->Process()) { + //struct in_addr in; + //in.s_addr = world->GetIP(); + if (world->GetAccountID() == 0 || !(world->ShowDown()) || world->GetType() == Chat) { + map_list++; + worldmap.erase ( account_id ); + + net.numservers--; + net.UpdateWindowTitle(); + if(account_id > 0){ + LWorld* world2 = FindByID(account_id); + if(world2) + world2->ShowDownActive(true); + } + SendWorldChanged(account_id, true); + safe_delete ( world ); + continue; + } + else { + world->ChangeToPlaceholder(); + } + } + map_list++; + } +} + +// Sends packet to all World and Chat servers, local and remote (but not to remote login server's ::Process()) +void LWorldList::SendPacket(ServerPacket* pack, LWorld* butnotme) { + map::iterator map_list; + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + LWorld* world = map_list->second; + if (world != butnotme) { + if (world->GetType() == Login) { + ServerPacket* outpack = new ServerPacket(ServerOP_EncapPacket, sizeof(ServerEncapPacket_Struct) + pack->size); + ServerEncapPacket_Struct* seps = (ServerEncapPacket_Struct*) outpack->pBuffer; + seps->ToID = 0xFFFFFFFF; + seps->opcode = pack->opcode; + seps->size = pack->size; + memcpy(seps->data, pack->pBuffer, pack->size); + world->SendPacket(outpack); + delete outpack; + } + else if (world->GetRemoteID() == 0) { + world->SendPacket(pack); + } + } + } +} + +// Sends a packet to every local TCP Connection, all types +void LWorldList::SendPacketLocal(ServerPacket* pack, LWorld* butnotme) { + map::iterator map_list; + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + LWorld* world = map_list->second; + if (world != butnotme && world->GetRemoteID() == 0) { + world->SendPacket(pack); + } + } +} + +// Sends the packet to all login servers +void LWorldList::SendPacketLogin(ServerPacket* pack, LWorld* butnotme) { + map::iterator map_list; + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++ ) { + LWorld* world = map_list->second; + if (world != butnotme && world->GetType() == Login) { + world->SendPacket(pack); + } + } +} + +void LWorldList::UpdateWorldList(LWorld* to) { + map::iterator map_list; + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + LWorld* world = map_list->second; + if (net.GetLoginMode() != Mesh || world->GetRemoteID() == 0) + world->UpdateWorldList(to); + } +} + +LWorld* LWorldList::FindByID(int32 LWorldID) { + if(worldmap.count(LWorldID) > 0) + return worldmap[LWorldID]; + return 0; +} + +LWorld* LWorldList::FindByIP(int32 ip) { + map::iterator map_list; + LWorld* world = 0; + LWorld* ret = 0; + MWorldMap.readlock(); + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + world = map_list->second; + if (world->GetIP() == ip){ + ret = world; + break; + } + } + MWorldMap.releasereadlock(); + return ret; +} + +LWorld* LWorldList::FindByAddress(char* address) { + map::iterator map_list; + LWorld* world = 0; + LWorld* ret = 0; + MWorldMap.readlock(); + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + world = map_list->second; + if (strcasecmp(world->GetAddress(), address) == 0){ + ret = world; + break; + } + } + MWorldMap.releasereadlock(); + return ret; +} + +LWorld* LWorldList::FindByLink(TCPConnection* in_link, int32 in_id) { + if (in_link == 0) + return 0; + LWorld* world = 0; + LWorld* ret = 0; + map::iterator map_list; + MWorldMap.readlock(); + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + world = map_list->second; + if (world->GetLink() == in_link && world->GetRemoteID() == in_id){ + ret = world; + break; + } + } + MWorldMap.releasereadlock(); + return ret; +} + +LWorld* LWorldList::FindByAccount(int32 in_accountid, ConType in_type) { + if (in_accountid == 0) + return 0; + LWorld* world = 0; + LWorld* ret = 0; + map::iterator map_list; + MWorldMap.readlock(); + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + world = map_list->second; + if (world->GetAccountID() == in_accountid && world->GetType() == in_type){ + ret = world; + break; + } + } + MWorldMap.releasereadlock(); + return ret; +} + +int8 LWorld::GetWorldStatus(){ + if(IsDevelServer() && IsLocked() == false) + return 1; + else if(IsInit && IsLocked() == false) + return 0; + else + return 2; +} + +void LWorld::SendDeleteCharacter ( int32 char_id , int32 account_id ) +{ + ServerPacket* outpack = new ServerPacket(ServerOP_BasicCharUpdate, sizeof(CharDataUpdate_Struct)); + CharDataUpdate_Struct* cdu = (CharDataUpdate_Struct*)outpack->pBuffer; + cdu->account_id = account_id; + cdu->char_id = char_id; + cdu->update_field = DELETE_UPDATE_FLAG; + cdu->update_data = 1; + SendPacket(outpack); +} + +vector* LWorldList::GetServerListUpdate(int16 version){ + vector* ret = new vector; + map::iterator map_list; + PacketStruct* packet = 0; + MWorldMap.readlock(); + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + LWorld* world = map_list->second; + if ((world->IsInit || (world->ShowDown() && world->ShowDownActive())) && world->GetType() == World) { + packet = configReader.getStruct("LS_WorldUpdate", version); + if(packet){ + packet->setDataByName("server_id", world->GetID()); + packet->setDataByName("up", 1); + if(world->IsLocked()) + packet->setDataByName("locked", 1); + ret->push_back(packet); + } + } + } + MWorldMap.releasereadlock(); + return ret; +} + +EQ2Packet* LWorldList::MakeServerListPacket(int8 lsadmin, int16 version) { + + // if the latest world list has already been loaded, just return the string + MWorldMap.readlock(); + if (!UpdateServerList && ServerListData.count(version)) + { + MWorldMap.releasereadlock(); + return ServerListData[version]; + } + + //LWorld* world = 0; + int32 ServerNum = 0; + /* while(iterator.MoreElements()){ + world = iterator.GetData(); + if ((world->IsInit || (world->ShowDown() && world->ShowDownActive())) && world->GetType() == World) + ServerNum++; + iterator.Advance(); + } + ServerNum+=3; + */ + uint32 tmpCount = 0; + map::iterator map_list; + for (map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + LWorld* world = map_list->second; + if ((world->IsInit || (world->ShowDown() && world->ShowDownActive())) && world->GetType() == World) { + tmpCount++; + } + } + + PacketStruct* packet = configReader.getStruct("LS_WorldList", version); + packet->setArrayLengthByName("num_worlds", tmpCount); + + string world_data; + for (map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + LWorld* world = map_list->second; + if ((world->IsInit || (world->ShowDown() && world->ShowDownActive())) && world->GetType() == World) { + ServerNum++; + packet->setArrayDataByName("id", world->GetID(), ServerNum - 1); + + if (version <= 283) { + packet->setArrayDataByName("name", world->GetName(), ServerNum - 1); + if (!world->ShowDown()) + packet->setArrayDataByName("online", 1, ServerNum - 1); + if (world->IsLocked()) + packet->setArrayDataByName("locked", 1, ServerNum - 1); + packet->setArrayDataByName("unknown2", 1, ServerNum - 1); + packet->setArrayDataByName("unknown3", 1, ServerNum - 1); + packet->setArrayDataByName("load", world->GetWorldStatus(), ServerNum - 1); + } + else + { + if (version < 1212) + packet->setArrayDataByName("allowed_races", 0xFFFFFFFF, ServerNum - 1); + else if (version < 60006) + packet->setArrayDataByName("allowed_races", 0x000FFFFF, ServerNum - 1); // + Freeblood + else + packet->setArrayDataByName("allowed_races", 0x001FFFFF, ServerNum - 1); // + Aerakyn + + packet->setArrayDataByName("number_online_flag", 1, ServerNum - 1); + packet->setArrayDataByName("num_players", world->GetPlayerNum(), ServerNum - 1); + packet->setArrayDataByName("name", world->GetName(), ServerNum - 1); + packet->setArrayDataByName("name2", world->GetName(), ServerNum - 1); + packet->setArrayDataByName("feature_set", 0, ServerNum - 1); + + packet->setArrayDataByName("load", world->GetWorldStatus(), ServerNum - 1); + if (world->IsLocked()) + packet->setArrayDataByName("locked", 1, ServerNum - 1); + + if (world->ShowDown()) + packet->setArrayDataByName("tag", 0, ServerNum - 1); + else + packet->setArrayDataByName("tag", 1, ServerNum - 1); + + if (version < 1212) + packet->setArrayDataByName("unknown", ServerNum, ServerNum - 1); + } + } + } + + EQ2Packet* pack = packet->serialize(); + #ifdef DEBUG + //Only dump these for people trying to debug this... + printf("WorldList:\n"); + DumpPacket(pack->pBuffer, pack->size); + #endif + if (ServerListData.count(version)) + { + map::iterator it = ServerListData.find(version); + EQ2Packet* tmpPack = ServerListData[version]; + safe_delete(tmpPack); + ServerListData.erase(it); + } + ServerListData.insert(make_pair(version, pack)); + MWorldMap.releasereadlock(); + + SetUpdateServerList(false); + + return ServerListData[version]; +} + +void LWorldList::SendWorldStatus(LWorld* chat, char* adminname) { + struct in_addr in; + + int32 count = 0; + map::iterator map_list; + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + LWorld* world = map_list->second; + if (world->GetIP() != 0 && world->GetType() == World) { + chat->Message(adminname, "Name: %s", world->GetName()); + in.s_addr = world->GetIP(); + if (world->GetAccountID() != 0) { + chat->Message(adminname, " Account: %s", world->GetAccount()); + } + chat->Message(adminname, " Number of Zones: %i", world->GetZoneNum()); + chat->Message(adminname, " Number of Players: %i", world->GetPlayerNum()); + chat->Message(adminname, " IP: %s", inet_ntoa(in)); + if (!world->IsAddressIP()) { + chat->Message(adminname, " Address: %s", world->GetAddress()); + } + count++; + } + } + chat->Message(adminname, "%i worlds listed.", count); +} + +void LWorldList::RemoveByLink(TCPConnection* in_link, int32 in_id, LWorld* ButNotMe) { + if (in_link == 0) + return; + map::iterator map_list; + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + LWorld* world = map_list->second; + if (world != ButNotMe && world->GetLink() == in_link && (in_id == 0 || world->GetRemoteID() == in_id)) { + // world->Link = 0; + map_list++; + worldmap.erase ( world->GetID ( ) ); + safe_delete ( world ); + continue; + } + } +} + +void LWorldList::RemoveByID(int32 in_id) { + if (in_id == 0) + return; + + LWorld* existWorld = FindByID(in_id); + if ( existWorld != NULL ) + { + MWorldMap.writelock(); + worldmap.erase ( in_id ); + MWorldMap.releasewritelock(); + safe_delete ( existWorld ); + } +} + +bool LWorldList::Init() { + + database.ResetWorldStats ( ); + + if (!tcplistener->IsOpen()) { + return tcplistener->Open(net.GetPort()); + } + + return false; +} + +void LWorldList::InitWorlds(){ + vector server_list; + database.GetServerAccounts(&server_list); + vector::iterator iter; + int i = 0; + for(iter = server_list.begin(); iter != server_list.end(); iter++, i++){ + LWorld* world = FindByID(server_list[i]->GetAccountID()); + if(!world){ + server_list[i]->ShowDown(true); + server_list[i]->ShowDownActive(true); + server_list[i]->SetID ( server_list[i]->GetAccountID ( ) ); + Add ( server_list[i] ); + } + } +} + +int32 LWorldList::GetCount(ConType type) { + int32 count = 0; + map::iterator map_list; + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + LWorld* world = map_list->second; + if (world->GetType() == type) { + count++; + } + } + return count; +} + +void LWorldList::ListWorldsToConsole() { + struct in_addr in; + + cout << "World List:" << endl; + cout << "============================" << endl; + map::iterator map_list; + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + LWorld* world = map_list->second; + in.s_addr = world->GetIP(); + if (world->GetType() == World) { + if (world->GetRemoteID() == 0) + cout << "ID: " << world->GetID() << ", Name: " << world->GetName() << ", Local, IP: " << inet_ntoa(in) << ":" << world->GetPort() << ", Status: " << world->GetStatus() << ", AccID: " << world->GetAccountID() << endl; + else + cout << "ID: " << world->GetID() << ", Name: " << world->GetName() << ", RemoteID: " << world->GetRemoteID() << ", LinkWorldID: " << world->GetLinkWorldID() << ", IP: " << inet_ntoa(in) << ":" << world->GetPort() << ", Status: " << world->GetStatus() << ", AccID: " << world->GetAccountID() << endl; + } + else if (world->GetType() == Chat) { + cout << "ID: " << world->GetID() << ", Chat Server, IP: " << inet_ntoa(in) << ":" << world->GetPort() << ", AccID: " << world->GetAccountID() << endl; + } + else if (world->GetType() == Login) { + if (world->IsOutgoingUplink()) { + if (world->Connected()) + cout << "ID: " << world->GetID() << ", Login Server (out), IP: " << inet_ntoa(in) << ":" << world->GetPort() << ", AccID: " << world->GetAccountID() << endl; + else + cout << "ID: " << world->GetID() << ", Login Server (nc), IP: " << inet_ntoa(in) << ":" << world->GetPort() << ", AccID: " << world->GetAccountID() << endl; + } + else + cout << "ID: " << world->GetID() << ", Login Server (in), IP: " << inet_ntoa(in) << ":" << world->GetPort() << " (" << world->GetClientPort() << "), AccID: " << world->GetAccountID() << endl; + } + else { + cout << "ID: " << world->GetID() << ", Unknown Type, Name: " << world->GetName() << ", IP: " << inet_ntoa(in) << ":" << world->GetPort() << ", AccID: " << world->GetAccountID() << endl; + } + } + cout << "============================" << endl; +} + +void LWorldList::AddServerZoneUpdates(LWorld* world, map updates){ + int32 server_id = world->GetID(); + map::iterator itr; + for(itr = updates.begin(); itr != updates.end(); itr++){ + if(zone_updates_already_used.size() >= 1500 || zone_updates_already_used[server_id].count(itr->first) > 0){ + world->Kick("Hacking attempt."); + return; + } + zone_updates_already_used[server_id][itr->first] = true; + } + server_zone_updates.Put(server_id, updates); +} +//devn00b temp + +void LWorldList::AddServerEquipmentUpdates(LWorld* world, map updates){ + int32 server_id = world->GetID(); + map::iterator itr; + for(itr = updates.begin(); itr != updates.end(); itr++){ + LogWrite(MISC__TODO, 1, "TODO", "JA: Until we learn what this does, can't risk worlds being kicked performing login appearance updates...\n%s, func: %s, line: %i", __FILE__, __FUNCTION__, __LINE__); + + /*if(equip_updates_already_used.size() >= 1500 || equip_updates_already_used[server_id].count(itr->first) > 0) + { + LogWrite(LOGIN__ERROR, 0, "Login", "World ID '%i': Hacking attempt. (function: %s, line: %i", world->GetAccountID(), __FUNCTION__, __LINE__); + world->Kick("Hacking attempt."); + return; + }*/ + equip_updates_already_used[server_id][itr->first] = true; + } + server_equip_updates.Put(server_id, updates); +} + +void LWorldList::RequestServerUpdates(LWorld* world){ + if(world){ + ServerPacket* pack = new ServerPacket(ServerOP_ZoneUpdates, sizeof(ZoneUpdateRequest_Struct)); + ZoneUpdateRequest_Struct* request = (ZoneUpdateRequest_Struct*)pack->pBuffer; + request->max_per_batch = MAX_UPDATE_COUNT; + world->SendPacket(pack); + delete pack; + zone_update_timeouts.Put(world->GetID(), Timer::GetCurrentTime2() + 30000); + } +} + +void LWorldList::ProcessServerUpdates(){ + MutexMap >::iterator itr = server_zone_updates.begin(); + while(itr.Next()){ + if(itr->second.size() > 0){ + database.SetServerZoneDescriptions(itr->first, itr->second); + if(itr->second.size() == MAX_UPDATE_COUNT) + awaiting_zone_update.Put(itr->first, Timer::GetCurrentTime2() + 10000); //only process 20 updates in a 10 second period to avoid network problems + server_zone_updates.erase(itr->first); + } + if(zone_update_timeouts.count(itr->first) == 0 || zone_update_timeouts.Get(itr->first) <= Timer::GetCurrentTime2()){ + zone_update_timeouts.erase(itr->first); + server_zone_updates.erase(itr->first); + } + } + LWorld* world = 0; + MWorldMap.readlock(); + map::iterator map_itr; + for(map_itr = worldmap.begin(); map_itr != worldmap.end(); map_itr++){ + world = map_itr->second; + if(world && world->GetID()){ + if(last_updated.count(world) == 0 || last_updated.Get(world) <= Timer::GetCurrentTime2()){ + zone_updates_already_used[world->GetID()].clear(); + RequestServerUpdates(world); + last_updated.Put(world, Timer::GetCurrentTime2() + 21600000); + } + if(awaiting_zone_update.count(world->GetID()) > 0 && awaiting_zone_update.Get(world->GetID()) <= Timer::GetCurrentTime2()){ + awaiting_zone_update.erase(world->GetID()); + RequestServerUpdates(world); + } + } + } + ProcessLSEquipUpdates(); + MWorldMap.releasereadlock(); + + +} +void LWorldList::RequestServerEquipUpdates(LWorld* world) +{ + if(world) + { + ServerPacket *pack_equip = new ServerPacket(ServerOP_LoginEquipment, sizeof(EquipmentUpdateRequest_Struct)); + EquipmentUpdateRequest_Struct *request_equip = (EquipmentUpdateRequest_Struct *)pack_equip->pBuffer; + request_equip->max_per_batch = MAX_LOGIN_APPEARANCE_COUNT; // item appearance data smaller, request more at a time? + LogWrite(LOGIN__DEBUG, 1, "Login", "Sending equipment update requests to world: (%s)... (Batch Size: %i)", world->GetName(), request_equip->max_per_batch); + world->SendPacket(pack_equip); + delete pack_equip; + equip_update_timeouts.Put(world->GetID(), Timer::GetCurrentTime2() + 30000); + } +} +void LWorldList::ProcessLSEquipUpdates() +{ + // process login_equipment updates + MutexMap >::iterator itr_equip = server_equip_updates.begin(); + while(itr_equip.Next()) + { + if(itr_equip->second.size() > 0) + { + LogWrite(LOGIN__DEBUG, 1, "Login", "Setting Login Appearances..."); + database.SetServerEquipmentAppearances(itr_equip->first, itr_equip->second); + if(itr_equip->second.size() == MAX_LOGIN_APPEARANCE_COUNT) + awaiting_equip_update.Put(itr_equip->first, Timer::GetCurrentTime2() + 10000); //only process 100 updates in a 10 second period to avoid network problems + server_equip_updates.erase(itr_equip->first); + } + if(equip_update_timeouts.count(itr_equip->first) == 0 || equip_update_timeouts.Get(itr_equip->first) <= Timer::GetCurrentTime2()) + { + LogWrite(LOGIN__DEBUG, 1, "Login", "Clearing Login Appearances Update Timers..."); + equip_update_timeouts.erase(itr_equip->first); + server_equip_updates.erase(itr_equip->first); + } + } + LWorld* world = 0; + MWorldMap.readlock(); + map::iterator map_itr; + for(map_itr = worldmap.begin(); map_itr != worldmap.end(); map_itr++) + { + world = map_itr->second; + if(world && world->GetID()) + { + if(last_equip_updated.count(world) == 0 || last_equip_updated.Get(world) <= Timer::GetCurrentTime2()) + { + LogWrite(LOGIN__DEBUG, 1, "Login", "Clearing Login Appearances Update Counters..."); + equip_updates_already_used[world->GetID()].clear(); + RequestServerEquipUpdates(world); + last_equip_updated.Put(world, Timer::GetCurrentTime2() + 900000); // every 15 mins + } + if( awaiting_equip_update.count(world->GetID()) > 0 && awaiting_equip_update.Get(world->GetID()) <= Timer::GetCurrentTime2()) + { + LogWrite(LOGIN__DEBUG, 1, "Login", "Erase awaiting equip updates..."); + awaiting_equip_update.erase(world->GetID()); + RequestServerEquipUpdates(world); + } + } + } + MWorldMap.releasereadlock(); +} + + +ThreadReturnType ServerUpdateLoop(void* tmp) { +#ifdef WIN32 + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL); +#endif + if (tmp == 0) { + ThrowError("ServerUpdateLoop(): tmp = 0!"); + THREAD_RETURN(NULL); + } + LWorldList* worldList = (LWorldList*) tmp; + while (worldList->ContinueServerUpdates()) { + Sleep(1000); + worldList->ProcessServerUpdates(); + } + worldList->ResetServerUpdates(); + THREAD_RETURN(NULL); +} diff --git a/source/LoginServer/LWorld.h b/source/LoginServer/LWorld.h new file mode 100644 index 0000000..d9e3361 --- /dev/null +++ b/source/LoginServer/LWorld.h @@ -0,0 +1,253 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#ifndef LWORLD_H +#define LWORLD_H + +#include "../common/Mutex.h" + +#define ERROR_BADPASSWORD "Bad password" +#define INVALID_ACCOUNT "Invalid Server Account." +#define ERROR_BADVERSION "Incorrect version" +#define ERROR_UNNAMED "Unnamed servers not allowed to connect to full login servers" +#define ERROR_NOTMASTER "Not a master server" +#define ERROR_NOTMESH "Not a mesh server" +#define ERROR_GHOST "Ghost kick" +#define ERROR_UnknownServerType "Unknown Server Type" +#define ERROR_BADNAME_SERVER "Bad server name, name may not contain the word \"Server\" (case sensitive)" +#define ERROR_BADNAME "Bad server name. Unknown reason." + +#define WORLD_NAME_SUFFIX " Server" + +#include +#include +#include +#include +#include +#include +#include + +namespace beast = boost::beast; // from +namespace http = beast::http; // from + +#include "../common/linked_list.h" +#include "../WorldServer/MutexList.h" +#include "../WorldServer/MutexMap.h" +#include "../common/timer.h" +#include "../common/types.h" +#include "../common/queue.h" +#include "../common/servertalk.h" +#include "../common/TCPConnection.h" +#include "client.h" + +#define MAX_UPDATE_COUNT 20 +#define MAX_LOGIN_APPEARANCE_COUNT 100 + +#ifdef WIN32 + void ServerUpdateLoop(void* tmp); +#else + void* ServerUpdateLoop(void* tmp); +#endif + +enum ConType { UnknownW, World, Chat, Login }; + +class LWorld +{ +public: + LWorld(TCPConnection* in_con, bool OutgoingLoginUplink = false, int32 iIP = 0, int16 iPort = 0, bool iNeverKick = false); + LWorld(int32 in_accountid, char* in_accountname, char* in_worldname, int32 in_admin_id); + LWorld(TCPConnection* in_RemoteLink, int32 in_ip, int32 in_RemoteID, int32 in_accountid, char* in_accountname, char* in_worldname, char* in_address, sint32 in_status, int32 in_adminid, bool in_showdown, int8 in_authlevel, bool in_placeholder, int32 iLinkWorldID); + ~LWorld(); + + static bool CheckServerName(const char* name); + + bool Process(); + void SendPacket(ServerPacket* pack); + void Message(const char* to, const char* message, ...); + + bool SetupWorld(char* in_worldname, char* in_worldaddress, char* in_account, char* in_password, char* in_version); + void UpdateStatus(sint32 in_status, sint32 in_players, sint32 in_zones, int8 in_level) { + // we don't want the server list to update unless something has changed + if(status != in_status || num_players != in_players || num_zones != in_zones || world_max_level != in_level) + { + status = in_status; + num_players = in_players; + num_zones = in_zones; + world_max_level = in_level; + UpdateWorldList(); + } + } + void UpdateWorldList(LWorld* to = 0); + void SetRemoteInfo(int32 in_ip, int32 in_accountid, char* in_account, char* in_name, char* in_address, int32 in_status, int32 in_adminid, sint32 in_players, sint32 in_zones); + + inline bool IsPlaceholder() { return pPlaceholder; } + inline int32 GetAccountID() { return accountid; } + inline char* GetAccount() { return account; } + inline char* GetAddress() { return address; } + inline int16 GetClientPort() { return pClientPort; } + inline bool IsAddressIP() { return isaddressip; } + inline char* GetName() { return worldname; } + inline sint32 GetStatus() { return status; } + bool IsLocked() { return status==-2; } + inline int32 GetIP() { return ip; } + inline int16 GetPort() { return port; } + inline int32 GetID() { return ID; } + inline int32 GetAdmin() { return admin_id; } + inline bool ShowDown() { return pshowdown; } + inline bool ShowDownActive(){ return show_down_active; } + void ShowDownActive(bool show){ show_down_active = show; } + void ShowDown(bool show){ pshowdown = show; } + inline bool Connected() { return pConnected; } + int8 GetWorldStatus(); + + void ChangeToPlaceholder(); + void Kick(const char* message = ERROR_GHOST, bool iSetKickedFlag = true); + inline bool IsKicked() { return kicked; } + inline bool IsNeverKick() { return pNeverKick; } + inline ConType GetType() { return ptype; } + inline bool IsOutgoingUplink() { return OutgoingUplink; } + inline TCPConnection* GetLink() { return Link; } + inline int32 GetRemoteID() { return RemoteID; } + inline int32 GetLinkWorldID() { return LinkWorldID; } + inline sint32 GetZoneNum() { return num_zones; } + inline sint32 GetPlayerNum() { return num_players; } + void SetID(int32 new_id) { ID = new_id; } + + void SendDeleteCharacter( int32 char_id, int32 account_id ); + bool IsDevelServer(){ return devel_server; } + + inline int8 GetMaxWorldLevel() { return world_max_level; } + + bool IsInit; +protected: + friend class LWorldList; + TCPConnection* Link; + Timer* pReconnectTimer; + Timer* pStatsTimer; +private: + int32 ID; + int32 ip; + char IPAddr[64]; + int16 port; + bool kicked; + bool pNeverKick; + bool pPlaceholder; + bool devel_server; + + int32 accountid; + char account[30]; + char address[250]; + bool isAuthenticated; + int16 pClientPort; + bool isaddressip; + char worldname[200]; + sint32 status; + int32 admin_id; + bool pshowdown; + bool show_down_active; + ConType ptype; + bool OutgoingUplink; + bool pConnected; + sint32 num_players; + sint32 num_zones; + int32 RemoteID; + int32 LinkWorldID; + int8 world_max_level; + +}; + +class LWorldList +{ +public: + + LWorldList(); + ~LWorldList(); + + LWorld* FindByID(int32 WorldID); + LWorld* FindByIP(int32 ip); + LWorld* FindByAddress(char* address); + LWorld* FindByLink(TCPConnection* in_link, int32 in_id); + LWorld* FindByAccount(int32 in_accountid, ConType in_type = World); + + void Add(LWorld* worldserver); + void AddInitiateWorld ( LWorld* world ); + void Process(); + void ReceiveData(); + void SendPacket(ServerPacket* pack, LWorld* butnotme = 0); + void SendPacketLocal(ServerPacket* pack, LWorld* butnotme = 0); + void SendPacketLogin(ServerPacket* pack, LWorld* butnotme = 0); + void SendWorldChanged(int32 server_id, bool sendtoallclients=false, Client* sendto = 0); + vector* GetServerListUpdate(int16 version); + EQ2Packet* MakeServerListPacket(int8 lsadmin, int16 version); + + void UpdateWorldList(LWorld* to = 0); + void UpdateWorldStats(); + void KickGhost(ConType in_type, int32 in_accountid = 0, LWorld* ButNotMe = 0); + void KickGhostIP(int32 ip, LWorld* NotMe = 0, int16 iClientPort = 0); + void RemoveByLink(TCPConnection* in_link, int32 in_id = 0, LWorld* ButNotMe = 0); + void RemoveByID(int32 in_id); + + void SendWorldStatus(LWorld* chat, char* adminname); + + void ConnectUplink(); + bool Init(); + void InitWorlds(); + void Shutdown(); + bool WriteXML(); + + int32 GetCount(ConType type); + void PopulateWorldList(http::response& res); + + void ListWorldsToConsole(); + //devn00b temp + void AddServerEquipmentUpdates(LWorld* world, map updates); + void ProcessLSEquipUpdates(); + void RequestServerEquipUpdates(LWorld* world); + + void SetUpdateServerList ( bool var ) { UpdateServerList = var; } + bool ContinueServerUpdates(){ return server_update_thread; } + void ResetServerUpdates(){server_update_thread = true;} + void ProcessServerUpdates(); + void RequestServerUpdates(LWorld* world); + void AddServerZoneUpdates(LWorld* world, map updates); + +protected: + friend class LWorld; + int32 GetNextID() { return NextID++; } + +private: + Mutex MWorldMap; + map > zone_updates_already_used; //used to determine if someone is trying to DOS us + MutexMap zone_update_timeouts; + MutexMap awaiting_zone_update; + MutexMap last_updated; + MutexMap > server_zone_updates; + bool server_update_thread; + int32 NextID; + + LinkedList list; + + map worldmap; + + TCPServer* tcplistener; + TCPConnection* OutLink; + + //devn00b temp + // JohnAdams: login appearances, copied from above + map > equip_updates_already_used; + MutexMap equip_update_timeouts; + MutexMap awaiting_equip_update; + MutexMap last_equip_updated; + MutexMap > server_equip_updates; + // + /// + + // holds the world server list so we don't have to create it for every character + // logging in + map ServerListData; + bool UpdateServerList; +}; +#endif diff --git a/source/LoginServer/Login.dsp b/source/LoginServer/Login.dsp new file mode 100644 index 0000000..cf66e51 --- /dev/null +++ b/source/LoginServer/Login.dsp @@ -0,0 +1,447 @@ +# Microsoft Developer Studio Project File - Name="Login" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=Login - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "Login.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "Login.mak" CFG="Login - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "Login - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "Login - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE "Login - Win32 MiniLogin" (based on "Win32 (x86) Console Application") +!MESSAGE "Login - Win32 PublicLogin" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "Login - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "../Build" +# PROP Intermediate_Dir "../Build/Login" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /MT /w /W0 /GX /Zi /O2 /Ob2 /D "LOGINCRYPTO" /D "INVERSEXY" /D _WIN32_WINNT=0x0400 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /FR /YX /FD /c +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo /o"../Build/Login/Login.bsc" +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib wsock32.lib zlib.lib mysqlclient.lib /nologo /subsystem:console /map:"../Build/Login.map" /debug /machine:I386 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "Login - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Login___Win32_Debug" +# PROP BASE Intermediate_Dir "Login___Win32_Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "../build/login/Debug" +# PROP Intermediate_Dir "../build/login/debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /Gm /GX /ZI /Od /D "LOGINCRYPTO" /D "INVERSEXY" /D _WIN32_WINNT=0x0400 /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /FR /YX /FD /GZ /c +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib wsock32.lib zlib.lib mysqlclient.lib /nologo /subsystem:console /debug /machine:I386 /nodefaultlib:"LIBCMT" /out:"../build/login/Debug/LoginDebug.exe" /pdbtype:sept +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "Login - Win32 MiniLogin" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Login___Win32_MiniLogin" +# PROP BASE Intermediate_Dir "Login___Win32_MiniLogin" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "../Build" +# PROP Intermediate_Dir "../Build/MiniLogin" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /w /W0 /GX /O2 /Ob2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /D "BUILD_FOR_WINDOWS" /FR /YX /FD /c +# ADD CPP /nologo /MT /w /W0 /GX /O2 /Ob2 /D _WIN32_WINNT=0x0400 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /D "MINILOGIN" /FR /YX /FD /c +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo /o"../Build/Login/Login.bsc" +# ADD BSC32 /nologo /o"../Build/MiniLogin/Login.bsc" +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib wsock32.lib zlib.lib mysqlclient.lib /nologo /subsystem:console /machine:I386 +# SUBTRACT BASE LINK32 /pdb:none +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib wsock32.lib zlib.lib mysqlclient.lib /nologo /subsystem:console /machine:I386 /out:"../Build/MiniLogin.exe" +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "Login - Win32 PublicLogin" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Login___Win32_PublicLogin" +# PROP BASE Intermediate_Dir "Login___Win32_PublicLogin" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "../Build" +# PROP Intermediate_Dir "../Build/PublicLogin" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /w /W0 /GX /O2 /Ob2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /D "BUILD_FOR_WINDOWS" /FR /YX /FD /c +# ADD CPP /nologo /MT /w /W0 /GX /O2 /Ob2 /D _WIN32_WINNT=0x0400 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /D "PUBLICLOGIN" /FR /YX /FD /c +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo /o"../Build/Login/Login.bsc" +# ADD BSC32 /nologo /o"../Build/Login/Login.bsc" +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib wsock32.lib zlib.lib mysqlclient.lib /nologo /subsystem:console /machine:I386 +# SUBTRACT BASE LINK32 /pdb:none +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib wsock32.lib zlib.lib mysqlclient.lib /nologo /subsystem:console /machine:I386 /out:"../Build/PublicLogin.exe" +# SUBTRACT LINK32 /pdb:none + +!ENDIF + +# Begin Target + +# Name "Login - Win32 Release" +# Name "Login - Win32 Debug" +# Name "Login - Win32 MiniLogin" +# Name "Login - Win32 PublicLogin" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\client.cpp +# End Source File +# Begin Source File + +SOURCE=.\EQCrypto.cpp + +!IF "$(CFG)" == "Login - Win32 Release" + +!ELSEIF "$(CFG)" == "Login - Win32 Debug" + +!ELSEIF "$(CFG)" == "Login - Win32 MiniLogin" + +# PROP Exclude_From_Build 1 + +!ELSEIF "$(CFG)" == "Login - Win32 PublicLogin" + +# PROP Exclude_From_Build 1 + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\logindatabase.cpp + +!IF "$(CFG)" == "Login - Win32 Release" + +!ELSEIF "$(CFG)" == "Login - Win32 Debug" + +!ELSEIF "$(CFG)" == "Login - Win32 MiniLogin" + +# PROP Exclude_From_Build 1 + +!ELSEIF "$(CFG)" == "Login - Win32 PublicLogin" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\LWorld.cpp +# End Source File +# Begin Source File + +SOURCE=.\net.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\client.h +# End Source File +# Begin Source File + +SOURCE=.\EQCrypto.h +# End Source File +# Begin Source File + +SOURCE=.\login_opcodes.h +# End Source File +# Begin Source File + +SOURCE=.\login_structs.h +# End Source File +# Begin Source File + +SOURCE=.\LWorld.h +# End Source File +# Begin Source File + +SOURCE=.\net.h +# End Source File +# End Group +# Begin Group "Common Source Files" + +# PROP Default_Filter ".cpp" +# Begin Source File + +SOURCE=..\common\crc32.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\database.cpp + +!IF "$(CFG)" == "Login - Win32 Release" + +!ELSEIF "$(CFG)" == "Login - Win32 Debug" + +!ELSEIF "$(CFG)" == "Login - Win32 MiniLogin" + +# PROP Exclude_From_Build 1 + +!ELSEIF "$(CFG)" == "Login - Win32 PublicLogin" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\common\dbcore.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\DBMemLeak.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\debug.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\EQNetwork.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\md5.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\MiscFunctions.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\Mutex.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\packet_dump.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\packet_functions.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\TCPConnection.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\timer.cpp +# End Source File +# End Group +# Begin Group "Common Header Files" + +# PROP Default_Filter ".h" +# Begin Source File + +SOURCE=..\common\classes.h +# End Source File +# Begin Source File + +SOURCE=..\common\crc32.h +# End Source File +# Begin Source File + +SOURCE=..\common\database.h +# End Source File +# Begin Source File + +SOURCE=..\common\DBMemLeak.h +# End Source File +# Begin Source File + +SOURCE=..\common\debug.h +# End Source File +# Begin Source File + +SOURCE=..\common\deity.h +# End Source File +# Begin Source File + +SOURCE=..\common\eq_opcodes.h +# End Source File +# Begin Source File + +SOURCE=..\common\eq_packet_structs.h +# End Source File +# Begin Source File + +SOURCE=..\common\EQCheckTable.h +# End Source File +# Begin Source File + +SOURCE=..\common\EQFragment.h +# End Source File +# Begin Source File + +SOURCE=..\common\EQNetwork.h +# End Source File +# Begin Source File + +SOURCE=..\common\EQOpcodes.h +# End Source File +# Begin Source File + +SOURCE=..\common\EQPacket.h +# End Source File +# Begin Source File + +SOURCE=..\common\EQPacketManager.h +# End Source File +# Begin Source File + +SOURCE=..\common\errmsg.h +# End Source File +# Begin Source File + +SOURCE=..\common\Guilds.h +# End Source File +# Begin Source File + +SOURCE=..\common\linked_list.h +# End Source File +# Begin Source File + +SOURCE=..\common\md5.h +# End Source File +# Begin Source File + +SOURCE=..\common\MiscFunctions.h +# End Source File +# Begin Source File + +SOURCE=..\common\moremath.h +# End Source File +# Begin Source File + +SOURCE=..\common\Mutex.h +# End Source File +# Begin Source File + +SOURCE=..\common\packet_dump.h +# End Source File +# Begin Source File + +SOURCE=..\common\packet_dump_file.h +# End Source File +# Begin Source File + +SOURCE=..\common\packet_functions.h +# End Source File +# Begin Source File + +SOURCE=..\common\queue.h +# End Source File +# Begin Source File + +SOURCE=..\common\queues.h +# End Source File +# Begin Source File + +SOURCE=..\common\races.h +# End Source File +# Begin Source File + +SOURCE=..\common\Seperator.h +# End Source File +# Begin Source File + +SOURCE=..\common\servertalk.h +# End Source File +# Begin Source File + +SOURCE=..\common\TCPConnection.h +# End Source File +# Begin Source File + +SOURCE=..\common\timer.h +# End Source File +# Begin Source File + +SOURCE=..\common\types.h +# End Source File +# Begin Source File + +SOURCE=..\common\version.h +# End Source File +# End Group +# Begin Group "Text Files" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\Protocol.txt +# End Source File +# Begin Source File + +SOURCE=.\Tables.txt +# End Source File +# Begin Source File + +SOURCE=.\ThanksTo.txt +# End Source File +# End Group +# End Target +# End Project diff --git a/source/LoginServer/Login.dsw b/source/LoginServer/Login.dsw new file mode 100644 index 0000000..4ed0adf --- /dev/null +++ b/source/LoginServer/Login.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "Login"=.\Login.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/source/LoginServer/Login.vcproj b/source/LoginServer/Login.vcproj new file mode 100644 index 0000000..7c1f7a6 --- /dev/null +++ b/source/LoginServer/Login.vcproj @@ -0,0 +1,542 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/LoginServer/Login.vcxproj b/source/LoginServer/Login.vcxproj new file mode 100644 index 0000000..5622e75 --- /dev/null +++ b/source/LoginServer/Login.vcxproj @@ -0,0 +1,154 @@ + + + + + EQ2Login + x64 + + + + EQ2Login + {BE2C1914-FCCC-4F65-A7DD-105142B36104} + EQ2 Login + 10.0 + + + + v142 + + + + + + + <_ProjectFileVersion>10.0.30319.1 + + + $(SolutionDir)..\source\depends\mariadb-10.1.19\include;$(SolutionDir)..\source\depends\zlib\include;$(SolutionDir)..\source\depends\recastnavigation\Detour\Include;$(SolutionDir)..\source\depends\boost_1_72_0\;$(SolutionDir)..\source\depends\glm\;$(VC_IncludePath);$(WindowsSDK_IncludePath); + $(SolutionDir)..\source\depends\recastnavigation\RecastDemo\Build\vs2019\lib\Debug;$(SolutionDir)..\source\depends\mariadb-10.1.19\lib\64-debug;$(SolutionDir)..\source\depends\zlib\lib;$(SolutionDir)..\source\depends\boost_1_72_0\lib64-msvc-14.2;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64 + false + $(SolutionDir)loginserver\ + .\$(ProjectName)__Debug64\ + $(ProjectName)__Debug64 + + + + Disabled + AnySuitable + _WIN32_WINNT=0x0400;WIN32;NDEBUG;_CONSOLE;LOGIN; EQ2; EQN_DEBUG;_CRT_SECURE_NO_DEPRECATE;_HAS_STD_BYTE=0 +;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebug + false + false + + + $(IntDir) + + + 4996;%(DisableSpecificWarnings) + stdcpp17 + + + odbc32.lib;odbccp32.lib;ws2_32.lib;zlib.lib;mysqlclient.lib;DebugUtils.lib;Detour.lib;DetourCrowd.lib;DetourTileCache.lib;Recast.lib;%(AdditionalDependencies) + LIBCMT;LIBC;%(IgnoreSpecificDefaultLibraries) + true + $(IntDir)$(TargetName).pdb + true + true + Default + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/LoginServer/Login.vcxproj.filters b/source/LoginServer/Login.vcxproj.filters new file mode 100644 index 0000000..fae3865 --- /dev/null +++ b/source/LoginServer/Login.vcxproj.filters @@ -0,0 +1,277 @@ + + + + + {bfe8d6b0-594f-4b55-9f95-101bbcf4069c} + cpp;c;cxx;rc;def;r;odl;idl;hpj;bat + + + {d65b2760-468c-4206-a19a-48323a50ba5a} + h;hpp;hxx;hm;inl + + + {27b769a5-0972-4e9e-b78c-09ad3341579c} + .cpp + + + {11757e5a-691c-49c9-a627-df027ad58326} + .h + + + {99e7f9f9-abcd-4abf-8200-a4b5a467788c} + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + World Files + + + World Files + + + World Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + \ No newline at end of file diff --git a/source/LoginServer/Login.vcxproj.user b/source/LoginServer/Login.vcxproj.user new file mode 100644 index 0000000..ace9a86 --- /dev/null +++ b/source/LoginServer/Login.vcxproj.user @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/source/LoginServer/LoginAccount.cpp b/source/LoginServer/LoginAccount.cpp new file mode 100644 index 0000000..228f0b5 --- /dev/null +++ b/source/LoginServer/LoginAccount.cpp @@ -0,0 +1,58 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#include "LoginAccount.h" + +LoginAccount::LoginAccount(){ +} +LoginAccount::~LoginAccount(){ + vector::iterator iter; + for(iter = charlist.begin(); iter != charlist.end(); iter++){ + safe_delete(*iter); + } +} + +void LoginAccount::flushCharacters ( ) +{ + vector::iterator iter; + for(iter = charlist.begin(); iter != charlist.end(); iter++){ + safe_delete(*iter); + } + + charlist.clear ( ); +} + +CharSelectProfile* LoginAccount::getCharacter(char* name){ + vector::iterator char_iterator; + CharSelectProfile* profile = 0; + EQ2_16BitString temp; + for(char_iterator = charlist.begin(); char_iterator != charlist.end(); char_iterator++){ + profile = *char_iterator; + temp = profile->packet->getType_EQ2_16BitString_ByName("name"); + if(strcmp(temp.data.c_str(), name)==0) + return profile; + } + return 0; +} +void LoginAccount::removeCharacter(char* name, int16 version){ + vector::iterator iter; + CharSelectProfile* profile = 0; + EQ2_16BitString temp; + for(iter = charlist.begin(); iter != charlist.end(); iter++){ + profile = *iter; + temp = profile->packet->getType_EQ2_16BitString_ByName("name"); + if(strcmp(temp.data.c_str(), name)==0){ + if(version <= 561) { + profile->deleted = true; // workaround for char select crash on old clients + } + else { + safe_delete(*iter); + charlist.erase(iter); + } + return; + } + } +} \ No newline at end of file diff --git a/source/LoginServer/LoginAccount.h b/source/LoginServer/LoginAccount.h new file mode 100644 index 0000000..b5bb540 --- /dev/null +++ b/source/LoginServer/LoginAccount.h @@ -0,0 +1,54 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#ifndef _LOGINACCOUNT_ +#define _LOGINACCOUNT_ + +#include +#include +#include +#include "../common/linked_list.h" +#include "PacketHeaders.h" +#include "../common/PacketStruct.h" + +using namespace std; +class LoginAccount { +public: + LoginAccount(); + LoginAccount(int32 id, const char* in_name, const char* in_pass){ + account_id = id; + strcpy(name, in_name); + strcpy(password, in_pass); + } + ~LoginAccount(); + bool SaveAccount(LoginAccount* acct); + vector charlist; + void setName(const char* in_name) { strcpy(name, in_name); } + void setPassword(const char* in_pass) { strcpy(password, in_pass); } + void setAuthenticated(bool in_auth) { authenticated=in_auth; } + void setAccountID(int32 id){ account_id = id; } + void addCharacter(CharSelectProfile* profile){ + charlist.push_back(profile); + } + void removeCharacter(PacketStruct* profile); + void removeCharacter(char* name, int16 version); + void serializeCharacter(uchar* buffer, CharSelectProfile* profile); + + void flushCharacters ( ); + + CharSelectProfile* getCharacter(char* name); + int32 getLoginAccountID(){ return account_id; } + char* getLoginName() { return name; } + char* getLoginPassword() { return password; } + bool getLoginAuthenticated() { return authenticated; } + +private: + int32 account_id; + char name[32]; + char password[32]; + bool authenticated; +}; +#endif \ No newline at end of file diff --git a/source/LoginServer/LoginDatabase.cpp b/source/LoginServer/LoginDatabase.cpp new file mode 100644 index 0000000..b750a62 --- /dev/null +++ b/source/LoginServer/LoginDatabase.cpp @@ -0,0 +1,1070 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#include "../common/debug.h" + +#include +#include +#include +using namespace std; + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#define snprintf _snprintf +#define strncasecmp _strnicmp +#define strcasecmp _stricmp +#else +#include "../common/unix.h" +#include +#endif + +#include "../common/Log.h" +#include "../common/DatabaseNew.h" +#include "LoginDatabase.h" +#include "LoginAccount.h" +#include "../common/MiscFunctions.h" +#include "../common/packet_functions.h" +#include "../common/packet_dump.h" +#include "LWorld.h" + +extern LoginDatabase database; +extern LWorldList world_list; + + +bool LoginDatabase::ConnectNewDatabase() { + return dbLogin.Connect(); +} + +void LoginDatabase::RemoveDeletedCharacterData() +{ + dbLogin.Query("DELETE FROM login_char_colors WHERE login_characters_id IN (SELECT id FROM login_characters WHERE deleted = 1)"); + dbLogin.Query("DELETE FROM login_equipment WHERE login_characters_id IN (SELECT id FROM login_characters WHERE deleted = 1)"); +} + +void LoginDatabase::SetZoneInformation(int32 server_id, int32 zone_id, int32 version, PacketStruct* packet){ + if(packet){ + Query query; + MYSQL_RES* result = 0; + + if ( version >= 1212 ) + result = query.RunQuery2(Q_SELECT, "SELECT name, description from ls_world_zones where server_id=%i and zone_id=%i", server_id, zone_id); + + MYSQL_ROW row; + if(result && (row = mysql_fetch_row(result))) { + if (row[0]) + packet->setMediumStringByName("zone", row[0]); + else + packet->setMediumStringByName("zone", " "); + if(row[1]) + packet->setMediumStringByName("zonedesc", row[1]); + else + packet->setMediumStringByName("zonedesc", " "); + } + else{ + Query query2; + MYSQL_RES* result2 = 0; + + if (version < 1212) + result2 = query2.RunQuery2(Q_SELECT, "SELECT file, description from zones where id=%i", zone_id); + else + result2 = query2.RunQuery2(Q_SELECT, "SELECT name, description from zones where id=%i", zone_id); + + MYSQL_ROW row2; + if(result2 && (row2 = mysql_fetch_row(result2))) { + + if (version < 546 && version > 561 && version < 1212) + { + if (row2[0]) + { + int len = strlen(row2[0]); + char* zoneName = new char[len + 2]; + strncpy(zoneName, row2[0], len); + zoneName[len] = 0x2E; + zoneName[len + 1] = 0x30; + + packet->setMediumStringByName("zone", zoneName); + safe_delete_array(zoneName); + } + else + packet->setMediumStringByName("zone", ".0"); + } + else + { + if (row2[0]) + packet->setMediumStringByName("zone", row2[0]); + else + packet->setMediumStringByName("zone", " "); + } + if(row2[1]) + packet->setMediumStringByName("zonedesc", row2[1]); + else + packet->setMediumStringByName("zonedesc", " "); + } + } + packet->setMediumStringByName("zonename2"," "); + } +} + +string LoginDatabase::GetZoneDescription(char* name){ + string ret; + Query query; + query.escaped_name = getEscapeString(name); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT description from zones where file=substring_index('%s', '.', 1)", query.escaped_name); + MYSQL_ROW row; + if((row = mysql_fetch_row(result))) { + ret = string(row[0]); + } + return ret; +} + + +int32 LoginDatabase::GetLoginCharacterIDFromWorldCharID(int32 server_id, int32 char_id) +{ + int32 ret; + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id FROM login_characters WHERE server_id = %u AND char_id = %u AND deleted = 0 LIMIT 0,1", server_id, char_id); + MYSQL_ROW row; + if((row = mysql_fetch_row(result))) { + ret = atoi(row[0]); + } + + return ret; +} + +void LoginDatabase::SetServerEquipmentAppearances(int32 server_id, map equip_updates) +{ + + if(equip_updates.size() > 0) + { + + LogWrite(LOGIN__DEBUG, 0, "Login", "Saving appearance info from world %u...", server_id); + + map::iterator equip_itr; + stringstream ss; + ss << "replace into login_equipment (login_characters_id, equip_type, red, green, blue, highlight_red, highlight_green, highlight_blue, slot) values"; + int count=0; + int32 char_id = 0; + + for(equip_itr = equip_updates.begin(); equip_itr != equip_updates.end(); equip_itr++) + { + char_id = GetLoginCharacterIDFromWorldCharID(server_id, (int32)equip_itr->second.world_char_id); + + if( char_id == 0 ) // invalid character/world match + continue; + + LogWrite(LOGIN__DEBUG, 5, "Login", "--Processing character %u, slot %i", char_id, (int32)equip_itr->second.slot); + + if(count > 0) + ss << ", "; + + ss << "(" << char_id << ", "; + ss << (int32)equip_itr->second.equip_type << ", "; + ss << (int32)equip_itr->second.red << ", "; + ss << (int32)equip_itr->second.green << ", "; + ss << (int32)equip_itr->second.blue << ", "; + ss << (int32)equip_itr->second.highlight_red << ", "; + ss << (int32)equip_itr->second.highlight_green << ", "; + ss << (int32)equip_itr->second.highlight_blue << ", "; + ss << (int32)equip_itr->second.slot << ")"; + + count++; + } + + Query query; + query.RunQuery2(ss.str(), Q_REPLACE); + + if (query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF) + LogWrite(LOGIN__ERROR, 0, "Login", "Error saving login_equipment data Error: ", query.GetError()); + } +} + + +void LoginDatabase::SetServerZoneDescriptions(int32 server_id, map zone_descriptions){ + if(zone_descriptions.size() > 0){ + map::iterator zone_itr; + string query_string = "replace into ls_world_zones (server_id, zone_id, name, description) values"; + int count=0; + char server_id_str[12] = {0}; + sprintf(server_id_str, "%i", server_id); + for(zone_itr = zone_descriptions.begin(); zone_itr != zone_descriptions.end(); zone_itr++, count++){ + char zone_id_str[12] = {0}; + sprintf(zone_id_str, "%i", zone_itr->first); + if(count > 0) + query_string.append(", "); + query_string.append("(").append(server_id_str).append(","); + query_string.append(zone_id_str).append(","); + query_string.append("'").append(getSafeEscapeString(zone_itr->second.name.c_str()).c_str()).append("', '"); + query_string.append(getSafeEscapeString(zone_itr->second.description.c_str()).c_str()).append("')"); + } + Query query; + query.RunQuery2(query_string, Q_REPLACE); + } +} + +//this is really just for the version that doesn't send the server id in its play request +int32 LoginDatabase::GetServer(int32 accountID, int32 charID, string name) { + int32 id = 0; + Query query; + MYSQL_ROW row; + query.escaped_name = getEscapeString(name.c_str()); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT server_id from login_characters where account_id=%i and char_id=%i and name='%s'", accountID, charID, query.escaped_name); + if (result && mysql_num_rows(result) == 1) { + row = mysql_fetch_row(result); + id = atoi(row[0]); + } + return id; +} + +void LoginDatabase::LoadCharacters(LoginAccount* acct, int16 version){ + if(acct != NULL) + acct->flushCharacters ( ); + + Query query; + Query query2; + int32 id = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT lc.char_id, lc.server_id, lc.name, lc.race, lc.class, lc.gender, lc.deity, lc.body_size, lc.body_age, lc.current_zone_id, lc.level, lc.soga_wing_type, lc.soga_chest_type, lc.soga_legs_type, lc.soga_hair_type, lc.legs_type, lc.chest_type, lc.wing_type, lc.hair_type, unix_timestamp(lc.created_date), unix_timestamp(lc.last_played), lc.id, lw.name, lc.facial_hair_type, lc.soga_facial_hair_type, lc.soga_model_type, lc.model_type from login_characters lc, login_worldservers lw where lw.id = lc.server_id and lc.account_id=%i and lc.deleted=0",acct->getLoginAccountID()); + if(result) { + MYSQL_ROW row; + MYSQL_ROW row2; + MYSQL_ROW row3; + while ((row = mysql_fetch_row(result))) { + CharSelectProfile* player = new CharSelectProfile(version); + id = atoul(row[0]); + //for (int i = 0; i < 10; i++) + // player->packet->setDataByName("hair_type", 0, i); + //player->packet->setDataByName("test23", 413); + //player->packet->setDataByName("test24", 414); + player->packet->setDataByName("charid", id); + player->packet->setDataByName("server_id", atoul(row[1])); + player->packet->setMediumStringByName("name", row[2]); + player->packet->setDataByName("race", atoi(row[3])); + player->packet->setDataByName("class", atoi(row[4])); + player->packet->setDataByName("gender", atoi(row[5])); + player->packet->setDataByName("deity", atoi(row[6])); + player->packet->setDataByName("body_size", atof(row[7])); + player->packet->setDataByName("body_age", atof(row[8])); + SetZoneInformation(atoi(row[1]), atoi(row[9]), version, player->packet); + player->packet->setDataByName("level", atoi(row[10])); + if(atoi(row[11]) > 0) + player->packet->setDataByName("soga_wing_type", atoi(row[11])); + else + player->packet->setDataByName("soga_wing_type", atoi(row[17])); + if(atoi(row[12]) > 0) + player->packet->setDataByName("soga_chest_type", atoi(row[12])); + else + player->packet->setDataByName("soga_chest_type", atoi(row[16])); + if(atoi(row[13]) > 0) + player->packet->setDataByName("soga_legs_type", atoi(row[13])); + else + player->packet->setDataByName("soga_legs_type", atoi(row[15])); + if(atoi(row[14]) > 0) + player->packet->setDataByName("soga_hair_type", atoi(row[14])); + else + player->packet->setDataByName("soga_hair_type", atoi(row[18])); + player->packet->setDataByName("legs_type", atoi(row[15])); + player->packet->setDataByName("chest_type", atoi(row[16])); + player->packet->setDataByName("wing_type", atoi(row[17])); + player->packet->setDataByName("hair_type", atoi(row[18])); + player->packet->setDataByName("created_date", atol(row[19])); + if (row[20]) + player->packet->setDataByName("last_played", atol(row[20])); + if(version == 546 || version == 561) + player->packet->setDataByName("version", 11); + else if(version >= 887) + player->packet->setDataByName("version", 6); + else + player->packet->setDataByName("version", 5); + player->packet->setDataByName("account_id", acct->getLoginAccountID()); + player->packet->setDataByName("account_id2", acct->getLoginAccountID()); + + LoadAppearanceData(atoul(row[21]), player->packet); + + if(row[22]) + player->packet->setMediumStringByName("server_name", row[22]); + player->packet->setDataByName("hair_face_type", atoi(row[23])); + if(atoi(row[24]) > 0) + player->packet->setDataByName("soga_hair_face_type", atoi(row[24])); + else + player->packet->setDataByName("soga_hair_face_type", atoi(row[23])); + if(atoi(row[25]) > 0) + player->packet->setDataByName("soga_race_type", atoi(row[25])); + else + player->packet->setDataByName("soga_race_type", atoi(row[26])); + player->packet->setDataByName("race_type", atoi(row[26])); + + player->packet->setDataByName("unknown3", 57); + player->packet->setDataByName("unknown4", 56); + player->packet->setDataByName("unknown6", 1, 1); //if not here will not display character + player->packet->setDataByName("unknown8", 15); + player->packet->setDataByName("unknown13", 212); + player->packet->setColorByName("unknown14", 0xFF, 0xFF, 0xFF); + + uchar tmp[] = {0xFF, 0xFF, 0xFF, 0x61, 0x00, 0x2C, 0x04, 0xA5, 0x09, 0x02, 0x0F, 0x00, 0x00}; + for(size_t y=0;ypacket->setDataByName("unknown11", tmp[y], y); + MYSQL_RES* result3 = query2.RunQuery2(Q_SELECT, "SELECT slot, equip_type, red, green, blue, highlight_red, highlight_green, highlight_blue from login_equipment where login_characters_id=%i order by slot",atoi(row[21])); + if(result3){ + for(int i=0;(row3 = mysql_fetch_row(result3)) && i<24; i++){ + player->packet->setEquipmentByName("equip", atoi(row3[1]), atoi(row3[2]), atoi(row3[3]), atoi(row3[4]), atoi(row3[5]), atoi(row3[6]), atoi(row3[7]), atoi(row3[0])); + } + } + player->packet->setDataByName("mount", 1377); + player->packet->setDataByName("mount_color1", 57); + /* + enum NetAppearance::NetAppearanceFlags + { + NAF_INVISIBLE=1, + NAF_SHOW_HOOD=2 + }; + */ + acct->addCharacter(player); + } + } + else + LogWrite(LOGIN__ERROR, 0, "Login", "Error in LoadCharacters query '%s': %s", query.GetQuery(), query.GetError()); + +} + +void LoginDatabase::CheckCharacterTimeStamps(LoginAccount* acct){ + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT char_id, unix_timestamp from login_characters where account_id=%i",acct->getLoginAccountID()); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + + ServerPacket* outpack = new ServerPacket(ServerOP_CharTimeStamp, sizeof(CharacterTimeStamp_Struct)); + CharacterTimeStamp_Struct* cts = (CharacterTimeStamp_Struct*) outpack->pBuffer; + cts->account_id = acct->getLoginAccountID(); + int32 server_id = 0; + LWorld* world_server = 0; + while ((row = mysql_fetch_row(result))) { + server_id = atoi(row[1]); + if(server_id != 0) + world_server = world_list.FindByAccount(server_id, World); + if(world_server) // If the pointer is 0, the world server must be down, we can't do any updates... + { + cts->char_id = atoi(row[0]); + cts->unix_timestamp = atoi(row[1]); + world_server->SendPacket(outpack); + //Reset for next character + world_server = 0; + server_id = 0; + } + } + safe_delete(outpack); + } +} + +void LoginDatabase::SaveCharacterFloats(int32 char_id, char* type, float float1, float float2, float float3,float multiplier){ + Query query; + string create_char = string("insert into login_char_colors (login_characters_id, type, red, green, blue, signed_value) values(%i,'%s',%i,%i,%i, 1)"); + query.RunQuery2(Q_INSERT, create_char.c_str(), char_id, type, (sint8)(float1*multiplier), (sint8)(float2*multiplier), (sint8)(float3*multiplier)); +} + +void LoginDatabase::SaveCharacterColors(int32 char_id, char* type, EQ2_Color color){ + Query query; + string create_char = string("insert into login_char_colors (login_characters_id, type, red, green, blue) values(%i,'%s',%i,%i,%i)"); + query.RunQuery2(Q_INSERT, create_char.c_str(), char_id, type, color.red, color.green, color.blue); +} + +void LoginDatabase::LoadAppearanceData(int32 char_id, PacketStruct* char_select_packet){ + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT type, signed_value, red, green, blue from login_char_colors where login_characters_id = %i",char_id); + while((row = mysql_fetch_row(result))){ + if(atoi(row[1]) == 0) + char_select_packet->setColorByName(row[0], atoul(row[2]), atoul(row[3]), atoul(row[4])); + else{ + char_select_packet->setDataByName(row[0], atoi(row[2]), 0); + char_select_packet->setDataByName(row[0], atoi(row[3]), 1); + char_select_packet->setDataByName(row[0], atoi(row[4]), 2); + } + } +} +int16 LoginDatabase::GetAppearanceID(string name){ + int32 id = 0; + Query query; + MYSQL_ROW row; + query.escaped_name = getEscapeString(name.c_str()); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT appearance_id from appearances where name='%s'", query.escaped_name); + if(result && mysql_num_rows(result) == 1){ + row = mysql_fetch_row(result); + id = atoi(row[0]); + } + return id; +} + +void LoginDatabase::DeactivateCharID(int32 server_id, int32 char_id, int32 exception_id){ + Query query; + query.RunQuery2(Q_UPDATE, "update login_characters set deleted=1 where char_id=%u and server_id=%u and id!=%u",char_id,server_id,exception_id); +} + +int32 LoginDatabase::SaveCharacter(PacketStruct* create, LoginAccount* acct, int32 world_charid, int32 client_version){ + int32 ret_id = 0; + Query query; + string create_char = + string("Insert into login_characters (account_id, server_id, char_id, name, race, class, gender, deity, body_size, body_age, soga_wing_type, soga_chest_type, soga_legs_type, soga_hair_type, soga_facial_hair_type, legs_type, chest_type, wing_type, hair_type, facial_hair_type, soga_model_type, model_type)" + " values(%i, %i, %i, '%s', %i, %i, %i, %i, %f, %f, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i)"); + query.RunQuery2(Q_INSERT, create_char.c_str(), + acct->getLoginAccountID(), + create->getType_int32_ByName("server_id"), world_charid, + create->getType_EQ2_16BitString_ByName("name").data.c_str(), + create->getType_int8_ByName("race"), + create->getType_int8_ByName("class"), + create->getType_int8_ByName("gender"), + create->getType_int8_ByName("deity"), + create->getType_float_ByName("body_size"), + create->getType_float_ByName("body_age"), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("soga_wing_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("soga_chest_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("soga_legs_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("soga_hair_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("soga_face_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("legs_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("chest_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("wing_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("hair_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("face_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("soga_race_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("race_file").data)); + if(query.GetError() && strlen(query.GetError()) > 0){ + LogWrite(LOGIN__ERROR, 0, "Login", "Error in SaveCharacter query '%s': %s", query.GetQuery(), query.GetError()); + return 0; + } + + int32 last_insert_id = query.GetLastInsertedID(); + + //mark any remaining characters with same id as deleted (creates problems if world deleted their db and started assigning new char ids) + DeactivateCharID(create->getType_int32_ByName("server_id"), world_charid, last_insert_id); + int32 char_id = last_insert_id; + if (client_version <= 561) { + float classic_multiplier = 250.0f; + SaveCharacterFloats(char_id, "skin_color", create->getType_float_ByName("skin_color", 0), create->getType_float_ByName("skin_color", 1), create->getType_float_ByName("skin_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "eye_color", create->getType_float_ByName("eye_color", 0), create->getType_float_ByName("eye_color", 1), create->getType_float_ByName("eye_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_color1", create->getType_float_ByName("hair_color1", 0), create->getType_float_ByName("hair_color1", 1), create->getType_float_ByName("hair_color1", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_color2", create->getType_float_ByName("hair_color2", 0), create->getType_float_ByName("hair_color2", 1), create->getType_float_ByName("hair_color2", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_highlight", create->getType_float_ByName("hair_highlight", 0), create->getType_float_ByName("hair_highlight", 1), create->getType_float_ByName("hair_highlight", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_type_color", create->getType_float_ByName("hair_type_color", 0), create->getType_float_ByName("hair_type_color", 1), create->getType_float_ByName("hair_type_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_type_highlight_color", create->getType_float_ByName("hair_type_highlight_color", 0), create->getType_float_ByName("hair_type_highlight_color", 1), create->getType_float_ByName("hair_type_highlight_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_type_color", create->getType_float_ByName("hair_type_color", 0), create->getType_float_ByName("hair_type_color", 1), create->getType_float_ByName("hair_type_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_type_highlight_color", create->getType_float_ByName("hair_type_highlight_color", 0), create->getType_float_ByName("hair_type_highlight_color", 1), create->getType_float_ByName("hair_type_highlight_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_face_color", create->getType_float_ByName("hair_face_color", 0), create->getType_float_ByName("hair_face_color", 1), create->getType_float_ByName("hair_face_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_face_highlight_color", create->getType_float_ByName("hair_face_highlight_color", 0), create->getType_float_ByName("hair_face_highlight_color", 1), create->getType_float_ByName("hair_face_highlight_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "shirt_color", create->getType_float_ByName("shirt_color", 0), create->getType_float_ByName("shirt_color", 1), create->getType_float_ByName("shirt_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "unknown_chest_color", create->getType_float_ByName("unknown_chest_color", 0), create->getType_float_ByName("unknown_chest_color", 1), create->getType_float_ByName("unknown_chest_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "pants_color", create->getType_float_ByName("pants_color", 0), create->getType_float_ByName("pants_color", 1), create->getType_float_ByName("pants_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "unknown_legs_color", create->getType_float_ByName("unknown_legs_color", 0), create->getType_float_ByName("unknown_legs_color", 1), create->getType_float_ByName("unknown_legs_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "unknown9", create->getType_float_ByName("unknown9", 0), create->getType_float_ByName("unknown9", 1), create->getType_float_ByName("unknown9", 2), classic_multiplier); + } + else { + SaveCharacterColors(char_id, "skin_color", create->getType_EQ2_Color_ByName("skin_color")); + SaveCharacterColors(char_id, "model_color", create->getType_EQ2_Color_ByName("model_color")); + SaveCharacterColors(char_id, "eye_color", create->getType_EQ2_Color_ByName("eye_color")); + SaveCharacterColors(char_id, "hair_color1", create->getType_EQ2_Color_ByName("hair_color1")); + SaveCharacterColors(char_id, "hair_color2", create->getType_EQ2_Color_ByName("hair_color2")); + SaveCharacterColors(char_id, "hair_highlight", create->getType_EQ2_Color_ByName("hair_highlight")); + SaveCharacterColors(char_id, "hair_type_color", create->getType_EQ2_Color_ByName("hair_type_color")); + SaveCharacterColors(char_id, "hair_type_highlight_color", create->getType_EQ2_Color_ByName("hair_type_highlight_color")); + SaveCharacterColors(char_id, "hair_face_color", create->getType_EQ2_Color_ByName("hair_face_color")); + SaveCharacterColors(char_id, "hair_face_highlight_color", create->getType_EQ2_Color_ByName("hair_face_highlight_color")); + SaveCharacterColors(char_id, "wing_color1", create->getType_EQ2_Color_ByName("wing_color1")); + SaveCharacterColors(char_id, "wing_color2", create->getType_EQ2_Color_ByName("wing_color2")); + SaveCharacterColors(char_id, "shirt_color", create->getType_EQ2_Color_ByName("shirt_color")); + SaveCharacterColors(char_id, "unknown_chest_color", create->getType_EQ2_Color_ByName("unknown_chest_color")); + SaveCharacterColors(char_id, "pants_color", create->getType_EQ2_Color_ByName("pants_color")); + SaveCharacterColors(char_id, "unknown_legs_color", create->getType_EQ2_Color_ByName("unknown_legs_color")); + SaveCharacterColors(char_id, "unknown9", create->getType_EQ2_Color_ByName("unknown9")); + + SaveCharacterColors(char_id, "soga_skin_color", create->getType_EQ2_Color_ByName("soga_skin_color")); + SaveCharacterColors(char_id, "soga_model_color", create->getType_EQ2_Color_ByName("soga_model_color")); + SaveCharacterColors(char_id, "soga_eye_color", create->getType_EQ2_Color_ByName("soga_eye_color")); + SaveCharacterColors(char_id, "soga_hair_color1", create->getType_EQ2_Color_ByName("soga_hair_color1")); + SaveCharacterColors(char_id, "soga_hair_color2", create->getType_EQ2_Color_ByName("soga_hair_color2")); + SaveCharacterColors(char_id, "soga_hair_highlight", create->getType_EQ2_Color_ByName("soga_hair_highlight")); + SaveCharacterColors(char_id, "soga_hair_type_color", create->getType_EQ2_Color_ByName("soga_hair_type_color")); + SaveCharacterColors(char_id, "soga_hair_type_highlight_color", create->getType_EQ2_Color_ByName("soga_hair_type_highlight_color")); + SaveCharacterColors(char_id, "soga_hair_face_color", create->getType_EQ2_Color_ByName("soga_hair_face_color")); + SaveCharacterColors(char_id, "soga_hair_face_highlight_color", create->getType_EQ2_Color_ByName("soga_hair_face_highlight_color")); + SaveCharacterColors(char_id, "soga_wing_color1", create->getType_EQ2_Color_ByName("soga_wing_color1")); + SaveCharacterColors(char_id, "soga_wing_color2", create->getType_EQ2_Color_ByName("soga_wing_color2")); + SaveCharacterColors(char_id, "soga_shirt_color", create->getType_EQ2_Color_ByName("soga_shirt_color")); + SaveCharacterColors(char_id, "soga_unknown_chest_color", create->getType_EQ2_Color_ByName("soga_unknown_chest_color")); + SaveCharacterColors(char_id, "soga_pants_color", create->getType_EQ2_Color_ByName("soga_pants_color")); + SaveCharacterColors(char_id, "soga_unknown_legs_color", create->getType_EQ2_Color_ByName("soga_unknown_legs_color")); + SaveCharacterColors(char_id, "soga_unknown13", create->getType_EQ2_Color_ByName("soga_unknown13")); + SaveCharacterFloats(char_id, "soga_eye_type", create->getType_float_ByName("soga_eyes2", 0), create->getType_float_ByName("soga_eyes2", 1), create->getType_float_ByName("soga_eyes2", 2)); + SaveCharacterFloats(char_id, "soga_ear_type", create->getType_float_ByName("soga_ears", 0), create->getType_float_ByName("soga_ears", 1), create->getType_float_ByName("soga_ears", 2)); + SaveCharacterFloats(char_id, "soga_eye_brow_type", create->getType_float_ByName("soga_eye_brows", 0), create->getType_float_ByName("soga_eye_brows", 1), create->getType_float_ByName("soga_eye_brows", 2)); + SaveCharacterFloats(char_id, "soga_cheek_type", create->getType_float_ByName("soga_cheeks", 0), create->getType_float_ByName("soga_cheeks", 1), create->getType_float_ByName("soga_cheeks", 2)); + SaveCharacterFloats(char_id, "soga_lip_type", create->getType_float_ByName("soga_lips", 0), create->getType_float_ByName("soga_lips", 1), create->getType_float_ByName("soga_lips", 2)); + SaveCharacterFloats(char_id, "soga_chin_type", create->getType_float_ByName("soga_chin", 0), create->getType_float_ByName("soga_chin", 1), create->getType_float_ByName("soga_chin", 2)); + SaveCharacterFloats(char_id, "soga_nose_type", create->getType_float_ByName("soga_nose", 0), create->getType_float_ByName("soga_nose", 1), create->getType_float_ByName("soga_nose", 2)); + } + SaveCharacterFloats(char_id, "eye_type", create->getType_float_ByName("eyes2", 0), create->getType_float_ByName("eyes2", 1), create->getType_float_ByName("eyes2", 2)); + SaveCharacterFloats(char_id, "ear_type", create->getType_float_ByName("ears", 0), create->getType_float_ByName("ears", 1), create->getType_float_ByName("ears", 2)); + SaveCharacterFloats(char_id, "eye_brow_type", create->getType_float_ByName("eye_brows", 0), create->getType_float_ByName("eye_brows", 1), create->getType_float_ByName("eye_brows", 2)); + SaveCharacterFloats(char_id, "cheek_type", create->getType_float_ByName("cheeks", 0), create->getType_float_ByName("cheeks", 1), create->getType_float_ByName("cheeks", 2)); + SaveCharacterFloats(char_id, "lip_type", create->getType_float_ByName("lips", 0), create->getType_float_ByName("lips", 1), create->getType_float_ByName("lips", 2)); + SaveCharacterFloats(char_id, "chin_type", create->getType_float_ByName("chin", 0), create->getType_float_ByName("chin", 1), create->getType_float_ByName("chin", 2)); + SaveCharacterFloats(char_id, "nose_type", create->getType_float_ByName("nose", 0), create->getType_float_ByName("nose", 1), create->getType_float_ByName("nose", 2)); + SaveCharacterFloats(char_id, "body_size", create->getType_float_ByName("body_size", 0), 0, 0); + return ret_id; +} + +bool LoginDatabase::DeleteCharacter(int32 account_id, int32 character_id, int32 server_id){ + Query query; + string delete_char = string("delete from login_characters where char_id=%i and account_id=%i and server_id=%i"); + query.RunQuery2(Q_DELETE, delete_char.c_str(),character_id,account_id,server_id); + if(!query.GetAffectedRows()) + { + //No error just in case ppl try doing stupid stuff + return false; + } + + return true; +} + +string LoginDatabase::GetCharacterName(int32 char_id, int32 server_id, int32 account_id){ + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT name from login_characters where char_id=%lu and server_id=%lu and account_id=%lu and deleted = 0 limit 1", char_id, server_id, account_id); + + if(result && mysql_num_rows(result) == 1){ + row = mysql_fetch_row(result); + return string(row[0]); + } + return string(""); +} + +bool LoginDatabase::UpdateCharacterTimeStamp(int32 account_id, int32 character_id, int32 timestamp_update, int32 server_id){ + Query query; + string update_charts = string("update login_characters set unix_timestamp=%lu where char_id=%lu and account_id=%lu and server_id=%lu"); + query.RunQuery2(Q_UPDATE, update_charts.c_str(),timestamp_update,character_id,account_id,server_id); + if(!query.GetAffectedRows()) + { + LogWrite(LOGIN__ERROR, 0, "Login", "Error in UpdateCharacterTimeStamp query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + + return true; +} + +bool LoginDatabase::UpdateCharacterLevel(int32 account_id, int32 character_id, int8 in_level, int32 server_id){ + Query query; + string update_charts = string("update login_characters set level=%i where char_id=%lu and account_id=%lu and server_id=%lu"); + query.RunQuery2(Q_UPDATE, update_charts.c_str(),in_level,character_id,account_id,server_id); + if(!query.GetAffectedRows()) + { + LogWrite(LOGIN__ERROR, 0, "Login", "Error in UpdateCharacterLevel query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + + return true; +} + +bool LoginDatabase::UpdateCharacterRace(int32 account_id, int32 character_id, int16 in_racetype, int8 in_race, int32 server_id){ + Query query; + string update_charts = string("update login_characters set race_type=%i, race=%i where char_id=%lu and account_id=%lu and server_id=%lu"); + query.RunQuery2(Q_UPDATE, update_charts.c_str(),in_racetype,in_race,character_id,account_id,server_id); + if(!query.GetAffectedRows()) + { + LogWrite(LOGIN__ERROR, 0, "Login", "Error in UpdateCharacterRace query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + + return true; +} + +bool LoginDatabase::UpdateCharacterZone(int32 account_id, int32 character_id, int32 zone_id, int32 server_id){ + Query query; + string update_chars = string("update login_characters set current_zone_id=%i where char_id=%lu and account_id=%lu and server_id=%lu"); + query.RunQuery2(Q_UPDATE, update_chars.c_str(), zone_id, character_id, account_id, server_id); + if(!query.GetAffectedRows()) + { + LogWrite(LOGIN__ERROR, 0, "Login", "Error in UpdateCharacterZone query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + + return true; +} + +bool LoginDatabase::UpdateCharacterClass(int32 account_id, int32 character_id, int8 in_class, int32 server_id){ + Query query; + string update_charts = string("update login_characters set class=%i where char_id=%lu and account_id=%lu and server_id=%lu"); + query.RunQuery2(Q_UPDATE, update_charts.c_str(),in_class,character_id,account_id,server_id); + if(!query.GetAffectedRows()) + { + LogWrite(LOGIN__ERROR, 0, "Login", "Error in UpdateCharacterClass query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + + return true; +} + +bool LoginDatabase::UpdateCharacterGender(int32 account_id, int32 character_id, int8 in_gender, int32 server_id){ + Query query; + string update_charts = string("update login_characters set gender=%i where char_id=%lu and account_id=%lu and server_id=%lu"); + query.RunQuery2(Q_UPDATE, update_charts.c_str(),in_gender,character_id,account_id,server_id); + if(!query.GetAffectedRows()) + { + LogWrite(LOGIN__ERROR, 0, "Login", "Error in UpdateCharacterClass query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + + return true; +} + +LoginAccount* LoginDatabase::LoadAccount(const char* name, const char* password, bool attemptAccountCreation){ + LoginAccount* acct = NULL; + Query query; + query.escaped_name = getEscapeString(name); + query.escaped_pass = getEscapeString(password); + time_t now = time(0); //get the current epoc time + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id from account where name='%s' and passwd=sha2('%s',512)", query.escaped_name, query.escaped_pass); + if(result){ + if (mysql_num_rows(result) == 1){ + row = mysql_fetch_row(result); + + int32 id = atol(row[0]); + + acct = new LoginAccount(id, name, password); + acct->setAuthenticated(true); + } + else if(mysql_num_rows(result) > 0) + LogWrite(LOGIN__ERROR, 0, "Login", "Error in LoginAccount: more than one account returned for '%s'", name); + else if (attemptAccountCreation && !database.GetAccountIDByName(name)) + { + Query newquery; + newquery.RunQuery2(Q_INSERT, "insert into account set name='%s',passwd=sha2('%s',512), created_date=%i", query.escaped_name, query.escaped_pass, now); + // re-run the query for select only not account creation + return LoadAccount(name, password, false); + } + + } + return acct; +} + +int32 LoginDatabase::GetAccountIDByName(const char* name) { + int32 id = 0; + Query query; + MYSQL_ROW row; + query.escaped_name = getEscapeString(name); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id from account where name='%s'", query.escaped_name); + if (result && mysql_num_rows(result) == 1) { + row = mysql_fetch_row(result); + id = atoi(row[0]); + } + return id; +} + +int32 LoginDatabase::CheckServerAccount(char* name, char* passwd){ + int32 id = 0; + Query query; + MYSQL_ROW row; + query.escaped_name = getEscapeString(name); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT lower(password), id from login_worldservers where account='%s' and disabled = 0", query.escaped_name); + + LogWrite(LOGIN__INFO, 0, "Login", "WorldServer CheckServerAccount Account=%s\nSHA=%s", (char*)query.escaped_name, passwd); + if(result && mysql_num_rows(result) == 1){ + row = mysql_fetch_row(result); + + LogWrite(LOGIN__INFO, 0, "Login", "WorldServer CheckServerAccountResult Account=%s\nPassword=%s", (char*)query.escaped_name, (row && row[0]) ? row[0] : "(NULL)"); + + if (memcmp(row[0], passwd, strnlen(row[0], 256)) == 0) + { + LogWrite(LOGIN__INFO, 0, "Login", "WorldServer CheckServerAccountResultMatch Account=%s", (char*)query.escaped_name); + id = atoi(row[1]); + } + } + return id; +} + +bool LoginDatabase::IsServerAccountDisabled(char* name){ + Query query; + MYSQL_ROW row; + query.escaped_name = getEscapeString(name); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id from login_worldservers where account='%s' and disabled = 1", query.escaped_name); + + LogWrite(LOGIN__DEBUG, 0, "Login", "WorldServer IsServerAccountDisabled Account=%s", (char*)query.escaped_name); + if(result && mysql_num_rows(result) > 0){ + row = mysql_fetch_row(result); + + LogWrite(LOGIN__INFO, 0, "Login", "WorldServer IsServerAccountDisabled Match Account=%s", (char*)query.escaped_name); + + return true; + } + return false; +} + +bool LoginDatabase::IsIPBanned(char* ipaddr){ + if(!ipaddr) + return false; + + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT ip from login_bannedips where '%s' LIKE CONCAT(ip ,'%%')", ipaddr); + + LogWrite(LOGIN__DEBUG, 0, "Login", "WorldServer IsServerIPBanned IPPartial=%s", (char*)ipaddr); + if(result && mysql_num_rows(result) > 0){ + row = mysql_fetch_row(result); + + LogWrite(LOGIN__INFO, 0, "Login", "WorldServer IsServerIPBanned Match IPBan=%s", row[0]); + + return true; + } + return false; +} + +void LoginDatabase::GetServerAccounts(vector* server_list){ + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, account, name, admin_id from login_worldservers"); + while((row = mysql_fetch_row(result))){ + LWorld* world = new LWorld(atol(row[0]), row[1], row[2], atoi(row[3])); + world->SetID(world->GetAccountID()); + server_list->push_back(world); + } +} +void LoginDatabase::SaveClientLog(const char* type, const char* message, const char* player_name, int16 version){ + Query query; + query.escaped_data1 = getEscapeString(message); + query.escaped_name = getEscapeString(player_name); + query.RunQuery2(Q_INSERT, "insert into log_messages (type, message, name, version) values('%s', '%s', '%s', %i)", type, query.escaped_data1, query.escaped_name, version); +} +bool LoginDatabase::VerifyDelete(int32 account_id, int32 character_id, const char* name){ + Query query; + query.escaped_name = getEscapeString(name); + query.RunQuery2(Q_UPDATE, "update login_characters set deleted = 1 where char_id=%i and account_id=%i and name='%s'", character_id, account_id, query.escaped_name); + if(query.GetAffectedRows() == 1) + return true; + else + return false; +} +char* LoginDatabase::GetServerAccountName(int32 id){ + Query query; + MYSQL_ROW row; + char* name = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT name from login_worldservers where id=%lu", id); + if(result && mysql_num_rows(result) == 1){ + row = mysql_fetch_row(result); + if(strlen(row[0]) > 0){ + name = new char[strlen(row[0])+1]; + strcpy(name, row[0]); + } + } + return name; +} +int32 LoginDatabase::GetRaceID(char* name){ + int32 ret = 1487; + Query query; + MYSQL_ROW row; + query.escaped_name = getEscapeString(name); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT race_type from login_races where name='%s'", query.escaped_name); + if(result && mysql_num_rows(result) == 1){ + row = mysql_fetch_row(result); + ret = atol(row[0]); + } + else if(!result || mysql_num_rows(result) == 0) + UpdateRaceID(query.escaped_name); + return ret; +} +void LoginDatabase::UpdateRaceID(char* name){ + Query query; + query.RunQuery2(Q_UPDATE, "insert into login_races (name) values('%s')", name); +} +bool LoginDatabase::CheckVersion(char* in_version){ + Query query; + query.escaped_data1 = getEscapeString(in_version); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id from login_versions where version='%s' or version='*'", query.escaped_data1); + if(result && mysql_num_rows(result) > 0) + return true; + else + return false; +} +void LoginDatabase::GetLatestTableVersions(LatestTableVersions* table_versions){ + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT name, max(version) from login_table_versions group by name order by id"); + if(result && mysql_num_rows(result) > 0){ + table_versions->SetTableSize(mysql_num_rows(result)); + } + else // we need to return if theres no result, otherwise it will crash attempting to loop through rows + + return; + while((row = mysql_fetch_row(result))){ + if(VerifyDataTable(row[0])) + table_versions->AddTable(row[0], atoi(row[1]), GetDataVersion(row[0])); + else + table_versions->AddTable(row[0], atoi(row[1]), 0); + } +} +bool LoginDatabase::VerifyDataTable(char* name){ + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT table_name from download_tables where table_name='%s'", name); + if(result && mysql_num_rows(result) > 0) + return true; + return false; +} +string LoginDatabase::GetColumnNames(char* name){ + Query query; + MYSQL_ROW row; + string columns = "("; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "show columns from %s", name); + if(result && mysql_num_rows(result) > 0){ + int16 i = 0; + while((row = mysql_fetch_row(result))){ + if(strcmp(row[0], "table_data_version") != 0){ + if(i>0) + columns.append(","); + columns.append(row[0]); + i++; + } + } + } + columns.append(") "); + return columns; +} +TableDataQuery* LoginDatabase::GetTableDataQuery(int32 server_ip, char* name, int16 version){ + Query query; + MYSQL_ROW row; + query.escaped_name = getEscapeString(name); + TableDataQuery* table_query = 0; + MYSQL_RES* result = 0; + string columns; + + if(VerifyDataTable(query.escaped_name)){ + result = query.RunQuery2(Q_SELECT, "SELECT * from %s where table_data_version > %i", query.escaped_name, version); + columns = GetColumnNames(query.escaped_name); + } + if(result && mysql_num_rows(result) > 0){ + table_query = new TableDataQuery(query.escaped_name); + table_query->num_queries = mysql_num_rows(result); + table_query->columns_size = columns.length() + 1; + table_query->columns = new char[table_query->columns_size + 1]; + table_query->version = GetDataVersion(query.escaped_name); + strcpy(table_query->columns, (char*)columns.c_str()); + string query_data; + MYSQL_FIELD* field; + int* int_list = new int[mysql_num_fields(result)]; + int16 ndx = 0; + while((field = mysql_fetch_field(result))){ + int_list[ndx] = IS_NUM(field->type); + if(strcmp(field->name,"table_data_version") == 0) + int_list[ndx] = 2; + ndx++; + } + ndx = 0; + while((row = mysql_fetch_row(result))){ + query_data = ""; + for(int i=0;i0) + query_data.append(","); + if(!int_list[i]){ + query_data.append("'").append(getEscapeString(row[i])).append("'"); + } + else + query_data.append(row[i]); + } + } + TableData* new_query = new TableData; + new_query->size = query_data.length() + 1; + new_query->query = new char[query_data.length() + 1]; + strcpy(new_query->query, query_data.c_str()); + table_query->queries.push_back(new_query); + ndx++; + } + safe_delete_array(int_list); + } + else{ + string query2 = string("The user tried to download the following table: ").append(query.escaped_name); + SaveClientLog("Possible Hacking Attempt", (char*)query2.c_str(), "Hacking Data", server_ip); + } + return table_query; +} +TableQuery* LoginDatabase::GetLatestTableQuery(int32 server_ip, char* name, int16 version){ + Query query; + MYSQL_ROW row; + query.escaped_name = getEscapeString(name); + TableQuery* table_query = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT query, version from login_table_versions where name = '%s' and version>=%i order by version", query.escaped_name, version + 1); + if(result && mysql_num_rows(result) > 0){ + int16 i = 0; + table_query = new TableQuery; + while((row = mysql_fetch_row(result))){ + char* rowdata = row[0]; + if(strstr(rowdata, ";")){ + char* token = strtok(rowdata,";"); + while(token){ + char* new_query = new char[strlen(token) + 1]; + strcpy(new_query, token); + table_query->AddQuery(new_query); + token = strtok(NULL, ";"); + } + } + else + table_query->AddQuery(rowdata); + table_query->latest_version = atoi(row[1]); + } + strcpy(table_query->tablename, name); + table_query->your_version = version; + } + else{ + string query2 = string("The following was the DB Query: ").append(query.GetQuery()); + SaveClientLog("Possible Hacking Attempt", (char*)query2.c_str(), "Hacking Query", server_ip); + } + return table_query; +} +sint16 LoginDatabase::GetDataVersion(char* name){ + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT max(table_data_version) from %s", name); + sint16 ret_version = 0; + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + if(row[0]) + ret_version = atoi(row[0]); + } + return ret_version; +} + +void LoginDatabase::RemoveOldWorldServerStats(){ + Query query; + query.RunQuery2(Q_DELETE, "delete from login_worldstats where (UNIX_TIMESTAMP(NOW())-UNIX_TIMESTAMP(last_update)) > 86400"); +} + + +void LoginDatabase::UpdateWorldServerStats(LWorld* world, sint32 status) +{ + Query query; + query.RunQuery2(Q_INSERT, "insert into login_worldstats (world_id, world_status, current_players, current_zones, last_update, world_max_level) values(%u, %i, %i, %i, NOW(), %i) ON DUPLICATE KEY UPDATE current_players=%i,current_zones=%i,world_max_level=%i,world_status=%i,last_update=NOW()", + world->GetAccountID(), status, world->GetPlayerNum(), world->GetZoneNum(), world->GetMaxWorldLevel(), world->GetPlayerNum(), world->GetZoneNum(), world->GetMaxWorldLevel(), status); + + string update_stats = string("update login_worldservers set lastseen=%u where id=%i"); + query.RunQuery2(Q_UPDATE, update_stats.c_str(), Timer::GetUnixTimeStamp(), world->GetAccountID()); +} + +bool LoginDatabase::ResetWorldServerStatsConnectedTime(LWorld* world){ + if(!world || world->GetAccountID() == 0) + return false; + + Query query; + string update_stats = string("update login_worldstats set connected_time=now() where world_id=%i and (UNIX_TIMESTAMP(NOW())-UNIX_TIMESTAMP(last_update)) > 300"); + query.RunQuery2(Q_UPDATE, update_stats.c_str(),world->GetAccountID()); + + return true; +} + +void LoginDatabase::ResetWorldStats ( ) +{ + Query query; + string update_stats = string("update login_worldstats set world_status=-4, current_players=0, current_zones=0"); + query.RunQuery2(update_stats.c_str(), Q_UPDATE); +} + +void LoginDatabase::SaveBugReport(int32 world_id, char* category, char* subcategory, char* causes_crash, char* reproducible, char* summary, char* description, char* version, char* player, int32 account_id, char* spawn_name, int32 spawn_id, int32 zone_id){ + Query query; + string bug_report = string("insert into bugs (world_id, category, subcategory, causes_crash, reproducible, summary, description, version, player, account_id, spawn_name, spawn_id, zone_id) values(%lu, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %lu, '%s', %lu, %lu)"); + query.RunQuery2(Q_INSERT, bug_report.c_str(), world_id, database.getSafeEscapeString(category).c_str(), database.getSafeEscapeString(subcategory).c_str(), + database.getSafeEscapeString(causes_crash).c_str(), database.getSafeEscapeString(reproducible).c_str(), database.getSafeEscapeString(summary).c_str(), + database.getSafeEscapeString(description).c_str(), database.getSafeEscapeString(version).c_str(), database.getSafeEscapeString(player).c_str(), account_id, + database.getSafeEscapeString(spawn_name).c_str(), spawn_id, zone_id); + FixBugReport(); +} + +void LoginDatabase::FixBugReport(){ + Query query; + string bug_report = string("update bugs set description = REPLACE(description,SUBSTRING(description,INSTR(description,'%'), 3),char(CONV(SUBSTRING(description,INSTR(description,'%')+1, 2), 16, 10))), summary = REPLACE(summary,SUBSTRING(summary,INSTR(summary,'%'), 3),char(CONV(SUBSTRING(summary,INSTR(summary,'%')+1, 2), 16, 10)))"); + query.RunQuery2(bug_report.c_str(), Q_UPDATE); +} + +void LoginDatabase::UpdateWorldIPAddress(int32 world_id, int32 address){ + struct in_addr in; + in.s_addr = address; + Query query; + query.RunQuery2(Q_UPDATE, "update login_worldservers set ip_address='%s' where id=%lu", inet_ntoa(in), world_id); +} + +void LoginDatabase::UpdateAccountIPAddress(int32 account_id, int32 address){ + struct in_addr in; + in.s_addr = address; + Query query; + query.RunQuery2(Q_UPDATE, "update account set ip_address='%s' where id=%lu", inet_ntoa(in), account_id); +} + +//devn00b: There is no rulesystem for login, so im going to use login_config for future things like this. +//devn00b: Returns the number of characters a player may create per account. This should be set by server owners -> login, +//devn00b: However, better semi-working for now than not working at all. +//devn00b: TODO: EQ2World sends max char per acct. +int8 LoginDatabase::GetMaxCharsSetting() { + //live defaults to 7 for GOLD members. + int8 max_chars = 7; + Query query; + MYSQL_ROW row; + + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "select config_value from login_config where config_name='max_characters_per_account'"); + if (result && mysql_num_rows(result) == 1) { + row = mysql_fetch_row(result); + if (row[0]) + max_chars = atoi(row[0]); + } + //if nothing else return the default. + return max_chars; +} + +int16 LoginDatabase::GetAccountBonus(int32 acct_id) { + int32 bonus = 0; + int16 world_id = 0; + Query query; + MYSQL_ROW row; + Query query2; + MYSQL_ROW row2; + + //get the world ID for the character. TODO: Support multi server characters. + MYSQL_RES* result2 = query2.RunQuery2(Q_SELECT, "select server_id from login_characters where account_id=%i", acct_id); + + if (result2 && mysql_num_rows(result2) >= 1) { + row2 = mysql_fetch_row(result2); + if (row2[0]) + world_id = atoi(row2[0]); + } + + //pull all characters greater than the max level from the server + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT COUNT(id) FROM login_characters WHERE LEVEL >= (select world_max_level from login_worldstats where world_id=%i) AND account_id=%i", world_id, acct_id); + + if (result && mysql_num_rows(result) == 1) { + row = mysql_fetch_row(result); + if(row[0]) + bonus = atoi(row[0]); + } + return bonus; +} + +void LoginDatabase::UpdateWorldVersion(int32 world_id, char* version) { + Query query; + query.RunQuery2(Q_UPDATE, "update login_worldservers set login_version='%s' where id=%u", version, world_id); +} + +void LoginDatabase::UpdateAccountClientDataVersion(int32 account_id, int16 version) +{ + Query query; + query.RunQuery2(Q_UPDATE, "UPDATE account SET last_client_version='%i' WHERE id = %u", version, account_id); +} + +//devn00b todo: finish this. +void LoginDatabase::SaveCharacterPicture(int32 account_id, int32 character_id, int32 server_id, int16 picture_size, uchar* picture) { + stringstream ss_hex; + stringstream ss_query; + ss_hex.flags(ios::hex); + for (int32 i = 0; i < picture_size; i++) + ss_hex << setfill('0') << setw(2) << (int32)picture[i]; + + ss_query << "INSERT INTO `ls_character_picture` (`server_id`, `account_id`, `character_id`, `picture`) VALUES (" << server_id << ", " << account_id << ", " << character_id << ", '" << ss_hex.str() << "') ON DUPLICATE KEY UPDATE `picture` = '" << ss_hex.str() << "'"; + + if (!dbLogin.Query(ss_query.str().c_str())) + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", dbLogin.GetError(), dbLogin.GetErrorMsg()); +} \ No newline at end of file diff --git a/source/LoginServer/LoginDatabase.h b/source/LoginServer/LoginDatabase.h new file mode 100644 index 0000000..84f8d7b --- /dev/null +++ b/source/LoginServer/LoginDatabase.h @@ -0,0 +1,95 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#ifndef EQ2LOGIN_EMU_DATABASE_H +#define EQ2LOGIN_EMU_DATABASE_H + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN + #include + #include +#endif +#include +#include +#include + +#include "../common/database.h" +#include "../common/DatabaseNew.h" +#include "../common/types.h" +#include "../common/MiscFunctions.h" +#include "../common/servertalk.h" +#include "../common/Mutex.h" +#include "PacketHeaders.h" +#include "LoginAccount.h" +#include "LWorld.h" +#include "../common/PacketStruct.h" + +using namespace std; +#pragma pack() +class LoginDatabase : public Database +{ +public: + void FixBugReport(); + void UpdateAccountIPAddress(int32 account_id, int32 address); + void UpdateWorldIPAddress(int32 world_id, int32 address); + void SaveBugReport(int32 world_id, char* category, char* subcategory, char* causes_crash, char* reproducible, char* summary, char* description, char* version, char* player, int32 account_id, char* spawn_name, int32 spawn_id, int32 zone_id); + LoginAccount* LoadAccount(const char* name, const char* password, bool attemptAccountCreation=true); + int32 GetAccountIDByName(const char* name); + int32 CheckServerAccount(char* name, char* passwd); + bool IsServerAccountDisabled(char* name); + bool IsIPBanned(char* ipaddr); + void GetServerAccounts(vector* server_list); + char* GetServerAccountName(int32 id); + bool VerifyDelete(int32 account_id, int32 character_id, const char* name); + void SetServerZoneDescriptions(int32 server_id, map zone_descriptions); + int32 GetServer(int32 accountID, int32 charID, string name); + void LoadCharacters(LoginAccount* acct, int16 version); + void CheckCharacterTimeStamps(LoginAccount* acct); + string GetCharacterName(int32 char_id , int32 server_id, int32 account_id); + void SaveCharacterColors(int32 char_id, char* type, EQ2_Color color); + void SaveCharacterFloats(int32 char_id, char* type, float float1, float float2, float float3, float multiplier=100.0f); + int16 GetAppearanceID(string name); + void DeactivateCharID(int32 server_id, int32 char_id, int32 exception_id); + int32 SaveCharacter(PacketStruct* create, LoginAccount* acct, int32 world_charid, int32 client_version); + void LoadAppearanceData(int32 char_id, PacketStruct* char_select_packet); + bool UpdateCharacterTimeStamp(int32 account_id, int32 character_id, int32 timestamp_update, int32 server_id); + bool UpdateCharacterLevel(int32 account_id, int32 character_id, int8 in_level, int32 server_id); + bool UpdateCharacterRace(int32 account_id, int32 character_id, int16 in_racetype, int8 in_race, int32 server_id); + bool UpdateCharacterClass(int32 account_id, int32 character_id, int8 in_class, int32 server_id); + bool UpdateCharacterZone(int32 account_id, int32 character_id, int32 zone_id, int32 server_id); + bool UpdateCharacterGender(int32 account_id, int32 character_id, int8 in_gender, int32 server_id); + int32 GetRaceID(char* name); + void UpdateRaceID(char* name); + bool DeleteCharacter(int32 account_id, int32 character_id, int32 server_id); + void SaveClientLog(const char* type, const char* message, const char* player_name, int16 version); + bool CheckVersion(char* version); + void GetLatestTableVersions(LatestTableVersions* table_versions); + TableQuery* GetLatestTableQuery(int32 server_ip, char* name, int16 version); + bool VerifyDataTable(char* name); + sint16 GetDataVersion(char* name); + void SetZoneInformation(int32 server_id, int32 zone_id, int32 version, PacketStruct* packet); + string GetZoneDescription(char* name); + string GetColumnNames(char* name); + TableDataQuery* GetTableDataQuery(int32 server_ip, char* name, int16 version); + + void UpdateWorldServerStats( LWorld* world, sint32 status); + bool ResetWorldServerStatsConnectedTime( LWorld* world ); + void RemoveOldWorldServerStats(); + void ResetWorldStats(); + //devn00b temp + bool ConnectNewDatabase(); + void SetServerEquipmentAppearances(int32 server_id, map equip_updates); // JohnAdams: login appearances + int32 GetLoginCharacterIDFromWorldCharID(int32 server_id, int32 char_id); // JohnAdams: login appearances + void RemoveDeletedCharacterData(); + int8 GetMaxCharsSetting(); + int16 GetAccountBonus(int32 acct_id); + void UpdateWorldVersion(int32 world_id, char* version); + void UpdateAccountClientDataVersion(int32 account_id, int16 version); + void SaveCharacterPicture(int32 account_id, int32 character_id, int32 server_id, int16 picture_size, uchar* picture); + + DatabaseNew dbLogin; +}; +#endif \ No newline at end of file diff --git a/source/LoginServer/PacketHeaders.cpp b/source/LoginServer/PacketHeaders.cpp new file mode 100644 index 0000000..4511f6a --- /dev/null +++ b/source/LoginServer/PacketHeaders.cpp @@ -0,0 +1,88 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#include "PacketHeaders.h" +#include "../common/MiscFunctions.h" +#include "LoginDatabase.h" +#include "LWorld.h" + +extern LWorldList world_list; +extern LoginDatabase database; + +void LS_DeleteCharacterRequest::loadData(EQApplicationPacket* packet){ + InitializeLoadData(packet->pBuffer, packet->size); + LoadData(character_number); + LoadData(server_id); + LoadData(spacer); + LoadDataString(name); +} + +EQ2Packet* LS_CharSelectList::serialize(int16 version){ + Clear(); + AddData(num_characters); + AddData(char_data); + if (version <= 561) { + LS_CharListAccountInfoEarlyClient account_info; + account_info.account_id = account_id; + account_info.unknown1 = 0xFFFFFFFF; + account_info.unknown2 = 0; + account_info.maxchars = 7; //live has a max of 7 on gold accounts base. + account_info.unknown4 = 0; + AddData(account_info); + } + else { + LS_CharListAccountInfo account_info; + account_info.account_id = account_id; + account_info.unknown1 = 0xFFFFFFFF; + account_info.unknown2 = 0; + account_info.maxchars = database.GetMaxCharsSetting(); + account_info.vet_adv_bonus = database.GetAccountBonus(account_id); + account_info.vet_trade_bonus = 0; + account_info.unknown4 = 0; + for (int i = 0; i < 3; i++) + account_info.unknown5[i] = 0xFFFFFFFF; + account_info.unknown5[3] = 0; + + AddData(account_info); + } + return new EQ2Packet(OP_AllCharactersDescReplyMsg, getData(), getDataSize()); +} + +void LS_CharSelectList::addChar(uchar* data, int16 size){ + char_data.append((char*)data, size); +} + +void LS_CharSelectList::loadData(int32 account, vector charlist, int16 version){ + vector::iterator itr; + account_id = account; + num_characters = 0; + char_data = ""; + CharSelectProfile* character = 0; + for(itr = charlist.begin();itr != charlist.end();itr++){ + character = *itr; + int32 serverID = character->packet->getType_int32_ByName("server_id"); + if(character->deleted) { // workaround for old clients <= 561 that crash if you delete a char (Doesn't refresh the char panel correctly) + character->packet->setDataByName("name", "(deleted)"); + character->packet->setDataByName("charid", 0xFFFFFFFF); + character->packet->setDataByName("name", 0xFFFFFFFF); + character->packet->setDataByName("server_id", 0xFFFFFFFF); + character->packet->setDataByName("created_date", 0xFFFFFFFF); + character->packet->setDataByName("unknown1", 0xFFFFFFFF); + character->packet->setDataByName("unknown2", 0xFFFFFFFF); + character->packet->setDataByName("flags", 0xFF); + } + else if(serverID == 0 || !world_list.FindByID(serverID)) + continue; + num_characters++; + character->SaveData(version); + addChar(character->getData(), character->getDataSize()); + } +} + +void CharSelectProfile::SaveData(int16 in_version){ + Clear(); + AddData(*packet->serializeString()); +} diff --git a/source/LoginServer/PacketHeaders.h b/source/LoginServer/PacketHeaders.h new file mode 100644 index 0000000..69a30c5 --- /dev/null +++ b/source/LoginServer/PacketHeaders.h @@ -0,0 +1,61 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#ifndef __PACKET_HEADERS__ +#define __PACKET_HEADERS__ + +#include "../common/types.h" +#include "../common/EQPacket.h" +#include "../common/EQ2_Common_Structs.h" +#include "login_structs.h" +#include "../common/DataBuffer.h" +#include "../common/GlobalHeaders.h" +#include "../common/ConfigReader.h" +#include + +extern ConfigReader configReader; + +class CharSelectProfile : public DataBuffer{ +public: + CharSelectProfile(int16 version){ + deleted = false; + packet = configReader.getStruct("CharSelectProfile",version); + for(int8 i=0;i<24;i++){ + packet->setEquipmentByName("equip",0,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,i); + } + } + + ~CharSelectProfile(){ + safe_delete(packet); + } + PacketStruct* packet; + + void SaveData(int16 in_version); + void Data(); + int16 size; + bool deleted; +}; + +class LS_CharSelectList : public DataBuffer { +public: + int8 num_characters; + int32 account_id; + + EQ2Packet* serialize(int16 version); + void addChar(uchar* data, int16 size); + string char_data; + void loadData(int32 account, vector charlist, int16 version); +}; + +class LS_DeleteCharacterRequest : public DataBuffer{ +public: + int32 character_number; + int32 server_id; + int32 spacer; + EQ2_16BitString name; + void loadData(EQApplicationPacket* packet); +}; +#endif \ No newline at end of file diff --git a/source/LoginServer/Web/LoginWeb.cpp b/source/LoginServer/Web/LoginWeb.cpp new file mode 100644 index 0000000..e1e26e3 --- /dev/null +++ b/source/LoginServer/Web/LoginWeb.cpp @@ -0,0 +1,64 @@ +#include "../net.h" +#include "../LWorld.h" + +#include +#include +#include + +extern ClientList client_list; +extern LWorldList world_list; +extern NetConnection net; + +void NetConnection::Web_loginhandle_status(const http::request& req, http::response& res) { + res.set(http::field::content_type, "application/json"); + boost::property_tree::ptree pt; + + pt.put("web_status", "online"); + pt.put("login_status", net.login_running ? "online" : "offline"); + pt.put("login_uptime", (getCurrentTimestamp() - net.login_uptime)); + auto [days, hours, minutes, seconds] = convertTimestampDuration((getCurrentTimestamp() - net.login_uptime)); + std::string uptime_str("Days: " + std::to_string(days) + ", " + "Hours: " + std::to_string(hours) + ", " + "Minutes: " + std::to_string(minutes) + ", " + "Seconds: " + std::to_string(seconds)); + pt.put("login_uptime_string", uptime_str); + pt.put("world_count", world_list.GetCount(ConType::World)); + pt.put("client_count", net.numclients); + + std::ostringstream oss; + boost::property_tree::write_json(oss, pt); + std::string json = oss.str(); + res.body() = json; + res.prepare_payload(); +} + +void NetConnection::Web_loginhandle_worlds(const http::request& req, http::response& res) { + world_list.PopulateWorldList(res); +} + +void LWorldList::PopulateWorldList(http::response& res) { + + struct in_addr in; + res.set(http::field::content_type, "application/json"); + boost::property_tree::ptree maintree; + + std::ostringstream oss; + + map::iterator map_list; + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + LWorld* world = map_list->second; + in.s_addr = world->GetIP(); + if (world->GetType() == World) { + + boost::property_tree::ptree pt; + pt.put("id", world->GetID()); + pt.put("world_name", world->GetName()); + pt.put("status", (world->GetStatus() == 1) ? "online" : "offline"); + pt.put("ip_addr", inet_ntoa(in)); + maintree.add_child("WorldServer", pt); + } + } + + boost::property_tree::write_json(oss, maintree); + std::string json = oss.str(); + res.body() = json; + res.prepare_payload(); +} + diff --git a/source/LoginServer/Web/LoginWeb.o b/source/LoginServer/Web/LoginWeb.o new file mode 100644 index 0000000..29c26ff Binary files /dev/null and b/source/LoginServer/Web/LoginWeb.o differ diff --git a/source/LoginServer/client.cpp b/source/LoginServer/client.cpp new file mode 100644 index 0000000..d41728f --- /dev/null +++ b/source/LoginServer/client.cpp @@ -0,0 +1,813 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#include "../common/debug.h" +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#else +#include +#include +#include +#include +#endif +#include +#include +#include +#include + +#include "net.h" +#include "client.h" +#include "../common/EQStream.h" +#include "../common/packet_dump.h" +#include "../common/packet_functions.h" +#include "../common/emu_opcodes.h" +#include "../common/MiscFunctions.h" +#include "LWorld.h" +#include "LoginDatabase.h" +#include "../common/ConfigReader.h" +#include "../common/Log.h" + +extern NetConnection net; +extern LWorldList world_list; +extern ClientList client_list; +extern LoginDatabase database; +extern mapEQOpcodeManager; +extern ConfigReader configReader; +using namespace std; +Client::Client(EQStream* ieqnc) { + eqnc = ieqnc; + ip = eqnc->GetrIP(); + port = ntohs(eqnc->GetrPort()); + account_id = 0; + lsadmin = 0; + worldadmin = 0; + lsstatus = 0; + version = 0; + kicked = false; + verified = false; + memset(bannedreason, 0, sizeof(bannedreason)); + //worldresponse_timer = new Timer(10000); + //worldresponse_timer->Disable(); + memset(key,0,10); + LoginMode = None; + num_updates = 0; + updatetimer = new Timer(500); + updatelisttimer = new Timer(10000); + //keepalive = new Timer(5000); + //logintimer = new Timer(500); // Give time for the servers to send updates + //keepalive->Start(); + //updatetimer->Start(); + //logintimer->Disable(); + disconnectTimer = 0; + memset(ClientSession,0,25); + request_num = 0; + login_account = 0; + createRequest = 0; + playWaitTimer = NULL; + start = false; + update_position = 0; + update_packets = 0; + needs_world_list = true; + sent_character_list = false; +} + +Client::~Client() { + //safe_delete(worldresponse_timer); + //safe_delete(logintimer); + safe_delete(login_account); + eqnc->Close(); + safe_delete(playWaitTimer); + safe_delete(createRequest); + safe_delete(disconnectTimer); + safe_delete(updatetimer); +} + +bool Client::Process() { + if(!start && !eqnc->CheckActive()){ + if(!playWaitTimer) + playWaitTimer = new Timer(5000); + else if(playWaitTimer->Check()){ + safe_delete(playWaitTimer); + return false; + } + return true; + } + else if(!start){ + safe_delete(playWaitTimer); + start = true; + } + + if (disconnectTimer && disconnectTimer->Check()) + { + safe_delete(disconnectTimer); + getConnection()->SendDisconnect(); + } + + if (!kicked) { + /************ Get all packets from packet manager out queue and process them ************/ + EQApplicationPacket *app = 0; + /*if(logintimer && logintimer->Check()) + { + database.LoadCharacters(GetLoginAccount()); + SendLoginAccepted(); + logintimer->Disable(); + }*/ + /*if(worldresponse_timer && worldresponse_timer->Check()) + { + FatalError(WorldDownErrorMessage); + worldresponse_timer->Disable(); + }*/ + + if(playWaitTimer != NULL && playWaitTimer->Check ( ) ) + { + SendPlayFailed(PLAY_ERROR_SERVER_TIMEOUT); + safe_delete(playWaitTimer); + } + if(!needs_world_list && updatetimer && updatetimer->Check()){ + if(updatelisttimer && updatelisttimer->Check()){ + if(num_updates >= 180){ //30 minutes + getConnection()->SendDisconnect(); + } + else{ + vector::iterator itr; + if(update_packets){ + for(itr = update_packets->begin(); itr != update_packets->end(); itr++){ + safe_delete(*itr); + } + } + safe_delete(update_packets); + update_packets = world_list.GetServerListUpdate(version); + } + num_updates++; + } + else{ + if(!update_packets){ + update_packets = world_list.GetServerListUpdate(version); + } + else{ + if(update_position < update_packets->size()){ + QueuePacket(update_packets->at(update_position)->serialize()); + update_position++; + } + else + update_position = 0; + } + } + } + + while(app = eqnc->PopPacket()) + { + switch(app->GetOpcode()) + { + case OP_LoginRequestMsg:{ + DumpPacket(app); + PacketStruct* packet = configReader.getStruct("LS_LoginRequest", 1); + if(packet && packet->LoadPacketData(app->pBuffer,app->size)){ + version = packet->getType_int16_ByName("version"); + LogWrite(LOGIN__DEBUG, 0, "Login", "Classic Client Version Provided: %i", version); + + if (version == 0 || EQOpcodeManager.count(GetOpcodeVersion(version)) == 0) + { + safe_delete(packet); + packet = configReader.getStruct("LS_LoginRequest", 1208); + if (packet && packet->LoadPacketData(app->pBuffer, app->size)) { + version = packet->getType_int16_ByName("version"); + } + else + break; + } + //[7:19 PM] Kirmmin: Well, I very quickly learned that unknown3 in LS_LoginRequest packet is the same value as cl_eqversion in the eq2_defaults.ini file. + + LogWrite(LOGIN__DEBUG, 0, "Login", "New Client Version Provided: %i", version); + + if (EQOpcodeManager.count(GetOpcodeVersion(version)) == 0) { + LogWrite(LOGIN__ERROR, 0, "Login", "Incompatible client version provided: %i", version); + SendLoginDenied(); + return false; + } + + if(EQOpcodeManager.count(GetOpcodeVersion(version)) > 0 && getConnection()){ + getConnection()->SetClientVersion(GetVersion()); + EQ2_16BitString username = packet->getType_EQ2_16BitString_ByName("username"); + EQ2_16BitString password = packet->getType_EQ2_16BitString_ByName("password"); + LoginAccount* acct = database.LoadAccount(username.data.c_str(),password.data.c_str(), net.IsAllowingAccountCreation()); + if(acct){ + Client* otherclient = client_list.FindByLSID(acct->getLoginAccountID()); + if(otherclient) + otherclient->getConnection()->SendDisconnect(); // This person is already logged in, we don't want them logged in twice, kick the previous client as it might be a ghost + } + if(acct){ + SetAccountName(username.data.c_str()); + database.UpdateAccountIPAddress(acct->getLoginAccountID(), getConnection()->GetrIP()); + database.UpdateAccountClientDataVersion(acct->getLoginAccountID(), version); + LogWrite(LOGIN__INFO, 0, "Login", "%s successfully logged in.", (char*)username.data.c_str()); + } + else + { + if (username.size > 0) + LogWrite(LOGIN__ERROR, 0, "Login", "%s login failed!", (char*)username.data.c_str()); + else + LogWrite(LOGIN__ERROR, 0, "Login", "[UNKNOWN USER] login failed!"); + } + + if(!acct) + SendLoginDenied(); + else{ + needs_world_list = true; + SetLoginAccount(acct); + SendLoginAccepted(); + } + } + else{ + cout << "Error bad version: " << version << endl; + SendLoginDeniedBadVersion(); + } + } + else{ + cout << "Error loading LS_LoginRequest packet: \n"; + //DumpPacket(app); + } + safe_delete(packet); + break; + } + case OP_KeymapLoadMsg:{ + // cout << "Received OP_KeymapNoneMsg\n"; + //dunno what this is for + break; + } + case OP_AllWSDescRequestMsg:{ + SendWorldList(); + needs_world_list = false; + if(!sent_character_list) { + database.LoadCharacters(GetLoginAccount(), GetVersion()); + sent_character_list = true; + } + SendCharList(); + break; + } + case OP_LsClientCrashlogReplyMsg:{ +// DumpPacket(app); + SaveErrorsToDB(app, "Crash Log", GetVersion()); + break; + } + case OP_LsClientVerifylogReplyMsg:{ +// DumpPacket(app); + SaveErrorsToDB(app, "Verify Log", GetVersion()); + break; + } + case OP_LsClientAlertlogReplyMsg:{ +// DumpPacket(app); + SaveErrorsToDB(app, "Alert Log", GetVersion()); + break; + } + case OP_LsClientBaselogReplyMsg:{ +// DumpPacket(app); + SaveErrorsToDB(app, "Base Log", GetVersion()); + break; + } + case OP_AllCharactersDescRequestMsg:{ + break; + } + case OP_CreateCharacterRequestMsg:{ + PacketStruct* packet = configReader.getStruct("CreateCharacter", GetVersion()); + + DumpPacket(app); + playWaitTimer = new Timer ( 15000 ); + playWaitTimer->Start ( ); + + LogWrite(WORLD__INFO, 1, "World", "Character creation request from account %s", GetAccountName()); + if(packet->LoadPacketData(app->pBuffer,app->size, GetVersion() <= 561 ? false : true)){ + DumpPacket(app->pBuffer, app->size); + packet->setDataByName("account_id",GetAccountID()); + LWorld* world_server = world_list.FindByID(packet->getType_int32_ByName("server_id")); + if(!world_server) + { + DumpPacket(app->pBuffer, app->size); + cout << GetAccountName() << " attempted creation of character with an invalid server id of: " << packet->getType_int32_ByName("server_id") << "\n"; + break; + } + else + { + createRequest = packet; + ServerPacket* outpack = new ServerPacket(ServerOP_CharacterCreate, app->size+sizeof(int16)); + int16 out_version = GetVersion(); + memcpy(outpack->pBuffer, &out_version, sizeof(int16)); + memcpy(outpack->pBuffer + sizeof(int16), app->pBuffer, app->size); + uchar* tmp = outpack->pBuffer; + + if(out_version<=283) + tmp+=2; + else if(out_version == 373) { + tmp += 6; + } + else + tmp += 7; + + int32 account_id = GetAccountID(); + memcpy(tmp, &account_id, sizeof(int32)); + world_server->SendPacket(outpack); + safe_delete(outpack); + } + } + else{ + LogWrite(WORLD__ERROR, 1, "World", "Error in character creation request from account %s!", GetAccountName()); + safe_delete(packet); + } + // world_list.SendWorldChanged(create.profile.server_id, false, this); + break; + } + case OP_PlayCharacterRequestMsg:{ + int32 char_id = 0; + int32 server_id = 0; + PacketStruct* request = configReader.getStruct("LS_PlayRequest",GetVersion()); + if(request && request->LoadPacketData(app->pBuffer,app->size)){ + char_id = request->getType_int32_ByName("char_id"); + if (GetVersion() <= 283) { + server_id = database.GetServer(GetAccountID(), char_id, request->getType_EQ2_16BitString_ByName("name").data); + } + else { + server_id = request->getType_int32_ByName("server_id"); + } + LWorld* world = world_list.FindByID(server_id); + string name = database.GetCharacterName(char_id,server_id,GetAccountID()); + if(world && name.length() > 0){ + pending_play_char_id = char_id; + ServerPacket* outpack = new ServerPacket(ServerOP_UsertoWorldReq, sizeof(UsertoWorldRequest_Struct)); + UsertoWorldRequest_Struct* req = (UsertoWorldRequest_Struct*)outpack->pBuffer; + req->char_id = char_id; + req->lsaccountid = GetAccountID(); + req->worldid = server_id; + + struct in_addr in; + in.s_addr = GetIP(); + strcpy(req->ip_address, inet_ntoa(in)); + world->SendPacket(outpack); + delete outpack; + + safe_delete(playWaitTimer); + + playWaitTimer = new Timer ( 5000 ); + playWaitTimer->Start ( ); + } + else{ + cout << GetAccountName() << " sent invalid Play Request: \n"; + SendPlayFailed(PLAY_ERROR_PROBLEM); + DumpPacket(app); + } + } + safe_delete(request); + break; + } + case OP_DeleteCharacterRequestMsg:{ + PacketStruct* request = configReader.getStruct("LS_DeleteCharacterRequest", GetVersion()); + PacketStruct* response = configReader.getStruct("LS_DeleteCharacterResponse", GetVersion()); + if(request && response && request->LoadPacketData(app->pBuffer,app->size)){ + EQ2_16BitString name = request->getType_EQ2_16BitString_ByName("name"); + int32 acct_id = GetAccountID(); + int32 char_id = request->getType_int32_ByName("char_id"); + int32 server_id = request->getType_int32_ByName("server_id"); + if(database.VerifyDelete(acct_id, char_id, name.data.c_str())){ + response->setDataByName("response", 1); + GetLoginAccount()->removeCharacter((char*)name.data.c_str(), GetVersion()); + LWorld* world_server = world_list.FindByID(server_id); + if(world_server != NULL) + world_server->SendDeleteCharacter ( char_id , acct_id ); + } + else + response->setDataByName("response", 0); + response->setDataByName("server_id", server_id); + response->setDataByName("char_id", char_id); + response->setDataByName("account_id", account_id); + response->setMediumStringByName("name", (char*)name.data.c_str()); + response->setDataByName("max_characters", 10); + + EQ2Packet* outapp = response->serialize(); + QueuePacket(outapp); + + this->SendCharList(); + } + safe_delete(request); + safe_delete(response); + break; + } + default: { + const char* name = app->GetOpcodeName(); + if (name) + LogWrite(OPCODE__DEBUG, 1, "Opcode", "%s Received %04X (%i)", name, app->GetRawOpcode(), app->GetRawOpcode()); + else + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Received %04X (%i)", app->GetRawOpcode(), app->GetRawOpcode()); + } + } + delete app; + } + } + + if (!eqnc->CheckActive()) { + return false; + } + + return true; +} + +void Client::SaveErrorsToDB(EQApplicationPacket* app, char* type, int32 version){ + int32 size = 0; + z_stream zstream; + if (version >= 546) { + memcpy(&size, app->pBuffer + sizeof(int32), sizeof(int32)); + zstream.next_in = app->pBuffer + 8; + zstream.avail_in = app->size - 8; + } + else { //box set + size = 0xFFFF; + zstream.next_in = app->pBuffer + 2; + zstream.avail_in = app->size - 2; + } + size++; + char* message = new char[size]; + memset(message, 0, size); + + int zerror = 0; + + zstream.next_out = (BYTE*)message; + zstream.avail_out = size; + zstream.zalloc = Z_NULL; + zstream.zfree = Z_NULL; + zstream.opaque = Z_NULL; + + zerror = inflateInit( &zstream); + if(zerror != Z_OK) { + safe_delete_array(message); + return; + } + zerror = inflate( &zstream, 0 ); + if(message && strlen(message) > 0) + database.SaveClientLog(type, message, GetLoginAccount()->getLoginName(), GetVersion()); + safe_delete_array(message); +} + +void Client::CharacterApproved(int32 server_id,int32 char_id) +{ + if(createRequest && server_id == createRequest->getType_int32_ByName("server_id")){ + LWorld* world_server = world_list.FindByID(server_id); + if(!world_server) + return; + + PacketStruct* packet = configReader.getStruct("LS_CreateCharacterReply", GetVersion()); + if(packet){ + packet->setDataByName("account_id", GetAccountID()); + packet->setDataByName("unknown", 0xFFFFFFFF); + packet->setDataByName("response", CREATESUCCESS_REPLY); + packet->setMediumStringByName("name", (char*)createRequest->getType_EQ2_16BitString_ByName("name").data.c_str()); + EQ2Packet* outapp = packet->serialize(); + QueuePacket(outapp); + safe_delete(packet); + database.SaveCharacter(createRequest, GetLoginAccount(),char_id, GetVersion()); + + // refresh characters for this account + database.LoadCharacters(GetLoginAccount(), GetVersion()); + + SendCharList(); + + if (GetVersion() <= 561) + { + pending_play_char_id = char_id; + ServerPacket* outpack = new ServerPacket(ServerOP_UsertoWorldReq, sizeof(UsertoWorldRequest_Struct)); + UsertoWorldRequest_Struct* req = (UsertoWorldRequest_Struct*)outpack->pBuffer; + req->char_id = char_id; + req->lsaccountid = GetAccountID(); + req->worldid = server_id; + + struct in_addr in; + in.s_addr = GetIP(); + strcpy(req->ip_address, inet_ntoa(in)); + world_server->SendPacket(outpack); + delete outpack; + } + } + } + else{ + cout << GetAccountName() << " received invalid CharacterApproval from server: " << server_id << endl; + } + safe_delete(createRequest); +} + +void Client::CharacterRejected(int8 reason_number) +{ + PacketStruct* packet = configReader.getStruct("LS_CreateCharacterReply", GetVersion()); + if(createRequest && packet){ + packet->setDataByName("account_id", GetAccountID()); + int8 clientReasonNum = reason_number; + // reason numbers change and instead of updating the world server + // the login server will hold the up to date #'s +/* + switch(reason_number) + { + // these error codes seem to be removed now, they shutdown the client rather immediately + // for now we are just going to play a joke on them and say they can't create a new character. + case INVALIDRACE_REPLY: + case INVALIDGENDER_REPLY: + clientReasonNum = 8; + break; + case BADNAMELENGTH_REPLY: + clientReasonNum = 9; + break; + case NAMEINVALID_REPLY: + clientReasonNum = 10; + break; + case NAMEFILTER_REPLY: + clientReasonNum = 11; + break; + case NAMETAKEN_REPLY: + clientReasonNum = 12; + break; + case OVERLOADEDSERVER_REPLY: + clientReasonNum = 13; + break; + } +*/ + packet->setDataByName("response", clientReasonNum); + packet->setMediumStringByName("name", ""); + EQ2Packet* outapp = packet->serialize(); + QueuePacket(outapp); + safe_delete(packet); + } + /*LS_CreateCharacterReply reply(GetAccountID(), reason_number, create.profile.name.data); + EQ2Packet* outapp = reply.serialize(); + QueuePacket(outapp); + create.Clear();*/ +} + +void Client::SendCharList(){ + /*PacketStruct* packet = configReader.getStruct("LS_CreateCharacterReply"); + packet->setDataByName("account_id", GetAccountID()); + packet->setDataByName("response", reason_number); + packet->setDataByName("name", &create.profile.name); + EQ2Packet* outapp = packet->serialize(); + QueuePacket(outapp); + safe_delete(packet);*/ + LogWrite(LOGIN__INFO, 0, "Login", "[%s] sending character list.", GetAccountName()); + LS_CharSelectList list; + list.loadData(GetAccountID(), GetLoginAccount()->charlist, GetVersion()); + EQ2Packet* outapp = list.serialize(GetVersion()); + DumpPacket(outapp->pBuffer, outapp->size); + QueuePacket(outapp); + +} +void Client::SendLoginDeniedBadVersion(){ + EQ2Packet* app = new EQ2Packet(OP_LoginReplyMsg, 0, sizeof(LS_LoginResponse)); + LS_LoginResponse* ls_response = (LS_LoginResponse*)app->pBuffer; + ls_response->reply_code = 6; + ls_response->unknown03 = 0xFFFFFFFF; + ls_response->unknown04 = 0xFFFFFFFF; + QueuePacket(app); + StartDisconnectTimer(); +} +void Client::SendLoginDenied(){ + EQ2Packet* app = new EQ2Packet(OP_LoginReplyMsg, 0, sizeof(LS_LoginResponse)); + LS_LoginResponse* ls_response = (LS_LoginResponse*)app->pBuffer; + ls_response->reply_code = 1; + // reply_codes for AoM: + /* 1 = Login rejected: Invalid username or password. Please try again. + 2 = Login rejected: Server thinks your account is currently playing; you may have to wait " + "a few minutes for it to clear, then try again + 6 = Login rejected: The client's version does not match the server's. Please re-run the patcher. + 7 = Login rejected: You have no scheduled playtimes. + 8 = Your account does not have the features required to play on this server. + 11 = The client's build does not match the server's. Please re-run the patcher. + 12 = You must update your password in order to log in. Pressing OK will op" + "en your web browser to the SOE password management page + Other Value > 1 = Login rejected for an unknown reason. + */ + ls_response->unknown03 = 0xFFFFFFFF; + ls_response->unknown04 = 0xFFFFFFFF; + QueuePacket(app); + StartDisconnectTimer(); +} + +void Client::SendLoginAccepted(int32 account_id, int8 login_response) { + PacketStruct* packet = configReader.getStruct("LS_LoginReplyMsg", GetVersion()); + int i = 0; + if (packet) + { + packet->setDataByName("account_id", account_id); + + packet->setDataByName("login_response", login_response); + + packet->setDataByName("do_not_force_soga", 1); + + // sub_level 0xFFFFFFFF = blacks out all portraits for class alignments, considered non membership + // sub_level > 0 = class alignments still required, but portraits are viewable and race selectable + // sub_level = 2 membership, you can 'create characters on time locked servers' vs standard + // sub_level = 0 forces popup on close to web browser + packet->setDataByName("sub_level", net.GetDefaultSubscriptionLevel()); + packet->setDataByName("race_flag", 0x1FFFFF); + packet->setDataByName("class_flag", 0x7FFFFFE); + packet->setMediumStringByName("username", GetAccountName()); + packet->setMediumStringByName("password", GetAccountName()); + + // unknown5 + // full support = 0x7CFF + // 1 << 12 (-4096) = missing echoes of faydwer, disables Fae and Arasai (black portraits) and kelethin as starting city + // 1 << 13 (-8192) = disables sarnak (black portraits) and gorowyn as starting city + packet->setDataByName("unknown5", net.GetExpansionFlag()); + packet->setDataByName("unknown6", 0xFF); + packet->setDataByName("unknown6", 0xFF, 1); + packet->setDataByName("unknown6", 0xFF, 2); + + // controls class access / playable characters + packet->setDataByName("unknown10", 0xFF); + + // packet->setDataByName("unknown7a", 0x0101); + // packet->setDataByName("race_unknown", 0x01); + packet->setDataByName("unknown7", net.GetEnabledRaces()); // 0x01-0xFF disable extra races FAE(16) ARASAI (17) SARNAK (18) -- with 4096/8192 flags, no visibility of portraits + packet->setDataByName("unknown7a", 0xEE); + packet->setDataByName("unknown8", net.GetCitiesFlag(), 1); // dword_1ECBA18 operand for race flag packs (sublevel 0,1,2?) -- (sublevel -1) controls starting zones omission 0xEE vs 0xCF (CF misses halas) + + /* + 1 = city of qeynos + 2 = city of freeport + 4 = city of kelethin + 8 = city of neriak + 16 = gorowyn + 32 = new halas + 64 = queens colony + 128 = outpost overlord + */ + + EQ2Packet* outapp = packet->serialize(); + QueuePacket(outapp); + safe_delete(packet); + } +} + +void Client::SendWorldList(){ + EQ2Packet* pack = world_list.MakeServerListPacket(lsadmin, version); + EQ2Packet* dupe = pack->Copy(); + DumpPacket(dupe->pBuffer,dupe->size); + QueuePacket(dupe); + + SendLoginAccepted(0, 10); // triggers a different code path in the client to set certain flags + return; +} + +void Client::QueuePacket(EQ2Packet* app){ + eqnc->EQ2QueuePacket(app); +} + +void Client::WorldResponse(int32 worldid, int8 response, char* ip_address, int32 port, int32 access_key) +{ + LWorld* world = world_list.FindByID(worldid); + if(world == 0) { + FatalError(0); + return; + } + if(response != 1){ + if(response == PLAY_ERROR_CHAR_NOT_LOADED){ + string pending_play_char_name = database.GetCharacterName(pending_play_char_id, worldid, GetAccountID()); + if(database.VerifyDelete(GetAccountID(), pending_play_char_id, pending_play_char_name.c_str())){ + GetLoginAccount()->removeCharacter((char*)pending_play_char_name.c_str(), GetVersion()); + } + } + FatalError(response); + return; + } + + PacketStruct* response_packet = configReader.getStruct("LS_PlayResponse", GetVersion()); + if(response_packet){ + safe_delete(playWaitTimer); + response_packet->setDataByName("response", 1); + response_packet->setSmallStringByName("server", ip_address); + response_packet->setDataByName("port", port); + response_packet->setDataByName("account_id", GetAccountID()); + response_packet->setDataByName("access_code", access_key); + EQ2Packet* outapp = response_packet->serialize(); + QueuePacket(outapp); + safe_delete(response_packet); + } + return; +} +void Client::FatalError(int8 response) { + safe_delete(playWaitTimer); + SendPlayFailed(response); +} + +void Client::SendPlayFailed(int8 response){ + PacketStruct* response_packet = configReader.getStruct("LS_PlayResponse", GetVersion()); + if(response_packet){ + response_packet->setDataByName("response", response); + response_packet->setSmallStringByName("server", ""); + response_packet->setDataByName("port", 0); + response_packet->setDataByName("account_id", GetAccountID()); + response_packet->setDataByName("access_code", 0); + EQ2Packet* outapp = response_packet->serialize(); + QueuePacket(outapp); + safe_delete(response_packet); + } +} + +void ClientList::Add(Client* client) { + MClientList.writelock(); + client_list[client] = true; + MClientList.releasewritelock(); +} + +Client* ClientList::Get(int32 ip, int16 port) { + Client* ret = 0; + map::iterator itr; + MClientList.readlock(); + for(itr = client_list.begin(); itr != client_list.end(); itr++){ + if(itr->first->GetIP() == ip && itr->first->GetPort() == port){ + ret = itr->first; + break; + } + } + MClientList.releasereadlock(); + return ret; +} + +void ClientList::FindByCreateRequest(){ + Client* client = 0; + map::iterator itr; + MClientList.readlock(); + for(itr = client_list.begin(); itr != client_list.end(); itr++){ + if(itr->first->AwaitingCharCreationRequest()){ + if(!client) + client = itr->first; + else{ + client = 0;//more than 1 character waiting, dont want to send rejection to wrong one + break; + } + } + } + MClientList.releasereadlock(); + if(client) + client->CharacterRejected(UNKNOWNERROR_REPLY); +} + +Client* ClientList::FindByLSID(int32 lsaccountid) { + Client* client = 0; + map::iterator itr; + MClientList.readlock(); + for(itr = client_list.begin(); itr != client_list.end(); itr++){ + if(itr->first->GetAccountID() == lsaccountid){ + client = itr->first; + break; + } + } + MClientList.releasereadlock(); + return client; +} +void ClientList::SendPacketToAllClients(EQ2Packet* app){ + Client* client = 0; + map::iterator itr; + MClientList.readlock(); + if(client_list.size() > 0){ + for(itr = client_list.begin(); itr != client_list.end(); itr++){ + itr->first->QueuePacket(app->Copy()); + } + } + safe_delete(app); + MClientList.releasereadlock(); +} +void ClientList::Process() { + Client* client = 0; + vector erase_list; + map::iterator itr; + MClientList.readlock(); + for(itr = client_list.begin(); itr != client_list.end(); itr++){ + client = itr->first; + if(!client->Process()) + erase_list.push_back(client); + } + MClientList.releasereadlock(); + if(erase_list.size() > 0){ + vector::iterator erase_itr; + MClientList.writelock(); + for(erase_itr = erase_list.begin(); erase_itr != erase_list.end(); erase_itr++){ + client = *erase_itr; + struct in_addr in; + in.s_addr = client->getConnection()->GetRemoteIP(); + net.numclients--; + LogWrite(LOGIN__INFO, 0, "Login", "Removing client from ip: %s on port %i, Account Name: %s", inet_ntoa(in), ntohs(client->getConnection()->GetRemotePort()), client->GetAccountName()); + client->getConnection()->Close(); + net.UpdateWindowTitle(); + client_list.erase(client); + } + MClientList.releasewritelock(); + } +} + + +void Client::StartDisconnectTimer() { + if (!disconnectTimer) + { + disconnectTimer = new Timer(1000); + disconnectTimer->Start(); + } +} diff --git a/source/LoginServer/client.h b/source/LoginServer/client.h new file mode 100644 index 0000000..4d3936e --- /dev/null +++ b/source/LoginServer/client.h @@ -0,0 +1,131 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#ifndef CLIENT_H +#define CLIENT_H + +#include "../common/linked_list.h" +#include "../common/timer.h" +#include "../common/TCPConnection.h" +#include "login_structs.h" +#include "LoginAccount.h" +#include "../common/PacketStruct.h" +#include +#include + +enum eLoginMode { None, Normal, Registration }; +class DelayQue; +class Client +{ +public: + Client(EQStream* ieqnc); + ~Client(); + void SendLoginDenied(); + void SendLoginDeniedBadVersion(); + void SendLoginAccepted(int32 account_id = 1, int8 login_response = 0); + void SendWorldList(); + void SendCharList(); + int16 AddWorldToList2(uchar* buffer, char* name, int32 id, int16* flags); + void GenerateChecksum(EQApplicationPacket* outapp); + int8 LoginKey[10]; + int8 ClientSession[25]; + bool Process(); + void SaveErrorsToDB(EQApplicationPacket* app, char* type, int32 version); + void CharacterApproved(int32 server_id,int32 char_id); + void CharacterRejected(int8 reason_number); + EQStream* getConnection() { return eqnc; } + LoginAccount* GetLoginAccount() { return login_account; } + void SetLoginAccount(LoginAccount* in_account) { + login_account = in_account; + if(in_account) + account_id = in_account->getLoginAccountID(); + } + int16 GetVersion(){ return version; } + char* GetKey() { return key; } + void SetKey(char* in_key) { strcpy(key,in_key); } + int32 GetIP() { return ip; } + int16 GetPort() { return port; } + int32 GetAccountID() { return account_id; } + const char* GetAccountName(){ return (char*)account_name.c_str(); } + void SetAccountName(const char* name){ account_name = string(name); } + void ProcessLogin(char* name, char* pass,int seq=0); + void QueuePacket(EQ2Packet* app); + void FatalError(int8 response); + void WorldResponse(int32 worldid, int8 response, char* ip_address, int32 port, int32 access_key); + bool AwaitingCharCreationRequest(){ + if(createRequest) + return true; + else + return false; + } + Timer* updatetimer; + Timer* updatelisttimer; + Timer* disconnectTimer; + //Timer* keepalive; + //Timer* logintimer; + int16 packettotal; + int32 requested_server_id; + int32 request_num; + LinkedList delay_que; + void SendPlayFailed(int8 response); + + void StartDisconnectTimer(); +private: + string pending_play_char_name; + int32 pending_play_char_id; + int8 update_position; + int16 num_updates; + vector* update_packets; + LoginAccount* login_account; + EQStream* eqnc; + + int32 ip; + int16 port; + + int32 account_id; + string account_name; + char key[10]; + int8 lsadmin; + sint16 worldadmin; + int lsstatus; + bool kicked; + bool verified; + bool start; + bool needs_world_list; + int16 version; + char bannedreason[30]; + bool sent_character_list; + eLoginMode LoginMode; + PacketStruct* createRequest; + Timer* playWaitTimer; +}; + +class ClientList +{ +public: + ClientList() {} + ~ClientList() {} + + void Add(Client* client); + Client* Get(int32 ip, int16 port); + Client* FindByLSID(int32 lsaccountid); + void FindByCreateRequest(); + void SendPacketToAllClients(EQ2Packet* app); + void Process(); +private: + Mutex MClientList; + map client_list; +}; +class DelayQue { +public: + DelayQue(Timer* in_timer, EQApplicationPacket* in_packet){ + timer = in_timer; + packet = in_packet; + }; + Timer* timer; + EQApplicationPacket* packet; +}; +#endif diff --git a/source/LoginServer/login_opcodes.h b/source/LoginServer/login_opcodes.h new file mode 100644 index 0000000..734ae2f --- /dev/null +++ b/source/LoginServer/login_opcodes.h @@ -0,0 +1,52 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ + +#ifndef LOGIN_OPCODES_H +#define LOGIN_OPCODES_H +#define OP_Login2 0x0200 +#define OP_GetLoginInfo 0x0300 +#define OP_SendServersFragment 0x0D00 +#define OP_LoginInfo 0x0100 +#define OP_SessionId 0x0900 +#define OP_Disconnect 0x0500 +//#define OP_Reg_SendPricing 0x0400 +#define OP_AllFinish 0x0500 +#define OP_Ack5 0x1500 +#define OP_Chat_ChannelList 0x0600 +#define OP_Chat_JoinChannel 0x0700 +#define OP_Chat_PartChannel 0x0800 +#define OP_Chat_ChannelMessage 0x0930 +#define OP_Chat_Tell 0x0a00 +#define OP_Chat_SysMsg 0x0b00 +#define OP_Chat_CreateChannel 0x0c00 +#define OP_Chat_ChangeChannel 0x0d00 +#define OP_Chat_DeleteChannel 0x0e00 +#define OP_Chat_UserList 0x1000 +#define OP_Reg_GetPricing 0x1a00 // for new account signup +#define OP_Reg_SendPricing 0x1b00 +#define OP_RegisterAccount 0x2300 +#define OP_Chat_ChannelWelcome 0x2400 +#define OP_Chat_PopupMakeWindow 0x3000 +#define OP_BillingInfoAccepted 0x3300 // i THINK =p +#define OP_CheckGameCardValid 0x3400 +#define OP_GameCardTimeLeft 0x3600 +#define OP_AccountExpired 0x4200 +#define OP_Reg_GetPricing2 0x4400 // for re-registering +#define OP_ChangePassword 0x4500 +#define OP_ServerList 0x4600 +#define OP_SessionKey 0x4700 +#define OP_RequestServerStatus 0x4800 +#define OP_SendServerStatus 0x4A00 +#define OP_Reg_ChangeAcctLogin 0x5100 +#define OP_LoginBanner 0x5200 +#define OP_Chat_GuildsList 0x5500 +#define OP_Chat_GuildEdit 0x5700 +#define OP_Version 0x5900 +#define OP_RenewAccountBillingInfo 0x7a00 + +#endif /* LOGIN_OPCODES_H */ + diff --git a/source/LoginServer/login_structs.h b/source/LoginServer/login_structs.h new file mode 100644 index 0000000..bd8e9b9 --- /dev/null +++ b/source/LoginServer/login_structs.h @@ -0,0 +1,61 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#ifndef LOGIN_STRUCTS_H +#define LOGIN_STRUCTS_H + +#include "../common/types.h" +#include "PacketHeaders.h" + +#pragma pack(1) +struct LS_LoginRequest{ + EQ2_16BitString AccessCode; + EQ2_16BitString unknown1; + EQ2_16BitString username; + EQ2_16BitString password; + EQ2_16BitString unknown2[4]; + int16 unknown3; + int32 unknown4[2]; +}; +struct LS_WorldStatusChanged{ + int32 server_id; + int8 up; + int8 locked; + int8 hidden; +}; +struct LS_PlayCharacterRequest{ + int32 character_id; + int32 server_id; + int16 unknown1; +}; +struct LS_OLDPlayCharacterRequest{ + int32 character_id; + EQ2_16BitString name; +}; + +struct LS_CharListAccountInfoEarlyClient { + int32 account_id; + int32 unknown1; + int16 unknown2; + int32 maxchars; + int8 unknown4; // 15 bytes total + // int8 unknown7; // adds 'free' option.. +}; + +struct LS_CharListAccountInfo{ + int32 account_id; + int32 unknown1; + int16 unknown2; + int32 maxchars; + // DoF does not have the following data + int8 unknown4; + int32 unknown5[4]; + int8 vet_adv_bonus; // sets Veteran Bonus under 'Select Character' yellow (vs greyed out), adventure/tradeskill bonus 200% + int8 vet_trade_bonus; // when 1 (count?) provides free upgrade option for character to lvl 90 (heroic character) -- its a green 'Free' up arrow next to the character that is selected in char select +}; // 33 bytes +#pragma pack() + +#endif diff --git a/source/LoginServer/makefile b/source/LoginServer/makefile new file mode 100644 index 0000000..a4ce509 --- /dev/null +++ b/source/LoginServer/makefile @@ -0,0 +1,32 @@ +APP=login +SF= ../common/Log.o ../common/timer.o ../common/packet_dump.o ../common/unix.o \ + ../common/Mutex.o ../common/MiscFunctions.o LoginDatabase.o LoginAccount.o \ + ../common/TCPConnection.o ../common/emu_opcodes.o \ + client.o net.o PacketHeaders.o LWorld.o ../common/md5.o ../common/dbcore.o \ + Web/LoginWeb.o \ + ../common/EQEMuError.o ../common/misc.o ../common/Crypto.o ../common/RC4.o \ + .obj/debug.o .obj/database.o .obj/EQStream.o ../common/xmlParser.o \ + .obj/EQStreamFactory.o .obj/EQPacket.o ../common/CRC16.o ../common/packet_functions.o \ + ../common/Condition.o ../common/opcodemgr.o ../common/PacketStruct.o ../common/ConfigReader.o \ + ../common/DatabaseNew.o ../common/DatabaseResult.o ../common/Web/WebServer.o ../common/JsonParser.o + +CC=g++ +LINKER=gcc +DFLAGS=-DEQ2 -DLOGIN +WFLAGS=-Wall -Wuninitialized -Wwrite-strings -Wcast-qual -Wcomment -Wcast-align -Wno-deprecated +COPTS=$(WFLAGS) -ggdb -march=native -pthread -pipe -DFX -D_GNU_SOURCE -DINVERSEXY $(DFLAGS) -I/usr/include/mariadb -I/usr/local/include/boost -std=c++17 +LINKOPTS=-rdynamic -L. -lstdc++ -lm -lz -L/usr/lib/x86_64-linux-gnu -lmariadb -lboost_system -lboost_thread -lboost_filesystem -lssl -lcrypto -lpthread -ldl +all: $(APP) + +$(APP): $(SF) + $(LINKER) $(COPTS) $(OBJS) $^ $(LINKOPTS) -o $@ + +clean: + rm -f $(SF) $(APP) + +%.o: %.cpp + $(CC) -c $(COPTS) $< -o $@ + +.obj/%.o: ../common/%.cpp ../common/%.h + mkdir -p .obj + $(CC) $(COPTS) -c $< -o $@ diff --git a/source/LoginServer/net.cpp b/source/LoginServer/net.cpp new file mode 100644 index 0000000..fe6b8cd --- /dev/null +++ b/source/LoginServer/net.cpp @@ -0,0 +1,363 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#include "../common/debug.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include "../common/queue.h" +#include "../common/timer.h" + +#include "../common/seperator.h" + +#include "net.h" +#include "client.h" + +#include "LoginDatabase.h" +#include "LWorld.h" +#include "../common/packet_functions.h" +#include "../common/EQStreamFactory.h" +#include "../common/MiscFunctions.h" +#include "../common/version.h" + +#include "../common/PacketStruct.h" +#include "../common/DataBuffer.h" +#include "../common/ConfigReader.h" +#include "../common/Log.h" +#include "../common/JsonParser.h" +#include "../common/Common_Defines.h" + +#ifdef WIN32 + #define snprintf _snprintf + #define vsnprintf _vsnprintf + #define strncasecmp _strnicmp + #define strcasecmp _stricmp + #include +#else + #include + #include "../common/unix.h" +#endif +EQStreamFactory eqsf(LoginStream); +mapEQOpcodeManager; +//TCPServer eqns(5999); +NetConnection net; +ClientList client_list; +LWorldList world_list; +LoginDatabase database; +ConfigReader configReader; +map EQOpcodeVersions; +Timer statTimer(60000); + +volatile bool RunLoops = true; +bool ReadLoginConfig(); + +#ifdef PUBLICLOGIN +char version[200], consoletitle[200]; +#endif +#include "../common/timer.h" + +#include "../common/CRC16.h" +#include + +int main(int argc, char** argv){ +#ifdef _DEBUG + _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); +#endif + if (signal(SIGINT, CatchSignal) == SIG_ERR) { + cerr << "Could not set signal handler" << endl; + } + + LogStart(); + + LogParseConfigs(); + net.WelcomeHeader(); + + srand(time(NULL)); + + if(!net.ReadLoginConfig()) + return 1; + + net.InitWebServer(net.GetWebLoginAddress(), net.GetWebLoginPort(), net.GetWebCertFile(), net.GetWebKeyFile(), net.GetWebKeyPassword(), net.GetWebHardcodeUser(), net.GetWebHardcodePassword()); + + const char* structList[] = { "CommonStructs.xml", "LoginStructs.xml" }; + + for (int s = 0; s < sizeof(structList) / sizeof(const char*); s++) + { + LogWrite(INIT__INFO, 0, "Init", "Loading Structs File %s..", structList[s]); + if (configReader.processXML_Elements(structList[s])) + LogWrite(INIT__INFO, 0, "Init", "Loading Structs File %s completed..", structList[s]); + else + { + LogWrite(INIT__ERROR, 0, "Init", "Loading Structs File %s FAILED!", structList[s]); + return 1; + } + } + + + LogWrite(INIT__INFO, 0, "Init", "Initialize World List.."); + world_list.Init(); + + if(eqsf.listen_ip_address) + LogWrite(INIT__INFO, 0, "Init", "Login server listening on %s port %i", eqsf.listen_ip_address, net.GetPort()); + else + LogWrite(INIT__INFO, 0, "Init", "Login server listening on port %i", net.GetPort()); + /*} + else { + cout << "EQNetworkServer.Open() error" << endl; + return 1; + }*/ + if (!eqsf.Open(net.GetPort())) { + LogWrite(INIT__ERROR, 0, "Init", "Failed to open port %i.", net.GetPort()); + return 1; + } + net.login_running = true; + net.login_uptime = getCurrentTimestamp(); + + net.UpdateWindowTitle(); + EQStream* eqs; + Timer* TimeoutTimer = new Timer(5000); + TimeoutTimer->Start(); + while(RunLoops) { + Timer::SetCurrentTime(); + while ((eqs = eqsf.Pop())) { + struct in_addr in; + in.s_addr = eqs->GetRemoteIP(); + + LogWrite(LOGIN__INFO, 0, "Login", "New client from IP: %s on port %i", inet_ntoa(in), ntohs(eqs->GetRemotePort())); + Client* client = new Client(eqs); + eqs->SetClientVersion(0); + client_list.Add(client); + net.numclients++; + net.UpdateWindowTitle(); + } + if(TimeoutTimer->Check()){ + eqsf.CheckTimeout(); + } + if(statTimer.Check()){ + world_list.UpdateWorldStats(); + database.RemoveOldWorldServerStats(); + database.FixBugReport(); + } + client_list.Process(); + world_list.Process(); +#ifdef WIN32 + if(kbhit()) + { + int hitkey = getch(); + net.HitKey(hitkey); + } +#endif + Sleep(1); + } + //close + //eqns.Close(); + eqsf.Close(); + world_list.Shutdown(); + return 0; +} +#ifdef WIN32 +void NetConnection::HitKey(int keyhit) +{ + switch(keyhit) + { + case 'l': + case 'L': { + world_list.ListWorldsToConsole(); + break; + } + case 'v': + case 'V': + { + printf("========Version Info=========\n"); + printf("%s %s\n", EQ2EMU_MODULE, CURRENT_VERSION); + printf("Last Compiled on %s %s\n", COMPILE_DATE, COMPILE_TIME); + printf("=============================\n\n"); + break; + } + case 'H': + case 'h': { + printf("===========Help=============\n"); + printf("Available Commands:\n"); + printf("l = Listing of World Servers\n"); + printf("v = Login Version\n"); +// printf("0 = Kick all connected world servers\n"); + printf("============================\n\n"); + break; + } + default: + printf("Invalid Command.\n"); + break; + } +} +#endif + +void CatchSignal(int sig_num) { + cout << "Got signal " << sig_num << endl; + RunLoops = false; +} + +bool NetConnection::ReadLoginConfig() { + JsonParser parser(MAIN_CONFIG_FILE); + if(!parser.IsLoaded()) { + LogWrite(INIT__ERROR, 0, "Init", "Failed to find %s in server directory..", MAIN_CONFIG_FILE); + return false; + } + std::string serverport = parser.getValue("loginconfig.serverport"); + std::string serverip = parser.getValue("loginconfig.serverip"); + + if (!parser.convertStringToUnsignedShort(serverport, port)) { + LogWrite(INIT__ERROR, 0, "Init", "Failed to translate loginconfig.serverport.."); + return false; + } + + if(serverip.size() > 0) { + eqsf.listen_ip_address = new char[serverip.size() + 1]; + strcpy(eqsf.listen_ip_address, serverip.c_str()); + } + else { + safe_delete(eqsf.listen_ip_address); + eqsf.listen_ip_address = nullptr; + } + + std::string acctcreate_str = parser.getValue("loginconfig.accountcreation"); + int16 allow_acct = 0; + parser.convertStringToUnsignedShort(acctcreate_str, allow_acct); + allowAccountCreation = allow_acct > 0 ? true : false; + + std::string expflag_str = parser.getValue("loginconfig.expansionflag"); + parser.convertStringToUnsignedInt(expflag_str, expansionFlag); + + std::string citiesflag_str = parser.getValue("loginconfig.citiesflag"); + parser.convertStringToUnsignedChar(citiesflag_str, citiesFlag); + + std::string defaultsublevel_str = parser.getValue("loginconfig.defaultsubscriptionlevel"); + parser.convertStringToUnsignedInt(defaultsublevel_str, defaultSubscriptionLevel); + + std::string enableraces_str = parser.getValue("loginconfig.enabledraces"); + parser.convertStringToUnsignedInt(enableraces_str, enabledRaces); + + web_loginaddress = parser.getValue("loginconfig.webloginaddress"); + web_certfile = parser.getValue("loginconfig.webcertfile"); + web_keyfile = parser.getValue("loginconfig.webkeyfile"); + web_keypassword = parser.getValue("loginconfig.webkeypassword"); + web_hardcodeuser = parser.getValue("loginconfig.webhardcodeuser"); + web_hardcodepassword = parser.getValue("loginconfig.webhardcodepassword"); + + std::string webloginport_str = parser.getValue("loginconfig.webloginport"); + parser.convertStringToUnsignedShort(webloginport_str, web_loginport); + + LogWrite(INIT__INFO, 0, "Init", "%s loaded..", MAIN_CONFIG_FILE); + + + LogWrite(INIT__INFO, 0, "Init", "Database init begin.."); + //remove this when all database calls are using the new database class + if (!database.Init()) { + LogWrite(INIT__ERROR, 0, "Init", "Database init FAILED!"); + LogStop(); + return false; + } + + LogWrite(INIT__INFO, 0, "Init", "Loading opcodes 2.0.."); + EQOpcodeVersions = database.GetVersions(); + map::iterator version_itr2; + int16 version1 = 0; + for (version_itr2 = EQOpcodeVersions.begin(); version_itr2 != EQOpcodeVersions.end(); version_itr2++) { + version1 = version_itr2->first; + EQOpcodeManager[version1] = new RegularOpcodeManager(); + map eq = database.GetOpcodes(version1); + if(!EQOpcodeManager[version1]->LoadOpcodes(&eq)) { + LogWrite(INIT__ERROR, 0, "Init", "Loading opcodes failed. Make sure you have sourced the opcodes.sql file!"); + return false; + } + } + + return true; +} + +void NetConnection::UpdateWindowTitle(char* iNewTitle) { +#ifdef WIN32 + char tmp[500]; + if (iNewTitle) { + snprintf(tmp, sizeof(tmp), "Login: %s", iNewTitle); + } + else { + snprintf(tmp, sizeof(tmp), "%s, Version: %s: %i Server(s), %i Client(s) Connected", EQ2EMU_MODULE, CURRENT_VERSION, net.numservers, net.numclients); + } + SetConsoleTitle(tmp); +#endif +} + +void NetConnection::WelcomeHeader() +{ +#ifdef _WIN32 + HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleTextAttribute(console, FOREGROUND_WHITE_BOLD); +#endif + printf("Module: %s, Version: %s", EQ2EMU_MODULE, CURRENT_VERSION); +#ifdef _WIN32 + SetConsoleTextAttribute(console, FOREGROUND_YELLOW_BOLD); +#endif + printf("\n\nCopyright (C) 2007-2021 EQ2Emulator. https://www.eq2emu.com \n\n"); + printf("EQ2Emulator is free software: you can redistribute it and/or modify\n"); + printf("it under the terms of the GNU General Public License as published by\n"); + printf("the Free Software Foundation, either version 3 of the License, or\n"); + printf("(at your option) any later version.\n\n"); + printf("EQ2Emulator is distributed in the hope that it will be useful,\n"); + printf("but WITHOUT ANY WARRANTY; without even the implied warranty of\n"); + printf("MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"); + printf("GNU General Public License for more details.\n\n"); +#ifdef _WIN32 + SetConsoleTextAttribute(console, FOREGROUND_GREEN_BOLD); +#endif + printf(" /$$$$$$$$ /$$$$$$ /$$$$$$ /$$$$$$$$ \n"); + printf("| $$_____/ /$$__ $$ /$$__ $$| $$_____/ \n"); + printf("| $$ | $$ \\ $$|__/ \\ $$| $$ /$$$$$$/$$$$ /$$ /$$\n"); + printf("| $$$$$ | $$ | $$ /$$$$$$/| $$$$$ | $$_ $$_ $$| $$ | $$\n"); + printf("| $$__/ | $$ | $$ /$$____/ | $$__/ | $$ \\ $$ \\ $$| $$ | $$\n"); + printf("| $$ | $$/$$ $$| $$ | $$ | $$ | $$ | $$| $$ | $$\n"); + printf("| $$$$$$$$| $$$$$$/| $$$$$$$$| $$$$$$$$| $$ | $$ | $$| $$$$$$/\n"); + printf("|________/ \\____ $$$|________/|________/|__/ |__/ |__/ \\______/ \n"); + printf(" \\__/ \n\n"); +#ifdef _WIN32 + SetConsoleTextAttribute(console, FOREGROUND_MAGENTA_BOLD); +#endif + printf(" Website : https://eq2emu.com \n"); + printf(" Wiki : https://wiki.eq2emu.com \n"); + printf(" Git : https://git.eq2emu.com \n"); + printf(" Discord : https://discord.gg/5Cavm9NYQf \n\n"); +#ifdef _WIN32 + SetConsoleTextAttribute(console, FOREGROUND_WHITE_BOLD); +#endif + printf("For more detailed logging, modify 'Level' param the log_config.xml file.\n\n"); +#ifdef _WIN32 + SetConsoleTextAttribute(console, FOREGROUND_WHITE); +#endif + + fflush(stdout); +} + +void NetConnection::InitWebServer(std::string web_ipaddr, int16 web_port, std::string cert_file, std::string key_file, std::string key_password, std::string hardcode_user, std::string hardcode_password) { + if(web_ipaddr.size() > 0 && web_port > 0) { + try { + login_webserver = new WebServer(web_ipaddr, web_port, cert_file, key_file, key_password, hardcode_user, hardcode_password); + + login_webserver->register_route("/status", NetConnection::Web_loginhandle_status); + login_webserver->register_route("/worlds", NetConnection::Web_loginhandle_worlds); + login_webserver->run(); + LogWrite(INIT__INFO, 0, "Init", "Login Web Server is listening on %s:%u..", web_ipaddr.c_str(), web_port); + } + catch (const std::exception& e) { + LogWrite(INIT__ERROR, 0, "Init", "Login Web Server failed to listen on %s:%u due to reason %s", web_ipaddr.c_str(), web_port, e.what()); + } + } +} \ No newline at end of file diff --git a/source/LoginServer/net.h b/source/LoginServer/net.h new file mode 100644 index 0000000..0f7b628 --- /dev/null +++ b/source/LoginServer/net.h @@ -0,0 +1,147 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN + #include + #include +#else + #include + #include + #include + #include +#endif + +//#include +#include +#include +#include + +#include "../common/types.h" +#include "../common/Web/WebServer.h" +#include "../common/MiscFunctions.h" + +void CatchSignal(int sig_num); +enum eServerMode { Standalone, Master, Slave, Mesh }; + +class NetConnection +{ +public: + NetConnection() { + port = 5999; + listening_socket = 0; + memset(masteraddress, 0, sizeof(masteraddress)); + uplinkport = 0; + memset(uplinkaccount, 0, sizeof(uplinkaccount)); + memset(uplinkpassword, 0, sizeof(uplinkpassword)); + LoginMode = Standalone; + Uplink_WrongVersion = false; + numclients = 0; + numservers = 0; + allowAccountCreation = true; + + // full support = 0x7CFF + // 1 << 12 (-4096) = missing echoes of faydwer, disables Fae and Arasai (black portraits) and kelethin as starting city + // 1 << 13 (-8192) = disables sarnak (black portraits) and gorowyn as starting city + expansionFlag = 0x7CFF; // 0x4CF5 + + /* dword_1ECBA18 operand for race flag packs (sublevel 0,1,2?) -- (sublevel -1) controls starting zones omission 0xEE vs 0xCF (CF misses halas) + 1 = city of qeynos + 2 = city of freeport + 4 = city of kelethin + 8 = city of neriak + 16 = gorowyn + 32 = new halas + 64 = queens colony + 128 = outpost overlord + */ + citiesFlag = 0xFF; + + // sub_level 0xFFFFFFFF = blacks out all portraits for class alignments, considered non membership + // sub_level > 0 = class alignments still required, but portraits are viewable and race selectable + // sub_level = 2 membership, you can 'create characters on time locked servers' vs standard + // sub_level = 0 forces popup on close to web browser + defaultSubscriptionLevel = 0xFFFFFFFF; + + // disable extra races FAE(16) ARASAI (17) SARNAK (18) -- with 4096/8192 flags, no visibility of portraits + enabledRaces = 0xFFFF; // 0xCFFF + + web_loginport = 0; + + login_webserver = nullptr; + + login_running = false; + login_uptime = getCurrentTimestamp(); + } + + ~NetConnection() { + safe_delete(login_webserver); + } + + void UpdateWindowTitle(char* iNewTitle = 0); + bool Init(); + void ListenNewClients(); + void HitKey(int keyhit); + char address[1024]; + int32 numclients; + int32 numservers; + + int16 GetPort() { return port; } + void SetPort(int16 in_port) { port = in_port; } + eServerMode GetLoginMode() { return LoginMode; } + + bool ReadLoginConfig(); + char* GetMasterAddress() { return masteraddress; } + int16 GetUplinkPort() { if (uplinkport != 0) return uplinkport; else return port; } + char* GetUplinkAccount() { return uplinkaccount; } + char* GetUplinkPassword() { return uplinkpassword; } + + bool IsAllowingAccountCreation() { return allowAccountCreation; } + int32 GetExpansionFlag() { return expansionFlag; } + int8 GetCitiesFlag() { return citiesFlag; } + int32 GetDefaultSubscriptionLevel() { return defaultSubscriptionLevel; } + int32 GetEnabledRaces() { return enabledRaces; } + std::string GetWebLoginAddress() { return web_loginaddress; } + inline int16 GetWebLoginPort() { return web_loginport; } + std::string GetWebCertFile() { return web_certfile; } + std::string GetWebKeyFile() { return web_keyfile; } + std::string GetWebKeyPassword() { return web_keypassword; } + std::string GetWebHardcodeUser() { return web_hardcodeuser; } + std::string GetWebHardcodePassword() { return web_hardcodepassword; } + void WelcomeHeader(); + + void InitWebServer(std::string web_ipaddr, int16 web_port, std::string cert_file, std::string key_file, std::string key_password, std::string hardcode_user, std::string hardcode_password); + + static void Web_loginhandle_status(const http::request& req, http::response& res); + static void Web_loginhandle_worlds(const http::request& req, http::response& res); + + bool login_running; + std::atomic login_uptime; +protected: + friend class LWorld; + bool Uplink_WrongVersion; +private: + int16 port; + int listening_socket; + char masteraddress[300]; + int16 uplinkport; + char uplinkaccount[300]; + char uplinkpassword[300]; + eServerMode LoginMode; + bool allowAccountCreation; + int32 expansionFlag; + int8 citiesFlag; + int32 defaultSubscriptionLevel; + int32 enabledRaces; + std::string web_loginaddress; + std::string web_certfile; + std::string web_keyfile; + std::string web_keypassword; + std::string web_hardcodeuser; + std::string web_hardcodepassword; + int16 web_loginport; + WebServer* login_webserver; +}; diff --git a/source/WorldServer/Achievements/Achievements.cpp b/source/WorldServer/Achievements/Achievements.cpp new file mode 100644 index 0000000..0d65164 --- /dev/null +++ b/source/WorldServer/Achievements/Achievements.cpp @@ -0,0 +1,332 @@ +/* +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 . +*/ + +#include "Achievements.h" + +#include "../../common/Log.h" +#include "../../common/ConfigReader.h" +#include + +extern ConfigReader configReader; +extern MasterAchievementList master_achievement_list; + +Achievement::Achievement() { + id = 0; + memset(title, 0, sizeof(title)); + memset(uncompleted_text, 0, sizeof(uncompleted_text)); + memset(completed_text, 0, sizeof(completed_text)); + memset(category, 0, sizeof(category)); + memset(expansion, 0, sizeof(expansion)); + icon = 0; + point_value = 0; + qty_req = 0; + hide = false; + unknown3a = 0; + unknown3b = 0; +} + +Achievement::Achievement(Achievement *in) { + vector *requirements_in; + vector *rewards_in; + vector::iterator itr; + vector::iterator itr2; + struct AchievementRequirements *achievement_requirement; + struct AchievementRewards *achievement_reward; + + assert(in); + + id = in->GetID(); + strncpy(title, in->GetTitle(), sizeof(title)); + strncpy(uncompleted_text, in->GetUncompletedText(), sizeof(uncompleted_text)); + strncpy(completed_text, in->GetCompletedText(), sizeof(completed_text)); + strncpy(category, in->GetCategory(), sizeof(category)); + strncpy(expansion, in->GetExpansion(), sizeof(expansion)); + icon = in->GetIcon(); + point_value = in->GetPointValue(); + qty_req = in->GetQtyReq(); + hide = in->GetHide(); + unknown3a = in->GetUnknown3a(); + unknown3b = in->GetUnknown3b(); + + requirements_in = in->GetRequirements(); + for (itr = requirements_in->begin(); itr != requirements_in->end(); itr++) { + achievement_requirement = new struct AchievementRequirements; + achievement_requirement->achievement_id = (*itr)->achievement_id; + achievement_requirement->name = (*itr)->name; + achievement_requirement->qty_req = (*itr)->qty_req; + requirements.push_back(achievement_requirement); + } + + rewards_in = in->GetRewards(); + for (itr2 = rewards_in->begin(); itr2 != rewards_in->end(); itr2++) { + achievement_reward = new struct AchievementRewards; + achievement_reward->achievement_id = (*itr2)->achievement_id; + achievement_reward->reward = (*itr2)->reward; + rewards.push_back(achievement_reward); + } +} + +Achievement::~Achievement() { + vector::iterator itr; + vector::iterator itr2; + + for (itr = requirements.begin(); itr != requirements.end(); itr++) + safe_delete(*itr); + for (itr2 = rewards.begin(); itr2 != rewards.end(); itr2++) + safe_delete(*itr2); +} + +void Achievement::AddAchievementRequirement(struct AchievementRequirements *requirement) { + assert(requirement); + + requirements.push_back(requirement); +} + +void Achievement::AddAchievementReward(struct AchievementRewards *reward) { + assert(reward); + + rewards.push_back(reward); +} + +void AchievementUpdate::AddAchievementUpdateItems(struct AchievementUpdateItems *update_item) { + assert(update_item); + + update_items.push_back(update_item); +} + +MasterAchievementList::MasterAchievementList() { + m_packetsCreated = false; + masterPacket = 0; + mutex_achievements.SetName("MasterAchievementList::achievements"); +} + +MasterAchievementList::~MasterAchievementList() { + ClearAchievements(); +} + +bool MasterAchievementList::AddAchievement(Achievement *achievement) { + bool ret = false; + + assert(achievement); + + mutex_achievements.writelock(__FUNCTION__, __LINE__); + if (achievements.count(achievement->GetID()) == 0) { + achievements[achievement->GetID()] = achievement; + ret = true; + } + mutex_achievements.releasewritelock(__FUNCTION__, __LINE__); + + return ret; +} + +Achievement * MasterAchievementList::GetAchievement(int32 achievement_id) { + Achievement *achievement = 0; + + mutex_achievements.readlock(__FUNCTION__, __LINE__); + if (achievements.count(achievement_id) > 0) + achievement = achievements[achievement_id]; + mutex_achievements.releasereadlock(__FUNCTION__, __LINE__); + + return achievement; +} + +void MasterAchievementList::ClearAchievements() { + map::iterator itr; + + mutex_achievements.writelock(__FUNCTION__, __LINE__); + for (itr = achievements.begin(); itr != achievements.end(); itr++) + safe_delete(itr->second); + achievements.clear(); + mutex_achievements.releasewritelock(__FUNCTION__, __LINE__); +} + +int32 MasterAchievementList::Size() { + int32 size; + + mutex_achievements.readlock(__FUNCTION__, __LINE__); + size = achievements.size(); + mutex_achievements.releasereadlock(__FUNCTION__, __LINE__); + + return size; +} + +PlayerAchievementList::PlayerAchievementList() { +} + +PlayerAchievementList::~PlayerAchievementList() { + ClearAchievements(); +} + +bool PlayerAchievementList::AddAchievement(Achievement *achievement) { + assert(achievement); + + if (achievements.count(achievement->GetID()) == 0) { + achievements[achievement->GetID()] = achievement; + return true; + } + + return false; +} + +Achievement * PlayerAchievementList::GetAchievement(int32 achievement_id) { + if (achievements.count(achievement_id) > 0) + return achievements[achievement_id]; + + return 0; +} + +void PlayerAchievementList::ClearAchievements() { + map::iterator itr; + + for (itr = achievements.begin(); itr != achievements.end(); itr++) + safe_delete(itr->second); + achievements.clear(); +} + +int32 PlayerAchievementList::Size() { + return achievements.size(); +} + +AchievementUpdate::AchievementUpdate() { + id = 0; + completed_date = 0; + +} + +AchievementUpdate::AchievementUpdate(AchievementUpdate *in) { + vector *items_in; + vector::iterator itr; + struct AchievementUpdateItems *items; + + assert(in); + + id = in->GetID(); + completed_date = in->GetCompletedDate(); + + items_in = in->GetUpdateItems(); + for (itr = items_in->begin(); itr != items_in->end(); itr++) { + items = new struct AchievementUpdateItems; + items->achievement_id = (*itr)->achievement_id; + items->item_update = (*itr)->item_update; + update_items.push_back(items); + } +} + +AchievementUpdate::~AchievementUpdate() { + vector::iterator itr; + + for (itr = update_items.begin(); itr != update_items.end(); itr++) + safe_delete(*itr); +} + + +PlayerAchievementUpdateList::PlayerAchievementUpdateList() { + +} + +PlayerAchievementUpdateList::~PlayerAchievementUpdateList() { + ClearAchievementUpdates(); +} + +bool PlayerAchievementUpdateList::AddAchievementUpdate(AchievementUpdate *update) { + assert(update); + + if (achievement_updates.count(update->GetID()) == 0) { + achievement_updates[update->GetID()] = update; + return true; + } + return false; +} + +void PlayerAchievementUpdateList::ClearAchievementUpdates() { + map::iterator itr; + + for (itr = achievement_updates.begin(); itr != achievement_updates.end(); itr++) + safe_delete(itr->second); + achievement_updates.clear(); +} + +int32 PlayerAchievementUpdateList::Size() { + return achievement_updates.size(); +} + +void MasterAchievementList::CreateMasterAchievementListPacket() { + map::iterator itr; + Achievement *achievement; + vector *requirements = 0; + vector::iterator itr2; + AchievementRequirements *requirement; + vector *rewards = 0; + vector::iterator itr3; + AchievementRewards *reward; + PacketStruct *packet; + int16 i = 0; + int16 j = 0; + int16 k = 0; + int16 version = 1096; + + if (!(packet = configReader.getStruct("WS_CharacterAchievements", version))) { + return; + } + + packet->setArrayLengthByName("num_achievements" , achievements.size()); + for (itr = achievements.begin(); itr != achievements.end(); itr++) { + achievement = itr->second; + packet->setArrayDataByName("achievement_id", achievement->GetID(), i); + packet->setArrayDataByName("title", achievement->GetTitle(), i); + packet->setArrayDataByName("uncompleted_text", achievement->GetUncompletedText(), i); + packet->setArrayDataByName("completed_text", achievement->GetCompletedText(), i); + packet->setArrayDataByName("category", achievement->GetCategory(), i); + packet->setArrayDataByName("expansion", achievement->GetExpansion(), i); + packet->setArrayDataByName("icon", achievement->GetIcon(), i); + packet->setArrayDataByName("point_value", achievement->GetPointValue(), i); + packet->setArrayDataByName("qty_req", achievement->GetQtyReq(), i); + packet->setArrayDataByName("hide_achievement", achievement->GetHide(), i); + packet->setArrayDataByName("unknown3", achievement->GetUnknown3a(), i); + packet->setArrayDataByName("unknown3", achievement->GetUnknown3b(), i); + requirements = achievement->GetRequirements(); + rewards = achievement->GetRewards(); + j = 0; + k = 0; + packet->setSubArrayLengthByName("num_items", requirements->size(), i, j); + for (itr2 = requirements->begin(); itr2 != requirements->end(); itr2++) { + requirement = *itr2; + packet->setSubArrayDataByName("item_name", requirement->name.c_str(), i, j); + packet->setSubArrayDataByName("item_qty_req", requirement->qty_req, i, j); + j++; + } + packet->setSubArrayLengthByName("num_rewards", achievement->GetRewards()->size(), i, k); + for (itr3 = rewards->begin(); itr3 != rewards->end(); itr3++) { + reward = *itr3; + packet->setSubArrayDataByName("reward_item", reward->reward.c_str(), i, k); + k++; + } + i++; + } + + //packet->PrintPacket(); + EQ2Packet* data = packet->serialize(); + masterPacket = new EQ2Packet(OP_ClientCmdMsg, data->pBuffer, data->size); + safe_delete(packet); + safe_delete(data); + //DumpPacket(app); + + m_packetsCreated = true; +} diff --git a/source/WorldServer/Achievements/Achievements.h b/source/WorldServer/Achievements/Achievements.h new file mode 100644 index 0000000..7b1895c --- /dev/null +++ b/source/WorldServer/Achievements/Achievements.h @@ -0,0 +1,176 @@ +/* +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 . +*/ + +#ifndef ACHIEVEMENTS_H_ +#define ACHIEVEMENTS_H_ + +#include "../../common/types.h" +#include "../../common/Mutex.h" +#include "../Items/Items.h" +#include +#include + +using namespace std; + +struct AchievementRewards +{ + int32 achievement_id; + string reward; +}; + +struct AchievementRequirements +{ + int32 achievement_id; + string name; + int32 qty_req; +}; + +struct AchievementUpdateItems +{ + int32 achievement_id; + int32 item_update; +}; + +class Achievement { +public: + Achievement(); + Achievement(Achievement *in); + virtual ~Achievement(); + + void SetID(int32 id) {this->id = id;} + void SetTitle(const char *title) {strncpy(this->title, title, sizeof(this->title));} + void SetUncompletedText(const char *uncompleted_text) {strncpy(this->uncompleted_text, uncompleted_text, sizeof(this->uncompleted_text));} + void SetCompletedText(const char *completed_text) {strncpy(this->completed_text, completed_text, sizeof(this->completed_text));} + void SetCategory(const char *category) {strncpy(this->category, category, sizeof(this->category));} + void SetExpansion(const char *expansion) {strncpy(this->expansion, expansion, sizeof(this->expansion));} + void SetIcon(int16 icon) {this->icon = icon;} + void SetPointValue(int32 point_value) {this->point_value = point_value;} + void SetQtyReq(int32 qty_req) {this->qty_req = qty_req;} + void SetHide(bool hide) {this->hide = hide;} + void SetUnknown3a(int32 unknown3a) {this->unknown3a = unknown3a;} + void SetUnknown3b(int32 unknown3b) {this->unknown3b = unknown3b;} + + void AddAchievementRequirement(struct AchievementRequirements *requirements); + void AddAchievementReward(struct AchievementRewards *reward); + + int32 GetID() {return id;} + const char * GetTitle() {return title;} + const char * GetUncompletedText() {return uncompleted_text;} + const char * GetCompletedText() {return completed_text;} + const char * GetCategory() {return category;} + const char * GetExpansion() {return expansion;} + int16 GetIcon() {return icon;} + int32 GetPointValue() {return point_value;} + int32 GetQtyReq() {return qty_req;} + bool GetHide() {return hide;} + int32 GetUnknown3a() {return unknown3a;} + int32 GetUnknown3b() {return unknown3b;} + vector * GetRequirements() {return &requirements;} + vector * GetRewards() {return &rewards;} + +private: + int32 id; + char title[512]; + char uncompleted_text[512]; + char completed_text[512]; + char category[32]; + char expansion[32]; + int16 icon; + int32 point_value; + int32 qty_req; + bool hide; + int32 unknown3a; + int32 unknown3b; + vector requirements; + vector rewards; +}; + +class AchievementUpdate { +public: + AchievementUpdate(); + AchievementUpdate(AchievementUpdate *in); + virtual ~AchievementUpdate(); + + void SetID(int32 id) {this->id = id;} + void SetCompletedDate(int32 completed_date) {this->completed_date = completed_date;} + + void AddAchievementUpdateItems(struct AchievementUpdateItems *update_items); + + int32 GetID() {return id;} + int32 GetCompletedDate() {return completed_date;} + + vector * GetUpdateItems() {return &update_items;} + +private: + int32 id; + int32 completed_date; + vector update_items; +}; + +class MasterAchievementList { +public: + MasterAchievementList(); + virtual ~MasterAchievementList(); + + bool AddAchievement(Achievement *achievement); + Achievement * GetAchievement(int32 achievement_id); + void ClearAchievements(); + int32 Size(); + void CreateMasterAchievementListPacket(); + EQ2Packet * GetAchievementPacket() { return m_packetsCreated ? masterPacket : 0;} + EQ2Packet *masterPacket; +private: + Mutex mutex_achievements; + map achievements; + + bool m_packetsCreated; +}; + +class PlayerAchievementList { +public: + PlayerAchievementList(); + virtual ~PlayerAchievementList(); + + bool AddAchievement(Achievement *achievement); + Achievement * GetAchievement(int32 achievement_id); + void ClearAchievements(); + int32 Size(); + + map * GetAchievements() {return &achievements;} + +private: + map achievements; +}; + +class PlayerAchievementUpdateList { +public: + PlayerAchievementUpdateList(); + virtual ~PlayerAchievementUpdateList(); + + bool AddAchievementUpdate(AchievementUpdate *achievement_update); + void ClearAchievementUpdates(); + int32 Size(); + + map * GetAchievementUpdates() {return &achievement_updates;} + +private: + map achievement_updates; +}; +#endif \ No newline at end of file diff --git a/source/WorldServer/Achievements/AchievementsDB.cpp b/source/WorldServer/Achievements/AchievementsDB.cpp new file mode 100644 index 0000000..b8fd9bd --- /dev/null +++ b/source/WorldServer/Achievements/AchievementsDB.cpp @@ -0,0 +1,241 @@ +/* + 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 . +*/ + +#ifdef WIN32 + #include + #include +#endif +#include +#include +#include "../../common/Log.h" +#include "../WorldDatabase.h" +#include "Achievements.h" + +extern MasterAchievementList master_achievement_list; + +void WorldDatabase::LoadAchievements() +{ + Achievement *achievement; + Query query; + MYSQL_ROW row; + MYSQL_RES *res; + int32 aReqs_total = 0; + int32 aRewards_total = 0; + + res = query.RunQuery2(Q_SELECT, "SELECT `achievement_id`,`title`,`uncompleted_text`,`completed_text`,`category`,`expansion`,`icon`,`point_value`,`qty_req`,`hide_achievement`,`unknown3a`,`unknown3b`\n" + "FROM `achievements`"); + + if (res) + { + while ((row = mysql_fetch_row(res))) + { + achievement = new Achievement(); + achievement->SetID(atoul(row[0])); + achievement->SetTitle(row[1]); + achievement->SetUncompletedText(row[2]); + achievement->SetCompletedText(row[3]); + achievement->SetCategory(row[4]); + achievement->SetExpansion(row[5]); + achievement->SetIcon(atoi(row[6])); + achievement->SetPointValue(atoul(row[7])); + achievement->SetQtyReq(atoul(row[8])); + achievement->SetHide( atoi(row[9]) == 0 ? false : true ); + achievement->SetUnknown3a(atoul(row[10])); + achievement->SetUnknown3b(atoul(row[11])); + + LogWrite(ACHIEVEMENT__DEBUG, 5, "Achievements", "\tLoading Achievement: '%s' (%u)", achievement->GetTitle(), achievement->GetID()); + + if (!master_achievement_list.AddAchievement(achievement)) + { + LogWrite(ACHIEVEMENT__ERROR, 0, "Achievements", "Error adding achievement '%s' - duplicate ID: %u", achievement->GetTitle(), achievement->GetID()); + safe_delete(achievement); + continue; + } + + aReqs_total += LoadAchievementRequirements(achievement); + aRewards_total += LoadAchievementRewards(achievement); + } + } + LogWrite(ACHIEVEMENT__DEBUG, 0, "Achievements", "\tLoaded %u achievements", master_achievement_list.Size()); + LogWrite(ACHIEVEMENT__DEBUG, 0, "Achievements", "\tLoaded %u achievement requirements", aReqs_total); + LogWrite(ACHIEVEMENT__DEBUG, 0, "Achievements", "\tLoaded %u achievement rewards", aRewards_total); + +} + +int32 WorldDatabase::LoadAchievementRequirements(Achievement *achievement) +{ + AchievementRequirements *requirements; + Query query; + MYSQL_ROW row; + MYSQL_RES *res; + int16 total = 0; + + assert(achievement); + + res = query.RunQuery2(Q_SELECT, "SELECT `achievement_id`, `name`, `qty_req` FROM `achievements_requirements` WHERE `achievement_id` = %u", achievement->GetID()); + if (res) { + while ((row = mysql_fetch_row(res))) { + requirements = new AchievementRequirements(); + requirements->achievement_id = atoul(row[0]); + requirements->name = row[1]; + requirements->qty_req = atoul(row[2]); + achievement->AddAchievementRequirement(requirements); + LogWrite(ACHIEVEMENT__DEBUG, 5, "Achievements", "Loading Achievements Requirement '%s'", requirements->name.c_str()); + total++; + } + } + LogWrite(ACHIEVEMENT__DEBUG, 5, "Achievements", "Loaded %u requirements for achievement '%s' ID: %u", total, achievement->GetTitle(), achievement->GetID()); + return total; +} + +int32 WorldDatabase::LoadAchievementRewards(Achievement *achievement) +{ + AchievementRewards *rewards; + Query query; + MYSQL_ROW row; + MYSQL_RES *res; + int16 total = 0; + + assert(achievement); + + res = query.RunQuery2(Q_SELECT, "SELECT `achievement_id`, `reward` FROM `achievements_rewards` WHERE `achievement_id` = %u", achievement->GetID()); + if (res) { + while ((row = mysql_fetch_row(res))) { + rewards = new AchievementRewards(); + rewards->achievement_id = atoul(row[0]); + rewards->reward = row[1]; + achievement->AddAchievementReward(rewards); + LogWrite(ACHIEVEMENT__DEBUG, 5, "Achievements", "Loading Achievements Reward '%s'", rewards->reward.c_str()); + total++; + } + } + LogWrite(ACHIEVEMENT__DEBUG, 5, "Achievements", "Loaded %u rewards for achievement '%s' ID: %u", total, achievement->GetTitle(), achievement->GetID()); + return total; +} + +void WorldDatabase::LoadPlayerAchievements(Player *player) { + Achievement *achievement; + Query query; + MYSQL_ROW row; + MYSQL_RES *res; + int32 aReqs_total = 0; + int32 aRewards_total = 0; + int32 total = 0; + + assert(player); + + res = query.RunQuery2(Q_SELECT, "SELECT `achievement_id`,`title`,`uncompleted_text`,`completed_text`,`category`,`expansion`,`icon`,`point_value`,`qty_req`,`hide_achievement`,`unknown3a`,`unknown3b`\n" + "FROM `achievements`"); + + if (res) + { + while ((row = mysql_fetch_row(res))) + { + achievement = new Achievement(); + achievement->SetID(atoul(row[0])); + achievement->SetTitle(row[1]); + achievement->SetUncompletedText(row[2]); + achievement->SetCompletedText(row[3]); + achievement->SetCategory(row[4]); + achievement->SetExpansion(row[5]); + achievement->SetIcon(atoi(row[6])); + achievement->SetPointValue(atoul(row[7])); + achievement->SetQtyReq(atoul(row[8])); + achievement->SetHide( atoi(row[9]) == 0 ? false : true ); + achievement->SetUnknown3a(atoul(row[10])); + achievement->SetUnknown3b(atoul(row[11])); + + LogWrite(ACHIEVEMENT__DEBUG, 5, "Achievements", "\tLoading Achievement: '%s' (%u)", achievement->GetTitle(), achievement->GetID()); + + if (!player->GetAchievementList()->AddAchievement(achievement)) + { + LogWrite(ACHIEVEMENT__ERROR, 0, "Achievements", "Error adding achievement '%s' - duplicate ID: %u", achievement->GetTitle(), achievement->GetID()); + safe_delete(achievement); + continue; + } + total++; + aReqs_total += LoadAchievementRequirements(achievement); + aRewards_total += LoadAchievementRewards(achievement); + } + } + LogWrite(ACHIEVEMENT__DEBUG, 0, "Achievements", "\tLoaded %u achievements for '%s'", total, player->GetName()); + LogWrite(ACHIEVEMENT__DEBUG, 0, "Achievements", "\tLoaded %u achievement requirements for '%s'", aReqs_total, player->GetName()); + LogWrite(ACHIEVEMENT__DEBUG, 0, "Achievements", "\tLoaded %u achievement rewards for '%s'", aRewards_total, player->GetName()); +} + +int32 WorldDatabase::LoadPlayerAchievementsUpdates(Player *player) { + AchievementUpdate *update; + Query query; + MYSQL_ROW row; + MYSQL_RES *res; + int32 total = 0; + int32 items_total = 0; + + assert(player); + + res = query.RunQuery2(Q_SELECT, "SELECT `char_id`, `achievement_id`, `completed_date` FROM character_achievements WHERE char_id = %u ", player->GetCharacterID()); + if (res) { + while ((row = mysql_fetch_row(res))) { + update = new AchievementUpdate(); + update->SetID(atoul(row[1])); + update->SetCompletedDate(atoul(row[2])); + + LogWrite(ACHIEVEMENT__DEBUG, 5, "Achievements", "Loading Player Achievement Update for Achievement ID: %u ", update->GetID()); + + if (!player->GetAchievementUpdateList()->AddAchievementUpdate(update)) + { + LogWrite(ACHIEVEMENT__ERROR, 0, "Achievements", "Error adding achievement update %u - diplicate ID", update->GetID()); + safe_delete(update); + continue; + } + total++; + items_total += LoadPlayerAchievementsUpdateItems(update, player->GetCharacterID()); + } + } + + LogWrite(ACHIEVEMENT__DEBUG, 0, "Achievements", "Loaded %u player achievement updates", total); + + return total; +} + +int32 WorldDatabase::LoadPlayerAchievementsUpdateItems(AchievementUpdate *update, int32 player_id) { + AchievementUpdateItems *update_items; + Query query; + MYSQL_ROW row; + MYSQL_RES *res; + int32 total = 0; + + assert(update); + + res = query.RunQuery2(Q_SELECT, "SELECT `achievement_id`, `items` FROM character_achievements_items WHERE char_id = %u AND achievement_id = %u;", player_id, update->GetID()); + if (res) { + while ((row = mysql_fetch_row(res))) { + update_items = new AchievementUpdateItems(); + update_items->achievement_id = atoul(row[0]); + update_items->item_update = atoul(row[1]); + update->AddAchievementUpdateItems(update_items); + + LogWrite(ACHIEVEMENT__DEBUG, 5, "Achievements", "Loading Player Achievement Update Items for Achievement ID: %u ", update_items->achievement_id); + total++; + } + } + LogWrite(ACHIEVEMENT__DEBUG, 0, "Achievements", "Loaded %u player achievement update items", total); + return total; +} \ No newline at end of file diff --git a/source/WorldServer/AltAdvancement/AltAdvancement.cpp b/source/WorldServer/AltAdvancement/AltAdvancement.cpp new file mode 100644 index 0000000..32678d4 --- /dev/null +++ b/source/WorldServer/AltAdvancement/AltAdvancement.cpp @@ -0,0 +1,1707 @@ +/* +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 . +*/ + +#include "AltAdvancement.h" +#include "../../common/ConfigReader.h" +#include "../../common/Log.h" +#include "../Spells.h" +#include "../classes.h" +#include "../Rules/Rules.h" +#include +#include +#include +#include "../../common/DatabaseNew.h" +#include "../WorldDatabase.h" +extern ConfigReader configReader; +extern MasterSpellList master_spell_list; +extern Classes classes; +extern RuleManager rule_manager; +extern MasterAANodeList master_tree_nodes; + +MasterAAList::MasterAAList() +{ + MMasterAAList.SetName("MasterAAList::AAList"); +} + +MasterAAList::~MasterAAList() +{ + DestroyAltAdvancements(); +} + +void MasterAAList::AddAltAdvancement(AltAdvanceData* data) { + MMasterAAList.writelock(__FUNCTION__, __LINE__); + AAList.push_back(data); + MMasterAAList.releasewritelock(__FUNCTION__, __LINE__); +} + +int MasterAAList::Size() { + return AAList.size(); +} + +// Jabantiz: Probably a better way to do this but can't think of it right now +AltAdvanceData* MasterAAList::GetAltAdvancement(int32 spellID) { + vector::iterator itr; + AltAdvanceData* data = NULL; + + MMasterAAList.readlock(__FUNCTION__, __LINE__); + for (itr = AAList.begin(); itr != AAList.end(); itr++) { + if ((*itr)->spellID == spellID) { + data = (*itr); + break; + } + } + MMasterAAList.releasereadlock(__FUNCTION__, __LINE__); + + return data; +} + +void MasterAAList::DestroyAltAdvancements() { + MMasterAAList.writelock(__FUNCTION__, __LINE__); + vector::iterator itr; + for (itr = AAList.begin(); itr != AAList.end(); itr++) + safe_delete(*itr); + AAList.clear(); + MMasterAAList.releasewritelock(__FUNCTION__, __LINE__); +} + +MasterAANodeList::MasterAANodeList() { +} + +MasterAANodeList::~MasterAANodeList() { + DestroyTreeNodes(); +} + +void MasterAANodeList::AddTreeNode(TreeNodeData* data) { + TreeNodeList.push_back(data); +} + +void MasterAANodeList::DestroyTreeNodes() { + vector::iterator itr; + for (itr = TreeNodeList.begin(); itr != TreeNodeList.end(); itr++) + safe_delete(*itr); + TreeNodeList.clear(); +} + +int MasterAANodeList::Size() { + return TreeNodeList.size(); +} + +vector MasterAANodeList::GetTreeNodes() { + return TreeNodeList; +} + +EQ2Packet* MasterAAList::GetAAListPacket(Client* client) +{ + + /* + -- OP_DispatchESMsg -- + 5/24/2011 20:54:15 + 199.108.12.165 -> 192.168.0.197 + 0000: 00 38 3B 00 00 00 FF A3 02 FF FF FF FF 00 00 00 .8;............. + 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + 0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + 0030: 00 00 00 00 00 00 00 00 FF FF FF FF 00 00 00 00 ................ + 0040 00 . + */ + + uchar blah[] = {0xFF,0xE8,0x01, +0x00, //unknown +0x07,0x00,0x00,0x00, //unknown2 +0x07,0x00,0x57,0x61,0x72,0x72,0x69,0x6F,0x72, //class_title_tab +0x0C,0x00, //unknown3 +0x64,0x00,0x00,0x00, //max_class_aa +0xFD,0x74,0xB6,0x73, //class_id +0x00, //kos_req +0x00,0x00,0x00,0x00, //num_class_items + +0x0B,0x00,0x00,0x00, //unknown10 +0x11,0x00,0x00,0x00, //class_points_spent +0x00,0x00,0x3B,0x81,0x01,0x00, //unknown11 +0x00,0x00, //unknown12 +0x00,0x00, //unknown13 +0x00,0x00,0x00,0x00, //unknown14 +0x00,0x00, //unknown15 +0x00,0x00,0x00,0x00,0x00,0x00,0x00, //unknown16 +0x09,0x00,0x42,0x65,0x72,0x73,0x65,0x72,0x6B,0x65,0x72, //subclass_title_tab +0x0E,0x00, //unknown17 +0x64,0x00,0x00,0x00, //max_subclass_aa +0x5F,0xD6,0xAF,0x50, //subclass_id +0x00, //eof_req +0x00,0x00,0x00,0x00, //num_subclass_items + +0x0C,0x00,0x00,0x00, //unknown20 +0x08,0x00,0x00,0x00, //subclass_points_spent +0x00,0x00,0x3B,0x81,0x03,0x14, //unknown21 +0x00,0x00,0x00, //unknown22 +0x1D,0x00,0x3A,0x63,0x65,0x31,0x38,0x36,0x34,0x63,0x37,0x66,0x35,0x33,0x66,0x65,0x62,0x37,0x62,0x5F,0x31,0x3A,0x42,0x65,0x72,0x73,0x65,0x72,0x6B,0x65,0x72, //unknown23 +0x01,0x00,0x00,0x00, //unknown24 +0x1D,0x00,0x3A,0x63,0x65,0x31,0x38,0x36,0x34,0x63,0x37,0x35,0x66,0x39,0x34,0x61,0x32,0x64,0x37,0x5F,0x31,0x3A,0x45,0x78,0x70,0x65,0x72,0x74,0x69,0x73,0x65, //unknown25 +0x00,0x00,0x00,0x00,0x00,0x00, //unknown26 +0x07,0x00,0x53,0x68,0x61,0x64,0x6F,0x77,0x73, //shadows_tab_title +0x2C,0x00, //unknown27 +0x46,0x00,0x00,0x00, //max_shadows_aa +0x53,0x88,0x59,0x62, //shadows_id +0x00, //rok_req +0x00,0x00,0x00,0x00, //num_shadow_items + +0x0E,0x00,0x00,0x00, //unknown30 +0x00,0x00,0x00,0x00, //shadows_points_spent +0x00,0x00,0x3B,0x81,0x03,0x00, //unknown31 +0x00,0x00,0x00, //unknown32 +0x00,0x00, //uknown33 +0x00,0x00,0x00,0x00, //unknown34 +0x00,0x00, //unknown35 +0x00,0x00,0x00,0x00,0x00,0x00, //unknown36 +0x06,0x00,0x48,0x65,0x72,0x6F,0x69,0x63, //heroic_tab_title +0x48,0x00, //unknown37 +0x32,0x00,0x00,0x00, //max_heroic_aa +0xC0,0x6B,0xFC,0x3C, //heroic_id +0x01, //heroic_dov_req +0x00,0x00,0x00,0x00, //num_heroic_items + +0x10,0x00,0x00,0x00, //unknown40 +0x00,0x00,0x00,0x00, //heroic_points_spent +0x00,0x00,0x3B,0x81,0x03,0x00, //unknown41 +0x00,0x00,0x00, //unknown42 +0x00,0x00, //unknown43 +0x00,0x00,0x00,0x00, //unknown44 +0x00,0x00, //unknown45 +0x00,0x00,0x00,0x00,0x00,0x00, //unknown46 +0x0A,0x00,0x54,0x72,0x61,0x64,0x65,0x73,0x6B,0x69,0x6C,0x6C, //tradeskill_tab_title +0x49,0x00, //unknown47 +0x28,0x00,0x00,0x00, //max_tradeskill_aa +0x1E,0xDB,0x41,0x2F, //tradeskill_id +0x00, //exp_req +0x00,0x00,0x00,0x00, //num_tradeskill_items + +0x00,0x00,0x00,0x00, //unknown50 +0x00,0x00,0x00,0x00, //tradeskill_points_spent +0x00,0x00,0x3B,0x81,0x03,0x00, //unknown51 +0x00,0x00,0x00, //unknown52 +0x00,0x00, //unknown53 +0x00,0x00,0x00,0x00, //unknown54 +0x00,0x00, //unknown55 +0x03,0x00,0x00,0x00,0x00,0x00, //unknown56 +0x08,0x00,0x50,0x72,0x65,0x73,0x74,0x69,0x67,0x65, //prestige_tab_title +0x67,0x00, //unknown57 +0x19,0x00,0x00,0x00, //max_prestige_aa +0xC6,0xA8,0x83,0xBD, //prestige_id +0x01, //prestige_dov_req +0x00,0x00,0x00,0x00, //num_prestige_items + +0x10,0x00,0x00,0x00, //unknown60 +0x00,0x00,0x00,0x00, //prestige_points_spent +0x00,0x00,0x3B,0x81,0x03,0x06, //unknown61 +0x00,0x00,0x00, //unknown62 +0x1D,0x00,0x3A,0x34,0x39,0x33,0x64,0x65,0x62,0x62,0x33,0x65,0x36,0x37,0x38,0x62,0x39,0x37,0x37,0x5F,0x35,0x35,0x3A,0x50,0x72,0x65,0x73,0x74,0x69,0x67,0x65, //unknown63 +0x01,0x00,0x00,0x00, //unknown64 +0x27,0x00,0x3A,0x34,0x39,0x33,0x64,0x65,0x62,0x62,0x33,0x65,0x36,0x61,0x38,0x62,0x62,0x37,0x39,0x5F,0x31,0x32,0x3A,0x50,0x72,0x65,0x73,0x74,0x69,0x67,0x65,0x20,0x45,0x78,0x70,0x65,0x72,0x74,0x69,0x73,0x65, //unknown65 +0x02,0x00,0x00,0x00,0x00,0x00, //unknown66 +0x13,0x00,0x54,0x72,0x61,0x64,0x65,0x73,0x6B,0x69,0x6C,0x6C,0x20,0x50,0x72,0x65,0x73,0x74,0x69,0x67,0x65, //tradeskill_prestige_tab_title +0x79,0x00, //unknown67 +0x19,0x00,0x00,0x00, //max_tradeskill_prestige_aa +0x18,0x2C,0x0B,0x74, //tradeskill_prestige_id +0x01, //coe_req +0x00,0x00,0x00,0x00, //num_tradeskill_prestige_items + +0x12,0x00,0x00,0x00, //unknown70 +0x00,0x00,0x00,0x00, //tradeskill_prestige_points_spent +0x00,0x00,0x3B,0x81,0x03,0x00, //unknown71 +0x00,0x00,0x00, //unknown72 +0x00,0x00, //unknown73 +0x00,0x00,0x00,0x00, //unknown74 +0x00,0x00, //unknown75 +0x04,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00, //unknown76 +0x00,0x00,0x00,0x01,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //unknown77 +0x01, //num_templates +0x64, //template_unknown1 +0x03,0x00,0x4E,0x65,0x77, //template_name +0x00, //template_unknown2 +0x00,0x00}; //num_tabs + + return (new EQ2Packet(OP_AdventureList, blah, sizeof(blah))); +} + + + +struct AAEntry { + int8 template_id; + int8 tab_id; + int32 aa_id; + int16 order; + int8 treeid; +}; +void MasterAAList::DisplayAA(Client* client,int8 newtemplate,int8 changemode) { + map AAtree_id; + map >::iterator itr_tree2; + vector::iterator itr_tree3; + map > Nodes; + vector TreeNodeList = master_tree_nodes.GetTreeNodes(); + if (TreeNodeList.size() == 0) + return; + vector > > AAEntryList ; + Query query, query2; + MYSQL_ROW row; + int32 Pid = client->GetCharacterID(); + + AAEntryList.resize(8); // max number of templates + for (int i = 0; i < 8; i++) { + AAEntryList[i].resize(5); // max number of tabs + + } + + // load templates 1-3 Personal + + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `template_id`,`tab_id`,`aa_id`,`order`,treeid FROM character_aa_defaults where class = %i order by `order`", client->GetPlayer()->GetAdventureClass()); + + while (result && (row = mysql_fetch_row(result))) { + AAEntry newentry; + newentry.template_id = strtoul(row[0], NULL, 0); + newentry.tab_id = strtoul(row[1], NULL, 0); + newentry.aa_id = strtoul(row[2], NULL, 0); + newentry.order = strtoul(row[3], NULL, 0); + newentry.treeid = strtoul(row[4], NULL, 0); + AAEntryList[newentry.template_id][newentry.tab_id].push_back(newentry); + } + LogWrite(SPELL__INFO, 0, "AA", "Loaded %u AA Tree Nodes", AAEntryList.size()); + // load tmplates 4-6 Server + MYSQL_RES* result2 = query2.RunQuery2(Q_SELECT, "SELECT `template_id`,`tab_id`,`aa_id`,`order`,treeid FROM character_aa where char_id = %i order by `order`", client->GetCharacterID()); + + while (result2 && (row = mysql_fetch_row(result2))) { + AAEntry newentry; + newentry.template_id = strtoul(row[0], NULL, 0); + newentry.tab_id = strtoul(row[1], NULL, 0); + newentry.aa_id = strtoul(row[2], NULL, 0); + newentry.order = strtoul(row[3], NULL, 0); + newentry.treeid = strtoul(row[4], NULL, 0); + AAEntryList[newentry.template_id][newentry.tab_id].push_back(newentry); + } + LogWrite(SPELL__INFO, 0, "AA", "Loaded %u AA Tree Nodes", AAEntryList.size()); + + + + + for (int x = 0; x < TreeNodeList.size(); x++) + { + int8 class_id = client->GetPlayer()->GetInfoStruct()->get_class3(); + + if (TreeNodeList[x]->classID == class_id) + { + itr_tree2 = Nodes.lower_bound(TreeNodeList[x]->classID); + if (itr_tree2 != Nodes.end() && !(Nodes.key_comp()(TreeNodeList[x]->classID, itr_tree2->first))) + { + (itr_tree2->second).push_back(TreeNodeList[x]); + LogWrite(SPELL__TRACE, 0, "AA", "Added AA Tree node ID: %u", TreeNodeList[x]->treeID); + } + else + { + vector tmpVec; + tmpVec.push_back(TreeNodeList[x]); + Nodes.insert(make_pair(TreeNodeList[x]->classID, tmpVec)); + LogWrite(SPELL__TRACE, 0, "AA", "Added AA Tree node ID: %u", TreeNodeList[x]->treeID); + } + } + } + + map >::iterator itr2; + vector::iterator itr3; + + map > ClassTab; + map > SubclassTab; + map > ShadowsTab; + map > HeroicTab; + map > TradeskillTab; + map > PrestigeTab; + map > TradeskillPrestigeTab; + map > DragonTab; + map > DragonclassTab; + map > FarseasTab; + + MMasterAAList.readlock(__FUNCTION__, __LINE__); + // Get Tree Node ID's + map node_id; + map classid; + + + for (itr_tree2 = Nodes.begin(); itr_tree2 != Nodes.end(); itr_tree2++) { + int8 x = 0; + for (itr_tree3 = itr_tree2->second.begin(); itr_tree3 != itr_tree2->second.end(); itr_tree3++, x++ ) { + node_id[x] = (*itr_tree3)->treeID; + classid[(*itr_tree3)->treeID] = (*itr_tree3)->AAtreeID; + } + } + int rrr = 0; + for (int i =0; i < Size(); i++) { + if (AAList[i]->group == node_id[AA_CLASS]) { + itr2 = ClassTab.lower_bound(AAList[i]->group); + if (itr2 != ClassTab.end() && !(ClassTab.key_comp()(AAList[i]->group, itr2->first))) { + (itr2->second).push_back(AAList[i]); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + else { + vector tmpVec; + tmpVec.push_back(AAList[i]); + ClassTab.insert(make_pair(AAList[i]->group, tmpVec)); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + } + // Sort for Subclass Tab + if (AAList[i]->group == node_id[AA_SUBCLASS]) { + itr2 = SubclassTab.lower_bound(AAList[i]->group); + if (itr2 != SubclassTab.end() && !(SubclassTab.key_comp()(AAList[i]->group, itr2->first))) { + (itr2->second).push_back(AAList[i]); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + else { + vector tmpVec; + tmpVec.push_back(AAList[i]); + SubclassTab.insert(make_pair(AAList[i]->group, tmpVec)); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + } + // Sort for Shadows Tab + if (AAList[i]->group == node_id[AA_SHADOW]) { + itr2 = ShadowsTab.lower_bound(AAList[i]->group); + if (itr2 != ShadowsTab.end() && !(ShadowsTab.key_comp()(AAList[i]->group, itr2->first))) { + (itr2->second).push_back(AAList[i]); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + else { + vector tmpVec; + tmpVec.push_back(AAList[i]); + ShadowsTab.insert(make_pair(AAList[i]->group, tmpVec)); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + } + // Sort for Heroic Tab + if (AAList[i]->group == node_id[AA_HEROIC]) { + itr2 = HeroicTab.lower_bound(AAList[i]->group); + if (itr2 != HeroicTab.end() && !(HeroicTab.key_comp()(AAList[i]->group, itr2->first))) { + (itr2->second).push_back(AAList[i]); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + else { + vector tmpVec; + tmpVec.push_back(AAList[i]); + HeroicTab.insert(make_pair(AAList[i]->group, tmpVec)); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + } + // Sort for Tradeskill Tab + if (AAList[i]->group == node_id[AA_TRADESKILL]) { + itr2 = TradeskillTab.lower_bound(AAList[i]->group); + if (itr2 != TradeskillTab.end() && !(TradeskillTab.key_comp()(AAList[i]->group, itr2->first))) { + (itr2->second).push_back(AAList[i]); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + else { + vector tmpVec; + tmpVec.push_back(AAList[i]); + TradeskillTab.insert(make_pair(AAList[i]->group, tmpVec)); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + } + // Sort for Prestige Tab + if (AAList[i]->group == node_id[AA_PRESTIGE]) { + itr2 = PrestigeTab.lower_bound(AAList[i]->group); + if (itr2 != PrestigeTab.end() && !(PrestigeTab.key_comp()(AAList[i]->group, itr2->first))) { + (itr2->second).push_back(AAList[i]); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + else { + vector tmpVec; + tmpVec.push_back(AAList[i]); + PrestigeTab.insert(make_pair(AAList[i]->group, tmpVec)); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + } + // Sort for TradeskillPrestige Tab + if (AAList[i]->group == node_id[AA_TRADESKILL_PRESTIGE]) { + itr2 = TradeskillPrestigeTab.lower_bound(AAList[i]->group); + if (itr2 != TradeskillPrestigeTab.end() && !(TradeskillPrestigeTab.key_comp()(AAList[i]->group, itr2->first))) { + (itr2->second).push_back(AAList[i]); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + else { + vector tmpVec; + tmpVec.push_back(AAList[i]); + TradeskillPrestigeTab.insert(make_pair(AAList[i]->group, tmpVec)); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + } + // Sort for Dragon Tab + if (AAList[i]->group == node_id[AA_DRAGON]) { + itr2 = DragonTab.lower_bound(AAList[i]->group); + if (itr2 != DragonTab.end() && !(DragonTab.key_comp()(AAList[i]->group, itr2->first))) { + (itr2->second).push_back(AAList[i]); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + else { + vector tmpVec; + tmpVec.push_back(AAList[i]); + DragonTab.insert(make_pair(AAList[i]->group, tmpVec)); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + } + // Sort for Dragon Class Tab + if (AAList[i]->group == node_id[AA_DRAGONCLASS]) { + itr2 = DragonclassTab.lower_bound(AAList[i]->group); + if (itr2 != DragonclassTab.end() && !(DragonclassTab.key_comp()(AAList[i]->group, itr2->first))) { + (itr2->second).push_back(AAList[i]); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + else { + vector tmpVec; + tmpVec.push_back(AAList[i]); + DragonclassTab.insert(make_pair(AAList[i]->group, tmpVec)); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + } + // Sort for Farseas Tab + if (AAList[i]->group == node_id[AA_FARSEAS]) { + itr2 = FarseasTab.lower_bound(AAList[i]->group); + if (itr2 != FarseasTab.end() && !(FarseasTab.key_comp()(AAList[i]->group, itr2->first))) { + (itr2->second).push_back(AAList[i]); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + else { + vector tmpVec; + tmpVec.push_back(AAList[i]); + FarseasTab.insert(make_pair(AAList[i]->group, tmpVec)); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + } + } + MMasterAAList.releasereadlock(__FUNCTION__, __LINE__); + + int16 version = 0; + int8 class_num_items = 0; + int8 subclass_num_items = 0; + int8 shadows_num_items = 0; + int8 heroic_num_items = 0; + int8 tradeskill_num_items = 0; + int8 prestige_num_items = 0; + int8 tradeskillprestige_num_items = 0; + int8 dragon_num_items = 0; + int8 dragonclass_num_items = 0; + int8 farseas_num_items = 0; + int8 index = 0; + Spell* spell = 0; + int8 current_rank = 0; + int32 class_node_id = 0; + + if (client) + version = client->GetVersion(); + + + + PacketStruct* packet = configReader.getStruct("WS_AdventureList", version); + + + + if (version >= 58617) { + packet->setDataByName("num_aa_trees", 10);// number of AA tabs + } + else if (version >= 1193) { + packet->setDataByName("num_aa_trees", 7);// number of AA tabs + } + else if (version >= 1096) { + packet->setDataByName("num_aa_trees", 4);// number of AA tabs + } + // since we do not have a proper way of supporting 3 levels of nested arrays the first array is manual here and not looped + + //__________________________________________________________START OF CLASS TREE____________________________________________________________________________________ + // Get the value for num_class_items based on size of ClassTab vector + for (itr2 = ClassTab.begin(); itr2 != ClassTab.end(); itr2++) { + class_num_items += (itr2->second).size(); + } + LogWrite(SPELL__DEBUG, 0, "AA", "ClassTab Size...%i ", class_num_items); + index = 0; + packet->setDataByName("class_tab_title", classes.GetClassNameCase(classes.GetSecondaryBaseClass(client->GetPlayer()->GetAdventureClass())).c_str()); + packet->setDataByName("class_tree_node_id", node_id[AA_CLASS]); + packet->setDataByName("class_max_aa", rule_manager.GetGlobalRule(R_Player, MaxClassAA)->GetInt32()); + int32 class_id = TreeNodeList[node_id[AA_CLASS]]->AAtreeID; + class_id = classid[node_id[AA_CLASS]]; + packet->setDataByName("class_id", classid[node_id[AA_CLASS]]); + packet->setDataByName("class_kos_req", 0); + packet->setArrayLengthByName("class_num_items", class_num_items,0); + for (itr2 = ClassTab.begin(); itr2 != ClassTab.end(); itr2++) { + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++, index++) { + spell = master_spell_list.GetSpell((*itr3)->spellID, (*itr3)->tier); + current_rank = client->GetPlayer()->GetSpellTier((*itr3)->spellID); + if (index == 0) + class_node_id = (*itr3)->spellID; + //if (spell) { + packet->setArrayDataByName("class_parent_id", (*itr3)->rankPrereqID, index); + packet->setArrayDataByName("class_req_tier", (*itr3)->rankPrereq, index); + packet->setArrayDataByName("class_spell_id", (*itr3)->spellID, index); + int myrank = (current_rank == 0 ? 0 : (current_rank >= (*itr3)->maxRank) ? 2 : 1); + packet->setArrayDataByName("class_active", (current_rank == 0 ? 0 : (current_rank >= (*itr3)->maxRank) ? 2 : 1), index); //1= have tier >= 1; 3 = not available for selection; 0 available for selection + packet->setArrayDataByName("class_spell_name", (*itr3)->name.c_str(), index); + packet->setArrayDataByName("class_spell_description", (*itr3)->description.c_str(), index); + packet->setArrayDataByName("class_icon", (*itr3)->icon, index); + packet->setArrayDataByName("class_icon2",(*itr3)->icon2, index); + packet->setArrayDataByName("class_current_rank", current_rank, index); // TODO: need to get this value from the DB + packet->setArrayDataByName("class_max_rank", (*itr3)->maxRank , index); + packet->setArrayDataByName("class_rank_cost", (*itr3)->rankCost, index); + packet->setArrayDataByName("class_min_lev", (*itr3)->min_level, index); + packet->setSubArrayLengthByName("class_unknown5_numitems", 0,index, 0); + //packet->setSubArrayDataByName("class_unknown5", 308397057, index, 0); + //packet->setSubArrayDataByName("class_unknown5", 3215564363, index, 1); + //packet->setSubArrayDataByName("class_unknown5", 445192837, index, 2); + //packet->setSubArrayDataByName("class_unknown5", 3345493294, index, 3); + //packet->setSubArrayDataByName("class_unknown5", 696953971, index, 4); + packet->setArrayDataByName("class_unknown6", 4294967295, index); + packet->setArrayDataByName("class_unknown7", 1, index); + packet->setArrayDataByName("class_classification1", (*itr3)->class_name.c_str(), index); + packet->setArrayDataByName("class_points_req", (*itr3)->req_points, index); + packet->setArrayDataByName("class_unknown8", 0, index); + packet->setArrayDataByName("class_classification2", (*itr3)->subclass_name.c_str(), index); + packet->setArrayDataByName("class_col", (*itr3)->col, index); + packet->setArrayDataByName("class_row", (*itr3)->row, index); + packet->setArrayDataByName("class_line_title", (*itr3)->line_title.c_str(), index); + packet->setArrayDataByName("class_unknown9", ((*itr3)->title_level > 0 ? 258 : 0 ), index); + packet->setArrayDataByName("class_points_to_unlock", (*itr3)->req_tree_points, index); + packet->setArrayDataByName("class_unknown9b", current_rank, index);// aom + //} + //else + //LogWrite(SPELL__ERROR, 0, "AltAdvancement","Could not find Spell ID %u and Tier %i in Master Spell List", (*itr3)->spellID, (*itr3)->tier); + } + } + + packet->setDataByName("class_points_spent", 11); + if (version >= 58617) { + packet->setDataByName("class_unknown10", 11); + + packet->setDataByName("class_unknown11a", 0); + packet->setDataByName("class_unknown11b", 0); + packet->setDataByName("class_unknown11c", 1); + } + else if (version >= 1193) { + packet->setDataByName("class_unknown10", 11); + packet->setDataByName("class_unknown11", 0, 0); + packet->setDataByName("class_unknown11", 1, 1); + packet->setDataByName("class_unknown11", 1, 2); + } + else if (version >= 1096) { + packet->setDataByName("class_unknown10", 11); + packet->setDataByName("class_unknown11", 0, 0); + packet->setDataByName("class_unknown11", 0, 1); + packet->setDataByName("class_unknown11", 1, 2); + packet->setDataByName("class_unknown11", 0, 3); + packet->setDataByName("class_unknown11", 1, 4); + } + else { // this will change if there is ever a lower client supported + packet->setDataByName("class_unknown10", 11); + packet->setDataByName("class_unknown11", 0, 0); + packet->setDataByName("class_unknown11", 0, 1); + packet->setDataByName("class_unknown11", 1, 2); + packet->setDataByName("class_unknown11", 0, 3); + packet->setDataByName("class_unknown11", 1, 4); + } + + + + + + //__________________________________________________________START OF SUBCLASS TREE____________________________________________________________________________________ + // Get the value for num_class_items based on size of SubclassTab vector + for (itr2 = SubclassTab.begin(); itr2 != SubclassTab.end(); itr2++) { + subclass_num_items += (itr2->second).size(); + } + LogWrite(SPELL__DEBUG, 0, "AA", "SubclassTab Size...%i ", subclass_num_items); + index = 0; + packet->setDataByName("subclass_tab_title", classes.GetClassNameCase(client->GetPlayer()->GetAdventureClass()).c_str()); + packet->setDataByName("subclass_tree_node_id", node_id[AA_SUBCLASS]); + packet->setDataByName("subclass_max_aa", rule_manager.GetGlobalRule(R_Player, MaxSubclassAA)->GetInt32()); + int32 unknown3 = TreeNodeList[node_id[AA_SUBCLASS]]->AAtreeID; + + packet->setDataByName("subclass_id", classid[node_id[AA_SUBCLASS]]); + packet->setDataByName("subclass_eof_req", 0); + packet->setArrayLengthByName("subclass_num_items", subclass_num_items, 0); + for (itr2 = SubclassTab.begin(); itr2 != SubclassTab.end(); itr2++) { + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++, index++) { + //spell = master_spell_list.GetSpell((*itr3)->spellID, (*itr3)->tier); + //current_rank = client->GetPlayer()->GetSpellTier((*itr3)->spellID); + if (index == 0) + class_node_id = (*itr3)->spellID; + //if (spell) { + packet->setArrayDataByName("subclass_parent_id", (*itr3)->rankPrereqID, index); + packet->setArrayDataByName("subclass_req_tier", (*itr3)->rankPrereq, index); + packet->setArrayDataByName("subclass_spell_id", (*itr3)->spellID, index); + packet->setArrayDataByName("subclass_active", (current_rank == 0 ? 0 : (current_rank >= (*itr3)->maxRank) ? 2 : 1), index); + packet->setArrayDataByName("subclass_spell_name", (*itr3)->name.c_str(), index); + packet->setArrayDataByName("subclass_spell_description", (*itr3)->description.c_str(), index); + packet->setArrayDataByName("subclass_icon", (*itr3)->icon, index); + packet->setArrayDataByName("subclass_icon2", (*itr3)->icon2, index); + packet->setArrayDataByName("subclass_current_rank", current_rank, index); // TODO: need to get this value from the DB + packet->setArrayDataByName("subclass_max_rank", (*itr3)->maxRank, index); + packet->setArrayDataByName("subclass_rank_cost", (*itr3)->rankCost, index); + packet->setArrayDataByName("subclass_min_lev", (*itr3)->min_level, index); + packet->setSubArrayLengthByName("subclass_unknown5_numitems", 0,index,0); + //packet->setSubArrayDataByName("subclass_unknown5", 308397057, index, 0); + //packet->setSubArrayDataByName("subclass_unknown5", 3215564363, index, 1); + //packet->setSubArrayDataByName("subclass_unknown5", 445192837, index, 2); + //packet->setSubArrayDataByName("subclass_unknown5", 3345493294, index, 3); + //packet->setSubArrayDataByName("subclass_unknown5", 696953971, index, 4); + packet->setArrayDataByName("subclass_unknown6", 4294967295, index); + packet->setArrayDataByName("subclass_unknown7", 1, index); + packet->setArrayDataByName("subclass_classification1", (*itr3)->class_name.c_str(), index); + packet->setArrayDataByName("subclass_points_req", (*itr3)->req_points, index); + packet->setArrayDataByName("subclass_unknown8", 0, index); + packet->setArrayDataByName("subclass_classification2", (*itr3)->subclass_name.c_str(), index); + packet->setArrayDataByName("subclass_col", (*itr3)->col, index); + packet->setArrayDataByName("subclass_row", (*itr3)->row, index); + packet->setArrayDataByName("subclass_line_title", (*itr3)->line_title.c_str(), index); + packet->setArrayDataByName("subclass_unknown9", ((*itr3)->title_level > 0 ? 258 : 0), index); + packet->setArrayDataByName("subclass_points_to_unlock", (*itr3)->req_tree_points, index); + packet->setArrayDataByName("subclass_unknown9b", 0, index); //added with 68617 AOM something to do with points + //} + //else + //LogWrite(SPELL__ERROR, 0, "AltAdvancement","Could not find Spell ID %u and Tier %i in Master Spell List", (*itr3)->spellID, (*itr3)->tier); + } + } + + packet->setDataByName("subclass_points_spent", 12); // to change the 34 to a track value + + + if (version >= 58617) { + packet->setDataByName("subclass_unknown10", 12); + + packet->setDataByName("subclass_unknown11a", 0); + packet->setDataByName("subclass_unknown11b", 50386); + packet->setDataByName("subclass_unknown11c", 5123); + + packet->setDataByName("subclass_unknown12", 0, 0); + packet->setDataByName("subclass_unknown12", 0, 1); + packet->setDataByName("subclass_unknown12", 0, 2); + packet->setDataByName("subclass_unknown13",":493debb3e678b977_91:test_unknown13");// this is based on class + packet->setDataByName("subclass_unknown14", 1); + packet->setDataByName("subclass_unknown15", ":ce1864c75f94a2d7_14:Expertise"); + packet->setDataByName("subclass_unknown16", 0, 0); + packet->setDataByName("subclass_unknown16", 0, 1); + packet->setDataByName("subclass_unknown16", 0, 2); + packet->setDataByName("subclass_unknown16", 0, 3); + packet->setDataByName("subclass_unknown16", 0, 4); + packet->setDataByName("subclass_unknown16", 0, 5); + } + else if (version >= 1193) { + packet->setDataByName("subclass_unknown10", 12); + packet->setDataByName("subclass_unknown11", 0, 0); + packet->setDataByName("subclass_unknown11", 1, 1); + packet->setDataByName("subclass_unknown11", 5123, 2); + packet->setDataByName("subclass_unknown12", 0, 0); + packet->setDataByName("subclass_unknown12", 0, 1); + packet->setDataByName("subclass_unknown12", 0, 2); + packet->setDataByName("subclass_unknown13", ":493debb3e678b977_91:test_unknown13");// this is based on class + packet->setDataByName("subclass_unknown14", 1); + packet->setDataByName("subclass_unknown15", ":ce1864c75f94a2d7_14:Expertise"); + packet->setDataByName("subclass_unknown16", 111, 0); + packet->setDataByName("subclass_unknown16", 108, 1); + packet->setDataByName("subclass_unknown16", 108, 2); + packet->setDataByName("subclass_unknown16", 1101, 3); + packet->setDataByName("subclass_unknown16", 121, 4); + packet->setDataByName("subclass_unknown16", 129, 5); + } + else if (version >= 1096) { + packet->setDataByName("subclass_unknown10", 12); + packet->setDataByName("subclass_unknown11", 0, 0); + packet->setDataByName("subclass_unknown11", 0, 1); + packet->setDataByName("subclass_unknown11", 1, 2); + packet->setDataByName("subclass_unknown11", 0, 3); + packet->setDataByName("subclass_unknown11", 3, 4); + } + else { // this will change if there is ever a lower client supported + packet->setDataByName("subclass_unknown10", 12); + packet->setDataByName("subclass_unknown11", 0, 0); + packet->setDataByName("subclass_unknown11", 0, 1); + packet->setDataByName("subclass_unknown11", 1, 2); + packet->setDataByName("subclass_unknown11", 0, 3); + packet->setDataByName("subclass_unknown11", 3, 4); + } + //__________________________________________________________START OF SHADOWS TREE____________________________________________________________________________________ + + for (itr2 = ShadowsTab.begin(); itr2 != ShadowsTab.end(); itr2++) { + shadows_num_items += (itr2->second).size(); + } + LogWrite(SPELL__DEBUG, 0, "AA", "ShadowsTab Size...%i ", shadows_num_items); + index = 0; + packet->setDataByName("shadows_tab_title", "Shadows"); + packet->setDataByName("shadows_tree_node_id", node_id[AA_SHADOW]); + packet->setDataByName("shadows_max_aa", rule_manager.GetGlobalRule(R_Player, MaxShadowsAA)->GetInt32()); + packet->setDataByName("shadows_id", classid[node_id[AA_SHADOW]]); + packet->setDataByName("shadows_eof_req", 0); + packet->setArrayLengthByName("shadows_num_items", shadows_num_items, 0); + //packet->PrintPacket(); + for (itr2 = ShadowsTab.begin(); itr2 != ShadowsTab.end(); itr2++) { + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++, index++) { + spell = master_spell_list.GetSpell((*itr3)->spellID, (*itr3)->tier); + current_rank = client->GetPlayer()->GetSpellTier((*itr3)->spellID); + if (index == 0) + class_node_id = (*itr3)->spellID; + //if (spell) { + packet->setArrayDataByName("shadows_parent_id", (*itr3)->rankPrereqID, index); + packet->setArrayDataByName("shadows_req_tier", (*itr3)->rankPrereq, index); + packet->setArrayDataByName("shadows_spell_id", (*itr3)->spellID, index); + packet->setArrayDataByName("shadows_active", (current_rank == 0 ? 0 : (current_rank >= (*itr3)->maxRank) ? 2 : 1), index); + packet->setArrayDataByName("shadows_spell_name", (*itr3)->name.c_str(), index); + packet->setArrayDataByName("shadows_spell_description", (*itr3)->description.c_str(), index); + packet->setArrayDataByName("shadows_icon", (*itr3)->icon, index); + packet->setArrayDataByName("shadows_icon2", (*itr3)->icon2, index); + packet->setArrayDataByName("shadows_current_rank", current_rank, index); // TODO: need to get this value from the DB + packet->setArrayDataByName("shadows_max_rank", (*itr3)->maxRank, index); + packet->setArrayDataByName("shadows_rank_cost", (*itr3)->rankCost, index); + packet->setArrayDataByName("shadows_min_lev", (*itr3)->min_level, index); + packet->setSubArrayLengthByName("shadows_unknown5_num_items", 0, index, 0); + //packet->setSubArrayDataByName("shadows_unknown5", 308397057, index, 0); + //packet->setSubArrayDataByName("shadows_unknown5", 3215564363, index, 1); + //packet->setSubArrayDataByName("shadows_unknown5", 445192837, index, 2); + //packet->setSubArrayDataByName("shadows_unknown5", 3345493294, index, 3); + //packet->setSubArrayDataByName("shadows_unknown5", 696953971, index, 4); + packet->setArrayDataByName("shadows_unknown6", 4294967295, index); + packet->setArrayDataByName("shadows_unknown7", 1, index); + packet->setArrayDataByName("shadows_classification1", (*itr3)->class_name.c_str(), index); + packet->setArrayDataByName("shadows_points_req", (*itr3)->req_points, index); + packet->setArrayDataByName("shadows_unknown8", 0, index); + packet->setArrayDataByName("shadows_classification2", (*itr3)->subclass_name.c_str(), index); + packet->setArrayDataByName("shadows_col", (*itr3)->col, index); + packet->setArrayDataByName("shadows_row", (*itr3)->row, index); + packet->setArrayDataByName("shadows_line_title", (*itr3)->line_title.c_str(), index); + packet->setArrayDataByName("shadows_unknown9", ((*itr3)->title_level > 0 ? 258 : 0), index); + packet->setArrayDataByName("shadows_points_to_unlock", (*itr3)->req_tree_points, index); + packet->setArrayDataByName("shadows_unknown9b", 0, index); + //} + //else + //LogWrite(SPELL__ERROR, 0, "AltAdvancement","Could not find Spell ID %u and Tier %i in Master Spell List", (*itr3)->spellID, (*itr3)->tier); + } + } + + packet->setDataByName("shadows_points_spent", 14); + + + if (version >= 58617) { + packet->setDataByName("shadows_unknown10", 14); + + packet->setDataByName("shadows_unknown11a", 0); + packet->setDataByName("shadows_unknown11b", 50386); + packet->setDataByName("shadows_unknown11c", 3); + + packet->setDataByName("shadows_unknown12", 0, 0); + packet->setDataByName("shadows_unknown12", 0, 1); + packet->setDataByName("shadows_unknown12", 0, 2); + } + else if (version >= 1193) { + packet->setDataByName("shadows_unknown10", 14); + packet->setDataByName("shadows_unknown11", 0, 0); + packet->setDataByName("shadows_unknown11", 1, 1); + packet->setDataByName("shadows_unknown11", 3, 2); + packet->setDataByName("shadows_unknown12", 0, 0); + packet->setDataByName("shadows_unknown12", 0, 1); + packet->setDataByName("shadows_unknown12", 0, 2); + } + else if (version >= 1096) { + packet->setDataByName("shadows_unknown11", 0, 0); + packet->setDataByName("shadows_unknown11", 0, 1); + packet->setDataByName("shadows_unknown11", 1, 2); + packet->setDataByName("shadows_unknown11", 0, 3); + packet->setDataByName("shadows_unknown11", 3, 4); + } + else { // this will change if there is ever a lower client supported + packet->setDataByName("shadows_unknown11", 0, 0); + packet->setDataByName("shadows_unknown11", 0, 1); + packet->setDataByName("shadows_unknown11", 1, 2); + packet->setDataByName("shadows_unknown11", 0, 3); + packet->setDataByName("shadows_unknown11", 3, 4); + packet->setDataByName("shadows_unknown12", 103, 0); + packet->setDataByName("shadows_unknown12", 101, 1); + packet->setDataByName("shadows_unknown12", 114, 2); + packet->setDataByName("shadows_unknown14", 1835365408); + packet->setDataByName("shadows_unknown16", 114, 0); + packet->setDataByName("shadows_unknown16", 97, 1); + packet->setDataByName("shadows_unknown16", 114, 2); + packet->setDataByName("shadows_unknown16", 121, 3); + packet->setDataByName("shadows_unknown16", 32, 4); + packet->setDataByName("shadows_unknown16", 98, 5); + } + //__________________________________________________________START OF HEROIC TREE____________________________________________________________________________________ + for (itr2 = HeroicTab.begin(); itr2 != HeroicTab.end(); itr2++) { + heroic_num_items += (itr2->second).size(); + } + LogWrite(SPELL__DEBUG, 0, "AA", "HeroicTab Size...%i ", heroic_num_items); + index = 0; + packet->setDataByName("heroic_tab_title", "Heroic"); + packet->setDataByName("heroic_tree_node_id", node_id[AA_HEROIC]); + packet->setDataByName("heroic_max_aa", rule_manager.GetGlobalRule(R_Player, MaxHeroicAA)->GetInt32()); + packet->setDataByName("heroic_id", classid[node_id[AA_HEROIC]]); + packet->setDataByName("heroic_eof_req", 0); + packet->setArrayLengthByName("heroic_num_items", heroic_num_items, 0); + for (itr2 = HeroicTab.begin(); itr2 != HeroicTab.end(); itr2++) { + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++, index++) { + //spell = master_spell_list.GetSpell((*itr3)->spellID, (*itr3)->tier); + //current_rank = client->GetPlayer()->GetSpellTier((*itr3)->spellID); + if (index == 0) + class_node_id = (*itr3)->spellID; + //if (spell) { + packet->setArrayDataByName("heroic_parent_id", (*itr3)->rankPrereqID, index); + packet->setArrayDataByName("heroic_req_tier", (*itr3)->rankPrereq, index); + packet->setArrayDataByName("heroic_spell_id", (*itr3)->spellID, index); + packet->setArrayDataByName("heroic_active", (current_rank == 0 ? 0 : (current_rank >= (*itr3)->maxRank) ? 2 : 1), index); + packet->setArrayDataByName("heroic_spell_name", (*itr3)->name.c_str(), index); + packet->setArrayDataByName("heroic_spell_description", (*itr3)->description.c_str(), index); + packet->setArrayDataByName("heroic_icon", (*itr3)->icon, index); + packet->setArrayDataByName("heroic_icon2", (*itr3)->icon2, index); + packet->setArrayDataByName("heroic_current_rank", current_rank, index); // TODO: need to get this value from the DB + packet->setArrayDataByName("heroic_max_rank", (*itr3)->maxRank, index); + packet->setArrayDataByName("heroic_rank_cost", (*itr3)->rankCost, index); + packet->setArrayDataByName("heroic_min_lev", (*itr3)->min_level, index); + packet->setSubArrayLengthByName("heroic_unknown5_num_items", 0, index, 0); + //packet->setSubArrayDataByName("heroic_unknown5", 308397057, index, 0); + //packet->setSubArrayDataByName("heroic_unknown5", 3215564363, index, 1); + //packet->setSubArrayDataByName("heroic_unknown5", 445192837, index, 2); + //packet->setSubArrayDataByName("heroic_unknown5", 3345493294, index, 3); + //packet->setSubArrayDataByName("heroic_unknown5", 696953971, index, 4); + packet->setArrayDataByName("heroic_unknown6", 4294967295, index); + packet->setArrayDataByName("heroic_unknown7", 1, index); + packet->setArrayDataByName("heroic_classification1", (*itr3)->class_name.c_str(), index); + packet->setArrayDataByName("heroic_points_req", (*itr3)->req_points, index); + packet->setArrayDataByName("heroic_unknown8", 0, index); + packet->setArrayDataByName("heroic_classification2", (*itr3)->subclass_name.c_str(), index); + packet->setArrayDataByName("heroic_col", (*itr3)->col, index); + packet->setArrayDataByName("heroic_row", (*itr3)->row, index); + packet->setArrayDataByName("heroic_line_title", (*itr3)->line_title.c_str(), index); + packet->setArrayDataByName("heroic_unknown9", ((*itr3)->title_level > 0 ? 258 : 0), index); + packet->setArrayDataByName("heroic_points_to_unlock", (*itr3)->req_tree_points, index); + packet->setArrayDataByName("heroic_unknown9b", 0, index); + //} + //else + //LogWrite(SPELL__ERROR, 0, "AltAdvancement","Could not find Spell ID %u and Tier %i in Master Spell List", (*itr3)->spellID, (*itr3)->tier); + } + } + + packet->setDataByName("heroic_points_spent", 16); + + if (version >= 58617) { + packet->setDataByName("heroic_unknown10", 16); + + packet->setDataByName("heroic_unknown11a", 0); + packet->setDataByName("heroic_unknown11b", 50386); + packet->setDataByName("heroic_unknown11c", 3); + + packet->setDataByName("heroic_unknown12", 0, 0); + packet->setDataByName("heroic_unknown12", 0, 1); + packet->setDataByName("heroic_unknown12", 0, 2); + + packet->setDataByName("heroic_unknown14", 0); + packet->setDataByName("heroic_unknown16", 39, 0); + packet->setDataByName("heroic_unknown16", 115, 1); + packet->setDataByName("heroic_unknown16", 32, 2); + packet->setDataByName("heroic_unknown16", 115, 3); + packet->setDataByName("heroic_unknown16", 101, 4); + packet->setDataByName("heroic_unknown16", 108, 5); + } + else if (version >= 1193) { + packet->setDataByName("heroic_unknown10", 16); + packet->setDataByName("heroic_unknown11", 0, 0); + packet->setDataByName("heroic_unknown11", 1, 1); + packet->setDataByName("heroic_unknown11", 3, 2); + packet->setDataByName("heroic_unknown12", 0, 0); + packet->setDataByName("heroic_unknown12", 0, 1); + packet->setDataByName("heroic_unknown12", 0, 2); + packet->setDataByName("heroic_unknown14", 0); + packet->setDataByName("heroic_unknown16", 0, 0); + packet->setDataByName("heroic_unknown16", 0, 1); + packet->setDataByName("heroic_unknown16", 0, 2); + packet->setDataByName("heroic_unknown16", 0, 3); + packet->setDataByName("heroic_unknown16", 0, 4); + packet->setDataByName("heroic_unknown16", 0, 5); + } + else if (version >= 1096) { + packet->setDataByName("heroic_unknown11", 0, 0); + packet->setDataByName("heroic_unknown11", 0, 1); + packet->setDataByName("heroic_unknown11", 1, 2); + packet->setDataByName("heroic_unknown11", 0, 3); + packet->setDataByName("heroic_unknown11", 3, 4); + } + else { // this will change if there is ever a lower client supported + packet->setDataByName("heroic_unknown11", 0, 0); + packet->setDataByName("heroic_unknown11", 0, 1); + packet->setDataByName("heroic_unknown11", 1, 2); + packet->setDataByName("heroic_unknown11", 0, 3); + packet->setDataByName("heroic_unknown11", 3, 4); + } + if (version >= 1193) { + + //__________________________________________________________START OF TRADESKILL TREE____________________________________________________________________________________ + for (itr2 = TradeskillTab.begin(); itr2 != TradeskillTab.end(); itr2++) { + tradeskill_num_items += (itr2->second).size(); + } + LogWrite(SPELL__DEBUG, 0, "AA", "TradeskillTab Size...%i ", tradeskill_num_items); + index = 0; + packet->setDataByName("tradeskill_tab_title", "Tradeskill"); + packet->setDataByName("tradeskill_tree_node_id", node_id[AA_TRADESKILL]); + packet->setDataByName("tradeskill_max_aa", rule_manager.GetGlobalRule(R_Player, MaxTradeskillAA)->GetInt32()); + packet->setDataByName("tradeskill_id", classid[node_id[AA_TRADESKILL]]); + packet->setDataByName("tradeskill_eof_req", 0); + packet->setArrayLengthByName("tradeskill_num_items", tradeskill_num_items, 0); + for (itr2 = TradeskillTab.begin(); itr2 != TradeskillTab.end(); itr2++) { + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++, index++) { + //spell = master_spell_list.GetSpell((*itr3)->spellID, (*itr3)->tier); + //current_rank = client->GetPlayer()->GetSpellTier((*itr3)->spellID); + if (index == 0) + class_node_id = (*itr3)->spellID; + //if (spell) { + packet->setArrayDataByName("tradeskill_parent_id", (*itr3)->rankPrereqID, index); + packet->setArrayDataByName("tradeskill_req_tier", (*itr3)->rankPrereq, index); + packet->setArrayDataByName("tradeskill_spell_id", (*itr3)->spellID, index); + packet->setArrayDataByName("tradeskill_active", (current_rank == 0 ? 0 : (current_rank >= (*itr3)->maxRank) ? 2 : 1), index); + packet->setArrayDataByName("tradeskill_spell_name", (*itr3)->name.c_str(), index); + packet->setArrayDataByName("tradeskill_spell_description", (*itr3)->description.c_str(), index); + packet->setArrayDataByName("tradeskill_icon", (*itr3)->icon, index); + packet->setArrayDataByName("tradeskill_icon2", (*itr3)->icon2, index); + packet->setArrayDataByName("tradeskill_current_rank", current_rank, index); // TODO: need to get this value from the DB + packet->setArrayDataByName("tradeskill_max_rank", (*itr3)->maxRank, index); + packet->setArrayDataByName("tradeskill_rank_cost", (*itr3)->rankCost, index); + packet->setArrayDataByName("tradeskill_min_lev", (*itr3)->min_level, index); + packet->setSubArrayLengthByName("tradeskill_unknown5_num_items", 0, index, 0); + //packet->setSubArrayDataByName("tradeskill_unknown5", 308397057, index, 0); + //packet->setSubArrayDataByName("tradeskill_unknown5", 3215564363, index, 1); + //packet->setSubArrayDataByName("tradeskill_unknown5", 445192837, index, 2); + //packet->setSubArrayDataByName("tradeskill_unknown5", 3345493294, index, 3); + //packet->setSubArrayDataByName("tradeskill_unknown5", 696953971, index, 4); + packet->setArrayDataByName("tradeskill_unknown6", 4294967295, index); + packet->setArrayDataByName("tradeskill_unknown7", 0, index); + packet->setArrayDataByName("tradeskill_classification1", (*itr3)->class_name.c_str(), index); + packet->setArrayDataByName("tradeskill_points_req", (*itr3)->req_points, index); + packet->setArrayDataByName("tradeskill_unknown8", 0, index); + packet->setArrayDataByName("tradeskill_classification2", (*itr3)->subclass_name.c_str(), index); + packet->setArrayDataByName("tradeskill_col", (*itr3)->col, index); + packet->setArrayDataByName("tradeskill_row", (*itr3)->row, index); + packet->setArrayDataByName("tradeskill_line_title", (*itr3)->line_title.c_str(), index); + packet->setArrayDataByName("tradeskill_unknown9", ((*itr3)->title_level > 0 ? 258 : 0), index); + packet->setArrayDataByName("tradeskill_points_to_unlock", (*itr3)->req_tree_points, index); + packet->setArrayDataByName("tradeskill_unknown9b", 0, index); + //} + //else + //LogWrite(SPELL__ERROR, 0, "AltAdvancement","Could not find Spell ID %u and Tier %i in Master Spell List", (*itr3)->spellID, (*itr3)->tier); + } + } + packet->setDataByName("tradeskill_unknown10", 0); + packet->setDataByName("tradeskill_points_spent", 22); + if (version >= 58617) { + packet->setDataByName("tradeskill_unknown10", 11); + + packet->setDataByName("tradeskill_unknown11a", 0); + packet->setDataByName("tradeskill_unknown11b", 50386); + packet->setDataByName("tradeskill_unknown11c", 3); + + packet->setDataByName("tradeskill_unknown12", 0, 0); + packet->setDataByName("tradeskill_unknown12", 0, 1); + packet->setDataByName("tradeskill_unknown12", 0, 2); + + packet->setDataByName("tradeskill_unknown14", 0); + packet->setDataByName("tradeskill_unknown16", 3, 0); + packet->setDataByName("tradeskill_unknown16", 0, 1); + packet->setDataByName("tradeskill_unknown16", 0, 2); + packet->setDataByName("tradeskill_unknown16", 0, 3); + packet->setDataByName("tradeskill_unknown16", 0, 4); + packet->setDataByName("tradeskill_unknown16", 0, 5); + } + else if (version >= 1193) { + packet->setDataByName("tradeskill_unknown10", 0); + packet->setDataByName("tradeskill_unknown11", 0, 0); + packet->setDataByName("tradeskill_unknown11", 1, 1); + packet->setDataByName("tradeskill_unknown11", 3, 2); + packet->setDataByName("tradeskill_unknown12", 0, 0); + packet->setDataByName("tradeskill_unknown12", 0, 1); + packet->setDataByName("tradeskill_unknown12", 0, 2); + packet->setDataByName("tradeskill_unknown14", 0); + packet->setDataByName("tradeskill_unknown16", 3, 0); + packet->setDataByName("tradeskill_unknown16", 0, 1); + packet->setDataByName("tradeskill_unknown16", 0, 2); + packet->setDataByName("tradeskill_unknown16", 0, 3); + packet->setDataByName("tradeskill_unknown16", 0, 4); + packet->setDataByName("tradeskill_unknown16", 0, 5); + } + + //__________________________________________________________START OF PRESTIGE TREE____________________________________________________________________________________ + for (itr2 = PrestigeTab.begin(); itr2 != PrestigeTab.end(); itr2++) { + prestige_num_items += (itr2->second).size(); + } + LogWrite(SPELL__DEBUG, 0, "AA", "PrestigeTab Size...%i ", prestige_num_items); + index = 0; + packet->setDataByName("prestige_tab_title", "Prestige"); + packet->setDataByName("prestige_tree_node_id", node_id[AA_PRESTIGE]); + packet->setDataByName("prestige_max_aa", rule_manager.GetGlobalRule(R_Player, MaxPrestigeAA)->GetInt32()); + packet->setDataByName("prestige_id", classid[node_id[AA_PRESTIGE]]); + packet->setDataByName("prestige_eof_req", 0); + packet->setArrayLengthByName("prestige_num_items", prestige_num_items, 0); + for (itr2 = PrestigeTab.begin(); itr2 != PrestigeTab.end(); itr2++) { + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++, index++) { + //spell = master_spell_list.GetSpell((*itr3)->spellID, (*itr3)->tier); + //current_rank = client->GetPlayer()->GetSpellTier((*itr3)->spellID); + if (index == 0) + class_node_id = (*itr3)->spellID; + //if (spell) { + packet->setArrayDataByName("prestige_parent_id", (*itr3)->rankPrereqID, index); + packet->setArrayDataByName("prestige_req_tier", (*itr3)->rankPrereq, index); + packet->setArrayDataByName("prestige_spell_id", (*itr3)->spellID, index); + packet->setArrayDataByName("prestige_active", (current_rank == 0 ? 0 : (current_rank >= (*itr3)->maxRank) ? 2 : 1), index); + packet->setArrayDataByName("prestige_spell_name", (*itr3)->name.c_str(), index); + packet->setArrayDataByName("prestige_spell_description", (*itr3)->description.c_str(), index); + packet->setArrayDataByName("prestige_icon", (*itr3)->icon, index); + packet->setArrayDataByName("prestige_icon2", (*itr3)->icon2, index); + packet->setArrayDataByName("prestige_current_rank", current_rank, index); // TODO: need to get this value from the DB + packet->setArrayDataByName("prestige_max_rank", (*itr3)->maxRank, index); + packet->setArrayDataByName("prestige_rank_cost", (*itr3)->rankCost, index); + packet->setArrayDataByName("prestige_min_lev", (*itr3)->min_level, index); + packet->setSubArrayLengthByName("prestige_unknown5_num_items", 0, index, 0); + //packet->setSubArrayDataByName("prestige_unknown5", 308397057, index, 0); + //packet->setSubArrayDataByName("prestige_unknown5", 3215564363, index, 1); + //packet->setSubArrayDataByName("prestige_unknown5", 445192837, index, 2); + //packet->setSubArrayDataByName("prestige_unknown5", 3345493294, index, 3); + //packet->setSubArrayDataByName("prestige_unknown5", 696953971, index, 4); + packet->setArrayDataByName("prestige_unknown6", 4294967295, index); + packet->setArrayDataByName("prestige_unknown7", 1, index); + packet->setArrayDataByName("prestige_classification1", (*itr3)->class_name.c_str(), index); + packet->setArrayDataByName("prestige_points_req", (*itr3)->req_points, index); + packet->setArrayDataByName("prestige_unknown8", 0, index); + packet->setArrayDataByName("prestige_classification2", (*itr3)->subclass_name.c_str(), index); + packet->setArrayDataByName("prestige_col", (*itr3)->col, index); + packet->setArrayDataByName("prestige_row", (*itr3)->row, index); + packet->setArrayDataByName("prestige_line_title", (*itr3)->line_title.c_str(), index); + packet->setArrayDataByName("prestige_unknown9", ((*itr3)->title_level > 0 ? 258 : 0), index); + packet->setArrayDataByName("prestige_points_to_unlock", (*itr3)->req_tree_points, index); + packet->setArrayDataByName("prestige_unknown9b", 0, index); + + //} + //else + //LogWrite(SPELL__ERROR, 0, "AltAdvancement","Could not find Spell ID %u and Tier %i in Master Spell List", (*itr3)->spellID, (*itr3)->tier); + } + } + + packet->setDataByName("prestige_points_spent", 34); + + if (version >= 58617) { + packet->setDataByName("prestige_unknown10", 16); + + packet->setDataByName("prestige_unknown11a", 0); + packet->setDataByName("prestige_unknown11b", 50386); + packet->setDataByName("prestige_unknown11c", 1539); + + packet->setDataByName("prestige_unknown12", 0, 0); + packet->setDataByName("prestige_unknown12", 0, 1); + packet->setDataByName("prestige_unknown12", 0, 2); + + packet->setDataByName("prestige_unknown13", ":493debb3e678b977_91:Prestige"); + packet->setDataByName("prestige_unknown14", 1); + packet->setDataByName("prestige_unknown15", ":493debb3e6a8bb79_20:Prestige Expertise"); + packet->setDataByName("prestige_unknown16", 2, 0); + packet->setDataByName("prestige_unknown16", 0, 1); + packet->setDataByName("prestige_unknown16", 0, 2); + packet->setDataByName("prestige_unknown16", 0, 3); + packet->setDataByName("prestige_unknown16", 0, 4); + packet->setDataByName("prestige_unknown16", 0, 5); + } + else if (version >= 1193) { + packet->setDataByName("prestige_unknown10", 16); + packet->setDataByName("prestige_unknown11", 0, 0); + packet->setDataByName("prestige_unknown11", 1, 1); + packet->setDataByName("prestige_unknown11", 1539, 2); + packet->setDataByName("prestige_unknown12", 0, 0); + packet->setDataByName("prestige_unknown12", 0, 1); + packet->setDataByName("prestige_unknown12", 0, 2); + packet->setDataByName("prestige_unknown13", ":493debb3e678b977_91:Prestige"); + packet->setDataByName("prestige_unknown14", 1); + packet->setDataByName("prestige_unknown15", ":493debb3e6a8bb79_20:Prestige Expertise"); + packet->setDataByName("prestige_unknown16", 2, 0); + packet->setDataByName("prestige_unknown16", 0, 1); + packet->setDataByName("prestige_unknown16", 0, 2); + packet->setDataByName("prestige_unknown16", 0, 3); + packet->setDataByName("prestige_unknown16", 0, 4); + packet->setDataByName("prestige_unknown16", 0, 5); + } + + //__________________________________________________________START OF TRADESKILL PRESTIGE TREE____________________________________________________________________________________ + for (itr2 = TradeskillPrestigeTab.begin(); itr2 != TradeskillPrestigeTab.end(); itr2++) { + tradeskillprestige_num_items += (itr2->second).size(); + } + LogWrite(SPELL__DEBUG, 0, "AA", "TradeskillPrestigeTab Size...%i ", tradeskillprestige_num_items); + index = 0; + if (version >= 58617) { + packet->setDataByName("tradeskillprestige_tab_title", "General"); + } + else { + packet->setDataByName("tradeskillprestige_tab_title", "Tradeskill Prestige"); + } + packet->setDataByName("tradeskillprestige_tree_node_id", node_id[AA_TRADESKILL_PRESTIGE]); + packet->setDataByName("tradeskillprestige_max_aa", rule_manager.GetGlobalRule(R_Player, MaxPrestigeAA)->GetInt32()); + packet->setDataByName("tradeskillprestige_id", classid[node_id[AA_TRADESKILL_PRESTIGE]]); + packet->setDataByName("tradeskillprestige_eof_req", 0); + packet->setArrayLengthByName("tradeskillprestige_num_items", tradeskillprestige_num_items, 0); + for (itr2 = PrestigeTab.begin(); itr2 != PrestigeTab.end(); itr2++) { + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++, index++) { + //spell = master_spell_list.GetSpell((*itr3)->spellID, (*itr3)->tier); + //current_rank = client->GetPlayer()->GetSpellTier((*itr3)->spellID); + if (index == 0) + class_node_id = (*itr3)->spellID; + //if (spell) { + packet->setArrayDataByName("tradeskillprestige_parent_id", (*itr3)->rankPrereqID, index); + packet->setArrayDataByName("tradeskillprestige_req_tier", (*itr3)->rankPrereq, index); + packet->setArrayDataByName("tradeskillprestige_spell_id", (*itr3)->spellID, index); + packet->setArrayDataByName("tradeskillprestige_active", (current_rank == 0 ? 0 : (current_rank >= (*itr3)->maxRank) ? 2 : 1), index); + packet->setArrayDataByName("tradeskillprestige_spell_name", (*itr3)->name.c_str(), index); + packet->setArrayDataByName("tradeskillprestige_spell_description", (*itr3)->description.c_str(), index); + packet->setArrayDataByName("tradeskillprestige_icon", (*itr3)->icon, index); + packet->setArrayDataByName("tradeskillprestige_icon2", (*itr3)->icon2, index); + packet->setArrayDataByName("tradeskillprestige_current_rank", current_rank, index); // TODO: need to get this value from the DB + packet->setArrayDataByName("tradeskillprestige_max_rank", (*itr3)->maxRank, index); + packet->setArrayDataByName("tradeskillprestige_rank_cost", (*itr3)->rankCost, index); + packet->setArrayDataByName("tradeskillprestige_min_lev", (*itr3)->min_level, index); + packet->setSubArrayLengthByName("tradeskillprestige_unknown5_num_items", 0, index, 0); + //packet->setSubArrayDataByName("tradeskillprestige_unknown5", 308397057, index, 0); + //packet->setSubArrayDataByName("tradeskillprestige_unknown5", 3215564363, index, 1); + //packet->setSubArrayDataByName("tradeskillprestige_unknown5", 445192837, index, 2); + //packet->setSubArrayDataByName("tradeskillprestige_unknown5", 3345493294, index, 3); + //packet->setSubArrayDataByName("tradeskillprestige_unknown5", 696953971, index, 4); + packet->setArrayDataByName("tradeskillprestige_unknown6", 4294967295, index); + packet->setArrayDataByName("tradeskillprestige_unknown7", 1, index); + packet->setArrayDataByName("tradeskillprestige_classification1", (*itr3)->class_name.c_str(), index); + packet->setArrayDataByName("tradeskillprestige_points_req", (*itr3)->req_points, index); + packet->setArrayDataByName("tradeskillprestige_unknown8", 0, index); + packet->setArrayDataByName("tradeskillprestige_classification2", (*itr3)->subclass_name.c_str(), index); + packet->setArrayDataByName("tradeskillprestige_col", (*itr3)->col, index); + packet->setArrayDataByName("tradeskillprestige_row", (*itr3)->row, index); + packet->setArrayDataByName("tradeskillprestige_line_title", (*itr3)->line_title.c_str(), index); + packet->setArrayDataByName("tradeskillprestige_unknown9", ((*itr3)->title_level > 0 ? 258 : 0), index); + packet->setArrayDataByName("tradeskillprestige_points_to_unlock", (*itr3)->req_tree_points, index); + packet->setArrayDataByName("tradeskillprestige_unknown9b", 0, index); + //} + //else + //LogWrite(SPELL__ERROR, 0, "AltAdvancement","Could not find Spell ID %u and Tier %i in Master Spell List", (*itr3)->spellID, (*itr3)->tier); + } + } + + packet->setDataByName("tradeskillprestige_points_spent", 18); + if (version >= 58617) { + packet->setDataByName("tradeskillprestige_unknown10", 18); + + packet->setDataByName("tradeskillprestige_unknown11a", 0); + packet->setDataByName("tradeskillprestige_unknown11b", 50386); + packet->setDataByName("tradeskillprestige_unknown11c", 3); + + packet->setDataByName("tradeskillprestige_unknown12", 0, 0); + packet->setDataByName("tradeskillprestige_unknown12", 0, 1); + packet->setDataByName("tradeskillprestige_unknown12", 0, 2); + + packet->setDataByName("tradeskillprestige_unknown14", 0); + packet->setDataByName("tradeskillprestige_unknown16", 4, 0); + packet->setDataByName("tradeskillprestige_unknown16", 0, 1); + packet->setDataByName("tradeskillprestige_unknown16", 0, 2); + packet->setDataByName("tradeskillprestige_unknown16", 0, 3); + packet->setDataByName("tradeskillprestige_unknown16", 0, 4); + packet->setDataByName("tradeskillprestige_unknown16", 0, 5); + } + else if (version >= 1193) { + packet->setDataByName("tradeskillprestige_unknown10", 16); + packet->setDataByName("tradeskillprestige_unknown11", 0, 0); + packet->setDataByName("tradeskillprestige_unknown11", 1, 1); + packet->setDataByName("tradeskillprestige_unknown11", 3, 2); + + packet->setDataByName("tradeskillprestige_unknown12", 0, 0); + packet->setDataByName("tradeskillprestige_unknown12", 0, 1); + packet->setDataByName("tradeskillprestige_unknown12", 0, 2); + + packet->setDataByName("tradeskillprestige_unknown16", 4, 0); + packet->setDataByName("tradeskillprestige_unknown16", 0, 1); + packet->setDataByName("tradeskillprestige_unknown16", 0, 2); + packet->setDataByName("tradeskillprestige_unknown16", 0, 3); + packet->setDataByName("tradeskillprestige_unknown16", 0, 4); + packet->setDataByName("tradeskillprestige_unknown16", 0, 5); + } + } + if (version >= 58617) { + //__________________________________________________________START OF DRAGON TREE____________________________________________________________________________________ + for (itr2 = DragonTab.begin(); itr2 != DragonTab.end(); itr2++) { + dragon_num_items += (itr2->second).size(); + } + LogWrite(SPELL__DEBUG, 0, "AA", "DragonTab Size...%i ", dragon_num_items); + index = 0; + packet->setDataByName("dragon_tab_title", "Dragon"); + packet->setDataByName("dragon_tree_node_id", node_id[AA_DRAGON]); + packet->setDataByName("dragon_max_aa", rule_manager.GetGlobalRule(R_Player, MaxDragonAA)->GetInt32()); + packet->setDataByName("dragon_id", classid[node_id[AA_DRAGON]]); + packet->setDataByName("dragon_eof_req", 0); + packet->setArrayLengthByName("dragon_num_items", dragon_num_items, 0); + for (itr2 = DragonTab.begin(); itr2 != DragonTab.end(); itr2++) { + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++, index++) { + //spell = master_spell_list.GetSpell((*itr3)->spellID, (*itr3)->tier); + //current_rank = client->GetPlayer()->GetSpellTier((*itr3)->spellID); + if (index == 0) + class_node_id = (*itr3)->spellID; + + + //if (spell) { + packet->setArrayDataByName("dragon_parent_id", (*itr3)->rankPrereqID, index); + packet->setArrayDataByName("dragon_req_tier", (*itr3)->rankPrereq, index); + packet->setArrayDataByName("dragon_spell_id", (*itr3)->spellID, index); + packet->setArrayDataByName("dragon_active", (current_rank == 0 ? 0 : (current_rank >= (*itr3)->maxRank) ? 2 : 1), index); + packet->setArrayDataByName("dragon_spell_name", (*itr3)->name.c_str(), index); + packet->setArrayDataByName("dragon_spell_description", (*itr3)->description.c_str(), index); + packet->setArrayDataByName("dragon_icon", (*itr3)->icon, index); + packet->setArrayDataByName("dragon_icon2", (*itr3)->icon2, index); + packet->setArrayDataByName("dragon_current_rank", current_rank, index); // TODO: need to get this value from the DB + packet->setArrayDataByName("dragon_max_rank", (*itr3)->maxRank, index); + packet->setArrayDataByName("dragon_rank_cost", (*itr3)->rankCost, index); + packet->setArrayDataByName("dragon_min_lev", (*itr3)->min_level, index); + packet->setSubArrayLengthByName("dragon_unknown5_num_items", 0, index, 0); + //packet->setSubArrayDataByName("dragon_unknown5", 308397057, index, 0); + //packet->setSubArrayDataByName("dragon_unknown5", 3215564363, index, 1); + //packet->setSubArrayDataByName("dragon_unknown5", 445192837, index, 2); + //packet->setSubArrayDataByName("dragon_unknown5", 3345493294, index, 3); + //packet->setSubArrayDataByName("dragon_unknown5", 696953971, index, 4); + packet->setArrayDataByName("dragon_unknown6", 4294967295, index); + packet->setArrayDataByName("dragon_unknown7", 1, index); + packet->setArrayDataByName("dragon_classification1", (*itr3)->class_name.c_str(), index); + packet->setArrayDataByName("dragon_points_req", (*itr3)->req_points, index); + packet->setArrayDataByName("dragon_unknown8", 0, index); + packet->setArrayDataByName("dragon_classification2", (*itr3)->subclass_name.c_str(), index); + packet->setArrayDataByName("dragon_col", (*itr3)->col, index); + packet->setArrayDataByName("dragon_row", (*itr3)->row, index); + packet->setArrayDataByName("dragon_line_title", (*itr3)->line_title.c_str(), index); + packet->setArrayDataByName("dragon_unknown9", ((*itr3)->title_level > 0 ? 258 : 0), index); + packet->setArrayDataByName("dragon_points_to_unlock", (*itr3)->req_tree_points, index); + packet->setArrayDataByName("dragon_unknown9b", 0, index); + + //} + //else + //LogWrite(SPELL__ERROR, 0, "AltAdvancement","Could not find Spell ID %u and Tier %i in Master Spell List", (*itr3)->spellID, (*itr3)->tier); + } + } + + packet->setDataByName("dragon_points_spent", 19); + + if (version >= 58617) { + packet->setDataByName("dragon_unknown10", 19); + + packet->setDataByName("dragon_unknown11a", 0); + packet->setDataByName("dragon_unknown11b", 50386); + packet->setDataByName("dragon_unknown11c", 1); + + packet->setDataByName("dragon_unknown12", 0, 0); + packet->setDataByName("dragon_unknown12", 0, 1); + packet->setDataByName("dragon_unknown12", 0, 2); + + packet->setDataByName("dragon_unknown14", 0); + packet->setDataByName("dragon_unknown16", 0, 0); + packet->setDataByName("dragon_unknown16", 0, 1); + packet->setDataByName("dragon_unknown16", 0, 2); + packet->setDataByName("dragon_unknown16", 0, 3); + packet->setDataByName("dragon_unknown16", 0, 4); + packet->setDataByName("dragon_unknown16", 0, 5); + } + //__________________________________________________________START OF DRAGON CLASS TREE____________________________________________________________________________________ + for (itr2 = DragonclassTab.begin(); itr2 != DragonclassTab.end(); itr2++) { + dragonclass_num_items += (itr2->second).size(); + } + LogWrite(SPELL__DEBUG, 0, "AA", "DragonclassTab Size...%i ", dragonclass_num_items); + index = 0; + packet->setDataByName("dragonclass_tab_title", classes.GetClassNameCase(client->GetPlayer()->GetAdventureClass()).c_str()); + packet->setDataByName("dragonclass_tree_node_id", node_id[AA_DRAGONCLASS]); + packet->setDataByName("dragonclass_max_aa", rule_manager.GetGlobalRule(R_Player, MaxDragonAA)->GetInt32()); + packet->setDataByName("dragonclass_id", classid[node_id[AA_DRAGONCLASS]]); + packet->setDataByName("dragonclass_eof_req", 0); + packet->setArrayLengthByName("dragonclass_num_items", dragonclass_num_items, 0); + for (itr2 = DragonclassTab.begin(); itr2 != DragonclassTab.end(); itr2++) { + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++, index++) { + //spell = master_spell_list.GetSpell((*itr3)->spellID, (*itr3)->tier); + //current_rank = client->GetPlayer()->GetSpellTier((*itr3)->spellID); + if (index == 0) + class_node_id = (*itr3)->spellID; + //if (spell) { + packet->setArrayDataByName("dragonclass_parent_id", (*itr3)->rankPrereqID, index); + packet->setArrayDataByName("dragonclass_req_tier", (*itr3)->rankPrereq, index); + packet->setArrayDataByName("dragonclass_spell_id", (*itr3)->spellID, index); + packet->setArrayDataByName("dragonclass_active", (current_rank == 0 ? 0 : (current_rank >= (*itr3)->maxRank) ? 2 : 1), index); + packet->setArrayDataByName("dragonclass_spell_name", (*itr3)->name.c_str(), index); + packet->setArrayDataByName("dragonclass_spell_description", (*itr3)->description.c_str(), index); + packet->setArrayDataByName("dragonclass_icon", (*itr3)->icon, index); + packet->setArrayDataByName("dragonclass_icon2", (*itr3)->icon2, index); + packet->setArrayDataByName("dragonclass_current_rank", current_rank, index); // TODO: need to get this value from the DB + packet->setArrayDataByName("dragonclass_max_rank", (*itr3)->maxRank, index); + packet->setArrayDataByName("dragonclass_rank_cost", (*itr3)->rankCost, index); + packet->setArrayDataByName("dragonclass_min_lev", (*itr3)->min_level, index); + packet->setSubArrayLengthByName("dragonclass_unknown5_num_items", 0, index, 0); + //packet->setSubArrayDataByName("dragonclass_unknown5", 308397057, index, 0); + //packet->setSubArrayDataByName("dragonclass_unknown5", 3215564363, index, 1); + //packet->setSubArrayDataByName("dragonclass_unknown5", 445192837, index, 2); + //packet->setSubArrayDataByName("dragonclass_unknown5", 3345493294, index, 3); + //packet->setSubArrayDataByName("dragonclass_unknown5", 696953971, index, 4); + packet->setArrayDataByName("dragonclass_unknown6", 4294967295, index); + packet->setArrayDataByName("dragonclass_unknown7", 1, index); + packet->setArrayDataByName("dragonclass_classification1", (*itr3)->class_name.c_str(), index); + packet->setArrayDataByName("dragonclass_points_req", (*itr3)->req_points, index); + packet->setArrayDataByName("dragonclass_unknown8", 0, index); + packet->setArrayDataByName("dragonclass_classification2", (*itr3)->subclass_name.c_str(), index); + packet->setArrayDataByName("dragonclass_col", (*itr3)->col, index); + packet->setArrayDataByName("dragonclass_row", (*itr3)->row, index); + packet->setArrayDataByName("dragonclass_line_title", (*itr3)->line_title.c_str(), index); + packet->setArrayDataByName("dragonclass_unknown9", ((*itr3)->title_level > 0 ? 258 : 0), index); + packet->setArrayDataByName("dragonclass_points_to_unlock", (*itr3)->req_tree_points, index); + packet->setArrayDataByName("dragonclass_unknown9b", 0, index); + + //} + //else + //LogWrite(SPELL__ERROR, 0, "AltAdvancement","Could not find Spell ID %u and Tier %i in Master Spell List", (*itr3)->spellID, (*itr3)->tier); + } + } + + packet->setDataByName("dragonclass_points_spent",20); + + if (version >= 58617) { + packet->setDataByName("dragonclass_unknown10", 20); + + packet->setDataByName("dragonclass_unknown11a", 0); + packet->setDataByName("dragonclass_unknown11b", 50386); + packet->setDataByName("dragonclass_unknown11c", 3); + + packet->setDataByName("dragonclass_unknown12", 0, 0); + packet->setDataByName("dragonclass_unknown12", 0, 1); + packet->setDataByName("dragonclass_unknown12", 0, 2); + + packet->setDataByName("dragonclass_unknown14", 0); + packet->setDataByName("dragonclass_unknown16", 2, 0); + packet->setDataByName("dragonclass_unknown16", 0, 1); + packet->setDataByName("dragonclass_unknown16", 0, 2); + packet->setDataByName("dragonclass_unknown16", 0, 3); + packet->setDataByName("dragonclass_unknown16", 0, 4); + packet->setDataByName("dragonclass_unknown16", 0, 5); + } + + //__________________________________________________________START OF FARSEAS TREE____________________________________________________________________________________ + for (itr2 = FarseasTab.begin(); itr2 != FarseasTab.end(); itr2++) { + farseas_num_items += (itr2->second).size(); + } + LogWrite(SPELL__DEBUG, 0, "AA", "FarseasTab Size...%i ", farseas_num_items); + index = 0; + packet->setDataByName("farseas_tab_title", "Farseas"); + packet->setDataByName("farseas_tree_node_id", node_id[AA_FARSEAS]); + packet->setDataByName("farseas_max_aa", rule_manager.GetGlobalRule(R_Player, MaxDragonAA)->GetInt32()); + packet->setDataByName("farseas_id", classid[node_id[AA_FARSEAS]]); + packet->setDataByName("farseas_eof_req", 0); + packet->setArrayLengthByName("farseas_num_items", farseas_num_items, 0); + for (itr2 = FarseasTab.begin(); itr2 != FarseasTab.end(); itr2++) { + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++, index++) { + //spell = master_spell_list.GetSpell((*itr3)->spellID, (*itr3)->tier); + //current_rank = client->GetPlayer()->GetSpellTier((*itr3)->spellID); + if (index == 0) + class_node_id = (*itr3)->spellID; + //if (spell) { + packet->setArrayDataByName("farseas_parent_id", (*itr3)->rankPrereqID, index); + packet->setArrayDataByName("farseas_req_tier", (*itr3)->rankPrereq, index); + packet->setArrayDataByName("farseas_spell_id", (*itr3)->spellID, index); + packet->setArrayDataByName("farseas_active", (current_rank == 0 ? 0 : (current_rank >= (*itr3)->maxRank) ? 2 : 1), index); + packet->setArrayDataByName("farseas_spell_name", (*itr3)->name.c_str(), index); + packet->setArrayDataByName("farseas_spell_description", (*itr3)->description.c_str(), index); + packet->setArrayDataByName("farseas_icon", (*itr3)->icon, index); + packet->setArrayDataByName("farseas_icon2", (*itr3)->icon2, index); + packet->setArrayDataByName("farseas_current_rank", current_rank, index); // TODO: need to get this value from the DB + packet->setArrayDataByName("farseas_max_rank", (*itr3)->maxRank, index); + packet->setArrayDataByName("farseas_rank_cost", (*itr3)->rankCost, index); + packet->setArrayDataByName("farseas_min_lev", (*itr3)->min_level, index); + packet->setSubArrayLengthByName("farseas_unknown5_num_items", 0, index, 0); + //packet->setSubArrayDataByName("farseas_unknown5", 308397057, index, 0); + //packet->setSubArrayDataByName("farseas_unknown5", 3215564363, index, 1); + //packet->setSubArrayDataByName("farseas_unknown5", 445192837, index, 2); + //packet->setSubArrayDataByName("farseas_unknown5", 3345493294, index, 3); + //packet->setSubArrayDataByName("farseas_unknown5", 696953971, index, 4); + packet->setArrayDataByName("farseas_unknown6", 4294967295, index); + packet->setArrayDataByName("farseas_unknown7", 1, index); + packet->setArrayDataByName("farseas_classification1", (*itr3)->class_name.c_str(), index); + packet->setArrayDataByName("farseas_points_req", (*itr3)->req_points, index); + packet->setArrayDataByName("farseas_unknown8", 0, index); + packet->setArrayDataByName("farseas_classification2", (*itr3)->subclass_name.c_str(), index); + packet->setArrayDataByName("farseas_col", (*itr3)->col, index); + packet->setArrayDataByName("farseas_row", (*itr3)->row, index); + packet->setArrayDataByName("farseas_line_title", (*itr3)->line_title.c_str(), index); + packet->setArrayDataByName("farseas_unknown9", ((*itr3)->title_level > 0 ? 258 : 0), index); + packet->setArrayDataByName("farseas_points_to_unlock", (*itr3)->req_tree_points, index); + packet->setArrayDataByName("farseas_unknown9b", 0, index); + + //} + //else + //LogWrite(SPELL__ERROR, 0, "AltAdvancement","Could not find Spell ID %u and Tier %i in Master Spell List", (*itr3)->spellID, (*itr3)->tier); + } + } + + packet->setDataByName("farseas_points_spent",20); + + if (version >= 58617) { + packet->setDataByName("farseas_unknown10", 20); + + packet->setDataByName("farseas_unknown11a", 0); + packet->setDataByName("farseas_unknown11b", 50386); + packet->setDataByName("farseas_unknown11c", 3); + + packet->setDataByName("farseas_unknown12", 0, 0); + packet->setDataByName("farseas_unknown12", 0, 1); + packet->setDataByName("farseas_unknown12", 0, 2); + + packet->setDataByName("farseas_unknown14", 0); + packet->setDataByName("farseas_unknown16", 4, 0); + packet->setDataByName("farseas_unknown16", 0, 1); + packet->setDataByName("farseas_unknown16", 0, 2); + packet->setDataByName("farseas_unknown16", 0, 3); + packet->setDataByName("farseas_unknown16", 0, 4); + packet->setDataByName("farseas_unknown16", 0, 5); + } + int8 tt = 0; + int8 numtabs = 0; + int xxx = 0; + bool sendblanktabs = false; + packet->setDataByName("template_unknown1", (changemode == 2 ? 255 : 25)); + packet->setDataByName("template_unknown2a", 0); + packet->setDataByName("template_unknown2b", 0); + packet->setDataByName("template_unknown2c", (changemode == 3 ? 1 : 0)); + packet->setDataByName("template_unknown2d", (changemode == 1 || changemode == 2 ? 1 :0)); + packet->setDataByName("template_unknown3", (changemode == 2 ? 0 : 4294967295)); //4294967295); + packet->setDataByName("template_unknown4", newtemplate);// active template ID + packet->setDataByName("template_unknown5", 0); + + packet->setDataByName("num_templates", 7); + packet->setDataByName("slot1_template_id", 0); + packet->setDataByName("slot1_name", "Unused Slot 1"); + packet->setDataByName("slot1_active", 0); //0 is server type ,1 = personal type + tt = 1; // template # + numtabs = 0; + if (AAEntryList[tt][0].size() > 0) { numtabs++; } + if (AAEntryList[tt][1].size() > 0) { numtabs++; } + if (AAEntryList[tt][2].size() > 0) { numtabs++; } + if (AAEntryList[tt][3].size() > 0) { numtabs++; } + if (AAEntryList[tt][4].size() > 0) { numtabs++; } + + if (sendblanktabs == true) { + numtabs = 4; + } + packet->setArrayLengthByName("slot1_num_tabs", numtabs); + xxx = 0; + for (int xx = 0; xx < AAEntryList[tt].size(); xx++) { + + if (sendblanktabs == false && AAEntryList[tt][xx].size() < 1 ) {continue;} + packet->setArrayDataByName("slot1_tab_typenum", xx, xxx); + packet->setArrayDataByName("slot1_tab_unknown2", 1, xxx); + packet->setSubArrayLengthByName("slot1_num_items", AAEntryList[tt][xx].size(), xxx); + for (int yy = 0; yy < AAEntryList[tt][xx].size(); yy++) { + packet->setSubArrayDataByName("slot1_item_order", AAEntryList[tt][xx][yy].order, xxx, yy); + packet->setSubArrayDataByName("slot1_item_treeid", AAEntryList[tt][xx][yy].treeid, xxx, yy); + packet->setSubArrayDataByName("slot1_item_id", AAEntryList[tt][xx][yy].aa_id, xxx, yy); + } + xxx++; + } + + packet->setDataByName("slot2_template_id", 1); + packet->setDataByName("slot2_name", "Unused Slot 2"); + packet->setDataByName("slot2_active", 0); + tt = 2; // template # + numtabs = 0; + if (AAEntryList[tt][0].size() > 0) { numtabs++; } + if (AAEntryList[tt][1].size() > 0) { numtabs++; } + if (AAEntryList[tt][2].size() > 0) { numtabs++; } + if (AAEntryList[tt][3].size() > 0) { numtabs++; } + if (AAEntryList[tt][4].size() > 0) { numtabs++; } + + if (sendblanktabs == true) { + numtabs = 4; + } + packet->setArrayLengthByName("slot2_num_tabs", numtabs); + xxx = 0; + for (int xx = 0; xx < AAEntryList[tt].size(); xx++) { + + if (sendblanktabs == false && AAEntryList[tt][xx].size() < 1) { continue; } + packet->setArrayDataByName("slot2_tab_typenum", xx, xxx); + packet->setArrayDataByName("slot2_tab_unknown2", 1, xxx); + packet->setSubArrayLengthByName("slot2_num_items", AAEntryList[tt][xx].size(), xxx); + for (int yy = 0; yy < AAEntryList[tt][xx].size(); yy++) { + packet->setSubArrayDataByName("slot2_item_order", AAEntryList[tt][xx][yy].order, xxx, yy); + packet->setSubArrayDataByName("slot2_item_treeid", AAEntryList[tt][xx][yy].treeid, xxx, yy); + packet->setSubArrayDataByName("slot2_item_id", AAEntryList[tt][xx][yy].aa_id, xxx, yy); + } + xxx++; + } + + packet->setDataByName("slot3_template_id", 2); + packet->setDataByName("slot3_name", "Unused Slot 3"); + packet->setDataByName("slot3_active", 0); + tt = 3; // template # + numtabs = 0; + if (AAEntryList[tt][0].size() > 0) { numtabs++; } + if (AAEntryList[tt][1].size() > 0) { numtabs++; } + if (AAEntryList[tt][2].size() > 0) { numtabs++; } + if (AAEntryList[tt][3].size() > 0) { numtabs++; } + if (AAEntryList[tt][4].size() > 0) { numtabs++; } + + if (sendblanktabs == true) { + numtabs = 4; + } + packet->setArrayLengthByName("slot3_num_tabs", numtabs); + xxx = 0; + for (int xx = 0; xx < AAEntryList[tt].size(); xx++) { + + if (sendblanktabs == false && AAEntryList[tt][xx].size() < 1) { continue; } + packet->setArrayDataByName("slot3_tab_typenum", xx, xxx); + packet->setArrayDataByName("slot3_tab_unknown2", 1, xxx); + packet->setSubArrayLengthByName("slot3_num_items", AAEntryList[tt][xx].size(), xxx); + for (int yy = 0; yy < AAEntryList[tt][xx].size(); yy++) { + packet->setSubArrayDataByName("slot3_item_order", AAEntryList[tt][xx][yy].order, xxx, yy); + packet->setSubArrayDataByName("slot3_item_treeid", AAEntryList[tt][xx][yy].treeid, xxx, yy); + packet->setSubArrayDataByName("slot3_item_id", AAEntryList[tt][xx][yy].aa_id, xxx, yy); + } + xxx++; + } + + packet->setDataByName("slot4_template_id", 20); + packet->setDataByName("slot4_name", "Basic Leveling Profile - Solo"); + packet->setDataByName("slot4_active", 1); + tt = 4; // template # + numtabs = 0; + if (AAEntryList[tt][0].size() > 0) { numtabs++; } + if (AAEntryList[tt][1].size() > 0) { numtabs++; } + if (AAEntryList[tt][2].size() > 0) { numtabs++; } + if (AAEntryList[tt][3].size() > 0) { numtabs++; } + if (AAEntryList[tt][4].size() > 0) { numtabs++; } + + if (sendblanktabs == true) { + numtabs = 4; + } + packet->setArrayLengthByName("slot4_num_tabs", numtabs); + xxx = 0; + for (int xx = 0; xx < AAEntryList[tt].size(); xx++) { + + if (sendblanktabs == false && AAEntryList[tt][xx].size() < 1) { continue; } + packet->setArrayDataByName("slot4_tab_typenum", xx, xxx); + packet->setArrayDataByName("slot4_tab_unknown2", 1, xxx); + packet->setSubArrayLengthByName("slot4_num_items", AAEntryList[tt][xx].size(), xxx); + for (int yy = 0; yy < AAEntryList[tt][xx].size(); yy++) { + packet->setSubArrayDataByName("slot4_item_order", AAEntryList[tt][xx][yy].order, xxx, yy); + packet->setSubArrayDataByName("slot4_item_treeid", AAEntryList[tt][xx][yy].treeid, xxx, yy); + packet->setSubArrayDataByName("slot4_item_id", AAEntryList[tt][xx][yy].aa_id, xxx, yy); + } + xxx++; + } + + + packet->setDataByName("slot5_template_id", 21); + packet->setDataByName("slot5_name", "Basic Leveling Profile - Group"); + packet->setDataByName("slot5_active", 1); + tt = 5; // template # + numtabs = 0; + if (AAEntryList[tt][0].size() > 0) { numtabs++; } + if (AAEntryList[tt][1].size() > 0) { numtabs++; } + if (AAEntryList[tt][2].size() > 0) { numtabs++; } + if (AAEntryList[tt][3].size() > 0) { numtabs++; } + if (AAEntryList[tt][4].size() > 0) { numtabs++; } + + if (sendblanktabs == true) { + numtabs = 4; + } + packet->setArrayLengthByName("slot5_num_tabs", numtabs); + xxx = 0; + for (int xx = 0; xx < AAEntryList[tt].size(); xx++) { + + if (sendblanktabs == false && AAEntryList[tt][xx].size() < 1) { continue; } + packet->setArrayDataByName("slot5_tab_typenum", xx, xxx); + packet->setArrayDataByName("slot5_tab_unknown2", 1, xxx); + packet->setSubArrayLengthByName("slot5_num_items", AAEntryList[tt][xx].size(), xxx); + for (int yy = 0; yy < AAEntryList[tt][xx].size(); yy++) { + packet->setSubArrayDataByName("slot5_item_order", AAEntryList[tt][xx][yy].order, xxx, yy); + packet->setSubArrayDataByName("slot5_item_treeid", AAEntryList[tt][xx][yy].treeid, xxx, yy); + packet->setSubArrayDataByName("slot5_item_id", AAEntryList[tt][xx][yy].aa_id, xxx, yy); + } + xxx++; + } + + packet->setDataByName("slot6_template_id", 22); + packet->setDataByName("slot6_name", "Basic Leveling Profile - PVP"); + packet->setDataByName("slot6_active", 1); + tt = 6; // template # + numtabs = 0; + if (AAEntryList[tt][0].size() > 0) { numtabs++; } + if (AAEntryList[tt][1].size() > 0) { numtabs++; } + if (AAEntryList[tt][2].size() > 0) { numtabs++; } + if (AAEntryList[tt][3].size() > 0) { numtabs++; } + if (AAEntryList[tt][4].size() > 0) { numtabs++; } + + if (sendblanktabs == true) { + numtabs = 4; + } + packet->setArrayLengthByName("slot6_num_tabs", numtabs); + xxx = 0; + for (int xx = 0; xx < AAEntryList[tt].size(); xx++) { + + if (sendblanktabs == false && AAEntryList[tt][xx].size() < 1) { continue; } + packet->setArrayDataByName("slot6_tab_typenum", xx, xxx); + packet->setArrayDataByName("slot6_tab_unknown2", 1, xxx); + packet->setSubArrayLengthByName("slot6_num_items", AAEntryList[tt][xx].size(), xxx); + for (int yy = 0; yy < AAEntryList[tt][xx].size(); yy++) { + packet->setSubArrayDataByName("slot6_item_order", AAEntryList[tt][xx][yy].order, xxx, yy); + packet->setSubArrayDataByName("slot6_item_treeid", AAEntryList[tt][xx][yy].treeid, xxx, yy); + packet->setSubArrayDataByName("slot6_item_id", AAEntryList[tt][xx][yy].aa_id, xxx, yy); + } + xxx++; + } + + packet->setDataByName("slot7_template_id",100); + packet->setDataByName("slot7_name", "New"); + packet->setDataByName("slot7_active", 0); + tt = 7; // template # + numtabs = 0; + if (AAEntryList[tt][0].size() > 0) { numtabs++; } + if (AAEntryList[tt][1].size() > 0) { numtabs++; } + if (AAEntryList[tt][2].size() > 0) { numtabs++; } + if (AAEntryList[tt][3].size() > 0) { numtabs++; } + if (AAEntryList[tt][4].size() > 0) { numtabs++; } + + if (sendblanktabs == true) { + numtabs = 4; + } + packet->setArrayLengthByName("slot7_num_tabs", numtabs); + xxx = 0; + for (int xx = 0; xx < AAEntryList[tt].size(); xx++) { + + if (sendblanktabs == false && AAEntryList[tt][xx].size() < 1) { continue; } + packet->setArrayDataByName("slot7_tab_typenum", xx, xxx); + packet->setArrayDataByName("slot7_tab_unknown2", 1, xxx); + packet->setSubArrayLengthByName("slot7_num_items", AAEntryList[tt][xx].size(), xxx); + for (int yy = 0; yy < AAEntryList[tt][xx].size(); yy++) { + packet->setSubArrayDataByName("slot7_item_order", AAEntryList[tt][xx][yy].order, xxx, yy); + packet->setSubArrayDataByName("slot7_item_treeid", AAEntryList[tt][xx][yy].treeid, xxx, yy); + packet->setSubArrayDataByName("slot7_item_id", AAEntryList[tt][xx][yy].aa_id, xxx, yy); + } + xxx++; + } + //packet->PrintPacket(); + } + //packet->PrintPacket(); + EQ2Packet* data = packet->serialize(); + EQ2Packet* app = new EQ2Packet(OP_AdventureList, data->pBuffer, data->size); + //DumpPacket(app); + client->QueuePacket(app); + safe_delete(packet); + safe_delete(data); +} \ No newline at end of file diff --git a/source/WorldServer/AltAdvancement/AltAdvancement.h b/source/WorldServer/AltAdvancement/AltAdvancement.h new file mode 100644 index 0000000..9b0ed93 --- /dev/null +++ b/source/WorldServer/AltAdvancement/AltAdvancement.h @@ -0,0 +1,118 @@ +/* +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 . +*/ + +#ifndef __AltAdvancement__ +#define __AltAdvancement__ + +#include +#include "../../common/types.h" +#include "../../common/EQPacket.h" +#include "../client.h" + +// defines for AA tabs based on group # from DB +#define AA_CLASS 0 +#define AA_SUBCLASS 1 +#define AA_SHADOW 2 +#define AA_HEROIC 3 +#define AA_TRADESKILL 4 +#define AA_PRESTIGE 5 +#define AA_TRADESKILL_PRESTIGE 6 +#define AA_DRAGON 7 +#define AA_DRAGONCLASS 8 +#define AA_FARSEAS 9 +struct AltAdvanceData +{ + int32 spellID; + int8 min_level; + int32 spell_crc; + string name; + string description; + int8 group; + int16 icon; + int16 icon2; + int8 col; + int8 row; + int8 rankCost; + int8 maxRank; + int32 rankPrereqID; + int8 rankPrereq; + int8 class_req; + int8 tier; + int8 req_points; + int16 req_tree_points; + string class_name; + string subclass_name; + string line_title; + int8 title_level; + int32 node_id; +}; + + +class MasterAAList +{ +public: + MasterAAList(); + ~MasterAAList(); + /// Sorts the Alternate Advancements for the given client, creates and sends the OP_AdventureList packet. + /// The Client calling this function + /// EQ2Packet* + EQ2Packet* GetAAListPacket(Client* client); + + /// Add Alternate Advancement data to the global list. + /// The Alternate Advancement data to add. + void AddAltAdvancement(AltAdvanceData* data); + + /// Get the total number of Alternate Advancements in the global list. + int Size(); + + /// Get the Alternate Advancement data for the given spell. + /// Spell ID to get Alternate Advancement data for. + AltAdvanceData* GetAltAdvancement(int32 spellID); + + /// empties the master Alternate Advancement list + void DestroyAltAdvancements(); + void DisplayAA(Client* client,int8 newtemplate,int8 changemode); +private: + vector AAList; + Mutex MMasterAAList; +}; + +struct TreeNodeData +{ + int32 classID; + int32 treeID; + int32 AAtreeID; +}; + +class MasterAANodeList +{ +public: + MasterAANodeList(); + ~MasterAANodeList(); + void AddTreeNode(TreeNodeData* data); + int Size(); + void DestroyTreeNodes(); + vector GetTreeNodes(); + +private: + vector TreeNodeList; +}; + +#endif \ No newline at end of file diff --git a/source/WorldServer/AltAdvancement/AltAdvancementDB.cpp b/source/WorldServer/AltAdvancement/AltAdvancementDB.cpp new file mode 100644 index 0000000..b7f3da9 --- /dev/null +++ b/source/WorldServer/AltAdvancement/AltAdvancementDB.cpp @@ -0,0 +1,102 @@ +/* + 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 . +*/ + +#ifdef WIN32 + #include + #include +#endif +#include +#include +#include "../../common/Log.h" +#include "../../common/DatabaseNew.h" +#include "../WorldDatabase.h" +#include "AltAdvancement.h" + +extern MasterAAList master_aa_list; +extern MasterAANodeList master_tree_nodes; + +void WorldDatabase::LoadAltAdvancements() +{ + Query query; + MYSQL_ROW row; + AltAdvanceData* data; + + //MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `spell_id`, `group`, `icon`, `icon2`, `col`, `row`, `rank_cost`, `max_cost`, `rank_prereq_id`, `rank_prereq`, `class_req`, `tier`, `class_name`, `subclass_name`, `line_title` FROM spell_aa"); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `nodeid`,`minlevel`, `spellcrc`, `name`, `description`, `aa_list_fk`, `icon_id`, `icon_backdrop`, `xcoord`, `ycoord`, `pointspertier`, `maxtier`, `firstparentid`, `firstparentrequiredtier`, `displayedclassification`,`requiredclassification`, `classificationpointsrequired`, `pointsspentintreetounlock`, `title`,`titlelevel` FROM spell_aa_nodelist"); + while (result && (row = mysql_fetch_row(result))) { + data = new AltAdvanceData; + int8 i = 0; + data->spellID = strtoul(row[0], NULL, 0); + data->min_level = atoi(row[++i]); + data->spell_crc = strtoul(row[++i], NULL, 0); + data->name = string(row[++i]); + data->description = string(row[++i]); + data->group = atoi(row[++i]); + data->icon = atoi(row[++i]); + data->icon2 = atoi(row[++i]); + data->col = atoi(row[++i]); + data->row = atoi(row[++i]); + data->rankCost = atoi(row[++i]); + data->maxRank = atoi(row[++i]); + data->rankPrereqID = strtoul(row[++i], NULL, 0); + data->rankPrereq = atoi(row[++i]); + data->tier = 1; + data->class_name = string(row[++i]); + data->subclass_name = string(row[i]); + data->req_points = atoi(row[++i]); + data->req_tree_points = atoi(row[++i]); + data->line_title = string(row[++i]); + data->title_level = atoi(row[++i]); + + master_aa_list.AddAltAdvancement(data); + } + + LogWrite(SPELL__INFO, 0, "AA", "Loaded %u Alternate Advancement(s)", master_aa_list.Size()); + +} + +void WorldDatabase::LoadTreeNodes() +{ + Query query; + MYSQL_ROW row; + TreeNodeData* data; + + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT class_id, tree_node, aa_tree_id FROM spell_aa_class_list"); + while (result && (row = mysql_fetch_row(result))) { + data = new TreeNodeData; + data->classID = strtoul(row[0], NULL, 0); + data->treeID = strtoul(row[1], NULL, 0); + data->AAtreeID = strtoul(row[2], NULL, 0); + master_tree_nodes.AddTreeNode(data); + } + LogWrite(SPELL__INFO, 0, "AA", "Loaded %u AA Tree Nodes", master_tree_nodes.Size()); +} +void WorldDatabase::LoadPlayerAA(Player *player) +{ + Query query; + MYSQL_ROW row; + + + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `template_id`,`tab_id`,`aa_id`,`order`,treeid FROM character_aa where char_id = %i order by `order`",player->id); + while (result && (row = mysql_fetch_row(result))) { + + } + LogWrite(SPELL__INFO, 0, "AA", "Loaded %u AA Tree Nodes", master_tree_nodes.Size()); +} \ No newline at end of file diff --git a/source/WorldServer/Appearances.h b/source/WorldServer/Appearances.h new file mode 100644 index 0000000..11f1de6 --- /dev/null +++ b/source/WorldServer/Appearances.h @@ -0,0 +1,87 @@ +/* + 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 . +*/ +#include + +using namespace std; + +// Appearances must use a hash table because of the large amount that exists and the large spacing +// between their ID's. String and character arrays could not be used for the first iterator because +// it would require the same pointer to access it from the hash table, which is obviously not possible +// since the text is from the client. + +// maximum amount of iterations it will attempt to find a entree +#define HASH_SEARCH_MAX 20 + +class Appearance +{ +public: + // JA: someday add the min_client_version to the map to determine which appearance_id to set per client version + Appearance(int32 inID, const char *inName, int16 inVer) + { + if( !inName ) + return; + name = string(inName); + id = inID; + min_client = inVer; + } + + int32 GetID() { return id; } + const char* GetName() { return name.c_str(); } + int16 GetMinClientVersion() { return min_client; } + string GetNameString() { return name; } + +private: + int32 id; + string name; + int16 min_client; +}; + +class Appearances +{ +public: + ~Appearances(){ + Reset(); + } + + void Reset(){ + ClearAppearances(); + } + + void ClearAppearances(){ + map::iterator map_list; + for(map_list = appearanceMap.begin(); map_list != appearanceMap.end(); map_list++ ) + safe_delete(map_list->second); + appearanceMap.clear(); + } + + void InsertAppearance(Appearance* a){ + appearanceMap[a->GetID()] = a; + } + + Appearance* FindAppearanceByID(int32 id){ + if(appearanceMap.count(id) > 0) + return appearanceMap[id]; + return 0; + } + +private: + map appearanceMap; +}; + diff --git a/source/WorldServer/Bots/Bot.cpp b/source/WorldServer/Bots/Bot.cpp new file mode 100644 index 0000000..953aa32 --- /dev/null +++ b/source/WorldServer/Bots/Bot.cpp @@ -0,0 +1,732 @@ +#include "Bot.h" +#include "BotBrain.h" +#include "../Trade.h" +#include "../../common/Log.h" +#include "../WorldDatabase.h" +#include "../classes.h" +#include "../SpellProcess.h" + +extern WorldDatabase database; +extern MasterSpellList master_spell_list; +extern World world; +extern Classes classes; + +Bot::Bot() : NPC() { + SetBrain(new BotBrain(this)); + BotID = 0; + ShowHelm = true; + ShowCloak = true; + CanTaunt = false; + combat_target = 0; + main_tank = 0; + + //AddPrimaryEntityCommand("hail", 10000, "hail", "", 0, 0); + AddSecondaryEntityCommand("invite bot", 10000, "invite", "", 0, 0); + AddSecondaryEntityCommand("bot inventory", 10000, "bot inv list", "", 0, 0); + + InfoStruct* info = GetInfoStruct(); + info->set_str_base(50); + info->set_sta_base(20); + info->set_wis_base(20); + info->set_intel_base(20); + info->set_agi_base(20); + + camping = false; + immediate_camp = false; +} + +Bot::~Bot() { +} + +void Bot::GiveItem(int32 item_id) { + Item* master_item = master_item_list.GetItem(item_id); + Item* item = 0; + if (master_item) + item = new Item(master_item); + if (item) { + int8 slot = GetEquipmentList()->GetFreeSlot(item); + if (slot != 255) { + GetEquipmentList()->AddItem(slot, item); + SetEquipment(item, slot); + database.SaveBotItem(BotID, item_id, slot); + if (slot == 0) { + ChangePrimaryWeapon(); + if (IsBot()) + LogWrite(PLAYER__ERROR, 0, "Bot", "Changing bot primary weapon."); + } + + CalculateBonuses(); + } + } +} + +void Bot::GiveItem(Item* item) { + if (item) { + int8 slot = GetEquipmentList()->GetFreeSlot(item); + if (slot != 255) { + GetEquipmentList()->AddItem(slot, item); + SetEquipment(item, slot); + database.SaveBotItem(BotID, item->details.item_id, slot); + if (slot == 0) { + ChangePrimaryWeapon(); + if (IsBot()) + LogWrite(PLAYER__ERROR, 0, "Bot", "Changing bot primary weapon."); + } + + CalculateBonuses(); + } + } +} + +void Bot::RemoveItem(Item* item) { + int8 slot = GetEquipmentList()->GetSlotByItem(item); + if (slot != 255) { + GetEquipmentList()->RemoveItem(slot, true); + SetEquipment(0, slot); + } +} + +void Bot::TradeItemAdded(Item* item) { + int8 slot = GetEquipmentList()->GetFreeSlot(item); + if (slot == 255 && item->slot_data.size() > 0) { + slot = item->slot_data[0]; + AddItemToTrade(slot); + } +} + +void Bot::AddItemToTrade(int8 slot) { + Item* item = GetEquipmentList()->GetItem(slot); + if (trading_slots.count(slot) == 0 && item && trade) { + trade->AddItemToTrade(this, item, 1, 255); + trading_slots.insert(slot); + } +} + +bool Bot::CheckTradeItems(map* list) { + if (!list) { + LogWrite(PLAYER__ERROR, 0, "Bot", "CheckTradeItems did not recieve a valid list of items"); + return false; + } + + bool ret = true; + map::iterator itr; + for (itr = list->begin(); itr != list->end(); itr++) { + Item* item = itr->second.item; + if (item) { + if (!CanEquipItem(item)) { + // No slots means not equipable so reject the trade + ret = false; + break; + } + } + } + + return ret; +} + +void Bot::FinishTrade() { + trading_slots.clear(); +} + +bool Bot::CanEquipItem(Item* item) { + if (item) { + if (item->IsArmor() || item->IsWeapon() || item->IsFood() || item->IsRanged() || item->IsShield() || item->IsBauble() || item->IsAmmo() || item->IsThrown()) { + int16 override_level = item->GetOverrideLevel(GetAdventureClass(), GetTradeskillClass()); + if (override_level > 0 && override_level <= GetLevel()) { + LogWrite(PLAYER__ERROR, 0, "Bot", "Passed in override_level check"); + return true; + } + if (item->CheckClass(GetAdventureClass(), GetTradeskillClass())) + if (item->CheckLevel(GetAdventureClass(), GetTradeskillClass(), GetLevel())) { + LogWrite(PLAYER__ERROR, 0, "Bot", "Passed in normal check"); + return true; + } + } + } + return false; +} + +void Bot::MessageGroup(string msg) { + GroupMemberInfo* gmi = GetGroupMemberInfo(); + if (gmi) + world.GetGroupManager()->GroupChatMessage(gmi->group_id, this, 0, msg.c_str()); +} + +void Bot::GetNewSpells() { + vector spells; + vector* spells1 = master_spell_list.GetSpellListByAdventureClass(GetAdventureClass(), GetLevel(), 1); + vector* spells2 = master_spell_list.GetSpellListByAdventureClass(classes.GetBaseClass(GetAdventureClass()), GetLevel(), 1); + vector* spells3 = master_spell_list.GetSpellListByAdventureClass(classes.GetSecondaryBaseClass(GetAdventureClass()), GetLevel(), 1); + + spells.insert(spells.end(), spells1->begin(), spells1->end()); + spells.insert(spells.end(), spells2->begin(), spells2->end()); + spells.insert(spells.end(), spells3->begin(), spells3->end()); + + vector::iterator itr; + map* spell_list = 0; + for (itr = spells.begin(); itr != spells.end(); itr++) { + switch ((*itr)->GetSpellData()->spell_type) { + case SPELL_TYPE_DD: + spell_list = &dd_spells; + break; + case SPELL_TYPE_DOT: + spell_list = &dot_spells; + break; + case SPELL_TYPE_HEAL: + spell_list = &heal_spells; + break; + case SPELL_TYPE_HOT_WARD: + spell_list = &hot_ward_spells; + break; + case SPELL_TYPE_DEBUFF: + spell_list = &debuff_spells; + break; + case SPELL_TYPE_BUFF: + spell_list = &buff_spells; + break; + case SPELL_TYPE_COMBATBUFF: + spell_list = &combat_buff_spells; + break; + case SPELL_TYPE_TAUNT: + spell_list = &taunt_spells; + break; + case SPELL_TYPE_DETAUNT: + spell_list = &detaunt_spells; + break; + case SPELL_TYPE_REZ: + LogWrite(PLAYER__ERROR, 0, "Bot", "Adding rez spell."); + spell_list = &rez_spells; + break; + case SPELL_TYPE_CURE: + spell_list = &cure_spells; + break; + default: + spell_list = 0; + break; + } + if (spell_list && spell_list->count((*itr)->GetSpellID()) == 0) + (*spell_list)[(*itr)->GetSpellID()] = 1; + } + + safe_delete(spells1); + safe_delete(spells2); + safe_delete(spells3); +} + +Entity* Bot::GetCombatTarget() { + Spawn* target = GetZone()->GetSpawnByID(combat_target); + if (target && target->IsEntity()) + return (Entity*)target; + + combat_target = 0; + return 0; +} + +Spell* Bot::SelectSpellToCast(float distance) { + Spell* spell = 0; + map::iterator itr; + + // Heal + spell = GetHealSpell(); + if (spell) + return spell; + + // Taunt + spell = GetTauntSpell(); + if (spell) + return spell; + + // Detaunt + spell = GetDetauntSpell(); + if (spell) + return spell; + + // Hot/Ward + spell = GetHoTWardSpell(); + if (spell) + return spell; + + // Debuff + spell = GetDebuffSpell(); + if (spell) + return spell; + + // Combat Buff + spell = GetCombatBuffSpell(); + if (spell) + return spell; + + // DoT + spell = GetDoTSpell(); + if (spell) + return spell; + + // DD + spell = GetDDSpell(); + if (spell) + return spell; + + return 0; +} + +void Bot::SetRecast(Spell* spell, int32 time) { + recast_times[spell->GetSpellID()] = time; +} + +bool Bot::IsSpellReady(Spell* spell) { + if (recast_times.count(spell->GetSpellID()) > 0) { + if (recast_times[spell->GetSpellID()] > Timer::GetCurrentTime2()) + return false; + } + + return true; +} + +Spell* Bot::GetDDSpell() { + if (dd_spells.size() == 0) + return 0; + + Spell* spell = 0; + map::iterator itr; + for (itr = dd_spells.begin(); itr != dd_spells.end(); itr++) { + spell = master_spell_list.GetSpell(itr->first, itr->second); + if (spell && IsSpellReady(spell)) { + return spell; + } + } + + return 0; +} + +Spell* Bot::GetHealSpell() { + if (heal_spells.size() == 0) + return 0; + + // Get an available heal spell + Spell* spell = 0; + map::iterator itr; + for (itr = heal_spells.begin(); itr != heal_spells.end(); itr++) { + spell = master_spell_list.GetSpell(itr->first, itr->second); + if (spell && IsSpellReady(spell)) { + break; + } + } + + // No heal available, return out + if (!spell) + return 0; + + + // There was a heal spell so find a group member that needs healing + int8 threshold = GetHealThreshold(); + GroupMemberInfo* gmi = GetGroupMemberInfo(); + PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + for (int8 i = 0; i < members->size(); i++) { + Entity* member = members->at(i)->member; + if(!member) + continue; + + if (!member->Alive()) + continue; + + int8 percent = (int8)(((float)member->GetHP() / member->GetTotalHP()) * 100); + if (percent <= threshold) { + if (spell) { + SetTarget(member); + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + return spell; + } + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + + return 0; +} + +Spell* Bot::GetTauntSpell() { + if (taunt_spells.size() == 0) + return 0; + + // If not the main tank and taunts are turned off return out + if (main_tank != this && !CanTaunt) + return 0; + + Spell* spell = 0; + map::iterator itr; + for (itr = taunt_spells.begin(); itr != taunt_spells.end(); itr++) { + spell = master_spell_list.GetSpell(itr->first, itr->second); + if (spell && IsSpellReady(spell)) { + return spell; + } + } + + return 0; +} + +Spell* Bot::GetDetauntSpell() { + if (detaunt_spells.size() == 0) + return 0; + + if (!GetTarget() || !GetTarget()->IsNPC()) + return 0; + + NPC* target = (NPC*)GetTarget(); + Entity* hated = target->Brain()->GetMostHated(); + if (hated == this) { + Spell* spell = 0; + map::iterator itr; + for (itr = detaunt_spells.begin(); itr != detaunt_spells.end(); itr++) { + spell = master_spell_list.GetSpell(itr->first, itr->second); + if (spell && IsSpellReady(spell)) { + return spell; + } + } + } + + return 0; +} + +Spell* Bot::GetHoTWardSpell() { + if (hot_ward_spells.size() == 0) + return 0; + + // Get an available spell + Spell* spell = 0; + map::iterator itr; + for (itr = hot_ward_spells.begin(); itr != hot_ward_spells.end(); itr++) { + spell = master_spell_list.GetSpell(itr->first, itr->second); + if (spell && IsSpellReady(spell)) { + break; + } + } + + // No spell available, return out + if (!spell) + return 0; + + // There was a spell so find a group member that needs healing + int8 threshold = GetHealThreshold(); + GroupMemberInfo* gmi = GetGroupMemberInfo(); + PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + + for (int8 i = 0; i < members->size(); i++) { + Entity* member = members->at(i)->member; + + if(!member) + continue; + + int8 percent = 0; + if (member->GetHP() > 0) + percent = (int8)(((float)member->GetHP() / member->GetTotalHP()) * 100); + + if (percent <= 99 && percent > threshold) { + if (spell) { + SetTarget(member); + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + return spell; + } + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + return 0; +} +Spell* Bot::GetDebuffSpell() { + if (debuff_spells.size() == 0) + return 0; + + Spell* spell = 0; + map::iterator itr; + for (itr = debuff_spells.begin(); itr != debuff_spells.end(); itr++) { + spell = master_spell_list.GetSpell(itr->first, itr->second); + if (spell && IsSpellReady(spell)) { + // If target already has this effect on them then continue to the next spell + if (((Entity*)GetTarget())->GetSpellEffect(itr->first)) + continue; + + return spell; + } + } + + return 0; +} + +Spell* Bot::GetCombatBuffSpell() { + return 0; +} + +Spell* Bot::GetDoTSpell() { + if (dot_spells.size() == 0) + return 0; + + Spell* spell = 0; + map::iterator itr; + for (itr = dot_spells.begin(); itr != dot_spells.end(); itr++) { + spell = master_spell_list.GetSpell(itr->first, itr->second); + if (spell && IsSpellReady(spell)) { + // If target already has this effect on them then continue to the next spell + if (((Entity*)GetTarget())->GetSpellEffect(itr->first)) + continue; + + return spell; + } + } + + return 0; +} + +Spell* Bot::GetBuffSpell() { + if (buff_spells.size() == 0) + return 0; + + Spell* spell = 0; + Entity* target = 0; + map::iterator itr; + for (itr = buff_spells.begin(); itr != buff_spells.end(); itr++) { + spell = master_spell_list.GetSpell(itr->first, itr->second); + if (spell && IsSpellReady(spell)) { + target = 0; + + if (spell->GetSpellData()->target_type == SPELL_TARGET_SELF) + target = this; + if (spell->GetSpellData()->target_type == SPELL_TARGET_GROUP_AE) + target = this; + if (spell->GetSpellData()->target_type == SPELL_TARGET_ENEMY && spell->GetSpellData()->friendly_spell == 1) + target = (main_tank != NULL) ? main_tank : GetOwner(); + if (!target) + continue; + if (!target->Alive()) + continue; + + // If target already has this effect on them then continue to the next spell + if (target->GetSpellEffect(itr->first)) + continue; + + SetTarget(target); + return spell; + } + } + + return 0; +} + +Spell* Bot::GetRezSpell() { + if (rez_spells.size() == 0) + return 0; + + GroupMemberInfo* gmi = GetGroupMemberInfo(); + if (!gmi) + return 0; + + Entity* target = 0; + PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + for (int8 i = 0; i < members->size(); i++) { + Entity* member = members->at(i)->member; + if (member && !member->Alive() && member->IsPlayer()) { + PendingResurrection* rez = members->at(i)->client->GetCurrentRez(); + if (rez->active) + continue; + + target = member; + break; + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + if (!target) + return 0; + + Spell* spell = 0; + map::iterator itr; + for (itr = rez_spells.begin(); itr != rez_spells.end(); itr++) { + spell = master_spell_list.GetSpell(itr->first, itr->second); + if (spell && IsSpellReady(spell)) { + SetTarget(target); + return spell; + } + } + + return 0; +} + +Spell* Bot::GetCureSpell() { + return 0; +} + +int8 Bot::GetHealThreshold() { + int8 ret = 0; + + switch (GetAdventureClass()) { + case PRIEST: + case CLERIC: + case TEMPLAR: + case INQUISITOR: + case DRUID: + case WARDEN: + case FURY: + case SHAMAN: + case MYSTIC: + case DEFILER: + ret = 70; + break; + default: + ret = 30; + break; + } + + return ret; +} + +bool Bot::ShouldMelee() { + bool ret = true; + + switch (GetAdventureClass()) { + case PRIEST: + case CLERIC: + case TEMPLAR: + case INQUISITOR: + case DRUID: + case WARDEN: + case FURY: + case SHAMAN: + case MYSTIC: + case DEFILER: + case MAGE: + case SORCERER: + case WIZARD: + case WARLOCK: + case ENCHANTER: + case ILLUSIONIST: + case COERCER: + case SUMMONER: + case CONJUROR: + case NECROMANCER: + ret = false; + break; + default: + ret = true; + break; + } + + if (GetTarget() == GetOwner()) + ret = false; + + return ret; +} + +void Bot::Camp(bool immediate) { + // Copy from COMMAND_GROUP_LEAVE + camping = true; + immediate_camp = immediate; +} + +void Bot::ChangeLevel(int16 old_level, int16 new_level) { + if (new_level < 1) + return; + + if (GetLevel() != new_level) { + SetLevel(new_level); + if (GetGroupMemberInfo()) { + UpdateGroupMemberInfo(); + world.GetGroupManager()->SendGroupUpdate(GetGroupMemberInfo()->group_id); + } + } + + if (GetPet()) { + NPC* pet = (NPC*)GetPet(); + if (pet->GetMaxPetLevel() == 0 || new_level <= pet->GetMaxPetLevel()) { + pet->SetLevel(new_level); + GetZone()->PlayAnimation(pet, 1753); + } + } + + // level up animation + GetZone()->PlayAnimation(this, 1753); + + //player->GetSkills()->IncreaseAllSkillCaps(5 * (new_level - old_level)); + GetNewSpells(); + //SendNewSpells(player->GetAdventureClass()); + //SendNewSpells(classes.GetBaseClass(player->GetAdventureClass())); + //SendNewSpells(classes.GetSecondaryBaseClass(player->GetAdventureClass())); + + GetInfoStruct()->set_level(new_level); + UpdateWeapons(); + // GetPlayer()->SetLevel(new_level); + + LogWrite(MISC__TODO, 1, "TODO", "Get new HP/POWER/stat based on default values from DB\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__); + + SetTotalHPBase(new_level*new_level * 2 + 40); + SetTotalPowerBase((sint32)(new_level*new_level*2.1 + 45)); + CalculateBonuses(); + SetHP(GetTotalHP()); + SetPower(GetTotalPower()); + + GetInfoStruct()->set_agi_base(new_level * 2 + 15); + GetInfoStruct()->set_intel_base(new_level * 2 + 15); + GetInfoStruct()->set_wis_base(new_level * 2 + 15); + GetInfoStruct()->set_str_base(new_level * 2 + 15); + GetInfoStruct()->set_sta_base(new_level * 2 + 15); + GetInfoStruct()->set_cold_base((int16)(new_level*1.5 + 10)); + GetInfoStruct()->set_heat_base((int16)(new_level*1.5 + 10)); + GetInfoStruct()->set_disease_base((int16)(new_level*1.5 + 10)); + GetInfoStruct()->set_mental_base((int16)(new_level*1.5 + 10)); + GetInfoStruct()->set_magic_base((int16)(new_level*1.5 + 10)); + GetInfoStruct()->set_divine_base((int16)(new_level*1.5 + 10)); + GetInfoStruct()->set_poison_base((int16)(new_level*1.5 + 10)); + /*UpdateTimeStampFlag(LEVEL_UPDATE_FLAG); + GetPlayer()->SetCharSheetChanged(true); + + Message(CHANNEL_COLOR_EXP, "You are now level %i!", new_level); + LogWrite(WORLD__DEBUG, 0, "World", "Player: %s leveled from %u to %u", GetPlayer()->GetName(), old_level, new_level); + GetPlayer()->GetSkills()->SetSkillCapsByType(1, 5 * new_level); + GetPlayer()->GetSkills()->SetSkillCapsByType(3, 5 * new_level); + GetPlayer()->GetSkills()->SetSkillCapsByType(6, 5 * new_level); + GetPlayer()->GetSkills()->SetSkillCapsByType(13, 5 * new_level); + */ +} + +void Bot::Begin_Camp() { + GroupMemberInfo* gmi = GetGroupMemberInfo(); + if (gmi) { + int32 group_id = gmi->group_id; + world.GetGroupManager()->RemoveGroupMember(group_id, this); + if (!world.GetGroupManager()->IsGroupIDValid(group_id)) { + // leader->Message(CHANNEL_COLOR_GROUP, "%s has left the group.", client->GetPlayer()->GetName()); + } + else { + world.GetGroupManager()->GroupMessage(group_id, "%s has left the group.", GetName()); + } + } + + if(!immediate_camp) + { + GetZone()->PlayAnimation(this, 538); + SetVisualState(540); + GetZone()->Despawn(this, 5000); + } + + if (!GetOwner()) + return; + + if (GetOwner()->IsPlayer()) + ((Player*)GetOwner())->SpawnedBots.erase(BotIndex); + + camping = false; + immediate_camp = true; +} \ No newline at end of file diff --git a/source/WorldServer/Bots/Bot.h b/source/WorldServer/Bots/Bot.h new file mode 100644 index 0000000..a6008bf --- /dev/null +++ b/source/WorldServer/Bots/Bot.h @@ -0,0 +1,95 @@ +#pragma once + +#include "../NPC.h" +#include + +struct TradeItemInfo; + +class Bot : public NPC { +public: + Bot(); + ~Bot(); + + int32 BotID; // DB id + int32 BotIndex; // Bot id with its owner (player) + bool IsBot() { return true; } + + void GiveItem(int32 item_id); + void GiveItem(Item* item); + void RemoveItem(Item* item); + void TradeItemAdded(Item* item); + void AddItemToTrade(int8 slot); + bool CheckTradeItems(map* list); + void FinishTrade(); + void GetNewSpells(); + map* GetBotSpells() { return &dd_spells; } + + bool ShowHelm; + bool ShowCloak; + bool CanTaunt; + + Entity* GetCombatTarget(); + void SetCombatTarget(int32 target) { combat_target = target; } + + Spell* SelectSpellToCast(float distance); + + void MessageGroup(string msg); + + void SetRecast(Spell* spell, int32 time); + bool ShouldMelee(); + + Spell* GetNextBuffSpell(Spawn* target = 0) { return GetBuffSpell(); } + Spell* GetHealSpell(); + Spell* GetRezSpell(); + + void SetMainTank(Entity* tank) { main_tank = tank; } + + void Camp(bool immediate=false); + void ChangeLevel(int16 old_level, int16 new_level); + + bool IsCamping() { return camping; } + bool IsImmediateCamp() { return immediate_camp; } + void Begin_Camp(); +private: + bool CanEquipItem(Item* item); + bool IsSpellReady(Spell* spell); + + + Spell* GetTauntSpell(); + Spell* GetDetauntSpell(); + Spell* GetHoTWardSpell(); + Spell* GetDebuffSpell(); + Spell* GetCombatBuffSpell(); + Spell* GetDoTSpell(); + Spell* GetDDSpell(); + + Spell* GetBuffSpell(); + Spell* GetCureSpell(); + + + int8 GetHealThreshold(); + + set trading_slots; + int32 combat_target; + + Entity* main_tank; + + map bot_spells; + map dd_spells; + map dot_spells; + map heal_spells; + map hot_ward_spells; + map debuff_spells; + map buff_spells; + map combat_buff_spells; + map taunt_spells; + map detaunt_spells; + map rez_spells; + map cure_spells; + + // First int32 = spell id (change to timer id later), second int32 is time the spell is available to cast again + map recast_times; + std::atomic camping; + std::atomic immediate_camp; + +}; diff --git a/source/WorldServer/Bots/BotBrain.cpp b/source/WorldServer/Bots/BotBrain.cpp new file mode 100644 index 0000000..69d19b0 --- /dev/null +++ b/source/WorldServer/Bots/BotBrain.cpp @@ -0,0 +1,205 @@ +#include "BotBrain.h" +#include "../Combat.h" +#include "../Spells.h" +#include "../../common/Log.h" +#include "../Rules/Rules.h" + +extern RuleManager rule_manager; + +BotBrain::BotBrain(Bot* body) : Brain(body) { + Body = body; +} + +BotBrain::~BotBrain() { + +} + +void BotBrain::Think() { + // No ownder do nothing, probably despawn as owner should never be empty for bots + if (!m_body->GetOwner()) + return; + + // Not in a group yet then do nothing + if (!m_body->GetGroupMemberInfo()) + return; + + if (!Body->Alive()) + return; + + if (Body->IsMezzedOrStunned()) + return; + + // If combat was processed we can return out + if (ProcessCombat()) + return; + + // Combat failed to process so do out of combat tasks like follow the player + if (ProcessOutOfCombatSpells()) + return; + + // put htis here so bots don't try to follow the owner while in combat + if (Body->EngagedInCombat()) + return; + + // Set target to owner + Spawn* target = GetBody()->GetFollowTarget(); + + if(target) + { + // Get distance from the owner + float distance = GetBody()->GetDistance(target); + + // If out of melee range then move closer + if (distance > rule_manager.GetGlobalRule(R_Combat, MaxCombatRange)->GetFloat()) + MoveCloser(target); + } +} + +bool BotBrain::ProcessCombat() { + SetTarget(); + + if (Body->GetTarget() && Body->EngagedInCombat()) { + if (Body->GetTarget() && Body->GetTarget()->IsEntity() && Body->AttackAllowed((Entity*)Body->GetTarget())) { + Entity* target = (Entity*)Body->GetTarget(); + float distance = Body->GetDistance(target); + + if (!ProcessSpell(target, distance)) { + if (Body->ShouldMelee()) + ProcessMelee(target, distance); + } + + NPC* pet = (NPC*)Body->GetPet(); + if (pet) { + if (pet->Brain()->GetHate(target) == 0) + pet->AddHate(target, 1); + } + } + + return true; + } + + return false; +} + +void BotBrain::SetTarget() { + // The target issued from /bot attack + if (Body->GetCombatTarget() && Body->GetCombatTarget()->Alive()) { + Body->SetTarget(Body->GetCombatTarget()); + Body->InCombat(true); + return; + } + + // Assist + Entity* owner = Body->GetOwner(); + if (owner && owner->EngagedInCombat()) { + if (owner->GetTarget() && owner->GetTarget()->IsEntity() && owner->GetTarget()->Alive() && owner->AttackAllowed((Entity*)owner->GetTarget())) { + Body->SetTarget(owner->GetTarget()); + Body->InCombat(true); + // Add some hate to keep the bot attacking if + // the player toggles combat off + if (GetHate((Entity*)Body->GetTarget()) == 0) + AddHate((Entity*)Body->GetTarget(), 1); + return; + } + } + + // Most hated + Entity* hated = GetMostHated(); + if (hated && hated->Alive()) { + if (hated == Body->GetOwner()) { + ClearHate(hated); + } + else { + Body->SetTarget(hated); + Body->InCombat(true); + return; + } + } + + // None of the above true so clear target and turn combat off + Body->SetTarget(0); + Body->InCombat(false); +} + +bool BotBrain::ProcessSpell(Entity* target, float distance) { + if (Body->IsStifled() || Body->IsFeared()) + return false; + + if (Body->IsCasting()) + return false; + + if (!HasRecovered()) + return false; + + Spell* spell = Body->SelectSpellToCast(distance); + if (spell) { + // Target can change (heals for example) so recalculate distance and if out of range move closer + float distance = Body->GetDistance(Body->GetTarget()); + if (distance > spell->GetSpellData()->range) { + if (Body->GetTarget()->IsEntity()) + MoveCloser((Spawn*)Body->GetTarget()); + } + else { + // stop movement if spell can't be cast while moving + if (!spell->GetSpellData()->cast_while_moving) + Body->CalculateRunningLocation(true); + + Body->GetZone()->ProcessSpell(spell, Body, Body->GetTarget()); + m_spellRecovery = (int32)(Timer::GetCurrentTime2() + (spell->GetSpellData()->cast_time * 10) + (spell->GetSpellData()->recovery * 10) + 2000); + // recast time + int32 time = Timer::GetCurrentTime2() + (spell->CalculateRecastTimer(Body)); + Body->SetRecast(spell, time); + + string str = "I am casting "; + str += spell->GetName(); + Body->MessageGroup(str); + } + return true; + } + + return false; +} + +bool BotBrain::ProcessOutOfCombatSpells() { + if (Body->IsStifled() || Body->IsFeared()) + return false; + + if (Body->IsCasting()) + return false; + + if (!HasRecovered()) + return false; + + Spell* spell = Body->GetHealSpell(); + if (!spell) + spell = Body->GetRezSpell(); + if (!spell) + spell = Body->GetNextBuffSpell(); + + if (spell) { + // stop movement if spell can't be cast while moving + if (!spell->GetSpellData()->cast_while_moving) + Body->CalculateRunningLocation(true); + + // See if we are in range of target, if not move closer + float distance = Body->GetDistance(Body->GetTarget()); + if (distance > spell->GetSpellData()->range) { + if (Body->GetTarget()->IsEntity()) + MoveCloser((Spawn*)Body->GetTarget()); + } + else { + Body->GetZone()->ProcessSpell(spell, Body, Body->GetTarget()); + m_spellRecovery = (int32)(Timer::GetCurrentTime2() + (spell->GetSpellData()->cast_time * 10) + (spell->GetSpellData()->recovery * 10) + 2000); + // recast time + int32 time = Timer::GetCurrentTime2() + (spell->CalculateRecastTimer(Body)); + Body->SetRecast(spell, time); + + string str = "I am casting "; + str += spell->GetName(); + Body->MessageGroup(str); + } + return true; + } + + return false; +} \ No newline at end of file diff --git a/source/WorldServer/Bots/BotBrain.h b/source/WorldServer/Bots/BotBrain.h new file mode 100644 index 0000000..aa8911f --- /dev/null +++ b/source/WorldServer/Bots/BotBrain.h @@ -0,0 +1,20 @@ +#pragma once + +#include "../NPC_AI.h" +#include "Bot.h" + +class BotBrain : public Brain { +public: + BotBrain(Bot* body); + virtual ~BotBrain(); + void Think(); + bool ProcessSpell(Entity* target, float distance); + + bool ProcessOutOfCombatSpells(); + +private: + Bot* Body; + + bool ProcessCombat(); + void SetTarget(); +}; \ No newline at end of file diff --git a/source/WorldServer/Bots/BotCommands.cpp b/source/WorldServer/Bots/BotCommands.cpp new file mode 100644 index 0000000..494edc0 --- /dev/null +++ b/source/WorldServer/Bots/BotCommands.cpp @@ -0,0 +1,830 @@ +#include "../Commands/Commands.h" +#include "../WorldDatabase.h" +#include "../classes.h" +#include "../races.h" +#include "../Bots/Bot.h" +#include "../../common/Log.h" +#include "../Trade.h" +#include "../PlayerGroups.h" +#include "../World.h" +#include "../../common/GlobalHeaders.h" + +extern WorldDatabase database; +extern ConfigReader configReader; +extern World world; +extern MasterSpellList master_spell_list; + +void Commands::Command_Bot(Client* client, Seperator* sep) { + + if (sep && sep->IsSet(0)) { + if (strncasecmp("camp", sep->arg[0], 4) == 0) { + if (!client->GetPlayer()->GetTarget() || !client->GetPlayer()->GetTarget()->IsBot()) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must target a bot"); + return; + } + + Bot* bot = (Bot*)client->GetPlayer()->GetTarget(); + if (bot->GetOwner() != client->GetPlayer()) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You can only camp your own bots."); + return; + } + + bot->Camp(); + return; + } + else if (strncasecmp("attack", sep->arg[0], 6) == 0) { + if (client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsEntity() && client->GetPlayer()->GetTarget()->Alive()) { + Entity* target = (Entity*)client->GetPlayer()->GetTarget(); + + if (client->GetPlayer()->GetDistance(target) <= 50) { + if (client->GetPlayer()->AttackAllowed(target)) { + GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo(); + if (gmi) { + PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + deque::iterator itr; + + for (itr = members->begin(); itr != members->end(); itr++) { +//devn00b compile says this is no good, commenting out for now. +//if(!member) +// continue; + if ((*itr)->member && (*itr)->member->IsBot() && ((Bot*)(*itr)->member)->GetOwner() == client->GetPlayer()) { + + ((Bot*)(*itr)->member)->SetCombatTarget(target->GetID()); + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + } + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You can not attack that target."); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Target is to far away."); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Not a valid target."); + + return; + } + else if (strncasecmp("spells", sep->arg[0], 6) == 0) { + if (client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsBot()) { + Bot* bot = (Bot*)client->GetPlayer()->GetTarget(); + + map* spells = bot->GetBotSpells(); + map::iterator itr; + string output; + for (itr = spells->begin(); itr != spells->end(); itr++) { + Spell* spell = master_spell_list.GetSpell(itr->first, itr->second); + if (spell) { + output += spell->GetName(); + output += "\n"; + } + } + + client->SimpleMessage(CHANNEL_COLOR_YELLOW, output.c_str()); + return; + } + } + else if (strncasecmp("maintank", sep->arg[0], 8) == 0) { + if (!client->GetPlayer()->GetTarget() || !client->GetPlayer()->GetTarget()->IsEntity()) { + client->SimpleMessage(CHANNEL_COMMAND_TEXT, "Not a valid target."); + return; + } + + GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo(); + if (!gmi) { + client->SimpleMessage(CHANNEL_COMMAND_TEXT, "You are not in a group."); + return; + } + + Entity* target = (Entity*)client->GetPlayer()->GetTarget(); + if (!world.GetGroupManager()->IsInGroup(gmi->group_id, target)) { + client->SimpleMessage(CHANNEL_COMMAND_TEXT, "Target is not in your group."); + return; + } + + PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + + for (int8 i = 0; i < members->size(); i++) { + GroupMemberInfo* gmi2 = members->at(i); + if(!gmi2 || !gmi2->member) + continue; + if (gmi2->member->IsBot() && ((Bot*)gmi2->member)->GetOwner() == client->GetPlayer()) { + ((Bot*)gmi2->member)->SetMainTank(target); + client->Message(CHANNEL_COMMAND_TEXT, "Setting main tank for %s to %s", gmi2->member->GetName(), target->GetName()); + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + return; + } + else if (strncasecmp("delete", sep->arg[0], 6) == 0) { + if (sep->IsSet(1) && sep->IsNumber(1)) { + int32 index = atoi(sep->arg[1]); + + // Check if bot is currently spawned and if so camp it out + if (client->GetPlayer()->SpawnedBots.count(index) > 0) { + Spawn* bot = client->GetCurrentZone()->GetSpawnByID(client->GetPlayer()->SpawnedBots[index]); + if (bot && bot->IsBot()) + ((Bot*)bot)->Camp(); + } + + database.DeleteBot(client->GetCharacterID(), index); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Bot has been deleted."); + return; + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must give the id (from /bot list) to delete a bot"); + return; + } + } + else if (strncasecmp("follow", sep->arg[0], 6) == 0) { + if (sep->IsSet(1) && sep->IsNumber(1)) { + int32 index = atoi(sep->arg[1]); + + // Check if bot is currently spawned and if so camp it out + if (client->GetPlayer()->SpawnedBots.count(index) > 0) { + Spawn* bot = client->GetCurrentZone()->GetSpawnByID(client->GetPlayer()->SpawnedBots[index]); + if (bot && bot->IsBot()) + ((Bot*)bot)->SetFollowTarget(client->GetPlayer(), 5); + } + return; + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must give the id (from /bot list) to have a bot follow you"); + return; + } + } + else if (strncasecmp("stopfollow", sep->arg[0], 10) == 0) { + if (sep->IsSet(1) && sep->IsNumber(1)) { + int32 index = atoi(sep->arg[1]); + + // Check if bot is currently spawned and if so camp it out + if (client->GetPlayer()->SpawnedBots.count(index) > 0) { + Spawn* bot = client->GetCurrentZone()->GetSpawnByID(client->GetPlayer()->SpawnedBots[index]); + if (bot && bot->IsBot()) + ((Bot*)bot)->SetFollowTarget(nullptr); + } + return; + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must give the id (from /bot list) to stop a following bot"); + return; + } + } + else if (strncasecmp("summon", sep->arg[0], 6) == 0) { + if (sep->IsSet(1) && strncasecmp("group", sep->arg[1], 5) == 0) { + GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo(); + if (gmi) { + Player* player = client->GetPlayer(); + PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + for (int8 i = 0; i < members->size(); i++) { + Entity* member = members->at(i)->member; + + if(!member) + continue; + + if (member->IsBot() && ((Bot*)member)->GetOwner() == player) { + if(member->GetZone() && member->GetLocation() != player->GetLocation()) { + member->SetLocation(player->GetLocation()); + } + member->SetX(player->GetX()); + member->SetY(player->GetY()); + member->SetZ(player->GetZ()); + client->Message(CHANNEL_COLOR_YELLOW, "Summoning %s.", member->GetName()); + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + return; + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You are not in a group."); + return; + } + } + else { + if (client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsBot()) { + Bot* bot = (Bot*)client->GetPlayer()->GetTarget(); + Player* player = client->GetPlayer(); + if (bot && bot->GetOwner() == player) { + bot->SetLocation(player->GetLocation()); + bot->SetX(player->GetX()); + bot->SetY(player->GetY()); + bot->SetZ(player->GetZ()); + client->Message(CHANNEL_COLOR_YELLOW, "Summoning %s.", bot->GetName()); + return; + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You can only summon your own bots."); + return; + } + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must target a bot."); + return; + } + } + } + else if (strncasecmp("test", sep->arg[0], 4) == 0) { + if (client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsBot()) { + ((Bot*)client->GetPlayer()->GetTarget())->MessageGroup("Test message"); + return; + } + } + } + + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "BotCommands:"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot create [race] [gender] [class] [name]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot customize - customize the appearance of the bot"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot list - list all the bots you have created with this character"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot spawn [id] - spawns a bot into the world, id obtained from /bot list"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot inv [give/list/remove] - manage bot equipment, for remove a slot must be provided"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot settings [helm/hood/cloak/taunt] [0/1] - Turn setting on (1) or off(0)"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot camp - removes the bot from your group and despawns them"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot attack - commands your bots to attack your target"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot spells - lists bot spells, not fully implemented yet"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot maintank - sets targeted group member as the main tank for your bots"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot delete [id] - deletes the bot with the given id (obtained from /bot list)"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot help"); +} + +void Commands::Command_Bot_Create(Client* client, Seperator* sep) { + int8 race = BARBARIAN; + int8 gender = 0; + int8 advClass = GUARDIAN; + string name; + + if (sep) { + if (sep->IsSet(0) && sep->IsNumber(0)) + race = atoi(sep->arg[0]); + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "First param of \"/bot create\" needs to be a number"); + return; + } + + if (sep->IsSet(1) && sep->IsNumber(1)) + gender = atoi(sep->arg[1]); + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Second param of \"/bot create\" needs to be a number"); + return; + } + + if (sep->IsSet(2) && sep->IsNumber(2)) + advClass = atoi(sep->arg[2]); + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Third param of \"/bot create\" needs to be a number"); + return; + } + + if (sep->IsSet(3)) { + name = string(sep->arg[3]); + transform(name.begin(), name.begin() + 1, name.begin(), ::toupper); + transform(name.begin() + 1, name.end(), name.begin() + 1, ::tolower); + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Fourth param (name) of \"/bot create\" is required"); + return; + } + + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Syntax: /bot create [race ID] [Gender ID] [class ID] [name]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "All parameters are required. /bot help race or /bot help class for ID's."); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Gender ID's: 0 = Female, 1 = Male"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Ex: /bot create 0 0 3 Botty"); + return; + } + + int8 result = database.CheckNameFilter(name.c_str()); + if (result == BADNAMELENGTH_REPLY) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Name length is invalid, must be greater then 3 characters and less then 16."); + return; + } + else if (result == NAMEINVALID_REPLY) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Name is invalid, can only contain letters."); + return; + } + else if (result == NAMETAKEN_REPLY) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Name is already taken, please choose another."); + return; + } + else if (result == NAMEFILTER_REPLY) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Name failed the filter check."); + return; + } + else if (result == UNKNOWNERROR_REPLY) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Unknown error while checking the name."); + return; + } + + string race_string; + switch (race) { + case BARBARIAN: + race_string = "/barbarian/barbarian"; + break; + case DARK_ELF: + race_string = "/darkelf/darkelf"; + break; + case DWARF: + race_string = "/dwarf/dwarf"; + break; + case ERUDITE: + race_string = "/erudite/erudite"; + break; + case FROGLOK: + race_string = "/froglok/froglok"; + break; + case GNOME: + race_string = "/gnome/gnome"; + break; + case HALF_ELF: + race_string = "/halfelf/halfelf"; + break; + case HALFLING: + race_string = "/halfling/halfling"; + break; + case HIGH_ELF: + race_string = "/highelf/highelf"; + break; + case HUMAN: + race_string = "/human/human"; + break; + case IKSAR: + race_string = "/iksar/iksar"; + break; + case KERRA: + race_string = "/kerra/kerra"; + break; + case OGRE: + race_string = "/ogre/ogre"; + break; + case RATONGA: + race_string = "/ratonga/ratonga"; + break; + case TROLL: + race_string = "/troll/troll"; + break; + case WOOD_ELF: + race_string = "/woodelf/woodelf"; + break; + case FAE: + race_string = "/fae/fae_light"; + break; + case ARASAI: + race_string = "/fae/fae_dark"; + break; + case SARNAK: + gender == 1 ? race_string = "01/sarnak_male/sarnak" : race_string = "01/sarnak_female/sarnak"; + break; + case VAMPIRE: + race_string = "/vampire/vampire"; + break; + case AERAKYN: + race_string = "/aerakyn/aerakyn"; + break; + } + + if (race_string.length() > 0) { + string gender_string; + Bot* bot = 0; + + gender == 1 ? gender_string = "male" : gender_string = "female"; + + vector* id_list = database.GetAppearanceIDsLikeName("ec/pc" + race_string + "_" + gender_string); + + if (id_list) { + bot = new Bot(); + memset(&bot->appearance, 0, sizeof(bot->appearance)); + bot->appearance.pos.collision_radius = 32; + bot->secondary_command_list_id = 0; + bot->primary_command_list_id = 0; + bot->appearance.display_name = 1; + bot->appearance.show_level = 1; + bot->appearance.attackable = 1; + bot->appearance.show_command_icon = 1; + bot->appearance.targetable = 1; + bot->appearance.race = race; + bot->appearance.gender = gender; + bot->SetID(Spawn::NextID()); + bot->SetX(client->GetPlayer()->GetX()); + bot->SetY(client->GetPlayer()->GetY()); + bot->SetZ(client->GetPlayer()->GetZ()); + bot->SetHeading(client->GetPlayer()->GetHeading()); + bot->SetSpawnOrigX(bot->GetX()); + bot->SetSpawnOrigY(bot->GetY()); + bot->SetSpawnOrigZ(bot->GetZ()); + bot->SetSpawnOrigHeading(bot->GetHeading()); + bot->SetLocation(client->GetPlayer()->GetLocation()); + bot->SetInitialState(16512); + bot->SetModelType(id_list->at(0)); + bot->SetAdventureClass(advClass); + bot->SetLevel(client->GetPlayer()->GetLevel()); + bot->SetName(name.c_str()); + bot->SetDifficulty(6); + bot->size = 32; + if (bot->GetTotalHP() == 0) { + bot->SetTotalHP(25 * bot->GetLevel() + 1); + bot->SetTotalHPBaseInstance(bot->GetTotalHP()); + bot->SetHP(25 * bot->GetLevel() + 1); + } + if (bot->GetTotalPower() == 0) { + bot->SetTotalPower(25 * bot->GetLevel() + 1); + bot->SetTotalPowerBaseInstance(bot->GetTotalPower()); + bot->SetPower(25 * bot->GetLevel() + 1); + } + bot->SetOwner(client->GetPlayer()); + bot->GetNewSpells(); + client->GetCurrentZone()->AddSpawn(bot); + + int32 index; + int32 bot_id = database.CreateNewBot(client->GetCharacterID(), name, race, advClass, gender, id_list->at(0), index); + if (bot_id == 0) { + LogWrite(PLAYER__ERROR, 0, "Player", "Error saving bot to DB. Bot was not saved!"); + client->SimpleMessage(CHANNEL_ERROR, "Error saving bot to DB. Bot was not saved!"); + } + else { + bot->BotID = bot_id; + bot->BotIndex = index; + client->GetPlayer()->SpawnedBots[bot->BotIndex] = bot->GetID(); + + // Add Items + database.SetBotStartingItems(bot, advClass, race); + } + } + else { + client->SimpleMessage(CHANNEL_COLOR_RED, "Error finding the id list for your race, please verify the race id."); + } + + safe_delete(id_list); + } + else + client->SimpleMessage(CHANNEL_COLOR_RED, "Error finding the race string, please verify the race id."); +} + +void Commands::Command_Bot_Customize(Client* client, Seperator* sep) { + Bot* bot = 0; + if (client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsBot()) + bot = (Bot*)client->GetPlayer()->GetTarget(); + + + client->Message(CHANNEL_COLOR_RED, "This command is disabled and requires new implementation."); + + /*if (bot && bot->GetOwner() == client->GetPlayer()) { + PacketStruct* packet = configReader.getStruct("WS_OpenCharCust", client->GetVersion()); + if (packet) { + + AppearanceData* botApp = &bot->appearance; + CharFeatures* botFeatures = &bot->features; + + AppearanceData* playerApp = &client->GetPlayer()->appearance; + CharFeatures* playerFeatures = &client->GetPlayer()->features; + + memcpy(&client->GetPlayer()->SavedApp, playerApp, sizeof(AppearanceData)); + memcpy(&client->GetPlayer()->SavedFeatures, playerFeatures, sizeof(CharFeatures)); + + client->GetPlayer()->custNPC = true; + client->GetPlayer()->custNPCTarget = bot; + memcpy(playerApp, botApp, sizeof(AppearanceData)); + memcpy(playerFeatures, botFeatures, sizeof(CharFeatures)); + client->GetPlayer()->changed = true; + client->GetPlayer()->info_changed = true; + client->GetCurrentZone()->SendSpawnChanges(client->GetPlayer(), client); + packet->setDataByName("race_id", 255); + client->QueuePacket(packet->serialize()); + } + }*/ +} + +void Commands::Command_Bot_Spawn(Client* client, Seperator* sep) { + + if (sep && sep->IsSet(0) && sep->IsNumber(0)) { + int32 bot_id = atoi(sep->arg[0]); + if (client->GetPlayer()->SpawnedBots.count(bot_id) > 0) { + client->Message(CHANNEL_COLOR_YELLOW, "The bot with id %u is already spawned.", bot_id); + return; + } + + Bot* bot = new Bot(); + memset(&bot->appearance, 0, sizeof(bot->appearance)); + + if (database.LoadBot(client->GetCharacterID(), bot_id, bot)) { + bot->SetFollowTarget(client->GetPlayer(), 5); + bot->appearance.pos.collision_radius = 32; + bot->secondary_command_list_id = 0; + bot->primary_command_list_id = 0; + bot->appearance.display_name = 1; + bot->appearance.show_level = 1; + bot->appearance.attackable = 1; + bot->appearance.show_command_icon = 1; + bot->appearance.targetable = 1; + bot->SetID(Spawn::NextID()); + bot->SetX(client->GetPlayer()->GetX()); + bot->SetY(client->GetPlayer()->GetY()); + bot->SetZ(client->GetPlayer()->GetZ()); + bot->SetHeading(client->GetPlayer()->GetHeading()); + bot->SetSpawnOrigX(bot->GetX()); + bot->SetSpawnOrigY(bot->GetY()); + bot->SetSpawnOrigZ(bot->GetZ()); + bot->SetSpawnOrigHeading(bot->GetHeading()); + bot->SetLocation(client->GetPlayer()->GetLocation()); + bot->SetInitialState(16512); + bot->SetLevel(client->GetPlayer()->GetLevel()); + bot->SetDifficulty(6); + bot->size = 32; + if (bot->GetTotalHP() == 0) { + bot->SetTotalHP(25 * bot->GetLevel() + 1); + bot->SetHP(25 * bot->GetLevel() + 1); + } + if (bot->GetTotalPower() == 0) { + bot->SetTotalPower(25 * bot->GetLevel() + 1); + bot->SetPower(25 * bot->GetLevel() + 1); + } + bot->SetOwner(client->GetPlayer()); + bot->UpdateWeapons(); + bot->CalculateBonuses(); + bot->GetNewSpells(); + client->GetCurrentZone()->AddSpawn(bot); + + if (sep->IsSet(1) && sep->IsNumber(1) && atoi(sep->arg[1]) == 1) { + client->GetCurrentZone()->SendSpawn(bot, client); + + int8 result = world.GetGroupManager()->Invite(client->GetPlayer(), bot); + + if (result == 0) + client->Message(CHANNEL_COMMANDS, "You invite %s to group with you.", bot->GetName()); + else if (result == 1) + client->SimpleMessage(CHANNEL_COMMANDS, "That player is already in a group."); + else if (result == 2) + client->SimpleMessage(CHANNEL_COMMANDS, "That player has been invited to another group."); + else if (result == 3) + client->SimpleMessage(CHANNEL_COMMANDS, "Your group is already full."); + else if (result == 4) + client->SimpleMessage(CHANNEL_COMMANDS, "You have a pending invitation, cancel it first."); + else if (result == 5) + client->SimpleMessage(CHANNEL_COMMANDS, "You cannot invite yourself!"); + else if (result == 6) + client->SimpleMessage(CHANNEL_COMMANDS, "Could not locate the player."); + else + client->SimpleMessage(CHANNEL_COMMANDS, "Group invite failed, unknown error!"); + } + + client->GetPlayer()->SpawnedBots[bot_id] = bot->GetID(); + } + else { + client->Message(CHANNEL_ERROR, "Error spawning bot (%u)", bot_id); + } + } + else { + Command_Bot(client, sep); + } +} + +void Commands::Command_Bot_List(Client* client, Seperator* sep) { + string bot_list; + bot_list = database.GetBotList(client->GetCharacterID()); + if (!bot_list.empty()) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, bot_list.c_str()); +} + +void Commands::Command_Bot_Inv(Client* client, Seperator* sep) { + if (sep && sep->IsSet(0)) { + if (strncasecmp("give", sep->arg[0], 4) == 0) { + if (client->GetPlayer()->trade) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You are already trading."); + return; + } + + if (!client->GetPlayer()->GetTarget() || !client->GetPlayer()->GetTarget()->IsBot()) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must target a bot"); + return; + } + + Bot* bot = (Bot*)client->GetPlayer()->GetTarget(); + if (bot->trade) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Bot is already in a trade..."); + return; + } + + if (bot->GetOwner() != client->GetPlayer()) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You can only trade with your own bot."); + return; + } + + Trade* trade = new Trade(client->GetPlayer(), bot); + client->GetPlayer()->trade = trade; + bot->trade = trade; + } + else if (strncasecmp("list", sep->arg[0], 4) == 0) { + if (!client->GetPlayer()->GetTarget() || !client->GetPlayer()->GetTarget()->IsBot()) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must target a bot"); + return; + } + + Bot* bot = (Bot*)client->GetPlayer()->GetTarget(); + if (bot->GetOwner() != client->GetPlayer()) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You can only see the inventory of your own bot."); + return; + } + + string item_list = "Bot Items:\nSlot\tName\n"; + for (int8 i = 0; i < NUM_SLOTS; i++) { + Item* item = bot->GetEquipmentList()->GetItem(i); + if (item) { + //\\aITEM %u %u:%s\\/a + item_list += to_string(i) + ":\t" + item->CreateItemLink(client->GetVersion(), true) + "\n"; + } + } + + client->SimpleMessage(CHANNEL_COLOR_YELLOW, item_list.c_str()); + } + else if (strncasecmp("remove", sep->arg[0], 6) == 0) { + if (sep->IsSet(1) && sep->IsNumber(1)) { + int8 slot = atoi(sep->arg[1]); + if (slot >= NUM_SLOTS) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Invalid slot"); + return; + } + + if (!client->GetPlayer()->GetTarget() || !client->GetPlayer()->GetTarget()->IsBot()) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must target a bot."); + return; + } + + Bot* bot = (Bot*)client->GetPlayer()->GetTarget(); + if (bot->GetOwner() != client->GetPlayer()) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You can only remove items from your own bot."); + return; + } + + + if (client->GetPlayer()->trade) { + Trade* trade = client->GetPlayer()->trade; + if (trade->GetTradee(client->GetPlayer()) != bot) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You are already in a trade."); + return; + } + + bot->AddItemToTrade(slot); + } + else { + if (bot->trade) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Your bot is already trading..."); + return; + } + + Trade* trade = new Trade(client->GetPlayer(), bot); + client->GetPlayer()->trade = trade; + bot->trade = trade; + + bot->AddItemToTrade(slot); + } + } + } + else + Command_Bot(client, sep); + } + else + Command_Bot(client, sep); +} + +void Commands::Command_Bot_Settings(Client* client, Seperator* sep) { + if (sep && sep->IsSet(0) && sep->IsSet(1) && sep->IsNumber(1)) { + if (client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsBot()) { + Bot* bot = (Bot*)client->GetPlayer()->GetTarget(); + if (bot->GetOwner() == client->GetPlayer()) { + if (strncasecmp("helm", sep->arg[0], 4) == 0) { + bot->ShowHelm = (atoi(sep->arg[1]) == 1) ? true : false; + bot->info_changed = true; + bot->changed = true; + bot->GetZone()->SendSpawnChanges(bot); + } + else if (strncasecmp("cloak", sep->arg[0], 5) == 0) { + bot->ShowCloak = (atoi(sep->arg[1]) == 1) ? true : false; + bot->info_changed = true; + bot->changed = true; + bot->GetZone()->SendSpawnChanges(bot); + } + else if (strncasecmp("taunt", sep->arg[0], 5) == 0) { + bot->CanTaunt = (atoi(sep->arg[1]) == 1) ? true : false; + } + else if (strncasecmp("hood", sep->arg[0], 4) == 0) { + bot->SetHideHood((atoi(sep->arg[0]) == 1) ? 0 : 1); + } + else + Command_Bot(client, sep); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You can only change settings on your own bot."); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must target a bot."); + } + else + Command_Bot(client, sep); +} + +void Commands::Command_Bot_Help(Client* client, Seperator* sep) { + if (sep && sep->IsSet(0)) { + if (strncasecmp("race", sep->arg[0], 4) == 0) { + string title = "Race ID's"; + string details; + details += "0\tBarbarian\n"; + details += "1\tDark Elf\n"; + details += "2\tDwarf\n"; + details += "3\tErudite\n"; + details += "4\tFroglok\n"; + details += "5\tGnome\n"; + details += "6\tHalf Elf\n"; + details += "7\tHalfling\n"; + details += "8\tHigh Elf\n"; + details += "9\tHuman\n"; + details += "10\tIksar\n"; + details += "11\tKerra\n"; + details += "12\tOgre\n"; + details += "13\tRatonga\n"; + details += "14\tTroll\n"; + details += "15\tWood Elf\n"; + details += "16\tFae\n"; + details += "17\tArasai\n"; + details += "18\tSarnak\n"; + details += "19\tVampire\n"; + details += "20\tAerakyn\n"; + client->SendShowBook(client->GetPlayer(), title, 0, 1, details); + return; + } + else if (strncasecmp("class", sep->arg[0], 5) == 0) { + string title = "Class ID's"; + string details; + details += "0\tCOMMONER\n"; + details += "1\tFIGHTER\n"; + details += "2\tWARRIOR\n"; + details += "3\tGUARDIAN\n"; + details += "4\tBERSERKER\n"; + details += "5\tBRAWLER\n"; + details += "6\tMONK\n"; + details += "7\tBRUISER\n"; + details += "8\tCRUSADER\n"; + details += "9\tSHADOWKNIGHT\n"; + details += "10\tPALADIN\n"; + details += "11\tPRIEST\n"; + details += "12\tCLERIC\n"; + details += "13\tTEMPLAR\n"; + details += "14\tINQUISITOR\n"; + details += "15\tDRUID\n"; + details += "16\tWARDEN\n"; + details += "17\tFURY\n"; + details += "18\tSHAMAN\n"; + details += "19\tMYSTIC\n"; + details += "20\tDEFILER\n"; + + string details2 = "21\tMAGE\n"; + details2 += "22\tSORCERER\n"; + details2 += "23\tWIZARD\n"; + details2 += "24\tWARLOCK\n"; + details2 += "25\tENCHANTER\n"; + details2 += "26\tILLUSIONIST\n"; + details2 += "27\tCOERCER\n"; + details2 += "28\tSUMMONER\n"; + details2 += "29\tCONJUROR\n"; + details2 += "30\tNECROMANCER\n"; + details2 += "31\tSCOUT\n"; + details2 += "32\tROGUE\n"; + details2 += "33\tSWASHBUCKLER\n"; + details2 += "34\tBRIGAND\n"; + details2 += "35\tBARD\n"; + details2 += "36\tTROUBADOR\n"; + details2 += "37\tDIRGE\n"; + details2 += "38\tPREDATOR\n"; + details2 += "39\tRANGER\n"; + details2 += "40\tASSASSIN\n"; + + string details3 = "\\#FF0000Following aren't implemented yet.\\#000000\n"; + details3 += "41\tANIMALIST\n"; + details3 += "42\tBEASTLORD\n"; + details3 += "43\tSHAPER\n"; + details3 += "44\tCHANNELER\n"; + + client->SendShowBook(client->GetPlayer(), title, 0, 3, details, details2, details3); + return; + } + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Bot help is WIP."); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot help race - race id list"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot help class - class id list"); + } +} diff --git a/source/WorldServer/Bots/BotDB.cpp b/source/WorldServer/Bots/BotDB.cpp new file mode 100644 index 0000000..86afc51 --- /dev/null +++ b/source/WorldServer/Bots/BotDB.cpp @@ -0,0 +1,467 @@ +#include "../WorldDatabase.h" +#include "../../common/Log.h" +#include "Bot.h" +#include "../classes.h" +#include "../races.h" + +extern Classes classes; +extern Races races; + +int32 WorldDatabase::CreateNewBot(int32 char_id, string name, int8 race, int8 advClass, int8 gender, int16 model_id, int32& index) { + DatabaseResult result; + index = 0; + + if (!database_new.Select(&result, "SELECT MAX(`bot_id`) FROM `bots` WHERE `char_id` = %u", char_id)) { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return 0; + } + + if (result.Next()) { + if (result.IsNull(0)) + index = 1; + else + index = result.GetInt32(0) + 1; + } + + if (!database_new.Query("INSERT INTO `bots` (`char_id`, `bot_id`, `name`, `race`, `class`, `gender`, `model_type`) VALUES (%u, %u, \"%s\", %u, %u, %u, %u)", char_id, index, name.c_str(), race, advClass, gender, model_id)) { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return 0; + } + int32 ret = database_new.LastInsertID(); + + LogWrite(PLAYER__DEBUG, 0, "Player", "New bot (%s) created for player (%u)", name.c_str(), char_id); + return ret; +} + +void WorldDatabase::SaveBotAppearance(Bot* bot) { + SaveBotColors(bot->BotID, "skin_color", bot->features.skin_color); + SaveBotColors(bot->BotID, "model_color", bot->features.model_color); + SaveBotColors(bot->BotID, "eye_color", bot->features.eye_color); + SaveBotColors(bot->BotID, "hair_color1", bot->features.hair_color1); + SaveBotColors(bot->BotID, "hair_color2", bot->features.hair_color2); + SaveBotColors(bot->BotID, "hair_highlight", bot->features.hair_highlight_color); + SaveBotColors(bot->BotID, "hair_type_color", bot->features.hair_type_color); + SaveBotColors(bot->BotID, "hair_type_highlight_color", bot->features.hair_type_highlight_color); + SaveBotColors(bot->BotID, "hair_face_color", bot->features.hair_face_color); + SaveBotColors(bot->BotID, "hair_face_highlight_color", bot->features.hair_face_highlight_color); + SaveBotColors(bot->BotID, "wing_color1", bot->features.wing_color1); + SaveBotColors(bot->BotID, "wing_color2", bot->features.wing_color2); + SaveBotColors(bot->BotID, "shirt_color", bot->features.shirt_color); + //SaveBotColors(bot->BotID, "unknown_chest_color", ); + SaveBotColors(bot->BotID, "pants_color", bot->features.pants_color); + //SaveBotColors(bot->BotID, "unknown_legs_color", ); + //SaveBotColors(bot->BotID, "unknown9", ); + SaveBotFloats(bot->BotID, "eye_type", bot->features.eye_type[0], bot->features.eye_type[1], bot->features.eye_type[2]); + SaveBotFloats(bot->BotID, "ear_type", bot->features.ear_type[0], bot->features.ear_type[1], bot->features.ear_type[2]); + SaveBotFloats(bot->BotID, "eye_brow_type", bot->features.eye_brow_type[0], bot->features.eye_brow_type[1], bot->features.eye_brow_type[2]); + SaveBotFloats(bot->BotID, "cheek_type", bot->features.cheek_type[0], bot->features.cheek_type[1], bot->features.cheek_type[2]); + SaveBotFloats(bot->BotID, "lip_type", bot->features.lip_type[0], bot->features.lip_type[1], bot->features.lip_type[2]); + SaveBotFloats(bot->BotID, "chin_type", bot->features.chin_type[0], bot->features.chin_type[1], bot->features.chin_type[2]); + SaveBotFloats(bot->BotID, "nose_type", bot->features.nose_type[0], bot->features.nose_type[1], bot->features.nose_type[2]); + + SaveBotFloats(bot->BotID, "body_size", bot->features.body_size, 0, 0); + SaveBotFloats(bot->BotID, "body_age", bot->features.body_age, 0, 0); + + SaveBotColors(bot->BotID, "soga_skin_color", bot->features.soga_skin_color); + SaveBotColors(bot->BotID, "soga_model_color", bot->features.soga_model_color); + SaveBotColors(bot->BotID, "soga_eye_color", bot->features.soga_eye_color); + SaveBotColors(bot->BotID, "soga_hair_color1", bot->features.soga_hair_color1); + SaveBotColors(bot->BotID, "soga_hair_color2", bot->features.soga_hair_color2); + SaveBotColors(bot->BotID, "soga_hair_highlight", bot->features.soga_hair_highlight_color); + SaveBotColors(bot->BotID, "soga_hair_type_color", bot->features.soga_hair_type_color); + SaveBotColors(bot->BotID, "soga_hair_type_highlight_color", bot->features.soga_hair_type_highlight_color); + SaveBotColors(bot->BotID, "soga_hair_face_color", bot->features.soga_hair_face_color); + SaveBotColors(bot->BotID, "soga_hair_face_highlight_color", bot->features.soga_hair_face_highlight_color); + SaveBotColors(bot->BotID, "soga_wing_color1", bot->features.wing_color1); + SaveBotColors(bot->BotID, "soga_wing_color2", bot->features.wing_color2); + SaveBotColors(bot->BotID, "soga_shirt_color", bot->features.shirt_color); + //SaveBotColors(bot->BotID, "soga_unknown_chest_color", ); + SaveBotColors(bot->BotID, "soga_pants_color", bot->features.pants_color); + //SaveBotColors(bot->BotID, "soga_unknown_legs_color", ); + //SaveBotColors(bot->BotID, "soga_unknown13", ); + SaveBotFloats(bot->BotID, "soga_eye_type", bot->features.soga_eye_type[0], bot->features.soga_eye_type[1], bot->features.soga_eye_type[2]); + SaveBotFloats(bot->BotID, "soga_ear_type", bot->features.soga_ear_type[0], bot->features.soga_ear_type[1], bot->features.soga_ear_type[2]); + SaveBotFloats(bot->BotID, "soga_eye_brow_type", bot->features.soga_eye_brow_type[0], bot->features.soga_eye_brow_type[1], bot->features.soga_eye_brow_type[2]); + SaveBotFloats(bot->BotID, "soga_cheek_type", bot->features.soga_cheek_type[0], bot->features.soga_cheek_type[1], bot->features.soga_cheek_type[2]); + SaveBotFloats(bot->BotID, "soga_lip_type", bot->features.soga_lip_type[0], bot->features.soga_lip_type[1], bot->features.soga_lip_type[2]); + SaveBotFloats(bot->BotID, "soga_chin_type", bot->features.soga_chin_type[0], bot->features.soga_chin_type[1], bot->features.soga_chin_type[2]); + SaveBotFloats(bot->BotID, "soga_nose_type", bot->features.soga_nose_type[0], bot->features.soga_nose_type[1], bot->features.soga_nose_type[2]); + + if (!database_new.Query("UPDATE `bots` SET `model_type` = %u, `hair_type` = %u, `face_type` = %u, `wing_type` = %u, `chest_type` = %u, `legs_type` = %u, `soga_model_type` = %u, `soga_hair_type` = %u, `soga_face_type` = %u WHERE `id` = %u", + bot->GetModelType(), bot->GetHairType(), bot->GetFacialHairType(), bot->GetWingType(), bot->GetChestType(), bot->GetLegsType(), bot->GetSogaModelType(), bot->GetSogaHairType(), bot->GetSogaFacialHairType(), bot->BotID)) { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return; + } +} + +void WorldDatabase::SaveBotColors(int32 bot_id, const char* type, EQ2_Color color) { + if (!database_new.Query("INSERT INTO `bot_appearance` (`bot_id`, `type`, `red`, `green`, `blue`) VALUES (%i, '%s', %i, %i, %i) ON DUPLICATE KEY UPDATE `red` = %i, `blue` = %i, `green` = %i", bot_id, type, color.red, color.green, color.blue, color.red, color.blue, color.green)) { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return; + } +} + +void WorldDatabase::SaveBotFloats(int32 bot_id, const char* type, float float1, float float2, float float3) { + if (!database_new.Query("INSERT INTO `bot_appearance` (`bot_id`, `type`, `red`, `green`, `blue`, `signed_value`) VALUES (%i, '%s', %i, %i, %i, 1) ON DUPLICATE KEY UPDATE `red` = %i, `blue` = %i, `green` = %i", bot_id, type, float1, float2, float3, float1, float2, float3)) { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return; + } +} + +bool WorldDatabase::LoadBot(int32 char_id, int32 bot_index, Bot* bot) { + DatabaseResult result; + + if (!database_new.Select(&result, "SELECT * FROM bots WHERE `char_id` = %u AND `bot_id` = %u", char_id, bot_index)) { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return false; + } + + if (result.Next()) { + bot->BotID = result.GetInt32(0); + bot->BotIndex = result.GetInt32(2); + bot->SetName(result.GetString(3)); + bot->SetRace(result.GetInt8(4)); + bot->SetAdventureClass(result.GetInt8(5)); + bot->SetGender(result.GetInt8(6)); + bot->SetModelType(result.GetInt16(7)); + bot->SetHairType(result.GetInt16(8)); + bot->SetFacialHairType(result.GetInt16(9)); + bot->SetWingType(result.GetInt16(10)); + bot->SetChestType(result.GetInt16(11)); + bot->SetLegsType(result.GetInt16(12)); + bot->SetSogaModelType(result.GetInt16(13)); + bot->SetSogaHairType(result.GetInt16(14)); + bot->SetSogaFacialHairType(result.GetInt16(15)); + } + else + return false; + + LoadBotAppearance(bot); + LoadBotEquipment(bot); + return true; +} + +void WorldDatabase::LoadBotAppearance(Bot* bot) { + DatabaseResult result; + string type; + map appearance_types; + EQ2_Color color; + color.red = 0; + color.green = 0; + color.blue = 0; + + if (!database_new.Select(&result, "SELECT distinct `type` FROM bot_appearance WHERE length(`type`) > 0 AND `bot_id` = %u", bot->BotID)) { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return; + } + + while (result.Next()) { + type = result.GetString(0); + appearance_types[type] = GetAppearanceType(type); + if (appearance_types[type] == 255) + LogWrite(WORLD__ERROR, 0, "Appearance", "Unknown appearance type '%s' in LoadBotAppearances.", type.c_str()); + } + + if (!database_new.Select(&result, "SELECT `type`, `signed_value`, `red`, `green`, `blue` FROM bot_appearance WHERE length(`type`) > 0 AND bot_id = %u", bot->BotID)) { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return; + } + + while (result.Next()) { + type = result.GetString(0); + if (appearance_types[type] < APPEARANCE_SOGA_EBT) { + color.red = result.GetInt8(2); + color.green = result.GetInt8(3); + color.blue = result.GetInt8(4); + } + switch (appearance_types[type]) { + case APPEARANCE_SOGA_HFHC: { + bot->features.soga_hair_face_highlight_color = color; + break; + } + case APPEARANCE_SOGA_HTHC: { + bot->features.soga_hair_type_highlight_color = color; + break; + } + case APPEARANCE_SOGA_HFC: { + bot->features.soga_hair_face_color = color; + break; + } + case APPEARANCE_SOGA_HTC: { + bot->features.soga_hair_type_color = color; + break; + } + case APPEARANCE_SOGA_HH: { + bot->features.soga_hair_highlight_color = color; + break; + } + case APPEARANCE_SOGA_HC1: { + bot->features.soga_hair_color1 = color; + break; + } + case APPEARANCE_SOGA_HC2: { + bot->features.soga_hair_color2 = color; + break; + } + case APPEARANCE_SOGA_SC: { + bot->features.soga_skin_color = color; + break; + } + case APPEARANCE_SOGA_EC: { + bot->features.soga_eye_color = color; + break; + } + case APPEARANCE_HTHC: { + bot->features.hair_type_highlight_color = color; + break; + } + case APPEARANCE_HFHC: { + bot->features.hair_face_highlight_color = color; + break; + } + case APPEARANCE_HTC: { + bot->features.hair_type_color = color; + break; + } + case APPEARANCE_HFC: { + bot->features.hair_face_color = color; + break; + } + case APPEARANCE_HH: { + bot->features.hair_highlight_color = color; + break; + } + case APPEARANCE_HC1: { + bot->features.hair_color1 = color; + break; + } + case APPEARANCE_HC2: { + bot->features.hair_color2 = color; + break; + } + case APPEARANCE_WC1: { + bot->features.wing_color1 = color; + break; + } + case APPEARANCE_WC2: { + bot->features.wing_color2 = color; + break; + } + case APPEARANCE_SC: { + bot->features.skin_color = color; + break; + } + case APPEARANCE_EC: { + bot->features.eye_color = color; + break; + } + case APPEARANCE_SOGA_EBT: { + for (int i = 0; i < 3; i++) + bot->features.soga_eye_brow_type[i] = result.GetSInt8(2 + i); + break; + } + case APPEARANCE_SOGA_CHEEKT: { + for (int i = 0; i < 3; i++) + bot->features.soga_cheek_type[i] = result.GetSInt8(2 + i); + break; + } + case APPEARANCE_SOGA_NT: { + for (int i = 0; i < 3; i++) + bot->features.soga_nose_type[i] = result.GetSInt8(2 + i); + break; + } + case APPEARANCE_SOGA_CHINT: { + for (int i = 0; i < 3; i++) + bot->features.soga_chin_type[i] = result.GetSInt8(2 + i); + break; + } + case APPEARANCE_SOGA_LT: { + for (int i = 0; i < 3; i++) + bot->features.soga_lip_type[i] = result.GetSInt8(2 + i); + break; + } + case APPEARANCE_SOGA_EART: { + for (int i = 0; i < 3; i++) + bot->features.soga_ear_type[i] = result.GetSInt8(2 + i); + break; + } + case APPEARANCE_SOGA_EYET: { + for (int i = 0; i < 3; i++) + bot->features.soga_eye_type[i] = result.GetSInt8(2 + i); + break; + } + case APPEARANCE_EBT: { + for (int i = 0; i < 3; i++) + bot->features.eye_brow_type[i] = result.GetSInt8(2 + i); + break; + } + case APPEARANCE_CHEEKT: { + for (int i = 0; i < 3; i++) + bot->features.cheek_type[i] = result.GetSInt8(2 + i); + break; + } + case APPEARANCE_NT: { + for (int i = 0; i < 3; i++) + bot->features.nose_type[i] = result.GetSInt8(2 + i); + break; + } + case APPEARANCE_CHINT: { + for (int i = 0; i < 3; i++) + bot->features.chin_type[i] = result.GetSInt8(2 + i); + break; + } + case APPEARANCE_EART: { + for (int i = 0; i < 3; i++) + bot->features.ear_type[i] = result.GetSInt8(2 + i); + break; + } + case APPEARANCE_EYET: { + for (int i = 0; i < 3; i++) + bot->features.eye_type[i] = result.GetSInt8(2 + i); + break; + } + case APPEARANCE_LT: { + for (int i = 0; i < 3; i++) + bot->features.lip_type[i] = result.GetSInt8(2 + i); + break; + } + case APPEARANCE_SHIRT: { + bot->features.shirt_color = color; + break; + } + case APPEARANCE_UCC: { + break; + } + case APPEARANCE_PANTS: { + bot->features.pants_color = color; + break; + } + case APPEARANCE_ULC: { + break; + } + case APPEARANCE_U9: { + break; + } + case APPEARANCE_BODY_SIZE: { + bot->features.body_size = color.red; + break; + } + case APPEARANCE_SOGA_WC1: { + break; + } + case APPEARANCE_SOGA_WC2: { + break; + } + case APPEARANCE_SOGA_SHIRT: { + break; + } + case APPEARANCE_SOGA_UCC: { + break; + } + case APPEARANCE_SOGA_PANTS: { + break; + } + case APPEARANCE_SOGA_ULC: { + break; + } + case APPEARANCE_SOGA_U13: { + break; + } + case APPEARANCE_BODY_AGE: { + bot->features.body_age = color.red; + break; + } + case APPEARANCE_MC:{ + bot->features.model_color = color; + break; + } + case APPEARANCE_SMC:{ + bot->features.soga_model_color = color; + break; + } + case APPEARANCE_SBS: { + bot->features.soga_body_size = color.red; + break; + } + case APPEARANCE_SBA: { + bot->features.soga_body_age = color.red; + break; + } + } + } +} + +void WorldDatabase::SaveBotItem(int32 bot_id, int32 item_id, int8 slot) { + if (!database_new.Query("INSERT INTO `bot_equipment` (`bot_id`, `slot`, `item_id`) VALUES (%u, %u, %u) ON DUPLICATE KEY UPDATE `item_id` = %u", bot_id, slot, item_id, item_id)) { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return; + } +} + +void WorldDatabase::LoadBotEquipment(Bot* bot) { + DatabaseResult result; + + if (!database_new.Select(&result, "SELECT `slot`, `item_id` FROM `bot_equipment` WHERE `bot_id` = %u", bot->BotID)) { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return; + } + + Item* master_item = 0; + Item* item = 0; + + while (result.Next()) { + int8 slot = result.GetInt8(0); + int32 item_id = result.GetInt32(1); + + master_item = master_item_list.GetItem(item_id); + if (master_item) { + item = new Item(master_item); + if (item) { + bot->GetEquipmentList()->AddItem(slot, item); + bot->SetEquipment(item, slot); + } + } + } +} + +string WorldDatabase::GetBotList(int32 char_id) { + DatabaseResult result; + string ret; + + if (!database_new.Select(&result, "SELECT `bot_id`, `name`, `race`, `class` FROM `bots` WHERE `char_id` = %u", char_id)) { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return ret; + } + + while (result.Next()) { + ret += to_string(result.GetInt32(0)) + ": "; + ret += result.GetString(1); + ret += " the "; + ret += races.GetRaceNameCase(result.GetInt8(2)); + ret += " "; + ret += classes.GetClassNameCase(result.GetInt8(3)) + "\n"; + } + + return ret; +} + +void WorldDatabase::DeleteBot(int32 char_id, int32 bot_index) { + if (!database_new.Query("DELETE FROM `bots` WHERE `char_id` = %u AND `bot_id` = %u", char_id, bot_index)) { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + } +} + +void WorldDatabase::SetBotStartingItems(Bot* bot, int8 class_id, int8 race_id) { + int32 bot_id = bot->BotID; + LogWrite(PLAYER__DEBUG, 0, "Bot", "Adding default items for race: %u, class: %u for bot_id: %u", race_id, class_id, bot_id); + + DatabaseResult result; + if (!database_new.Select(&result, "SELECT item_id FROM starting_items WHERE class_id IN (%i, %i, %i, 255) AND race_id IN (%i, 255) ORDER BY id", classes.GetBaseClass(class_id), classes.GetSecondaryBaseClass(class_id), class_id, race_id)) { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return; + } + + while (result.Next()) { + bot->GiveItem(result.GetInt32(0)); + } +} \ No newline at end of file diff --git a/source/WorldServer/Chat/Chat.cpp b/source/WorldServer/Chat/Chat.cpp new file mode 100644 index 0000000..8c41c25 --- /dev/null +++ b/source/WorldServer/Chat/Chat.cpp @@ -0,0 +1,372 @@ +/* + 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 . +*/ + +#include "Chat.h" +#include "../../common/Log.h" +#include "../../common/ConfigReader.h" +#include "../../common/PacketStruct.h" + #include "../Rules/Rules.h" + extern RuleManager rule_manager; + +//devn00b +#ifdef DISCORD + #ifndef WIN32 + #include + #include "ChatChannel.h" + + extern ChatChannel channel; + #endif +#endif + + +extern ConfigReader configReader; + + + +Chat::Chat() { + m_channels.SetName("Chat::Channels"); +} + +Chat::~Chat() { + vector::iterator itr; + + m_channels.writelock(__FUNCTION__, __LINE__); + for (itr = channels.begin(); itr != channels.end(); itr++) + safe_delete(*itr); + m_channels.releasewritelock(__FUNCTION__, __LINE__); +} + +void Chat::AddChannel(ChatChannel *channel) { + m_channels.writelock(__FUNCTION__, __LINE__); + channels.push_back(channel); + m_channels.releasewritelock(__FUNCTION__, __LINE__); +} + +unsigned int Chat::GetNumChannels() { + unsigned int ret; + + m_channels.readlock(__FUNCTION__, __LINE__); + ret = (unsigned int)channels.size(); + m_channels.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +EQ2Packet * Chat::GetWorldChannelList(Client *client) { + PacketStruct *packet_struct = configReader.getStruct("WS_AvailWorldChannels", client->GetVersion()); + Player *player = client->GetPlayer(); + vector channels_to_send; + vector::iterator itr; + ChatChannel *channel; + EQ2Packet *packet; + int32 i = 0; + bool add; + + if (packet_struct == NULL) { + LogWrite(CHAT__ERROR, 0, "Chat", "Could not find packet 'WS_AvailWorldChannels' for client %s on version %i\n", player->GetName(), client->GetVersion()); + return NULL; + } + + m_channels.readlock(__FUNCTION__, __LINE__); + for (itr = channels.begin(); itr != channels.end(); itr++) { + channel = *itr; + + if (channel->GetType() == CHAT_CHANNEL_TYPE_WORLD) { + add = true; + + if (add && !channel->CanJoinChannelByLevel(player->GetLevel())) + add = false; + if (add && !channel->CanJoinChannelByRace(player->GetRace())) + add = false; + if (add && !channel->CanJoinChannelByClass(player->GetAdventureClass())) + add = false; + + if (add) + channels_to_send.push_back(channel); + } + } + m_channels.releasereadlock(__FUNCTION__, __LINE__); + + packet_struct->setArrayLengthByName("num_channels", channels_to_send.size()); + for (itr = channels_to_send.begin(); itr != channels_to_send.end(); itr++, i++) { + packet_struct->setArrayDataByName("channel_name", (*itr)->GetName(), i); + packet_struct->setArrayDataByName("unknown", 0, i); + } + + packet = packet_struct->serialize(); + safe_delete(packet_struct); + + return packet; +} + +bool Chat::ChannelExists(const char *channel_name) { + vector::iterator itr; + bool ret = false; + + m_channels.readlock(__FUNCTION__, __LINE__); + for (itr = channels.begin(); itr != channels.end(); itr++) { + if (strncasecmp(channel_name, (*itr)->GetName(), CHAT_CHANNEL_MAX_NAME) == 0) { + ret = true; + break; + } + } + m_channels.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +bool Chat::HasPassword(const char *channel_name) { + vector::iterator itr; + bool ret = false; + + m_channels.readlock(__FUNCTION__, __LINE__); + for (itr = channels.begin(); itr != channels.end(); itr++) { + if (strncasecmp(channel_name, (*itr)->GetName(), CHAT_CHANNEL_MAX_NAME) == 0) { + ret = (*itr)->HasPassword(); + break; + } + } + m_channels.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +bool Chat::PasswordMatches(const char *channel_name, const char *password) { + vector::iterator itr; + bool ret = false; + + m_channels.readlock(__FUNCTION__, __LINE__); + for (itr = channels.begin(); itr != channels.end(); itr++) { + if (strncasecmp(channel_name, (*itr)->GetName(), CHAT_CHANNEL_MAX_NAME) == 0) { + ret = (*itr)->PasswordMatches(password); + break; + } + } + m_channels.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +bool Chat::CreateChannel(const char *channel_name) { + return CreateChannel(channel_name, NULL); +} + +bool Chat::CreateChannel(const char *channel_name, const char *password) { + LogWrite(CHAT__DEBUG, 0, "Chat", "Channel %s being created", channel_name); + + ChatChannel *channel = new ChatChannel(); + channel->SetName(channel_name); + channel->SetType(CHAT_CHANNEL_TYPE_CUSTOM); + if (password != NULL) + channel->SetPassword(password); + + m_channels.writelock(__FUNCTION__, __LINE__); + channels.push_back(channel); + m_channels.releasewritelock(__FUNCTION__, __LINE__); + + return true; +} + +bool Chat::IsInChannel(Client *client, const char *channel_name) { + vector::iterator itr; + bool ret = false; + + m_channels.readlock(__FUNCTION__, __LINE__); + for (itr = channels.begin(); itr != channels.end(); itr++) { + if (strncasecmp(channel_name, (*itr)->GetName(), CHAT_CHANNEL_MAX_NAME) == 0) { + ret = (*itr)->IsInChannel(client->GetCharacterID()); + break; + } + } + m_channels.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +bool Chat::JoinChannel(Client *client, const char *channel_name) { + vector::iterator itr; + bool ret = false; + + LogWrite(CHAT__DEBUG, 1, "Chat", "Client %s is joining channel %s", client->GetPlayer()->GetName(), channel_name); + + m_channels.writelock(__FUNCTION__, __LINE__); + for (itr = channels.begin(); itr != channels.end(); itr++) { + if (strncasecmp(channel_name, (*itr)->GetName(), CHAT_CHANNEL_MAX_NAME) == 0) { + ret = (*itr)->JoinChannel(client); + break; + } + } + m_channels.releasewritelock(__FUNCTION__, __LINE__); + + return ret; +} + +bool Chat::LeaveChannel(Client *client, const char *channel_name) { + vector::iterator itr; + bool ret = false; + + LogWrite(CHAT__DEBUG, 1, "Chat", "Client %s is leaving channel %s", client->GetPlayer()->GetName(), channel_name); + + m_channels.writelock(__FUNCTION__, __LINE__); + for (itr = channels.begin(); itr != channels.end(); itr++) { + if (strncasecmp(channel_name, (*itr)->GetName(), CHAT_CHANNEL_MAX_NAME) == 0) { + ret = (*itr)->LeaveChannel(client); + + if ((*itr)->GetType() == CHAT_CHANNEL_TYPE_CUSTOM && (*itr)->GetNumClients() == 0) { + LogWrite(CHAT__DEBUG, 0, "Chat", "Custom channel %s has 0 clients left, deleting channel", channel_name); + safe_delete(*itr); + channels.erase(itr); + } + + break; + } + } + m_channels.releasewritelock(__FUNCTION__, __LINE__); + + return ret; +} + +bool Chat::LeaveAllChannels(Client *client) { + vector::iterator itr; + ChatChannel *channel; + bool erased; + + m_channels.writelock(__FUNCTION__, __LINE__); + itr = channels.begin(); + while (itr != channels.end()) { + channel = *itr; + erased = false; + + if (channel->IsInChannel(client->GetCharacterID())) { + LogWrite(CHAT__DEBUG, 1, "Chat", "Client %s is leaving channel %s", client->GetPlayer()->GetName(), channel->GetName()); + channel->LeaveChannel(client); + + if (channel->GetType() == CHAT_CHANNEL_TYPE_CUSTOM && channel->GetNumClients() == 0) { + LogWrite(CHAT__DEBUG, 0, "Chat", "Custom channel %s has 0 clients left, deleting channel", channel->GetName()); + safe_delete(*itr); + itr = channels.erase(itr); + erased = true; + } + } + + if (!erased) + itr++; + } + m_channels.releasewritelock(__FUNCTION__, __LINE__); + + return true; +} + +bool Chat::TellChannel(Client *client, const char *channel_name, const char *message, const char* name) { + vector::iterator itr; + bool ret = false; + bool enablediscord = rule_manager.GetGlobalRule(R_Discord, DiscordEnabled)->GetBool(); + const char* discordchan = rule_manager.GetGlobalRule(R_Discord, DiscordChannel)->GetString(); + + m_channels.readlock(__FUNCTION__, __LINE__); + for (itr = channels.begin(); itr != channels.end(); itr++) { + if (strncasecmp(channel_name, (*itr)->GetName(), CHAT_CHANNEL_MAX_NAME) == 0) { + if (client && name) + ret = (*itr)->TellChannelClient(client, message, name); + else + ret = (*itr)->TellChannel(client, message, name); + + if(enablediscord == true && client){ + + if (strcmp(channel_name, discordchan) != 0){ + m_channels.releasereadlock(__FUNCTION__, __LINE__); + return ret; + } +#ifdef DISCORD + if (client) { + std::string whofrom = client->GetPlayer()->GetName(); + std::string msg = string(message); + ret = PushDiscordMsg(msg.c_str(), whofrom.c_str()); + } +#endif + } + + break; + } + } + m_channels.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +bool Chat::SendChannelUserList(Client *client, const char *channel_name) { + vector::iterator itr; + bool ret = false; + + m_channels.readlock(__FUNCTION__, __LINE__); + for (itr = channels.begin(); itr != channels.end(); itr++) { + if (strncasecmp(channel_name, (*itr)->GetName(), CHAT_CHANNEL_MAX_NAME) == 0) { + ret = (*itr)->SendChannelUserList(client); + break; + } + } + m_channels.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +ChatChannel* Chat::GetChannel(const char *channel_name) { + vector::iterator itr; + ChatChannel* ret = 0; + + m_channels.readlock(__FUNCTION__, __LINE__); + for (itr = channels.begin(); itr != channels.end(); itr++) { + if (strncasecmp(channel_name, (*itr)->GetName(), CHAT_CHANNEL_MAX_NAME) == 0) { + ret = (*itr); + break; + } + } + m_channels.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +#ifdef DISCORD +//this sends chat from EQ2EMu to Discord. Currently using webhooks. Makes things simpler code wise. +int Chat::PushDiscordMsg(const char* msg, const char* from) { + bool enablediscord = rule_manager.GetGlobalRule(R_Discord, DiscordEnabled)->GetBool(); + + if(enablediscord == false) { + LogWrite(INIT__INFO, 0,"Discord","Bot Disabled By Rule..."); + return 0; + } + + m_channels.readlock(__FUNCTION__, __LINE__); + const char* hook = rule_manager.GetGlobalRule(R_Discord, DiscordWebhookURL)->GetString(); + std::string servername = net.GetWorldName(); + char ourmsg[4096]; + + //form our message + sprintf(ourmsg,"[%s] [%s] Says: %s",from, servername.c_str(), msg); + + /* send a message with this webhook */ + dpp::cluster bot(""); + dpp::webhook wh(hook); + bot.execute_webhook(wh, dpp::message(ourmsg)); + m_channels.releasereadlock(__FUNCTION__, __LINE__); + + return 1; +} +#endif \ No newline at end of file diff --git a/source/WorldServer/Chat/Chat.h b/source/WorldServer/Chat/Chat.h new file mode 100644 index 0000000..fe6c4e2 --- /dev/null +++ b/source/WorldServer/Chat/Chat.h @@ -0,0 +1,119 @@ +/* + 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 . +*/ +#ifndef CHAT_CHAT_H_ +#define CHAT_CHAT_H_ + +#include +#include "../../common/types.h" +#include "../../common/EQPacket.h" +#include "../../common/Mutex.h" +#include "../client.h" +#include "ChatChannel.h" + +#ifdef DISCORD + #ifndef WIN32 + #pragma once + #include + #endif +#endif + +using namespace std; +/* + +CREATING A CHANNEL + +-- OP_RemoteCmdMsg -- +3/14/2012 20:17:06 +192.168.1.198 -> 69.174.200.73 +0000: 00 09 05 9A 2A 0E 00 0F 00 63 75 73 74 6F 6D 20 ....*....custom +0010 70 61 73 73 77 6F 72 64 password + + +TALKING IN A CHANNEL + +[11:52.23] <@Xinux> -- OP_RemoteCmdMsg -- +[11:52.23] <@Xinux> 3/14/2012 20:17:25 +[11:52.23] <@Xinux> 192.168.1.198 -> 69.174.200.73 +[11:52.23] <@Xinux> 0000: 00 09 06 2D 2A 11 00 21 00 63 75 73 74 6F 6D 20 ...-*..!.custom +[11:52.23] <@Xinux> 0010: 20 74 68 69 73 20 69 73 20 6D 79 20 63 75 73 74 this is my cust +[11:52.23] <@Xinux> 0020 6F 6D 20 63 68 61 6E 6E 65 6C om channel + + +[08:37.46] <@Xinux_Work> 00 09 05 8B 00 3A 53 00 00 00 FF 3C 02 00 00 FF .....:S....<.... +[08:37.46] <@Xinux_Work> FF FF FF FF FF FF FF 06 00 4C 65 69 68 69 61 07 .........Leihia. +[08:37.46] <@Xinux_Work> 00 4B 6F 65 63 68 6F 68 00 02 00 00 00 00 01 00 .Koechoh........ +[08:37.46] <@Xinux_Work> 00 00 22 00 18 00 62 65 74 74 65 72 20 74 68 61 .."...better tha +[08:37.46] <@Xinux_Work> 6E 20 61 20 72 65 64 20 6F 6E 65 20 3A 50 09 00 n a red one :P.. +[08:37.46] <@Xinux_Work> 4C 65 76 65 6C 5F 31 2D 39 01 01 00 00 Level_1-9.... + +OTHERS LEAVING AND JOINING A CHANNEL + +-- OP_ClientCmdMsg::OP_EqChatChannelUpdateCmd -- +3/14/2012 20:17:06 +69.174.200.73 -> 192.168.1.198 +0000: 00 3A 18 00 00 00 FF 88 02 03 09 00 4C 65 76 65 .:..........Leve +0010 6C 5F 31 2D 39 07 00 53 68 61 77 6E 61 68 l_1-9..Shawnah + + +-- OP_ClientCmdMsg::OP_EqChatChannelUpdateCmd -- +3/14/2012 20:17:06 +69.174.200.73 -> 192.168.1.198 +0000: 00 3A 16 00 00 00 FF 88 02 03 07 00 41 75 63 74 .:..........Auct +0010 69 6F 6E 07 00 53 68 61 77 6E 61 68 ion..Shawnah + +OP_EqChatChannelUpdateCmd +unknown=0 unknown1=blank join +unknown=1 unknown1=blank leave +unknown=2 unknown2=player join/leave? +unknown=3 unknown2=player join/leave? +*/ + + +class Chat{ +public: + Chat(); + virtual ~Chat(); + + void AddChannel(ChatChannel *channel); + unsigned int GetNumChannels(); + + EQ2Packet * GetWorldChannelList(Client *client); + + bool ChannelExists(const char *channel_name); + bool HasPassword(const char *channel_name); + bool PasswordMatches(const char *channel_name, const char *password); + bool CreateChannel(const char *channel_name); + bool CreateChannel(const char *channel_name, const char *password); + bool IsInChannel(Client *client, const char *channel_name); + bool JoinChannel(Client *client, const char *channel_name); + bool LeaveChannel(Client *client, const char *channel_name); + bool LeaveAllChannels(Client *client); + bool TellChannel(Client *client, const char *channel_name, const char *message, const char* name = 0); + bool SendChannelUserList(Client *client, const char *channel_name); + //devn00b + int PushDiscordMsg(const char*, const char*); + ChatChannel* GetChannel(const char* channel_name); + +private: + Mutex m_channels; + vector channels; +}; + +#endif diff --git a/source/WorldServer/Chat/ChatChannel.cpp b/source/WorldServer/Chat/ChatChannel.cpp new file mode 100644 index 0000000..e0fbf9e --- /dev/null +++ b/source/WorldServer/Chat/ChatChannel.cpp @@ -0,0 +1,227 @@ +#include +#include "../../common/Log.h" +#include "../../common/ConfigReader.h" +#include "../../common/PacketStruct.h" +#include "../World.h" +#include "ChatChannel.h" + +extern ConfigReader configReader; +extern ZoneList zone_list; + +#define CHAT_CHANNEL_JOIN 0 +#define CHAT_CHANNEL_LEAVE 1 +#define CHAT_CHANNEL_OTHER_JOIN 2 +#define CHAT_CHANNEL_OTHER_LEAVE 3 + +ChatChannel::ChatChannel() { + memset(name, 0, sizeof(name)); + memset(password, 0, sizeof(password)); + type = CHAT_CHANNEL_TYPE_NONE; + level_restriction = 0; + races = 0; + classes = 0; +} + +ChatChannel::~ChatChannel() { +} + +bool ChatChannel::IsInChannel(int32 character_id) { + vector::iterator itr; + + for (itr = clients.begin(); itr != clients.end(); itr++) { + if (character_id == *itr) + return true; + } + + return false; +} + +bool ChatChannel::JoinChannel(Client *client) { + PacketStruct *packet_struct; + vector::iterator itr; + Client *to_client; + + //send the player join packet to the joining client + if ((packet_struct = configReader.getStruct("WS_ChatChannelUpdate", client->GetVersion())) == NULL) { + LogWrite(CHAT__ERROR, 0, "Chat", "Could not find packet 'WS_ChatChannelUpdate' when client %s was trying to join channel %s", client->GetPlayer()->GetName(), name); + return false; + } + + packet_struct->setDataByName("action", CHAT_CHANNEL_JOIN); + packet_struct->setDataByName("channel_name", name); + client->QueuePacket(packet_struct->serialize()); + safe_delete(packet_struct); + clients.push_back(client->GetCharacterID()); + + //loop through everyone else in the channel and send the "other" player join packet + for (itr = clients.begin(); itr != clients.end(); itr++) { + if (client->GetCharacterID() == *itr) + continue; + + if ((to_client = zone_list.GetClientByCharID(*itr)) == NULL) + continue; + + if ((packet_struct = configReader.getStruct("WS_ChatChannelUpdate", to_client->GetVersion())) == NULL) + continue; + + packet_struct->setDataByName("action", CHAT_CHANNEL_OTHER_JOIN); + packet_struct->setDataByName("channel_name", name); + packet_struct->setDataByName("player_name", client->GetPlayer()->GetName()); + to_client->QueuePacket(packet_struct->serialize()); + safe_delete(packet_struct); + } + + return true; +} + +bool ChatChannel::LeaveChannel(Client *client) { + vector::iterator itr; + PacketStruct *packet_struct; + Client *to_client; + bool ret = false; + + for (itr = clients.begin(); itr != clients.end(); itr++) { + if (client->GetCharacterID() == *itr) { + clients.erase(itr); + ret = true; + break; + } + } + + if (ret) { + //send the packet to the leaving client + if ((packet_struct = configReader.getStruct("WS_ChatChannelUpdate", client->GetVersion())) == NULL) + return false; + + packet_struct->setDataByName("action", CHAT_CHANNEL_LEAVE); + packet_struct->setDataByName("channel_name", name); + + client->QueuePacket(packet_struct->serialize()); + safe_delete(packet_struct); + + //send the leave packet to all other clients in the channel + for (itr = clients.begin(); itr != clients.end(); itr++) { + if ((to_client = zone_list.GetClientByCharID(*itr)) == NULL) + continue; + if (to_client == client) // don't need to send to self. + continue; + + if ((packet_struct = configReader.getStruct("WS_ChatChannelUpdate", to_client->GetVersion())) == NULL) + continue; + + packet_struct->setDataByName("action", CHAT_CHANNEL_OTHER_LEAVE); + packet_struct->setDataByName("channel_name", name); + packet_struct->setDataByName("player_name", client->GetPlayer()->GetName()); + to_client->QueuePacket(packet_struct->serialize()); + safe_delete(packet_struct); + } + } + + return ret; +} + +bool ChatChannel::TellChannel(Client *client, const char *message, const char* name2) { + vector::iterator itr; + PacketStruct *packet_struct; + Client *to_client; + + for (itr = clients.begin(); itr != clients.end(); itr++) { + if ((to_client = zone_list.GetClientByCharID(*itr)) == NULL) + continue; + if ((packet_struct = configReader.getStruct("WS_HearChat", to_client->GetVersion())) == NULL) + continue; + + packet_struct->setDataByName("unknown", 0); + packet_struct->setDataByName("from_spawn_id", 0xFFFFFFFF); + packet_struct->setDataByName("to_spawn_id", 0xFFFFFFFF); + + if (client != NULL){ + packet_struct->setDataByName("from", client->GetPlayer()->GetName()); + } else { + char name3[128]; + sprintf(name3,"[%s] from discord",name2); + packet_struct->setDataByName("from", name3); + } + + packet_struct->setDataByName("to", to_client->GetPlayer()->GetName()); + packet_struct->setDataByName("channel", to_client->GetMessageChannelColor(CHANNEL_CUSTOM_CHANNEL)); + + if(client != NULL){ + packet_struct->setDataByName("language", client->GetPlayer()->GetCurrentLanguage()); + }else{ + packet_struct->setDataByName("language", 0); + } + packet_struct->setDataByName("message", message); + packet_struct->setDataByName("channel_name", name); + packet_struct->setDataByName("show_bubble", 1); + + if(client != NULL){ + if (client->GetPlayer()->GetCurrentLanguage() == 0 || to_client->GetPlayer()->HasLanguage(client->GetPlayer()->GetCurrentLanguage())) { + packet_struct->setDataByName("understood", 1); + } + } else { + packet_struct->setDataByName("understood", 1); + } + + packet_struct->setDataByName("unknown4", 0); + + to_client->QueuePacket(packet_struct->serialize()); + safe_delete(packet_struct); + } + + return true; +} + +bool ChatChannel::TellChannelClient(Client* to_client, const char* message, const char* name2) { + PacketStruct *packet_struct; + + if (string(name2).find('[') != string::npos) + return true; + + packet_struct = configReader.getStruct("WS_HearChat", to_client->GetVersion()); + if (packet_struct) { + packet_struct->setDataByName("unknown", 0); + packet_struct->setDataByName("from_spawn_id", 0xFFFFFFFF); + packet_struct->setDataByName("to_spawn_id", 0xFFFFFFFF); + packet_struct->setDataByName("from", name2); + packet_struct->setDataByName("to", to_client->GetPlayer()->GetName()); + packet_struct->setDataByName("channel", to_client->GetMessageChannelColor(CHANNEL_CUSTOM_CHANNEL)); + packet_struct->setDataByName("language", 0); + packet_struct->setDataByName("message", message); + packet_struct->setDataByName("channel_name", name); + packet_struct->setDataByName("show_bubble", 1); + packet_struct->setDataByName("understood", 1); + packet_struct->setDataByName("unknown4", 0); + + to_client->QueuePacket(packet_struct->serialize()); + } + safe_delete(packet_struct); + + return true; +} + +bool ChatChannel::SendChannelUserList(Client *client) { + vector::iterator itr; + PacketStruct *packet_struct; + Client *to_client; + int8 i = 0; + + if ((packet_struct = configReader.getStruct("WS_WhoChannelQueryReply", client->GetVersion())) == NULL) + return false; + + packet_struct->setDataByName("channel_name", name); + packet_struct->setDataByName("unknown", 0); + packet_struct->setArrayLengthByName("num_players", clients.size()); + for (itr = clients.begin(); itr != clients.end(); itr++) { + if ((to_client = zone_list.GetClientByCharID(*itr)) != NULL) + packet_struct->setArrayDataByName("player_name", client->GetPlayer()->GetName(), i++); + else + packet_struct->setArrayDataByName("player_name", "", i++); + } + + client->QueuePacket(packet_struct->serialize()); + safe_delete(packet_struct); + + return true; +} + diff --git a/source/WorldServer/Chat/ChatChannel.h b/source/WorldServer/Chat/ChatChannel.h new file mode 100644 index 0000000..8dce202 --- /dev/null +++ b/source/WorldServer/Chat/ChatChannel.h @@ -0,0 +1,79 @@ +/* + 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 . +*/ + +#ifndef CHAT_CHATCHANNEL_H_ +#define CHAT_CHATCHANNEL_H_ + +#include "../../common/types.h" +#include "../client.h" +#include + +using namespace std; + +#define CHAT_CHANNEL_MAX_NAME 100 +#define CHAT_CHANNEL_MAX_PASSWORD 100 + +enum ChatChannelType { + CHAT_CHANNEL_TYPE_NONE = 0, + CHAT_CHANNEL_TYPE_WORLD, + CHAT_CHANNEL_TYPE_CUSTOM +}; + +class ChatChannel { +public: + ChatChannel(); + virtual ~ChatChannel(); + + void SetName(const char *name) {strncpy(this->name, name, CHAT_CHANNEL_MAX_NAME);} + void SetPassword(const char *password) {strncpy(this->password, password, CHAT_CHANNEL_MAX_PASSWORD);} + void SetType(ChatChannelType type) {this->type = type;} + void SetLevelRestriction(int16 level_restriction) {this->level_restriction = level_restriction;} + void SetRacesAllowed(int64 races) {this->races = races;} + void SetClassesAllowed(int64 classes) {this->classes = classes;} + + const char * GetName() {return name;} + ChatChannelType GetType() {return type;} + unsigned int GetNumClients() {return clients.size();} + + bool HasPassword() {return password[0] != '\0';} + bool PasswordMatches(const char *password) {return strncmp(this->password, password, CHAT_CHANNEL_MAX_PASSWORD) == 0;} + bool CanJoinChannelByLevel(int16 level) {return level >= level_restriction;} + bool CanJoinChannelByRace(int8 race_id) {return races == 0 || (1 << race_id) & races;} + bool CanJoinChannelByClass(int8 class_id) {return classes == 0 || (1 << class_id) & classes;} + + bool IsInChannel(int32 character_id); + bool JoinChannel(Client *client); + bool LeaveChannel(Client *client); + bool TellChannel(Client *client, const char *message, const char* name2 = 0); + bool TellChannelClient(Client* to_client, const char* message, const char* name2 = 0); + bool SendChannelUserList(Client *client); + + +private: + char name[CHAT_CHANNEL_MAX_NAME + 1]; + char password[CHAT_CHANNEL_MAX_PASSWORD + 1]; + ChatChannelType type; + vector clients; + int16 level_restriction; + int64 races; + int64 classes; +}; + +#endif diff --git a/source/WorldServer/Chat/ChatDB.cpp b/source/WorldServer/Chat/ChatDB.cpp new file mode 100644 index 0000000..297ecf7 --- /dev/null +++ b/source/WorldServer/Chat/ChatDB.cpp @@ -0,0 +1,46 @@ +/* + 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 . +*/ + + +#include "../../common/Log.h" +#include "Chat.h" +#include "../WorldDatabase.h" + +extern Chat chat; + +void WorldDatabase::LoadChannels() { + DatabaseResult result; + ChatChannel *channel; + + if (database_new.Select(&result, "SELECT `name`,`password`,`level_restriction`,`classes`,`races` FROM `channels`")) { + while (result.Next()) { + channel = new ChatChannel(); + channel->SetName(result.GetString(0)); + if (!result.IsNull(1)) + channel->SetPassword(result.GetString(1)); + channel->SetLevelRestriction(result.GetInt16(2)); + channel->SetClassesAllowed(result.GetInt64(3)); + channel->SetRacesAllowed(result.GetInt64(4)); + channel->SetType(CHAT_CHANNEL_TYPE_WORLD); + + chat.AddChannel(channel); + } + } +} \ No newline at end of file diff --git a/source/WorldServer/ClientPacketFunctions.cpp b/source/WorldServer/ClientPacketFunctions.cpp new file mode 100644 index 0000000..8f18143 --- /dev/null +++ b/source/WorldServer/ClientPacketFunctions.cpp @@ -0,0 +1,475 @@ +/* + 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 . +*/ +#include "ClientPacketFunctions.h" +#include "WorldDatabase.h" +#include "../common/ConfigReader.h" +#include "Variables.h" +#include "World.h" +#include "classes.h" +#include "../common/Log.h" +#include "Traits/Traits.h" + +extern Classes classes; +extern Commands commands; +extern WorldDatabase database; +extern ConfigReader configReader; +extern MasterSpellList master_spell_list; +extern MasterTraitList master_trait_list; +extern Variables variables; +extern World world; + +void ClientPacketFunctions::SendFinishedEntitiesList ( Client* client ){ + + EQ2Packet* finishedEntitiesApp = new EQ2Packet(OP_DoneSendingInitialEntitiesMsg, 0, 0); + client->QueuePacket(finishedEntitiesApp); + +} + +void ClientPacketFunctions::SendSkillSlotMappings(Client* client){ + EQ2Packet* app = client->GetPlayer()->GetSpellSlotMappingPacket(client->GetVersion()); + if(app) + client->QueuePacket(app); +} + +void ClientPacketFunctions::SendLoginDenied ( Client* client ){ + PacketStruct* packet = configReader.getStruct("LS_LoginResponse", 1); + if(packet){ + packet->setDataByName("reply_code", 1); + packet->setDataByName("unknown03", 0xFFFFFFFF); + packet->setDataByName("unknown04", 0xFFFFFFFF); + EQ2Packet* app = packet->serialize(); + client->QueuePacket(app); + safe_delete(packet); + } +} + +void ClientPacketFunctions::SendLoginAccepted ( Client* client ){ + LogWrite(PACKET__DEBUG, 0, "Packet", "Sending Login Accepted packet (LS_LoginResponse, %i)", client->GetVersion()); + PacketStruct* response_packet = configReader.getStruct("LS_LoginResponse", client->GetVersion()); + if(response_packet){ + response_packet->setDataByName("unknown02", 1); + response_packet->setDataByName("unknown05", -959971393); + response_packet->setDataByName("unknown08", 2); + response_packet->setDataByName("unknown09", 585); + response_packet->setDataByName("unknown10", 1597830); + response_packet->setDataByName("accountid", 3); //client->GetAccountID()); + EQ2Packet* outapp = response_packet->serialize(); + client->QueuePacket(outapp); + safe_delete(response_packet); + } +} + +void ClientPacketFunctions::SendCommandList ( Client* client ){ + EQ2Packet* app = commands.GetRemoteCommands()->serialize(client->GetVersion()); + client->QueuePacket(app); +} + +void ClientPacketFunctions::SendGameWorldTime ( Client* client ){ + PacketStruct* packet = world.GetWorldTime(client->GetVersion()); + if(packet){ + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + //opcode 501 was the selection display opcode +} + +void ClientPacketFunctions::SendCharacterData ( Client* client ){ + client->GetPlayer()->SetCharacterID(client->GetCharacterID()); + if(!client->IsReloadingZone()) { + EQ2Packet* outapp = client->GetPlayer()->serialize(client->GetPlayer(), client->GetVersion()); + //DumpPacket(outapp); + client->QueuePacket(outapp); + } +} + +void ClientPacketFunctions::SendCharacterSheet ( Client* client ){ + EQ2Packet* app = client->GetPlayer()->GetPlayerInfo()->serialize(client->GetVersion()); + client->QueuePacket(app); + + if (client->GetVersion() >= 1188) { + EQ2Packet* app2 = client->GetPlayer()->GetPlayerInfo()->serializePet(client->GetVersion()); + if (app2) + client->QueuePacket(app2); + } +} + +void ClientPacketFunctions::SendSkillBook ( Client* client ){ + EQ2Packet* app = client->GetPlayer()->skill_list.GetSkillPacket(client->GetVersion()); + if(app) + client->QueuePacket(app); +} + +// Jabantiz: Attempt to get the char trait list working +void ClientPacketFunctions::SendTraitList(Client* client) { + if (client->GetVersion() >= 562) { + EQ2Packet* traitApp = master_trait_list.GetTraitListPacket(client); + //DumpPacket(traitApp); + if (traitApp) { + client->QueuePacket(traitApp); + } + } +} + +void ClientPacketFunctions::SendAbilities ( Client* client ){ + LogWrite(MISC__TODO, 1, "TODO", " Add SendAbilities functionality\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__); + // this is the featherfall ability data + // later this would loop through and send all abilities + /*uchar abilityData[] ={0x11,0x00,0x00,0x00,0xff,0x15,0x02,0x00,0x0b,0x00,0x46,0x65,0x61,0x74 + ,0x68,0x65,0x72,0x66,0x61,0x6c,0x6c}; + EQ2Packet* abilityApp = new EQ2Packet(OP_ClientCmdMsg, abilityData, sizeof(abilityData)); + client->QueuePacket(abilityApp);*/ +} + +void ClientPacketFunctions::SendCommandNamePacket ( Client* client ){ + LogWrite(MISC__TODO, 1, "TODO", " fix, this is actually quest/collection information\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__); + /* + PacketStruct* command_packet = configReader.getStruct("WS_CommandName", client->GetVersion()); + if(command_packet){ + command_packet->setDataByName("unknown03", 0x221bfb47); + + char* charName = { "BogusName" }; + command_packet->setMediumStringByName("character_name",charName); + EQ2Packet* outapp = command_packet->serialize(); + client->QueuePacket(outapp); + safe_delete(command_packet); + } + */ +} + +void ClientPacketFunctions::SendQuickBarInit ( Client* client ){ + int32 count = database.LoadPlayerSkillbar(client); + if(count == 0) { + LogWrite(PACKET__DEBUG, 0, "Packet", "No character quickbar found!"); + database.UpdateStartingSkillbar(client->GetCharacterID(), client->GetPlayer()->GetAdventureClass(), client->GetPlayer()->GetRace()); + database.LoadPlayerSkillbar(client); + } + EQ2Packet* quickbarApp = client->GetPlayer()->GetQuickbarPacket(client->GetVersion()); + if(quickbarApp) + client->QueuePacket(quickbarApp); +} + +void ClientPacketFunctions::SendCharacterMacros(Client* client) { + LogWrite(PACKET__DEBUG, 0, "Packet", "Sending Character Macro packet (WS_MacroInit, %i)", client->GetVersion()); + map >* macros = database.LoadCharacterMacros(client->GetCharacterID()); + if (macros) { + PacketStruct* macro_packet = configReader.getStruct("WS_MacroInit", client->GetVersion()); + if (macro_packet) { + map >::iterator itr; + macro_packet->setArrayLengthByName("macro_count", macros->size()); + int8 x = 0; + for (itr = macros->begin(); itr != macros->end(); itr++, x++) { + macro_packet->setArrayDataByName("number", itr->first, x); + if (itr->second.size() > 0) { + LogWrite(PACKET__DEBUG, 5, "Packet", "Loading Macro %i, name: %s", itr->first, itr->second[0]->name.c_str()); + macro_packet->setArrayDataByName("name", itr->second[0]->name.c_str(), x); + } + if (client->GetVersion() > 373) { + char tmp_details_count[25] = { 0 }; + sprintf(tmp_details_count, "macro_details_count_%i", x); + macro_packet->setArrayLengthByName(tmp_details_count, itr->second.size()); + for (int8 i = 0; i < itr->second.size(); i++) { + char tmp_command[15] = { 0 }; + sprintf(tmp_command, "command%i", x); + LogWrite(PACKET__DEBUG, 5, "Packet", "\tLoading Command %i: %s", itr->first, x, itr->second[i]->text.c_str()); + macro_packet->setArrayDataByName(tmp_command, itr->second[i]->text.c_str(), i); + if ( i > 0 ) // itr->second[0] used below, we will delete it later + safe_delete(itr->second[i]); // delete MacroData* + } + macro_packet->setArrayDataByName("unknown2", 2, x); + macro_packet->setArrayDataByName("unknown3", 0xFFFFFFFF, x); + } + else { + if (itr->second.size() > 0) + macro_packet->setArrayDataByName("command", itr->second[0]->text.c_str(), x); + } + macro_packet->setArrayDataByName("icon", itr->second[0]->icon, x); + client->GetPlayer()->macro_icons[itr->first] = itr->second[0]->icon; + + // remove itr->second[0] now that we are done with it + safe_delete(itr->second[0]); // delete MacroData* + } + EQ2Packet* packet = macro_packet->serialize(); + client->QueuePacket(packet); + safe_delete(macro_packet); + } + safe_delete(macros); + } +} + +void ClientPacketFunctions::SendMOTD ( Client* client ){ + + const char* motd = 0; + + // fetch MOTD from `variables` table + Variable* var = variables.FindVariable("motd"); + + if( var == NULL || strlen (var->GetValue()) == 0) { + LogWrite(WORLD__WARNING, 0, "World", "No MOTD set. Sending generic message..."); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Message of the Day: Welcome to EQ2Emulator! Customize this message in the `variables`.`motd` data!"); + } + else { + motd = var->GetValue(); + LogWrite(WORLD__DEBUG, 0, "World", "Send MOTD..."); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, motd); + } + +} + +void ClientPacketFunctions::SendUpdateSpellBook ( Client* client ){ + if(client->IsReadyForSpawns()){ + EQ2Packet* app = client->GetPlayer()->GetSpellBookUpdatePacket(client->GetVersion()); + if(app) + client->QueuePacket(app); + } + client->GetPlayer()->UnlockAllSpells(true); +} + +void ClientPacketFunctions::SendServerControlFlagsClassic(Client* client, int32 param, int32 value) { + PacketStruct* packet = configReader.getStruct("WS_ServerControlFlags", client->GetVersion()); + if(packet) { + packet->setDataByName("parameter", param); + packet->setDataByName("value", value); + client->QueuePacket(packet->serialize()); + } + safe_delete(packet); +} +void ClientPacketFunctions::SendServerControlFlags(Client* client, int8 param, int8 param_val, int8 value) { + PacketStruct* packet = configReader.getStruct("WS_ServerControlFlags", client->GetVersion()); + if(packet) { + if (param == 1) + packet->setDataByName("parameter1", param_val); + else if (param == 2) + packet->setDataByName("parameter2", param_val); + else if (param == 3) + packet->setDataByName("parameter3", param_val); + else if (param == 4) + packet->setDataByName("parameter4", param_val); + else if (param == 5) + packet->setDataByName("parameter5", param_val); + else { + safe_delete(packet); + return; + } + packet->setDataByName("value", value); + client->QueuePacket(packet->serialize()); + /* + Some other values for this packet + first param: + 01 flymode + 02 collisons off + 04 unknown + 08 heading movement only + 16 forward/reverse movement only + 32 low gravity + 64 sit + + second + 2 crouch + + + third: + 04 float when trying to jump, no movement + 08 jump high, no movement + 128 walk underwater + + fourth: + 01 moon jump underwater + 04 fear + 16 moon jumps + 32 safe fall (float to ground) + 64 cant move + + fifth: + 01 die + 08 hover (fae) + 32 flymode2? + + */ + } + safe_delete(packet); +} + +void ClientPacketFunctions::SendInstanceList(Client* client) { + if (client->GetPlayer()->GetCharacterInstances()->GetInstanceCount() > 0) { + PacketStruct* packet = configReader.getStruct("WS_InstanceCreated", client->GetVersion()); + if (packet) { + vector persist = client->GetPlayer()->GetCharacterInstances()->GetPersistentInstances(); + vector lockout = client->GetPlayer()->GetCharacterInstances()->GetLockoutInstances(); + + packet->setArrayLengthByName("num_instances", lockout.size()); + for (int32 i = 0; i < lockout.size(); i++) { + InstanceData data = lockout.at(i); + + packet->setArrayDataByName("unknown1", data.db_id, i); // unique id per player + packet->setArrayDataByName("instance_zone_name", data.zone_name.c_str(), i); + packet->setArrayDataByName("unknown2", 0x0B, i); // Always set to 0x0B on live packets + packet->setArrayDataByName("success_last", data.last_success_timestamp, i); + packet->setArrayDataByName("last_failure", data.last_failure_timestamp, i); + packet->setArrayDataByName("failure", data.failure_lockout_time, i); + packet->setArrayDataByName("success", data.success_lockout_time, i); + } + + packet->setArrayLengthByName("num_persistent", persist.size()); + for (int32 i = 0; i < persist.size(); i++) { + InstanceData data = persist.at(i); + + packet->setArrayDataByName("unknown1a", data.db_id, i); // unique id per player + packet->setArrayDataByName("persistent_zone_name", data.zone_name.c_str(), i); + packet->setArrayDataByName("unknown2a", 0x0B, i); // set to 0x0B in all live packets + packet->setArrayDataByName("persist_success_timestamp", data.last_success_timestamp, i); + packet->setArrayDataByName("persist_failure_timestamp", data.last_failure_timestamp, i); + + // Check min duration (last success + failure) + //if (Timer::GetUnixTimeStamp() < data.last_success_timestamp + data.failure_lockout_time*/) + //packet->setArrayDataByName("unknown3b", 1, i); + packet->setArrayDataByName("unknown3b", 1, i); + + packet->setArrayDataByName("minimum_duration", data.failure_lockout_time, i); + packet->setArrayDataByName("maximum_duration", data.success_lockout_time, i); + + packet->setArrayDataByName("unknown4a", 1800, i); // All live logs have 0x0708 + } + + client->QueuePacket(packet->serialize()); + } + safe_delete(packet); + } +} + +void ClientPacketFunctions::SendMaintainedExamineUpdate(Client* client, int8 slot_pos, int32 update_value, int8 update_type){ + if (!client) + return; + + PacketStruct* packet = configReader.getStruct("WS_UpdateMaintainedExamine", client->GetVersion()); + + if (packet){ + packet->setSubstructDataByName("info_header", "show_name", 1); + packet->setSubstructDataByName("info_header", "packettype", 19710); + packet->setSubstructDataByName("info_header", "packetsubtype", 5); + packet->setDataByName("time_stamp", Timer::GetCurrentTime2()); + packet->setDataByName("slot_pos", slot_pos); + packet->setDataByName("update_value", update_value > 0 ? update_value : 0xFFFFFFFF); + packet->setDataByName("update_type", update_type); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } +} + +void ClientPacketFunctions::SendZoneChange(Client* client, char* zone_ip, int16 zone_port, int32 key) { + if (!client) + return; + + PacketStruct* packet = configReader.getStruct("WS_ZoneChangeMsg", client->GetVersion()); + if (packet) { + packet->setDataByName("account_id", client->GetAccountID()); + packet->setDataByName("key", key); + packet->setDataByName("ip_address", zone_ip); + packet->setDataByName("port", zone_port); + client->QueuePacket(packet->serialize()); + } + safe_delete(packet); +} + +void ClientPacketFunctions::SendStateCommand(Client* client, int32 spawn_id, int32 state) { + if (!client || !spawn_id) { + return; + } + + PacketStruct* packet = configReader.getStruct("WS_StateCmd", client->GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", spawn_id); + packet->setDataByName("state", state); + client->QueuePacket(packet->serialize()); + } + safe_delete(packet); +} + +void ClientPacketFunctions::SendFlyMode(Client* client, int8 flymode, bool updateCharProperty) +{ + if (updateCharProperty) + database.insertCharacterProperty(client, CHAR_PROPERTY_FLYMODE, (char*)std::to_string(flymode).c_str()); + if(client->GetVersion() <= 561) { + if(flymode) { + // old flymode + SendServerControlFlagsClassic(client, flymode, 1); + if(flymode == 1) { + // disable noclip + SendServerControlFlagsClassic(client, 2, 0); + } + } + else { + // disable flymode and noclip + SendServerControlFlagsClassic(client, 2, 0); + SendServerControlFlagsClassic(client, 1, 0); + } + } + else { + if(flymode == 2) { + // new flymode + noclip + SendServerControlFlags(client, 5, 32, 1); + SendServerControlFlags(client, 1, 2, 1); + } + else if(flymode == 1) { + // new flymode + SendServerControlFlags(client, 5, 32, 1); + SendServerControlFlags(client, 1, 2, 0); + } + else { + // disable flymode and noclip + SendServerControlFlags(client, 5, 32, 0); + SendServerControlFlags(client, 1, 2, 0); + } + } + + client->Message(CHANNEL_STATUS, "Flymode %s, No Clip %s", flymode > 0 ? "on" : "off", flymode > 1 ? "on" : "off"); + /* + CLASSIC/DOF ONLY HAS THE FIRST SET OF FLAGS + Some other values for this packet + first param: + 01 flymode + 02 collisons off + 04 unknown + 08 forward movement + 16 heading movement + 32 low gravity + 64 sit + + EVERYTHING BELOW NOT SUPPORTED BY CLASSIC/DOF + second + 2 crouch + + + third: + 04 float when trying to jump, no movement + 08 jump high, no movement + + fourth: + 04 autorun (fear?) + 16 moon jumps + 32 safe fall (float to ground) + 64 cant move + + fifth: + 01 die + 08 hover (fae) + 32 flymode2? + + */ +} \ No newline at end of file diff --git a/source/WorldServer/ClientPacketFunctions.h b/source/WorldServer/ClientPacketFunctions.h new file mode 100644 index 0000000..3c4578d --- /dev/null +++ b/source/WorldServer/ClientPacketFunctions.h @@ -0,0 +1,92 @@ +/* + 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 . +*/ +#pragma once +#include "client.h" + +struct HouseZone; +struct PlayerHouse; +struct HeroicOP; + +class ClientPacketFunctions +{ +public: + static void SendFinishedEntitiesList ( Client* client ); + + static void SendLoginDenied ( Client* client ); + static void SendLoginAccepted ( Client* client ); + + static void SendCommandList ( Client* client ); + + static void SendGameWorldTime ( Client* client ); + + static void SendCharacterData ( Client* client ); + static void SendCharacterSheet ( Client* client ); + static void SendSkillBook ( Client* client ); + static void SendTraitList ( Client* client ); + static void SendAbilities ( Client* client ); + + static void SendCommandNamePacket ( Client* client ); + + static void SendQuickBarInit ( Client* client ); + + static void SendMOTD ( Client* client ); + + static void SendCharacterMacros(Client* client); + + static void SendUpdateSpellBook ( Client* client ); + + static void SendSkillSlotMappings(Client* client); + + static void SendRestartZoneMsg(Client* client); + + static void SendServerControlFlags(Client* client, int8 param, int8 param_val, int8 value); + + static void SendServerControlFlagsClassic(Client* client, int32 param, int32 value); + + static void SendInstanceList(Client* client); + + static void SendZoneChange(Client* client, char* zone_ip, int16 zone_port, int32 key); + + static void SendStateCommand(Client* client, int32 spawn_id, int32 state); + + static void SendFlyMode(Client* client, int8 flymode, bool updateCharProperty=true); + + /* Tradeskills (/Tradeskills/TradeskillsPackets.cpp) */ + static void SendCreateFromRecipe(Client* client, int32 recipeID); + static void SendItemCreationUI(Client* client, Recipe* recipe); + static void StopCrafting(Client* client); + static void CounterReaction(Client* client, bool countered); + + static void SendAchievementList(Client* client); + + /* Housing (/Housing/HousingPackets.cpp) */ + static void SendHousePurchase(Client* client, HouseZone* hz, int32 spawnID); + static void SendHousingList(Client* client); + static void SendBaseHouseWindow(Client* client, HouseZone* hz, PlayerHouse* ph, int32 spawnID); + static void SendHouseVisitWindow(Client* client, vector houses); + static void SendLocalizedTextMessage(Client* client); + + /* Heroic OP's (/HeroicOp/HeroicOpPackets.cpp) */ + static void SendHeroicOPUpdate(Client* client, HeroicOP* ho); + + //UI updates for trigger count and damage remaining on maintained spells + static void SendMaintainedExamineUpdate(Client* client, int8 slot_pos, int32 update_value, int8 update_type); +}; + diff --git a/source/WorldServer/Collections/Collections.cpp b/source/WorldServer/Collections/Collections.cpp new file mode 100644 index 0000000..041d3a6 --- /dev/null +++ b/source/WorldServer/Collections/Collections.cpp @@ -0,0 +1,317 @@ +/* +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 . +*/ +#include "Collections.h" + +#include "../../common/Log.h" +#include + +extern MasterCollectionList master_collection_list; + +Collection::Collection() { + id = 0; + memset(name, 0, sizeof(name)); + memset(category, 0, sizeof(category)); + level = 0; + reward_coin = 0; + reward_xp = 0; + completed = false; + save_needed = false; +} + +Collection::Collection(Collection *in) { + vector *collection_items_in; + vector *reward_items_in; + vector::iterator itr; + vector::iterator itr2; + struct CollectionItem *collection_item; + struct CollectionRewardItem *reward_item; + + assert(in); + + id = in->GetID(); + strncpy(name, in->GetName(), sizeof(name)); + strncpy(category, in->GetCategory(), sizeof(category)); + level = in->GetLevel(); + reward_coin = in->GetRewardCoin(); + reward_xp = in->GetRewardXP(); + completed = in->GetCompleted(); + save_needed = in->GetSaveNeeded(); + + collection_items_in = in->GetCollectionItems(); + for (itr = collection_items_in->begin(); itr != collection_items_in->end(); itr++) { + collection_item = new struct CollectionItem; + collection_item->item = (*itr)->item; + collection_item->index = (*itr)->index; + collection_item->found = (*itr)->found; + collection_items.push_back(collection_item); + } + + reward_items_in = in->GetRewardItems(); + for (itr2 = reward_items_in->begin(); itr2 != reward_items_in->end(); itr2++) { + reward_item = new struct CollectionRewardItem; + reward_item->item = (*itr2)->item; + reward_item->quantity = (*itr2)->quantity; + reward_items.push_back(reward_item); + } + + reward_items_in = in->GetSelectableRewardItems(); + for (itr2 = reward_items_in->begin(); itr2 != reward_items_in->end(); itr2++) { + reward_item = new struct CollectionRewardItem; + reward_item->item = (*itr2)->item; + reward_item->quantity = (*itr2)->quantity; + selectable_reward_items.push_back(reward_item); + } +} + +Collection::~Collection() { + vector::iterator itr; + vector::iterator itr2; + + for (itr = collection_items.begin(); itr != collection_items.end(); itr++) + safe_delete(*itr); + for (itr2 = reward_items.begin(); itr2 != reward_items.end(); itr2++) + safe_delete(*itr2); + for (itr2 = selectable_reward_items.begin(); itr2 != selectable_reward_items.end(); itr2++) + safe_delete(*itr2); +} + +void Collection::AddCollectionItem(struct CollectionItem *collection_item) { + assert(collection_item); + + collection_items.push_back(collection_item); +} + +void Collection::AddRewardItem(struct CollectionRewardItem *reward_item) { + assert(reward_item); + + reward_items.push_back(reward_item); +} + +void Collection::AddSelectableRewardItem(struct CollectionRewardItem *reward_item) { + assert(reward_item); + + selectable_reward_items.push_back(reward_item); +} + +bool Collection::NeedsItem(Item *item) { + vector::iterator itr; + struct CollectionItem *collection_item; + + assert(item); + + if (completed) + return false; + + for (itr = collection_items.begin(); itr != collection_items.end(); itr++) { + collection_item = *itr; + if (collection_item->item == item->details.item_id) { + if (collection_item->found) + return false; + else + return true; + } + } + + /* item is not required by this collection at all */ + return false; +} + +struct CollectionItem * Collection::GetCollectionItemByItemID(int32 item_id) { + vector::iterator itr; + struct CollectionItem *collection_item; + + for (itr = collection_items.begin(); itr != collection_items.end(); itr++) { + collection_item = *itr; + if (collection_item->item == item_id) + return collection_item; + } + + return 0; +} + +bool Collection::GetIsReadyToTurnIn() { + vector::iterator itr; + + if (completed) + return false; + + for (itr = collection_items.begin(); itr != collection_items.end(); itr++) { + if (!(*itr)->found) + return false; + } + + return true; +} + +MasterCollectionList::MasterCollectionList() { + mutex_collections.SetName("MasterCollectionList::collections"); +} + +MasterCollectionList::~MasterCollectionList() { + ClearCollections(); +} + +bool MasterCollectionList::AddCollection(Collection *collection) { + bool ret = false; + + assert(collection); + + mutex_collections.writelock(__FUNCTION__, __LINE__); + if (collections.count(collection->GetID()) == 0) { + collections[collection->GetID()] = collection; + ret = true; + } + mutex_collections.releasewritelock(__FUNCTION__, __LINE__); + + return ret; +} + +Collection * MasterCollectionList::GetCollection(int32 collection_id) { + Collection *collection = 0; + + mutex_collections.readlock(__FUNCTION__, __LINE__); + if (collections.count(collection_id) > 0) + collection = collections[collection_id]; + mutex_collections.releasereadlock(__FUNCTION__, __LINE__); + + return collection; +} + +void MasterCollectionList::ClearCollections() { + map::iterator itr; + + mutex_collections.writelock(__FUNCTION__, __LINE__); + for (itr = collections.begin(); itr != collections.end(); itr++) + safe_delete(itr->second); + collections.clear(); + mutex_collections.releasewritelock(__FUNCTION__, __LINE__); +} + +int32 MasterCollectionList::Size() { + int32 size; + + mutex_collections.readlock(__FUNCTION__, __LINE__); + size = collections.size(); + mutex_collections.releasereadlock(__FUNCTION__, __LINE__); + + return size; +} + +bool MasterCollectionList::NeedsItem(Item *item) { + map::iterator itr; + bool ret = false; + + assert(item); + + mutex_collections.readlock(__FUNCTION__, __LINE__); + for (itr = collections.begin(); itr != collections.end(); itr++) { + if (itr->second->NeedsItem(item)) { + ret = true; + break; + } + } + mutex_collections.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +PlayerCollectionList::PlayerCollectionList() { +} + +PlayerCollectionList::~PlayerCollectionList() { + ClearCollections(); +} + +bool PlayerCollectionList::AddCollection(Collection *collection) { + assert(collection); + + if (collections.count(collection->GetID()) == 0) { + collections[collection->GetID()] = collection; + return true; + } + + return false; +} + +Collection * PlayerCollectionList::GetCollection(int32 collection_id) { + if (collections.count(collection_id) > 0) + return collections[collection_id]; + + return 0; +} + +void PlayerCollectionList::ClearCollections() { + map::iterator itr; + + for (itr = collections.begin(); itr != collections.end(); itr++) + safe_delete(itr->second); + collections.clear(); +} + +int32 PlayerCollectionList::Size() { + return collections.size(); +} + +bool PlayerCollectionList::NeedsItem(Item *item) { + map *master_collections; + map::iterator itr; + Collection *collection; + Mutex *master_mutex; + bool ret = false; + + assert(item); + + for (itr = collections.begin(); itr != collections.end(); itr++) { + if (itr->second->NeedsItem(item)) { + ret = true; + break; + } + } + + /* if the player doesnt have a collection that needs the item, check the master collection list to see if there's a collection + * in there that needs the item that the player does not have yet */ + if (!ret) { + master_mutex = master_collection_list.GetMutex(); + master_collections = master_collection_list.GetCollections(); + + master_mutex->readlock(__FUNCTION__, __LINE__); + for (itr = master_collections->begin(); itr != master_collections->end(); itr++) { + collection = itr->second; + if (collection->NeedsItem(item) && !GetCollection(collection->GetID())) { + ret = true; + break; + } + } + master_mutex->releasereadlock(__FUNCTION__, __LINE__); + } + + return ret; +} + +bool PlayerCollectionList::HasCollectionsToHandIn() { + map::iterator itr; + + for (itr = collections.begin(); itr != collections.end(); itr++) { + if (itr->second->GetIsReadyToTurnIn()) + return true; + } + + return false; +} \ No newline at end of file diff --git a/source/WorldServer/Collections/Collections.h b/source/WorldServer/Collections/Collections.h new file mode 100644 index 0000000..eb39873 --- /dev/null +++ b/source/WorldServer/Collections/Collections.h @@ -0,0 +1,107 @@ +#ifndef COLLECTIONS_H_ +#define COLLECTIONS_H_ + +#include "../../common/types.h" +#include "../../common/Mutex.h" +#include "../Items/Items.h" +#include +#include + +using namespace std; + +struct CollectionItem { + int32 item; + int8 index; + int8 found; +}; + +struct CollectionRewardItem { + Item *item; + int8 quantity; +}; + +class Collection { +public: + Collection(); + Collection(Collection *in); + virtual ~Collection(); + + void SetID(int32 id) {this->id = id;} + void SetName(const char *name) {strncpy(this->name, name, sizeof(this->name));} + void SetCategory(const char *category) {strncpy(this->category, category, sizeof(this->category));} + void SetLevel(int8 level) {this->level = level;} + void SetCompleted(bool completed) {this->completed = completed;} + void SetSaveNeeded(bool save_needed) {this->save_needed = save_needed;} + void AddCollectionItem(struct CollectionItem *collection_item); + void AddRewardItem(struct CollectionRewardItem *reward_item); + void AddSelectableRewardItem(struct CollectionRewardItem *reward_item); + void SetRewardCoin(int64 reward_coin) {this->reward_coin = reward_coin;} + void SetRewardXP(int64 reward_xp) {this->reward_xp = reward_xp;} + bool NeedsItem(Item *item); + struct CollectionItem * GetCollectionItemByItemID(int32 item_id); + + int32 GetID() {return id;} + const char * GetName() {return name;} + const char * GetCategory() {return category;} + int8 GetLevel() {return level;} + bool GetIsReadyToTurnIn(); + bool GetCompleted() {return completed;} + bool GetSaveNeeded() {return save_needed;} + vector * GetCollectionItems() {return &collection_items;} + vector * GetRewardItems() {return &reward_items;} + vector * GetSelectableRewardItems() {return &selectable_reward_items;} + int64 GetRewardCoin() {return reward_coin;} + int64 GetRewardXP() {return reward_xp;} + +private: + int32 id; + char name[512]; + char category[512]; + int8 level; + int64 reward_coin; + int64 reward_xp; + bool completed; + bool save_needed; + vector collection_items; + vector reward_items; + vector selectable_reward_items; +}; + +class MasterCollectionList { +public: + MasterCollectionList(); + virtual ~MasterCollectionList(); + + bool AddCollection(Collection *collection); + Collection * GetCollection(int32 collection_id); + void ClearCollections(); + int32 Size(); + bool NeedsItem(Item *item); + + Mutex * GetMutex() {return &mutex_collections;} + map * GetCollections() {return &collections;} + +private: + Mutex mutex_collections; + map collections; +}; + +class PlayerCollectionList { +public: + PlayerCollectionList(); + virtual ~PlayerCollectionList(); + + bool AddCollection(Collection *collection); + Collection * GetCollection(int32 collection_id); + void ClearCollections(); + int32 Size(); + bool NeedsItem(Item *item); + bool HasCollectionsToHandIn(); + + map * GetCollections() {return &collections;} + +private: + map collections; +}; + +#endif \ No newline at end of file diff --git a/source/WorldServer/Collections/CollectionsDB.cpp b/source/WorldServer/Collections/CollectionsDB.cpp new file mode 100644 index 0000000..3e39cbb --- /dev/null +++ b/source/WorldServer/Collections/CollectionsDB.cpp @@ -0,0 +1,295 @@ +/* + 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 . +*/ +#ifdef WIN32 + #include + #include +#endif +#include +#include +#include "../../common/Log.h" +#include "../WorldDatabase.h" +#include "Collections.h" + +extern MasterCollectionList master_collection_list; + + +void WorldDatabase::LoadCollections() +{ + Collection *collection; + Query query; + MYSQL_ROW row; + MYSQL_RES *res; + int32 cItems_total = 0; + int32 cItems_rewards = 0; + + res = query.RunQuery2(Q_SELECT, "SELECT `id`,`collection_name`,`collection_category`,`level`\n" + "FROM `collections`"); + if (res) + { + while ((row = mysql_fetch_row(res))) + { + collection = new Collection(); + collection->SetID(atoul(row[0])); + collection->SetName(row[1]); + collection->SetCategory(row[2]); + collection->SetLevel(atoi(row[3])); + + LogWrite(COLLECTION__DEBUG, 5, "Collect", "\tLoading Collection: '%s' (%u)", collection->GetName(),collection->GetID()); + + if (!master_collection_list.AddCollection(collection)) + { + LogWrite(COLLECTION__ERROR, 0, "Collect", "Error adding collection '%s' - duplicate ID: %u", collection->GetName(),collection->GetID()); + safe_delete(collection); + continue; + } + + cItems_total += LoadCollectionItems(collection); + cItems_rewards += LoadCollectionRewards(collection); + } + } + LogWrite(COLLECTION__DEBUG, 0, "Collect", "\tLoaded %u collections", master_collection_list.Size()); + LogWrite(COLLECTION__DEBUG, 0, "Collect", "\tLoaded %u collection items", cItems_total); + LogWrite(COLLECTION__DEBUG, 0, "Collect", "\tLoaded %u collection rewards", cItems_rewards); +} + +int32 WorldDatabase::LoadCollectionItems(Collection *collection) +{ + struct CollectionItem *collection_item; + Item *item; + Query query; + MYSQL_ROW row; + MYSQL_RES *res; + int32 total = 0; + + assert(collection); + + res = query.RunQuery2(Q_SELECT, "SELECT `item_id`,`item_index`\n" + "FROM `collection_details`\n" + "WHERE `collection_id`=%u\n" + "ORDER BY `item_index` ASC", + collection->GetID()); + + if (res) + { + while ((row = mysql_fetch_row(res))) + { + if ((item = master_item_list.GetItem(atoul(row[0])))) + { + collection_item = new struct CollectionItem; + collection_item->item = atoul(row[0]); + collection_item->index = atoi(row[1]); + collection_item->found = 0; + LogWrite(COLLECTION__DEBUG, 5, "Collect", "\tLoading Collection Item: (%u)", atoul(row[0])); //LogWrite(COLLECTION__DEBUG, 5, "Collect", "\tLoading Collection Item: '%s' (%u)", master_item_list.GetItem(collection_item->item)->name.c_str(), atoul(row[0])); + collection->AddCollectionItem(collection_item); + total++; + } + } + } + if(query.GetErrorNumber()) + LogWrite(COLLECTION__ERROR, 0, "Collect", "Error Loading Collection Items, Query: %s, Error: %s", query.GetQuery(), query.GetError()); + + return total; +} + +int32 WorldDatabase::LoadCollectionRewards(Collection *collection) +{ + struct CollectionRewardItem *reward_item; + Query query; + MYSQL_ROW row; + MYSQL_RES *res; + int32 total = 0; + + assert(collection); + + res = query.RunQuery2(Q_SELECT, "SELECT `reward_type`,`reward_value`,`reward_quantity`\n" + "FROM `collection_rewards`\n" + "WHERE `collection_id`=%u", + collection->GetID()); + + if (res) + { + while ((row = mysql_fetch_row(res))) + { + LogWrite(COLLECTION__DEBUG, 5, "Collect", "\tLoading Collection Reward: Type: %s, Val: %s, Qty: %u", row[0], row[1], atoi(row[2])); + + if (!strcasecmp(row[0], "Item")) + { + reward_item = new struct CollectionRewardItem; + reward_item->item = master_item_list.GetItem(atoul(row[1])); + reward_item->quantity = atoi(row[2]); + collection->AddRewardItem(reward_item); + total++; + } + else if (!strcasecmp(row[0], "Selectable")) + { + reward_item = new struct CollectionRewardItem; + reward_item->item = master_item_list.GetItem(atoul(row[1])); + reward_item->quantity = atoi(row[2]); + collection->AddSelectableRewardItem(reward_item); + total++; + } + else if (!strcasecmp(row[0], "Coin")) + { + collection->SetRewardCoin(atoi64(row[1])); + total++; + } + else if (!strcasecmp(row[0], "XP")) + { + collection->SetRewardXP(atoi64(row[1])); + total++; + } + else + LogWrite(COLLECTION__ERROR, 0, "Collect", "Error adding collection reward to collection '%s'. Unknown reward type '%s'", collection->GetName(), row[0]); + } + } + + if(query.GetErrorNumber()) + LogWrite(COLLECTION__ERROR, 0, "Collect", "Error Loading Collection Rewards, Query: %s, Error: %s", query.GetQuery(), query.GetError()); + + return total; +} + +void WorldDatabase::LoadPlayerCollections(Player *player) +{ + Collection *collection; + Query query; + MYSQL_ROW row; + MYSQL_RES *res; + + assert(player); + + res = query.RunQuery2(Q_SELECT, "SELECT `collection_id`,`completed` FROM `character_collections` WHERE `char_id`=%u", player->GetCharacterID()); + if (res) + { + while ((row = mysql_fetch_row(res))) + { + collection = new Collection(master_collection_list.GetCollection(atoul(row[0]))); + collection->SetCompleted(atoi(row[1])); + + if (!player->GetCollectionList()->AddCollection(collection)) + { + LogWrite(COLLECTION__ERROR, 0, "Collect", "Error adding collection %u to player '%s' - duplicate ID\n", collection->GetID(), player->GetName()); + safe_delete(collection); + continue; + } + + LoadPlayerCollectionItems(player, collection); + } + } + if(query.GetErrorNumber()) + LogWrite(COLLECTION__ERROR, 0, "Collect", "Error Loading Character Collections, Query: %s, Error: %s", query.GetQuery(), query.GetError()); +} + +void WorldDatabase::LoadPlayerCollectionItems(Player *player, Collection *collection) +{ + struct CollectionItem *collection_item; + Query query; + MYSQL_ROW row; + MYSQL_RES *res; + + assert(player); + assert(collection); + + res = query.RunQuery2(Q_SELECT, "SELECT `collection_item_id`\n" + "FROM `character_collection_items`\n" + "WHERE `char_id`=%u\n" + "AND `collection_id`=%u", + player->GetCharacterID(), collection->GetID()); + + if (res) + { + while ((row = mysql_fetch_row(res))) + { + if ((collection_item = collection->GetCollectionItemByItemID(atoul(row[0])))) + collection_item->found = true; + else + LogWrite(COLLECTION__ERROR, 0, "Collect", "Error Loading character collection items. Item ID %u does not exist in collection %s", atoul(row[0]), collection->GetName()); + } + } + if(query.GetErrorNumber()) + LogWrite(COLLECTION__ERROR, 0, "Collect", "Error Loading Character Collection Items, Query: %s, Error: %s", query.GetQuery(), query.GetError()); +} + +void WorldDatabase::SavePlayerCollections(Client *client) +{ + map *collections; + map::iterator itr; + Collection *collection; + + assert(client); + + collections = client->GetPlayer()->GetCollectionList()->GetCollections(); + for (itr = collections->begin(); itr != collections->end(); itr++) + { + collection = itr->second; + if (collection->GetSaveNeeded()) + { + SavePlayerCollection(client, collection); + SavePlayerCollectionItems(client, collection); + collection->SetSaveNeeded(false); + } + } +} + +void WorldDatabase::SavePlayerCollection(Client *client, Collection *collection) +{ + Query query; + + assert(client); + assert(collection); + + query.AddQueryAsync(client->GetCharacterID(), this, Q_UPDATE, "INSERT INTO `character_collections` (`char_id`,`collection_id`,`completed`)\n" + "VALUES (%u,%u,0)\n" + "ON DUPLICATE KEY UPDATE `completed`=%i", + client->GetPlayer()->GetCharacterID(), collection->GetID(), + collection->GetCompleted() ? 1 : 0); +} + +void WorldDatabase::SavePlayerCollectionItems(Client *client, Collection *collection) +{ + vector *collection_items; + vector::iterator itr; + struct CollectionItem *collection_item; + + assert(client); + assert(collection); + + collection_items = collection->GetCollectionItems(); + for (itr = collection_items->begin(); itr != collection_items->end(); itr++) + { + collection_item = *itr; + if (collection_item->found > 0) + SavePlayerCollectionItem(client, collection, collection_item->item); + } +} + +void WorldDatabase::SavePlayerCollectionItem(Client *client, Collection *collection, int32 item_id) +{ + Query query; + + assert(client); + assert(collection); + //assert(item); + + query.AddQueryAsync(client->GetCharacterID(), this, Q_INSERT, "INSERT IGNORE INTO `character_collection_items` (`char_id`,`collection_id`,`collection_item_id`)\n" + "VALUES (%u,%u,%u)", + client->GetPlayer()->GetCharacterID(), collection->GetID(), item_id); +} + diff --git a/source/WorldServer/Combat.cpp b/source/WorldServer/Combat.cpp new file mode 100644 index 0000000..dc0e522 --- /dev/null +++ b/source/WorldServer/Combat.cpp @@ -0,0 +1,1980 @@ +/* + 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 . +*/ +#include "Combat.h" +#include "client.h" +#include "../common/ConfigReader.h" +#include "classes.h" +#include "../common/debug.h" +#include "../common/Log.h" +#include "zoneserver.h" +#include "Skills.h" +#include "classes.h" +#include "World.h" +#include "LuaInterface.h" +#include "Rules/Rules.h" +#include "SpellProcess.h" +#include "World.h" +#include + +extern Classes classes; +extern ConfigReader configReader; +extern MasterSkillList master_skill_list; +extern RuleManager rule_manager; +extern LuaInterface* lua_interface; +extern World world; + +/* ****************************************************************************** + +DamageSpawn() - Damage equation +MeleeAttack() - Melee auto attacks +RangeAttack() - Range auto attacks +DetermineHit() - ToHit chance as well as defender parry / dodge / block / riposte +CheckInterruptSpell() - Interrupt equations + + +No mitigation equations yet + +****************************************************************************** */ + +/* New Combat code */ + +bool Entity::PrimaryWeaponReady() { + //Can only be ready if no ranged timer + if (GetPrimaryLastAttackTime() == 0 || (Timer::GetCurrentTime2() >= (GetPrimaryLastAttackTime() + GetPrimaryAttackDelay()))) { + if (GetRangeLastAttackTime() == 0 || Timer::GetCurrentTime2() >= (GetRangeLastAttackTime() + GetRangeAttackDelay())) + return true; + } + + return false; +} + +bool Entity::SecondaryWeaponReady() { + //Can only be ready if no ranged timer + // if(IsDualWield() && (GetPrimaryLastAttackTime() + if (IsDualWield() && (GetSecondaryLastAttackTime() == 0 || (Timer::GetCurrentTime2() >= (GetSecondaryLastAttackTime() + GetSecondaryAttackDelay())))) { + if(GetRangeLastAttackTime() == 0 || Timer::GetCurrentTime2() >= (GetRangeLastAttackTime() + GetRangeAttackDelay())) + return true; + } + + return false; +} + +bool Entity::RangeWeaponReady() { + //Ranged can only be ready if no other attack timers are active + if(GetRangeLastAttackTime() == 0 || (Timer::GetCurrentTime2() >= (GetRangeLastAttackTime() + GetRangeAttackDelay()))) { + if((GetPrimaryLastAttackTime() == 0 || (Timer::GetCurrentTime2() >= (GetPrimaryLastAttackTime() + GetPrimaryAttackDelay()))) && (GetSecondaryLastAttackTime() == 0 || Timer::GetCurrentTime2() >= (GetSecondaryLastAttackTime() + GetSecondaryAttackDelay()))){ + if(!IsPlayer() || ((Player*)this)->GetRangeAttack()) { + return true; + } + } + } + + return false; +} + +bool Entity::AttackAllowed(Entity* target, float distance, bool range_attack) { + Entity* attacker = this; + Client* client = 0; + if(!target || IsMezzedOrStunned() || IsDazed()) { + LogWrite(COMBAT__DEBUG, 3, "AttackAllowed", "Failed to attack: no target, mezzed, stunned or dazed"); + return false; + } + if (IsPlayer()) + client = ((Player*)this)->GetClient(); + + if (IsPet()) + attacker = ((NPC*)this)->GetOwner(); + if (target->IsNPC() && ((NPC*)target)->IsPet()){ + if (((NPC*)target)->GetOwner()) + target = ((NPC*)target)->GetOwner(); + } + + if((IsBot() || (client || (attacker && (attacker->IsPlayer() || attacker->IsBot())))) && !target->IsPlayer() && !target->GetAttackable()) { + return false; + } + + if (attacker == target) { + LogWrite(COMBAT__DEBUG, 3, "AttackAllowed", "Failed to attack: attacker tried to attack himself or his pet."); + return false; + } + + if (IsPlayer() && target->GetAttackable() == 0) { + LogWrite(COMBAT__DEBUG, 3, "AttackAllowed", "Failed to attack: target is not attackable"); + return false; + } + + if (IsPlayer() && target->IsBot()) { + LogWrite(COMBAT__DEBUG, 3, "AttackAllowed", "Failed to attack: players are not allowed to attack bots"); + return false; + } + + if(rule_manager.GetGlobalRule(R_Combat, LockedEncounterNoAttack)->GetBool()) { + if(target->IsNPC() && (target->GetLockedNoLoot() == ENCOUNTER_STATE_LOCKED || target->GetLockedNoLoot() == ENCOUNTER_STATE_OVERMATCHED) && + !attacker->IsEngagedBySpawnID(target->GetID())) { + return false; + } + else if(IsNPC() && (GetLockedNoLoot() == ENCOUNTER_STATE_LOCKED || GetLockedNoLoot() == ENCOUNTER_STATE_OVERMATCHED) && + !target->IsEngagedBySpawnID(GetID())) { + return false; + } + } + + if (attacker->IsPlayer() && target->IsPlayer()) + { + bool pvp_allowed = rule_manager.GetGlobalRule(R_PVP, AllowPVP)->GetBool(); + if (!pvp_allowed) { + LogWrite(COMBAT__DEBUG, 3, "AttackAllowed", "Failed to attack: pvp is not allowed"); + return false; + } + else + { + sint32 pvpLevelRange = rule_manager.GetGlobalRule(R_PVP, LevelRange)->GetSInt32(); + int32 attackerLevel = attacker->GetLevel(); + int32 defenderLevel = target->GetLevel(); + if ((sint32)abs((sint32)attackerLevel - (sint32)defenderLevel) > pvpLevelRange) + { + LogWrite(COMBAT__DEBUG, 3, "AttackAllowed", "Failed to attack: pvp range of %i exceeded abs(%i-%i).", pvpLevelRange, attackerLevel, defenderLevel); + return false; + } + } + } + + if (target->GetHP() <= 0) { + LogWrite(COMBAT__DEBUG, 3, "AttackAllowed", "Failed to attack: target is dead"); + return false; + } + + if(range_attack && distance != 0) { + Item* weapon = 0; + Item* ammo = 0; + if(attacker->IsPlayer()) { + weapon = ((Player*)attacker)->GetEquipmentList()->GetItem(EQ2_RANGE_SLOT); + ammo = GetAmmoFromSlot(true, true); + } + if(weapon && weapon->IsRanged() && ammo && ammo->IsAmmo() && ammo->IsThrown()) { + // Distance is less then min weapon range + if(distance < weapon->ranged_info->range_low) { + if (client) + client->SimpleMessage(CHANNEL_GENERAL_COMBAT, "Your target is too close! Move back!"); + LogWrite(COMBAT__DEBUG, 3, "AttackAllowed", "Failed to attack: range attack, target to close"); + return false; + } + // Distance is greater then max weapon range + if (distance > (weapon->ranged_info->range_high + ammo->thrown_info->range)) { + if (client) + client->SimpleMessage(CHANNEL_GENERAL_COMBAT, "Your target is too far away! Move closer!"); + LogWrite(COMBAT__DEBUG, 3, "AttackAllowed", "Failed to attack: range attack, target is to far"); + return false; + } + } + } + else if (distance != 0) { + if(distance >= rule_manager.GetGlobalRule(R_Combat, MaxCombatRange)->GetFloat()) { + LogWrite(COMBAT__DEBUG, 3, "AttackAllowed", "Failed to attack: distance is beyond melee range"); + return false; + } + } + + if((target->IsPrivateSpawn() && !target->AllowedAccess(this)) || (IsPrivateSpawn() && AllowedAccess(target))) { + LogWrite(COMBAT__DEBUG, 3, "AttackAllowed", "Access to spawn disallowed."); + return false; + } + + return true; +} + +Item* Entity::GetAmmoFromSlot(bool is_ammo, bool is_thrown) { + Item* ammo = GetEquipmentList()->GetItem(EQ2_AMMO_SLOT); + if(ammo && ammo->IsBag() && IsPlayer()) { + Item* ammo_bag = ammo; + vector* items = ((Player*)this)->GetPlayerItemList()->GetItemsInBag(ammo_bag); + + vector::iterator itr; + int16 i = 0; + Item* tmp_bag_item = 0; + for (itr = items->begin(); itr != items->end(); itr++) { + tmp_bag_item = *itr; + if (tmp_bag_item) { + if(is_ammo && !tmp_bag_item->IsAmmo()) + continue; + if(is_thrown && !tmp_bag_item->IsThrown()) + continue; + ammo = tmp_bag_item; + break; + } + } + } + + if(ammo && is_ammo && !ammo->IsAmmo()) + ammo = nullptr; + if(ammo && is_thrown && !ammo->IsThrown()) + ammo = nullptr; + + return ammo; +} + +void Entity::MeleeAttack(Spawn* victim, float distance, bool primary, bool multi_attack) { + if(!victim) + return; + + int8 damage_type = 0; + int32 min_damage = 0; + int32 max_damage = 0; + if(primary) { + damage_type = GetPrimaryWeaponType(); + min_damage = GetPrimaryWeaponMinDamage(); + max_damage = GetPrimaryWeaponMaxDamage(); + } + else { + damage_type = GetSecondaryWeaponType(); + min_damage = GetSecondaryWeaponMinDamage(); + max_damage = GetSecondaryWeaponMaxDamage(); + } + if (IsStealthed() || IsInvis()) + CancelAllStealth(); + + int8 hit_result = DetermineHit(victim, DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE, damage_type, 0, false); + + if(victim->IsEntity()) { + CheckEncounterState((Entity*)victim); + } + + if(hit_result == DAMAGE_PACKET_RESULT_SUCCESSFUL){ + /*if(GetAdventureClass() == MONK){ + max_damage*=3; + crit_chance = GetLevel()/4+5; + } + else if(GetAdventureClass() == BRUISER){ + min_damage = GetLevel(); + max_damage*=3; + crit_chance = GetLevel()/3+5; + } + if(rand()%100 <=crit_chance){ + max_damage*= 2; + DamageSpawn((Entity*)victim, DAMAGE_PACKET_TYPE_SIMPLE_CRIT_DMG, damage_type, min_damage, max_damage, 0); + } + else*/ + DamageSpawn((Entity*)victim, DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE, damage_type, min_damage, max_damage, 0); + if (!multi_attack) { + CheckProcs(PROC_TYPE_OFFENSIVE, victim); + CheckProcs(PROC_TYPE_PHYSICAL_OFFENSIVE, victim); + } + } + else{ + + GetZone()->SendDamagePacket(this, victim, DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE, hit_result, damage_type, 0, 0); + if(hit_result == DAMAGE_PACKET_RESULT_RIPOSTE && victim->IsEntity()) + ((Entity*)victim)->MeleeAttack(this, distance, true); + } + + //Multi Attack roll + if(!multi_attack){ + float multi_attack = info_struct.get_multi_attack(); + if(multi_attack > 0){ + float chance = multi_attack; + if (multi_attack > 100){ + int8 automatic_multi = (int8)floor((float)(multi_attack / 100)); + chance = (multi_attack - (floor((float) ((multi_attack / 100) * 100)))); + while(automatic_multi > 0){ + MeleeAttack(victim, 100, primary, true); + automatic_multi--; + } + } + if (MakeRandomFloat(0, 100) <= chance) + MeleeAttack(victim, 100, primary, true); + } + } + + //Apply attack speed mods + if(!multi_attack) + SetAttackDelay(primary); + + if(victim->IsNPC() && victim->EngagedInCombat() == false) { + ((NPC*)victim)->AddHate(this, 50); + } + + if (victim->IsEntity() && victim->GetHP() > 0 && ((Entity*)victim)->HasPet()) { + Entity* pet = 0; + bool AddHate = false; + if (victim->IsPlayer()) { + if (((Player*)victim)->GetInfoStruct()->get_pet_behavior() & 1) + AddHate = true; + } + else + AddHate = true; + + if (AddHate) { + pet = ((Entity*)victim)->GetPet(); + if (pet) + pet->AddHate(this, 1); + pet = ((Entity*)victim)->GetCharmedPet(); + if (pet) + pet->AddHate(this, 1); + } + } +} + +void Entity::RangeAttack(Spawn* victim, float distance, Item* weapon, Item* ammo, bool multi_attack) { + if(!victim) + return; + + if(weapon && weapon->IsRanged() && ammo && ammo->IsAmmo() && ammo->IsThrown()) { + if(weapon->ranged_info->range_low <= distance && (weapon->ranged_info->range_high + ammo->thrown_info->range) >= distance) { + int8 hit_result = DetermineHit(victim, DAMAGE_PACKET_TYPE_RANGE_DAMAGE, ammo->thrown_info->damage_type, ammo->thrown_info->hit_bonus, false); + if(hit_result == DAMAGE_PACKET_RESULT_SUCCESSFUL) { + DamageSpawn((Entity*)victim, DAMAGE_PACKET_TYPE_RANGE_DAMAGE, ammo->thrown_info->damage_type, weapon->ranged_info->weapon_info.damage_low3, weapon->ranged_info->weapon_info.damage_high3+ammo->thrown_info->damage_modifier, 0); + if (!multi_attack) { + CheckProcs(PROC_TYPE_OFFENSIVE, victim); + CheckProcs(PROC_TYPE_PHYSICAL_OFFENSIVE, victim); + } + } + else + GetZone()->SendDamagePacket(this, victim, DAMAGE_PACKET_TYPE_RANGE_DAMAGE, hit_result, ammo->thrown_info->damage_type, 0, 0); + + // If is a player subtract ammo + if (IsPlayer()) { + if (ammo->details.count > 1) { + ammo->details.count -= 1; + ammo->save_needed = true; + } + else { + if(ammo->details.inv_slot_id >= 6) { + ((Player*)this)->equipment_list.RemoveItem(ammo->details.slot_id, false); + ((Player*)this)->item_list.DestroyItem(ammo->details.index); + } + else { + ((Player*)this)->equipment_list.RemoveItem(ammo->details.slot_id, true); + } + } + + Client* client = ((Player*)this)->GetClient(); + if(client) { + EQ2Packet* outapp = ((Player*)this)->GetEquipmentList()->serialize(client->GetVersion(), (Player*)this); + if(outapp) + client->QueuePacket(outapp); + + if(ammo->details.inv_slot_id > 6) { + EQ2Packet* outapp = client->GetPlayer()->SendInventoryUpdate(client->GetVersion()); + if (outapp) + client->QueuePacket(outapp); + } + } + } + + if(victim->IsNPC() && victim->EngagedInCombat() == false) { + ((NPC*)victim)->AddHate(this, 50); + } + + if (victim->IsEntity() && victim->GetHP() > 0 && ((Entity*)victim)->HasPet()) { + Entity* pet = 0; + bool AddHate = false; + if (victim->IsPlayer()) { + if (((Player*)victim)->GetInfoStruct()->get_pet_behavior() & 1) + AddHate = true; + } + else + AddHate = true; + + if (AddHate) { + pet = ((Entity*)victim)->GetPet(); + if (pet) + pet->AddHate(this, 1); + pet = ((Entity*)victim)->GetCharmedPet(); + if (pet) + pet->AddHate(this, 1); + } + } + // Check Ranged attack proc + CheckProcs(PROC_TYPE_RANGED_ATTACK, victim); + + // Check Ranged defence proc + if (victim->IsEntity()) + ((Entity*)victim)->CheckProcs(PROC_TYPE_RANGED_DEFENSE, this); + + SetRangeLastAttackTime(Timer::GetCurrentTime2()); + } + } + //Multi Attack roll + if(!multi_attack){ + float multi_attack = info_struct.get_multi_attack(); + if(multi_attack > 0){ + float chance = multi_attack; + if (multi_attack > 100){ + int8 automatic_multi = (int8)floor((float)(multi_attack / 100)); + chance = (multi_attack - (floor((float)(multi_attack / 100) * 100))); + while(automatic_multi > 0){ + RangeAttack(victim, 100, weapon, ammo, true); + automatic_multi--; + } + } + if (MakeRandomFloat(0, 100) <= chance) + RangeAttack(victim, 100, weapon, ammo, true); + } + } + + //Apply attack speed mods + if(!multi_attack) + SetAttackDelay(false, true); +} + +bool Entity::SpellAttack(Spawn* victim, float distance, LuaSpell* luaspell, int8 damage_type, int32 low_damage, int32 high_damage, int8 crit_mod, bool no_calcs, int8 override_packet_type, bool take_power){ + if(!victim || !luaspell || !luaspell->spell) + return false; + + Spell* spell = luaspell->spell; + Skill* skill = nullptr; + int8 packet_type = DAMAGE_PACKET_TYPE_SPELL_DAMAGE; + + if(override_packet_type) { + packet_type = override_packet_type; + } + + int8 hit_result = 0; + bool is_tick = false; // if spell is already active, this is a tick + if (GetZone()->GetSpellProcess()->GetActiveSpells()->count(luaspell)){ + hit_result = DAMAGE_PACKET_RESULT_SUCCESSFUL; + is_tick = true; + } + else if(spell->GetSpellData()->type == SPELL_BOOK_TYPE_COMBAT_ART) + hit_result = DetermineHit(victim, packet_type, damage_type, 0, false, luaspell); + else + hit_result = DetermineHit(victim, packet_type, damage_type, 0, true, luaspell); + + if(victim->IsEntity()) { + CheckEncounterState((Entity*)victim); + } + bool successful_hit = true; + if(hit_result == DAMAGE_PACKET_RESULT_SUCCESSFUL) { + luaspell->last_spellattack_hit = true; + //If this spell is a tick and has already crit, force the tick to crit + if(is_tick){ + if(luaspell->crit) + crit_mod = 1; + else + crit_mod = 2; + } + DamageSpawn((Entity*)victim, packet_type, damage_type, low_damage, high_damage, spell->GetName(), crit_mod, is_tick, no_calcs, false, take_power, luaspell); + + CheckProcs(PROC_TYPE_OFFENSIVE, victim); + CheckProcs(PROC_TYPE_MAGICAL_OFFENSIVE, victim); + + if(spell->GetSpellData()->success_message.length() > 0){ + Client* client = nullptr; + if(IsPlayer()) + client = ((Player*)this)->GetClient(); + if(client){ + string success_message = spell->GetSpellData()->success_message; + if(success_message.find("%t") < 0xFFFFFFFF) + success_message.replace(success_message.find("%t"), 2, victim->GetName()); + client->Message(CHANNEL_YOU_CAST, success_message.c_str()); + //commented out the following line as it was causing a duplicate message EmemJR 5/4/2019 + //GetZone()->SendDamagePacket(this, victim, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, hit_result, damage_type, 0, spell->GetName()); + } + } + if(spell->GetSpellData()->effect_message.length() > 0){ + string effect_message = spell->GetSpellData()->effect_message; + if(effect_message.find("%t") < 0xFFFFFFFF) + effect_message.replace(effect_message.find("%t"), 2, victim->GetName()); + GetZone()->SimpleMessage(CHANNEL_SPELLS, effect_message.c_str(), victim, 50); + } + } + else { + successful_hit = false; + if(hit_result == DAMAGE_PACKET_RESULT_RESIST) + luaspell->resisted = true; + if(victim->IsNPC()) + ((NPC*)victim)->AddHate(this, 5); + luaspell->last_spellattack_hit = false; + GetZone()->SendDamagePacket(this, victim, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, hit_result, damage_type, 0, spell->GetName()); + } + if(EngagedInCombat() == false) + { + LogWrite(MISC__TODO, 1, "TODO", "//It would probably be better to add a column to the spells table for 'starts autoattack'\nfile: %s, func: %s, Line: %i", __FILE__, __FUNCTION__, __LINE__); + int8 class1_ = GetInfoStruct()->get_class1(); + if(class1_ == COMMONER || + class1_ == FIGHTER || + class1_ == WARRIOR || + class1_ == GUARDIAN || + class1_ == BERSERKER || + class1_ == BRAWLER || + class1_ == MONK || + class1_ == BRUISER || + class1_ == CRUSADER || + class1_ == SHADOWKNIGHT || + class1_ == PALADIN || + class1_ == SCOUT || + class1_ == ROGUE || + class1_ == SWASHBUCKLER || + class1_ == BRIGAND || + class1_ == BARD || + class1_ == TROUBADOR || + class1_ == DIRGE || + class1_ == PREDATOR || + class1_ == RANGER || + class1_ == ASSASSIN || + class1_ == ANIMALIST || + class1_ == BEASTLORD || + class1_ == SHAPER || + class1_ == CHANNELER) //note: it would probably be better to add a column to the spells table for "starts autoattack". + { + if (victim->IsNPC()) + ((NPC*)victim)->AddHate(this, 5); + else { + Client* client = 0; + if(IsPlayer()) + client = ((Player*)this)->GetClient(); + if(client) { + client->GetPlayer()->InCombat(true, client->GetPlayer()->GetRangeAttack()); + } + InCombat(true); + } + } + } + + if (victim->IsEntity() && victim->GetHP() > 0 && ((Entity*)victim)->HasPet()) { + Entity* pet = 0; + bool AddHate = false; + if (victim->IsPlayer()) { + if (((Player*)victim)->GetInfoStruct()->get_pet_behavior() & 1) + AddHate = true; + } + else + AddHate = true; + + if (AddHate) { + pet = ((Entity*)victim)->GetPet(); + if (pet) + pet->AddHate(this, 1); + pet = ((Entity*)victim)->GetCharmedPet(); + if (pet) + pet->AddHate(this, 1); + } + } + + return successful_hit; +} + +bool Entity::ProcAttack(Spawn* victim, int8 damage_type, int32 low_damage, int32 high_damage, string name, string success_msg, string effect_msg) { + int8 hit_result = DetermineHit(victim, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, damage_type, 0, true); + + if (hit_result == DAMAGE_PACKET_RESULT_SUCCESSFUL) { + DamageSpawn((Entity*)victim, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, damage_type, low_damage, high_damage, name.c_str()); + + if (success_msg.length() > 0) { + Client* client = 0; + if(IsPlayer()) + client = ((Player*)this)->GetClient(); + if(client) { + if(success_msg.find("%t") < 0xFFFFFFFF) + success_msg.replace(success_msg.find("%t"), 2, victim->GetName()); + client->Message(CHANNEL_YOU_CAST, success_msg.c_str()); + } + } + if (effect_msg.length() > 0) { + if(effect_msg.find("%t") < 0xFFFFFFFF) + effect_msg.replace(effect_msg.find("%t"), 2, victim->GetName()); + GetZone()->SimpleMessage(CHANNEL_SPELLS, effect_msg.c_str(), victim, 50); + } + } + else { + if(victim->IsNPC()) + ((NPC*)victim)->AddHate(this, 5); + GetZone()->SendDamagePacket(this, victim, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, hit_result, damage_type, 0, name.c_str()); + } + + + if (victim->IsEntity() && victim->GetHP() > 0 && ((Entity*)victim)->HasPet()) { + Entity* pet = 0; + bool AddHate = false; + if (victim->IsPlayer()) { + if (((Player*)victim)->GetInfoStruct()->get_pet_behavior() & 1) + AddHate = true; + } + else + AddHate = true; + + if (AddHate) { + pet = ((Entity*)victim)->GetPet(); + if (pet) + pet->AddHate(this, 1); + pet = ((Entity*)victim)->GetCharmedPet(); + if (pet) + pet->AddHate(this, 1); + } + } + + return true; +} + +// this is used exclusively by LUA, heal_type is forced lower case via boost::lower(heal_type); in the LUA Functions used by this +bool Entity::SpellHeal(Spawn* target, float distance, LuaSpell* luaspell, string heal_type, int32 low_heal, int32 high_heal, int8 crit_mod, bool no_calcs, string custom_spell_name){ + if(!target || !luaspell || !luaspell->spell || (target->IsPrivateSpawn() && !target->AllowedAccess(this))) + return false; + + if (!target->Alive()) + return false; + + if (target->GetHP() == target->GetTotalHP()) + return true; + + int32 heal_amt = 0; + bool crit = false; + + if(high_heal < low_heal) + high_heal = low_heal; + if(high_heal == low_heal) + heal_amt = high_heal; + else + heal_amt = MakeRandomInt(low_heal, high_heal); + + if(!no_calcs){ + // if spell is already active, this is a tick + bool is_tick = GetZone()->GetSpellProcess()->GetActiveSpells()->count(luaspell); + + //if is a tick and the spell has crit, force crit, else disable + if(is_tick){ + if(luaspell->crit) + crit_mod = 1; + else + crit_mod = 2; + } + + if (heal_amt > 0){ + if(target->IsEntity()) + heal_amt = (int32)CalculateHealAmount((Entity*)target, (sint32)heal_amt, crit_mod, &crit); + else + heal_amt = (int32)CalculateHealAmount(nullptr, (sint32)heal_amt, crit_mod, &crit); + } + } + + int16 type = 0; + if (heal_type == "heal") { + if(crit) + type = HEAL_PACKET_TYPE_CRIT_HEAL; + else + type = HEAL_PACKET_TYPE_SIMPLE_HEAL; + //apply heal + + if (target->GetHP() + (sint32)heal_amt > target->GetTotalHP()) + heal_amt = target->GetTotalHP() - target->GetHP(); + target->SetHP(target->GetHP() + heal_amt); + + /* + if (target->GetHP() + (sint32)heal_amt > target->GetTotalHP()) + target->SetHP(target->GetTotalHP()); + else + target->SetHP(target->GetHP() + heal_amt); + */ + } + else if (heal_type == "power"){ + if(crit) + type = HEAL_PACKET_TYPE_CRIT_MANA; + else + type = HEAL_PACKET_TYPE_SIMPLE_MANA; + //give power + if (target->GetPower() + (sint32)heal_amt > target->GetTotalPower()) + heal_amt = target->GetTotalPower() - target->GetPower(); + target->SetPower(GetPower() + heal_amt); + + /* + if (target->GetPower() + (sint32)heal_amt > target->GetTotalPower()) + target->SetPower(target->GetTotalPower()); + else + target->SetPower(GetPower() + heal_amt); + */ + } + /*else if (heal_type == "Savagery"){ + if(crit) + type = HEAL_PACKET_TYPE_CRIT_SAVAGERY; + else + type = HEAL_PACKET_TYPE_SAVAGERY; + } + else if (heal_type == "Repair"){ + if(crit) + type = HEAL_PACKET_TYPE_CRIT_REPAIR; + else + type = HEAL_PACKET_TYPE_REPAIR; + }*/ + else{ //default to heal if type cannot be determined + if(crit) + type = HEAL_PACKET_TYPE_CRIT_HEAL; + else + type = HEAL_PACKET_TYPE_SIMPLE_HEAL; + + if (target->GetHP() + (sint32)heal_amt > target->GetTotalHP()) + heal_amt = target->GetTotalHP() - target->GetHP(); + target->SetHP(target->GetHP() + heal_amt); + /* + if (target->GetHP() + (sint32)heal_amt > target->GetTotalHP()) + target->SetHP(target->GetTotalHP()); + else + target->SetHP(target->GetHP() + heal_amt); + */ + } + + target->GetZone()->TriggerCharSheetTimer(); + if (heal_amt > 0) + GetZone()->SendHealPacket(this, target, type, heal_amt, custom_spell_name.length() > 0 ? (char*)custom_spell_name.c_str() : luaspell->spell->GetName()); + CheckProcs(PROC_TYPE_HEALING, target); + CheckProcs(PROC_TYPE_BENEFICIAL, target); + + if (target->IsEntity()) { + int32 hate_amt = heal_amt / 2; + set::iterator itr; + ((Entity*)target)->MHatedBy.lock(); + set hatedByCopy(((Entity*)target)->HatedBy); + ((Entity*)target)->MHatedBy.unlock(); + for (itr = hatedByCopy.begin(); itr != hatedByCopy.end(); itr++) { + Spawn* spawn = GetZone()->GetSpawnByID(*itr); + if (spawn && spawn->IsEntity() && target != this) { + CheckEncounterState((Entity*)spawn); + ((Entity*)spawn)->AddHate(this, hate_amt); + } + } + } + + return true; +} + +int8 Entity::DetermineHit(Spawn* victim, int8 type, int8 damage_type, float ToHitBonus, bool is_caster_spell, LuaSpell* lua_spell){ + if(lua_spell) { + lua_spell->is_damage_spell = true; + } + if(!victim) { + return DAMAGE_PACKET_RESULT_MISS; + } + + if(victim->GetInvulnerable()) { + return DAMAGE_PACKET_RESULT_INVULNERABLE; + } + + bool behind = false; + + // move this above the if statement to assure skill-ups on successful damage return + Skill* skill = GetSkillByWeaponType(type, damage_type, true); + + // Monk added with Brawler to 360 degree support per KoS Prima Official eGuide Fighter: Monk, pg 138, denoted '360-Degree Avoidance!' + if(!victim->IsEntity() || (!is_caster_spell && victim->GetAdventureClass() != BRAWLER && victim->GetAdventureClass() != MONK && (behind = BehindTarget(victim)))) { + return DAMAGE_PACKET_RESULT_SUCCESSFUL; + } + + float bonus = ToHitBonus; + + float skillAddedByWeapon = 0.0f; + if(skill) + { + int16 skillID = master_item_list.GetItemStatIDByName(skill->name.data); + if(skillID != 0xFFFFFFFF) + { + MStats.lock(); + skillAddedByWeapon = stats[skillID]; + if(!is_caster_spell) { + float item_stat_weapon_skill = stats[ITEM_STAT_WEAPON_SKILLS]; + skillAddedByWeapon += item_stat_weapon_skill; + } + MStats.unlock(); + + float max_bonus_skill = GetRuleSkillMaxBonus(); + if(skillAddedByWeapon > max_bonus_skill) { + skillAddedByWeapon = max_bonus_skill; + } + } + } + + if (skill) + bonus += (skill->current_val+skillAddedByWeapon) / 25; + + if(is_caster_spell && lua_spell) { + if(lua_spell->spell->GetSpellData()->resistibility > 0) + bonus -= (1.0f - lua_spell->spell->GetSpellData()->resistibility)*100.0f; + + LogWrite(COMBAT__DEBUG, 9, "Combat", "SpellResist: resistibility %f, bonus %f", lua_spell->spell->GetSpellData()->resistibility, bonus); + // Here we take into account Subjugation, Disruption and Ordination (debuffs) + if(lua_spell->spell->GetSpellData()->mastery_skill) { + int32 master_skill_reduce = rule_manager.GetGlobalRule(R_Spells, MasterSkillReduceSpellResist)->GetInt32(); + if(master_skill_reduce < 1) + master_skill_reduce = 25; + if(IsPlayer() && lua_spell->spell->GetSpellData()->spell_book_type == SPELL_BOOK_TYPE_TRADESKILL && + !((Player*)this)->GetSkills()->HasSkill(lua_spell->spell->GetSpellData()->mastery_skill)) { + ((Player*)this)->AddSkill(lua_spell->spell->GetSpellData()->mastery_skill, 1, ((Player*)this)->GetLevel() * 5, true); + } + + Skill* master_skill = GetSkillByID(lua_spell->spell->GetSpellData()->mastery_skill, true); + if(master_skill && (lua_spell->spell->GetSpellData()->spell_book_type == SPELL_BOOK_TYPE_TRADESKILL || + ((master_skill->name.data == "Subjugation" || master_skill->name.data == "Disruption" || master_skill->name.data == "Ordination" || master_skill->name.data == "Aggression")))) { + float item_stat_bonus = 0.0f; + int32 item_stat = master_item_list.GetItemStatIDByName(::ToLower(master_skill->name.data)); + if(item_stat != 0xFFFFFFFF) { + item_stat_bonus = GetStat(item_stat); + } + bonus += (master_skill->current_val + item_stat_bonus) / master_skill_reduce; + LogWrite(COMBAT__DEBUG, 9, "Combat", "SpellResistMasterySkill: skill %u, stat bonus %f, mastery skill reduce %u, bonus %f", master_skill->current_val, item_stat_bonus, master_skill_reduce, bonus); + + } + } + + } + + if (victim->IsEntity()) + bonus -= ((Entity*)victim)->GetDamageTypeResistPercentage(damage_type); + + LogWrite(COMBAT__DEBUG, 9, "Combat", "DamageResistPercent: type %u, bonus %f", damage_type, bonus); + + + Entity* entity_victim = (Entity*)victim; + float chance = 80 + bonus; //80% base chance that the victim will get hit (plus bonus) + sint16 roll_chance = 100; + if(is_caster_spell) { + chance += CalculateLevelStatBonus(GetWis()); + } + if(skill) + roll_chance -= skill->current_val / 10; + + LogWrite(COMBAT__DEBUG, 9, "Combat", "Chance: fchance %f, roll_chance %i", chance, roll_chance); + + if(!is_caster_spell){ // melee or range attack + skill = GetSkillByName("Offense", true); //add this skill for NPCs + if(skill) + roll_chance -= skill->current_val / 25; + + if(entity_victim->IsImmune(IMMUNITY_TYPE_RIPOSTE)) + return DAMAGE_PACKET_RESULT_RIPOSTE; + + // Avoidance Instructions: https://forums.daybreakgames.com/eq2/index.php?threads/avoidance-faq.482979/ + + /*Parry: reads as parry in the avoidance tooltip, increased by items with +parry on them + Caps at 70%. For plate tanks, works in the front quadrant only, for brawlers this is 360 degrees. + A small % of parries will be ripostes, which not only avoid the attack but also damage your attacker + */ + + skill = entity_victim->GetSkillByName("Parry", true); + if(skill){ + float parryChance = entity_victim->GetInfoStruct()->get_parry(); + float chanceValue = (100.0f - parryChance); + + if(chanceValue < 10.0f) { // min default per https://eq2.fandom.com/wiki/Update:29 + chanceValue = 10.0f; // this becomes 5.0f at EoF + } + + if(rand()%roll_chance >= chanceValue){ //successful parry + /* You may only riposte things in the front quadrant. + Riposte is based off of parry: a certain % of parries turn into ripostes. + */ + if(!behind && victim->InFrontSpawn((Spawn*)this, victim->GetX(), victim->GetZ())) { // if the attacker is not behind the victim, and the victim is facing the attacker (in front of spawn) then we can riposte + float riposteChanceValue = parryChance / 7.0f; // Riposte is based off of parry: a certain % of parries turn into ripostes. Unknown what the value is divided by, 7 to make it 10% even. + if(rand()%100 <= riposteChanceValue) { + entity_victim->CheckProcs(PROC_TYPE_RIPOSTE, this); + return DAMAGE_PACKET_RESULT_RIPOSTE; + } + } + entity_victim->CheckProcs(PROC_TYPE_PARRY, this); + return DAMAGE_PACKET_RESULT_PARRY; + } + } + + skill = nullptr; + + + float blockChance = 0.0f; + if(victim->GetAdventureClass() == BRAWLER) + skill = entity_victim->GetSkillByName("Deflection", true); + + blockChance = entity_victim->GetInfoStruct()->get_block(); + + if(blockChance > 0.0f) + { + blockChance += (blockChance*(entity_victim->GetInfoStruct()->get_block_chance()/100.0f)); + float chanceValue = (100.0f - blockChance); + \ + if(chanceValue < 30.0f) { // min default per https://eq2.fandom.com/wiki/Update:29 + chanceValue = 30.0f; // this becomes 25.0f at EoF + } + /* Non-brawlers may only block things in the front quadrant. + Riposte is based off of parry: a certain % of parries turn into ripostes. + */ + float rnd = rand()%roll_chance; + if(rnd >= chanceValue){ //successful block + if((victim->GetAdventureClass() == BRAWLER || victim->GetAdventureClass() == MONK) || (!behind && victim->InFrontSpawn((Spawn*)this, victim->GetX(), victim->GetZ()))) { // if the attacker is not behind the victim, and the victim is facing the attacker (in front of spawn) then we can block + entity_victim->CheckProcs(PROC_TYPE_BLOCK, this); + return (victim->GetAdventureClass() == BRAWLER) ? DAMAGE_PACKET_RESULT_DEFLECT : DAMAGE_PACKET_RESULT_BLOCK; + } + } + } + + skill = entity_victim->GetSkillByName("Defense", true); + + // calculated in Entity::CalculateBonuses + float dodgeChance = entity_victim->GetInfoStruct()->get_avoidance_base(); + if(dodgeChance > 0.0f) + { + float chanceValue = (100.0f - dodgeChance); + float rnd = rand()%roll_chance; + if(rnd >= chanceValue){ //successful dodge + entity_victim->CheckProcs(PROC_TYPE_EVADE, this); + return DAMAGE_PACKET_RESULT_DODGE;//successfully dodged + } + } + if(rand() % roll_chance >= chance) + return DAMAGE_PACKET_RESULT_MISS; //successfully avoided + } + else{ + float focus_skill_with_bonus = entity_victim->CalculateSkillWithBonus("Spell Avoidance", ITEM_STAT_SPELL_AVOIDANCE, true); + int16 effective_level = entity_victim->GetInfoStruct()->get_effective_level() != 0 ? entity_victim->GetInfoStruct()->get_effective_level() : entity_victim->GetLevel(); + focus_skill_with_bonus += entity_victim->CalculateLevelStatBonus(entity_victim->GetWis()); + chance -= ((focus_skill_with_bonus)/10); + LogWrite(COMBAT__DEBUG, 9, "Combat", "SpellAvoidChance: fchance %f, focus with skill bonus %f", chance, focus_skill_with_bonus); + if(rand()%roll_chance >= chance) { + return DAMAGE_PACKET_RESULT_RESIST; //successfully resisted + } + } + + return DAMAGE_PACKET_RESULT_SUCCESSFUL; +} + +float Entity::GetDamageTypeResistPercentage(int8 damage_type) { + float ret = 1; + + switch(damage_type) { + case DAMAGE_PACKET_DAMAGE_TYPE_CRUSH: + case DAMAGE_PACKET_DAMAGE_TYPE_PIERCE: + case DAMAGE_PACKET_DAMAGE_TYPE_SLASH: { + Skill* skill = GetSkillByName("Defense", true); + if(skill) + ret += skill->current_val / 25; + if(IsNPC()) + LogWrite(COMBAT__DEBUG, 3, "Combat", "DamageType: Crush/Pierce/Slash (%i)", damage_type, ret); + break; + } + case DAMAGE_PACKET_DAMAGE_TYPE_HEAT: { + ret += GetInfoStruct()->get_heat() / 50; + LogWrite(COMBAT__DEBUG, 3, "Combat", "DamageType: Heat (%i), Amt: %.2f", damage_type, ret); + break; + } + case DAMAGE_PACKET_DAMAGE_TYPE_COLD: { + ret += GetInfoStruct()->get_cold() / 50; + LogWrite(COMBAT__DEBUG, 3, "Combat", "DamageType: Cold (%i), Amt: %.2f", damage_type, ret); + break; + } + case DAMAGE_PACKET_DAMAGE_TYPE_MAGIC: { + ret += GetInfoStruct()->get_magic() / 50; + LogWrite(COMBAT__DEBUG, 3, "Combat", "DamageType: Magic (%i), Amt: %.2f", damage_type, ret); + break; + } + case DAMAGE_PACKET_DAMAGE_TYPE_MENTAL: { + ret += GetInfoStruct()->get_mental() / 50; + LogWrite(COMBAT__DEBUG, 3, "Combat", "DamageType: Mental (%i), Amt: %.2f", damage_type, ret); + break; + } + case DAMAGE_PACKET_DAMAGE_TYPE_DIVINE: { + ret += GetInfoStruct()->get_divine() / 50; + LogWrite(COMBAT__DEBUG, 3, "Combat", "DamageType: Divine (%i), Amt: %.2f", damage_type, ret); + break; + } + case DAMAGE_PACKET_DAMAGE_TYPE_DISEASE: { + ret += GetInfoStruct()->get_disease() / 50; + LogWrite(COMBAT__DEBUG, 3, "Combat", "DamageType: Disease (%i), Amt: %.2f", damage_type, ret); + break; + } + case DAMAGE_PACKET_DAMAGE_TYPE_POISON: { + ret += GetInfoStruct()->get_poison() / 50; + LogWrite(COMBAT__DEBUG, 3, "Combat", "DamageType: Poison (%i), Amt: %.2f", damage_type, ret); + break; + } + case DAMAGE_PACKET_DAMAGE_TYPE_FOCUS: { + ret = 0.0f; + break; + } + } + + return ret; +} + +Skill* Entity::GetSkillByWeaponType(int8 type, int8 damage_type, bool update) { + if(type == DAMAGE_PACKET_TYPE_RANGE_DAMAGE) { + return GetSkillByName("Ranged", update); + } + switch(damage_type) { + case DAMAGE_PACKET_DAMAGE_TYPE_SLASH: // slash + return GetSkillByName("Slashing", update); + case DAMAGE_PACKET_DAMAGE_TYPE_CRUSH: // crush + return GetSkillByName("Crushing", update); + case DAMAGE_PACKET_DAMAGE_TYPE_PIERCE: // pierce + return GetSkillByName("Piercing", update); + case DAMAGE_PACKET_DAMAGE_TYPE_HEAT: + case DAMAGE_PACKET_DAMAGE_TYPE_COLD: + case DAMAGE_PACKET_DAMAGE_TYPE_MAGIC: + case DAMAGE_PACKET_DAMAGE_TYPE_MENTAL: + case DAMAGE_PACKET_DAMAGE_TYPE_DIVINE: + case DAMAGE_PACKET_DAMAGE_TYPE_DISEASE: + case DAMAGE_PACKET_DAMAGE_TYPE_POISON: + return GetSkillByName("Disruption", update); + case DAMAGE_PACKET_DAMAGE_TYPE_FOCUS: + return GetSkillByName("Focus", update); + } + + return 0; +} + +bool Entity::DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_damage, int32 high_damage, const char* spell_name, int8 crit_mod, bool is_tick, bool no_calcs, bool ignore_attacker, bool take_power, LuaSpell* spell) { + if(spell) { + spell->is_damage_spell = true; + } + + bool has_damaged = false; + + if(!victim || !victim->Alive() || victim->GetHP() == 0) + return false; + + int8 hit_result = 0; + int16 blow_type = 0; + sint32 damage = 0; + sint32 damage_before_crit = 0; + bool crit = false; + + if(low_damage > high_damage) + high_damage = low_damage; + if(low_damage == high_damage) + damage = low_damage; + else + damage = MakeRandomInt(low_damage, high_damage); + + if(!no_calcs) { + //this can be simplified by taking out the / 2, but I wanted the damage to be more consistent + //damage = (rand()%((int)(high_damage/2-low_damage/2) + low_damage/2)) + (rand()%((int)(high_damage/2-low_damage/2) + low_damage/2)); + //damage = (rand()%((int)(high_damage-low_damage) + low_damage)) + (rand()%((int)(high_damage-low_damage) + low_damage)); + + //DPS mod is only applied to auto attacks + if (type == DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE || type == DAMAGE_PACKET_TYPE_RANGE_DAMAGE) { + if(info_struct.get_dps_multiplier() != 0.0f) { + damage *= (info_struct.get_dps_multiplier()); + } + } + //Potency and ability mod is only applied to spells/CAs + else { + damage = CalculateDamageAmount(victim, damage, type, damage_type, spell); + } + + if(!crit_mod || crit_mod == 1){ + //force crit if crit_mod == 1 + if(crit_mod == 1) + crit = true; + + // Crit Roll + else { + victim->MStats.lock(); + float chance = max((float)0, (info_struct.get_crit_chance() - victim->stats[ITEM_STAT_CRITAVOIDANCE])); + victim->MStats.unlock(); + if (MakeRandomFloat(0, 100) <= chance) + crit = true; + } + if(crit){ + damage_before_crit = damage; + //Apply total crit multiplier with crit bonus + if(info_struct.get_crit_bonus() > 0) + damage *= (1.3 + (info_struct.get_crit_bonus() / 100)); + else + damage *= 1.3; + + // Change packet type to crit + if (type == DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE) + type = DAMAGE_PACKET_TYPE_SIMPLE_CRIT_DMG; + else if (type == DAMAGE_PACKET_TYPE_SPELL_DAMAGE) + type = DAMAGE_PACKET_TYPE_SPELL_CRIT_DMG; + } + } + + if(type == DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE || type == DAMAGE_PACKET_TYPE_SIMPLE_CRIT_DMG || type == DAMAGE_PACKET_TYPE_RANGE_DAMAGE) { + int16 effective_level_attacker = GetInfoStruct()->get_effective_level() != 0 ? GetInfoStruct()->get_effective_level() : GetLevel(); + float mit_percentage = CalculateMitigation(type, damage_type, effective_level_attacker, (IsPlayer() && victim->IsPlayer())); + sint32 damage_to_reduce = (damage * mit_percentage); + if(damage_to_reduce > damage) + damage = 0; + else + damage -= damage_to_reduce; + + // if we reduce damage back below crit level then its no longer a crit, but we don't go below base damage + if(type == DAMAGE_PACKET_TYPE_SIMPLE_CRIT_DMG && damage <= damage_before_crit) { + damage = damage_before_crit; + type = DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE; + } + } + } + + bool useWards = false; + + if(damage <= 0){ + hit_result = DAMAGE_PACKET_RESULT_NO_DAMAGE; + damage = 0; + + if(victim->IsNPC() && victim->GetHP() > 0) + ((Entity*)victim)->AddHate(this, damage); + } + else{ + hit_result = DAMAGE_PACKET_RESULT_SUCCESSFUL; + sint32 return_damage = 0; + if(GetZone()->CallSpawnScript(victim, SPAWN_SCRIPT_HEALTHCHANGED, this, 0, false, damage, &return_damage) && return_damage != 0) + { + // anything not 0 (no return) becomes considered 'immune' to the damage + if(return_damage < 0) { + damage = 0; + hit_result = DAMAGE_PACKET_RESULT_NO_DAMAGE; + } + else if(return_damage != 0) { + // otherwise we use what was given back to us (either less or greater) + damage = return_damage; + } + } + + if(victim->IsNPC() && victim->GetHP() > 0) + ((Entity*)victim)->AddHate(this, damage); + + if(damage) { + int32 prevDmg = damage; + damage = victim->CheckWards(this, damage, damage_type); + + if (damage < (sint64)prevDmg) + useWards = true; + if(damage > 0 && spell) { + has_damaged = true; + spell->has_damaged = true; + } + if(take_power) { + sint32 curPower = victim->GetPower(); + if(curPower < damage) + curPower = 0; + else + curPower -= damage; + victim->SetPower(curPower); + } + else { + victim->TakeDamage(damage); + } + victim->CheckProcs(PROC_TYPE_DAMAGED, this); + + if (IsPlayer()) { + switch (damage_type) { + case DAMAGE_PACKET_DAMAGE_TYPE_SLASH: + case DAMAGE_PACKET_DAMAGE_TYPE_CRUSH: + case DAMAGE_PACKET_DAMAGE_TYPE_PIERCE: + if (((Player*)this)->GetPlayerStatisticValue(STAT_PLAYER_HIGHEST_MELEE_HIT) < damage) + ((Player*)this)->UpdatePlayerStatistic(STAT_PLAYER_HIGHEST_MELEE_HIT, damage, true); + victim->CheckProcs(PROC_TYPE_DAMAGED_MELEE, this); + break; + case DAMAGE_PACKET_DAMAGE_TYPE_HEAT: + case DAMAGE_PACKET_DAMAGE_TYPE_COLD: + case DAMAGE_PACKET_DAMAGE_TYPE_MAGIC: + case DAMAGE_PACKET_DAMAGE_TYPE_MENTAL: + case DAMAGE_PACKET_DAMAGE_TYPE_DIVINE: + case DAMAGE_PACKET_DAMAGE_TYPE_DISEASE: + case DAMAGE_PACKET_DAMAGE_TYPE_POISON: + if (((Player*)this)->GetPlayerStatisticValue(STAT_PLAYER_HIGHEST_MAGIC_HIT) < damage) + ((Player*)this)->UpdatePlayerStatistic(STAT_PLAYER_HIGHEST_MAGIC_HIT, damage, true); + victim->CheckProcs(PROC_TYPE_DAMAGED_MAGIC, this); + break; + } + } + } + } + + Entity* attacker = nullptr; + if(!ignore_attacker) + attacker = this; + if (damage > 0) { + GetZone()->SendDamagePacket(attacker, victim, type, hit_result, damage_type, damage, spell_name); + if (IsStealthed() || IsInvis()) + CancelAllStealth(); + + if (victim->IsEntity()) + ((Entity*)victim)->CheckInterruptSpell(this); + } + else if (useWards) + { + GetZone()->SendDamagePacket(attacker, victim, DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE, DAMAGE_PACKET_RESULT_NO_DAMAGE, damage_type, 0, spell_name); + } + + if (victim->GetHP() <= 0) + KillSpawn(victim, type, damage_type, blow_type); + else { + victim->CheckProcs(PROC_TYPE_DEFENSIVE, this); + if (spell_name) + victim->CheckProcs(PROC_TYPE_MAGICAL_DEFENSIVE, this); + else + victim->CheckProcs(PROC_TYPE_PHYSICAL_DEFENSIVE, this); + } + if(spell) + spell->crit = crit; + return has_damaged; +} + +float Entity::CalculateMitigation(int8 type, int8 damage_type, int16 effective_level_attacker, bool for_pvp) { + int16 effective_level_victim = GetInfoStruct()->get_effective_level() != 0 ? GetInfoStruct()->get_effective_level() : GetLevel(); + if(effective_level_attacker < 1 && effective_level_victim) + effective_level_attacker = effective_level_victim; + else + effective_level_attacker = 1; + + int32 effective_mit_cap = effective_level_victim * rule_manager.GetGlobalRule(R_Combat, EffectiveMitigationCapLevel)->GetInt32(); + float max_mit = (float)GetInfoStruct()->get_max_mitigation(); + if(max_mit == 0.0f) + max_mit = effective_level_victim * 100.0f; + + int32 mit_to_use = 0; + switch(type) { + + case DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE: + case DAMAGE_PACKET_TYPE_RANGE_DAMAGE: + mit_to_use = GetInfoStruct()->get_cur_mitigation(); + break; + case DAMAGE_PACKET_TYPE_SIMPLE_CRIT_DMG: + // since critical mitigation is a percentage we will reverse the mit value so we can add skill from specific types of weapons + mit_to_use = (int32)((float)GetInfoStruct()->get_max_mitigation() * (float)(GetInfoStruct()->get_critical_mitigation()/100.0f)); + break; + + } + + switch(damage_type) { + case DAMAGE_PACKET_DAMAGE_TYPE_SLASH: + mit_to_use += GetInfoStruct()->get_mitigation_skill1(); // slash + break; + case DAMAGE_PACKET_DAMAGE_TYPE_PIERCE: + mit_to_use += GetInfoStruct()->get_mitigation_skill2(); // pierce + break; + case DAMAGE_PACKET_DAMAGE_TYPE_CRUSH: + mit_to_use += GetInfoStruct()->get_mitigation_skill3(); // crush + break; + case DAMAGE_PACKET_DAMAGE_TYPE_FOCUS: + return 0.0f; // focus cannot be mitigated, just break out of this now + break; + default: + // do nothing + break; + } + + if(for_pvp) { + mit_to_use += effective_level_victim * rule_manager.GetGlobalRule(R_Combat, MitigationLevelEffectivenessMax)->GetInt32(); + } + + if(mit_to_use > effective_mit_cap) { + mit_to_use = effective_mit_cap; + } + float level_diff = ((float)effective_level_victim / (float)effective_level_attacker); + if(level_diff > rule_manager.GetGlobalRule(R_Combat, MitigationLevelEffectivenessMax)->GetFloat()) { + level_diff = rule_manager.GetGlobalRule(R_Combat, MitigationLevelEffectivenessMax)->GetFloat(); + } + else if(level_diff < rule_manager.GetGlobalRule(R_Combat, MitigationLevelEffectivenessMax)->GetFloat()) { + level_diff = rule_manager.GetGlobalRule(R_Combat, MitigationLevelEffectivenessMin)->GetFloat(); + } + float mit_percentage = ((float)mit_to_use / max_mit) * level_diff; + + if(!for_pvp && mit_percentage > rule_manager.GetGlobalRule(R_Combat, MaxMitigationAllowed)->GetFloat()) { + mit_percentage = rule_manager.GetGlobalRule(R_Combat, MaxMitigationAllowed)->GetFloat(); + } + else if(for_pvp && mit_percentage > rule_manager.GetGlobalRule(R_Combat, MaxMitigationAllowedPVP)->GetFloat()) { + mit_percentage = rule_manager.GetGlobalRule(R_Combat, MaxMitigationAllowedPVP)->GetFloat(); + } + + return mit_percentage; +} + +void Entity::AddHate(Entity* attacker, sint32 hate) { + if(!attacker || GetHP() <= 0 || attacker->GetHP() <= 0) + return; + + // If a players pet and protect self is off + if (IsPet() && ((NPC*)this)->GetOwner() && ((NPC*)this)->GetOwner()->IsPlayer() && ((((Player*)((NPC*)this)->GetOwner())->GetInfoStruct()->get_pet_behavior() & 2) == 0)) + return; + + hate = attacker->CalculateHateAmount(this, hate); + + if (IsNPC() && ((NPC*)this)->Brain()) { + LogWrite(COMBAT__DEBUG, 3, "Combat", "Add NPC_AI Hate: Victim '%s', Attacker '%s', Hate: %i", GetName(), attacker->GetName(), hate); + ((NPC*)this)->Brain()->AddHate(attacker, hate); + int8 loot_state = ((NPC*)this)->GetLockedNoLoot(); + // if encounter size is 0 then add the attacker to the encounter + if ((loot_state != ENCOUNTER_STATE_AVAILABLE && loot_state != ENCOUNTER_STATE_BROKEN && ((NPC*)this)->Brain()->GetEncounterSize() == 0)) { + ((NPC*)this)->Brain()->AddToEncounter(attacker); + AddTargetToEncounter(attacker); + } + } + + if (attacker->GetThreatTransfer() && hate > 0) { + Spawn* transfer_target = (Entity*)GetZone()->GetSpawnByID(attacker->GetThreatTransfer()->Target); + if (transfer_target && transfer_target->IsEntity()) { + sint32 transfered_hate = hate * (attacker->GetThreatTransfer()->Amount / 100); + hate -= transfered_hate; + this->AddHate((Entity*)transfer_target, transfered_hate); + } + } + + // If pet is adding hate add some to the pets owner as well + if (attacker->IsNPC() && ((NPC*)attacker)->IsPet()) + AddHate(((NPC*)attacker)->GetOwner(), 1); + + // If player and player has a pet and protect master is set add hate to the pet + if (IsPlayer() && HasPet() && (((Player*)this)->GetInfoStruct()->get_pet_behavior() & 1)) { + // If we have a combat pet add hate to it + if (((Player*)this)->GetPet()) + AddHate(((Player*)this)->GetPet(), 1); + if (((Player*)this)->GetCharmedPet()) + AddHate(((Player*)this)->GetCharmedPet(), 1); + } + + // If this spawn has a spawn group then add the attacker to the hate list of the other + // group members if not already in their list + if (HasSpawnGroup()) { + vector* group = GetSpawnGroup(); + vector::iterator itr; + for (itr = group->begin(); itr != group->end(); itr++) { + if (!(*itr)->IsNPC()) + continue; + NPC* spawn = (NPC*)(*itr); + if (spawn->Brain()->GetHate(attacker) == 0) + spawn->Brain()->AddHate(attacker, 1); + } + safe_delete(group); + } +} + +bool Entity::CheckFizzleSpell(LuaSpell* spell) { + if(!spell || !rule_manager.GetGlobalRule(R_Spells, EnableFizzleSpells)->GetInt8() + || spell->spell->GetSpellData()->can_fizzle == false) + return false; + + float fizzleMaxSkill = rule_manager.GetGlobalRule(R_Spells, FizzleMaxSkill)->GetFloat(); + float baseFizzle = rule_manager.GetGlobalRule(R_Spells, DefaultFizzleChance)->GetFloat()/100.0f; // 10% + float skillObtained = rule_manager.GetGlobalRule(R_Spells, FizzleDefaultSkill)->GetFloat(); // default of .2f so we don't go over the threshold if no skill + Skill* skill = GetSkillByID(spell->spell->GetSpellData()->mastery_skill, false); + if(skill && spell->spell->GetSpellData()->min_class_skill_req > 0) + { + float skillObtained = skill->current_val / spell->spell->GetSpellData()->min_class_skill_req; + if(skillObtained > fizzleMaxSkill) // 120% over the skill value + { + skillObtained = fizzleMaxSkill; + } + + baseFizzle = (fizzleMaxSkill - skillObtained) * baseFizzle; + + float totalSuccessChance = 1.0f - baseFizzle; + + float randResult = MakeRandomFloat(0.0f, 1.0f); + if(randResult > totalSuccessChance) + return true; + } + + return false; +} + +bool Entity::CheckInterruptSpell(Entity* attacker) { + if(!IsCasting()) + return false; + + Spell* spell = GetZone()->GetSpell(this); + if(!spell || spell->GetSpellData()->interruptable == 0) + return false; + + if(GetInfoStruct()->get_no_interrupt()) + return false; + + //originally base of 30 percent chance to continue casting if attacked + //modified to 50% and added global rule, 30% was too small at starting levels + int8 percent = rule_manager.GetGlobalRule(R_Spells, NoInterruptBaseChance)->GetInt32(); + + float focus_skill_with_bonus = CalculateSkillWithBonus("Focus", ITEM_STAT_FOCUS, true); + + percent += ((1 + focus_skill_with_bonus)/6); + + if(MakeRandomInt(1, 100) > percent) { + LogWrite(COMBAT__DEBUG, 0, "Combat", "'%s' interrupted spell for '%s': %i%%", attacker->GetName(), GetName(), percent); + GetZone()->Interrupted(this, attacker, SPELL_ERROR_INTERRUPTED); + return true; + } + + LogWrite(COMBAT__DEBUG, 0, "Combat", "'%s' failed to interrupt spell for '%s': %i%%", attacker->GetName(), GetName(), percent); + return false; +} + +void Entity::KillSpawn(Spawn* dead, int8 type, int8 damage_type, int16 kill_blow_type) { + if(!dead) + return; + + if (IsPlayer()) { + Client* client = ((Player*)this)->GetClient(); + if(client) { + PacketStruct* packet = configReader.getStruct("WS_EnterCombat", client->GetVersion()); + if (packet) { + client->QueuePacket(packet->serialize()); + } + safe_delete(packet); + } + + ((Player*)this)->InCombat(false); + } + + if (IsPlayer() && dead->IsEntity()) + GetZone()->GetSpellProcess()->KillHOBySpawnID(dead->GetID()); + + /* just for sake of not knowing if we are in a read lock, write lock, or no lock + ** say spawnlist is locked (DismissPet arg 3 true), which means RemoveSpawn will remove the id from the spawn_list outside of the potential lock + */ + if (dead->IsPet() && ((NPC*)dead)->GetOwner()) + ((NPC*)dead)->GetOwner()->DismissPet((NPC*)dead, true, true); + else if (dead->IsEntity()) { + // remove all pets for this entity + ((Entity*)dead)->DismissAllPets(false, true); + } + + if (IsCasting()) + GetZone()->Interrupted(this, dead, SPELL_ERROR_NOT_ALIVE); + + LogWrite(COMBAT__DEBUG, 3, "Combat", "Killing '%s'", dead->GetName()); + + // Kill movement for the dead npc so the corpse doesn't move + GetZone()->movementMgr->StopNavigation((Entity*)dead); + dead->ClearRunningLocations(); + dead->CalculateRunningLocation(true); + + GetZone()->KillSpawn(true, dead, this, true, type, damage_type, kill_blow_type); +} + +void Entity::HandleDeathExperienceDebt(Spawn* killer) +{ + if(!IsPlayer()) + return; + + float ruleDebt = 0.0f; + + if(killer && killer->IsPlayer()) + ruleDebt = rule_manager.GetGlobalRule(R_Combat, PVPDeathExperienceDebt)->GetFloat()/100.0f; + else + ruleDebt = rule_manager.GetGlobalRule(R_Combat, DeathExperienceDebt)->GetFloat()/100.0f; + + if(ruleDebt > 0.0f) + { + bool groupDebt = rule_manager.GetGlobalRule(R_Combat, GroupExperienceDebt)->GetBool(); + if(groupDebt && ((Player*)this)->GetGroupMemberInfo()) + { + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + PlayerGroup* group = world.GetGroupManager()->GetGroup(((Player*)this)->GetGroupMemberInfo()->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + + int32 size = members->size(); + float xpDebtPerMember = ruleDebt/(float)size; + deque::iterator itr; + for (itr = members->begin(); itr != members->end(); itr++) { + GroupMemberInfo* gmi = *itr; + if (gmi->client && gmi->client->GetPlayer()) { + gmi->client->GetPlayer()->GetInfoStruct()->set_xp_debt(gmi->client->GetPlayer()->GetInfoStruct()->get_xp_debt()+xpDebtPerMember); + gmi->client->GetPlayer()->SetCharSheetChanged(true); + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } + else + { + ((Player*)this)->GetInfoStruct()->set_xp_debt(((Player*)this)->GetInfoStruct()->get_xp_debt()+ruleDebt); + ((Player*)this)->SetCharSheetChanged(true); + } + } +} + +void Entity::ProcessCombat() { + // This is a virtual function so when a NPC calls this it will use the NPC::ProcessCombat() version + // and a player will use the Player::ProcessCombat() version, leave this function blank. +} + +void NPC::ProcessCombat() { + MBrain.writelock(__FUNCTION__, __LINE__); + // Check to see if it is time to call the AI again + if (m_brain && GetHP() > 0 && Timer::GetCurrentTime2() >= (m_brain->LastTick() + m_brain->Tick())) { + // Probably never want to use the following log, will spam the console for every NPC in a zone 4 times a second + //LogWrite(NPC_AI__DEBUG, 9, "NPC_AI", "%s is thinking...", GetName()); + m_brain->Think(); + // Set the time for when the brain was last called + m_brain->SetLastTick(Timer::GetCurrentTime2()); + } + MBrain.releasewritelock(__FUNCTION__, __LINE__); +} + +void Player::ProcessCombat() { + // if not in combat OR casting a spell OR dazed OR feared return out + if (!EngagedInCombat() || IsCasting() || IsDazed() || IsFeared()) + return; + + //If no target delete combat_target and return out + Spawn* Target = GetZone()->GetSpawnByID(target); + if (!Target) { + combat_target = 0; + if (target > 0) { + SetTarget(0); + } + return; + } + // If is not an entity return out + if (!Target->IsEntity()) + return; + + // Reset combat target + combat_target = 0; + + if (Target->HasTarget()) { + if (Target->IsPlayer() || (Target->IsNPC() && Target->IsPet() && ((NPC*)Target)->GetOwner() && ((NPC*)Target)->GetOwner()->IsPlayer())){ + Spawn* secondary_target = Target->GetTarget(); + if (secondary_target->IsNPC() && secondary_target->appearance.attackable) { + if (!secondary_target->IsPet() || (secondary_target->IsPet() && ((NPC*)secondary_target)->GetOwner() && ((NPC*)secondary_target)->GetOwner()->IsNPC())) { + combat_target = secondary_target; + } + } + } + } + + // If combat_target wasn't set in the above if set it to the original target + if (!combat_target) + combat_target = Target; + + // this if may not be required as at the min combat_target will be Target, which we already check at the begining + if(!combat_target) + return; + + float distance = 0; + distance = GetDistance(combat_target); + + // Check to see if we are doing ranged auto attacks if not check to see if we are in melee range + if (GetRangeAttack()) { + // We are doing ranged auto attacks + + //check to see if we can attack the target AND the ranged weapon is ready + if(AttackAllowed((Entity*)combat_target, distance, true) && RangeWeaponReady()) { + Item* weapon = 0; + Item* ammo = 0; + // Get the currently equiped weapon and ammo for the ranged attack + weapon = GetEquipmentList()->GetItem(EQ2_RANGE_SLOT); + ammo = GetAmmoFromSlot(true, true); + LogWrite(COMBAT__DEBUG, 1, "Combat", "Weapon '%s', Ammo '%s'", ( weapon )? weapon->name.c_str() : "None", ( ammo ) ? ammo->name.c_str() : "None"); + + // If weapon and ammo are both valid perform the ranged attack else send a message to the client + if(weapon && ammo) { + LogWrite(COMBAT__DEBUG, 1, "Combat", "Weapon: Primary, Fighter: '%s', Target: '%s', Distance: %.2f", GetName(), combat_target->GetName(), distance); + RangeAttack(combat_target, distance, weapon, ammo); + } + else { + Client* client = ((Player*)this)->GetClient(); + if (client) { + // Need to get messages from live, made these up so the player knows what is wrong in game if weapon or ammo are not valid + if (!ammo) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Out of ammo."); + if (!weapon) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "No ranged weapon found."); + + } + } + } + } + else if(distance <= rule_manager.GetGlobalRule(R_Combat, MaxCombatRange)->GetFloat()) { + // We are doing melee auto attacks and are within range + + // Check to see if we can attack the target + if(AttackAllowed((Entity*)combat_target)) { + // Check to see if the primary melee weapon is ready + if(PrimaryWeaponReady()) { + // Set the time of the last melee attack with the primary weapon and perform the melee attack with primary weapon + SetPrimaryLastAttackTime(Timer::GetCurrentTime2()); + MeleeAttack(combat_target, distance, true); + } + // Check to see if the secondary weapon is ready + if(SecondaryWeaponReady()) { + // set the time of the last melee attack with the secondary weapon and perform the melee attack with the secondary weapon + SetSecondaryLastAttackTime(Timer::GetCurrentTime2()); + MeleeAttack(combat_target, distance, false); + } + } + } +} + +void Entity::SetAttackDelay(bool primary, bool ranged) { + float mod = CalculateAttackSpeedMod(); + bool dual_wield = IsDualWield(); + + //Note: Capping all attack speed increases at 125% normal speed (from function CalculateAttackSpeedMod()) + //Add 33% longer delay if dual wielding + if(dual_wield && ! ranged) { + if(primary) + SetPrimaryAttackDelay((GetPrimaryWeaponDelay() * 1.33) / mod); + else + SetSecondaryAttackDelay((GetSecondaryWeaponDelay() * 1.33) / mod); + } + else { + if(primary) + SetPrimaryAttackDelay(GetPrimaryWeaponDelay() / mod); + else if(ranged) + SetRangeAttackDelay(GetRangeWeaponDelay() / mod); + else + SetSecondaryAttackDelay(GetSecondaryWeaponDelay() / mod); + } +} + +float Entity::CalculateAttackSpeedMod(){ + float aspeed = info_struct.get_attackspeed(); + + if(aspeed > 0) { + if (aspeed <= 100) + return (aspeed / 100 + 1); + else if (aspeed <= 200) + return 2.25; + } + return 1; +} + +void Entity::AddProc(int8 type, float chance, Item* item, LuaSpell* spell, int8 damage_type, int8 hp_ratio, bool below_health, bool target_health, bool extended_version) { + if (type == 0) { + LogWrite(COMBAT__ERROR, 0, "Proc", "Entity::AddProc called with an invalid type."); + return; + } + + if (!item && !spell) { + LogWrite(COMBAT__ERROR, 0, "Proc", "Entity::AddProc must have a valid item or spell."); + return; + } + + MProcList.writelock(__FUNCTION__, __LINE__); + Proc* proc = new Proc(); + proc->chance = chance; + proc->item = item; + proc->spell = spell; + proc->spellid = spell->spell->GetSpellID(); + proc->health_ratio = hp_ratio; + proc->below_health = below_health; + proc->damage_type = damage_type; + proc->target_health = target_health; + proc->extended_version = extended_version; + m_procList[type].push_back(proc); + MProcList.releasewritelock(__FUNCTION__, __LINE__); +} + +void Entity::RemoveProc(Item* item, LuaSpell* spell) { + if (!item && !spell) { + LogWrite(COMBAT__ERROR, 0, "Proc", "Entity::RemoveProc must have a valid item or spell."); + return; + } + + MProcList.writelock(__FUNCTION__, __LINE__); + map >::iterator proc_itr; + vector::iterator itr; + for (proc_itr = m_procList.begin(); proc_itr != m_procList.end(); proc_itr++) { + itr = proc_itr->second.begin(); + while (itr != proc_itr->second.end()) { + Proc* proc = *itr; + + if ((item && proc->item == item) || (spell && proc->spell == spell)) { + itr = proc_itr->second.erase(itr); + safe_delete(proc); + } + else + itr++; + } + } + MProcList.releasewritelock(__FUNCTION__, __LINE__); +} + +bool Entity::CastProc(Proc* proc, int8 type, Spawn* target) { + lua_State* state = 0; + bool item_proc = false; + int8 num_args = 3; + + if (proc->spell) { + state = proc->spell->state; + } + else if (proc->item) { + state = lua_interface->GetItemScript(proc->item->GetItemScript()); + item_proc = true; + } + + if (!state) { + LogWrite(COMBAT__ERROR, 0, "Proc", "No valid lua_State* found"); + return false; + } + + if(proc->extended_version) { + lua_getglobal(state, "proc_ext"); + } + else { + lua_getglobal(state, "proc"); + } + if (item_proc) { + num_args++; + lua_interface->SetItemValue(state, proc->item); + } + + lua_interface->SetSpawnValue(state, this); + lua_interface->SetSpawnValue(state, target); + lua_interface->SetInt32Value(state, type); + lua_interface->SetInt32Value(state, proc->damage_type); + + /* + Add spell data from db in case of a spell proc here... + */ + if (!item_proc) { + // Append spell data to the param list + vector* data = proc->spell->spell->GetLUAData(); + for(int32 i = 0; i < data->size(); i++) { + switch(data->at(i)->type) { + case 0:{ + lua_interface->SetSInt32Value(proc->spell->state, data->at(i)->int_value); + break; + } + case 1:{ + lua_interface->SetFloatValue(proc->spell->state, data->at(i)->float_value); + break; + } + case 2:{ + lua_interface->SetBooleanValue(proc->spell->state, data->at(i)->bool_value); + break; + } + case 3:{ + lua_interface->SetStringValue(proc->spell->state, data->at(i)->string_value.c_str()); + break; + } + default:{ + LogWrite(SPELL__ERROR, 0, "Spell", "Error: Unknown LUA Type '%i' in Entity::CastProc for Spell '%s'", (int)data->at(i)->type, proc->spell->spell->GetName()); + return false; + } + } + num_args++; + } + } + + if (lua_pcall(state, num_args, 0, 0) != 0) { + LogWrite(COMBAT__ERROR, 0, "Proc", "Unable to call the proc function for spell %i tier %i", proc->spell->spell->GetSpellID(), proc->spell->spell->GetSpellTier()); + lua_pop(state, 1); + return false; + } + + return true; +} + +void Entity::CheckProcs(int8 type, Spawn* target) { + if (type == 0) { + LogWrite(COMBAT__ERROR, 0, "Proc", "Entity::CheckProcs called with an invalid type."); + return; + } + + vector tmpList; + + MProcList.readlock(__FUNCTION__, __LINE__); + for (int8 i = 0; i < m_procList[type].size(); i++) { + // roll per proc, not overall + float roll = MakeRandomFloat(0, 100); + Proc* proc = m_procList[type].at(i); + if (roll <= proc->chance && + ((!proc->extended_version || proc->health_ratio == 0) || + (proc->below_health && !proc->target_health && proc->health_ratio < (int8)GetIntHPRatio()) || + (!proc->below_health && !proc->target_health && proc->health_ratio >= (int8)GetIntHPRatio()) || + (target && proc->below_health && proc->target_health && proc->health_ratio < (int8)target->GetIntHPRatio()) || + (target && !proc->below_health && proc->target_health && proc->health_ratio >= (int8)target->GetIntHPRatio())) + ) + { + Proc* tmpProc = new Proc(); + tmpProc->chance = proc->chance; + tmpProc->item = proc->item; + tmpProc->spell = proc->spell; + tmpProc->spellid = proc->spellid; + tmpProc->damage_type = proc->damage_type; + tmpProc->health_ratio = proc->health_ratio; + tmpProc->below_health = proc->below_health; + tmpProc->target_health = proc->target_health; + tmpProc->extended_version = proc->extended_version; + tmpList.push_back(tmpProc); + } + } + MProcList.releasereadlock(__FUNCTION__, __LINE__); + + + vector::iterator proc_itr; + for (proc_itr = tmpList.begin(); proc_itr != tmpList.end();) { + Proc* tmpProc = *proc_itr; + CastProc(tmpProc, type, target); + proc_itr++; + safe_delete(tmpProc); + } +} + +void Entity::ClearProcs() { + MProcList.writelock(__FUNCTION__, __LINE__); + + map >::iterator proc_itr; + vector::iterator itr; + for (proc_itr = m_procList.begin(); proc_itr != m_procList.end(); proc_itr++) { + itr = proc_itr->second.begin(); + while (itr != proc_itr->second.end()) { + safe_delete(*itr); + itr = proc_itr->second.erase(itr); + } + proc_itr->second.clear(); + } + m_procList.clear(); + + MProcList.releasewritelock(__FUNCTION__, __LINE__); +} + +sint32 Entity::CalculateHateAmount(Spawn* target, sint32 amt) { + amt = CalculateFormulaByStat(amt, ITEM_STAT_TAUNT_AMOUNT); + + amt = CalculateFormulaByStat(amt, ITEM_STAT_TAUNT_AND_COMBAT_ART_DAMAGE); + + amt = CalculateFormulaByStat(amt, ITEM_STAT_ABILITY_MODIFIER); + + return amt; +} + +sint32 Entity::CalculateHealAmount(Spawn* target, sint32 amt, int8 crit_mod, bool* crit, bool skip_crit_mod) { + amt = CalculateFormulaByStat(amt, ITEM_STAT_HEAL_AMOUNT); + + amt = CalculateFormulaByStat(amt, ITEM_STAT_SPELL_AND_HEAL); + + //Potency Mod + amt = CalculateFormulaByStat(amt, ITEM_STAT_POTENCY); + + //Ability Mod + amt += (int32)min((int32)GetInfoStruct()->get_ability_modifier(), (int32)(amt / 2)); + + if(!skip_crit_mod){ + if(!crit_mod || crit_mod == 1){ + if(crit_mod == 1) + *crit = true; + else if(!*crit) { + // Crit Roll + float chance = (float)max((float)0, (float)GetInfoStruct()->get_crit_chance()); + *crit = (MakeRandomFloat(0, 100) <= chance); + } + if(*crit){ + //Apply total crit multiplier with crit bonus + amt *= ((GetInfoStruct()->get_crit_bonus() / 100) + 1.3); + } + } + } + + return amt; +} + +sint32 Entity::CalculateDamageAmount(Spawn* target, sint32 damage, int8 base_type, int8 damage_type, LuaSpell* spell) { + return CalculateDamageAmount(target, damage, base_type, damage_type, (spell && spell->spell) ? spell->spell->GetSpellData()->target_type : 0); +} + +sint32 Entity::CalculateDamageAmount(Spawn* target, sint32 damage, int8 base_type, int8 damage_type, int8 target_type) { + if(damage_type == DAMAGE_PACKET_DAMAGE_TYPE_FOCUS) { + return damage; // cannot avoid focus damage + } + + // only spells may add spell damage item stat + if(damage_type >= DAMAGE_PACKET_DAMAGE_TYPE_HEAT && damage_type <= DAMAGE_PACKET_DAMAGE_TYPE_POISON) + { + // https://forums.daybreakgames.com/eq2/index.php?threads/potency-and-ability-mod-what.4316/ + // Spell damage model assuming 100% crit chance: + // (Spell base damage * Base damage modifier * Int bonus * Skill bonus * Potency + Ability modifier) * Crit bonus * Spell double attack + /** Spell base damage: Get all master spells + Base damage modifier: Very very rare. Available from Wizard aa Wisdom line(Brainstorm). Get it, cherish it. + Int bonus: Past 1200 or so int, you will get a 10% increase in spell damage per 30% increase in int. Look at int tooltip to see the numerical value. + Skill bonus: Past skill cap, 100 points skill is worth 2% increase in minimum spell damage, which translates to 1% overall increase. Looks at the skill that the spell uses, for wizards mostly disruption. Cap is 6.5*level. + Potency: A straight damage modifier, the more you can get the better. No practical cap. + Ability modifier: A straight damage modifier. Limited to 50% of the spell base damage, but for a wizard this usually has little consequence. However note that this is not affected by Potency. + Crit bonus: A straight damage modifier, the more you can get the better. No practical cap. Wizards got 50% intrinsic Crit Bonus that does not show up in the stat window, just add 50 to stat value for calculations. + Spell double cast: A straight damage modifier, the more you can get the better. You won't be able to get very much of this. + Makes the spell cast twice with some limitations. + **/ + damage = damage + damage * CalculateLevelStatBonus(GetInt()); // lvl 70 * 1200 int = 84000, log10f(84000) = 4.924 / 50.0f = 0.09848 + damage = CalculateFormulaByStat(damage, ITEM_STAT_SPELL_DAMAGE); + } + + if(base_type == DAMAGE_PACKET_TYPE_SPELL_DAMAGE) { + damage = CalculateFormulaByStat(damage, ITEM_STAT_SPELL_AND_COMBAT_ART_DAMAGE); + + if(target && target->IsEntity() && damage > 0) { + int16 effective_level_attacker = GetInfoStruct()->get_effective_level() != 0 ? GetInfoStruct()->get_effective_level() : GetLevel(); + float resistBase = ((Entity*)target)->GetDamageTypeResistPercentage(damage_type); + if(resistBase > 1.0f) { + float dmgReduction = ((Entity*)target)->CalculateSpellDamageReduction((float)damage, resistBase - 1.0f, effective_level_attacker); + float newDamage = static_cast(damage) - dmgReduction; + if(newDamage < 0.0f) + newDamage = 0.0f; + damage = static_cast(std::floor(newDamage)); + } + } + } + + // combat abilities only bonus + if(damage_type <= DAMAGE_PACKET_DAMAGE_TYPE_PIERCE) { + damage = CalculateFormulaByStat(damage, ITEM_STAT_TAUNT_AND_COMBAT_ART_DAMAGE); + } + + // Potency mod + damage = CalculateFormulaByStat(damage, ITEM_STAT_POTENCY); + + int32 modifier = 2; + if(target_type == SPELL_TARGET_GROUP_AE || target_type == SPELL_TARGET_RAID_AE || target_type == SPELL_TARGET_OTHER_GROUP_AE) + { + modifier = 3; + } + + // Ability mod can only give up to half of damage after potency + int32 mod = (int32)min(info_struct.get_ability_modifier(), (float)(damage / modifier)); + damage += mod; + + return damage; +} + +sint32 Entity::CalculateFormulaByStat(sint32 value, int16 stat) { + sint32 outValue = value; + MStats.lock(); + std::map::iterator itr = stats.find(stat); + if(itr != stats.end()) + outValue = (sint32)((float)value * ((itr->second / 100.0f) + 1.0f)); + MStats.unlock(); + + return outValue; +} + +int32 Entity::CalculateFormulaByStat(int32 value, int16 stat) { + int32 outValue = value; + MStats.lock(); + std::map::iterator itr = stats.find(stat); + if(itr != stats.end()) + outValue = (int32)((float)value * ((itr->second / 100.0f) + 1.0f)); + MStats.unlock(); + + return outValue; +} + +int32 Entity::CalculateFormulaBonus(int32 value, float percent_bonus) { + return (int32)((float)value * ((percent_bonus / 100.0f) + 1.0f)); +} + +float Entity::CalculateSpellDamageReduction(float spellDamage, float resistancePercentage, int16 attackerLevel) { + int16 effective_level = GetInfoStruct()->get_effective_level() != 0 ? GetInfoStruct()->get_effective_level() : GetLevel(); + float levelDifference = std::abs(effective_level - attackerLevel); + float logFactor = 1.0f / (1.0f + 0.1f * levelDifference); // Adjust the factor for smoother reduction + + // Calculate the actual damage reduction based on resistance percentage, logarithmic factor, and maximum reduction + float reducedDamage = spellDamage * (1.0f - resistancePercentage) * logFactor * GetInfoStruct()->get_max_spell_reduction(); + return reducedDamage; +} \ No newline at end of file diff --git a/source/WorldServer/Combat.h b/source/WorldServer/Combat.h new file mode 100644 index 0000000..627aae1 --- /dev/null +++ b/source/WorldServer/Combat.h @@ -0,0 +1,39 @@ +/* + 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 . +*/ +#ifndef __EQ2_COMBAT_H__ +#define __EQ2_COMBAT_H__ +#include "Player.h" +#include "../common/timer.h" +#include "NPC_AI.h" +#include "MutexList.h" + +#define COMBAT_NORMAL_FIGHTER 0 +#define COMBAT_ADD_FIGHTER 1 +#define COMBAT_REMOVE_FIGHTER 2 +// replace with rule rule_manager.GetGlobalRule(R_Combat, MaxCombatRange)->GetFloat() +//#define MAX_COMBAT_RANGE 3 + +class ZoneServer; +class SpellProcess; +class LuaSpell; + + +#endif + diff --git a/source/WorldServer/Commands/Commands.cpp b/source/WorldServer/Commands/Commands.cpp new file mode 100644 index 0000000..349f019 --- /dev/null +++ b/source/WorldServer/Commands/Commands.cpp @@ -0,0 +1,12380 @@ +/* +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 . +*/ +#include +#include +#include + +#include "Commands.h" +#include "../ClientPacketFunctions.h" +#include "../../common/version.h" +#include "../../common/seperator.h" +#include "../../common/servertalk.h" +#include "../WorldDatabase.h" +#include "../World.h" +#include "../../common/ConfigReader.h" +#include "../VisualStates.h" +#include "../../common/debug.h" +#include "../LuaInterface.h" +#include "../Quests.h" +#include "../client.h" +#include "../NPC.h" +#include "../Guilds/Guild.h" +#include "../SpellProcess.h" +#include "../Tradeskills/Tradeskills.h" +#include "../../common/Log.h" +#include "../../common/MiscFunctions.h" +#include "../Languages.h" +#include "../Traits/Traits.h" +#include "../Chat/Chat.h" +#include "../Rules/Rules.h" +#include "../AltAdvancement/AltAdvancement.h" +#include "../RaceTypes/RaceTypes.h" +#include "../classes.h" +#include "../Transmute.h" +#include "../Bots/Bot.h" + +extern WorldDatabase database; +extern MasterSpellList master_spell_list; +extern MasterTraitList master_trait_list; +extern MasterRecipeList master_recipe_list; +extern MasterRecipeBookList master_recipebook_list; +extern World world; +extern ClientList client_list; +extern ConfigReader configReader; +extern VisualStates visual_states; +extern ZoneList zone_list; +extern LuaInterface* lua_interface; +extern MasterQuestList master_quest_list; +extern MasterCollectionList master_collection_list; +extern MasterSkillList master_skill_list; +extern MasterFactionList master_faction_list; +extern GuildList guild_list; +extern MasterLanguagesList master_languages_list; +extern Chat chat; +extern RuleManager rule_manager; +extern MasterAAList master_aa_list; +extern MasterRaceTypeList race_types_list; +extern Classes classes; + +//devn00b: Fix for linux builds since we dont use stricmp we use strcasecmp +#if defined(__GNUC__) +#define stricmp strcasecmp +#define strnicmp strncasecmp +#endif + + +EQ2Packet* RemoteCommands::serialize(int16 version){ + buffer.clear(); + vector::iterator command_list; + buffer.append((char*)&num_commands, sizeof(int16)); + for( command_list = commands.begin(); command_list != commands.end(); command_list++ ) { + AddDataCommand(&(*command_list)); + } + EQ2Packet* app = new EQ2Packet(OP_SetRemoteCmdsMsg, (uchar*)buffer.c_str(), buffer.length() + 1); + return app; +} + +Commands::Commands(){ + remote_commands = new RemoteCommands(); + spawn_set_values["list"] = SPAWN_SET_VALUE_LIST; + spawn_set_values["name"] = SPAWN_SET_VALUE_NAME; + spawn_set_values["level"] = SPAWN_SET_VALUE_LEVEL; // TODO: Fix this, min_level, max_level + spawn_set_values["difficulty"] = SPAWN_SET_VALUE_DIFFICULTY; + spawn_set_values["model_type"] = SPAWN_SET_VALUE_MODEL_TYPE; + spawn_set_values["class"] = SPAWN_SET_VALUE_CLASS; + spawn_set_values["gender"] = SPAWN_SET_VALUE_GENDER; + spawn_set_values["show_name"] = SPAWN_SET_VALUE_SHOW_NAME; + spawn_set_values["attackable"] = SPAWN_SET_VALUE_ATTACKABLE; + spawn_set_values["show_level"] = SPAWN_SET_VALUE_SHOW_LEVEL; + spawn_set_values["targetable"] = SPAWN_SET_VALUE_TARGETABLE; + spawn_set_values["show_command_icon"] = SPAWN_SET_VALUE_SHOW_COMMAND_ICON; + spawn_set_values["display_hand_icon"] = SPAWN_SET_VALUE_HAND_ICON; + spawn_set_values["hair_type"] = SPAWN_SET_VALUE_HAIR_TYPE; + spawn_set_values["facial_hair_type"] = SPAWN_SET_VALUE_FACIAL_HAIR_TYPE; + spawn_set_values["wing_type"] = SPAWN_SET_VALUE_WING_TYPE; + spawn_set_values["chest_type"] = SPAWN_SET_VALUE_CHEST_TYPE; + spawn_set_values["legs_type"] = SPAWN_SET_VALUE_LEGS_TYPE; + spawn_set_values["soga_hair_type"] = SPAWN_SET_VALUE_SOGA_HAIR_TYPE; + spawn_set_values["soga_facial_hair_type"] = SPAWN_SET_VALUE_SOGA_FACIAL_HAIR_TYPE; + spawn_set_values["soga_model_type"] = SPAWN_SET_VALUE_SOGA_MODEL_TYPE; + spawn_set_values["size"] = SPAWN_SET_VALUE_SIZE; + spawn_set_values["hp"] = SPAWN_SET_VALUE_HP; + spawn_set_values["power"] = SPAWN_SET_VALUE_POWER; + spawn_set_values["heroic"] = SPAWN_SET_VALUE_HEROIC; + spawn_set_values["respawn"] = SPAWN_SET_VALUE_RESPAWN; + spawn_set_values["x"] = SPAWN_SET_VALUE_X; + spawn_set_values["y"] = SPAWN_SET_VALUE_Y; + spawn_set_values["z"] = SPAWN_SET_VALUE_Z; + spawn_set_values["heading"] = SPAWN_SET_VALUE_HEADING; + spawn_set_values["location"] = SPAWN_SET_VALUE_LOCATION; + spawn_set_values["command_primary"] = SPAWN_SET_VALUE_COMMAND_PRIMARY; + spawn_set_values["command_secondary"] = SPAWN_SET_VALUE_COMMAND_SECONDARY; + spawn_set_values["visual_state"] = SPAWN_SET_VALUE_VISUAL_STATE; + spawn_set_values["action_state"] = SPAWN_SET_VALUE_ACTION_STATE; + spawn_set_values["mood_state"] = SPAWN_SET_VALUE_MOOD_STATE; + spawn_set_values["initial_state"] = SPAWN_SET_VALUE_INITIAL_STATE; + spawn_set_values["activity_state"] = SPAWN_SET_VALUE_ACTIVITY_STATE; + spawn_set_values["collision_radius"] = SPAWN_SET_VALUE_COLLISION_RADIUS; + spawn_set_values["faction"] = SPAWN_SET_VALUE_FACTION; + spawn_set_values["spawn_script"] = SPAWN_SET_VALUE_SPAWN_SCRIPT; + spawn_set_values["spawnentry_script"] = SPAWN_SET_VALUE_SPAWNENTRY_SCRIPT; + spawn_set_values["spawnlocation_script"] = SPAWN_SET_VALUE_SPAWNLOCATION_SCRIPT; + spawn_set_values["sub_title"] = SPAWN_SET_VALUE_SUB_TITLE; + spawn_set_values["expire"] = SPAWN_SET_VALUE_EXPIRE; + spawn_set_values["expire_offset"] = SPAWN_SET_VALUE_EXPIRE_OFFSET; + spawn_set_values["x_offset"] = SPAWN_SET_VALUE_X_OFFSET; + spawn_set_values["y_offset"] = SPAWN_SET_VALUE_Y_OFFSET; + spawn_set_values["z_offset"] = SPAWN_SET_VALUE_Z_OFFSET; + spawn_set_values["device_id"] = SPAWN_SET_VALUE_DEVICE_ID; + spawn_set_values["pitch"] = SPAWN_SET_VALUE_PITCH; + spawn_set_values["roll"] = SPAWN_SET_VALUE_ROLL; + spawn_set_values["hide_hood"] = SPAWN_SET_VALUE_HIDE_HOOD; + spawn_set_values["emote_state"] = SPAWN_SET_VALUE_EMOTE_STATE; + spawn_set_values["icon"] = SPAWN_SET_VALUE_ICON; + spawn_set_values["prefix"] = SPAWN_SET_VALUE_PREFIX; + spawn_set_values["suffix"] = SPAWN_SET_VALUE_SUFFIX; + spawn_set_values["lastname"] = SPAWN_SET_VALUE_LASTNAME; + spawn_set_values["expansion_flag"] = SPAWN_SET_VALUE_EXPANSION_FLAG; + spawn_set_values["holiday_flag"] = SPAWN_SET_VALUE_HOLIDAY_FLAG; + spawn_set_values["merchant_min_level"] = SPAWN_SET_VALUE_MERCHANT_MIN_LEVEL; + spawn_set_values["merchant_max_level"] = SPAWN_SET_VALUE_MERCHANT_MAX_LEVEL; + spawn_set_values["skin_color"] = SPAWN_SET_SKIN_COLOR; + spawn_set_values["aaxp_rewards"] = SPAWN_SET_AAXP_REWARDS; + + spawn_set_values["hair_color1"] = SPAWN_SET_HAIR_COLOR1; + spawn_set_values["hair_color2"] = SPAWN_SET_HAIR_COLOR2; + spawn_set_values["hair_type_color"] = SPAWN_SET_HAIR_TYPE_COLOR; + spawn_set_values["hair_face_color"] = SPAWN_SET_HAIR_FACE_COLOR; + spawn_set_values["hair_type_highlight_color"] = SPAWN_SET_HAIR_TYPE_HIGHLIGHT_COLOR; + spawn_set_values["face_hairlight_color"] = SPAWN_SET_HAIR_FACE_HIGHLIGHT_COLOR; + spawn_set_values["hair_highlight"] = SPAWN_SET_HAIR_HIGHLIGHT; + spawn_set_values["model_color"] = SPAWN_SET_MODEL_COLOR; + spawn_set_values["eye_color"] = SPAWN_SET_EYE_COLOR; + + spawn_set_values["soga_skin_color"] = SPAWN_SET_SOGA_SKIN_COLOR; + spawn_set_values["soga_hair_color1"] = SPAWN_SET_SOGA_HAIR_COLOR1; + spawn_set_values["soga_hair_color2"] = SPAWN_SET_SOGA_HAIR_COLOR2; + spawn_set_values["soga_hair_type_color"] = SPAWN_SET_SOGA_HAIR_TYPE_COLOR; + spawn_set_values["soga_hair_face_color"] = SPAWN_SET_SOGA_HAIR_FACE_COLOR; + spawn_set_values["soga_hair_type_highlight_color"] = SPAWN_SET_SOGA_HAIR_TYPE_HIGHLIGHT_COLOR; + spawn_set_values["soga_face_hairlight_color"] = SPAWN_SET_SOGA_HAIR_FACE_HIGHLIGHT_COLOR; + spawn_set_values["soga_hair_highlight"] = SPAWN_SET_SOGA_HAIR_HIGHLIGHT; + spawn_set_values["soga_model_color"] = SPAWN_SET_SOGA_MODEL_COLOR; + spawn_set_values["soga_eye_color"] = SPAWN_SET_SOGA_EYE_COLOR; + + spawn_set_values["cheek_type"] = SPAWN_SET_CHEEK_TYPE; + spawn_set_values["chin_type"] = SPAWN_SET_CHIN_TYPE; + spawn_set_values["ear_type"] = SPAWN_SET_EAR_TYPE; + spawn_set_values["eye_brow_type"] = SPAWN_SET_EYE_BROW_TYPE; + spawn_set_values["eye_type"] = SPAWN_SET_EYE_TYPE; + spawn_set_values["lip_type"] = SPAWN_SET_LIP_TYPE; + spawn_set_values["nose_type"] = SPAWN_SET_NOSE_TYPE; + spawn_set_values["body_size"] = SPAWN_SET_BODY_SIZE; + spawn_set_values["body_age"] = SPAWN_SET_BODY_AGE; + spawn_set_values["soga_cheek_type"] = SPAWN_SET_SOGA_CHEEK_TYPE; + spawn_set_values["soga_chin_type"] = SPAWN_SET_SOGA_CHIN_TYPE; + spawn_set_values["soga_ear_type"] = SPAWN_SET_SOGA_EAR_TYPE; + spawn_set_values["soga_eye_brow_type"] = SPAWN_SET_SOGA_EYE_BROW_TYPE; + spawn_set_values["soga_eye_type"] = SPAWN_SET_SOGA_EYE_TYPE; + spawn_set_values["soga_lip_type"] = SPAWN_SET_SOGA_LIP_TYPE; + spawn_set_values["soga_nose_type"] = SPAWN_SET_SOGA_NOSE_TYPE; + spawn_set_values["soga_body_size"] = SPAWN_SET_SOGA_BODY_SIZE; + spawn_set_values["soga_body_age"] = SPAWN_SET_SOGA_BODY_AGE; + spawn_set_values["attack_type"] = SPAWN_SET_ATTACK_TYPE; + spawn_set_values["race_type"] = SPAWN_SET_RACE_TYPE; + spawn_set_values["loot_tier"] = SPAWN_SET_LOOT_TIER; + spawn_set_values["loot_drop_type"] = SPAWN_SET_LOOT_DROP_TYPE; + spawn_set_values["scared_strong_players"] = SPAWN_SET_SCARED_STRONG_PLAYERS; + + zone_set_values["expansion_id"] = ZONE_SET_VALUE_EXPANSION_ID; + zone_set_values["name"] = ZONE_SET_VALUE_NAME; + zone_set_values["file"] = ZONE_SET_VALUE_FILE ; + zone_set_values["description"] = ZONE_SET_VALUE_DESCRIPTION; + zone_set_values["safe_x"] = ZONE_SET_VALUE_SAFE_X; + zone_set_values["safe_y"] = ZONE_SET_VALUE_SAFE_Y; + zone_set_values["safe_z"] = ZONE_SET_VALUE_SAFE_Z; + zone_set_values["underworld"] = ZONE_SET_VALUE_UNDERWORLD; + zone_set_values["min_recommended"] = ZONE_SET_VALUE_MIN_RECOMMENDED; + zone_set_values["max_recommended"] = ZONE_SET_VALUE_MAX_RECOMMENDED; + zone_set_values["zone_type"] = ZONE_SET_VALUE_ZONE_TYPE; + zone_set_values["always_loaded"] = ZONE_SET_VALUE_ALWAYS_LOADED; + zone_set_values["city_zone"] = ZONE_SET_VALUE_CITY_ZONE; + zone_set_values["weather_allowed"] = ZONE_SET_VALUE_WEATHER_ALLOWED; + zone_set_values["min_status"] = ZONE_SET_VALUE_MIN_STATUS; + zone_set_values["min_level"] = ZONE_SET_VALUE_MIN_LEVEL; + zone_set_values["start_zone"] = ZONE_SET_VALUE_START_ZONE; + zone_set_values["instance_type"] = ZONE_SET_VALUE_INSTANCE_TYPE; + zone_set_values["default_reenter_time"] = ZONE_SET_VALUE_DEFAULT_REENTER_TIME; + zone_set_values["default_reset_time"] = ZONE_SET_VALUE_DEFAULT_RESET_TIME; + zone_set_values["default_lockout_time"] = ZONE_SET_VALUE_DEFAULT_LOCKOUT_TIME; + zone_set_values["force_group_to_zone"] = ZONE_SET_VALUE_FORCE_GROUP_TO_ZONE; + zone_set_values["lua_script"] = ZONE_SET_VALUE_LUA_SCRIPT; + zone_set_values["shutdown_timer"] = ZONE_SET_VALUE_SHUTDOWN_TIMER; + zone_set_values["zone_motd"] = ZONE_SET_VALUE_ZONE_MOTD; +} + +Commands::~Commands() { + safe_delete(remote_commands); +} + +int32 Commands::GetSpawnSetType(string val){ + if(spawn_set_values.count(val) > 0) + return spawn_set_values[val]; + return 0xFFFFFFFF; +} + +bool Commands::SetSpawnCommand(Client* client, Spawn* target, int8 type, const char* value, bool send_update, bool temporary, string* temp_value, int8 index){ + if(!target) + return false; + int32 val = 0; + try{ + if(type != SPAWN_SET_VALUE_NAME && + !(type >= SPAWN_SET_VALUE_SPAWN_SCRIPT && type <= SPAWN_SET_VALUE_SUB_TITLE) && !(type >= SPAWN_SET_VALUE_PREFIX && type <= SPAWN_SET_VALUE_EXPANSION_FLAG || type == SPAWN_SET_VALUE_HOLIDAY_FLAG)) + { + switch(type) + { + case SPAWN_SET_SKIN_COLOR: + case SPAWN_SET_HAIR_COLOR1: + case SPAWN_SET_HAIR_COLOR2: + case SPAWN_SET_HAIR_TYPE_COLOR: + case SPAWN_SET_HAIR_FACE_COLOR: + case SPAWN_SET_HAIR_TYPE_HIGHLIGHT_COLOR: + case SPAWN_SET_HAIR_FACE_HIGHLIGHT_COLOR: + case SPAWN_SET_HAIR_HIGHLIGHT: + case SPAWN_SET_EYE_COLOR: + case SPAWN_SET_SOGA_SKIN_COLOR: + case SPAWN_SET_SOGA_HAIR_COLOR1: + case SPAWN_SET_SOGA_HAIR_COLOR2: + case SPAWN_SET_SOGA_HAIR_TYPE_COLOR: + case SPAWN_SET_SOGA_HAIR_FACE_COLOR: + case SPAWN_SET_SOGA_HAIR_TYPE_HIGHLIGHT_COLOR: + case SPAWN_SET_SOGA_HAIR_FACE_HIGHLIGHT_COLOR: + case SPAWN_SET_SOGA_HAIR_HIGHLIGHT: + case SPAWN_SET_SOGA_EYE_COLOR: + case SPAWN_SET_RACE_TYPE: + // ignore these are colors can't pass as a integer value + break; + default: + val = atoul(value); + } + } + } + catch(...){ + if(client) + client->Message(CHANNEL_COLOR_RED, "Invalid numeric spawn value: %s", value); + return false; + } + if(temporary){ + char tmp[128] = {0}; + switch(type){ + case SPAWN_SET_VALUE_NAME:{ + sprintf(tmp, "%s", target->GetName()); + target->SetName(value); + if(target->GetZone()) + target->GetZone()->SendUpdateTitles(target); + break; + } + case SPAWN_SET_VALUE_X_OFFSET: + { + sprintf(tmp, "%f", target->GetXOffset()); + target->SetXOffset(float(val)); + break; + } + case SPAWN_SET_VALUE_Y_OFFSET: + { + sprintf(tmp, "%f", target->GetYOffset()); + target->SetYOffset(float(val)); + break; + } + case SPAWN_SET_VALUE_Z_OFFSET: + { + sprintf(tmp, "%f", target->GetZOffset()); + target->SetZOffset(float(val)); + break; + } + case SPAWN_SET_VALUE_EXPIRE: { + sprintf(tmp, "%u", target->GetExpireTime()); + target->SetExpireTime(val); + break; + } + case SPAWN_SET_VALUE_EXPIRE_OFFSET: { + sprintf(tmp, "%u", target->GetExpireOffsetTime()); + target->SetExpireOffsetTime(val); + break; + } + case SPAWN_SET_VALUE_SUB_TITLE: { + sprintf(tmp, "%s", target->GetSubTitle()); + target->SetSubTitle(value); + if(target->GetZone()) + target->GetZone()->SendUpdateTitles(target); + break; + } + case SPAWN_SET_VALUE_LEVEL:{ + sprintf(tmp, "%i", target->GetLevel()); + target->SetLevel(val, send_update); + break; + } + case SPAWN_SET_VALUE_DIFFICULTY:{ + sprintf(tmp, "%i", target->GetDifficulty()); + target->SetDifficulty(val, send_update); + break; + } + case SPAWN_SET_VALUE_MODEL_TYPE:{ + sprintf(tmp, "%i", target->GetModelType()); + target->SetModelType(val, send_update); + break; + } + case SPAWN_SET_VALUE_CLASS:{ + sprintf(tmp, "%i", target->GetAdventureClass()); + target->SetAdventureClass(val, send_update); + break; + } + case SPAWN_SET_VALUE_GENDER:{ + sprintf(tmp, "%i", target->GetGender()); + target->SetGender(val, send_update); + break; + } + case SPAWN_SET_VALUE_SHOW_NAME:{ + sprintf(tmp, "%i", target->GetShowName()); + target->SetShowName(val, send_update); + break; + } + case SPAWN_SET_VALUE_ATTACKABLE:{ + sprintf(tmp, "%i", target->GetAttackable()); + target->SetAttackable(val, send_update); + break; + } + case SPAWN_SET_VALUE_SHOW_LEVEL:{ + sprintf(tmp, "%i", target->GetShowLevel()); + target->SetShowLevel(val, send_update); + break; + } + case SPAWN_SET_VALUE_TARGETABLE:{ + sprintf(tmp, "%i", target->GetTargetable()); + target->SetTargetable(val, send_update); + break; + } + case SPAWN_SET_VALUE_SHOW_COMMAND_ICON:{ + sprintf(tmp, "%i", target->GetShowCommandIcon()); + target->SetShowCommandIcon(val, send_update); + break; + } + case SPAWN_SET_VALUE_HAND_ICON:{ + sprintf(tmp, "%i", target->GetShowHandIcon()); + target->SetShowHandIcon(val, send_update); + break; + } + case SPAWN_SET_VALUE_HAIR_TYPE:{ + if(target->IsEntity()){ + sprintf(tmp, "%i", ((Entity*)target)->GetHairType()); + ((Entity*)target)->SetHairType(val, send_update); + } + break; + } + case SPAWN_SET_VALUE_FACIAL_HAIR_TYPE:{ + if(target->IsEntity()){ + sprintf(tmp, "%i", ((Entity*)target)->GetFacialHairType()); + ((Entity*)target)->SetFacialHairType(val, send_update); + } + break; + } + case SPAWN_SET_VALUE_WING_TYPE:{ + if(target->IsEntity()){ + sprintf(tmp, "%i", ((Entity*)target)->GetWingType()); + ((Entity*)target)->SetWingType(val, send_update); + } + break; + } + case SPAWN_SET_VALUE_CHEST_TYPE:{ + if(target->IsEntity()){ + sprintf(tmp, "%i", ((Entity*)target)->GetChestType()); + ((Entity*)target)->SetChestType(val, send_update); + } + break; + } + case SPAWN_SET_VALUE_LEGS_TYPE:{ + if(target->IsEntity()){ + sprintf(tmp, "%i", ((Entity*)target)->GetLegsType()); + ((Entity*)target)->SetLegsType(val, send_update); + } + break; + } + case SPAWN_SET_VALUE_SOGA_HAIR_TYPE:{ + if(target->IsEntity()){ + sprintf(tmp, "%i", ((Entity*)target)->GetSogaHairType()); + ((Entity*)target)->SetSogaHairType(val, send_update); + } + break; + } + case SPAWN_SET_VALUE_SOGA_FACIAL_HAIR_TYPE:{ + if(target->IsEntity()){ + sprintf(tmp, "%i", ((Entity*)target)->GetSogaFacialHairType()); + ((Entity*)target)->SetSogaFacialHairType(val, send_update); + } + break; + } + case SPAWN_SET_VALUE_SOGA_MODEL_TYPE:{ + sprintf(tmp, "%i", target->GetSogaModelType()); + target->SetSogaModelType(val, send_update); + break; + } + case SPAWN_SET_VALUE_SIZE:{ + sprintf(tmp, "%i", target->GetSize()); + target->SetSize(val, send_update); + break; + } + case SPAWN_SET_VALUE_HP:{ + sprintf(tmp, "%i", target->GetHP()); + target->SetTotalHPBase(val); + target->SetHP(val, send_update); + break; + } + case SPAWN_SET_VALUE_POWER:{ + sprintf(tmp, "%i", target->GetPower()); + target->SetTotalPowerBase(val); + target->SetPower(val, send_update); + break; + } + case SPAWN_SET_VALUE_HEROIC:{ + sprintf(tmp, "%i", target->GetHeroic()); + target->SetHeroic(val, send_update); + break; + } + case SPAWN_SET_VALUE_RESPAWN:{ + sprintf(tmp, "%u", target->GetRespawnTime()); + target->SetRespawnTime(val); + break; + } + case SPAWN_SET_VALUE_X:{ + sprintf(tmp, "%f", target->GetX()); + target->SetX(atof(value), send_update); + break; + } + case SPAWN_SET_VALUE_Y:{ + sprintf(tmp, "%f", target->GetY()); + target->SetY(atof(value), send_update); + break; + } + case SPAWN_SET_VALUE_Z:{ + sprintf(tmp, "%f", target->GetZ()); + target->SetZ(atof(value), send_update); + break; + } + case SPAWN_SET_VALUE_HEADING:{ + sprintf(tmp, "%f", target->GetHeading()); + target->SetHeading(atof(value) + 360, send_update); + break; + } + case SPAWN_SET_VALUE_VISUAL_STATE:{ + sprintf(tmp, "%i", target->GetVisualState()); + target->SetVisualState(val, send_update); + break; + } + case SPAWN_SET_VALUE_ACTION_STATE:{ + sprintf(tmp, "%i", target->GetActionState()); + target->SetActionState(val, send_update); + break; + } + case SPAWN_SET_VALUE_MOOD_STATE:{ + sprintf(tmp, "%i", target->GetMoodState()); + target->SetMoodState(val, send_update); + break; + } + case SPAWN_SET_VALUE_INITIAL_STATE:{ + sprintf(tmp, "%i", target->GetInitialState()); + target->SetInitialState(val, send_update); + break; + } + case SPAWN_SET_VALUE_ACTIVITY_STATE:{ + sprintf(tmp, "%i", target->GetActivityStatus()); + target->SetActivityStatus(val, send_update); + break; + } + case SPAWN_SET_VALUE_COLLISION_RADIUS:{ + sprintf(tmp, "%i", target->GetCollisionRadius()); + target->SetCollisionRadius(val, send_update); + break; + } + case SPAWN_SET_VALUE_DEVICE_ID: { + if (target->IsObject()) { + sprintf(tmp, "%i", ((Object*)target)->GetDeviceID()); + ((Object*)target)->SetDeviceID(val); + } + break; + } + case SPAWN_SET_VALUE_PITCH: { + sprintf(tmp, "%f", target->GetPitch()); + target->SetPitch(atof(value), send_update); + break; + } + case SPAWN_SET_VALUE_ROLL: { + sprintf(tmp, "%f", target->GetRoll()); + target->SetRoll(atof(value), send_update); + break; + } + case SPAWN_SET_VALUE_HIDE_HOOD: { + sprintf(tmp, "%i", target->appearance.hide_hood); + target->SetHideHood(val); + break; + } + case SPAWN_SET_VALUE_EMOTE_STATE: { + sprintf(tmp, "%i", target->appearance.emote_state); + target->SetEmoteState(val); + break; + } + case SPAWN_SET_VALUE_ICON: { + sprintf(tmp, "%i", target->GetIconValue()); + target->SetIcon(val); + break; + } + + case SPAWN_SET_VALUE_PREFIX: { + sprintf(tmp, "%s", target->GetPrefixTitle()); + target->SetPrefixTitle(value); + if(target->GetZone()) + target->GetZone()->SendUpdateTitles(target); + break; + } + + case SPAWN_SET_VALUE_SUFFIX: { + sprintf(tmp, "%s", target->GetSuffixTitle()); + target->SetSuffixTitle(value); + if(target->GetZone()) + target->GetZone()->SendUpdateTitles(target); + break; + } + + case SPAWN_SET_VALUE_LASTNAME: { + sprintf(tmp, "%s", target->GetLastName()); + target->SetLastName(value); + if(target->GetZone()) + target->GetZone()->SendUpdateTitles(target); + break; + } + case SPAWN_SET_VALUE_EXPANSION_FLAG: + case SPAWN_SET_VALUE_HOLIDAY_FLAG: { + // nothing to do must reload spawns + break; + } + case SPAWN_SET_VALUE_MERCHANT_MIN_LEVEL: { + sprintf(tmp, "%i", target->GetMerchantMinLevel()); + target->SetMerchantLevelRange(atoul(value), target->GetMerchantMaxLevel()); + break; + } + case SPAWN_SET_VALUE_MERCHANT_MAX_LEVEL: { + sprintf(tmp, "%i", target->GetMerchantMaxLevel()); + target->SetMerchantLevelRange(target->GetMerchantMinLevel(), atoul(value)); + break; + } + case SPAWN_SET_VALUE_FACTION:{ + sprintf(tmp, "%i", target->faction_id); + ZoneServer* zone = target->GetZone(); + if (!zone && client) + zone = client->GetCurrentZone(); + + target->faction_id = atoul(value); + if(zone) + { + zone->RemoveDeadEnemyList(target); + if(target->IsNPC()) + zone->AddEnemyList((NPC*)target); + } + break; + } + case SPAWN_SET_SKIN_COLOR: + case SPAWN_SET_HAIR_COLOR1: + case SPAWN_SET_HAIR_COLOR2: + case SPAWN_SET_HAIR_TYPE_COLOR: + case SPAWN_SET_HAIR_FACE_COLOR: + case SPAWN_SET_HAIR_TYPE_HIGHLIGHT_COLOR: + case SPAWN_SET_HAIR_FACE_HIGHLIGHT_COLOR: + case SPAWN_SET_HAIR_HIGHLIGHT: + case SPAWN_SET_MODEL_COLOR: + case SPAWN_SET_EYE_COLOR: + case SPAWN_SET_SOGA_SKIN_COLOR: + case SPAWN_SET_SOGA_HAIR_COLOR1: + case SPAWN_SET_SOGA_HAIR_COLOR2: + case SPAWN_SET_SOGA_HAIR_TYPE_COLOR: + case SPAWN_SET_SOGA_HAIR_FACE_COLOR: + case SPAWN_SET_SOGA_HAIR_TYPE_HIGHLIGHT_COLOR: + case SPAWN_SET_SOGA_HAIR_FACE_HIGHLIGHT_COLOR: + case SPAWN_SET_SOGA_HAIR_HIGHLIGHT: + case SPAWN_SET_SOGA_MODEL_COLOR: + case SPAWN_SET_SOGA_EYE_COLOR: + { + if (target->IsEntity()) + { + Seperator* skinsep = new Seperator(value, ' ', 3, 500, true); + if (skinsep->IsNumber(0) && skinsep->IsNumber(1) && skinsep->IsNumber(2)) + { + EQ2_Color clr; + clr.red = atoul(skinsep->arg[0]); + clr.green = atoul(skinsep->arg[1]); + clr.blue = atoul(skinsep->arg[2]); + + switch(type) + { + case SPAWN_SET_SKIN_COLOR: + ((Entity*)target)->SetSkinColor(clr); + break; + case SPAWN_SET_HAIR_COLOR1: + ((Entity*)target)->SetHairColor1(clr); + break; + case SPAWN_SET_HAIR_COLOR2: + ((Entity*)target)->SetHairColor2(clr); + break; + case SPAWN_SET_HAIR_TYPE_COLOR: + ((Entity*)target)->SetHairColor(clr); + break; + case SPAWN_SET_HAIR_FACE_COLOR: + ((Entity*)target)->SetFacialHairColor(clr); + break; + case SPAWN_SET_HAIR_TYPE_HIGHLIGHT_COLOR: + ((Entity*)target)->SetHairTypeHighlightColor(clr); + break; + case SPAWN_SET_HAIR_FACE_HIGHLIGHT_COLOR: + ((Entity*)target)->SetFacialHairHighlightColor(clr); + break; + case SPAWN_SET_HAIR_HIGHLIGHT: + ((Entity*)target)->SetHairHighlightColor(clr); + break; + case SPAWN_SET_MODEL_COLOR: + ((Entity*)target)->SetModelColor(clr); + break; + case SPAWN_SET_EYE_COLOR: + ((Entity*)target)->SetEyeColor(clr); + break; + case SPAWN_SET_SOGA_SKIN_COLOR: + ((Entity*)target)->SetSogaSkinColor(clr); + break; + case SPAWN_SET_SOGA_HAIR_COLOR1: + ((Entity*)target)->SetSogaHairColor1(clr); + break; + case SPAWN_SET_SOGA_HAIR_COLOR2: + ((Entity*)target)->SetSogaHairColor2(clr); + break; + case SPAWN_SET_SOGA_HAIR_TYPE_COLOR: + ((Entity*)target)->SetSogaHairColor(clr); + break; + case SPAWN_SET_SOGA_HAIR_FACE_COLOR: + ((Entity*)target)->SetSogaFacialHairColor(clr); + break; + case SPAWN_SET_SOGA_HAIR_TYPE_HIGHLIGHT_COLOR: + ((Entity*)target)->SetSogaHairTypeHighlightColor(clr); + break; + case SPAWN_SET_SOGA_HAIR_FACE_HIGHLIGHT_COLOR: + ((Entity*)target)->SetSogaFacialHairHighlightColor(clr); + break; + case SPAWN_SET_SOGA_HAIR_HIGHLIGHT: + ((Entity*)target)->SetSogaHairHighlightColor(clr); + break; + case SPAWN_SET_SOGA_MODEL_COLOR: + ((Entity*)target)->SetSogaModelColor(clr); + break; + case SPAWN_SET_SOGA_EYE_COLOR: + ((Entity*)target)->SetSogaEyeColor(clr); + break; + } + } + safe_delete(skinsep); + } + break; + } + case SPAWN_SET_AAXP_REWARDS: { + target->SetAAXPRewards(atoul(value)); + break; + } + case SPAWN_SET_CHEEK_TYPE:{ + if(target->IsEntity() && index < 3){ + sprintf(tmp, "%i", ((Entity*)target)->features.cheek_type[index]); + sint8 new_value = atoi(value); + ((Entity*)target)->features.cheek_type[index] = new_value; + ((Entity*)target)->info_changed = true; + } + break; + } + case SPAWN_SET_CHIN_TYPE:{ + if(target->IsEntity() && index < 3){ + sprintf(tmp, "%i", ((Entity*)target)->features.chin_type[index]); + sint8 new_value = atoi(value); + ((Entity*)target)->features.chin_type[index] = new_value; + ((Entity*)target)->info_changed = true; + } + break; + } + case SPAWN_SET_EAR_TYPE:{ + if(target->IsEntity() && index < 3){ + sprintf(tmp, "%i", ((Entity*)target)->features.ear_type[index]); + sint8 new_value = atoi(value); + ((Entity*)target)->features.ear_type[index] = new_value; + ((Entity*)target)->info_changed = true; + } + break; + } + case SPAWN_SET_EYE_BROW_TYPE:{ + if(target->IsEntity() && index < 3){ + sprintf(tmp, "%i", ((Entity*)target)->features.eye_brow_type[index]); + sint8 new_value = atoi(value); + ((Entity*)target)->features.eye_brow_type[index] = new_value; + ((Entity*)target)->info_changed = true; + } + break; + } + case SPAWN_SET_EYE_TYPE:{ + if(target->IsEntity() && index < 3){ + sprintf(tmp, "%i", ((Entity*)target)->features.eye_type[index]); + sint8 new_value = atoi(value); + ((Entity*)target)->features.eye_type[index] = new_value; + ((Entity*)target)->info_changed = true; + } + break; + } + case SPAWN_SET_LIP_TYPE:{ + if(target->IsEntity() && index < 3){ + sprintf(tmp, "%i", ((Entity*)target)->features.lip_type[index]); + sint8 new_value = atoi(value); + ((Entity*)target)->features.lip_type[index] = new_value; + ((Entity*)target)->info_changed = true; + } + break; + } + case SPAWN_SET_NOSE_TYPE:{ + if(target->IsEntity() && index < 3){ + sprintf(tmp, "%i", ((Entity*)target)->features.nose_type[index]); + sint8 new_value = atoi(value); + ((Entity*)target)->features.nose_type[index] = new_value; + ((Entity*)target)->info_changed = true; + } + break; + } + case SPAWN_SET_BODY_SIZE:{ + if(target->IsEntity()){ + sprintf(tmp, "%i", ((Entity*)target)->features.body_size); + int8 new_value = atoul(value); + ((Entity*)target)->features.body_size = new_value; + ((Entity*)target)->info_changed = true; + } + break; + } + case SPAWN_SET_BODY_AGE:{ + if(target->IsEntity()){ + sprintf(tmp, "%i", ((Entity*)target)->features.body_age); + int8 new_value = atoul(value); + ((Entity*)target)->features.body_age = new_value; + ((Entity*)target)->info_changed = true; + } + break; + } + case SPAWN_SET_SOGA_CHEEK_TYPE:{ + if(target->IsEntity() && index < 3){ + sprintf(tmp, "%i", ((Entity*)target)->features.soga_cheek_type[index]); + sint8 new_value = atoi(value); + ((Entity*)target)->features.soga_cheek_type[index] = new_value; + ((Entity*)target)->info_changed = true; + } + break; + } + case SPAWN_SET_SOGA_CHIN_TYPE:{ + if(target->IsEntity() && index < 3){ + sprintf(tmp, "%i", ((Entity*)target)->features.soga_chin_type[index]); + sint8 new_value = atoi(value); + ((Entity*)target)->features.soga_chin_type[index] = new_value; + ((Entity*)target)->info_changed = true; + } + break; + } + case SPAWN_SET_SOGA_EAR_TYPE:{ + if(target->IsEntity() && index < 3){ + sprintf(tmp, "%i", ((Entity*)target)->features.soga_ear_type[index]); + sint8 new_value = atoi(value); + ((Entity*)target)->features.soga_ear_type[index] = new_value; + ((Entity*)target)->info_changed = true; + } + break; + } + case SPAWN_SET_SOGA_EYE_BROW_TYPE:{ + if(target->IsEntity() && index < 3){ + sprintf(tmp, "%i", ((Entity*)target)->features.soga_eye_brow_type[index]); + sint8 new_value = atoi(value); + ((Entity*)target)->features.soga_eye_brow_type[index] = new_value; + ((Entity*)target)->info_changed = true; + } + break; + } + case SPAWN_SET_SOGA_EYE_TYPE:{ + if(target->IsEntity() && index < 3){ + sprintf(tmp, "%i", ((Entity*)target)->features.soga_eye_type[index]); + sint8 new_value = atoi(value); + ((Entity*)target)->features.soga_eye_type[index] = new_value; + ((Entity*)target)->info_changed = true; + } + break; + } + case SPAWN_SET_SOGA_LIP_TYPE:{ + if(target->IsEntity() && index < 3){ + sprintf(tmp, "%i", ((Entity*)target)->features.soga_lip_type[index]); + sint8 new_value = atoi(value); + ((Entity*)target)->features.soga_lip_type[index] = new_value; + ((Entity*)target)->info_changed = true; + } + break; + } + case SPAWN_SET_SOGA_BODY_SIZE:{ + if(target->IsEntity()){ + sprintf(tmp, "%i", ((Entity*)target)->features.soga_body_size); + int8 new_value = atoul(value); + ((Entity*)target)->features.soga_body_size = new_value; + ((Entity*)target)->info_changed = true; + } + break; + } + case SPAWN_SET_SOGA_BODY_AGE:{ + if(target->IsEntity()){ + sprintf(tmp, "%i", ((Entity*)target)->features.soga_body_age); + int8 new_value = atoul(value); + ((Entity*)target)->features.soga_body_age = new_value; + ((Entity*)target)->info_changed = true; + } + break; + } + case SPAWN_SET_ATTACK_TYPE:{ + if(target->IsEntity()){ + sprintf(tmp, "%u", ((Entity*)target)->GetInfoStruct()->get_attack_type()); + int8 new_value = atoul(value); + ((Entity*)target)->GetInfoStruct()->set_attack_type(new_value); + } + break; + } + case SPAWN_SET_RACE_TYPE:{ + if(target->GetModelType() > 0){ + Seperator* tmpsep = new Seperator(value, ' ', 3, 500, true); + if (tmpsep->IsNumber(0)) + { + int16 race_type = atoul(value); + const char* category = tmpsep->IsSet(1) ? tmpsep->arg[1] : "NULL"; + const char* subcategory = tmpsep->IsSet(2) ? tmpsep->arg[2] : "NULL"; + const char* model_name = tmpsep->IsSet(3) ? tmpsep->arg[3] : "NULL"; + if(race_types_list.AddRaceType(target->GetModelType(), race_type, category, subcategory, model_name)) { + if(client) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Race type added to memory, however not saved to database."); + } + + } + else if(client) { + client->SimpleMessage(CHANNEL_COLOR_RED, "Model type already on the race_types_list, use spawn set database to override and update database."); + } + } + safe_delete(tmpsep); + } + break; + } + case SPAWN_SET_LOOT_TIER:{ + if(target->IsEntity()){ + sprintf(tmp, "%u", target->GetLootTier()); + int32 new_value = atoul(value); + target->SetLootTier(new_value); + } + break; + } + case SPAWN_SET_LOOT_DROP_TYPE:{ + if(target->IsEntity()){ + sprintf(tmp, "%u", target->GetLootDropType()); + int32 new_value = atoul(value); + target->SetLootDropType(new_value); + } + break; + } + case SPAWN_SET_SCARED_STRONG_PLAYERS:{ + if(target->IsNPC()){ + sprintf(tmp, "%u", target->IsScaredByStrongPlayers()); + int32 new_value = atoul(value); + target->SetScaredByStrongPlayers(new_value); + } + break; + } + + if(temp_value) + *temp_value = string(tmp); + } + } + else{ + /**** NOT TEMPORARY ELSE STATEMENT ****/ + /**** MUST RE-DEFINE WHAT IS ALREADY IN THE IF TEMPORARY BLOCK HERE ****/ + + switch(type){ + case SPAWN_SET_VALUE_MERCHANT_MIN_LEVEL: { + target->SetMerchantLevelRange(atoul(value), target->GetMerchantMaxLevel()); + break; + } + case SPAWN_SET_VALUE_MERCHANT_MAX_LEVEL: { + target->SetMerchantLevelRange(target->GetMerchantMinLevel(), atoul(value)); + break; + } + case SPAWN_SET_VALUE_EXPANSION_FLAG: { + + if (target->GetDatabaseID() > 0) + { + char query[256]; + snprintf(query, 256, "update spawn set expansion_flag=%u where id=%u", atoul(value), target->GetDatabaseID()); + if (database.RunQuery(query, strnlen(query, 256))) + { + if(client) + client->Message(CHANNEL_COLOR_RED, "Ran query:%s", query); + } + } + break; + } + case SPAWN_SET_VALUE_HOLIDAY_FLAG: { + + if (target->GetDatabaseID() > 0) + { + char query[256]; + snprintf(query, 256, "update spawn set holiday_flag=%u where id=%u", atoul(value), target->GetDatabaseID()); + if (database.RunQuery(query, strnlen(query, 256))) + { + if(client) + client->Message(CHANNEL_COLOR_RED, "Ran query:%s", query); + } + } + break; + } + case SPAWN_SET_AAXP_REWARDS: { + + if (target->GetDatabaseID() > 0) + { + char query[256]; + snprintf(query, 256, "update spawn set aaxp_rewards=%u where id=%u", atoul(value), target->GetDatabaseID()); + if (database.RunQuery(query, strnlen(query, 256))) + { + if(client) + client->Message(CHANNEL_COLOR_RED, "Ran query:%s", query); + } + } + break; + } + case SPAWN_SET_VALUE_NAME:{ + target->SetName(value); + if(target->GetZone()) + target->GetZone()->SendUpdateTitles(target); + break; + } + case SPAWN_SET_VALUE_SUB_TITLE: { + target->SetSubTitle(value); + if(target->GetZone()) + target->GetZone()->SendUpdateTitles(target); + break; + } + case SPAWN_SET_VALUE_X_OFFSET: + { + target->SetXOffset(atof(value)); + break; + } + case SPAWN_SET_VALUE_Y_OFFSET: + { + target->SetYOffset(atof(value)); + break; + } + case SPAWN_SET_VALUE_Z_OFFSET: + { + target->SetZOffset(atof(value)); + break; + } + case SPAWN_SET_VALUE_EXPIRE: { + target->SetExpireTime(val); + break; + } + case SPAWN_SET_VALUE_EXPIRE_OFFSET: { + target->SetExpireOffsetTime(val); + break; + } + case SPAWN_SET_VALUE_LEVEL:{ + target->SetLevel(val, send_update); + break; + } + case SPAWN_SET_VALUE_DIFFICULTY:{ + target->SetDifficulty(val, send_update); + break; + } + case SPAWN_SET_VALUE_MODEL_TYPE:{ + target->SetModelType(val, send_update); + break; + } + case SPAWN_SET_VALUE_CLASS:{ + target->SetAdventureClass(val, send_update); + break; + } + case SPAWN_SET_VALUE_GENDER:{ + target->SetGender(val, send_update); + break; + } + case SPAWN_SET_VALUE_SHOW_NAME:{ + target->SetShowName(val, send_update); + break; + } + case SPAWN_SET_VALUE_ATTACKABLE:{ + target->SetAttackable(val, send_update); + break; + } + case SPAWN_SET_VALUE_SHOW_LEVEL:{ + target->SetShowLevel(val, send_update); + break; + } + case SPAWN_SET_VALUE_TARGETABLE:{ + target->SetTargetable(val, send_update); + break; + } + case SPAWN_SET_VALUE_SHOW_COMMAND_ICON:{ + target->SetShowCommandIcon(val, send_update); + break; + } + case SPAWN_SET_VALUE_HAND_ICON:{ + target->SetShowHandIcon(val, send_update); + break; + } + case SPAWN_SET_VALUE_HAIR_TYPE:{ + if(target->IsEntity()) + ((Entity*)target)->SetHairType(val, send_update); + break; + } + case SPAWN_SET_VALUE_FACIAL_HAIR_TYPE:{ + if(target->IsEntity()) + ((Entity*)target)->SetFacialHairType(val, send_update); + break; + } + case SPAWN_SET_VALUE_WING_TYPE:{ + if(target->IsEntity()) + ((Entity*)target)->SetWingType(val, send_update); + break; + } + case SPAWN_SET_VALUE_CHEST_TYPE:{ + if(target->IsEntity()) + ((Entity*)target)->SetChestType(val, send_update); + break; + } + case SPAWN_SET_VALUE_LEGS_TYPE:{ + if(target->IsEntity()) + ((Entity*)target)->SetLegsType(val, send_update); + break; + } + case SPAWN_SET_VALUE_SOGA_HAIR_TYPE:{ + if(target->IsEntity()) + ((Entity*)target)->SetSogaHairType(val, send_update); + break; + } + case SPAWN_SET_VALUE_SOGA_FACIAL_HAIR_TYPE:{ + if(target->IsEntity()) + ((Entity*)target)->SetSogaFacialHairType(val, send_update); + break; + } + case SPAWN_SET_VALUE_SOGA_MODEL_TYPE:{ + target->SetSogaModelType(val, send_update); + break; + } + case SPAWN_SET_VALUE_SIZE:{ + target->SetSize(val, send_update); + break; + } + case SPAWN_SET_VALUE_HP:{ + target->SetTotalHPBase(val); + target->SetHP(val, send_update); + break; + } + case SPAWN_SET_VALUE_POWER:{ + target->SetTotalPowerBase(val); + target->SetPower(val, send_update); + break; + } + case SPAWN_SET_VALUE_HEROIC:{ + target->SetHeroic(val, send_update); + break; + } + case SPAWN_SET_VALUE_RESPAWN:{ + target->SetRespawnTime(val); + break; + } + case SPAWN_SET_VALUE_X:{ + target->SetX(atof(value), send_update); + target->SetSpawnOrigX(target->GetX()); + break; + } + case SPAWN_SET_VALUE_Y:{ + target->SetY(atof(value), send_update); + target->SetSpawnOrigY(target->GetY()); + break; + } + case SPAWN_SET_VALUE_Z:{ + target->SetZ(atof(value), send_update); + target->SetSpawnOrigZ(target->GetZ()); + break; + } + case SPAWN_SET_VALUE_HEADING:{ + target->SetHeading(atof(value), send_update); + target->SetSpawnOrigHeading(target->GetHeading()); + break; + } + case SPAWN_SET_VALUE_COMMAND_PRIMARY:{ + ZoneServer* zone = target->GetZone(); + if (!zone && client) + zone = client->GetCurrentZone(); + + if(zone && zone->GetEntityCommandList(val)) + target->SetPrimaryCommands(zone->GetEntityCommandList(val)); + target->primary_command_list_id = val; + break; + } + case SPAWN_SET_VALUE_COMMAND_SECONDARY:{ + ZoneServer* zone = target->GetZone(); + if (!zone && client) + zone = client->GetCurrentZone(); + + if (zone && zone->GetEntityCommandList(val)) + target->SetSecondaryCommands(zone->GetEntityCommandList(val)); + target->secondary_command_list_id = val; + break; + } + case SPAWN_SET_VALUE_VISUAL_STATE:{ + target->SetVisualState(val, send_update); + break; + } + case SPAWN_SET_VALUE_ACTION_STATE:{ + target->SetActionState(val, send_update); + break; + } + case SPAWN_SET_VALUE_MOOD_STATE:{ + target->SetMoodState(val, send_update); + break; + } + case SPAWN_SET_VALUE_INITIAL_STATE:{ + target->SetInitialState(val, send_update); + break; + } + case SPAWN_SET_VALUE_ACTIVITY_STATE:{ + target->SetActivityStatus(val, send_update); + break; + } + case SPAWN_SET_VALUE_COLLISION_RADIUS:{ + target->SetCollisionRadius(val, send_update); + break; + } + case SPAWN_SET_VALUE_FACTION:{ + ZoneServer* zone = target->GetZone(); + if (!zone && client) + zone = client->GetCurrentZone(); + + target->faction_id = val; + if(zone) + { + zone->RemoveDeadEnemyList(target); + if(target->IsNPC()) + zone->AddEnemyList((NPC*)target); + } + break; + } + case SPAWN_SET_VALUE_DEVICE_ID:{ + if (target->IsObject()) + ((Object*)target)->SetDeviceID(val); + break; + } + case SPAWN_SET_VALUE_PITCH: { + target->SetPitch(atof(value), send_update); + target->SetSpawnOrigPitch(atof(value)); + break; + } + case SPAWN_SET_VALUE_ROLL: { + target->SetRoll(atof(value), send_update); + target->SetSpawnOrigRoll(atof(value)); + break; + } + case SPAWN_SET_VALUE_HIDE_HOOD: { + target->SetHideHood(val); + break; + } + case SPAWN_SET_VALUE_EMOTE_STATE: { + target->SetEmoteState(val); + break; + } + case SPAWN_SET_VALUE_ICON: { + target->SetIcon(val); + break; + } + case SPAWN_SET_VALUE_PREFIX: { + target->SetPrefixTitle(value); + if(target->GetZone()) + target->GetZone()->SendUpdateTitles(target); + break; + } + case SPAWN_SET_VALUE_SUFFIX: { + target->SetSuffixTitle(value); + if(target->GetZone()) + target->GetZone()->SendUpdateTitles(target); + break; + } + case SPAWN_SET_VALUE_LASTNAME: { + target->SetLastName(value); + if(target->GetZone()) + target->GetZone()->SendUpdateTitles(target); + break; + } + case SPAWN_SET_VALUE_SPAWN_SCRIPT:{ + if(lua_interface && lua_interface->GetSpawnScript(value) == 0){ + if(client){ + client->Message(CHANNEL_COLOR_RED, "Invalid Spawn Script file. Be sure you give the absolute path."); + client->Message(CHANNEL_COLOR_RED, "Example: /spawn set spawn_script 'SpawnScripts/example.lua'"); + } + return false; + } + else if(!database.UpdateSpawnScriptData(target->GetDatabaseID(), 0, 0, value)) + return false; + else{ + if(!world.GetSpawnLocationScript(target->GetSpawnLocationID())) + target->SetSpawnScript(value); + } + break; + } + case SPAWN_SET_VALUE_SPAWNLOCATION_SCRIPT:{ + if(lua_interface && lua_interface->GetSpawnScript(value) == 0){ + if(client){ + client->Message(CHANNEL_COLOR_RED, "Invalid Spawn Script file. Be sure you give the absolute path."); + client->Message(CHANNEL_COLOR_RED, "Example: /spawn set spawnlocation_script 'SpawnScripts/example.lua'"); + } + return false; + } + else if(!database.UpdateSpawnScriptData(0, target->GetSpawnLocationID(), 0, value)) + return false; + else{ + if(!world.GetSpawnEntryScript(target->GetSpawnEntryID())) + target->SetSpawnScript(value); + } + break; + } + case SPAWN_SET_VALUE_SPAWNENTRY_SCRIPT:{ + if(lua_interface && lua_interface->GetSpawnScript(value) == 0){ + if(client){ + client->Message(CHANNEL_COLOR_RED, "Invalid Spawn Script file. Be sure you give the absolute path."); + client->Message(CHANNEL_COLOR_RED, "Example: /spawn set spawnentry_script 'SpawnScripts/example.lua'"); + } + return false; + } + else if(!database.UpdateSpawnScriptData(0, 0, target->GetSpawnEntryID(), value)) + return false; + else + target->SetSpawnScript(value); + break; + } + + case SPAWN_SET_SKIN_COLOR: + case SPAWN_SET_HAIR_COLOR1: + case SPAWN_SET_HAIR_COLOR2: + case SPAWN_SET_HAIR_TYPE_COLOR: + case SPAWN_SET_HAIR_FACE_COLOR: + case SPAWN_SET_HAIR_TYPE_HIGHLIGHT_COLOR: + case SPAWN_SET_HAIR_FACE_HIGHLIGHT_COLOR: + case SPAWN_SET_HAIR_HIGHLIGHT: + case SPAWN_SET_MODEL_COLOR: + case SPAWN_SET_EYE_COLOR: + case SPAWN_SET_SOGA_SKIN_COLOR: + case SPAWN_SET_SOGA_HAIR_COLOR1: + case SPAWN_SET_SOGA_HAIR_COLOR2: + case SPAWN_SET_SOGA_HAIR_TYPE_COLOR: + case SPAWN_SET_SOGA_HAIR_FACE_COLOR: + case SPAWN_SET_SOGA_HAIR_TYPE_HIGHLIGHT_COLOR: + case SPAWN_SET_SOGA_HAIR_FACE_HIGHLIGHT_COLOR: + case SPAWN_SET_SOGA_HAIR_HIGHLIGHT: + case SPAWN_SET_SOGA_MODEL_COLOR: + case SPAWN_SET_SOGA_EYE_COLOR: { + if (target->IsNPC()) + { + Seperator* skinsep = new Seperator(value, ' ', 3, 500, true); + if (skinsep->IsNumber(0) && skinsep->IsNumber(1) && skinsep->IsNumber(2)) + { + EQ2_Color clr; + clr.red = atoul(skinsep->arg[0]); + clr.green = atoul(skinsep->arg[1]); + clr.blue = atoul(skinsep->arg[2]); + Query replaceSkinQuery; + + string fieldName(""); + switch(type) + { + case SPAWN_SET_SKIN_COLOR: + ((Entity*)target)->SetSkinColor(clr); + fieldName.append("skin_color"); + break; + case SPAWN_SET_HAIR_COLOR1: + ((Entity*)target)->SetHairColor1(clr); + fieldName.append("hair_color1"); + break; + case SPAWN_SET_HAIR_COLOR2: + ((Entity*)target)->SetHairColor2(clr); + fieldName.append("hair_color2"); + break; + case SPAWN_SET_HAIR_TYPE_COLOR: + ((Entity*)target)->SetHairColor(clr); + fieldName.append("hair_type_color"); + break; + case SPAWN_SET_HAIR_FACE_COLOR: + ((Entity*)target)->SetFacialHairColor(clr); + fieldName.append("hair_face_color"); + break; + case SPAWN_SET_HAIR_TYPE_HIGHLIGHT_COLOR: + ((Entity*)target)->SetHairTypeHighlightColor(clr); + fieldName.append("hair_type_highlight_color"); + break; + case SPAWN_SET_HAIR_FACE_HIGHLIGHT_COLOR: + ((Entity*)target)->SetFacialHairHighlightColor(clr); + fieldName.append("hair_face_highlight_color"); + break; + case SPAWN_SET_HAIR_HIGHLIGHT: + ((Entity*)target)->SetHairHighlightColor(clr); + fieldName.append("hair_highlight"); + break; + case SPAWN_SET_MODEL_COLOR: + ((Entity*)target)->SetModelColor(clr); + fieldName.append("model_color"); + break; + case SPAWN_SET_EYE_COLOR: + ((Entity*)target)->SetEyeColor(clr); + fieldName.append("eye_color"); + break; + case SPAWN_SET_SOGA_SKIN_COLOR: + ((Entity*)target)->SetSogaSkinColor(clr); + fieldName.append("soga_skin_color"); + break; + case SPAWN_SET_SOGA_HAIR_COLOR1: + ((Entity*)target)->SetSogaHairColor1(clr); + fieldName.append("soga_hair_color1"); + break; + case SPAWN_SET_SOGA_HAIR_COLOR2: + ((Entity*)target)->SetSogaHairColor2(clr); + fieldName.append("soga_hair_color2"); + break; + case SPAWN_SET_SOGA_HAIR_TYPE_COLOR: + ((Entity*)target)->SetSogaHairColor(clr); + fieldName.append("soga_hair_type_color"); + break; + case SPAWN_SET_SOGA_HAIR_FACE_COLOR: + ((Entity*)target)->SetSogaFacialHairColor(clr); + fieldName.append("soga_hair_face_color"); + break; + case SPAWN_SET_SOGA_HAIR_TYPE_HIGHLIGHT_COLOR: + ((Entity*)target)->SetSogaHairTypeHighlightColor(clr); + fieldName.append("soga_hair_type_highlight_color"); + break; + case SPAWN_SET_SOGA_HAIR_FACE_HIGHLIGHT_COLOR: + ((Entity*)target)->SetSogaFacialHairHighlightColor(clr); + fieldName.append("soga_hair_face_highlight_color"); + break; + case SPAWN_SET_SOGA_HAIR_HIGHLIGHT: + ((Entity*)target)->SetSogaHairHighlightColor(clr); + fieldName.append("soga_hair_highlight"); + break; + case SPAWN_SET_SOGA_MODEL_COLOR: + ((Entity*)target)->SetSogaModelColor(clr); + fieldName.append("soga_model_color"); + break; + case SPAWN_SET_SOGA_EYE_COLOR: + ((Entity*)target)->SetSogaEyeColor(clr); + fieldName.append("soga_eye_color"); + break; + } + replaceSkinQuery.AddQueryAsync(0, &database, Q_DELETE, "delete from npc_appearance where spawn_id=%u and type='%s'", target->GetDatabaseID(), fieldName.c_str()); + replaceSkinQuery.AddQueryAsync(0, &database, Q_INSERT, "insert into npc_appearance set spawn_id=%u, type='%s', red=%u, green=%u, blue=%u", target->GetDatabaseID(), fieldName.c_str(), clr.red, clr.green, clr.blue); + } + safe_delete(skinsep); + } + break; + } + + case SPAWN_SET_CHEEK_TYPE:{ + if(target->IsEntity() && index < 3){ + sint8 new_value = atoi(value); + ((Entity*)target)->features.cheek_type[index] = new_value; + UpdateDatabaseAppearance(client, target, "cheek_type", ((Entity*)target)->features.cheek_type[0], ((Entity*)target)->features.cheek_type[1], ((Entity*)target)->features.cheek_type[2]); + } + break; + } + case SPAWN_SET_CHIN_TYPE:{ + if(target->IsEntity() && index < 3){ + sint8 new_value = atoi(value); + ((Entity*)target)->features.chin_type[index] = new_value; + UpdateDatabaseAppearance(client, target, "chin_type", ((Entity*)target)->features.chin_type[0], ((Entity*)target)->features.chin_type[1], ((Entity*)target)->features.chin_type[2]); + } + break; + } + case SPAWN_SET_EAR_TYPE:{ + if(target->IsEntity() && index < 3){ + sint8 new_value = atoi(value); + ((Entity*)target)->features.ear_type[index] = new_value; + UpdateDatabaseAppearance(client, target, "ear_type", ((Entity*)target)->features.ear_type[0], ((Entity*)target)->features.ear_type[1], ((Entity*)target)->features.ear_type[2]); + } + break; + } + case SPAWN_SET_EYE_BROW_TYPE:{ + if(target->IsEntity() && index < 3){ + sint8 new_value = atoi(value); + ((Entity*)target)->features.eye_brow_type[index] = new_value; + UpdateDatabaseAppearance(client, target, "eye_brow_type", ((Entity*)target)->features.eye_brow_type[0], ((Entity*)target)->features.eye_brow_type[1], ((Entity*)target)->features.eye_brow_type[2]); + } + break; + } + case SPAWN_SET_EYE_TYPE:{ + if(target->IsEntity() && index < 3){ + sint8 new_value = atoi(value); + ((Entity*)target)->features.eye_type[index] = new_value; + UpdateDatabaseAppearance(client, target, "eye_type", ((Entity*)target)->features.eye_type[0], ((Entity*)target)->features.eye_type[1], ((Entity*)target)->features.eye_type[2]); + } + break; + } + case SPAWN_SET_LIP_TYPE:{ + if(target->IsEntity() && index < 3){ + sint8 new_value = atoi(value); + ((Entity*)target)->features.lip_type[index] = new_value; + UpdateDatabaseAppearance(client, target, "lip_type", ((Entity*)target)->features.lip_type[0], ((Entity*)target)->features.lip_type[1], ((Entity*)target)->features.lip_type[2]); + } + break; + } + case SPAWN_SET_NOSE_TYPE:{ + if(target->IsEntity() && index < 3){ + sint8 new_value = atoi(value); + ((Entity*)target)->features.nose_type[index] = new_value; + UpdateDatabaseAppearance(client, target, "nose_type", ((Entity*)target)->features.nose_type[0], ((Entity*)target)->features.nose_type[1], ((Entity*)target)->features.nose_type[2]); + } + break; + } + case SPAWN_SET_BODY_SIZE:{ + if(target->IsEntity()){ + int8 new_value = atoul(value); + ((Entity*)target)->features.body_size = new_value; + UpdateDatabaseAppearance(client, target, "body_size", ((Entity*)target)->features.body_size, 0, 0); + } + break; + } + case SPAWN_SET_BODY_AGE:{ + if(target->IsEntity()){ + int8 new_value = atoul(value); + ((Entity*)target)->features.body_age = new_value; + UpdateDatabaseAppearance(client, target, "body_age", ((Entity*)target)->features.body_age, 0, 0); + } + break; + } + case SPAWN_SET_SOGA_CHEEK_TYPE:{ + if(target->IsEntity() && index < 3){ + sint8 new_value = atoi(value); + ((Entity*)target)->features.soga_cheek_type[index] = new_value; + UpdateDatabaseAppearance(client, target, "soga_cheek_type", ((Entity*)target)->features.soga_cheek_type[0], ((Entity*)target)->features.soga_cheek_type[1], ((Entity*)target)->features.soga_cheek_type[2]); + } + break; + } + case SPAWN_SET_SOGA_CHIN_TYPE:{ + if(target->IsEntity() && index < 3){ + sint8 new_value = atoi(value); + ((Entity*)target)->features.soga_chin_type[index] = new_value; + UpdateDatabaseAppearance(client, target, "soga_chin_type", ((Entity*)target)->features.soga_chin_type[0], ((Entity*)target)->features.soga_chin_type[1], ((Entity*)target)->features.soga_chin_type[2]); + } + break; + } + case SPAWN_SET_SOGA_EAR_TYPE:{ + if(target->IsEntity() && index < 3){ + sint8 new_value = atoi(value); + ((Entity*)target)->features.soga_ear_type[index] = new_value; + UpdateDatabaseAppearance(client, target, "soga_ear_type", ((Entity*)target)->features.soga_ear_type[0], ((Entity*)target)->features.soga_ear_type[1], ((Entity*)target)->features.soga_ear_type[2]); + } + break; + } + case SPAWN_SET_SOGA_EYE_BROW_TYPE:{ + if(target->IsEntity() && index < 3){ + sint8 new_value = atoi(value); + ((Entity*)target)->features.soga_eye_brow_type[index] = new_value; + UpdateDatabaseAppearance(client, target, "soga_eye_brow_type", ((Entity*)target)->features.soga_eye_brow_type[0], ((Entity*)target)->features.soga_eye_brow_type[1], ((Entity*)target)->features.soga_eye_brow_type[2]); + } + break; + } + case SPAWN_SET_SOGA_EYE_TYPE:{ + if(target->IsEntity() && index < 3){ + sint8 new_value = atoi(value); + ((Entity*)target)->features.soga_eye_type[index] = new_value; + UpdateDatabaseAppearance(client, target, "soga_eye_type", ((Entity*)target)->features.soga_eye_type[0], ((Entity*)target)->features.soga_eye_type[1], ((Entity*)target)->features.soga_eye_type[2]); + } + break; + } + case SPAWN_SET_SOGA_LIP_TYPE:{ + if(target->IsEntity() && index < 3){ + sint8 new_value = atoi(value); + ((Entity*)target)->features.soga_lip_type[index] = new_value; + UpdateDatabaseAppearance(client, target, "soga_lip_type", ((Entity*)target)->features.soga_lip_type[0], ((Entity*)target)->features.soga_lip_type[1], ((Entity*)target)->features.soga_lip_type[2]); + } + break; + } + case SPAWN_SET_SOGA_NOSE_TYPE:{ + if(target->IsEntity() && index < 3){ + sint8 new_value = atoi(value); + ((Entity*)target)->features.soga_nose_type[index] = new_value; + UpdateDatabaseAppearance(client, target, "soga_nose_type", ((Entity*)target)->features.soga_nose_type[0], ((Entity*)target)->features.soga_nose_type[1], ((Entity*)target)->features.soga_nose_type[2]); + } + break; + } + case SPAWN_SET_SOGA_BODY_SIZE:{ + if(target->IsEntity()){ + int8 new_value = atoul(value); + ((Entity*)target)->features.body_size = new_value; + UpdateDatabaseAppearance(client, target, "body_size", ((Entity*)target)->features.body_size, 0, 0); + } + break; + } + case SPAWN_SET_SOGA_BODY_AGE:{ + if(target->IsEntity()){ + int8 new_value = atoul(value); + ((Entity*)target)->features.body_age = new_value; + UpdateDatabaseAppearance(client, target, "body_age", ((Entity*)target)->features.body_age, 0, 0); + } + break; + } + case SPAWN_SET_ATTACK_TYPE:{ + if(target->IsEntity()){ + int8 new_value = atoul(value); + ((Entity*)target)->GetInfoStruct()->set_attack_type(new_value); + if(target->IsNPC()) { + if(target->GetDatabaseID()) { + Query spawnNPCQuery; + spawnNPCQuery.AddQueryAsync(0, &database, Q_INSERT, "update spawn_npcs set attack_type=%u where id=%u", new_value, target->GetDatabaseID()); + } else if(client) { + client->Message(CHANNEL_COLOR_RED, "Invalid spawn to update the database (NPC only) or no database id for the NPC present."); + } + } + } + break; + } + case SPAWN_SET_RACE_TYPE:{ + if(target->GetModelType() > 0){ + Seperator* tmpsep = new Seperator(value, ' ', 3, 500, true); + if (tmpsep->IsNumber(0)) + { + Query typequery; + int16 race_type = atoul(value); + const char* category = tmpsep->IsSet(1) ? tmpsep->arg[1] : "NULL"; + const char* subcategory = tmpsep->IsSet(2) ? tmpsep->arg[2] : "NULL"; + const char* model_name = tmpsep->IsSet(3) ? tmpsep->arg[3] : "NULL"; + if(race_types_list.AddRaceType(target->GetModelType(), race_type, category, subcategory, model_name)) { + if(client) + client->Message(CHANNEL_COLOR_YELLOW, "Model Type %u Race type %u inserted into memory + replaced into database, Model Type: %u set to Race Type: %u, Category: %s, SubCategory: %s, Model Name: %s.", race_type, target->GetModelType(), race_type, category, subcategory, model_name); + typequery.AddQueryAsync(0, &database, Q_INSERT, "insert into race_types (model_type, race_id, category, subcategory, model_name) values(%u, %u, '%s', '%s', '%s')", target->GetModelType(), race_type, category, subcategory, model_name); + } + else { + if(client) + client->Message(CHANNEL_COLOR_RED, "Model Type: %u Race type %u overrided in memory + replaced into database, Model Type: %u set to Race Type: %u, Category: %s, SubCategory: %s, Model Name: %s.", race_type, target->GetModelType(), race_type, category, subcategory, model_name); + race_types_list.AddRaceType(target->GetModelType(), race_type, category, subcategory, model_name, true); + typequery.AddQueryAsync(0, &database, Q_INSERT, "update race_types set race_id=%u, category='%s', subcategory='%s', model_name='%s' where model_type=%u", race_type, category, subcategory, model_name, target->GetModelType()); + } + } + safe_delete(tmpsep); + } + break; + } + case SPAWN_SET_LOOT_TIER:{ + int32 new_value = atoul(value); + target->SetLootTier(new_value); + + if (target->GetDatabaseID() > 0) + { + char query[256]; + snprintf(query, 256, "update spawn set loot_tier=%u where id=%u", atoul(value), target->GetDatabaseID()); + if (database.RunQuery(query, strnlen(query, 256))) + { + if(client) + client->Message(CHANNEL_COLOR_RED, "Ran query:%s", query); + } + } + break; + } + case SPAWN_SET_LOOT_DROP_TYPE:{ + int32 new_value = atoul(value); + target->SetLootDropType(new_value); + + if (target->GetDatabaseID() > 0) + { + char query[256]; + snprintf(query, 256, "update spawn set loot_drop_type=%u where id=%u", atoul(value), target->GetDatabaseID()); + if (database.RunQuery(query, strnlen(query, 256))) + { + if(client) + client->Message(CHANNEL_COLOR_RED, "Ran query:%s", query); + } + } + break; + } + case SPAWN_SET_SCARED_STRONG_PLAYERS:{ + int32 new_value = atoul(value); + target->SetScaredByStrongPlayers(new_value); + + if (target->GetDatabaseID() > 0) + { + char query[256]; + snprintf(query, 256, "update spawn_npcs set scared_by_strong_players=%u where id=%u", atoul(value), target->GetDatabaseID()); + if (database.RunQuery(query, strnlen(query, 256))) + { + if(client) + client->Message(CHANNEL_COLOR_RED, "Ran query:%s", query); + } + } + break; + } + } + } + return true; +} + +void Commands::UpdateDatabaseAppearance(Client* client, Spawn* target, string fieldName, sint8 r, sint8 g, sint8 b) +{ + Query replaceQuery; + + if(target->IsBot()) + { + Bot* bot = (Bot*)target; + if(bot->BotID) + database.SaveBotFloats(bot->BotID, fieldName.c_str(), r, g, b); + } + else if(target->IsPlayer()) + { + replaceQuery.AddQueryAsync(0, &database, Q_DELETE, "delete from char_colors where char_id=%u and type='%s'", ((Player*)target)->GetCharacterID(), fieldName.c_str()); + replaceQuery.AddQueryAsync(0, &database, Q_INSERT, "insert into char_colors set char_id=%u, type='%s', red=%i, green=%i, blue=%i", ((Player*)target)->GetCharacterID(), fieldName.c_str(), r, g, b); + } + else + { + replaceQuery.AddQueryAsync(0, &database, Q_DELETE, "delete from npc_appearance where spawn_id=%u and type='%s'", target->GetDatabaseID(), fieldName.c_str()); + replaceQuery.AddQueryAsync(0, &database, Q_INSERT, "insert into npc_appearance set spawn_id=%u, type='%s', red=%i, green=%i, blue=%i", target->GetDatabaseID(), fieldName.c_str(), r, g, b); + } + + ((Entity*)target)->changed = true; + ((Entity*)target)->info_changed = true; + if(target->GetZone()) + target->GetZone()->AddChangedSpawn(target); +} + +/* The zone object will be NULL if the zone is not currently running. We pass both of these in so we can update + the database fields always and also update the zone in memory if it's running. */ +bool Commands::SetZoneCommand(Client* client, int32 zone_id, ZoneServer* zone, int8 type, const char* value) { + if (client && zone_id > 0 && type > 0 && value) { + sint32 int_value = 0; + float float_value = 0; + if (type == ZONE_SET_VALUE_SAFE_X || type == ZONE_SET_VALUE_SAFE_Y || type == ZONE_SET_VALUE_SAFE_Z || type == ZONE_SET_VALUE_UNDERWORLD) { + try { + float_value = atof(value); + } + catch (...) { + client->Message(CHANNEL_COLOR_RED, "Error converting '%s' to a float value", value); + return false; + } + } + else if (type != ZONE_SET_VALUE_NAME && type != ZONE_SET_VALUE_FILE && type != ZONE_SET_VALUE_DESCRIPTION && type != ZONE_SET_VALUE_ZONE_TYPE && type != ZONE_SET_VALUE_LUA_SCRIPT && type != ZONE_SET_VALUE_ZONE_MOTD) { + try { + int_value = atoi(value); + } + catch (...) { + client->Message(CHANNEL_COLOR_RED, "Error converting '%s' to an integer value", value); + return false; + } + } + switch (type) { + case ZONE_SET_VALUE_EXPANSION_ID: { + break; + } + case ZONE_SET_VALUE_NAME: { + if (zone) + zone->SetZoneName(const_cast(value)); + database.SaveZoneInfo(zone_id, "name", value); + break; + } + case ZONE_SET_VALUE_FILE: { + if (zone) + zone->SetZoneFile(const_cast(value)); + database.SaveZoneInfo(zone_id, "file", value); + break; + } + case ZONE_SET_VALUE_DESCRIPTION: { + if (zone) + zone->SetZoneDescription(const_cast(value)); + database.SaveZoneInfo(zone_id, "description", value); + break; + } + case ZONE_SET_VALUE_SAFE_X: { + if (zone) + zone->SetSafeX(float_value); + database.SaveZoneInfo(zone_id, "safe_x", float_value); + break; + } + case ZONE_SET_VALUE_SAFE_Y: { + if (zone) + zone->SetSafeY(float_value); + database.SaveZoneInfo(zone_id, "safe_y", float_value); + break; + } + case ZONE_SET_VALUE_SAFE_Z: { + if (zone) + zone->SetSafeZ(float_value); + database.SaveZoneInfo(zone_id, "safe_z", float_value); + break; + } + case ZONE_SET_VALUE_UNDERWORLD: { + if (zone) + zone->SetUnderWorld(float_value); + database.SaveZoneInfo(zone_id, "underworld", float_value); + break; + } + case ZONE_SET_VALUE_MIN_RECOMMENDED: { + break; + } + case ZONE_SET_VALUE_MAX_RECOMMENDED: { + break; + } + case ZONE_SET_VALUE_ZONE_TYPE: { + break; + } + case ZONE_SET_VALUE_ALWAYS_LOADED: { + if (zone) + zone->SetAlwaysLoaded(int_value == 1); + database.SaveZoneInfo(zone_id, "always_loaded", int_value); + break; + } + case ZONE_SET_VALUE_CITY_ZONE: { + if (zone) + zone->SetCityZone(int_value == 1); + database.SaveZoneInfo(zone_id, "city_zone", int_value); + break; + } + case ZONE_SET_VALUE_WEATHER_ALLOWED: { + if (zone) + zone->SetWeatherAllowed(int_value == 1); + database.SaveZoneInfo(zone_id, "weather_allowed", int_value); + break; + } + case ZONE_SET_VALUE_MIN_STATUS: { + if (zone) + zone->SetMinimumStatus(int_value); + database.SaveZoneInfo(zone_id, "min_status", int_value); + break; + } + case ZONE_SET_VALUE_MIN_LEVEL: { + if (zone) + zone->SetMinimumLevel(int_value); + database.SaveZoneInfo(zone_id, "min_level", int_value); + break; + } + case ZONE_SET_VALUE_MAX_LEVEL: { + if (zone) + zone->SetMaximumLevel(int_value); + database.SaveZoneInfo(zone_id, "max_level", int_value); + break; + } + case ZONE_SET_VALUE_START_ZONE: { + database.SaveZoneInfo(zone_id, "start_zone", int_value); + break; + } + case ZONE_SET_VALUE_INSTANCE_TYPE: { + if (zone) + zone->SetInstanceType(int_value); + database.SaveZoneInfo(zone_id, "instance_type", int_value); + break; + } + case ZONE_SET_VALUE_DEFAULT_REENTER_TIME: { + if (zone) + zone->SetDefaultReenterTime(int_value); + database.SaveZoneInfo(zone_id, "default_reenter_time", int_value); + break; + } + case ZONE_SET_VALUE_DEFAULT_RESET_TIME: { + if (zone) + zone->SetDefaultResetTime(int_value); + database.SaveZoneInfo(zone_id, "default_reset_time", int_value); + break; + } + case ZONE_SET_VALUE_DEFAULT_LOCKOUT_TIME: { + if (zone) + zone->SetDefaultLockoutTime(int_value); + database.SaveZoneInfo(zone_id, "default_lockout_time", int_value); + break; + } + case ZONE_SET_VALUE_FORCE_GROUP_TO_ZONE: { + if (zone) + zone->SetForceGroupZoneOption(int_value); + database.SaveZoneInfo(zone_id, "force_group_to_zone", int_value); + break; + } + case ZONE_SET_VALUE_LUA_SCRIPT: { + if (lua_interface && lua_interface->GetZoneScript(value) == 0) { + client->Message(CHANNEL_COLOR_RED, "Invalid Zone Script file. Be sure you give the absolute path."); + client->Message(CHANNEL_COLOR_RED, "Example: /zone set lua_script 'ZoneScripts/QueensColony.lua'"); + return false; + } + else { + world.AddZoneScript(zone_id, const_cast(value)); + database.SaveZoneInfo(zone_id, "lua_script", value); + } + break; + } + case ZONE_SET_VALUE_SHUTDOWN_TIMER: { + if (zone) + zone->SetShutdownTimer(int_value); + database.SaveZoneInfo(zone_id, "shutdown_timer", int_value); + break; + } + case ZONE_SET_VALUE_ZONE_MOTD: { + if (zone) + zone->SetZoneMOTD(string(value)); + database.SaveZoneInfo(zone_id, "zone_motd", value); + break; + } + default: { + client->Message(CHANNEL_COLOR_RED, "Invalid zone attribute %i", type); + return false; + } + } + } + else { + if (client) + client->SimpleMessage(CHANNEL_COLOR_RED, "An error occured saving new zone data."); + return false; + } + return true; +} + +void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* client, Spawn* targetOverride) { + + if (index >= remote_commands->commands.size()) { + LogWrite(COMMAND__ERROR, 0, "Command", "Error, command handler of %u was requested, but max handler is %u", index, remote_commands->commands.size()); + return; + } + + Spawn* cmdTarget = targetOverride ? targetOverride : client->GetPlayer()->GetTarget(); + + EQ2_RemoteCommandString* parent_command = 0; + EQ2_RemoteCommandString* command = &remote_commands->commands[index]; + Seperator* sep = 0; + if (command_parms->size > 0) { + sep = new Seperator(command_parms->data.c_str(), ' ', 10, 500, true); + if (sep && sep->arg[0] && remote_commands->validSubCommand(command->command.data, string(sep->arg[0]))) { + parent_command = command; + command = &(remote_commands->subcommands[command->command.data][string(sep->arg[0])]); + safe_delete(sep); + if (command_parms->data.length() > (command->command.data.length() + 1)) + sep = new Seperator(command_parms->data.c_str() + (command->command.data.length() + 1), ' ', 10, 500, true); + LogWrite(COMMAND__DEBUG, 1, "Command", "Handler: %u, COMMAND: '%s', SUBCOMMAND: '%s'", index, parent_command->command.data.c_str(), command->command.data.c_str()); + } + else + LogWrite(COMMAND__DEBUG, 1, "Command", "Handler: %u, COMMAND: '%s'", index, command->command.data.c_str()); + } + + int ndx = 0; + if (command->required_status > client->GetAdminStatus()) + { + LogWrite(COMMAND__ERROR, 0, "Command", "Player '%s' (%u) needs status %i to use command: %s", client->GetPlayer()->GetName(), client->GetAccountID(), command->required_status, command->command.data.c_str()); + safe_delete(sep); + client->SimpleMessage(3, "Error: Your status is insufficient for this command."); + return; + } + + Player* player = client->GetPlayer(); + LogWrite(COMMAND__DEBUG, 0, "Command", "Player '%s' (%u), Command: %s", player->GetName(), client->GetAccountID(), command->command.data.c_str()); + + switch (command->handler) { + case COMMAND_RELOAD: { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reload commands:"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload structs"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload items"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload luasystem"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload spawnscripts"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload spells"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload spells npc"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload quests"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload spawns"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload groundspawns"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload zonescripts"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload entity_commands"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload factions"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload mail"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload guilds"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload locations"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload rules"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload transporters"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload startabilities"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload voiceovers"); + break; + } + case COMMAND_RELOADSTRUCTS: { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Structs..."); + world.SetReloadingSubsystem("Structs"); + configReader.ReloadStructs(); + world.RemoveReloadingSubSystem("Structs"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); + break; + } + case COMMAND_RELOAD_QUESTS: { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Quests..."); + world.SetReloadingSubsystem("Quests"); + master_quest_list.Reload(); + client_list.ReloadQuests(); + zone_list.ReloadClientQuests(); + world.RemoveReloadingSubSystem("Quests"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); + break; + } + case COMMAND_RELOAD_SPAWNS: { + client->GetCurrentZone()->ReloadSpawns(); + break; + } + case COMMAND_RELOAD_SPELLS: { + if (sep && sep->arg[0] && strcmp(sep->arg[0], "npc") == 0) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading NPC Spells Lists (Note: Must Reload Spawns/Repop to reset npc spells)..."); + world.PurgeNPCSpells(); + database.LoadNPCSpells(); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Spells & NPC Spell Lists (Note: Must Reload Spawns/Repop to reset npc spells)..."); + world.SetReloadingSubsystem("Spells"); + zone_list.DeleteSpellProcess(); + master_spell_list.Reload(); + if (lua_interface) + lua_interface->ReloadSpells(); + zone_list.LoadSpellProcess(); + world.RemoveReloadingSubSystem("Spells"); + world.PurgeNPCSpells(); + database.LoadNPCSpells(); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); + } + break; + } + case COMMAND_RELOAD_GROUNDSPAWNS: { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Groundspawn Entries..."); + world.SetReloadingSubsystem("GroundSpawns"); + client->GetCurrentZone()->DeleteGroundSpawnItems(); + client->GetCurrentZone()->LoadGroundSpawnEntries(); + world.RemoveReloadingSubSystem("GroundSpawns"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); + break; + } + + case COMMAND_RELOAD_ZONESCRIPTS: { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Zone Scripts..."); + world.SetReloadingSubsystem("ZoneScripts"); + world.ResetZoneScripts(); + database.LoadZoneScriptData(); + if (lua_interface) + lua_interface->DestroyZoneScripts(); + world.RemoveReloadingSubSystem("ZoneScripts"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); + break; + } + case COMMAND_RELOAD_ENTITYCOMMANDS: { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Entity Commands..."); + world.SetReloadingSubsystem("EntityCommands"); + client->GetCurrentZone()->ClearEntityCommands(); + database.LoadEntityCommands(client->GetCurrentZone()); + world.RemoveReloadingSubSystem("EntityCommands"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); + break; + } + case COMMAND_RELOAD_FACTIONS: { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Factions..."); + world.SetReloadingSubsystem("Factions"); + master_faction_list.Clear(); + database.LoadFactionList(); + world.RemoveReloadingSubSystem("Factions"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); + break; + } + case COMMAND_RELOAD_MAIL: { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Mail..."); + zone_list.ReloadMail(); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); + break; + } + case COMMAND_RELOAD_GUILDS: { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Guilds..."); + world.ReloadGuilds(); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); + break; + } + case COMMAND_RELOAD_LOCATIONS: { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Locations..."); + client->GetPlayer()->GetZone()->RemoveLocationGrids(); + database.LoadLocationGrids(client->GetPlayer()->GetZone()); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); + break; + } + case COMMAND_RELOAD_RULES: { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Rules..."); + database.LoadRuleSets(true); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); + break; + } + case COMMAND_RELOAD_TRANSPORTERS: { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Transporters in your current zone..."); + database.LoadTransporters(client->GetCurrentZone()); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); + break; + } + case COMMAND_RELOAD_STARTABILITIES: { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Starting Skills/Spells..."); + world.PurgeStartingLists(); + world.LoadStartingLists(); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); + break; + } + case COMMAND_RELOAD_VOICEOVERS: { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Voiceovers..."); + world.PurgeVoiceOvers(); + world.LoadVoiceOvers(); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); + break; + } + case COMMAND_READ: { + if (sep && sep->arg[1][0] && sep->IsNumber(1)) { + if (strcmp(sep->arg[0], "read") == 0) { + int32 item_index = atol(sep->arg[1]); + Item* item = client->GetPlayer()->item_list.GetItemFromIndex(item_index); + if (item) { + Spawn* spawn = cmdTarget; + client->SendShowBook(client->GetPlayer(), item->name, item->book_language, item->book_pages); + break; + } + } + } + break; + } + case COMMAND_USEABILITY:{ + if (sep && sep->arg[0][0] && sep->IsNumber(0)) { + if (!client->GetPlayer()->Alive()) { + client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot do that right now."); + } + else { + int32 spell_id = atoul(sep->arg[0]); + int8 spell_tier = 0; + if (sep->arg[1][0] && sep->IsNumber(1)) + spell_tier = atoi(sep->arg[1]); + else + spell_tier = client->GetPlayer()->GetSpellTier(spell_id); + if (!spell_tier) + spell_tier = 1; + Spell* spell = master_spell_list.GetSpell(spell_id, spell_tier); + if (spell) { + if (strncmp(spell->GetName(), "Gathering", 9) == 0 || strncmp(spell->GetName(), "Mining", 6) == 0 || strncmp(spell->GetName(), "Trapping", 8) == 0 || strncmp(spell->GetName(), "Foresting", 9) == 0 || strncmp(spell->GetName(), "Fishing", 7) == 0 || strncmp(spell->GetName(), "Collecting", 10) == 0) + client->GetCurrentZone()->ProcessSpell(spell, client->GetPlayer(), cmdTarget, true, true); + else + { + if (cmdTarget) + client->GetCurrentZone()->ProcessSpell(spell, client->GetPlayer(), cmdTarget); + else + client->GetCurrentZone()->ProcessSpell(spell, client->GetPlayer(), client->GetPlayer()); + } + } + } + } + else if (cmdTarget && cmdTarget->IsWidget()) + { + Widget* widget = (Widget*)cmdTarget; + widget->HandleUse(client, "use", WIDGET_TYPE_DOOR); + } + else + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /useability {spell_id} [spell_tier]"); + } + break; + } + case COMMAND_INFO:{ + bool check_self_trade = true; + if(sep && sep->arg[1][0] && sep->IsNumber(1)){ + if(strcmp(sep->arg[0], "inventory") == 0){ + int32 item_index = atol(sep->arg[1]); + + LogWrite(COMMAND__DEBUG, 5, "Command", "/info inventory item original index %u", item_index); + if(client->GetVersion() <= 561) { + if(item_index <= 255) { + item_index = 255 - item_index; + } + else { + if(item_index == 256) { // first "new" item to inventory is assigned index 256 by client + item_index = client->GetPlayer()->item_list.GetFirstNewItem(); + } + else { + // otherwise the slot has to be mapped out depending on the amount of new items + index sent in + item_index = client->GetPlayer()->item_list.GetNewItemByIndex((int16)item_index - 255); + } + } + } + Item* item = client->GetPlayer()->item_list.GetItemFromIndex(item_index); + if(item){ + if (item->IsCollectable() && client->SendCollectionsForItem(item)) + break; + + EQ2Packet* app = 0; + if(client->GetVersion() <= 561) { + app = item->serialize(client->GetVersion(), true, client->GetPlayer()); + } + else { + app = item->serialize(client->GetVersion(), (!item->GetItemScript() || !lua_interface), client->GetPlayer()); + } + //DumpPacket(app); + client->QueuePacket(app); + if(item->GetItemScript() && lua_interface) + lua_interface->RunItemScript(item->GetItemScript(), "examined", item, client->GetPlayer()); + else if(item->generic_info.offers_quest_id > 0){ //leave the current functionality in place if it doesnt have an item script + Quest* quest = master_quest_list.GetQuest(item->generic_info.offers_quest_id, false); + if(quest && client->GetPlayer()->HasQuestBeenCompleted(item->generic_info.offers_quest_id) == 0 && client->GetPlayer()->HasActiveQuest(item->generic_info.offers_quest_id) == 0) + client->AddPendingQuest(new Quest(quest)); // copy quest since we pulled the master quest to see if it existed or not + } + } + else + LogWrite(COMMAND__ERROR, 0, "Command", "/info inventory: Unknown Index: %u", item_index); + } + else if(strcmp(sep->arg[0], "equipment") == 0){ + int32 item_index = client->GetPlayer()->ConvertSlotFromClient(atol(sep->arg[1]), client->GetVersion()); + Item* item = client->GetPlayer()->GetEquipmentList()->GetItem(item_index); + if(item){ + EQ2Packet* app = item->serialize(client->GetVersion(), true, client->GetPlayer()); + client->QueuePacket(app); + if(item->GetItemScript() && lua_interface) + lua_interface->RunItemScript(item->GetItemScript(), "examined", item, client->GetPlayer()); + } + else + LogWrite(COMMAND__ERROR, 0, "Command", "/info equipment: Unknown Index: %u", item_index); + } + else if(strcmp(sep->arg[0], "appearance") == 0){ + int32 item_index = client->GetPlayer()->ConvertSlotFromClient(atol(sep->arg[1]), client->GetVersion()); + Item* item = client->GetPlayer()->GetAppearanceEquipmentList()->GetItem(item_index); + if(item){ + EQ2Packet* app = item->serialize(client->GetVersion(), true, client->GetPlayer()); + client->QueuePacket(app); + if(item->GetItemScript() && lua_interface) + lua_interface->RunItemScript(item->GetItemScript(), "examined", item, client->GetPlayer()); + } + else + LogWrite(COMMAND__ERROR, 0, "Command", "/info appearance: Unknown Index: %u", item_index); + } + else if(strcmp(sep->arg[0], "item") == 0 || strcmp(sep->arg[0], "merchant") == 0 || strcmp(sep->arg[0], "store") == 0 || strcmp(sep->arg[0], "buyback") == 0 || strcmp(sep->arg[0], "consignment") == 0){ + int32 item_id = atoul(sep->arg[1]); + Item* item = master_item_list.GetItem(item_id); + if(item){ + EQ2Packet* app = item->serialize(client->GetVersion(), true, client->GetPlayer()); + client->QueuePacket(app); + } + else + LogWrite(COMMAND__ERROR, 0, "Command", "/info item|merchant|store|buyback|consignment: Unknown Item ID: %u", item_id); + } + else if (strcmp(sep->arg[0], "spell") == 0) { + sint32 spell_id = atol(sep->arg[1]); + int8 tier = atoi(sep->arg[3]); + EQ2Packet* outapp = master_spell_list.GetSpellPacket(spell_id, tier, client, true, 0x2A); + if (outapp) + client->QueuePacket(outapp); + else + LogWrite(COMMAND__ERROR, 0, "Command", "/info spell: Unknown Spell ID and/or Tier, ID: %u, Tier: %i", spell_id, tier); + } + else if (strcmp(sep->arg[0], "achievement") == 0) { + sint32 spell_id = atol(sep->arg[2]); + + int8 group = atoi(sep->arg[1]); + AltAdvanceData* data = 0; + SpellBookEntry* spellentry = 0; + int8 tier = client->GetPlayer()->GetSpellTier(spell_id); + + LogWrite(COMMAND__ERROR, 0, "Command", "/info achievement: AA Spell ID and/or Tier, ID: %u, Group: %i", spell_id, group); + EQ2Packet* outapp = master_spell_list.GetAASpellPacket(spell_id, tier, client, true, 0x45); + if (outapp) + client->QueuePacket(outapp); + else + LogWrite(COMMAND__ERROR, 0, "Command", "/info achievement: Unknown Spell ID and/or Tier, ID: %u, Tier: %i", spell_id, group); + } + else if (strcmp(sep->arg[0], "spellbook") == 0) { + sint32 spell_id = atol(sep->arg[1]); + int32 tier = atoi(sep->arg[2]); + if (tier > 255) { + SpellBookEntry* ent = client->GetPlayer()->GetSpellBookSpell(spell_id); + if (ent) + tier = ent->tier; + } + EQ2Packet* outapp = master_spell_list.GetSpellPacket(spell_id, (int8)tier, client, true, 0x2A); + if (outapp) + client->QueuePacket(outapp); + else + LogWrite(COMMAND__ERROR, 0, "Command", "/info spellbook: Unknown Spell ID and/or Tier, ID: %u, Tier: %i", spell_id, tier); + } + else if (strcmp(sep->arg[0], "recipe") == 0) { + sint32 recipe_id = atol(sep->arg[1]); + EQ2Packet* outapp = master_recipe_list.GetRecipePacket(recipe_id, client, true, 0x2C); + if(outapp) + client->QueuePacket(outapp); + else + LogWrite(COMMAND__ERROR, 0, "Command", "/info recipe: Unknown Recipe ID: %u", recipe_id); + } + else if (strcmp(sep->arg[0], "recipe_product") == 0) { + sint32 recipe_id = atol(sep->arg[1]); + + PlayerRecipeList* prl = client->GetPlayer()->GetRecipeList(); + Recipe* recipe = nullptr; + if ((recipe = prl->GetRecipe(recipe_id)) && recipe->GetProductID()) { + RecipeProducts* rp = recipe->products[1]; + if (recipe->GetProductID() > 0) { + Item* item = master_item_list.GetItem(recipe->GetProductID()); + if(item){ + EQ2Packet* app = item->serialize(client->GetVersion(), true, client->GetPlayer()); + client->QueuePacket(app); + } + else + LogWrite(COMMAND__ERROR, 0, "Command", "/info recipe_product: Unknown Item ID: %u", recipe->GetProductID()); + } + else { + LogWrite(COMMAND__ERROR, 0, "Command", "/info recipe_product: recipe->GetProductID() has value 0 for recipe id %u.", recipe_id); + } + } + else { + LogWrite(COMMAND__ERROR, 0, "Command", "/info recipe_product: with recipe id %u not found (recipe missing or no product in stage 1 assigned).", recipe_id); + } + } + else if (strcmp(sep->arg[0], "maintained") ==0) { + int32 slot = atol(sep->arg[1]); + int32 spell_id = atol(sep->arg[2]); + LogWrite(COMMAND__DEBUG, 5, "Command", "/info maintained: Spell ID - Slot: %u unknown: %u", slot, spell_id); + //int8 tier = client->GetPlayer()->GetSpellTier(spell_id); + MaintainedEffects* info = client->GetPlayer()->GetMaintainedSpellBySlot(slot); + EQ2Packet* outapp = master_spell_list.GetSpellPacket(info->spell_id, info->tier, client, true, 0x00); + if(outapp) + client->QueuePacket(outapp); + else + LogWrite(COMMAND__ERROR, 0, "Command", "/info maintained: Unknown Spell ID: %u", spell_id); + } + else if (strcmp(sep->arg[0], "effect") == 0) { + int32 spell_id = atol(sep->arg[1]); + LogWrite(COMMAND__DEBUG, 5, "Command", "/info effect: Spell ID: %u", spell_id); + int8 tier = client->GetPlayer()->GetSpellTier(spell_id); + int8 type = 0; + if (client->GetVersion() <= 561) + type = 1; + EQ2Packet* outapp = master_spell_list.GetSpecialSpellPacket(spell_id, tier, client, true, type); + if (outapp){ + client->QueuePacket(outapp); + } + else + LogWrite(COMMAND__ERROR, 0, "Command", "/info effect: Unknown Spell ID: %u", spell_id); + } + else if(sep->IsNumber(1) && ((strncasecmp("your_trade", sep->arg[0], 10) == 0) || + (strncasecmp("their_trade", sep->arg[0], 11) == 0))) + { + if(strncasecmp("t", sep->arg[0], 1) == 0) { // their_trade not self trade + check_self_trade = false; + } + + int8 slot_id = atoul(sep->arg[1]); + if(client->GetPlayer()->trade) { + Entity* traderToCheck = client->GetPlayer(); + if(!check_self_trade) { + traderToCheck = client->GetPlayer()->trade->GetTradee(client->GetPlayer()); + } + Item* tradeItem = client->GetPlayer()->trade->GetTraderSlot(traderToCheck, slot_id); + if(tradeItem != nullptr) { + EQ2Packet* app = tradeItem->serialize(client->GetVersion(), true, client->GetPlayer(), true, 0, 0, client->GetVersion() > 561 ? true : false); + client->QueuePacket(app); + } + } + } + } + else if (sep && strcmp(sep->arg[0], "overflow") == 0) { + Item* item = player->item_list.GetOverflowItem(); + if(item) { + EQ2Packet* app = item->serialize(client->GetVersion(), true, client->GetPlayer()); + client->QueuePacket(app); + } + else + LogWrite(COMMAND__ERROR, 0,"Command", "/info overflow: Unable to retrieve an overflow item."); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /info {inventory|equipment|spell} {id}"); + break; + } + case COMMAND_USE_EQUIPPED_ITEM:{ + if (sep && sep->arg[0] && sep->IsNumber(0)){ + int32 slot_id = atoul(sep->arg[0]); + Item* item = player->GetEquipmentList()->GetItem(slot_id); + if (item && item->generic_info.usable && item->GetItemScript()) { + if(!item->CheckFlag2(INDESTRUCTABLE) && item->generic_info.condition == 0) { + client->SimpleMessage(CHANNEL_COLOR_RED, "This item is broken and must be repaired at a mender before it can be used"); + } + else if (item->CheckFlag(EVIL_ONLY) && client->GetPlayer()->GetAlignment() != ALIGNMENT_EVIL) { + client->Message(0, "%s requires an evil race.", item->name.c_str()); + } + else if (item->CheckFlag(GOOD_ONLY) && client->GetPlayer()->GetAlignment() != ALIGNMENT_GOOD) { + client->Message(0, "%s requires a good race.", item->name.c_str()); + } + else { + lua_interface->RunItemScript(item->GetItemScript(), "used", item, player, player->GetTarget()); + } + } + } + break; + } + case COMMAND_USE_ITEM: { + if (sep && sep->arg[0] && sep->IsNumber(0)) { + int32 item_index = atoul(sep->arg[0]); + Item* item = player->item_list.GetItemFromIndex(item_index); + client->UseItem(item, client->GetPlayer()->GetTarget() ? client->GetPlayer()->GetTarget() : client->GetPlayer()); + } + break; + } + case COMMAND_SCRIBE_SCROLL_ITEM: { + if (sep && sep->arg[0] && sep->IsNumber(0)) { + Item* item = player->item_list.GetItemFromUniqueID(atoul(sep->arg[0])); + if (item) { + LogWrite(ITEM__DEBUG, 0, "Items", "ITEM ID: %u", item->details.item_id); + + if(item->generic_info.item_type == 6) { + Spell* spell = master_spell_list.GetSpell(item->skill_info->spell_id, item->skill_info->spell_tier); + int8 old_slot = 0; + if (spell) { + if(!spell->ScribeAllowed(client->GetPlayer())) { + client->SimpleMessage(CHANNEL_COLOR_RED, "You do not meet one of the requirements to scribe, due to class or level."); + break; + } + int16 tier_up = player->GetTierUp(spell->GetSpellTier()); + if (rule_manager.GetGlobalRule(R_Spells, RequirePreviousTierScribe)->GetInt8() && !player->HasSpell(spell->GetSpellID(), tier_up, false, true)) + client->SimpleMessage(CHANNEL_COLOR_RED, "You have not scribed the required previous version of this ability."); + else if (!player->HasSpell(spell->GetSpellID(), spell->GetSpellTier(), true)) { + old_slot = player->GetSpellSlot(spell->GetSpellID()); + player->RemoveSpellBookEntry(spell->GetSpellID()); + player->AddSpellBookEntry(spell->GetSpellID(), spell->GetSpellTier(), old_slot, spell->GetSpellData()->spell_book_type, spell->GetSpellData()->linked_timer, true); + player->UnlockSpell(spell); + client->SendSpellUpdate(spell); + database.DeleteItem(client->GetCharacterID(), item, 0); + player->item_list.RemoveItem(item, true); + client->QueuePacket(player->GetSpellBookUpdatePacket(client->GetVersion())); + client->QueuePacket(player->SendInventoryUpdate(client->GetVersion())); + + // force purge client cache and display updated spell for hover over + EQ2Packet* app = spell->SerializeSpell(client, false, false); + client->QueuePacket(app); + } + } + else + LogWrite(COMMAND__ERROR, 0, "Command", "Unknown spell ID: %u and tier: %u", item->skill_info->spell_id, item->skill_info->spell_tier); + } + else if(item->generic_info.item_type == 7){ + if(item->recipebook_info) { + LogWrite(TRADESKILL__DEBUG, 0, "Recipe", "Scribing recipe book %s (%u) for player %s.", item->name.c_str(), item->recipebook_info->recipe_id, player->GetName()); + client->AddRecipeBookToPlayer(item->recipebook_info->recipe_id, item); + } + else { + client->Message(CHANNEL_NARRATIVE, "Recipe book is unavailable! Report to admin recipe id %u could not be retrieved.", item->recipebook_info ? item->recipebook_info->recipe_id : 0); + LogWrite(COMMAND__ERROR, 0, "Command", "Recipe Book %u could not be retrieved for item %s.", item->recipebook_info ? item->recipebook_info->recipe_id : 0, item->name.c_str()); + } + } + else { + LogWrite(COMMAND__ERROR, 0, "Command", "Recipe Book Info is not set for item %s.", item->name.c_str()); + } + } + else + LogWrite(COMMAND__ERROR, 0, "Command", "Unknown unique item ID: %s", sep->arg[0]); + } + break; + } + case COMMAND_SUMMONITEM: { + if (sep && sep->IsNumber(0)) { + int32 item_id = atol(sep->arg[0]); + int32 quantity = 1; + + if (sep->arg[1] && sep->IsNumber(1)) + quantity = atoi(sep->arg[1]); + + if (sep->arg[2] && strncasecmp(sep->arg[2], "bank", 4) == 0) { + client->AddItemToBank(item_id,quantity); + } + else { + client->AddItem(item_id, quantity); + } + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /summonitem {item_id} [quantity] or"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /summonitem {item_id} {quantity} [location] where location = bank"); + } + break; + } + case COMMAND_COLLECTION_ADDITEM: { + // takes 2 params: collection_id, slot + if (sep && sep->arg[0] && sep->arg[1] && sep->IsNumber(0) && sep->IsNumber(1)) { + Item *item = client->GetPlayer()->GetPlayerItemList()->GetItemFromIndex(atoul(sep->arg[1])); + if (item) + client->HandleCollectionAddItem(atoul(sep->arg[0]), item); + else + LogWrite(COLLECTION__ERROR, 0, "Collect", "Error in 'COMMAND_COLLECTION_ADDITEM'. Unable to get item from player '%s' at index %u", client->GetPlayer()->GetName(), atoul(sep->arg[0])); + } + else + LogWrite(COLLECTION__ERROR, 0, "Collect", "Received command 'COMMAND_COLLECTION_ADDITEM' with unhandeled argument types"); + break; + } + case COMMAND_COLLECTION_FILTER_MATCHITEM: { + // takes 1 param: slot + printf("COMMAND_COLLECTION_FILTER_MATCHITEM:\n"); + int i = 0; + while (sep->arg[i] && strlen(sep->arg[i]) > 0) { + printf("\t%u: %s\n", i, sep->arg[i]); + i++; + } + break; + } + case COMMAND_WAYPOINT: { + bool success = false; + client->ClearWaypoint(); + if (sep && sep->IsNumber(0) && sep->IsNumber(1) && sep->IsNumber(2)) { + if (!client->ShowPathToTarget(atof(sep->arg[0]), atof(sep->arg[1]), atof(sep->arg[2]), 0)) + client->Message(CHANNEL_COLOR_RED, "Invalid coordinates given"); + } + else if ( client->GetAdminStatus() > 100 && cmdTarget ) { + if (!client->ShowPathToTarget(cmdTarget->GetX(), cmdTarget->GetY(), cmdTarget->GetZ(), 0)) + client->Message(CHANNEL_COLOR_RED, "Invalid coordinates given"); + } + else { + client->Message(CHANNEL_COLOR_YELLOW, "Usage: /waypoint x y z"); + } + break; + } + case COMMAND_WHO:{ + const char* who = 0; + if(sep && sep->arg[0]){ + //cout << "Who query: \n"; + who = sep->argplus[0]; + //cout << who << endl; + } + zone_list.ProcessWhoQuery(who, client); + break; + } + case COMMAND_SELECT_JUNCTION:{ + // transporters (bells birds) use OP_SelectZoneTeleporterDestinatio / ProcessTeleportLocation(app) + // this is only used for revive it seems + int32 choice = 0; + if(sep && sep->arg[0] && sep->IsNumber(0)) + choice = atoul(sep->arg[0]); + if(!client->GetPlayer()->Alive()){ //dead and this is a revive request + client->HandlePlayerRevive(choice); + } + break; + } + case COMMAND_SPAWN_MOVE: { + if (cmdTarget && cmdTarget->IsPlayer() == false) { + PacketStruct* packet = configReader.getStruct("WS_MoveObjectMode", client->GetVersion()); + if (packet) { + float unknown2_3 = 0; + int8 placement_mode = 0; + client->SetSpawnPlacementMode(Client::ServerSpawnPlacementMode::DEFAULT); + if (sep && sep->arg[0][0]) { + if (strcmp(sep->arg[0], "wall") == 0) { + placement_mode = 2; + unknown2_3 = 150; + } + else if (strcmp(sep->arg[0], "ceiling") == 0) + placement_mode = 1; + else if (strcmp(sep->arg[0], "openheading") == 0 && cmdTarget->IsWidget()) + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "[PlacementMode] WIDGET OPEN HEADING MODE"); + client->SetSpawnPlacementMode(Client::ServerSpawnPlacementMode::OPEN_HEADING); + } + else if (strcmp(sep->arg[0], "closeheading") == 0 && cmdTarget->IsWidget()) + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "[PlacementMode] WIDGET CLOSE HEADING MODE"); + client->SetSpawnPlacementMode(Client::ServerSpawnPlacementMode::CLOSE_HEADING); + } + else if (strcmp(sep->arg[0], "myloc") == 0) + { + if (cmdTarget->GetSpawnLocationPlacementID() < 1) { + client->Message(CHANNEL_COLOR_YELLOW, "[PlacementMode] Spawn %s cannot be moved it is not assigned a spawn location placement id.", cmdTarget->GetName()); + safe_delete(packet); + break; + } + + cmdTarget->SetX(client->GetPlayer()->GetX(), true); + cmdTarget->SetY(client->GetPlayer()->GetY(), true); + cmdTarget->SetZ(client->GetPlayer()->GetZ(), true); + cmdTarget->SetHeading(client->GetPlayer()->GetHeading(), true); + + if (database.UpdateSpawnLocationSpawns(cmdTarget)) + client->Message(CHANNEL_COLOR_YELLOW, "[PlacementMode] Spawn %s placed at your location. Updated spawn_location_placement for spawn.", cmdTarget->GetName()); + safe_delete(packet); + break; + } + } + packet->setDataByName("placement_mode", placement_mode); + packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(cmdTarget)); + packet->setDataByName("model_type", cmdTarget->GetModelType()); + packet->setDataByName("unknown", 1); //size + packet->setDataByName("unknown2", 1); //size 2 + packet->setDataByName("unknown2", .5, 1); //size 3 + packet->setDataByName("unknown2", 3, 2); + packet->setDataByName("unknown2", unknown2_3, 3); + packet->setDataByName("max_distance", 500); + packet->setDataByName("CoEunknown", 0xFFFFFFFF); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /spawn move (wall OR ceiling)"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Moves a spawn anywhere you like. Optionally wall/ceiling can be provided to place wall/ceiling items."); + } + break; + } + case COMMAND_HAIL:{ + Spawn* spawn = cmdTarget; + if(spawn && spawn->GetTargetable()) + { + char tmp[75] = {0}; + + sprintf(tmp, "Hail, %s", spawn->GetName()); + + bool show_bubble = true; + + if (spawn->IsNPC()) + show_bubble = false; + client->GetCurrentZone()->HandleChatMessage(client->GetPlayer(), 0, CHANNEL_SAY, tmp, HEAR_SPAWN_DISTANCE, 0, show_bubble, client->GetPlayer()->GetCurrentLanguage()); + if(spawn->IsPlayer() == false && spawn->Alive() && spawn->GetDistance(client->GetPlayer()) < rule_manager.GetGlobalRule(R_Spawn, HailDistance)->GetInt32()){ + if(spawn->IsNPC() && ((NPC*)spawn)->EngagedInCombat()) + spawn->GetZone()->CallSpawnScript(spawn, SPAWN_SCRIPT_HAILED_BUSY, client->GetPlayer()); + else + { + bool pauseRunback = false; + // prime runback as the heading or anything can be altered when hailing succeeds + if(spawn->IsNPC() && (spawn->HasMovementLoop() || spawn->HasMovementLocations())) + { + ((NPC*)spawn)->StartRunback(); + pauseRunback = true; + } + + if(spawn->GetZone()->CallSpawnScript(spawn, SPAWN_SCRIPT_HAILED, client->GetPlayer()) && pauseRunback) + spawn->PauseMovement(rule_manager.GetGlobalRule(R_Spawn, HailMovementPause)->GetInt32()); + else if(spawn->IsNPC() && pauseRunback) + ((NPC*)spawn)->ClearRunback(); + } + } + } + else { + string tmp = "Hail"; + client->GetCurrentZone()->HandleChatMessage(client->GetPlayer(), 0, CHANNEL_SAY, tmp.c_str(), HEAR_SPAWN_DISTANCE, 0, true, client->GetPlayer()->GetCurrentLanguage()); + } + break; + } + case COMMAND_SAY:{ + if (sep && sep->arg[0][0]) { + client->GetCurrentZone()->HandleChatMessage(client->GetPlayer(), 0, CHANNEL_SAY, sep->argplus[0], HEAR_SPAWN_DISTANCE, 0, true, client->GetPlayer()->GetCurrentLanguage()); + if (cmdTarget && !(cmdTarget->IsPlayer())) + client->GetCurrentZone()->CallSpawnScript(cmdTarget, SPAWN_SCRIPT_HEAR_SAY, client->GetPlayer(), sep->argplus[0]); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /say {message}"); + break; + } + case COMMAND_TELL:{ + if(sep && sep->arg[0] && sep->argplus[1]){ + if(!zone_list.HandleGlobalChatMessage(client, sep->arg[0], CHANNEL_PRIVATE_TELL, sep->argplus[1], 0, client->GetPlayer()->GetCurrentLanguage())) + client->Message(CHANNEL_COLOR_RED,"Unable to find client %s",sep->arg[0]); + }else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /tell {character_name} {message}"); + break; + } + case COMMAND_SHOUT:{ + if(sep && sep->arg[0][0]) + client->GetCurrentZone()->HandleChatMessage(client->GetPlayer(), 0, CHANNEL_SHOUT, sep->argplus[0], 0, 0, true, client->GetPlayer()->GetCurrentLanguage()); + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /shout {message}"); + break; + } + case COMMAND_AUCTION:{ + if(sep && sep->arg[0][0]) + client->GetCurrentZone()->HandleChatMessage(client->GetPlayer(), 0, CHANNEL_AUCTION, sep->argplus[0], 0, 0, true, client->GetPlayer()->GetCurrentLanguage()); + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /auction {message}"); + break; + } + case COMMAND_OOC:{ + //For now ooc will be the global chat channel, eventually when we create more channels we will create a global chat channel + if(sep && sep->arg[0][0]) + zone_list.HandleGlobalChatMessage(client, 0, CHANNEL_OUT_OF_CHARACTER, sep->argplus[0], 0, client->GetPlayer()->GetCurrentLanguage()); + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /ooc {message}"); + break; + } + case COMMAND_EMOTE:{ + if(sep && sep->arg[0][0]) + client->GetCurrentZone()->HandleChatMessage(client->GetPlayer(), 0, CHANNEL_EMOTE, sep->argplus[0]); + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /emote {action}"); + break; + } + case COMMAND_RACE:{ + if(sep && sep->arg[0][0] && sep->IsNumber(0)){ + if(sep->arg[1][0] && sep->IsNumber(1)){ + client->GetPlayer()->GetInfoStruct()->set_race(atoi(sep->arg[1])); + client->GetPlayer()->SetRace(atoi(sep->arg[1])); + client->UpdateTimeStampFlag ( RACE_UPDATE_FLAG ); + client->GetPlayer()->SetCharSheetChanged(true); + } + client->GetPlayer()->SetModelType(atoi(sep->arg[0])); + //EQ2Packet* outapp = client->GetPlayer()->spawn_update_packet(client->GetPlayer(), client->GetVersion(), client->GetPlayer()->GetFeatures()); + //client->QueuePacket(outapp); + } + else{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /race {race type id} {race id}"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "{race id} is optional"); + } + break; + } + case COMMAND_BANK_DEPOSIT:{ + if(client->GetBanker() && sep && sep->arg[0]){ + int64 amount = 0; + string deposit = string(sep->arg[0]); + amount = atoi64(deposit.c_str()); + client->BankDeposit(amount); + } + break; + } + case COMMAND_BANK_WITHDRAWAL:{ + if(client->GetBanker() && sep && sep->arg[0] && sep->IsNumber(0)){ + int64 amount = 0; + string deposit = string(sep->arg[0]); + amount = atoi64(deposit.c_str()); + client->BankWithdrawal(amount); + } + break; + } + case COMMAND_BANK_CANCEL:{ + Spawn* banker = cmdTarget; + client->Bank(banker, true); + break; + } + case COMMAND_BANK:{ + LogWrite(PLAYER__DEBUG, 0, "Players", "Open Player Personal Bank..."); + Spawn* banker = cmdTarget; + client->Bank(banker); + break; + } + case COMMAND_GUILD_BANK:{ + LogWrite(GUILD__DEBUG, 0, "Guilds", "Open Guild Bank..."); + //Spawn* banker = client->GetPlayer()->GetTarget(); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "This will eventually open the guild bank!"); + break; + } + case COMMAND_START_MAIL: { + client->SetMailTransaction(cmdTarget); + client->SendMailList(); + break; + } + case COMMAND_CANCEL_MAIL: { + client->SetMailTransaction(0); + break; + } + case COMMAND_GET_MAIL_MESSAGE: { + if (sep && sep->arg[0] && sep->IsNumber(0)) + client->DisplayMailMessage(atoul(sep->arg[0])); + break; + } + case COMMAND_ADD_MAIL_PLAT: { + if (sep && sep->arg[0] && sep->IsNumber(0)) + client->AddMailCoin(0, 0, 0, atoul(sep->arg[0])); + break; + } + case COMMAND_ADD_MAIL_GOLD: { + if (sep && sep->arg[0] && sep->IsNumber(0)) + client->AddMailCoin(0, 0, atoul(sep->arg[0])); + break; + } + case COMMAND_ADD_MAIL_SILVER: { + if (sep && sep->arg[0] && sep->IsNumber(0)) + client->AddMailCoin(0, atoul(sep->arg[0])); + break; + } + case COMMAND_ADD_MAIL_COPPER: { + if (sep && sep->arg[0] && sep->IsNumber(0)) + client->AddMailCoin(atoul(sep->arg[0])); + break; + } + case COMMAND_SET_MAIL_ITEM: { + if(sep && sep->IsNumber(0) && sep->IsNumber(1)) + { + Item* item = client->GetPlayer()->item_list.GetItemFromIndex(atoul(sep->arg[0])); + if(item) + { + int16 quantity = atoul(sep->arg[1]); + if(item->CheckFlag(NO_TRADE) || item->CheckFlag(ATTUNED) || item->CheckFlag(ARTIFACT) || item->CheckFlag2(HEIRLOOM)) + { + return; + } + if(item->IsBag()) + { + vector* bag_items = player->GetPlayerItemList()->GetItemsInBag(item); + if(bag_items && bag_items->size() > 0) + { + client->SimpleMessage(CHANNEL_COLOR_RED,"You cannot mail a bag with items inside it."); + safe_delete(bag_items); + return; + } + safe_delete(bag_items); + } + Item* itemtoadd = item; + if(quantity > 0) + { + if(quantity > item->details.count) + return; + + Item* tmpItem = new Item(item); + tmpItem->details.count = quantity; + itemtoadd = tmpItem; + } + + if(client->AddMailItem(itemtoadd)) + { + client->RemoveItem(item, quantity, true); + } + } + } + break; + } + case COMMAND_REMOVE_MAIL_PLAT: { + if (sep && sep->arg[0] && sep->IsNumber(0)) + client->RemoveMailCoin(0, 0, 0, atoul(sep->arg[0])); + break; + } + case COMMAND_REMOVE_MAIL_GOLD: { + if (sep && sep->arg[0] && sep->IsNumber(0)) + client->RemoveMailCoin(0, 0, atoul(sep->arg[0])); + break; + } + case COMMAND_REMOVE_MAIL_SILVER: { + if (sep && sep->arg[0] && sep->IsNumber(0)) + client->RemoveMailCoin(0, atoul(sep->arg[0])); + break; + } + case COMMAND_REMOVE_MAIL_COPPER: { + if (sep && sep->arg[0] && sep->IsNumber(0)) + client->RemoveMailCoin(atoul(sep->arg[0])); + break; + } + case COMMAND_TAKE_MAIL_ATTACHMENTS: { + if (sep && sep->arg[0] && sep->IsNumber(0)) + client->TakeMailAttachments(atoul(sep->arg[0])); + break; + } + case COMMAND_CANCEL_SEND_MAIL: { + client->ResetSendMail(); + break; + } + case COMMAND_DELETE_MAIL_MESSAGE: { + if (sep && sep->arg[0] && sep->IsNumber(0)) + client->DeleteMail(atoul(sep->arg[0]), true); + break; + } + case COMMAND_REPORT_SPAM: { + LogWrite(MISC__TODO, 1, "TODO", " received reportspam\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__); + if (sep && sep->arg[0]) LogWrite(COMMAND__DEBUG, 0, "Command", "%s\n", sep->argplus[0]); + break; + } + case COMMAND_KILL:{ + Spawn* dead = 0; + if (sep && sep->arg[0] && strncasecmp(sep->arg[0],"self",4)==0){ + dead=client->GetPlayer(); + client->GetPlayer()->SetHP(0); + client->GetPlayer()->KillSpawn(dead); + }else{ + dead= cmdTarget; + if(dead && dead->IsPlayer() == false){ + if(dead->IsNPC() && ((NPC*)dead)->Brain()) { + client->GetPlayer()->CheckEncounterState((Entity*)dead); + ((NPC*)dead)->Brain()->AddToEncounter(client->GetPlayer()); + ((NPC*)dead)->AddTargetToEncounter(client->GetPlayer()); + ((NPC*)dead)->Brain()->AddHate(client->GetPlayer(), 1); + } + dead->SetHP(0); + if(sep && sep->arg[0] && sep->IsNumber(0) && atoi(sep->arg[0]) == 1) + client->GetCurrentZone()->RemoveSpawn(dead, true, true, true, true, true); + else + client->GetPlayer()->KillSpawn(dead); + }else{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /kill (self)"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Kills currently selected non-player target."); + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Optionally, self may be used to kill yourself"); + } + } + break; + } + case COMMAND_LEVEL:{ + if(sep && sep->arg[ndx][0] && sep->IsNumber(0)){ + int16 new_level = atoi(sep->arg[ndx]); + if (!client->GetPlayer()->CheckLevelStatus(new_level)) + client->SimpleMessage(CHANNEL_COLOR_RED, "You do not have the required status to level up anymore!"); + else { + if (new_level < 1) + new_level = 1; + + if (new_level > 255) + new_level = 255; + + client->ChangeLevel(client->GetPlayer()->GetLevel(), new_level); + } + }else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /level {new_level}"); + break; + } + case COMMAND_SIT: { + if(client->GetPlayer()->GetHP() > 0){ + client->QueuePacket(new EQ2Packet(OP_SitMsg, 0, 0)); + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"You sit down."); + client->GetPlayer()->set_character_flag(CF_IS_SITTING); + } + break; + } + case COMMAND_STAND: { + if(client->GetPlayer()->GetHP() > 0){ + client->QueuePacket(new EQ2Packet(OP_StandMsg, 0, 0)); + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"You stand up."); + client->GetPlayer()->reset_character_flag(CF_IS_SITTING); + } + break; + } + case COMMAND_CLASS:{ + if(sep && sep->arg[ndx][0]){ + client->GetPlayer()->SetPlayerAdventureClass(atoi(sep->arg[ndx])); + client->UpdateTimeStampFlag ( CLASS_UPDATE_FLAG ); + }else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /class {class_id}"); + break; + } + case COMMAND_FLYMODE:{ + if(sep && sep->arg[0] && sep->IsNumber(0)){ + PrintSep(sep, "COMMAND_FLYMODE"); + int8 val = atoul(sep->arg[0]); + ClientPacketFunctions::SendFlyMode(client, val); + } + else{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage ON: /flymode [1|2] 1 = fly, 2 = no clip"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage OFF: /flymode 0"); + } + break; + } + case COMMAND_LOOT_LIST:{ + Spawn* spawn = cmdTarget; + if(spawn && spawn->IsEntity()){ + if (sep && sep->arg[0]) { + if (!spawn->GetDatabaseID()) + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "NOTE: Spawn has no database id to assign to loottables."); + } + + if (!spawn->IsNPC()) + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "these /loot list [add/remove/clearall] sub-commands are only designed for an NPC."); + break; + } + + bool reloadLoot = false; + if (!stricmp(sep->arg[0], "remove") && sep->arg[1] && sep->IsNumber(1)) + { + int32 loottable_id = atoul(sep->arg[1]); + if (loottable_id > 0 && database.RemoveSpawnLootTable(spawn, loottable_id)) + { + client->Message(CHANNEL_COLOR_YELLOW, "Spawn %u loot table %u removed.", spawn->GetDatabaseID(), loottable_id); + reloadLoot = true; + } + else + client->Message(CHANNEL_COLOR_YELLOW, "/loot list remove [loottableid] - could not match any spawn_id entries against loottable_id %u.", loottable_id); + } + else if (!stricmp(sep->arg[0], "add") && sep->arg[1] && sep->IsNumber(1)) + { + + int32 loottable_id = atoul(sep->arg[1]); + if (loottable_id > 0) + { + database.AddLootTableToSpawn(spawn, loottable_id); + client->Message(CHANNEL_COLOR_YELLOW, "Spawn %u loot table %u added.", spawn->GetDatabaseID(), loottable_id); + reloadLoot = true; + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/loot list add [loottableid] - cannot add a loottable id of 0."); + } + else if (!stricmp(sep->arg[0], "clearall")) + { + if (database.RemoveSpawnLootTable(spawn, 0)) + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Spawn loot tables removed."); + reloadLoot = true; + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/loot list clearall - could not match any spawn_id entries in loottable_id."); + } + else if (!stricmp(sep->arg[0], "details")) + { + spawn->LockLoot(); + client->SimpleMessage(CHANNEL_COMMANDS, "Loot Window List:"); + if (spawn->GetLootWindowList()->size() > 0) { + std::map::iterator itr; + for (itr = spawn->GetLootWindowList()->begin(); itr != spawn->GetLootWindowList()->end(); itr++) { + Spawn* looter = client->GetPlayer()->GetZone()->GetSpawnByID(itr->first); + if (looter) { + client->Message(CHANNEL_COLOR_YELLOW, "Looter: %s IsLootWindowOpen: %s, HasCompletedLootWindow: %s.", looter->GetName(), itr->second ? "NO" : "YES", spawn->HasSpawnLootWindowCompleted(itr->first) ? "YES" : "NO"); + } + } + } + spawn->UnlockLoot(); + } + else + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/loot list argument not supported."); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/loot list add [loottableid] - add new loottable to spawn"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/loot list remove [loottableid] - remove existing loottable from spawn"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/loot list clearall - remove all loottables from spawn"); + break; + } + + if (reloadLoot) + { + database.LoadSpawnLoot(spawn->GetZone(), spawn); + if (spawn->IsNPC()) + { + Entity* ent = (Entity*)spawn; + ent->SetLootCoins(0); + ent->ClearLoot(); + spawn->GetZone()->AddLoot((NPC*)spawn); + client->Message(CHANNEL_COLOR_YELLOW, "Spawn %u active loot purged and reloaded.", spawn->GetDatabaseID()); + } + } + break; // nothing further this is the end of these sub commands + } + else + { + vector loot_list = client->GetCurrentZone()->GetSpawnLootList(spawn->GetDatabaseID(), spawn->GetZone()->GetZoneID(), spawn->GetLevel(), race_types_list.GetRaceType(spawn->GetModelType()), spawn); + if (loot_list.size() > 0) { + client->Message(CHANNEL_COLOR_YELLOW, "%s belongs to the following loot lists: ", spawn->GetName()); + vector::iterator list_itr; + LootTable* table = 0; + for (list_itr = loot_list.begin(); list_itr != loot_list.end(); list_itr++) { + table = client->GetCurrentZone()->GetLootTable(*list_itr); + if (table) + client->Message(CHANNEL_COLOR_YELLOW, "%u - %s", *list_itr, table->name.c_str()); + } + } + client->Message(CHANNEL_COLOR_YELLOW, "Coins being carried: %u", spawn->GetLootCoins()); + vector* items = spawn->GetLootItems(); + if (items) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Spawn is carrying the following items: "); + vector::iterator itr; + Item* item = 0; + for (itr = items->begin(); itr != items->end(); itr++) { + item = *itr; + client->Message(CHANNEL_COLOR_YELLOW, "%u - %s", item->details.item_id, item->name.c_str()); + } + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Spawn is not carrying any items."); + } + } + break; + } + case COMMAND_LOOT_SETCOIN:{ + Spawn* spawn = cmdTarget; + if(spawn && spawn->IsEntity() && sep && sep->arg[0] && sep->IsNumber(0)){ + spawn->SetLootCoins(atoul(sep->arg[0])); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Successfully set coins."); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Syntax: /loot setcoin {amount}"); + break; + } + case COMMAND_LOOT_ADDITEM:{ + Spawn* spawn = cmdTarget; + if(spawn && spawn->IsEntity() && sep && sep->arg[0] && sep->IsNumber(0)){ + int16 charges = 1; + if(sep->arg[1] && sep->IsNumber(1)) + charges = atoi(sep->arg[1]); + spawn->AddLootItem(atoul(sep->arg[0]), charges); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Successfully added item."); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Syntax: /loot additem {item_id}"); + break; + } + case COMMAND_LOOT_REMOVEITEM:{ + Spawn* spawn = cmdTarget; + if(spawn && spawn->IsEntity() && sep && sep->arg[0] && sep->IsNumber(0)){ + Item* item = spawn->LootItem(atoul(sep->arg[0])); + lua_interface->SetLuaUserDataStale(item); + safe_delete(item); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Successfully removed item."); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Syntax: /loot removeitem {item_id}"); + break; + } + case COMMAND_LOOT_CORPSE: + case COMMAND_DISARM: + case COMMAND_LOOT:{ + if (cmdTarget && ((Entity*)cmdTarget)->IsNPC() && cmdTarget->Alive()) + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Your target is not dead."); + break; + } + + if(cmdTarget && cmdTarget->IsEntity()){ + if (cmdTarget->GetDistance(client->GetPlayer()) <= rule_manager.GetGlobalRule(R_Loot, LootRadius)->GetFloat()){ + if (!rule_manager.GetGlobalRule(R_Loot, AutoDisarmChest)->GetBool() && command->handler == COMMAND_DISARM ) + client->OpenChest(cmdTarget, true); + else + client->LootSpawnRequest(cmdTarget, rule_manager.GetGlobalRule(R_Loot, AutoDisarmChest)->GetBool()); + if (!(cmdTarget)->HasLoot()){ + if (((Entity*)cmdTarget)->IsNPC()) + client->GetCurrentZone()->RemoveDeadSpawn(cmdTarget); + } + } + else + client->Message(CHANNEL_COLOR_YELLOW, "You are too far away to interact with that"); + } + else if(!cmdTarget || cmdTarget->GetHP() > 0) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Invalid target."); + break; + } + case COMMAND_RELOADSPAWNSCRIPTS:{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Spawn Scripts...."); + if (lua_interface) + lua_interface->SetLuaSystemReloading(true); + world.ResetSpawnScripts(); + database.LoadSpawnScriptData(); + if(lua_interface) { + lua_interface->DestroySpawnScripts(); + lua_interface->SetLuaSystemReloading(false); + } + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Success!"); + break; + } + case COMMAND_RELOADREGIONSCRIPTS:{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Region Scripts...."); + if(lua_interface) { + lua_interface->DestroyRegionScripts(); + } + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Success!"); + break; + } + case COMMAND_RELOADLUASYSTEM:{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Attempting to reload entire LUA system...."); + + world.SetReloadingSubsystem("LuaSystem"); + + if(lua_interface) { + lua_interface->SetLuaSystemReloading(true); + } + + zone_list.DeleteSpellProcess(); + master_spell_list.Reload(); + if (lua_interface) + lua_interface->ReloadSpells(); + zone_list.LoadSpellProcess(); + if(lua_interface){ + map debug_clients = lua_interface->GetDebugClients(); + map::iterator itr; + for(itr = debug_clients.begin(); itr != debug_clients.end(); itr++){ + if(lua_interface) + lua_interface->UpdateDebugClients(itr->first); + } + } + + world.ResetSpawnScripts(); + database.LoadSpawnScriptData(); + + world.ResetZoneScripts(); + database.LoadZoneScriptData(); + + if(lua_interface) { + lua_interface->DestroySpawnScripts(); + lua_interface->DestroyRegionScripts(); + lua_interface->DestroyQuests(); + lua_interface->DestroyItemScripts(); + lua_interface->DestroyZoneScripts(); + } + + int32 quest_count = database.LoadQuests(); + + int32 spell_count = 0; + + if(lua_interface) { + spell_count = database.LoadSpellScriptData(); + lua_interface->SetLuaSystemReloading(false); + } + + world.RemoveReloadingSubSystem("LuaSystem"); + + client->Message(CHANNEL_COLOR_YELLOW, "Successfully loaded %u spell(s), %u quest(s).", spell_count, quest_count); + break; + } + case COMMAND_DECLINE_QUEST:{ + int32 quest_id = 0; + if(sep && sep->arg[0] && sep->IsNumber(0)) + quest_id = atoul(sep->arg[0]); + if(quest_id > 0){ + client->RemovePendingQuest(quest_id); + } + break; + } + case COMMAND_DELETE_QUEST:{ + int32 quest_id = 0; + if(sep && sep->arg[0] && sep->IsNumber(0)) + quest_id = atoul(sep->arg[0]); + if(quest_id > 0){ + client->GetPlayer()->MPlayerQuests.readlock(__FUNCTION__, __LINE__); + if(lua_interface && client->GetPlayer()->player_quests.count(quest_id) > 0) { + Quest* quest = client->GetPlayer()->player_quests[quest_id]; + client->GetPlayer()->MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + if (quest && quest->CanDeleteQuest()) { + lua_interface->CallQuestFunction(quest, "Deleted", client->GetPlayer()); + client->RemovePlayerQuest(quest_id); + client->GetCurrentZone()->SendQuestUpdates(client); + } + } + else { + client->GetPlayer()->MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + } + } + break; + } + case COMMAND_ACCEPT_QUEST:{ + int32 quest_id = 0; + if(sep && sep->arg[0] && sep->IsNumber(0)) + quest_id = atoul(sep->arg[0]); + if(quest_id > 0){ + client->AcceptQuest(quest_id); + } + break; + } + case COMMAND_NAME:{ + if(sep && sep->arg[ndx][0]){ + client->GetPlayer()->GetInfoStruct()->set_name(std::string(sep->arg[ndx]).substr(0,39)); + client->GetPlayer()->SetCharSheetChanged(true); + }else + client->Message(CHANNEL_COLOR_YELLOW,"Usage: /name {new_name}"); + + break; + } + case COMMAND_GROUP: { + if(sep && sep->arg[0]) + printf("Group: %s\n",sep->argplus[0]); + break; + } + case COMMAND_GROUPSAY:{ + GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo(); + if(sep && sep->arg[0] && gmi) + world.GetGroupManager()->GroupChatMessage(gmi->group_id, client->GetPlayer(), client->GetPlayer()->GetCurrentLanguage(), sep->argplus[0]); + break; + } + case COMMAND_GROUPINVITE: { + Entity* target = 0; + Client* target_client = 0; + + if (client->GetPlayer()->GetGroupMemberInfo() && !client->GetPlayer()->GetGroupMemberInfo()->leader) { + client->SimpleMessage(CHANNEL_COMMANDS, "You must be the group leader to invite."); + return; + } + + // name provided with command so it has to be a player, npc not supported here + if (sep && sep->arg[0] && strlen(sep->arg[0]) > 0){ + target_client = zone_list.GetClientByCharName(sep->arg[0]); + if (target_client) { + if (!target_client->IsConnected() || !target_client->IsReadyForSpawns()) + target = 0; + else + target = target_client->GetPlayer(); + } + } + + // if name was not provided use the players target, npc or player supported here + if (!target && client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsEntity()) { + target = (Entity*)client->GetPlayer()->GetTarget(); + if (target->IsPlayer()) { + target_client = ((Player*)target)->GetClient(); + } + } + + int8 result = world.GetGroupManager()->Invite(client->GetPlayer(), target); + + if (result == 0) { + client->Message(CHANNEL_COMMANDS, "You invite %s to group with you.", target->GetName()); + if (target_client) { + PacketStruct* packet = configReader.getStruct("WS_ReceiveOffer", target_client->GetVersion()); + if (packet) { + packet->setDataByName("type", 1); + packet->setDataByName("name", client->GetPlayer()->GetName()); + packet->setDataByName("unknown2", 1); + target_client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + } + else if (result == 1) + client->SimpleMessage(CHANNEL_COMMANDS, "That player is already in a group."); + else if (result == 2) + client->SimpleMessage(CHANNEL_COMMANDS, "That player has been invited to another group."); + else if (result == 3) + client->SimpleMessage(CHANNEL_COMMANDS, "Your group is already full."); + else if (result == 4) + client->SimpleMessage(CHANNEL_COMMANDS, "You have a pending invitation, cancel it first."); + else if (result == 5) + client->SimpleMessage(CHANNEL_COMMANDS, "You cannot invite yourself!"); + else if (result == 6) + client->SimpleMessage(CHANNEL_COMMANDS, "Could not locate the player."); + else + client->SimpleMessage(CHANNEL_COMMANDS, "Group invite failed, unknown error!"); + + break; + } + case COMMAND_GROUPDISBAND: { + GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo(); + + if (gmi) { // TODO: Leader check + // world.GetGroupManager()->SimpleGroupMessage(gmi->group_id, "Your group has been disbanded."); + world.GetGroupManager()->RemoveGroup(gmi->group_id); + } + + break; + } + case COMMAND_GROUP_LEAVE: { + GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo(); + + if (gmi) { + int32 group_id = gmi->group_id; + world.GetGroupManager()->RemoveGroupMember(group_id, client->GetPlayer()); + if (!world.GetGroupManager()->IsGroupIDValid(group_id)) { + // leader->Message(CHANNEL_COLOR_GROUP, "%s has left the group.", client->GetPlayer()->GetName()); + } + else { + world.GetGroupManager()->GroupMessage(group_id, "%s has left the group.", client->GetPlayer()->GetName()); + } + + client->SimpleMessage(CHANNEL_GROUP_CHAT, "You have left the group"); + } + + break; + } + case COMMAND_FRIEND_ADD:{ + string name = ""; + if(sep && sep->arg[0] && strlen(sep->arg[0]) > 1) + name = database.GetPlayerName(sep->arg[0]); + else if(cmdTarget && cmdTarget->IsPlayer()) + name = string(cmdTarget->GetName()); + if(name.length() > 0){ + if(strcmp(name.c_str(), client->GetPlayer()->GetName()) != 0){ + if(client->GetPlayer()->IsIgnored(name.c_str())){ + client->GetPlayer()->RemoveIgnore(name.c_str()); + client->Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "Removed ignore: %s", name.c_str()); + client->SendChatRelationship(3, name.c_str()); + } + client->GetPlayer()->AddFriend(name.c_str(), true); + client->SendChatRelationship(0, name.c_str()); + client->Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "Added friend: %s", name.c_str()); + if(zone_list.GetClientByCharName(name)) + client->Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "Friend: %s has logged in.", name.c_str()); + } + else + client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot be more friendly towards yourself!"); + } + else + client->SimpleMessage(CHANNEL_COLOR_CHAT_RELATIONSHIP, "That character does not exist."); + break; + } + case COMMAND_FRIEND_REMOVE:{ + string name = ""; + if(sep && sep->arg[0] && strlen(sep->arg[0]) > 1) + name = database.GetPlayerName(sep->arg[0]); + else if(cmdTarget && cmdTarget->IsPlayer()) + name = string(cmdTarget->GetName()); + if(name.length() > 0){ + client->GetPlayer()->RemoveFriend(name.c_str()); + client->SendChatRelationship(1, name.c_str()); + client->Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "Removed friend: %s", name.c_str()); + } + else + client->SimpleMessage(CHANNEL_COLOR_CHAT_RELATIONSHIP, "That character does not exist."); + break; + } + case COMMAND_FRIENDS:{ + + break; + } + case COMMAND_IGNORE_ADD:{ + string name = ""; + if(sep && sep->arg[0] && strlen(sep->arg[0]) > 1) + name = database.GetPlayerName(sep->arg[0]); + else if(cmdTarget && cmdTarget->IsPlayer()) + name = string(cmdTarget->GetName()); + if(name.length() > 0){ + if(strcmp(name.c_str(), client->GetPlayer()->GetName()) != 0){ + if(client->GetPlayer()->IsFriend(name.c_str())){ + client->GetPlayer()->RemoveFriend(name.c_str()); + client->Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "Removed friend: %s", name.c_str()); + client->SendChatRelationship(1, name.c_str()); + } + client->GetPlayer()->AddIgnore(name.c_str(), true); + client->SendChatRelationship(2, name.c_str()); + client->Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "Added ignore: %s", name.c_str()); + } + else + client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot ignore yourself!"); + } + else + client->SimpleMessage(CHANNEL_COLOR_CHAT_RELATIONSHIP, "That character does not exist."); + break; + } + case COMMAND_IGNORE_REMOVE:{ + string name = ""; + if(sep && sep->arg[0] && strlen(sep->arg[0]) > 1) + name = database.GetPlayerName(sep->arg[0]); + else if(cmdTarget && cmdTarget->IsPlayer()) + name = string(cmdTarget->GetName()); + if(name.length() > 0){ + client->GetPlayer()->RemoveIgnore(name.c_str()); + client->SendChatRelationship(3, name.c_str()); + client->Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "Removed ignore: %s", name.c_str()); + } + break; + } + case COMMAND_IGNORES:{ + + break; + } + case COMMAND_GROUP_KICK:{ + Entity* kicked = 0; + Client* kicked_client = 0; + int32 group_id = 0; + + if (!client->GetPlayer()->GetGroupMemberInfo() || !client->GetPlayer()->GetGroupMemberInfo()->leader) + return; + + if (sep && sep->arg[0]) { + kicked_client = zone_list.GetClientByCharName(string(sep->arg[0])); + if (kicked_client) + kicked = kicked_client->GetPlayer(); + } + else if (cmdTarget && cmdTarget->IsEntity()) { + kicked = (Entity*)cmdTarget; + } + + group_id = client->GetPlayer()->GetGroupMemberInfo()->group_id; + + bool send_kicked_message = world.GetGroupManager()->GetGroupSize(group_id) > 2; + if (kicked) + world.GetGroupManager()->RemoveGroupMember(group_id, kicked); + + if(send_kicked_message) + world.GetGroupManager()->GroupMessage(group_id, "%s was kicked from the group.", kicked->GetName()); + else + client->Message(CHANNEL_GROUP_CHAT, "You kicked %s from the group", kicked->GetName()); + + if (kicked_client) + kicked_client->SimpleMessage(CHANNEL_GROUP_CHAT, "You were kicked from the group"); + + break; + } + case COMMAND_GROUP_MAKE_LEADER:{ + + if (!client->GetPlayer()->GetGroupMemberInfo() || !client->GetPlayer()->GetGroupMemberInfo()->leader) { + client->SimpleMessage(CHANNEL_COMMANDS, "You are not a group leader."); + return; + } + + if (sep && sep->arg[0] && strlen(sep->arg[0]) > 1) { + Client* new_leader = zone_list.GetClientByCharName(sep->arg[0]); + if (new_leader && new_leader->GetPlayer()->GetGroupMemberInfo() && new_leader->GetPlayer()->GetGroupMemberInfo()->group_id == client->GetPlayer()->GetGroupMemberInfo()->group_id) { + if (client->GetPlayer()->IsGroupMember(new_leader->GetPlayer())) { + if(world.GetGroupManager()->MakeLeader(client->GetPlayer()->GetGroupMemberInfo()->group_id, new_leader->GetPlayer())) { + world.GetGroupManager()->GroupMessage(client->GetPlayer()->GetGroupMemberInfo()->group_id, "%s is now leader of the group.", new_leader->GetPlayer()->GetName()); + } + } + } + else + client->SimpleMessage(CHANNEL_COMMANDS, "Unable to find the given player."); + } + else if (cmdTarget && cmdTarget->IsPlayer() && ((Player*)cmdTarget)->GetGroupMemberInfo() && ((Player*)cmdTarget)->GetGroupMemberInfo()->group_id == client->GetPlayer()->GetGroupMemberInfo()->group_id) { + if(client->GetPlayer()->IsGroupMember((Player*)cmdTarget) && world.GetGroupManager()->MakeLeader(client->GetPlayer()->GetGroupMemberInfo()->group_id, (Entity*)cmdTarget)) { + world.GetGroupManager()->GroupMessage(client->GetPlayer()->GetGroupMemberInfo()->group_id, "%s is now leader of the group.", cmdTarget->GetName()); + } + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Unable to change group leader."); + + break; + } + case COMMAND_GROUP_ACCEPT_INVITE: { + if((sep && sep->arg[0] && strcmp(sep->arg[0], "group") == 0) || (!sep && client->GetVersion() <= 561)) { + int8 result = world.GetGroupManager()->AcceptInvite(client->GetPlayer()); + + if (result == 0) + client->SimpleMessage(CHANNEL_GROUP_CHAT, "You have joined the group."); + else if (result == 1) + client->SimpleMessage(CHANNEL_GROUP_CHAT, "You do not have a pending invite."); + else if (result == 2) + client->SimpleMessage(CHANNEL_GROUP_CHAT, "Unable to join group - could not find leader."); + else + client->SimpleMessage(CHANNEL_GROUP_CHAT, "Unable to join group - unknown error."); + } + break; + } + case COMMAND_GROUP_DECLINE_INVITE: { + if(sep && sep->arg[0] && strcmp(sep->arg[0], "group") == 0) { + world.GetGroupManager()->DeclineInvite(client->GetPlayer()); // TODO: Add message to leader + } + break; + } + case COMMAND_SUMMON:{ + char* search_name = 0; + if(sep && sep->arg[0][0]) + search_name = sep->arg[0]; + + if(!search_name && !cmdTarget){ + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /summon [name]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Summons a targeted spawn or a spawn by name to you. If more than one spawn matches name, it will summon closest."); + } + else{ + if(!client->Summon(search_name)) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "No matches found."); + } + break; + } + case COMMAND_GOTO:{ + const char* search_name = 0; + if(sep && sep->arg[0][0]) + search_name = sep->argplus[0]; + if(!search_name && !cmdTarget){ + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /goto [name]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Moves to targeted spawn or to a spawn by name. If more than one spawn matches name, you will move to closest."); + } + else{ + if(!client->GotoSpawn(search_name)) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "No matches found."); + } + break; + } + case COMMAND_MOVE:{ + bool success = false; + try{ + if(sep && sep->arg[2][0] && sep->IsNumber(0) && sep->IsNumber(1) && sep->IsNumber(2)){ + EQ2Packet* app = client->GetPlayer()->Move(atof(sep->arg[0]), atof(sep->arg[1]), atof(sep->arg[2]), client->GetVersion()); + if(app){ + client->QueuePacket(app); + success = true; + } + } + } + catch(...){} + if(!success){ + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /move x y z"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Moves the client to the given coordinates."); + } + break; + } + case COMMAND_SETTIME:{ + if(sep && sep->arg[0]){ + + world.MWorldTime.writelock(__FUNCTION__, __LINE__); + sscanf (sep->arg[0], "%d:%d", &world.GetWorldTimeStruct()->hour, &world.GetWorldTimeStruct()->minute); + if(sep->arg[1] && sep->IsNumber(1)) + world.GetWorldTimeStruct()->month = atoi(sep->arg[1]) - 1; //zero based indexes + if(sep->arg[2] && sep->IsNumber(2)) + world.GetWorldTimeStruct()->day = atoi(sep->arg[2]) - 1; //zero based indexes + if(sep->arg[3] && sep->IsNumber(3)) + world.GetWorldTimeStruct()->year = atoi(sep->arg[3]); + world.MWorldTime.releasewritelock(__FUNCTION__, __LINE__); + client->GetCurrentZone()->SendTimeUpdateToAllClients(); + } + else{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /settime [hour:minute] (month) (day) (year)"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Example: /settime 8:30"); + } + break; + } + case COMMAND_VERSION:{ + client->Message(CHANNEL_COLOR_YELLOW,"%s %s", EQ2EMU_MODULE, CURRENT_VERSION); + client->Message(CHANNEL_COLOR_YELLOW,"Last Compiled on %s %s", COMPILE_DATE, COMPILE_TIME); + auto [days, hours, minutes, seconds] = convertTimestampDuration((getCurrentTimestamp() - world.world_uptime)); + client->Message(CHANNEL_COLOR_YELLOW,"Uptime %u day(s), %u hour(s), %u minute(s), %u second(s).", days, hours, minutes, seconds); + break; + } + case COMMAND_ZONE:{ + int32 instanceID = 0; + string zone; + int32 zone_id = 0; + bool listSearch = false; + bool isInstance = false; + ZoneServer* zsZone = 0; + + if(sep && sep->arg[0][0]) + { + if(strncasecmp(sep->arg[0], "list", 4) == 0) + listSearch = true; + + else if(strncasecmp(sep->arg[0], "active", 6) == 0) + { + zone_list.SendZoneList(client); + break; + } + + else if(strncasecmp(sep->arg[0], "instance", 6) == 0) + { + if(sep->IsNumber(1)) + instanceID = atol(sep->arg[1]); + } + + else if(strncasecmp(sep->arg[0], "lock", 4) == 0) + { + PrintSep(sep, "ZONE LOCK"); + + if(sep->IsNumber(1)) + zsZone = zone_list.Get(atoul(sep->arg[1]), false, false, false); + else + zsZone = zone_list.Get(sep->arg[1], false, false, false); + + if( zsZone ) + { + zsZone->SetZoneLockState(true); + client->Message(CHANNEL_COLOR_YELLOW, "Zone %s (%u) is now locked.", zsZone->GetZoneName(), zsZone->GetZoneID()); + } + else + client->Message(CHANNEL_COLOR_RED, "Zone %s is not running and cannot be locked.", sep->arg[1]); + + break; + } + else if(strncasecmp(sep->arg[0], "unlock", 6) == 0) + { + PrintSep(sep, "ZONE UNLOCK"); + + if(sep->IsNumber(1)) + zsZone = zone_list.Get(atoul(sep->arg[1]), false, false, false); + else + zsZone = zone_list.Get(sep->arg[1], false, false, false); + + if( zsZone ) + { + zsZone->SetZoneLockState(false); + client->Message(CHANNEL_COLOR_YELLOW, "Zone %s (%u) is now unlocked.", zsZone->GetZoneName(), zsZone->GetZoneID()); + } + else + client->Message(CHANNEL_COLOR_RED, "Zone %s is not running and cannot be unlocked.", sep->arg[1]); + break; + } + else + { + if(sep->IsNumber(0)) + { + zone_id = atoul(sep->arg[0]); + string zonename = database.GetZoneName(zone_id); + + if(zonename.length() > 0) + zone = zonename; + } + else + zone = sep->arg[0]; + } + if(instanceID > 0) + { + ZoneServer* zsInstance = zone_list.GetByInstanceID(instanceID); + + if(zsInstance != NULL) + { + instanceID = zsInstance->GetInstanceID(); + zone = zsInstance->GetZoneName(); + zone_id = zsInstance->GetZoneID(); + isInstance = true; + } + } + } + + if(!listSearch) + { + if(zone.length() == 0) + client->Message(CHANNEL_COLOR_RED, "Error: Invalid Zone"); + else if(instanceID != client->GetCurrentZone()->GetInstanceID() || + zone_id != client->GetCurrentZone()->GetZoneID()) + { + const char* zonestr = zone.c_str(); + if(database.VerifyZone(zonestr)) + { + if(client->CheckZoneAccess(zonestr)) + { + client->Message(CHANNEL_COLOR_YELLOW,"Zoning to %s...", zonestr); + if(isInstance) + client->Zone(instanceID,true,true,false); + else + client->Zone(zonestr); + } + } + else + client->Message(CHANNEL_COLOR_RED, "Error: Zone '%s' not found. To get a list type /zone list", zonestr); + } + else + client->Message(CHANNEL_COLOR_RED, "Error: You are already in that zone!"); + } + else + { + const char* name = 0; + if(sep && sep->arg[1][0]) + { + map* zone_names; + name = sep->argplus[1]; + sint16 status = client->GetAdminStatus(); + if( status > 0 ) + { + client->SimpleMessage(CHANNEL_COLOR_RED, "ADMIN MODE"); + zone_names = database.GetZoneList(name, true); + } + else + zone_names = database.GetZoneList(name); + + if(!zone_names) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "No zones found."); + else + { + map::iterator itr; + client->SimpleMessage(CHANNEL_COLOR_YELLOW," ID Name: "); + for(itr = zone_names->begin(); itr != zone_names->end(); itr++) + client->Message(CHANNEL_COLOR_YELLOW,"%03lu %s", itr->first, itr->second.c_str()); + safe_delete(zone_names); + } + } + else + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Syntax: /zone [zone name]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Syntax: /zone list [partial zone name]"); + } + } + break; + } + case COMMAND_USE: { + Spawn* target = cmdTarget; + if (target && target->IsWidget()) + ((Widget*)target)->HandleUse(client, "use"); + break; + } + case COMMAND_OPEN: { + Spawn* target = cmdTarget; + if (target && target->IsWidget()) + ((Widget*)target)->HandleUse(client, "Open", WIDGET_TYPE_DOOR); + break; + } + case COMMAND_CASTSPELL: { + if (sep && sep->arg[0] && sep->IsNumber(0)) + { + int8 tier = 1; + if (sep->arg[1] && sep->IsNumber(1)) + tier = atoul(sep->arg[1]); + + int8 self = 1; + if (sep->arg[1] && sep->IsNumber(2)) + { + self = atoul(sep->arg[2]); + if(self != 1 && !cmdTarget->IsEntity()) + { + client->Message(CHANNEL_COLOR_RED, "Target is not an entity required to cast non-self spell."); + self = 1; + } + } + + int32 spellid = atoul(sep->arg[0]); + Spell* spell = master_spell_list.GetSpell(spellid, tier); + + if (spell) + { + client->Message(CHANNEL_COLOR_RED, "Casting spell %u.", spellid); + SpellProcess* spellProcess = 0; + // Get the current zones spell process + spellProcess = client->GetCurrentZone()->GetSpellProcess(); + + spellProcess->CastInstant(spell, (!cmdTarget || self == 1) ? (Entity*)client->GetPlayer() : (Entity*)cmdTarget, (cmdTarget && cmdTarget->IsEntity()) ? (Entity*)cmdTarget : (Entity*)client->GetPlayer()); + } + else + { + client->Message(CHANNEL_COLOR_RED, "Could not find spell %u.",spellid); + } + } + else if (sep && sep->arg[0]) + { + database.FindSpell(client, (char*)sep->argplus[0]); + } + else + { + client->Message(CHANNEL_COLOR_YELLOW, "Syntax: /castspell [spellid] (tier=1) - Cast Spell with specified spell id, tier is optional, default of 1."); + client->Message(CHANNEL_COLOR_YELLOW, "Syntax: /castspell [spellname] - Find spells wildcard match with a partial spell name"); + } + break; + } + case COMMAND_KNOWLEDGEWINDOWSORT: { + + if (sep && (client->GetVersion() <= 561 && sep->GetArgNumber() == 3) || sep->GetArgNumber() == 4) + { + int32 book = atoul(sep->arg[0]); // 0 - spells, 1 - combat, 2 - abilities, 3 - tradeskill + + // cannot sort the ability book in this client it is greyed out + if (client->GetVersion() <= 561 && book == SPELL_BOOK_TYPE_ABILITY) + break; + + int32 sort_by = atoul(sep->arg[1]); // 0 - alpha, 1 - level, 2 - category + int32 order = atoul(sep->arg[2]); // 0 - ascending, 1 - descending + int32 pattern = atoul(sep->arg[3]); // 0 - zigzag, 1 - down, 2 - across + int32 maxlvlonly = 0; + + if(client->GetVersion() > 561 && sep->arg[4][0]) { + maxlvlonly = atoul(sep->arg[4]); // checkbox for newer clients + } + client->GetPlayer()->ResortSpellBook(sort_by, order, pattern, maxlvlonly, book); + ClientPacketFunctions::SendSkillSlotMappings(client); + } + else + { + client->Message(CHANNEL_COLOR_YELLOW, "Syntax: /knowledgewindow_sort [book] [sort_by] [order] [pattern]"); + } + break; + } + case COMMAND_MOVE_ITEM: + { + int32 id = 0; + + if (sep && sep->IsNumber(0)) + id = atoul(sep->arg[0]); + + Spawn* spawn = 0; + if (id == 0) + { + spawn = cmdTarget; + } + else + spawn = client->GetCurrentZone()->GetSpawnFromUniqueItemID(id); + + if (!spawn || !client->HasOwnerOrEditAccess() || !spawn->GetPickupItemID()) + break; + + client->SendMoveObjectMode(spawn, 0); + break; + } + case COMMAND_PICKUP: + { + int32 id = 0; + if (sep && sep->IsNumber(0)) + id = atoul(sep->arg[0]); + + Spawn* spawn = 0; + if (id == 0) + { + spawn = cmdTarget; + } + else + spawn = client->GetCurrentZone()->GetSpawnFromUniqueItemID(id); + + if (!spawn || !client->HasOwnerOrEditAccess() || !spawn->GetPickupItemID()) + break; + + if(client->AddItem(spawn->GetPickupItemID(), 1)) { + Query query; + query.RunQuery2(Q_INSERT, "delete from spawn_instance_data where spawn_id = %u and spawn_location_id = %u and pickup_item_id = %u", spawn->GetDatabaseID(), spawn->GetSpawnLocationID(), spawn->GetPickupItemID()); + + if (database.RemoveSpawnFromSpawnLocation(spawn)) { + client->GetCurrentZone()->RemoveSpawn(spawn, true, true, true, true, true); + } + + // we had a UI Window displayed, update the house items + if ( id > 0 ) + client->GetCurrentZone()->SendHouseItems(client); + } + + break; + } + case COMMAND_HOUSE_DEPOSIT: + { + PrintSep(sep, "COMMAND_HOUSE_DEPOSIT"); + // arg0 = spawn_id for DoF, could also be house_id for newer clients + // arg1 = coin (in copper) + // arg2 = status? (not implemented yet) + PlayerHouse* ph = nullptr; + int32 spawn_id = 0; + if(sep && sep->arg[0]) { + spawn_id = atoul(sep->arg[0]); + ph = world.GetPlayerHouse(client, spawn_id, client->GetVersion() > 561 ? spawn_id : 0, nullptr); + } + + if(!ph) { + ph = world.GetPlayerHouseByInstanceID(client->GetCurrentZone()->GetInstanceID()); + } + if (ph && sep && sep->IsNumber(1)) + { + int64 outValCoin = strtoull(sep->arg[1], NULL, 0); + int32 outValStatus = 0; + + if(sep->IsNumber(2)) + { + outValStatus = strtoull(sep->arg[2], NULL, 0); + if(outValStatus > client->GetPlayer()->GetInfoStruct()->get_status_points()) + outValStatus = 0; // cheat check + } + + if ((!outValCoin && outValStatus) || client->GetPlayer()->RemoveCoins(outValCoin)) + { + if(outValStatus) + client->GetPlayer()->GetInfoStruct()->subtract_status_points(outValStatus); + char query[256]; + map::iterator itr = ph->depositsMap.find(string(client->GetPlayer()->GetName())); + if (itr != ph->depositsMap.end()) + { + snprintf(query, 256, "update character_house_deposits set timestamp = %u, amount = amount + %llu, last_amount = %llu, status = status + %u, last_status = %u where house_id = %u and instance_id = %u and name='%s'", Timer::GetUnixTimeStamp(), outValCoin, outValCoin, outValStatus, outValStatus, ph->house_id, ph->instance_id, client->GetPlayer()->GetName()); + } + else + snprintf(query, 256, "insert into character_house_deposits set timestamp = %u, house_id = %u, instance_id = %u, name='%s', amount = %llu, status = %u", Timer::GetUnixTimeStamp(), ph->house_id, ph->instance_id, client->GetPlayer()->GetName(), outValCoin, outValStatus); + + if (database.RunQuery(query, strnlen(query, 256))) + { + ph->escrow_coins += outValCoin; + ph->escrow_status += outValStatus; + database.UpdateHouseEscrow(ph->house_id, ph->instance_id, ph->escrow_coins, ph->escrow_status); + + database.LoadDeposits(ph); + client->PlaySound("coin_cha_ching"); + HouseZone* hz = world.GetHouseZone(ph->house_id); + ClientPacketFunctions::SendBaseHouseWindow(client, hz, ph, client->GetVersion() <= 561 ? spawn_id : client->GetPlayer()->GetID()); + } + else + { + if(outValCoin) + client->GetPlayer()->AddCoins(outValCoin); + + if(outValStatus) + client->GetPlayer()->GetInfoStruct()->add_status_points(outValStatus); + client->SimpleMessage(CHANNEL_COLOR_RED, "Deposit failed!"); + } + } + } + break; + } + case COMMAND_HOUSE: + { + int32 spawn_id = 0; + if (sep && sep->IsNumber(0)) + { + PlayerHouse* ph = nullptr; + if(!ph && sep && sep->arg[0]) { + spawn_id = atoul(sep->arg[0]); + ph = world.GetPlayerHouse(client, spawn_id, spawn_id, nullptr); + } + + HouseZone* hz = 0; + + if (ph) + hz = world.GetHouseZone(ph->house_id); + // there is a arg[1] that is true/false, but not sure what it is for investigate more later + ClientPacketFunctions::SendBaseHouseWindow(client, hz, ph, client->GetVersion() <= 561 ? spawn_id : client->GetPlayer()->GetID()); + } + else if (client->GetCurrentZone()->GetInstanceType() != 0) + { + // inside a house or something? Send the access window + PlayerHouse* ph = world.GetPlayerHouseByInstanceID(client->GetCurrentZone()->GetInstanceID()); + + HouseZone* hz = 0; + if ( ph ) + hz = world.GetHouseZone(ph->house_id); + + ClientPacketFunctions::SendBaseHouseWindow(client, hz, ph, client->GetVersion() <= 561 ? spawn_id : client->GetPlayer()->GetID()); + client->GetCurrentZone()->SendHouseItems(client); + } + break; + } + case COMMAND_HOUSE_UI: + { + if (sep && sep->IsNumber(0) && client->GetCurrentZone()->GetInstanceType() == Instance_Type::NONE) + { + int32 unique_id = atoi(sep->arg[0]); + PlayerHouse* ph = world.GetPlayerHouseByUniqueID(unique_id); + HouseZone* hz = 0; + if ( ph ) + hz = world.GetHouseZone(ph->house_id); + // there is a arg[1] that is true/false, but not sure what it is for investigate more later + ClientPacketFunctions::SendBaseHouseWindow(client, hz, ph, client->GetPlayer()->GetID()); + } + break; + } + case COMMAND_PLACE_HOUSE_ITEM: { + if (sep && sep->IsNumber(0)) + { + int32 uniqueid = atoi(sep->arg[0]); + + Item* item = client->GetPlayer()->item_list.GetItemFromUniqueID(uniqueid); + //Item* item = player->GetEquipmentList()->GetItem(slot); + if (item && item->IsHouseItem()) + { + if (!client->HasOwnerOrEditAccess()) + { + client->SimpleMessage(CHANNEL_COLOR_RED, "This is not your home!"); + break; + } + else if (!item->generic_info.appearance_id) + { + client->Message(CHANNEL_COLOR_RED, "This item has not been configured in the database, %s (%u) needs an entry where ?? has the model type id, eg. insert into item_appearances set item_id=%u,equip_type=??;", item->name.c_str(), item->details.item_id, item->details.item_id); + break; + } + else if(item->CheckFlag2(HOUSE_LORE) && client->GetCurrentZone()->HouseItemSpawnExists(item->details.item_id)) { + client->Message(CHANNEL_COLOR_RED, "Item %s is house lore and you cannot place another.", item->name.c_str()); + break; + } + + if (client->GetTempPlacementSpawn()) + { + Spawn* tmp = client->GetTempPlacementSpawn(); + client->GetCurrentZone()->RemoveSpawn(tmp, true, true, true, true, true); + client->SetTempPlacementSpawn(nullptr); + } + + + Object* obj = new Object(); + Spawn* spawn = (Spawn*)obj; + memset(&spawn->appearance, 0, sizeof(spawn->appearance)); + strcpy(spawn->appearance.name, "temp"); + spawn->SetX(client->GetPlayer()->GetX()); + spawn->SetY(client->GetPlayer()->GetY()); + spawn->SetZ(client->GetPlayer()->GetZ()); + spawn->appearance.pos.collision_radius = 32; + spawn->SetHeading(client->GetPlayer()->GetHeading()); + spawn->SetSpawnOrigX(spawn->GetX()); + spawn->SetSpawnOrigY(spawn->GetY()); + spawn->SetSpawnOrigZ(spawn->GetZ()); + spawn->SetSpawnOrigHeading(spawn->GetHeading()); + spawn->appearance.targetable = 1; + spawn->appearance.activity_status = ACTIVITY_STATUS_SOLID; + spawn->appearance.race = item && item->generic_info.appearance_id ? item->generic_info.appearance_id : 1472; + spawn->SetModelType(item && item->generic_info.appearance_id ? item->generic_info.appearance_id : 1472); + spawn->SetLocation(client->GetPlayer()->GetLocation()); + spawn->SetZone(client->GetCurrentZone()); + client->SetTempPlacementSpawn(spawn); + client->SetPlacementUniqueItemID(uniqueid); + client->GetCurrentZone()->AddSpawn(spawn); + } + } + break; + } + case COMMAND_GM: + { + if (sep && sep->arg[0] && sep->arg[1]) + { + bool onOff = (strcmp(sep->arg[1], "on") == 0); + if (strcmp(sep->arg[0], "vision") == 0) + { + client->GetPlayer()->SetGMVision(onOff); + #if defined(__GNUC__) + database.insertCharacterProperty(client, CHAR_PROPERTY_GMVISION, (onOff) ? (char*)"1" : (char*)"0"); + #else + database.insertCharacterProperty(client, CHAR_PROPERTY_GMVISION, (onOff) ? "1" : "0"); + #endif + client->GetCurrentZone()->SendAllSpawnsForVisChange(client, false); + + if (onOff) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "GM Vision Enabled!"); + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "GM Vision Disabled!"); + } + else if (strcmp(sep->arg[0], "tag") == 0) + { + int32 value = 0; + int16 tag_icon = 0; + // groundspawn has special exception -1 argument so it needs to be handled here + if(sep->arg[2] && sep->arg[3] && ((tag_icon = atoul(sep->arg[3])) > 0) || (strncasecmp(sep->arg[1], "groundspawn", 11) == 0)) { + value = atoul(sep->arg[2]); + if(strncasecmp(sep->arg[1], "faction", 7) == 0){ + if(!value) { + client->SimpleMessage(CHANNEL_COLOR_RED, "Need to supply a valid faction id."); + break; + } + if(!tag_icon) { + client->SimpleMessage(CHANNEL_COLOR_RED, "Need to supply a valid tag icon id."); + break; + } + client->GetPlayer()->AddGMVisualFilter(GMTagFilterType::GMFILTERTYPE_FACTION, value, "", tag_icon); + client->GetCurrentZone()->SendAllSpawnsForVisChange(client, false); + client->Message(CHANNEL_COLOR_RED, "Adding faction id %u with tag icon %u.", value, tag_icon); + } + else if(strncasecmp(sep->arg[1], "spawngroup", 10) == 0){ + if(value>0) { + value = 1; + } + if(!tag_icon) { + client->SimpleMessage(CHANNEL_COLOR_RED, "Need to supply a valid tag icon id."); + break; + } + client->GetPlayer()->AddGMVisualFilter(GMTagFilterType::GMFILTERTYPE_SPAWNGROUP, value, "", tag_icon); + client->GetCurrentZone()->SendAllSpawnsForVisChange(client, false); + client->Message(CHANNEL_COLOR_RED, "Adding spawn group tag \"%s\" with tag icon %u.", (value == 1) ? "on" : "off", tag_icon); + } + else if(strncasecmp(sep->arg[1], "race", 4 == 0)){ + if(!value) { + client->SimpleMessage(CHANNEL_COLOR_RED, "Need to supply a valid race id."); + break; + } + if(!tag_icon) { + client->SimpleMessage(CHANNEL_COLOR_RED, "Need to supply a valid tag icon id."); + break; + } + client->GetPlayer()->AddGMVisualFilter(GMTagFilterType::GMFILTERTYPE_RACE, value, "", tag_icon); + client->GetCurrentZone()->SendAllSpawnsForVisChange(client, false); + client->Message(CHANNEL_COLOR_RED, "Adding race id %u with tag icon %u.", value, tag_icon); + } + else if(strncasecmp(sep->arg[1], "groundspawn", 11) == 0){ + // one less argument value field not tag_icon for this one + if(!value) { + client->SimpleMessage(CHANNEL_COLOR_RED, "Need to supply a valid tag icon id."); + break; + } + client->GetPlayer()->AddGMVisualFilter(GMTagFilterType::GMFILTERTYPE_GROUNDSPAWN, 1, "", value); + client->GetCurrentZone()->SendAllSpawnsForVisChange(client, false); + client->Message(CHANNEL_COLOR_RED, "Adding groundspawns with tag icon %u.", value); + } + } + else { + if(strncasecmp(sep->arg[1], "clear", 5) == 0){ + client->GetPlayer()->ClearGMVisualFilters(); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "GM Tags Cleared!"); + client->GetCurrentZone()->SendAllSpawnsForVisChange(client, false); + } + else { + client->SimpleMessage(CHANNEL_COLOR_RED, "GM Tagging Missing Arguments:"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/gm tag [type] [value] [visual_icon_display]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/gm tag clear - Clears all GM visual tags"); + + client->SimpleMessage(CHANNEL_COLOR_RED, "Visual Type Options:"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "type: faction, value: faction id of spawn(s)"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "type: spawngroup, value: 1 to show grouped, 0 to show not grouped"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "type: race, value: race id (either against base race or race id in spawn details)"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "type: groundspawn, value: (not used)"); + + client->SimpleMessage(CHANNEL_COLOR_RED, "Visual Icon Options:"); + + client->SimpleMessage(CHANNEL_COLOR_YELLOW, + "1 = skull, 2 = shield half dark blue / half light blue, 3 = purple? star, 4 = yellow sword, 5 = red X, 6 = green flame"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, + "7 = Number \"1\", 8 = Number \"2\", 9 = Number \"3\", 10 = Number \"4\", 11 = Number \"5\", 12 = Number \"6\""); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, + "25 = shield, 26 = green plus, 27 = crossed swords, 28 = bow with arrow in it, 29 = light blue lightning bolt"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, + "30 = bard instrument (hard to see), 31 = writ with shield, 32 = writ with green +, 33 = writ with crossed sword"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, + "34 = writ with bow, 35 = writ with light blue lightning bolt, 36 = same as 30, 37 = party with crossed sword, shield and lightning bolt"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, + "38 = shaking hands green background, 39 = shaking hands dark green background, unlocked keylock"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, + "40 = red aura icon with black shadow of person and big red aura, 41 = green aura icon with black shadow of person big green aura"); + } + } + } + else if (strcmp(sep->arg[0], "regiondebug") == 0) + { + client->SetRegionDebug(onOff); + #if defined(__GNUC__) + database.insertCharacterProperty(client, CHAR_PROPERTY_REGIONDEBUG, (onOff) ? (char*)"1" : (char*)"0"); + #else + database.insertCharacterProperty(client, CHAR_PROPERTY_REGIONDEBUG, (onOff) ? "1" : "0"); + #endif + + if (onOff) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Region Debug Enabled!"); + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Region Debug Disabled!"); + } + else if (strcmp(sep->arg[0], "sight") == 0) + { + if(onOff && cmdTarget) { + client->SetPlayerPOVGhost(cmdTarget); + } + else + client->SetPlayerPOVGhost(nullptr); + } + else if (strcmp(sep->arg[0], "controleffects") == 0) + { + if(cmdTarget && cmdTarget->IsEntity()) { + ((Entity*)cmdTarget)->SendControlEffectDetailsToClient(client); + } + else { + client->GetPlayer()->SendControlEffectDetailsToClient(client); + } + } + else if (strcmp(sep->arg[0], "luadebug") == 0) + { + client->SetLuaDebugClient(onOff); +#if defined(__GNUC__) + database.insertCharacterProperty(client, CHAR_PROPERTY_LUADEBUG, (onOff) ? (char*)"1" : (char*)"0"); +#else + database.insertCharacterProperty(client, CHAR_PROPERTY_LUADEBUG, (onOff) ? "1" : "0"); +#endif + + if (onOff) + { + if (lua_interface) + lua_interface->UpdateDebugClients(client); + + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You will now receive LUA error messages."); + } + else + { + if (lua_interface) + lua_interface->RemoveDebugClients(client); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You will no longer receive LUA error messages."); + } + } + } + break; + } + case COMMAND_ATTACK: + case COMMAND_AUTO_ATTACK:{ + Command_AutoAttack(client, sep); + break; + } + case COMMAND_DEPOP:{ + bool allow_respawns = false; + if(sep && sep->arg[0] && sep->IsNumber(0)){ + if(atoi(sep->arg[0]) == 1) + allow_respawns = true; + } + client->GetCurrentZone()->Depop(allow_respawns); + break; + } + case COMMAND_REPOP:{ + client->GetCurrentZone()->Depop(false, true); + break; + } + case COMMAND_BUYBACK:{ + if(sep && sep->arg[1][0] && sep->IsNumber(0) && sep->IsNumber(1)){ + // Item ID and Unique ID get combined in this command so we need to get the number as an int64 and break it into 2 int32's + int64 id = atoi64(sep->arg[0]); + // get the first int32, the item id + //int32 item_id = (int32)id; + // get the second int32, the unique id + int32 unique_id = (int32)(id>>32); + int8 quantity = atoi(sep->arg[1]); + if(quantity == 255) + quantity = 1; + client->BuyBack(unique_id, quantity); + } + break; + } + case COMMAND_MERCHANT_BUY:{ + if(sep && sep->arg[1][0] && sep->IsNumber(0) && sep->IsNumber(1)){ + int32 item_id = atoul(sep->arg[0]); + int8 quantity = atoi(sep->arg[1]); + client->BuyItem(item_id, quantity); + } + else + Command_SendMerchantWindow(client, sep); + break; + } + case COMMAND_MERCHANT_SELL:{ + if(sep && sep->arg[1][0] && sep->IsNumber(0) && sep->IsNumber(1)){ + // Item ID and Unique ID get combined in this command so we need to get the number as an int64 and break it into 2 int32's + int64 id = atoi64(sep->arg[0]); + // get the first int32, the item id + int32 item_id = (int32)id; + // get the second int32, the unique id + int32 unique_id = (int32)(id>>32); + + int8 quantity = atoi(sep->arg[1]); + LogWrite(MERCHANT__DEBUG, 0, "Merchant", "Selling Item: Item Id = %ul, unique id = %ul, Quantity = %i", item_id, unique_id, quantity); + client->SellItem(item_id, quantity, unique_id); + } + break; + } + case COMMAND_MENDER_REPAIR: { + if (sep && sep->arg[0] && sep->IsNumber(0)) + client->RepairItem(atoul(sep->arg[0])); + break; + } + case COMMAND_MENDER_REPAIR_ALL: { + client->RepairAllItems(); + break; + } + case COMMAND_CANCEL_MERCHANT:{ + client->SetMerchantTransaction(0); + break; + } + case COMMAND_START_MERCHANT:{ + break; + } + case COMMAND_INVULNERABLE:{ + sint8 val = -1; + if(sep && sep->arg[0] && sep->IsNumber(0)){ + val = atoi(sep->arg[0]); + +//devn00b: Fix for linux builds +#if defined(__GNUC__) + database.insertCharacterProperty(client, CHAR_PROPERTY_INVUL, (val == 1) ? (char*) "1" : (char*) "0"); +#else + database.insertCharacterProperty(client, CHAR_PROPERTY_INVUL, (val == 1) ? "1" : "0"); +#endif +// database.insertCharacterProperty(client, CHAR_PROPERTY_INVUL, (val == 1) ? "1" : "0"); + + client->GetPlayer()->SetInvulnerable(val==1); + if(client->GetPlayer()->GetInvulnerable()) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You are now invulnerable!"); + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You are no longer invulnerable!"); + } + if(val == -1) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /invulnerable [0/1]"); + break; + } + case COMMAND_REPAIR: { + Spawn* spawn = cmdTarget; + if (spawn && spawn->GetMerchantType() & MERCHANT_TYPE_REPAIR) { + client->SendHailCommand(spawn); + client->SetMerchantTransaction(spawn); + client->SendRepairList(); + } + break; + } + case COMMAND_LOTTO: { + Spawn* spawn = cmdTarget; + if (spawn && spawn->GetMerchantType() & MERCHANT_TYPE_LOTTO) { + client->SetMerchantTransaction(spawn); + client->ShowLottoWindow(); + } + break; + } + case COMMAND_ACCEPT_REWARD:{ + int32 unknown = 0; + int32 selectable_item_id = 0; + //Quest *quest = 0; + /* no idea what the first argument is for (faction maybe?) + if the reward has a selectable item reward, it's sent as the second argument + if neither of these are included in the reward, there is no sep + since the regular item rewards are manditary to receive, we don't have to know what they are until we accept the collection or quest + only 1 quest or collection reward may be displayed at a time */ + if (sep && sep->arg[0] && sep->arg[1] && sep->IsNumber(0) && sep->IsNumber(1)) { + unknown = atoul(sep->arg[0]); + selectable_item_id = atoul(sep->arg[1]); + } + + /* this logic here may seem unexpected, but the quest queue response for GetPendingQuestAcceptance is only populated if it is the current reward displayed to the client based on a quest + ** Otherwise it will likely be a DoF client scenario (pending item rewards, selectable item rewards) which is specifying an item id + ** lastly it will be a collection which also supplies an item id and you can only have one pending collection turn in at a time (they queue against Client::HandInCollections + */ + int32 item_id = 0; + if(sep && sep->arg[0][0] && sep->IsNumber(0)) + item_id = atoul(sep->arg[0]); + + bool collectedItems = client->GetPlayer()->AcceptQuestReward(item_id, selectable_item_id); + + if (collectedItems) { + EQ2Packet* outapp = client->GetPlayer()->SendInventoryUpdate(client->GetVersion()); + if (outapp) + client->QueuePacket(outapp); + } + else + LogWrite(COMMAND__ERROR, 0, "Command", "Error in COMMAND_ACCEPT_REWARD. No pending quest or collection reward was found (unknown=%u).", unknown); + break; + } + case COMMAND_BUY_FROM_BROKER:{ + if(sep && sep->arg[1][0] && sep->IsNumber(0) && sep->IsNumber(1)){ + int32 item_id = atoul(sep->arg[0]); + int16 quantity = atoul(sep->arg[1]); + Item* item = master_item_list.GetItem(item_id); + if(item && item->generic_info.max_charges > 1) + quantity = item->generic_info.max_charges; + client->AddItem(item_id, quantity, AddItemType::BUY_FROM_BROKER); + } + break; + } + case COMMAND_SEARCH_STORES_PAGE:{ + if(sep && sep->arg[0][0] && sep->IsNumber(0)){ + int32 page = atoul(sep->arg[0]); + client->SearchStore(page); + } + break; + } + case COMMAND_SEARCH_STORES:{ + if(sep && sep->arg[0][0]){ + const char* values = sep->argplus[0]; + if(values){ + LogWrite(ITEM__WARNING, 0, "Item", "SearchStores: %s", values); + + map str_values = TranslateBrokerRequest(values); + vector* items = master_item_list.GetItems(str_values, client); + if(items){ + client->SetItemSearch(items); + client->SearchStore(0); + } + } + } + break; + } + + case COMMAND_LUADEBUG:{ + if(sep && sep->arg[0][0] && strcmp(sep->arg[0], "start") == 0){ + client->SetLuaDebugClient(true); + if(lua_interface) + lua_interface->UpdateDebugClients(client); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You will now receive LUA error messages."); + } + else if(sep && sep->arg[0][0] && strcmp(sep->arg[0], "stop") == 0){ + client->SetLuaDebugClient(false); + if(lua_interface) + lua_interface->RemoveDebugClients(client); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You will no longer receive LUA error messages."); + } + else{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Syntax: /luadebug {start | stop}"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "This will allow you to receive lua debug messages normally seen only in the console."); + } + break; + } + case COMMAND_SPAWN_GROUP: + { + Spawn* target = cmdTarget; + + if(target && target->IsPlayer() == false) + { + if(sep && sep->arg[0][0] && strcmp(sep->arg[0], "create") == 0) + { + if(target->GetSpawnLocationPlacementID() > 0) + { + if(sep->arg[1] && !sep->IsNumber(1) && strlen(sep->arg[1]) > 0) + { + int32 id = database.CreateSpawnGroup(target, sep->arg[1]); + + if(id > 0) + client->Message(CHANNEL_COLOR_YELLOW, "Successfully created new spawn group with id: %u", id); + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Error creating group, check console for details."); + + target->SetSpawnGroupID(id); + target->AddSpawnToGroup(target); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Syntax: /spawn group create [name]"); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Could not use spawn to create a new group. Likely cause would be a newly spawned spawn that did not exist when /reload spawns was last used."); + } + else if(sep && sep->arg[0][0] && strcmp(sep->arg[0], "add") == 0) + { + if(sep->arg[1] && sep->IsNumber(1)) + { + int32 group_id = atoul(sep->arg[1]); + Spawn* leader = client->GetCurrentZone()->GetSpawnGroup(group_id); + + if(leader) + { + leader->AddSpawnToGroup(target); + target->SetSpawnGroupID(group_id); + + if(database.SpawnGroupAddSpawn(target, group_id)) + client->Message(CHANNEL_COLOR_YELLOW, "Successfully added '%s' to group id: %u", target->GetName(), group_id); + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Error adding spawn to group, check console for details."); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "No spawns found in the current zone for that spawn group, be sure to use '/spawn group create' first!"); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Syntax: /spawn group add [group id]"); + } + else if(target->GetSpawnGroupID() == 0) + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "That spawn is not in a group!"); + } + else if(sep && sep->arg[0][0] && strcmp(sep->arg[0], "remove") == 0) + { + int32 group_id = target->GetSpawnGroupID(); + target->RemoveSpawnFromGroup(); + + if(database.SpawnGroupRemoveSpawn(target, group_id)) + client->Message(CHANNEL_COLOR_YELLOW, "Successfully removed '%s' from group id: %u", target->GetName(), group_id); + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Error removing spawn from group, check console for details."); + } + else if(sep && sep->arg[0][0] && strcmp(sep->arg[0], "associate") == 0) + { + int32 group_id = target->GetSpawnGroupID(); + + if(sep->arg[1] && sep->IsNumber(1)) + { + if(database.SpawnGroupAddAssociation(group_id, atoul(sep->arg[1]))) + client->Message(CHANNEL_COLOR_YELLOW, "Successfully associated group id %u with group id %u", group_id, atoul(sep->arg[1])); + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Error associating groups, check console for details."); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Allows you to spawn only one associated group at once. Syntax: /spawn group associate [other group id]"); + } + else if(sep && sep->arg[0][0] && strcmp(sep->arg[0], "deassociate") == 0) + { + int32 group_id = target->GetSpawnGroupID(); + + if(sep->arg[1] && sep->IsNumber(1)) + { + if(database.SpawnGroupRemoveAssociation(group_id, atoul(sep->arg[1]))) + client->Message(CHANNEL_COLOR_YELLOW, "Successfully removed association between group id %u and group id %u", group_id, atoul(sep->arg[1])); + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Error removing group associations, check console for details."); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Removes previous group associations. Syntax: /spawn group deassociate [other group id]"); + } + else if(sep && sep->arg[0][0] && strcmp(sep->arg[0], "chance") == 0) + { + if(sep->arg[1] && sep->IsNumber(1)) + { + if(database.SetGroupSpawnChance(target->GetSpawnGroupID(), atof(sep->arg[1]))) + client->Message(CHANNEL_COLOR_YELLOW, "Successfully set group spawn chance to %f for group id: %u", atof(sep->arg[1]), target->GetSpawnGroupID()); + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Error setting group spawn chance, check console for details."); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Syntax: /spawn group chance [group's spawn chance percentage]"); + } + else + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "This command allows you to modify spawn groups."); + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Syntax: /spawn group [create/add/remove/chance/associate/deassociate]"); + } + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must select a valid spawn to use this command."); + break; + } + case COMMAND_SPAWN_COMBINE:{ + Spawn* spawn = cmdTarget; + float radius = 0; + bool failed = true; + if(spawn && !spawn->IsPlayer() && sep && sep->arg[0] && sep->arg[0][0]){ + failed = false; + if(spawn->GetSpawnGroupID() > 0){ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Can not combine this spawn as it is in a spawn group. Remove it from the group and try again."); + } + else if(sep->IsNumber(0)){ + radius = atof(sep->arg[0]); + client->CombineSpawns(radius, spawn); + } + else{ + if(strncasecmp(sep->arg[0], "add", 3) == 0){ + client->AddCombineSpawn(spawn); + } + else if(strncasecmp(sep->arg[0], "remove", 6) == 0){ + client->RemoveCombineSpawn(spawn); + } + else if(strncasecmp(sep->arg[0], "save", 4) == 0){ + const char* name = 0; + if(sep->arg[1] && sep->arg[1][0]) + name = sep->argplus[1]; + client->SaveCombineSpawns(name); + } + else + failed = true; + } + } + if(failed){ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "This command combines several spawns into a single spawn group."); + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Syntax: /spawn combine [radius/add/remove/save]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Ex: /spawn combine 1, /spawn combine save (spawn group name)"); + } + break; + } + case COMMAND_SPAWN_CREATE:{ + Spawn* spawn = 0; + if(sep && sep->arg[4][0] && strncasecmp(sep->arg[0], "object", 6) == 0 && sep->IsNumber(1) && sep->IsNumber(2) && sep->IsNumber(3)){ + spawn = new Object(); + memset(&spawn->appearance, 0, sizeof(spawn->appearance)); + } + else if (sep && sep->arg[4][0] && strncasecmp(sep->arg[0], "groundspawn", 11) == 0 && sep->IsNumber(1) && sep->IsNumber(2) && sep->IsNumber(3)) { + spawn = new GroundSpawn(); + memset(&spawn->appearance, 0, sizeof(spawn->appearance)); + } + else if (sep && sep->arg[4][0] && strncasecmp(sep->arg[0], "sign", 4) == 0 && sep->IsNumber(1) && sep->IsNumber(2) && sep->IsNumber(3)) { + spawn = new Sign(); + memset(&spawn->appearance, 0, sizeof(spawn->appearance)); + } + else if(sep && sep->arg[4][0] && strncasecmp(sep->arg[0], "npc", 3) == 0 && sep->IsNumber(1) && sep->IsNumber(2) && sep->IsNumber(3)){ + spawn = new NPC(); + memset(&spawn->appearance, 0, sizeof(spawn->appearance)); + spawn->appearance.pos.collision_radius = 32; + spawn->secondary_command_list_id = 0; + spawn->primary_command_list_id = 0; + spawn->appearance.display_name = 1; + spawn->appearance.show_level = 1; + spawn->appearance.attackable = 1; + } + else{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Syntax: /spawn create [spawn type] [race type] [class type] [level] [name] (difficulty) (size)"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "All parameters are required except difficulty and size. Valid types are Object, NPC, Sign, or GroundSpawn"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Ex: /spawn create npc 203 1 50 'Lady Vox' 1 32"); + break; + } + spawn->SetID(Spawn::NextID()); + spawn->SetX(client->GetPlayer()->GetX()); + spawn->SetY(client->GetPlayer()->GetY()); + spawn->SetZ(client->GetPlayer()->GetZ()); + spawn->SetHeading(client->GetPlayer()->GetHeading()); + spawn->SetSpawnOrigX(spawn->GetX()); + spawn->SetSpawnOrigY(spawn->GetY()); + spawn->SetSpawnOrigZ(spawn->GetZ()); + spawn->SetSpawnOrigHeading(spawn->GetHeading()); + + + if(spawn->IsSign()) + { + ((Sign*)spawn)->SetSignType(SIGN_TYPE_GENERIC); + ((Sign*)spawn)->SetSignDistance(20.0f); + ((Sign*)spawn)->SetIncludeLocation(1); + ((Sign*)spawn)->SetIncludeHeading(1); + ((Sign*)spawn)->SetInitialState(1); + ((Sign*)spawn)->SetSignTitle(sep->arg[4]); + ((Sign*)spawn)->SetActivityStatus(64); + spawn->appearance.race = 0; + spawn->SetLevel(0); + spawn->SetHP(0); + spawn->SetTotalHP(0); + spawn->SetPower(0); + spawn->SetTotalPower(0); + spawn->SetDifficulty(0); + spawn->SetTargetable(0); + spawn->SetSogaModelType(0); + spawn->SetCollisionRadius(19); + ((Sign*)spawn)->SetWidgetX(client->GetPlayer()->GetX()); + ((Sign*)spawn)->SetWidgetY(client->GetPlayer()->GetY()); + ((Sign*)spawn)->SetWidgetZ(client->GetPlayer()->GetZ()); + spawn->SetLocation(client->GetPlayer()->GetLocation()); + spawn->appearance.model_type = atoul(sep->arg[1]); + } + else + { + spawn->appearance.targetable = 1; + spawn->appearance.race = 255; + spawn->SetLocation(client->GetPlayer()->GetLocation()); + spawn->SetModelType(atoi(sep->arg[1])); + spawn->SetAdventureClass(atoi(sep->arg[2])); + spawn->SetLevel(atoi(sep->arg[3])); + spawn->SetName(sep->arg[4]); + if(sep->arg[5][0] && sep->IsNumber(5)) + spawn->SetDifficulty(atoi(sep->arg[5])); + if(sep->arg[6][0] && sep->IsNumber(6)) + spawn->size = atoi(sep->arg[6]); + if(spawn->GetTotalHP() == 0){ + spawn->SetTotalHP(25*spawn->GetLevel() + 1); + spawn->SetHP(25*spawn->GetLevel() + 1); + } + if(spawn->GetTotalPower() == 0){ + spawn->SetTotalPower(25*spawn->GetLevel() + 1); + spawn->SetPower(25*spawn->GetLevel() + 1); + } + } + + client->GetCurrentZone()->AddSpawn(spawn); + break; + } + case COMMAND_SPAWN_EQUIPMENT:{ + Spawn* spawn = cmdTarget; + if(spawn && sep && sep->arg[1][0] && sep->IsNumber(0) && sep->IsNumber(1) && spawn->IsEntity()){ + int8 slot = atoi(sep->arg[0]); + int16 type = atoi(sep->arg[1]); + int8 red = 0; + int8 green = 0; + int8 blue = 0; + int8 h_red = 0; + int8 h_green = 0; + int8 h_blue = 0; + if(sep->arg[2]) + red = atoi(sep->arg[2]); + if(sep->arg[3]) + green = atoi(sep->arg[3]); + if(sep->arg[4]) + blue = atoi(sep->arg[4]); + if(sep->arg[5]) + h_red = atoi(sep->arg[5]); + if(sep->arg[6]) + h_green = atoi(sep->arg[6]); + if(sep->arg[7]) + h_blue = atoi(sep->arg[7]); + ((Entity*)spawn)->SetEquipment(slot, type, red, green, blue, h_red, h_green, h_blue); + database.SaveNPCAppearanceEquipment(spawn->GetDatabaseID() , slot, type, red, green, blue, h_red, h_green, h_blue); + } + else{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Syntax: /spawn equipment [slot] [appearance id] (red) (green) (blue) (highlight red) (hgreen) (hblue)"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "This will set the given spawn's equipment. "); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Slot is 0-24, Appearance ID from appearances table, Colors are 0-255"); + } + break; + } + case COMMAND_SPAWN_DETAILS: { + Spawn* spawn = cmdTarget; + if (sep && sep->arg[0][0]) { + if (cmdTarget) + { + if (ToLower(string(sep->arg[0])) == "los") + { + bool hasLOS = client->GetPlayer()->CheckLoS(cmdTarget); + if (hasLOS) + client->Message(CHANNEL_COLOR_YELLOW, "You have line of sight with %s", spawn->GetName()); + else + client->Message(CHANNEL_COLOR_RED, "You DO NOT have line of sight with %s", spawn->GetName()); + + break; + } + else if (ToLower(string(sep->arg[0])) == "bestz") + { + glm::vec3 targPos(cmdTarget->GetX(), cmdTarget->GetZ(), cmdTarget->GetY()); + + float bestZ = client->GetPlayer()->FindDestGroundZ(targPos, cmdTarget->GetYOffset()); + client->Message(CHANNEL_COLOR_YELLOW, "Best Z for %s is %f", spawn->GetName(), bestZ); + break; + } + else if (ToLower(string(sep->arg[0])) == "inwater") + { + if (cmdTarget->GetRegionMap() == nullptr) + client->SimpleMessage(CHANNEL_COLOR_RED, "No water map for zone."); + else + { + bool inWater = cmdTarget->InWater(); + client->Message(CHANNEL_COLOR_YELLOW, "%s is %s.", cmdTarget->GetName(), inWater ? "in water" : "out of water"); + } + break; + } + else if (ToLower(string(sep->arg[0])) == "inlava") + { + if (cmdTarget->GetRegionMap() == nullptr) + client->SimpleMessage(CHANNEL_COLOR_RED, "No region map for zone."); + else + { + bool inLava = cmdTarget->InLava(); + client->Message(CHANNEL_COLOR_YELLOW, "%s is %s.", cmdTarget->GetName(), inLava ? "in lava" : "out of lava"); + } + break; + } + else if (ToLower(string(sep->arg[0])) == "regions") + { + glm::vec3 targPos(cmdTarget->GetX(), cmdTarget->GetY(), cmdTarget->GetZ()); + + if (cmdTarget->GetRegionMap() == nullptr) + client->SimpleMessage(CHANNEL_COLOR_RED, "No region map for zone."); + else + { + cmdTarget->GetRegionMap()->IdentifyRegionsInGrid(client, targPos); + } + break; + } + else if (ToLower(string(sep->arg[0])) == "behind") + { + bool isBehind = client->GetPlayer()->BehindTarget(cmdTarget); + client->Message(CHANNEL_COLOR_YELLOW, "%s %s.", isBehind ? "YOU are behind" : "YOU are NOT behind", cmdTarget->GetName()); + break; + } + else if (ToLower(string(sep->arg[0])) == "infront") + { + bool isBehind = client->GetPlayer()->InFrontSpawn(cmdTarget, client->GetPlayer()->GetX(), client->GetPlayer()->GetZ()); + client->Message(CHANNEL_COLOR_YELLOW, "%s %s.", isBehind ? "YOU are infront of" : "YOU are NOT infront of", cmdTarget->GetName()); + break; + } + else if (ToLower(string(sep->arg[0])) == "flank") + { + bool isFlanking = client->GetPlayer()->FlankingTarget(cmdTarget); + client->Message(CHANNEL_COLOR_YELLOW, "%s is %s.", isFlanking ? "YOU are flanking" : "YOU are NOT flanking", cmdTarget->GetName()); + break; + } + else if (ToLower(string(sep->arg[0])) == "aggro") + { + if(cmdTarget->IsNPC() && ((NPC*)cmdTarget)->Brain()) { + ((NPC*)cmdTarget)->Brain()->SendEncounterList(client); + ((NPC*)cmdTarget)->Brain()->SendHateList(client); + } + else { + client->SimpleMessage(CHANNEL_COLOR_RED, "/spawn details aggro is for NPCs only!"); + } + break; + } + else if (ToLower(string(sep->arg[0])) == "angle") + { + float spawnAngle = client->GetPlayer()->GetFaceTarget(cmdTarget->GetX(), cmdTarget->GetZ()); + + client->Message(CHANNEL_COLOR_YELLOW, "Angle %f between player %s and target %s", spawnAngle, client->GetPlayer()->GetTarget() ? client->GetPlayer()->GetTarget()->GetName() : client->GetPlayer()->GetName(), client->GetPlayer()->GetName()); + break; + } + } + if (sep->IsNumber(0)) + { + float radius = atof(sep->arg[0]); + if (!client->GetCurrentZone()->SendRadiusSpawnInfo(client, radius)) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "No results in the radius were found."); + break; + } + } + if (spawn) { + const char* type = "NPC"; + if (spawn->IsObject()) + type = "Object"; + else if (spawn->IsSign()) + type = "Sign"; + else if (spawn->IsWidget()) + type = "Widget"; + else if (spawn->IsGroundSpawn()) + type = "GroundSpawn"; + + int16 race_type = race_types_list.GetRaceType(spawn->GetModelType()); + int16 race_base_type = race_types_list.GetRaceBaseType(spawn->GetModelType()); + char* category = race_types_list.GetRaceTypeCategory(spawn->GetModelType()); + char* subcategory = race_types_list.GetRaceTypeSubCategory(spawn->GetModelType()); + char* modelname = race_types_list.GetRaceTypeModelName(spawn->GetModelType()); + + client->Message(CHANNEL_COLOR_YELLOW, "Name: %s, %s ID: %u", spawn->GetName(), type, spawn->GetDatabaseID()); + client->Message(CHANNEL_COLOR_YELLOW, "Last Name: %s, Sub-Title: %s, Prefix: %s, Suffix: %s", spawn->GetLastName(), spawn->GetSubTitle(), spawn->GetPrefixTitle(), spawn->GetSuffixTitle()); + client->Message(CHANNEL_COLOR_YELLOW, "Spawn Location ID: %u, Spawn Group ID: %u", spawn->GetSpawnLocationID(), spawn->GetSpawnGroupID()); + client->Message(CHANNEL_COLOR_YELLOW, "Faction ID: %u, Merchant ID: %u, Transporter ID: %u", spawn->GetFactionID(), spawn->GetMerchantID(), spawn->GetTransporterID()); + client->Message(CHANNEL_COLOR_YELLOW, "Grid ID: %u", spawn->GetLocation()); + client->Message(CHANNEL_COLOR_YELLOW, "Race: %i, Class: %i, Gender: %i", spawn->GetRace(), spawn->GetAdventureClass(), spawn->GetGender()); + client->Message(CHANNEL_COLOR_YELLOW, "Level: %i, HP: %u / %u, Power: %u / %u", spawn->GetLevel(), spawn->GetHP(), spawn->GetTotalHP(), spawn->GetPower(), spawn->GetTotalPower()); + client->Message(CHANNEL_COLOR_YELLOW, "Respawn Time: %u (sec), X: %f, Y: %f, Z: %f Heading: %f", spawn->GetRespawnTime(), spawn->GetX(), spawn->GetY(), spawn->GetZ(), spawn->GetHeading()); + client->Message(CHANNEL_COLOR_YELLOW, "Collision Radius: %i, Size: %i, Difficulty: %i, Heroic: %i", spawn->GetCollisionRadius(), spawn->GetSize(), spawn->GetDifficulty(), spawn->GetHeroic()); + client->Message(CHANNEL_COLOR_YELLOW, "Targetable: %i, Show Name: %i, Attackable: %i, Show Level: %i", spawn->GetTargetable(), spawn->GetShowName(), spawn->GetAttackable(), spawn->GetShowLevel()); + client->Message(CHANNEL_COLOR_YELLOW, "Show Command Icon: %i, Display Hand Icon: %i", spawn->GetShowCommandIcon(), spawn->GetShowHandIcon()); + if (spawn->IsEntity()) { + client->Message(CHANNEL_COLOR_YELLOW, "Facial Hair Type: %i, Hair Type: %i, Chest Type: %i, Legs Type: %i", ((Entity*)spawn)->GetFacialHairType(), ((Entity*)spawn)->GetHairType(), ((Entity*)spawn)->GetChestType(), ((Entity*)spawn)->GetLegsType()); + client->Message(CHANNEL_COLOR_YELLOW, "Soga Facial Hair Type: %i, Soga Hair Type: %i, Wing Type: %i", ((Entity*)spawn)->GetSogaFacialHairType(), ((Entity*)spawn)->GetSogaHairType(), ((Entity*)spawn)->GetWingType()); + } + + client->Message(CHANNEL_COLOR_YELLOW, "Model Type: %i, Soga Race Type: %i, Race Type: %u, Race Base Type: %u, Race Category: %s (subcategory: %s), Model Name: %s.", spawn->GetModelType(), spawn->GetSogaModelType(), race_type, race_base_type, category, subcategory, modelname); + client->Message(CHANNEL_COLOR_YELLOW, "Primary Command Type: %u, Secondary Command Type: %u", spawn->GetPrimaryCommandListID(), spawn->GetSecondaryCommandListID()); + client->Message(CHANNEL_COLOR_YELLOW, "Visual State: %i, Action State: %i, Mood State: %i, Initial State: %i, Activity Status: %i", spawn->GetVisualState(), spawn->GetActionState(), spawn->GetMoodState(), spawn->GetInitialState(), spawn->GetActivityStatus()); + client->Message(CHANNEL_COLOR_YELLOW, "Emote State: %i, Pitch: %f, Roll: %f, Hide Hood: %i", spawn->GetEmoteState(), spawn->GetPitch(), spawn->GetRoll(), spawn->appearance.hide_hood); + if (spawn->IsNPC()) + client->Message(CHANNEL_COLOR_YELLOW, "Randomize: %u", ((NPC*)spawn)->GetRandomize()); + if (spawn->IsNPC()){ + client->Message(CHANNEL_COLOR_YELLOW, "Heat Resist/Base: %u/%u",((NPC*)spawn)->GetHeatResistance(), ((NPC*)spawn)->GetHeatResistanceBase()); + client->Message(CHANNEL_COLOR_YELLOW, "Cold Resist/Base: %u/%u",((NPC*)spawn)->GetColdResistance(), ((NPC*)spawn)->GetColdResistanceBase()); + client->Message(CHANNEL_COLOR_YELLOW, "Magic Resist/Base: %u/%u",((NPC*)spawn)->GetMagicResistance(), ((NPC*)spawn)->GetMagicResistanceBase()); + client->Message(CHANNEL_COLOR_YELLOW, "Mental Resist/Base: %u/%u",((NPC*)spawn)->GetMentalResistance(), ((NPC*)spawn)->GetMentalResistanceBase()); + client->Message(CHANNEL_COLOR_YELLOW, "Divine Resist/Base: %u/%u",((NPC*)spawn)->GetDivineResistance(), ((NPC*)spawn)->GetDivineResistanceBase()); + client->Message(CHANNEL_COLOR_YELLOW, "Disease Resist/Base: %u/%u",((NPC*)spawn)->GetDiseaseResistance(), ((NPC*)spawn)->GetDiseaseResistanceBase()); + client->Message(CHANNEL_COLOR_YELLOW, "Poison Resist/Base: %u/%u",((NPC*)spawn)->GetPoisonResistance(), ((NPC*)spawn)->GetPoisonResistanceBase()); + } + + string details; + details += "\\#0000FFName: " + string(spawn->GetName()) + "\n"; + details += "Type: " + string(type) + "\n"; + details += "ID: " + to_string(spawn->GetDatabaseID()) + "\n"; + details += "Last Name: " + string(spawn->GetLastName()) + "\n"; + details += "Sub-Title: " + string(spawn->GetSubTitle()) + "\n"; + details += "Prefix: " + string(spawn->GetPrefixTitle()) + "\n"; + details += "Suffix: " + string(spawn->GetSuffixTitle()) + "\n"; + details += "Race: " + to_string(spawn->GetRace()) + "\n"; + details += "Class: " + to_string(spawn->GetAdventureClass()) + "\n"; + details += "Gender: " + to_string(spawn->GetGender()) + "\n"; + details += "Level: " + to_string(spawn->GetLevel()) + "\n"; + details += "HP: " + to_string(spawn->GetHP()) + " / " + to_string(spawn->GetTotalHP()) + "(" + to_string(spawn->GetIntHPRatio()) + "%)\n"; + details += "Power: " + to_string(spawn->GetPower()) + + " / " + to_string(spawn->GetTotalPower()) + "\n"; + details += "Difficulty: " + to_string(spawn->GetDifficulty()) + "\n"; + details += "Heroic: " + to_string(spawn->GetHeroic()) + "\n"; + details += "Group ID: " + to_string(spawn->GetSpawnGroupID()) + "\n"; + details += "Faction ID: " + to_string(spawn->GetFactionID()) + "\n"; + details += "Merchant ID: " + to_string(spawn->GetMerchantID()) + "\n"; + details += "Transport ID: " + to_string(spawn->GetTransporterID()) + "\n"; + details += "Location ID: " + to_string(spawn->GetSpawnLocationID()) + "\n"; + char x[16]; + sprintf(x, "%.2f", spawn->GetX()); + char y[16]; + sprintf(y, "%.2f", spawn->GetY()); + char z[16]; + sprintf(z, "%.2f", spawn->GetZ()); + details += "Location: " + string(x) + ", " + string(y) + ", " + string(z) + "\n"; + + string details2; + details2 += "Heading: " + to_string(spawn->GetHeading()) + "\n"; + details2 += "Grid ID: " + to_string(spawn->GetLocation()) + "\n"; + details2 += "Size: " + to_string(spawn->GetSize()) + "\n"; + details2 += "Collision Radius: " + to_string(spawn->GetCollisionRadius()) + "\n"; + details2 += "Respawn Time: " + to_string(spawn->GetRespawnTime()) + "\n"; + details2 += "Targetable: " + to_string(spawn->GetTargetable()) + "\n"; + details2 += "Show Name: " + to_string(spawn->GetShowName()) + "\n"; + details2 += "Attackable: " + to_string(spawn->GetAttackable()) + "\n"; + details2 += "Show Level: " + to_string(spawn->GetShowLevel()) + "\n"; + details2 += "Show Command Icon: " + to_string(spawn->GetShowCommandIcon()) + "\n"; + details2 += "Display Hand Icon: " + to_string(spawn->GetShowHandIcon()) + "\n"; + details2 += "Model Type: " + to_string(spawn->GetModelType()) + "\n"; + details2 += "Soga Race Type: " + to_string(spawn->GetSogaModelType()) + "\n"; + details2 += "Race Type: " + to_string(race_type) + "\n"; + details2 += "Race Base Type: " + to_string(race_base_type) + "\n"; + details2 += "Race Category (Sub Category): " + std::string(category) + " (" + std::string(subcategory) + ")\n"; + details2 += "Model Name: " + std::string(modelname) + "\n"; + details2 += "Primary Command ID: " + to_string(spawn->GetPrimaryCommandListID()) + "\n"; + details2 += "Secondary Cmd ID: " + to_string(spawn->GetSecondaryCommandListID()) + "\n"; + details2 += "Visual State: " + to_string(spawn->GetVisualState()) + "\n"; + details2 += "Action State: " + to_string(spawn->GetActionState()) + "\n"; + details2 += "Mood State: " + to_string(spawn->GetMoodState()) + "\n"; + details2 += "Initial State: " + to_string(spawn->GetInitialState()) + "\n"; + details2 += "Activity Status: " + to_string(spawn->GetActivityStatus()) + "\n"; + details2 += "Emote State: " + to_string(spawn->GetEmoteState()) + "\n"; + + string details3; + details3 += "Pitch: " + to_string(spawn->GetPitch()) + "\n"; + details3 += "Roll: " + to_string(spawn->GetRoll()) + "\n"; + details3 += "Hide Hood: " + to_string(spawn->appearance.hide_hood) + "\n"; + details3 += "Speed: " + to_string(spawn->GetSpeed()) + "\n"; + details3 += "BaseSpeed: " + to_string(spawn->GetBaseSpeed()) + "\n"; + + if(spawn->IsSign()) { + details3 += "Sign Language: " + to_string(((Sign*)spawn)->GetLanguage()) + "\n"; + } + + if(spawn->IsEntity()) + { + Entity* ent = (Entity*)spawn; + details3 += "STR / STRBase: " + to_string(ent->GetInfoStruct()->get_str()) + " / " + to_string(ent->GetInfoStruct()->get_str_base()) + "\n"; + details3 += "AGI / AGIBase: " + to_string(ent->GetInfoStruct()->get_agi()) + " / " + to_string(ent->GetInfoStruct()->get_agi_base()) + "\n"; + details3 += "STA / STABase: " + to_string(ent->GetInfoStruct()->get_sta()) + " / " + to_string(ent->GetInfoStruct()->get_sta_base()) + "\n"; + details3 += "INT / INTBase: " + to_string(ent->GetInfoStruct()->get_intel()) + " / " + to_string(ent->GetInfoStruct()->get_intel_base()) + "\n"; + details3 += "WIS / WISBase: " + to_string(ent->GetInfoStruct()->get_wis()) + " / " + to_string(ent->GetInfoStruct()->get_wis_base()) + "\n"; + } + + string details4; + if (spawn->IsEntity()) { + details4 += "Facial Hair Type: " + to_string(((Entity*)spawn)->GetFacialHairType()) + "\n"; + details4 += "Hair Type: " + to_string(((Entity*)spawn)->GetHairType()) + "\n"; + details4 += "Chest Type: " + to_string(((Entity*)spawn)->GetChestType()) + "\n"; + details4 += "Legs Type: " + to_string(((Entity*)spawn)->GetLegsType()) + "\n"; + details4 += "Soga Facial Hair Type: " + to_string(((Entity*)spawn)->GetSogaFacialHairType()) + "\n"; + details4 += "Soga Hair Type: " + to_string(((Entity*)spawn)->GetSogaHairType()) + "\n"; + details4 += "Wing Type: " + to_string(((Entity*)spawn)->GetWingType()) + "\n"; + if (spawn->IsNPC()) { + details4 += "\nRandomize: " + to_string(((NPC*)spawn)->GetRandomize()) + "\n"; + } + } + const char* spawnScriptMsg = (spawn->GetSpawnScript() && strlen(spawn->GetSpawnScript())>0) ? spawn->GetSpawnScript() : "Not Set"; + details4 += "\nSpawnScript: " + std::string(spawnScriptMsg) + "\n"; + + string title = string(spawn->GetName()) + "(" + to_string(spawn->GetDatabaseID()) + ")"; + client->SendShowBook(client->GetPlayer(), title, 0, 4, details, details2, details3, details4); + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Syntax: /spawn details (radius)"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "This will display information about the currently selected spawn, or in the case of specifying a numerical radius will show minor details about spawns in the players radius."); + } + break; + } + case COMMAND_SPAWN_TARGET:{ + if(sep && sep->arg[0][0] && sep->IsNumber(0)){ + int32 spawn_id = atoul(sep->arg[0]); + int16 response = client->GetCurrentZone()->SetSpawnTargetable(spawn_id); + client->Message(CHANNEL_COLOR_YELLOW, "%i spawn(s) in the current zone were reset to targetable.", response); + } + else if(sep && sep->arg[0][0] && sep->arg[1][0] && sep->IsNumber(1) && ToLower(string(sep->arg[0])) == "radius"){ + float distance = atof(sep->arg[1]); + int16 response = client->GetCurrentZone()->SetSpawnTargetable(client->GetPlayer(), distance); + client->Message(CHANNEL_COLOR_YELLOW, "%i spawn(s) in the current zone were reset to targetable.", response); + } + else{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Syntax: /spawn target [spawn id]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "This will set the given spawn as targetable. Used to change a spawn if it was set to untargetable."); + } + break; + } + case COMMAND_SPAWN_SET: + { + Spawn* spawn = cmdTarget; + sint16 set_type = -1; + string type_str; + + if (spawn) + { + // check if parameters are (location or list or not player and not a 2nd param), and that there is at least 1 value + if(sep && ((sep->arg[0][0] && ToLower(string(sep->arg[0])) == "location") || (sep->arg[0][0] && ToLower(string(sep->arg[0])) == "list") || (spawn && spawn->IsPlayer() == false && sep->arg[1][0])) && spawn_set_values.count(ToLower(string(sep->arg[0]))) == 1) + { + // set the type, which will be 0 if location or list or invalid + set_type = spawn_set_values[ToLower(string(sep->arg[0]))]; + } + + if(set_type > 0) + { + // check if spawn set is NOT a char update, or not location, or isn't a number + if(!(set_type >= SPAWN_SET_VALUE_PREFIX) && !(set_type <= SPAWN_SET_VALUE_MERCHANT_MAX_LEVEL) && set_type != SPAWN_SET_VALUE_NAME && ((set_type < SPAWN_SET_VALUE_SPAWN_SCRIPT) || (set_type > SPAWN_SET_VALUE_SUB_TITLE)) && set_type != SPAWN_SET_VALUE_LOCATION && sep->IsNumber(1) == false) + { + client->SimpleMessage(CHANNEL_COLOR_RED, "Invalid value for set command."); + } + else + { + string name = string(spawn->GetName()); + bool customSetSpawn = false; + if(set_type >= SPAWN_SET_CHEEK_TYPE && set_type <= SPAWN_SET_SOGA_NOSE_TYPE) + { + int8 index = sep->IsNumber(2) ? atoul(sep->arg[2]) : 0; + + // override the standard setspawncommand, we pass arguments different! + if(SetSpawnCommand(client, spawn, set_type, sep->arg[1], true, false, nullptr, index)) + customSetSpawn = true; + } + + if(customSetSpawn || SetSpawnCommand(client, spawn, set_type, sep->argplus[1])) + { + if (set_type == SPAWN_SET_VALUE_EXPANSION_FLAG || set_type == SPAWN_SET_VALUE_HOLIDAY_FLAG) + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "A /reload spawns is required to properly update the spawns with the xpack/holiday flag."); + } + else if (set_type == SPAWN_SET_VALUE_NAME) + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "New name will not be effective until zone reload."); + } + else if (set_type == SPAWN_SET_SKIN_COLOR || (set_type >= SPAWN_SET_HAIR_COLOR1 && set_type <= SPAWN_SET_SOGA_EYE_COLOR)) + { + client->Message(CHANNEL_COLOR_YELLOW, "Successfully set color field to R G B: %s.", sep->argplus[1]); + } + else if(set_type == SPAWN_SET_VALUE_LOCATION) + { + spawn->SetLocation(client->GetPlayer()->GetLocation()); + client->Message(CHANNEL_COLOR_YELLOW, "Successfully set '%s' to '%u' for spawn '%s' (DBID: %u)", sep->arg[0], client->GetPlayer()->GetLocation(), name.c_str(), spawn->GetDatabaseID()); + } + else + { + client->Message(CHANNEL_COLOR_YELLOW, "Successfully set '%s' to '%s' for spawn '%s' (DBID: %u)", sep->arg[0], sep->arg[1], name.c_str(), spawn->GetDatabaseID()); + } + + switch (set_type) + { + case SPAWN_SET_VALUE_EXPANSION_FLAG: + case SPAWN_SET_VALUE_HOLIDAY_FLAG: + case SPAWN_SET_VALUE_FACTION: + case SPAWN_SET_AAXP_REWARDS: + case SPAWN_SET_CHEEK_TYPE: + case SPAWN_SET_CHIN_TYPE: + case SPAWN_SET_EAR_TYPE: + case SPAWN_SET_EYE_BROW_TYPE: + case SPAWN_SET_EYE_TYPE: + case SPAWN_SET_LIP_TYPE: + case SPAWN_SET_NOSE_TYPE: + case SPAWN_SET_BODY_SIZE: + case SPAWN_SET_BODY_AGE: + case SPAWN_SET_SOGA_CHEEK_TYPE: + case SPAWN_SET_SOGA_CHIN_TYPE: + case SPAWN_SET_SOGA_EAR_TYPE: + case SPAWN_SET_SOGA_EYE_BROW_TYPE: + case SPAWN_SET_SOGA_EYE_TYPE: + case SPAWN_SET_SOGA_LIP_TYPE: + case SPAWN_SET_SOGA_NOSE_TYPE: + case SPAWN_SET_SOGA_BODY_SIZE: + case SPAWN_SET_SOGA_BODY_AGE: + case SPAWN_SET_ATTACK_TYPE: + case SPAWN_SET_RACE_TYPE: + case SPAWN_SET_LOOT_TIER: + case SPAWN_SET_LOOT_DROP_TYPE: + case SPAWN_SET_SCARED_STRONG_PLAYERS: + { + // not applicable already ran db command + break; + } + default: + { + client->GetCurrentZone()->ApplySetSpawnCommand(client, spawn, set_type, sep->argplus[1]); + break; + } + } + + if((set_type >= SPAWN_SET_VALUE_RESPAWN && set_type <=SPAWN_SET_VALUE_LOCATION) || (set_type >= SPAWN_SET_VALUE_EXPIRE && set_type <=SPAWN_SET_VALUE_Z_OFFSET) || (set_type == SPAWN_SET_VALUE_PITCH || set_type == SPAWN_SET_VALUE_ROLL)) + { + if(spawn->GetSpawnLocationID() > 0 && database.UpdateSpawnLocationSpawns(spawn)) + { + client->Message(CHANNEL_COLOR_YELLOW, "Successfully saved spawn information for spawn '%s' (DBID: %u)", name.c_str(), spawn->GetDatabaseID()); + } + else if(spawn->GetSpawnLocationID() > 0) + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Error saving spawn information, see console window for details."); + } + } + else + { + if(spawn->GetDatabaseID() > 0 && database.SaveSpawnInfo(spawn)) + { + client->Message(CHANNEL_COLOR_YELLOW, "Successfully saved spawn for spawn '%s' (DBID: %u)", name.c_str(), spawn->GetDatabaseID()); + } + else if(spawn->GetDatabaseID() > 0) + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Error saving spawn, see console window for details."); + } + } + } + } + } + else if(set_type == 0) + { + // /spawn set list - lists all possible attributes that can be changed with this command, 10 per line. + map::iterator itr; + int i=0; + string list; + for(itr = spawn_set_values.begin(); itr != spawn_set_values.end(); itr++, i++) + { + if(i==10) + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, list.c_str()); + i = 0; + } + + if(i>0) + { + list.append(", ").append(itr->first); + } + else + { + list = itr->first; + } + } + + if(list.length() > 0) + { + // if 1 or more, display in client. + client->SimpleMessage(CHANNEL_COLOR_YELLOW, list.c_str()); + } + } + else + { + // syntax fail + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Syntax: /spawn set [type] [value]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "This command is used to change various settings for the targeted NPC or Object."); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "For a list of changeable settings use /spawn set list"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Note: /spawn set location does not require a value. The client's current location is used."); + } + } + break; + } + case COMMAND_SPAWN_REMOVE:{ + Spawn* spawn = cmdTarget; + if(spawn && !spawn->IsPlayer()){ + if(spawn->GetSpawnLocationID() > 0){ + string name = string(spawn->GetName()); + int32 dbid = spawn->GetDatabaseID(); + if(database.RemoveSpawnFromSpawnLocation(spawn)){ + client->GetCurrentZone()->RemoveSpawn(spawn, true, false, true, true, true); + client->Message(CHANNEL_COLOR_YELLOW, "Successfully removed spawn from zone for spawn '%s' (DBID: %u)", name.c_str(), dbid); + } + else + client->SimpleMessage(CHANNEL_COLOR_RED, "Error removing spawn, see console window for details."); + } + else + client->SimpleMessage(CHANNEL_COLOR_RED, "This spawn does not have a spawn group associated with it"); + } + else{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Syntax: /spawn remove"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "This command is used for removing the targeted NPC or Object from the zone."); + } + break; + } + case COMMAND_SPAWN_LIST:{ + if(sep && sep->arg[0][0]){ + vector* results = database.GetSpawnNameList(sep->argplus[0]); + vector::iterator itr; + if(results){ + for(itr=results->begin();itr!=results->end();itr++){ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, (*itr).c_str()); + } + safe_delete(results); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "No matches found. "); + } + else{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Syntax: /spawn list [name]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Name can be a partial match."); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Ex: /spawn list Qeynos Guard"); + } + break; + } + case COMMAND_SPAWN_ADD:{ + Spawn* spawn = cmdTarget; + if(spawn && sep && ((sep->arg[1][0] && sep->IsNumber(0)) || (sep->arg[0][0] && strncasecmp(sep->arg[0], "new", 3) == 0))){ + if(spawn->GetSpawnLocationID() > 0){ + client->Message(CHANNEL_COLOR_RED, "This spawn already has a spawn group id of %u, use /spawn remove to reassign it", spawn->GetSpawnLocationID()); + break; + } + if(spawn->GetDatabaseID() == 0){ + if(database.SaveSpawnInfo(spawn)) { + char spawn_type[32]; + memset(spawn_type, 0, sizeof(spawn_type)); + if (spawn->IsNPC()) + strncpy(spawn_type, "NPC", sizeof(spawn_type) - 1); + else if (spawn->IsObject()) + strncpy(spawn_type, "Object", sizeof(spawn_type) - 1); + else if (spawn->IsSign()) + strncpy(spawn_type, "Sign", sizeof(spawn_type) - 1); + else if (spawn->IsGroundSpawn()) + strncpy(spawn_type, "GroundSpawn", sizeof(spawn_type) - 1); + else + strncpy(spawn_type, "Unknown", sizeof(spawn_type) - 1); + client->Message(CHANNEL_COLOR_YELLOW, "Successfully saved spawn information with a %s id of %u", spawn_type, spawn->GetDatabaseID()); + } + else + client->SimpleMessage(CHANNEL_COLOR_RED, "Error saving spawn information, see console window for details."); + } + int32 spawn_group_id = 0; + if(strncasecmp(sep->arg[0], "new", 3) == 0) + spawn_group_id = database.GetNextSpawnLocation(); + else + spawn_group_id = atol(sep->arg[0]); + int8 percent = 100; + if(sep->arg[2] && sep->IsNumber(2)) + percent = atoi(sep->arg[2]); + spawn->SetSpawnLocationID(spawn_group_id); + float x_offset = database.GetSpawnLocationPlacementOffsetX(spawn->GetSpawnLocationID()); + float y_offset = database.GetSpawnLocationPlacementOffsetY(spawn->GetSpawnLocationID()); + float z_offset = database.GetSpawnLocationPlacementOffsetZ(spawn->GetSpawnLocationID()); + if(database.SaveSpawnEntry(spawn, sep->arg[1], percent, x_offset, y_offset, z_offset)) + client->Message(CHANNEL_COLOR_YELLOW, "Successfully saved spawn location with a spawn group of %u", spawn->GetSpawnLocationID()); + else + client->SimpleMessage(CHANNEL_COLOR_RED, "Error saving spawn location, see console window for details."); + } + else{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Syntax: /spawn add [spawn group id] [spawn group name] (percentage)"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "This command is used for adding the targeted NPC or Object to the database."); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You can substitute new for [spawn group id] to create a new one."); + } + break; + } + case COMMAND_SPAWN:{ + int32 id = 0; + Spawn* spawn = 0; + if(sep && sep->arg[0] && sep->IsNumber(0)){ + id = atol(sep->arg[0]); + spawn = client->GetCurrentZone()->GetSpawn(id); + } + if(id > 0 && spawn && spawn->appearance.name[0] == 0) + id = 0; + if(!id || !spawn){ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Syntax: /spawn [spawn id] (x) (y) (z) (heading) (location)"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "All parameters are optional except the id. The spawn id must be in the database."); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Ex: /spawn 1 0 0 0 0"); + safe_delete(sep); + return; + } + if(sep && sep->arg[1][0]){ + float x = atof(sep->arg[1]); + spawn->appearance.pos.X = x; + } + else + spawn->SetX(client->GetPlayer()->GetX(), false); + if(sep && sep->arg[2][0]){ + float y = atof(sep->arg[2]); + spawn->appearance.pos.Y = y; + } + else + spawn->SetY(client->GetPlayer()->GetY(), false); + if(sep && sep->arg[3][0]){ + float z = atof(sep->arg[3]); + spawn->appearance.pos.Z = z; + } + else + spawn->SetZ(client->GetPlayer()->GetZ(), false); + if(sep && sep->arg[4][0]){ + float heading = atof(sep->arg[4]); + spawn->SetHeading(heading); + } + else + spawn->SetHeading(client->GetPlayer()->GetHeading(), false); + spawn->SetSpawnOrigX(spawn->GetX()); + spawn->SetSpawnOrigY(spawn->GetY()); + spawn->SetSpawnOrigZ(spawn->GetZ()); + spawn->SetSpawnOrigHeading(spawn->GetHeading()); + if(sep && sep->arg[5][0]) + spawn->SetLocation(atoul(sep->arg[5])); + else + spawn->SetLocation(client->GetPlayer()->GetLocation()); + + if(spawn->IsNPC() && spawn->GetTotalHP() == 0){ + spawn->SetTotalHP(spawn->GetLevel() * 15); + spawn->SetHP(spawn->GetTotalHP()); + } + if(spawn->GetTotalPower() == 0){ + spawn->SetTotalPower(spawn->GetLevel() * 15); + spawn->SetPower(spawn->GetTotalPower()); + } + const char* script = world.GetSpawnScript(id); + if(script && lua_interface && lua_interface->GetSpawnScript(script) != 0) + spawn->SetSpawnScript(string(script)); + + spawn->GetZone()->CallSpawnScript(spawn, SPAWN_SCRIPT_PRESPAWN); + + client->GetCurrentZone()->AddSpawn(spawn); + if(spawn->IsNPC()) + spawn->GetZone()->AddLoot((NPC*)spawn); + spawn->GetZone()->CallSpawnScript(spawn, SPAWN_SCRIPT_SPAWN); + LogWrite(COMMAND__INFO, 0, "Command", "Received spawn command - Parms: %s", command_parms->data.c_str()); + break; + } + case COMMAND_ADMINFLAG: + { + if(sep && sep->arg[0]){ + sint16 tmp_status = database.GetCharacterAdminStatus(sep->arg[0]); + sint16 new_status = atoi(sep->arg[1]); + if(tmp_status == -10) + client->SimpleMessage(CHANNEL_ERROR,"Unable to flag character. Reason: Character does not exist."); + else if(tmp_status >= client->GetAdminStatus()) + client->SimpleMessage(CHANNEL_ERROR,"Unable to flag character. Reason: Character has same or higher level status."); + else if (new_status > client->GetAdminStatus()) + client->SimpleMessage(CHANNEL_ERROR, "Unable to flag character. Reason: New status is higher then your status."); + else{ + Client* client2 = client->GetCurrentZone()->GetClientByName(sep->arg[0]); + if (!client2) + client2 = zone_list.GetClientByCharName(sep->arg[0]); + + if(database.UpdateAdminStatus(sep->arg[0],new_status)) { + client->Message(CHANNEL_COLOR_YELLOW,"Character status updated to %i for %s.",new_status,sep->arg[0]); + if (client2) { + client2->SetAdminStatus(new_status); + client2->Message(CHANNEL_COLOR_YELLOW, "%s has set your admin status to %i.", client->GetPlayer()->GetName(), new_status); + } + } + else + client->SimpleMessage(CHANNEL_ERROR,"Unable to flag character. Unknown reason."); + } + }else{ + sint16 status = database.GetCharacterAdminStatus(client->GetPlayer()->GetName()); + if(status != client->GetAdminStatus()) + { + client->Message(CHANNEL_COLOR_YELLOW,"Flag status was changed from %i to %i.",status,client->GetAdminStatus()); + client->SetAdminStatus(status); + } + else + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /flag {name} {new_status}"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW," Standard User: 0"); + client->Message(CHANNEL_COLOR_YELLOW," Admin User: %i", status); + } + } + break; + } + case COMMAND_CANNEDEMOTE:{ + client->GetCurrentZone()->HandleEmote(client->GetPlayer(), command_parms->data); + break; + } + case COMMAND_BROADCAST: { + if (sep && sep->arg[0]) + zone_list.HandleGlobalBroadcast(sep->argplus[0]); + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /broadcast {message}"); + break; + } + case COMMAND_ANNOUNCE: { + if (sep && sep->arg[0]) + zone_list.HandleGlobalAnnouncement(sep->argplus[0]); + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /announce {message}"); + break; + } + case COMMAND_RELOAD_ITEMS:{ + LogWrite(COMMAND__INFO, 0, "Command", "Reloading items.."); + + int32 item_id = (sep && sep->arg[0]) ? atoul(sep->arg[0]) : 0; + if(item_id > 0) { + client->Message(CHANNEL_COLOR_YELLOW, "Reloading item %u based on /reload items [item_id].", item_id); + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Started Reloading items (this might take a few minutes...)"); + } + + database.ReloadItemList(item_id); + + if(!item_id) { + database.LoadMerchantInformation(); // we skip if there is only a reload of single item not all items + } + + if(item_id > 0) { + client->Message(CHANNEL_COLOR_YELLOW, "Reloaded item %u.", item_id); + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Finished Reloading items."); + } + break; + } + case COMMAND_ENABLE_ABILITY_QUE:{ + EQ2Packet* app = client->GetPlayer()->GetSpellBookUpdatePacket(client->GetVersion()); + if(app) + client->QueuePacket(app); + break; + } + case COMMAND_ITEMSEARCH: + case COMMAND_FROMBROKER:{ + PacketStruct* packet = configReader.getStruct("WS_StartBroker", client->GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(client->GetPlayer())); + //packet->setDataByName("unknown", 1); + packet->setDataByName("unknown2", 5, 0); + packet->setDataByName("unknown2", 20, 1); + packet->setDataByName("unknown2", 58, 3); + packet->setDataByName("unknown2", 40, 4); + client->QueuePacket(packet->serialize()); + PacketStruct* packet2 = configReader.getStruct("WS_BrokerBags", client->GetVersion()); + if (packet2) { + packet2->setDataByName("char_id", client->GetCharacterID()); + client->QueuePacket(packet2->serialize()); //send this for now, needed to properly clear data + safe_delete(packet2); + } + safe_delete(packet); + } + break; + } + case COMMAND_ANIMTEST:{ + PacketStruct* command_packet = configReader.getStruct("WS_CannedEmote", client->GetVersion()); + if(command_packet){ + int32 id = client->GetPlayer()->GetIDWithPlayerSpawn(client->GetPlayer()); + if (cmdTarget) + id = client->GetPlayer()->GetIDWithPlayerSpawn(cmdTarget); + command_packet->setDataByName ( "spawn_id" , id); + + int animID = 1; + + if(sep && sep->arg[0] && sep->IsNumber(0)) + animID = atoi(sep->arg[0]); + + VisualState* vs = NULL; + if(animID == 0) + { + vs = visual_states.FindVisualState(sep->arg[0]); + } + + + char msg[128]; + sprintf(msg,"Animation Test ID: %i",animID); + command_packet->setMediumStringByName ( "emote_msg" , msg ); + + if(vs != NULL) + command_packet->setDataByName ( "anim_type", vs->GetID ( ) ); + else + command_packet->setDataByName ( "anim_type", animID ); + + command_packet->setDataByName ( "unknown0", 0 ); + EQ2Packet* outapp = command_packet->serialize(); + client->QueuePacket(outapp); + safe_delete(command_packet); + } + break; + } + case COMMAND_KICK: + { + if( sep == 0 || sep->arg[0] == 0) + { + client->SimpleMessage(CHANNEL_COLOR_RED, "/kick [name]"); + } + else + { + Client* kickClient = zone_list.GetClientByCharName(string(sep->arg[0])); + + if ( kickClient == client ) + { + client->Message(CHANNEL_COLOR_RED, "You can't kick yourself!"); + break; + } + else if(kickClient != NULL) + { + sint16 maxStatus = database.GetHighestCharacterAdminStatus(kickClient->GetAccountID()); + + if ( maxStatus >= client->GetAdminStatus( ) || kickClient->GetAdminStatus() >= client->GetAdminStatus() ) + { + client->Message(CHANNEL_COLOR_RED,"Don't even think about it..."); + break; + } + + client->Message(CHANNEL_COLOR_RED, "Kicking %s...",sep->arg[0]); + + kickClient->Disconnect(); + } + else + { + client->Message(CHANNEL_COLOR_RED, "Could not find %s.",sep->arg[0]); + } + } + + break; + } + case COMMAND_LOCK: + { + if( sep != NULL && sep->arg[0] != NULL && sep->IsNumber(0)){ + int worldLocked = atoi(sep->arg[0]); + net.world_locked = worldLocked; + if ( worldLocked ) + client->Message(CHANNEL_COLOR_YELLOW,"World server has been locked."); + else + client->Message(CHANNEL_COLOR_YELLOW,"World server has been unlocked."); + } + else + client->SimpleMessage(CHANNEL_COLOR_RED, "/lock [0/1]"); + + break; + } + case COMMAND_BAN:{ + if( sep == 0 || sep->arg[0] == 0 || (sep->arg[1][0] != 0 && !sep->IsNumber(1) ) ) + { + client->SimpleMessage(CHANNEL_COLOR_RED, "/ban [name] [permanent:0/1]"); + } + else + { + Client* kickClient = zone_list.GetClientByCharName(sep->arg[0]); + + if ( kickClient == client ) + { + client->Message(CHANNEL_COLOR_RED, "You can't ban yourself!"); + break; + } + else if(kickClient != NULL) + { + sint16 maxStatus = database.GetHighestCharacterAdminStatus(kickClient->GetAccountID()); + + if ( maxStatus > client->GetAdminStatus( ) || + client->GetAdminStatus ( ) > kickClient->GetAdminStatus ( ) ) + { + client->Message(CHANNEL_COLOR_RED,"Don't even think about it..."); + break; + } + + client->Message(CHANNEL_COLOR_RED, "Kicking & Banning %s...",sep->arg[0]); + + int perm = atol(sep->arg[1]); + if ( perm == 1 ) + database.UpdateAdminStatus(sep->arg[0],-2); + else + database.UpdateAdminStatus(sep->arg[0],-1); + kickClient->Disconnect(); + } + else + { + client->Message(CHANNEL_COLOR_RED, "Could not find %s.",sep->arg[0]); + } + } + break; + } + case COMMAND_SET_COMBAT_VOICE:{ + int32 value = 0; + if(sep && sep->arg[0] && sep->IsNumber(0)) + value = atoi(sep->arg[0]); + client->GetPlayer()->SetCombatVoice(value); + break; + } + case COMMAND_SET_EMOTE_VOICE:{ + int32 value = 0; + if(sep && sep->arg[0] && sep->IsNumber(0)) + value = atoi(sep->arg[0]); + client->GetPlayer()->SetEmoteVoice(value); + break; + } + case COMMAND_GIVEITEM:{ + + if(sep && sep->arg[0][0] && sep->arg[0][1] && sep->IsNumber(1)){ + Client* itemToClient = zone_list.GetClientByCharName(sep->arg[0]); + + if ( itemToClient == NULL ) + client->Message(CHANNEL_COLOR_YELLOW,"Could not find %s.",sep->arg[0]); + else + { + int32 item_id = atol(sep->arg[1]); + client->Message(CHANNEL_COLOR_YELLOW,"Gave %s item id %i.",sep->arg[0],item_id); + itemToClient->AddItem(item_id); + } + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /giveitem [name] [item_id]"); + + break; + } + + case COMMAND_REPORT_BUG : { Command_ReportBug(client, sep); break; } + case COMMAND_INVENTORY : { Command_Inventory(client, sep, command); break; } + case COMMAND_WEAPONSTATS : { Command_WeaponStats(client); break; } + case COMMAND_SKILL : + case COMMAND_SKILL_ADD : + case COMMAND_SKILL_REMOVE : + case COMMAND_SKILL_LIST : { Command_Skills(client, sep, command->handler); break; } + case COMMAND_ZONE_SET : { Command_ZoneSet(client, sep); break; } + case COMMAND_ZONE_DETAILS : { Command_ZoneDetails(client, sep); break; } + case COMMAND_ENTITYCOMMAND : + case COMMAND_ENTITYCOMMAND_LIST : { Command_EntityCommand(client, sep, command->handler); break; } + case COMMAND_MERCHANT : + case COMMAND_MERCHANT_LIST : { Command_Merchant(client, sep, command->handler); break; } + case COMMAND_APPEARANCE : + case COMMAND_APPEARANCE_LIST : { Command_Appearance(client, sep, command->handler); break; } + case COMMAND_TRACK : { Command_Track(client); break; } + case COMMAND_DISTANCE : { Command_Distance(client); break; } + case COMMAND_INSPECT_PLAYER : { Command_InspectPlayer(client, sep); break; } + case COMMAND_ZONE_SAFE : { Command_ZoneSafeCoords(client, sep); break; } + case COMMAND_GUILDSAY : { Command_GuildSay(client, sep); break; } + case COMMAND_OFFICERSAY : { Command_OfficerSay(client, sep); break; } + case COMMAND_SET_GUILD_MEMBER_NOTE : { Command_SetGuildMemberNote(client, sep); break; } + case COMMAND_SET_GUILD_OFFICER_NOTE : { Command_SetGuildOfficerNote(client, sep); break; } + case COMMAND_GUILD : { Command_Guild(client, sep); break; } + case COMMAND_CREATE_GUILD : { Command_CreateGuild(client); break; } + case COMMAND_GUILDS : { Command_Guilds(client); break; } + case COMMAND_GUILDS_ADD : { Command_GuildsAdd(client, sep); break; } + case COMMAND_GUILDS_CREATE : { Command_GuildsCreate(client, sep); break; } + case COMMAND_GUILDS_DELETE : { Command_GuildsDelete(client, sep); break; } + case COMMAND_GUILDS_LIST : { Command_GuildsList(client); break; } + case COMMAND_GUILDS_REMOVE : { Command_GuildsRemove(client, sep); break; } + case COMMAND_CLAIM : { Command_Claim(client, sep); break; } + case COMMAND_CLEAR_ALL_QUEUED : { Command_ClearAllQueued(client); break; } + case COMMAND_LOCATION : { Command_Location(client); break; } + case COMMAND_LOCATION_ADD : { Command_LocationAdd(client, sep); break; } + case COMMAND_LOCATION_CREATE : { Command_LocationCreate(client, sep); break; } + case COMMAND_LOCATION_DELETE : { Command_LocationDelete(client, sep); break; } + case COMMAND_LOCATION_LIST : { Command_LocationList(client, sep); break; } + case COMMAND_LOCATION_REMOVE : { Command_LocationRemove(client, sep); break; } + case COMMAND_GRID : { Command_Grid(client, sep); break; } + case COMMAND_TRY_ON : { Command_TryOn(client, sep); break; } + case COMMAND_RANDOMIZE : { Command_Randomize(client, sep); break; } + case COMMAND_AFK : { Command_AFK(client, sep); break; } + case COMMAND_SHOW_CLOAK : { Command_ShowCloak(client, sep); break; } + case COMMAND_SHOW_HELM : { Command_ShowHelm(client, sep); break; } + case COMMAND_SHOW_HOOD : { Command_ShowHood(client, sep); break; } + case COMMAND_SHOW_HOOD_OR_HELM : { Command_ShowHoodHelm(client, sep); break; } + case COMMAND_SHOW_RANGED : { Command_ShowRanged(client, sep); break; } + case COMMAND_STOP_DRINKING : { Command_StopDrinking(client); break; } + case COMMAND_STOP_EATING : { Command_StopEating(client); break; } + case COMMAND_TOGGLE_ANONYMOUS : { Command_Toggle_Anonymous(client); break; } + case COMMAND_TOGGLE_AUTOCONSUME : { Command_Toggle_AutoConsume(client, sep); break; } + case COMMAND_TOGGLE_BONUS_EXP : { Command_Toggle_BonusXP(client); break; } + case COMMAND_TOGGLE_COMBAT_EXP : { Command_Toggle_CombatXP(client); break; } + case COMMAND_TOGGLE_GM_HIDE : { Command_Toggle_GMHide(client); break; } + case COMMAND_TOGGLE_GM_VANISH : { Command_Toggle_GMVanish(client); break; } + case COMMAND_TOGGLE_ILLUSIONS : { Command_Toggle_Illusions(client, sep); break; } + case COMMAND_TOGGLE_LFG : { Command_Toggle_LFG(client); break; } + case COMMAND_TOGGLE_LFW : { Command_Toggle_LFW(client); break; } + case COMMAND_TOGGLE_QUEST_EXP : { Command_Toggle_QuestXP(client); break; } + case COMMAND_TOGGLE_ROLEPLAYING : { Command_Toggle_Roleplaying(client); break; } + case COMMAND_TOGGLE_DUELS : { Command_Toggle_Duels(client); break; } + case COMMAND_TOGGLE_TRADES : { Command_Toggle_Trades(client); break; } + case COMMAND_TOGGLE_GUILDS : { Command_Toggle_Guilds(client); break; } + case COMMAND_TOGGLE_GROUPS : { Command_Toggle_Groups(client); break; } + case COMMAND_TOGGLE_RAIDS : { Command_Toggle_Raids(client); break; } + case COMMAND_TOGGLE_LON : { Command_Toggle_LON(client); break; } + case COMMAND_TOGGLE_VCINVITE : { Command_Toggle_VoiceChat(client); break; } + case COMMAND_CANCEL_MAINTAINED : { Command_CancelMaintained(client, sep); break; } + case COMMAND_MOTD : { Command_MOTD(client); break; } + case COMMAND_RANDOM : { Command_Random(client, sep); break; } + case COMMAND_CREATE : { Command_Create(client, sep); break; } + case COMMAND_CREATEFROMRECIPE : { Command_CreateFromRecipe(client, sep); break; } + case COMMAND_TITLE : { Command_Title(client); break; } + case COMMAND_TITLE_LIST : { Command_TitleList(client); break; } + case COMMAND_TITLE_SETPREFIX : { Command_TitleSetPrefix(client, sep); break; } + case COMMAND_TITLE_SETSUFFIX : { Command_TitleSetSuffix(client, sep); break; } + case COMMAND_TITLE_FIX : { Command_TitleFix(client, sep); break; } + case COMMAND_LANGUAGES : { Command_Languages(client, sep); break; } + case COMMAND_SET_LANGUAGE : { Command_SetLanguage(client, sep); break; } + case COMMAND_FOLLOW : { Command_Follow(client, sep); break; } + case COMMAND_STOP_FOLLOW : { Command_StopFollow(client, sep); break; } + case COMMAND_LASTNAME : { Command_LastName(client, sep); break; } + case COMMAND_CONFIRMLASTNAME : { Command_ConfirmLastName(client, sep); break; } + case COMMAND_PET : { Command_Pet(client, sep); break; } + case COMMAND_PETNAME : { Command_PetName(client, sep); break; } + case COMMAND_NAME_PET : { Command_NamePet(client, sep); break; } + case COMMAND_RENAME : { Command_Rename(client, sep); break; } + case COMMAND_CONFIRMRENAME : { Command_ConfirmRename(client, sep); break; } + case COMMAND_PETOPTIONS : { Command_PetOptions(client, sep); break; } + case COMMAND_START_TRADE : { Command_TradeStart(client, sep); break; } + case COMMAND_ACCEPT_TRADE : { Command_TradeAccept(client, sep); break; } + case COMMAND_REJECT_TRADE : { Command_TradeReject(client, sep); break; } + case COMMAND_CANCEL_TRADE : { Command_TradeCancel(client, sep); break; } + case COMMAND_SET_TRADE_COIN : { Command_TradeSetCoin(client, sep); break; } + case COMMAND_ADD_TRADE_COPPER : { Command_TradeAddCoin(client, sep, COMMAND_ADD_TRADE_COPPER); break; } + case COMMAND_ADD_TRADE_SILVER : { Command_TradeAddCoin(client, sep, COMMAND_ADD_TRADE_SILVER); break; } + case COMMAND_ADD_TRADE_GOLD : { Command_TradeAddCoin(client, sep, COMMAND_ADD_TRADE_GOLD); break; } + case COMMAND_ADD_TRADE_PLAT : { Command_TradeAddCoin(client, sep, COMMAND_ADD_TRADE_PLAT); break; } + case COMMAND_REMOVE_TRADE_COPPER: { Command_TradeRemoveCoin(client, sep, COMMAND_REMOVE_TRADE_COPPER); break; } + case COMMAND_REMOVE_TRADE_SILVER: { Command_TradeRemoveCoin(client, sep, COMMAND_REMOVE_TRADE_SILVER); break; } + case COMMAND_REMOVE_TRADE_GOLD : { Command_TradeRemoveCoin(client, sep, COMMAND_REMOVE_TRADE_GOLD); break; } + case COMMAND_REMOVE_TRADE_PLAT : { Command_TradeRemoveCoin(client, sep, COMMAND_REMOVE_TRADE_PLAT); break; } + case COMMAND_ADD_TRADE_ITEM : { Command_TradeAddItem(client, sep); break; } + case COMMAND_REMOVE_TRADE_ITEM : { Command_TradeRemoveItem(client, sep); break; } + case COMMAND_ACCEPT_ADVANCEMENT : { Command_AcceptAdvancement(client, sep); break; } + case COMMAND_DUEL : { Command_Duel(client, sep); break; } + case COMMAND_DUELBET : { Command_DuelBet(client, sep); break; } + case COMMAND_DUEL_ACCEPT : { Command_DuelAccept(client, sep); break; } + case COMMAND_DUEL_DECLINE : { Command_DuelDecline(client, sep); break; } + case COMMAND_DUEL_SURRENDER : { Command_DuelSurrender(client, sep); break; } + case COMMAND_DUEL_TOGGLE : { Command_DuelToggle(client, sep); break; } + case COMMAND_SPAWN_TEMPLATE : { Command_SpawnTemplate(client, sep); break; } + //devn00b + case COMMAND_MOOD : { Command_Mood(client, sep); break;} + + case COMMAND_MODIFY : { Command_Modify(client); break; } + case COMMAND_MODIFY_CHARACTER : { Command_ModifyCharacter(client, sep); break; } + case COMMAND_MODIFY_QUEST : { Command_ModifyQuest(client, sep); break; } + case COMMAND_MODIFY_FACTION : { Command_ModifyFaction(client, sep); break; } + case COMMAND_MODIFY_GUILD : { Command_ModifyGuild(client, sep); break; } + case COMMAND_MODIFY_ITEM : { Command_ModifyItem(client, sep); break; } + case COMMAND_MODIFY_SKILL : { Command_ModifySkill(client, sep); break; } + case COMMAND_MODIFY_SPAWN : { Command_ModifySpawn(client, sep); break; } + case COMMAND_MODIFY_SPELL : { Command_ModifySpell(client, sep); break; } + case COMMAND_MODIFY_ZONE : { Command_ModifyZone(client, sep); break; } + + case COMMAND_JOIN_CHANNEL : { Command_JoinChannel(client, sep); break;} + case COMMAND_JOIN_CHANNEL_FROM_LOAD: { Command_JoinChannelFromLoad(client, sep); break;} + case COMMAND_TELL_CHANNEL : { Command_TellChannel(client, sep); break;} + case COMMAND_LEAVE_CHANNEL : { Command_LeaveChannel(client, sep); break;} + case COMMAND_WHO_CHANNEL : { Command_WhoChannel(client, sep); break;} + case COMMAND_RAIN : { Command_Rain(client, sep); break; } + case COMMAND_WIND : { Command_Wind(client, sep); break; } + case COMMAND_WEATHER : { Command_Weather(client, sep); break; } + case COMMAND_FROM_MERCHANT : { Command_SendMerchantWindow(client, sep); break; } + case COMMAND_TO_MERCHANT : { Command_SendMerchantWindow(client, sep, true); break; } + case COMMAND_SELECT : { Command_Select(client, sep); break; } + case COMMAND_SMP : { Command_StationMarketPlace(client, sep); break; } + case COMMAND_CONSUME_FOOD : { Command_ConsumeFood(client, sep); break; } + case COMMAND_SET_CONSUME_FOOD : { Command_ConsumeFood(client, sep); break; } + case COMMAND_AQUAMAN : { Command_Aquaman(client, sep); break; } + case COMMAND_ATTUNE_INV : { Command_Attune_Inv(client, sep); break; } + case COMMAND_PLAYER : { Command_Player(client, sep); break; } + case COMMAND_PLAYER_COINS : { Command_Player_Coins(client, sep); break; } + case COMMAND_RESET_ZONE_TIMER : { Command_Reset_Zone_Timer(client, sep); break; } + case COMMAND_ACHIEVEMENT_ADD : { Command_AchievementAdd(client, sep); break; } + case COMMAND_EDITOR : { Command_Editor(client, sep); break; } + case COMMAND_ACCEPT_RESURRECTION: { Command_AcceptResurrection(client, sep); break; } + case COMMAND_DECLINE_RESURRECTION:{ Command_DeclineResurrection(client, sep); break; } + case COMMAND_TEST : { Command_Test(client, command_parms); break; } + case COMMAND_SPEED : { Command_Speed(client, sep); break; } + + case COMMAND_BOT : { Command_Bot(client, sep); break; } + case COMMAND_BOT_CREATE : { Command_Bot_Create(client, sep); break; } + case COMMAND_BOT_CUSTOMIZE : { Command_Bot_Customize(client, sep); break; } + case COMMAND_BOT_SPAWN : { Command_Bot_Spawn(client, sep); break; } + case COMMAND_BOT_LIST : { Command_Bot_List(client, sep); break; } + case COMMAND_BOT_INV : { Command_Bot_Inv(client, sep); break; } + case COMMAND_BOT_SETTINGS : { Command_Bot_Settings(client, sep); break; } + case COMMAND_BOT_HELP : { Command_Bot_Help(client, sep); break; } + case GET_AA_XML : { Get_AA_Xml(client, sep); break; } + case ADD_AA : { Add_AA(client, sep); break; } + case COMMIT_AA_PROFILE : { Commit_AA_Profile(client, sep); break; } + case BEGIN_AA_PROFILE : { Begin_AA_Profile(client, sep); break; } + case BACK_AA : { Back_AA(client, sep); break; } + case REMOVE_AA : { Remove_AA(client, sep); break; } + case SWITCH_AA_PROFILE : { Switch_AA_Profile(client, sep); break; } + case CANCEL_AA_PROFILE : { Cancel_AA_Profile(client, sep); break; } + case SAVE_AA_PROFILE : { Save_AA_Profile(client, sep); break; } + case COMMAND_TARGETITEM : { Command_TargetItem(client, sep); break; } + case COMMAND_FINDSPAWN: { Command_FindSpawn(client, sep); break; } + case COMMAND_MOVECHARACTER: { Command_MoveCharacter(client, sep); break; } + case COMMAND_CRAFTITEM: { + Item* item = 0; + if (sep && sep->IsNumber(0)) { + int32 item_id = atol(sep->arg[0]); + int32 quantity = 1; + + if (sep->arg[1] && sep->IsNumber(1)) + quantity = atoi(sep->arg[1]); + item = new Item(master_item_list.GetItem(item_id)); + if (!item) { + LogWrite(TRADESKILL__ERROR, 0, "CraftItem", "Item (%u) not found.", item_id); + } + else { + item->details.count = quantity; + // use CHANNEL_COLOR_CHAT_RELATIONSHIP as that is the same value (4) as it is in a log for this message + client->Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "You created %s.", item->CreateItemLink(client->GetVersion()).c_str()); + bool itemDeleted = false; + client->AddItem(item, &itemDeleted); + //Check for crafting quest updates + int8 update_amt = 0; + if (!itemDeleted && item->stack_count > 1) + update_amt = 1; + else + update_amt = quantity; + + if(!itemDeleted) + client->GetPlayer()->CheckQuestsCraftUpdate(item, update_amt); + } + + } + + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /craftitem {item_id} [quantity] "); + } + break; + } + case COMMAND_UNMENTOR: + case COMMAND_MENTOR: { + client->GetPlayer()->MentorTarget(); + break; + } + case COMMAND_CANCEL_EFFECT: { Command_CancelEffect(client, sep); break; } + case COMMAND_CUREPLAYER: { Command_CurePlayer(client, sep); break; } + case COMMAND_SHARE_QUEST: { Command_ShareQuest(client, sep); break; } + case COMMAND_YELL: { Command_Yell(client, sep); break; } + case COMMAND_SETAUTOLOOTMODE: { Command_SetAutoLootMode(client, sep); break; } + case COMMAND_ASSIST: { Command_Assist(client, sep); break; } + case COMMAND_TARGET: { Command_Target(client, sep); break; } + case COMMAND_TARGET_PET: { Command_Target_Pet(client, sep); break; } + default: + { + LogWrite(COMMAND__WARNING, 0, "Command", "Unhandled command: %s", command->command.data.c_str()); + break; + } + + } + safe_delete(sep); +} + + +/******************** New COMMAND Handler Functions ********************/ +/* + Started breaking apart the huge switch() for commands into sepErate + functions so it is easier to locate the blocks of code by command + -- JA 2012.03.03 +*/ + +// sample function header +/* + Function: + Purpose : + Params : + Dev : + Example : +*/ +//void Commands::Command() +//{ +//} + + +/* + Function: Command_AcceptAdvancement() + Purpose : Player accepts a new advancement option + Params : Spell ID + Dev : Jabantiz +*/ +void Commands::Command_AcceptAdvancement(Client* client, Seperator* sep) +{ + Player *player = client->GetPlayer(); + if (sep && sep->IsSet(0)) { + int32 trait_id = atoul(sep->arg[0]); + TraitData* trait = nullptr; + if(client->GetVersion() <= 561) { + trait = master_trait_list.GetTraitByItemID(trait_id); + } + else { + trait = master_trait_list.GetTrait(trait_id); + } + + if(!trait) { + LogWrite(COMMAND__ERROR, 0, "Command", "Invalid accept advancement of trait %u, no trait found.", trait_id); + return; // not valid lets not crash! + } + + if(!master_trait_list.IsPlayerAllowedTrait(client, trait)) { + client->SimpleMessage(CHANNEL_COLOR_RED, "Not enough trait points to accept trait."); + return; + } + // Check to see if this is a trait or grandmaster training (traits are always new spells, training is always upgrades) + if (!player->HasSpell(trait->spellID, 0, true)) + { + Spell* spell = master_spell_list.GetSpell(trait->spellID, trait->tier); + if(spell) { + player->AddSpellBookEntry(trait->spellID, trait->tier, player->GetFreeSpellBookSlot(spell->GetSpellData()->spell_book_type), spell->GetSpellData()->spell_book_type, spell->GetSpellData()->linked_timer, true); + } + else { + client->Message(CHANNEL_COLOR_RED, "ERROR! Trait is not a valid spell id %u may be disabled.", trait->spellID); + } + } + else + { + Spell* spell = master_spell_list.GetSpell(trait->spellID, trait->tier); + if(spell) { + int8 old_slot = player->GetSpellSlot(spell->GetSpellID()); + player->RemoveSpellBookEntry(spell->GetSpellID()); + player->AddSpellBookEntry(spell->GetSpellID(), spell->GetSpellTier(), old_slot, spell->GetSpellData()->spell_book_type, spell->GetSpellData()->linked_timer, true); + player->UnlockSpell(spell); + client->SendSpellUpdate(spell); + } + else { + client->Message(CHANNEL_COLOR_RED, "ERROR! Trait is not a valid spell id %u may be disabled.", trait->spellID); + } + } + + // Spell book update + client->QueuePacket(player->GetSpellBookUpdatePacket(client->GetVersion())); + client->QueuePacket(master_trait_list.GetTraitListPacket(client)); + + if(client->GetVersion() <= 561) { + master_trait_list.ChooseNextTrait(client); + } + } +} + +/* + Function: + Purpose : + Params : + Dev : + Example : +*/ +void Commands::Command_AFK(Client* client, Seperator* sep) +{ + Player* player = client->GetPlayer(); + + player->toggle_character_flag(CF_AFK); + client->Message(CHANNEL_COLOR_YELLOW,"You are %s afk.", client->GetPlayer()->get_character_flag(CF_AFK)?"now":"no longer"); + + if (player->get_character_flag(CF_AFK)) + { + if (sep && sep->argplus[0]) + player->SetAwayMessage("I am away from the keyboard, " + string(sep->argplus[0])); + else + player->SetAwayMessage("Sorry, I am A.F.K. (Away From Keyboard)"); + + string message = string(player->GetName()) + " is going afk."; + Spawn* target = player->GetTarget(); + + if (target && target != player) + { + message = string(player->GetName()) + " tells " + string(target->GetName()) + " that "; + player->GetGender() == 1 ? message += "he" : message += "she"; + message += " is going afk."; + } + + player->GetZone()->SimpleMessage(CHANNEL_COLOR_YELLOW, message.c_str(), player, 30); + } + + if (player->get_character_flag(CF_AFK)) + player->SetActivityStatus(player->GetActivityStatus() + ACTIVITY_STATUS_AFK); + else + player->SetActivityStatus(player->GetActivityStatus() - ACTIVITY_STATUS_AFK); +} + +/* + Function: Command_Appearance() + Purpose : Handles /appearance commands + Params : list + Dev : Scatman + Example : /appearance list +*/ +void Commands::Command_Appearance(Client* client, Seperator* sep, int handler) +{ + if( handler == COMMAND_APPEARANCE ) + { + // /appearance command by itself shows help (to be extended) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /appearance list [appearance name]"); + return; + } + else if( handler == COMMAND_APPEARANCE_LIST ) + { + // /appearance list command expects "name" param + if (sep && sep->arg[0]) + { + const char* appearance_name = sep->argplus[0]; + client->Message(CHANNEL_COLOR_YELLOW, "Listing appearances like '%s':", appearance_name); + vector* appearances = database.GetAppearanceIDsLikeName(string(appearance_name)); + + if (appearances) + { + vector::iterator itr; + for (itr = appearances->begin(); itr != appearances->end(); itr++) + { + int16 id = *itr; + string name = database.GetAppearanceName(id); + + if (ToLower(name).find(ToLower(string(appearance_name))) < 0xFFFFFFFF) + client->Message(CHANNEL_COLOR_YELLOW, "%s (%u)", name.c_str(), id); + } + safe_delete(appearances); + } + } + else // no param + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /appearance list [appearance name]"); + } + +} + +/* + Function: Command_Claim() + Purpose : Summon veteran rewards + Params : nothing = show claim window, any number claims that item. + Dev : devn00b + Example : /claim 0 (claims the 1st item added to the list claim[0]) +*/ +void Commands::Command_Claim(Client* client, Seperator* sep) +{ + //if we were passed a claim id + if (sep && sep->argplus[0] && sep->IsNumber(0)) + { + int32 char_id = client->GetCharacterID(); + int8 my_claim_id = atoi(sep->argplus[0]); + vector claim = database.LoadCharacterClaimItems(char_id); + Item* item = master_item_list.GetItem(claim[my_claim_id].item_id); + database.ClaimItem(char_id, item->details.item_id, client); + return; + } + else { + //if no arg just send the window. + client->ShowClaimWindow(); + return; + } + return; +} + +/* + Function: Command_ClearAllQueued() + Purpose : + Params : + Dev : + Example : +*/ +void Commands::Command_ClearAllQueued(Client* client) +{ + ZoneServer* zone = client->GetPlayer()->GetZone(); + if (zone && zone->GetSpellProcess()) + zone->GetSpellProcess()->RemoveSpellFromQueue(client->GetPlayer()); +} + +/* + Function: Command_CancelMaintained() + Purpose : Cancels maintained spells + Params : Maintained Spell Index + Dev : Zcoretri + Example : /cancel_maintained 1 - would cancel the spell in slot 1 of Maintained Spells list +*/ +void Commands::Command_CancelMaintained(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0] && sep->IsNumber(0)) + { + int32 spell_index = atoul(sep->arg[0]); + if(spell_index > 29) + return; + + client->GetPlayer()->MMaintainedSpells.readlock(__FUNCTION__, __LINE__); + MaintainedEffects mEffects = client->GetPlayer()->GetInfoStruct()->maintained_effects[spell_index]; + client->GetPlayer()->MMaintainedSpells.releasereadlock(__FUNCTION__, __LINE__); + + if (!client->GetPlayer()->GetZone()->GetSpellProcess()->DeleteCasterSpell(mEffects.spell, "canceled", false)) + client->Message(CHANNEL_COLOR_RED, "The maintained spell could not be cancelled."); + } +} + +/* + Function: Command_Create() + Purpose : Handler for starting Tradeskilling table + Params : + Dev : Zcoretri + Example : +*/ +void Commands::Command_Create(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_CREATE"); + client->ShowRecipeBook(); +} + +void Commands::Command_CreateFromRecipe(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_CREATEFROMRECIPE"); + if (sep && sep->arg[0] && sep->IsNumber(0)) + ClientPacketFunctions::SendCreateFromRecipe(client, atoul(sep->arg[0])); +} + +/* + Function: Command_Distance() + Purpose : Displays distance from targeted spawn + Params : + Dev : Scatman + Example : /distance +*/ +void Commands::Command_Distance(Client* client) +{ + Spawn* target = client->GetPlayer()->GetTarget(); + + if (target) + client->Message(CHANNEL_COLOR_YELLOW, "Your distance from %s is %f", target->GetName(), client->GetPlayer()->GetDistance(target)); + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must have a spawn targeted to use /distance"); +} + +/* + Function: Command_Duel() + Purpose : Handle the /duel commands - not yet implemented + Params : unknown + Dev : + Example : +*/ +void Commands::Command_Duel(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_DUEL"); + LogWrite(MISC__TODO, 1, "Command", "TODO-Command: Duel Command"); + client->Message(CHANNEL_COLOR_YELLOW, "You cannot duel other players (Not Implemented)"); +} + +/* + Function: Command_DuelBet() + Purpose : Handle the /duel commands - not yet implemented + Params : unknown + Dev : + Example : +*/ +void Commands::Command_DuelBet(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_DUELBET"); + LogWrite(MISC__TODO, 1, "Command", "TODO-Command: Duel Bet Command"); + client->Message(CHANNEL_COLOR_YELLOW, "You cannot duel other players (Not Implemented)"); +} + +/* + Function: Command_DuelAccept() + Purpose : Handle the /duel commands - not yet implemented + Params : unknown + Dev : + Example : +*/ +void Commands::Command_DuelAccept(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_DUEL_ACCEPT"); + LogWrite(MISC__TODO, 1, "Command", "TODO-Command: Accept Duel Command"); + client->Message(CHANNEL_COLOR_YELLOW, "You cannot duel other players (Not Implemented)"); +} + +/* + Function: Command_DuelDecline() + Purpose : Handle the /duel commands - not yet implemented + Params : unknown + Dev : + Example : +*/ +void Commands::Command_DuelDecline(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_DUEL_DECLINE"); + LogWrite(MISC__TODO, 1, "Command", "TODO-Command: Decline Duel Request Command"); + client->Message(CHANNEL_COLOR_YELLOW, "You cannot duel other players (Not Implemented)"); +} + +/* + Function: Command_DuelSurrender() + Purpose : Handle the /duel commands - not yet implemented + Params : unknown + Dev : + Example : +*/ +void Commands::Command_DuelSurrender(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_DUEL_SURRENDER"); + LogWrite(MISC__TODO, 1, "Command", "TODO-Command: Surrender Duel Command"); + client->Message(CHANNEL_COLOR_YELLOW, "You cannot duel other players (Not Implemented)"); + + // JA, just messin around ;) + char surrender[64]; + sprintf(surrender, "%s surrendered like a cowardly dog!", client->GetPlayer()->GetName()); + zone_list.HandleGlobalAnnouncement(surrender); +} + +/* + Function: Command_DuelToggle() + Purpose : Handle the /duel commands - not yet implemented + Params : unknown + Dev : + Example : +*/ +void Commands::Command_DuelToggle(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_DUEL"); + LogWrite(MISC__TODO, 1, "Command", "TODO-Command: Duel Commands"); + client->Message(CHANNEL_COLOR_YELLOW, "You cannot duel other players (Not Implemented)"); +} + +/* + Function: + Purpose : + Params : + Dev : + Example : +*/ +void Commands::Command_EntityCommand(Client* client, Seperator* sep, int handler) +{ + if( handler == COMMAND_ENTITYCOMMAND ) + { + // /entitycommand command by itself shows help (to be extended) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /entity_command list [entity command name]"); + return; + } + else if( handler == COMMAND_ENTITYCOMMAND_LIST ) + { + // /entitycommand list command expects "name" param + if (sep && sep->arg[0]) + { + const char* entity_command_name = sep->argplus[0]; + client->Message(CHANNEL_COLOR_YELLOW, "Listing entity commands like '%s':", entity_command_name); + map*>* entity_command_list_all = client->GetCurrentZone()->GetEntityCommandListAll(); + map*>::iterator itr; + + for (itr = entity_command_list_all->begin(); itr != entity_command_list_all->end(); itr++) + { + vector* entity_command_list = itr->second; + vector::iterator itr2; + + for (itr2 = entity_command_list->begin(); itr2 != entity_command_list->end(); itr2++) + { + EntityCommand* entity_command = *itr2; + + if (ToLower(entity_command->name).find(ToLower(string(entity_command_name))) < 0xFFFFFFFF) + client->Message(CHANNEL_COLOR_YELLOW, "Command Text: %s, Command List ID: %u, Distance: %f\n", entity_command->name.c_str(), itr->first, entity_command->distance); + } + } + } + else // no command name + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /entity_command list [entity command name]"); + } +} + + +/* + Function: Command_Follow() + Purpose : Handle the /follow command - not yet implemented + Params : + Dev : + Example : +*/ +void Commands::Command_Follow(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_FOLLOW"); + // flag to toggle if the players target is in the players group + bool targetInGroup = false; + // get a pointer to the players group + GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo(); + // If the player has a group and has a target + if (gmi && client->GetPlayer()->GetTarget()) { + deque::iterator itr; + + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + + PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + // Loop through the group members + for (itr = members->begin(); itr != members->end(); itr++) { + // If a group member matches a target + if ((*itr)->member && (*itr)->member == client->GetPlayer()->GetTarget()) { + // toggle the flag and break the loop + targetInGroup = true; + break; + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } + if (targetInGroup) { + // CHANNEL_COLOR_CHAT_RELATIONSHIP = 4, which matches the value in logs + client->Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "You start to follow %s.", client->GetPlayer()->GetTarget()->GetName()); + client->GetPlayer()->SetFollowTarget(client->GetPlayer()->GetTarget()); + client->GetPlayer()->info_changed = true; + client->GetPlayer()->changed = true; + } + else + client->Message(CHANNEL_NARRATIVE, "You must first select a group member to follow."); +} + +/* + Function: Command_StopFollow() + Purpose : Handle the /stop_follow command - not yet implemented + Params : + Dev : + Example : +*/ +void Commands::Command_StopFollow(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_STOP_FOLLOW"); + if (client->GetPlayer()->GetFollowTarget()) { + // CHANNEL_COLOR_CHAT_RELATIONSHIP = 4, which matches the value in logs + client->Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "You are no longer following %s", client->GetPlayer()->GetFollowTarget()->GetName()); + client->GetPlayer()->SetFollowTarget(0); + client->GetPlayer()->info_changed = true; + client->GetPlayer()->changed = true; + } +} + +/* + Function: Command_Grid() + Purpose : Show player's current Grid ID + Params : + Dev : Scatman + Example : /grid +*/ +void Commands::Command_Grid(Client* client, Seperator* sep) +{ + if (client->GetPlayer()->GetMap() != nullptr) { + if(sep && sep->arg[0][0] && strncasecmp("spawns", sep->arg[0], 6) == 0) { + int32 grid = client->GetPlayer()->GetLocation(); + + if(sep->IsNumber(1)) + grid = atoul(sep->arg[1]); + + client->GetCurrentZone()->SendClientSpawnListInGrid(client, grid); + } + if(sep && sep->arg[0][0] && strncasecmp("boundary", sep->arg[0], 8) == 0) { + int32 grid = client->GetPlayer()->GetLocation(); + + if(sep->IsNumber(1)) + grid = atoul(sep->arg[1]); + + if(!client->GetPlayer()->GetMap()) { + client->Message(CHANNEL_COLOR_RED, "No map to check grid!"); + return; + } + GridMapBorder* gmb = client->GetPlayer()->GetMap()->GetMapGridBorder(grid, false); + if(gmb) { + client->Message(CHANNEL_COLOR_YELLOW, "Grid %u border MinX %f MaxX %f, MinY %f MaxY %f, MinZ %f MaxZ %f", grid, gmb->m_MinX, gmb->m_MaxX, gmb->m_MinY, gmb->m_MaxY, gmb->m_MinZ, gmb->m_MaxZ); + } + else { + client->Message(CHANNEL_COLOR_RED, "Grid %u has no grid map border", grid); + } + } + else { + client->Message(CHANNEL_COLOR_YELLOW, "Your Grid ID is %u", client->GetPlayer()->GetLocation()); + auto loc = glm::vec3(client->GetPlayer()->GetX(), client->GetPlayer()->GetZ(), client->GetPlayer()->GetY()); + uint32 GridID = 0; + uint32 WidgetID = 0; + float new_z = client->GetPlayer()->FindBestZ(loc, nullptr, &GridID, &WidgetID); + float minY = client->GetPlayer()->GetMap()->GetMinY(); + float maxY = client->GetPlayer()->GetMap()->GetMaxY(); + float minZ = client->GetPlayer()->GetMap()->GetMinZ(); + float maxZ = client->GetPlayer()->GetMap()->GetMaxZ(); + int32 grid_spawn_count = client->GetPlayer()->GetZone()->GetSpawnCountInGrid(GridID); + client->Message(CHANNEL_COLOR_YELLOW, "Grid result is %u, at EQ2 Y coordinate %f. Spawns on grid: %u. Min/Max Y %f/%f Z %f/%f. Widget ID: %u", GridID, new_z, grid_spawn_count, minY, maxY, minZ, maxZ, WidgetID); + } + } +} + +/* + Function: Command_Guild() + Purpose : Handler for all UI-related guild commands + Dev : Scatman +*/ +void Commands::Command_Guild(Client* client, Seperator* sep) +{ + Guild* guild = client->GetPlayer()->GetGuild(); + if (sep && sep->GetMaxArgNum() > 0 && sep->arg[0]) + { + const char* command = sep->arg[0]; + int32 length = strlen(command); + + LogWrite(COMMAND__DEBUG, 0, "Command", "Guild Command: %s", command); + + if (strncmp(command, "rank_name", length) == 0 && sep->arg[1] && sep->IsNumber(1) && sep->arg[2] && guild) + guild->SetRankName(atoi(sep->arg[1]), sep->argplus[2]); + else if (strncmp(command, "rank_permission", length) == 0 && sep->arg[1] && sep->IsNumber(1) && sep->arg[2] && sep->IsNumber(2) && sep->arg[3] && guild) + guild->SetPermission(atoi(sep->arg[1]), atoi(sep->arg[2]), strncmp(sep->arg[3], "true", 4) == 0 ? 1 : 0); + else if (strncmp(command, "filter_event", length) == 0 && sep->arg[1] && sep->IsNumber(1) && sep->arg[2] && sep->IsNumber(2) && sep->arg[3] && guild) + guild->SetEventFilter(atoi(sep->arg[1]), atoi(sep->arg[2]), strncmp(sep->arg[3], "true", 4) == 0 ? 1 : 0); + else if (strncmp(command, "kick", length) == 0 && sep->arg[1] && guild) + guild->KickGuildMember(client, sep->arg[1]); + else if (strncmp(command, "demote", length) == 0 && sep->arg[1] && guild) + guild->DemoteGuildMember(client, sep->arg[1]); + else if (strncmp(command, "promote", length) == 0 && sep->arg[1] && guild) + guild->PromoteGuildMember(client, sep->arg[1]); + else if (strncmp(command, "points", length) == 0 && guild) + { + if (sep->arg[1] && strncmp(sep->arg[1], "add", length) == 0) + { + if (sep->arg[2] && sep->IsNumber(2) && sep->arg[3]) + { + float points = atof(sep->arg[2]); + const char* option = sep->arg[3]; + const char* comment = sep->argplus[4]; + + if (strncmp(option, "all", strlen(option)) == 0) + guild->AddPointsToAll(client, points, comment); + else if (strncmp(option, "online", strlen(option)) == 0) + guild->AddPointsToAllOnline(client, points, comment); + else if (strncmp(option, "group", strlen(option)) == 0) + guild->AddPointsToGroup(client, points, comment); + else if (strncmp(option, "raid", strlen(option)) == 0) + guild->AddPointsToRaid(client, points, comment); + else + guild->AddPointsToGuildMember(client, points, option, comment); + } + } + else if (sep->arg[1] && strncmp(sep->arg[1], "view", strlen(sep->arg[1])) == 0 && sep->arg[2]) + guild->ViewGuildMemberPoints(client, sep->arg[2]); + else + LogWrite(COMMAND__ERROR, 0, "Command", "Unhandled guild points command: %s", sep->argplus[0]); + } + else if (strncmp(command, "motd", length) == 0 && sep->arg[1] && guild) + guild->SetMOTD(sep->argplus[1]); + else if (strncmp(command, "recruiting", length) == 0 && guild) + { + if (sep->arg[1]) + { + const char* option = sep->arg[1]; + + if (strncmp(option, "short_text", strlen(option)) == 0 && sep->arg[2]) + guild->SetRecruitingShortDesc(sep->argplus[2]); + else if (strncmp(option, "long_text", strlen(option)) == 0 && sep->arg[2]) + guild->SetRecruitingFullDesc(sep->argplus[2]); + else if (strncmp(option, "flag", strlen(option)) == 0 && sep->arg[2] && sep->IsNumber(2) && sep->arg[3]) + guild->SetRecruitingFlag(atoi(sep->arg[2]), strncmp(sep->arg[3], "true", 4) == 0 ? 1 : 0); + else if (strncmp(option, "min_level", strlen(option)) == 0 && sep->arg[2] && sep->IsNumber(2)) + guild->SetRecruitingMinLevel(atoi(sep->arg[2])); + else if (strncmp(option, "playstyle", strlen(option)) == 0 && sep->arg[2] && sep->IsNumber(2)) + guild->SetRecruitingPlayStyle(atoi(sep->arg[2])); + else if (strncmp(option, "tag", strlen(option)) == 0 && sep->arg[2] && sep->IsNumber(2) && sep->arg[3] && sep->IsNumber(3)) + guild->SetRecruitingDescTag(atoi(sep->arg[2]), atoi(sep->arg[3])); + else if (strncmp(command, "recruiting", strlen(option)) == 0) + guild->ChangeMemberFlag(client, GUILD_MEMBER_FLAGS_RECRUITING_FOR_GUILD, strncmp(sep->arg[1], "true", 4) == 0 ? 1 : 0); + else + LogWrite(COMMAND__ERROR, 0, "Command", "Unhandled guild recruiting command: %s", sep->argplus[0]); + } + else + LogWrite(COMMAND__ERROR, 0, "Command", "Unhandled guild recruiting command: %s", sep->argplus[0]); + } + else if (strncmp(command, "notify_online", length) == 0 && sep->arg[1] && guild) + guild->ChangeMemberFlag(client, GUILD_MEMBER_FLAGS_NOTIFY_LOGINS, strncmp(sep->arg[1], "true", 4) == 0 ? 1 : 0); + else if (strncmp(command, "event_privacy", length) == 0 && sep->arg[1] && guild) + guild->ChangeMemberFlag(client, GUILD_MEMBER_FLAGS_DONT_GENERATE_EVENTS, strncmp(sep->arg[1], "true", 4) == 0 ? 1 : 0); + else if (strncmp(command, "recruiter", length) == 0 && sep->arg[1] && sep->arg[2] && guild) + guild->SetGuildRecruiter(client, sep->arg[1], strncmp(sep->arg[2], "true", 4) == 0 ? true : false); + else if (strncmp(command, "recruiter_description", length) == 0 && sep->arg[1] && guild) + guild->SetGuildRecruiterDescription(client, sep->argplus[1]); + else if (strncmp(command, "lock_event", length) == 0 && sep->arg[1] && sep->arg[2] && guild) + guild->LockGuildEvent(atoul(sep->arg[1]), strncmp(sep->arg[2], "true", 4) == 0 ? true : false); + else if (strncmp(command, "delete_event", length) == 0 && sep->arg[1] && guild) + guild->DeleteGuildEvent(atoul(sep->arg[1])); + else if (strncmp(command, "invite", length) == 0 && guild) + { + if (sep->arg[1] && strlen(sep->arg[1]) > 0) + guild->InvitePlayer(client, sep->arg[1]); + else + { + Spawn* target = client->GetPlayer()->GetTarget(); + if (target) + { + if (target->IsPlayer()) + guild->InvitePlayer(client, target->GetName()); + else + client->Message(CHANNEL_NARRATIVE, "%s is not a player.", target->GetName()); + } + } + } + else if (strncmp(command, "accept", length) == 0) + { + PendingGuildInvite* pgi = client->GetPendingGuildInvite(); + + if (pgi && pgi->guild && pgi->invited_by) + pgi->guild->AddNewGuildMember(client, pgi->invited_by->GetName()); + client->SetPendingGuildInvite(0); + } + else if (strncmp(command, "decline", length) == 0) + { + PendingGuildInvite* pgi = client->GetPendingGuildInvite(); + + if (pgi && pgi->guild && pgi->invited_by && pgi->invited_by->IsPlayer()) + { + Client* client_inviter = ((Player*)pgi->invited_by)->GetClient(); + + if (client_inviter) { + client_inviter->Message(CHANNEL_NARRATIVE, "%s has declined your invitation to join %s.", client->GetPlayer()->GetName(), pgi->guild->GetName()); + } + } + client->SetPendingGuildInvite(0); + } + else if (strncmp(command, "create", length) == 0 && sep->arg[1]) + { + const char* guild_name = sep->argplus[1]; + + if (!guild_list.GetGuild(guild_name)) + world.CreateGuild(guild_name, client, client->GetPlayer()->GetGroupMemberInfo() ? client->GetPlayer()->GetGroupMemberInfo()->group_id : 0); + else + client->SimpleMessage(CHANNEL_NARRATIVE, "A guild with that name already exists."); + } + else if (strncmp(command, "search", length) == 0) + client->ShowGuildSearchWindow(); + else if (strncmp(command, "recruiting_details", length) == 0 && sep->arg[1] && sep->IsNumber(1)) + { + Guild* to_guild = guild_list.GetGuild(atoul(sep->arg[1])); + + if (to_guild) + to_guild->SendGuildRecruitingDetails(client); + } + else if (strncmp(command, "recruiting_image", length) == 0 && sep->arg[1] && sep->IsNumber(1)) + { + Guild* to_guild = guild_list.GetGuild(atoul(sep->arg[1])); + + if (to_guild) + to_guild->SendGuildRecruitingImages(client); + } + else if (strncmp(command, "recruiter_adventure_class", length) == 0) + { + if (sep->arg[1]) + { + const char* option = sep->arg[1]; + + if (strncmp(option, "toggle", strlen(option)) == 0) + guild->ToggleGuildRecruiterAdventureClass(client); + } + else + LogWrite(COMMAND__ERROR, 0, "Command", "Unhandled guild recruiter_adventure_class command: '%s'", sep->argplus[0]); + } + else if (strncmp(command, "display_heraldry", length) == 0) + { + // JA: not sure this is right... + client->GetPlayer()->toggle_character_flag(CF_SHOW_GUILD_HERALDRY); + client->GetPlayer()->SetCharSheetChanged(true); + } + else + LogWrite(COMMAND__ERROR, 0, "Command", "Unhandled guild command: '%s'", sep->argplus[0]); + } +} + +/* + Function: Command_GuildCreate() + Purpose : Display's in-game Guild Creation window + Dev : Scatman +*/ +void Commands::Command_CreateGuild(Client* client) +{ + client->SendGuildCreateWindow(); +} + +/* + Function: Command_SetGuildOfficerNote() + Purpose : + Dev : Scatman +*/ +void Commands::Command_SetGuildOfficerNote(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0] && sep->arg[1]) + { + Guild* guild = client->GetPlayer()->GetGuild(); + + if (guild) + guild->SetGuildOfficerNote(sep->arg[0], sep->argplus[1]); + } +} + +/* + Function: Command_SetGuildMemberNote() + Purpose : + Dev : Scatman +*/ +void Commands::Command_SetGuildMemberNote(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0] && sep->arg[1]) + { + Guild* guild = client->GetPlayer()->GetGuild(); + + if (guild) + guild->SetGuildMemberNote(sep->arg[0], sep->argplus[1]); + } +} + +/* + Function: Command_GuildSay() + Purpose : + Dev : Scatman +*/ +void Commands::Command_GuildSay(Client* client, Seperator* sep) +{ + Guild* guild = client->GetPlayer()->GetGuild(); + + if (guild) + { + if (sep && sep->arg[0]) + guild->HandleGuildSay(client, sep->argplus[0]); + } + else + client->SimpleMessage(CHANNEL_NARRATIVE, "You are not a member of a guild"); +} + +/* + Function: Command_OfficerSay() + Purpose : + Dev : Scatman +*/ +void Commands::Command_OfficerSay(Client* client, Seperator* sep) +{ + Guild* guild = client->GetPlayer()->GetGuild(); + + if (guild) + { + if (sep && sep->arg[0]) + guild->HandleOfficerSay(client, sep->argplus[0]); + } + else + client->SimpleMessage(CHANNEL_NARRATIVE, "You are not a member of a guild"); +} + +/* + Function: Command_Guilds() + Purpose : Shows help for /guild command + Params : + Dev : Scatman + Example : +*/ +void Commands::Command_Guilds(Client* client) +{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /guilds [create|delete|add|remove|list]"); +} + +/* + Function: Command_GuildsAdd() + Purpose : Add's players to a guild + Params : guild_name|guild_id, player name + Dev : Scatman + Example : /guilds add 1 Admin = adds player Admin to guild_id 1 +*/ +void Commands::Command_GuildsAdd(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0] && strlen(sep->arg[0]) > 0) + { + Guild* guild = 0; + bool found = true; + + if (sep->IsNumber(0)) + { + guild = guild_list.GetGuild(atoul(sep->arg[0])); + + if (!guild) + { + client->Message(CHANNEL_COLOR_YELLOW, "Guild with ID %u does not exist.", atoul(sep->arg[0])); + found = false; + } + } + else + { + guild = guild_list.GetGuild(sep->arg[0]); + + if (!guild) + { + client->Message(CHANNEL_COLOR_YELLOW, "Guild '%s' does not exist.", sep->arg[0]); + found = false; + } + } + if (found) + { + Client* to_client = 0; + + if (sep->arg[1] && strlen(sep->arg[1]) > 0) + to_client = zone_list.GetClientByCharName(string(sep->arg[1])); + else if (client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsPlayer()) + to_client = ((Player*)client->GetPlayer()->GetTarget())->GetClient(); + + if (to_client) + guild->InvitePlayer(client, to_client->GetPlayer()->GetName()); + else + client->Message(CHANNEL_COLOR_YELLOW, "Could not find player '%s' to invite to the guild.", sep->arg[1]); + } + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /guilds add [guild name|guild id] (player name)."); +} + +/* + Function: Command_GuildsCreate() + Purpose : Creates a guild + Params : guild_name, player_name (optional) + Dev : Scatman + Example : /guilds create [guild name] (player name) +*/ +void Commands::Command_GuildsCreate(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0]) + { + const char* guild_name = sep->arg[0]; + + if (!guild_list.GetGuild(guild_name)) + { + bool ret = false; + + if (sep->arg[1] && strlen(sep->arg[1]) > 0) + { + Client* to_client = zone_list.GetClientByCharName(string(sep->arg[1])); + + if (to_client) + { + world.CreateGuild(guild_name, to_client); + ret = true; + } + else + client->Message(CHANNEL_COLOR_YELLOW, "Could not find player with name '%s'", sep->arg[1]); + } + else if (client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsPlayer()) + { + Client* to_client = ((Player*)client->GetPlayer()->GetTarget())->GetClient(); + + if (to_client) + { + world.CreateGuild(guild_name, to_client); + ret = true; + } + } + else + { + world.CreateGuild(guild_name); + ret = true; + } + + if (ret) + client->Message(CHANNEL_COLOR_YELLOW, "Guild '%s' was successfully created.", guild_name); + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "There was an error creating the guild."); + } + else + client->Message(CHANNEL_COLOR_YELLOW, "Guild '%s' already exists.", guild_name); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /guilds create [guild name] (player name). If no player is specified, the player's target will be used. If the player has no target, a guild with no members will be created."); +} + +/* + Function: Command_GuildsDelete() + Purpose : Delete's a guild + Params : guild name + Dev : Scatman + Example : /guilds delete Test +*/ +void Commands::Command_GuildsDelete(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0]) + { + Guild* guild = 0; + bool found = true; + + if (sep->IsNumber(0)) + { + guild = guild_list.GetGuild(atoul(sep->arg[0])); + + if (!guild) + { + client->Message(CHANNEL_COLOR_YELLOW, "Guild with ID %u does not exist.", atoul(sep->arg[0])); + found = false; + } + } + else + { + guild = guild_list.GetGuild(sep->arg[0]); + + if (!guild) + { + client->Message(CHANNEL_COLOR_YELLOW, "Guild '%s' does not exist.", sep->arg[0]); + found = false; + } + } + + if (found) + { + guild->RemoveAllGuildMembers(); + database.DeleteGuild(guild); + + client->Message(CHANNEL_COLOR_YELLOW, "Guild '%s' was successfully deleted.", guild->GetName()); + guild_list.RemoveGuild(guild, true); + } + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /guilds delete [guild name]."); +} + +/* + Function: Command_GuildsList() + Purpose : Lists current guilds on server + Params : + Dev : Scatman + Example : /guilds list +*/ +void Commands::Command_GuildsList(Client* client) +{ + MutexMap* guilds = guild_list.GetGuilds(); + MutexMap::iterator itr = guilds->begin(); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Guild List:"); + if (guilds->size() == 0) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "None."); + else { + while (itr.Next()) { + Guild* guild = itr.second; + client->Message(CHANNEL_COLOR_YELLOW, "%u) %s", guild->GetID(), guild->GetName()); + } + } +} + +/* + Function: Command_GuildsRemove() + Purpose : Removes a player from a guild + Params : guild name|guild id, player name + Dev : Scatman + Example : /guilds remove 1 Admin = removes Admin from guild 1 +*/ +void Commands::Command_GuildsRemove(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0] && strlen(sep->arg[0]) > 0) + { + Guild* guild = 0; + bool found = true; + + if (sep->IsNumber(0)) + { + guild = guild_list.GetGuild(atoul(sep->arg[0])); + + if (!guild) + { + client->Message(CHANNEL_COLOR_YELLOW, "Guild with ID %u does not exist.", atoul(sep->arg[0])); + found = false; + } + } + else + { + guild = guild_list.GetGuild(sep->arg[0]); + + if (!guild) + { + client->Message(CHANNEL_COLOR_YELLOW, "Guild '%s' does not exist.", sep->arg[0]); + found = false; + } + } + + if (found) + { + Client* to_client = 0; + + if (sep->arg[1] && strlen(sep->arg[1]) > 0) + to_client = zone_list.GetClientByCharName(string(sep->arg[1])); + else if (client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsPlayer()) + to_client = ((Player*)client->GetPlayer()->GetTarget())->GetClient(); + + if (to_client) + { + Player* to_player = to_client->GetPlayer(); + if (to_player->GetGuild()) + { + if (to_player->GetGuild() == guild) + { + guild->KickGuildMember(client, to_player->GetName()); + } + else + client->Message(CHANNEL_COLOR_YELLOW, "%s is not in the guild '%s'.", to_player->GetName(), guild->GetName()); + } + else + client->Message(CHANNEL_COLOR_YELLOW, "%s is not in a guild.", to_player->GetName()); + } + else + client->Message(CHANNEL_COLOR_YELLOW, "Could not find player '%s' to invite to the guild.", sep->arg[1]); + } + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /guilds remove [guild name|guild id] (player name)."); +} + +/* + Function: Command_InspectPlayer() + Purpose : Handle the Inspect functions + Params : Client to inspect + Dev : Scatman + Example : /inspect Scatman +*/ +void Commands::Command_InspectPlayer(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0]) + { + Client* inspect_client = zone_list.GetClientByCharName(string(sep->arg[0])); + + if (inspect_client) + client->InspectPlayer(inspect_client->GetPlayer()); + } + else + { + Spawn* target = client->GetPlayer()->GetTarget(); + + if (target && target->IsPlayer()) + client->InspectPlayer((Player*)target); + } +} + +/* + Function: Command_Inventory() + Purpose : Handle changes in player inventory + Params : + Dev : All + Example : /inventory delete item_id +*/ +void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteCommandString* command) +{ + PrintSep(sep, "Command_Inventory"); // temp to figure out the params + + Player* player = client->GetPlayer(); + + if(sep && sep->arg[0][0]) + { + LogWrite(COMMAND__INFO, 0, "Command", "command: %s", sep->argplus[0]); + + if(!client->GetPlayer()->Alive()) + client->SimpleMessage(CHANNEL_COLOR_RED,"You cannot do that right now."); + else if(sep->arg[1][0] && strncasecmp("destroy", sep->arg[0], 6) == 0 && sep->IsNumber(1)) + { + int16 index = atoi(sep->arg[1]); + Item* item = client->GetPlayer()->item_list.GetItemFromIndex(index); + + if(item) + { + if(item->details.item_locked) { + client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot destroy the item in use."); + return; + } + else if(item->IsBag() && item->details.equip_slot_id) { + client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot destroy the item, it is being used as quiver."); + return; + } + else if(item->CheckFlag(NO_DESTROY)) { + client->SimpleMessage(CHANNEL_COLOR_RED, "You can't destroy this item."); + return; + } + if(item->GetItemScript() && lua_interface) + lua_interface->RunItemScript(item->GetItemScript(), "destroyed", item, client->GetPlayer()); + + //reobtain item make sure it wasn't removed + item = player->item_list.GetItemFromIndex(index); + int32 bag_id = 0; + if(item){ + bag_id = item->details.inv_slot_id; + database.DeleteItem(client->GetCharacterID(), item, 0); + } + client->GetPlayer()->item_list.DestroyItem(index); + client->GetPlayer()->UpdateInventory(bag_id); + client->GetPlayer()->CalculateApplyWeight(); + } + } + else if(sep->arg[4][0] && strncasecmp("move", sep->arg[0], 4) == 0 && sep->IsNumber(1) && sep->IsNumber(2) && sep->IsNumber(3) && sep->IsNumber(4)) + { + int16 from_index = atoi(sep->arg[1]); + sint16 to_slot = atoi(sep->arg[2]); // don't convert slot since this is inventory not equipment + sint32 bag_id = atol(sep->arg[3]); + int8 charges = atoi(sep->arg[4]); + Item* item = client->GetPlayer()->item_list.GetItemFromIndex(from_index); + + if(!item) { + client->SimpleMessage(CHANNEL_COLOR_RED, "You have no item."); + return; + } + + if(to_slot == item->details.slot_id && (bag_id < 0 || bag_id == item->details.inv_slot_id)) { + return; + } + if(item->details.item_locked) + { + client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot move the item in use."); + return; + } + if(bag_id == -4 && !client->GetPlayer()->item_list.SharedBankAddAllowed(item)) + { + client->SimpleMessage(CHANNEL_COLOR_RED, "That item (or an item inside) cannot be shared."); + return; + } + + sint32 old_inventory_id = 0; + + if(item) + old_inventory_id = item->details.inv_slot_id; + + //autobank + if (bag_id == -3 && to_slot == -1) + { + if (player->HasFreeBankSlot()) + to_slot = player->FindFreeBankSlot(); + else + { + client->SimpleMessage(CHANNEL_COLOR_RED, "You do not have any free bank slots."); + return; + } + } + + //auto inventory + if (bag_id == 0 && to_slot == -1) + { + if (!player->item_list.GetFirstFreeSlot(&bag_id, &to_slot)) + { + client->SimpleMessage(CHANNEL_COLOR_RED, "You do not have any free slots."); + return; + } + } + + bool item_deleted = false; + EQ2Packet* outapp = client->GetPlayer()->MoveInventoryItem(bag_id, from_index, (int8)to_slot, charges, 0, &item_deleted, client->GetVersion()); + client->QueuePacket(outapp); + + if(item_deleted) + item = nullptr; + + //removed from bag send update + if(old_inventory_id > 0 && item && item->details.inv_slot_id != old_inventory_id) + { + outapp = client->GetPlayer()->SendBagUpdate(old_inventory_id, client->GetVersion()); + if(outapp) + client->QueuePacket(outapp); + } + + if(item && item->details.inv_slot_id > 0 && item->details.inv_slot_id != old_inventory_id) + { + outapp = client->GetPlayer()->SendBagUpdate(item->details.inv_slot_id, client->GetVersion()); + + if(outapp) + client->QueuePacket(outapp); + } + } + else if(sep->arg[1][0] && strncasecmp("equip", sep->arg[0], 5) == 0 && sep->IsNumber(1)) + { + int16 index = atoi(sep->arg[1]); + int8 slot_id = 255; + int8 unk3 = 0; + int8 appearance_equip = 0; + + if(sep->arg[2][0] && sep->IsNumber(2)) + slot_id = player->ConvertSlotFromClient(atoi(sep->arg[2]), client->GetVersion()); + if(sep->arg[3][0] && sep->IsNumber(3)) + unk3 = atoul(sep->arg[3]); + if(sep->arg[4][0] && sep->IsNumber(4)) + appearance_equip = atoul(sep->arg[4]); + + vector packets = client->GetPlayer()->EquipItem(index, client->GetVersion(), appearance_equip, slot_id); + EQ2Packet* outapp = 0; + + for(int32 i=0;iQueuePacket(outapp); + } + + client->GetPlayer()->UpdateWeapons(); + EQ2Packet* characterSheetPackets = client->GetPlayer()->GetPlayerInfo()->serialize(client->GetVersion()); + client->QueuePacket(characterSheetPackets); + } + else if (sep->arg[1][0] && strncasecmp("unpack", sep->arg[0], 6) == 0 && sep->IsNumber(1)) + { + if (client->GetPlayer()->EngagedInCombat()) + client->SimpleMessage(CHANNEL_COLOR_RED, "You may not unpack items while in combat."); + else { + int16 index = atoi(sep->arg[1]); + Item* item = client->GetPlayer()->item_list.GetItemFromIndex(index); + if (item) { + if(item->details.item_locked) + { + client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot unpack the item in use."); + return; + } + // client->GetPlayer()->item_list.DestroyItem(index); + if (item->item_sets.size() > 0) { + for (int32 i = 0; i < item->item_sets.size(); i++) { + ItemSet* set = item->item_sets[i]; + if (set->item_stack_size == 0) + set->item_stack_size += 1; + client->AddItem(set->item_id, set->item_stack_size); + } + } + + } + client->RemoveItem(item, 1); + + } + + } + else if(sep->arg[1][0] && strncasecmp("unequip", sep->arg[0], 7) == 0 && sep->IsNumber(1)) + { + int16 index = player->ConvertSlotFromClient(atoi(sep->arg[1]), client->GetVersion()); + sint32 bag_id = -999; + int8 to_slot = 255; + + if(sep->arg[3][0]) + { + if(sep->IsNumber(2)) + bag_id = atol(sep->arg[2]); + + if(sep->IsNumber(3)) + to_slot = atoi(sep->arg[3]); + } + + sint8 unk4 = 0; + int8 appearance_equip = 0; + if(sep->arg[4][0] && sep->IsNumber(4)) + unk4 = atoi(sep->arg[4]); + if(sep->arg[5][0] && sep->IsNumber(5)) + appearance_equip = atoul(sep->arg[5]); + + vector packets = client->GetPlayer()->UnequipItem(index, bag_id, to_slot, client->GetVersion(), appearance_equip); + EQ2Packet* outapp = 0; + + for(int32 i=0;iQueuePacket(outapp); + } + + client->UnequipItem(index, bag_id, to_slot, appearance_equip); + } + else if(sep->arg[2][0] && strncasecmp("swap_equip", sep->arg[0], 10) == 0 && sep->IsNumber(1) && sep->IsNumber(2)) + { + if(client->GetPlayer()->EngagedInCombat() && rule_manager.GetGlobalRule(R_Player, AllowPlayerEquipCombat)->GetInt8() == 0) { + client->SimpleMessage(CHANNEL_COLOR_RED, "You may not swap items while in combat."); + } + else { + int16 index1 = client->GetPlayer()->ConvertSlotFromClient(atoi(sep->arg[1]), client->GetVersion()); + int16 index2 = client->GetPlayer()->ConvertSlotFromClient(atoi(sep->arg[2]), client->GetVersion()); + int8 type = 0; + if(sep->IsNumber(3)) + type = atoul(sep->arg[3]); // type 0 is combat, 3 = appearance + + EQ2Packet* outapp = client->GetPlayer()->SwapEquippedItems(index1, index2, client->GetVersion(), type); + + if(outapp) + client->QueuePacket(outapp); + else + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Unable to swap items"); + return; + } + } + } + else if (sep->arg[2][0] && strncasecmp("pop", sep->arg[0], 3) == 0 && sep->IsNumber(1) && sep->IsNumber(2)) + { + sint16 to_slot = atoi(sep->arg[1]); + sint32 bag_id = atoi(sep->arg[2]); + Item* item = client->GetPlayer()->item_list.GetOverflowItem(); + if (item) { + //auto inventory + if (bag_id == 0 && to_slot == -1) + { + if (!player->item_list.GetFirstFreeSlot(&bag_id, &to_slot)) + { + client->SimpleMessage(CHANNEL_ERROR, "You do not have any free slots."); + return; + } + // Set the slot for the item + item->details.inv_slot_id = bag_id; + item->details.slot_id = to_slot; + // Flag the item so it gets saved in its new location + item->save_needed = true; + + // Add the item to its new location + if(player->item_list.AddItem(item)) { + // Remove the item from the overflow list + player->item_list.RemoveOverflowItem(item); + } + + // Send the inventory update packet + client->QueuePacket(player->item_list.serialize(player, client->GetVersion())); + return; + } + else if (bag_id == -3 && to_slot == -1) { + // Auto Bank + if (!player->item_list.GetFirstFreeBankSlot(&bag_id, &to_slot)) { + client->SimpleMessage(CHANNEL_STATUS, "You do not have any free bank slots."); + return; + } + item->details.inv_slot_id = bag_id; + item->details.slot_id = to_slot; + item->save_needed = true; + if(player->item_list.AddItem(item)) { + player->item_list.RemoveOverflowItem(item); + } + client->QueuePacket(player->item_list.serialize(player, client->GetVersion())); + } + else if (bag_id == -4) { + // Shared Bank + if (!player->item_list.SharedBankAddAllowed(item)) { + client->SimpleMessage(CHANNEL_STATUS, "That item (or an item inside) cannot be shared."); + return; + } + Item* tmp_item = player->item_list.GetItem(-4, to_slot); + if (tmp_item) { + client->SimpleMessage(CHANNEL_STATUS, "You can not place an overflow item into an occupied slot"); + return; + } + else { + item->details.inv_slot_id = bag_id; + item->details.slot_id = to_slot; + item->save_needed = true; + if(player->item_list.AddItem(item)) { + player->item_list.RemoveOverflowItem(item); + } + client->QueuePacket(player->item_list.serialize(player, client->GetVersion())); + return; + } + } + else { + // Try to get an item from the given bag id and slot id + Item* tmp_item = player->item_list.GetItem(bag_id, to_slot); + // Check to see if we got an item, if we do send an error, + // if we don't put the overflow item into this slot + if (tmp_item) { + client->SimpleMessage(CHANNEL_STATUS, "You can not place an overflow item into an occupied slot"); + } + else { + item->details.inv_slot_id = bag_id; + item->details.slot_id = to_slot; + item->save_needed = true; + if(player->item_list.AddItem(item)) { + player->item_list.RemoveOverflowItem(item); + } + client->QueuePacket(player->item_list.serialize(player, client->GetVersion())); + return; + } + } + } + } + else if(sep->arg[2][0] && strncasecmp("nosale", sep->arg[0], 6) == 0 && sep->IsNumber(1) && sep->IsNumber(2)) + { + sint64 data = strtoull(sep->arg[1], NULL, 0); + + int32 character_item_id = (int32) (data >> 32); + int32 item_id = (int32) (data & 0xffffffffL); + + int8 sale_setting = atoi(sep->arg[2]); + Item* item = client->GetPlayer()->item_list.GetItemFromUniqueID(character_item_id); + if(item) + { + item->no_sale = sale_setting; + item->save_needed = true; + client->SendSellMerchantList(); + } + } + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /inventory {destroy|move|equip|unequip|swap_equip|pop} {item_id} [to_slot] [bag_id]"); + +} + +/* + Function: Command_Languages() + Purpose : Show's languages the player knows + Params : + Dev : Zcoretri + Example : +*/ +void Commands::Command_Languages(Client* client, Seperator* sep) +{ + list* languages = client->GetPlayer()->GetPlayerLanguages()->GetAllLanguages(); + list::iterator itr; + Language* language; + client->Message(CHANNEL_NARRATIVE, "You know the following languages:"); + + for(itr = languages->begin(); itr != languages->end(); itr++) + { + language = *itr; + client->Message(CHANNEL_NARRATIVE, "%s", language->GetName()); + } +} + +/* + Function: Command_SetLanguage() + Purpose : Handles language commands + Params : Language ID + Dev : Zcoretri + Example : +*/ +void Commands::Command_SetLanguage(Client* client, Seperator* sep) +{ + Player* player = client->GetPlayer(); + + if (sep && sep->arg[0]) + { + if(!sep->IsNumber(0)) + { + //String passed in + const char* value = sep->arg[0]; + + if(strncasecmp(value, "Common", strlen(value)) == 0) + { + database.SaveCharacterCurrentLang(0, client->GetCharacterID(), client); + client->SendLanguagesUpdate(0); + client->Message(CHANNEL_NARRATIVE, "You are now speaking %s", value); + } + else + { + if(player->HasLanguage(value)) + { + Language* language = player->GetPlayerLanguages()->GetLanguageByName(value); + database.SaveCharacterCurrentLang(language->GetID(), client->GetCharacterID(), client); + client->SendLanguagesUpdate(language->GetID()); + client->Message(CHANNEL_NARRATIVE, "You are now speaking %s", language->GetName()); + } + else + client->Message(CHANNEL_NARRATIVE, "You do not know how to speak %s", value); + } + } + else + { + //Number passed in + int32 id = atoul(sep->arg[0]); + + if(player->HasLanguage(id)) + { + Language* language = player->GetPlayerLanguages()->GetLanguage(id); + database.SaveCharacterCurrentLang(id, client->GetCharacterID(), client); + client->SendLanguagesUpdate(id); + client->Message(CHANNEL_NARRATIVE, "You are now speaking %s", language->GetName()); + } + else + { + Language* language = master_languages_list.GetLanguage(id); + if(language) + client->Message(CHANNEL_NARRATIVE, "You do not know how to speak %s", language->GetName()); + } + } + } + else + { + //No value was passed in + int32 id = database.GetCharacterCurrentLang(client->GetCharacterID(), player); + + if(id > 0) + { + Language* language = player->GetPlayerLanguages()->GetLanguage(id); + client->Message(CHANNEL_NARRATIVE, "You are currently speaking %s ", language->GetName()); + } + else + client->Message(CHANNEL_NARRATIVE, "You are currently speaking Common"); + } +} + +/* + Function: Command_LastName() + Purpose : Sets player surname + Params : Name text + Dev : theFoof + Example : +*/ +void Commands::Command_LastName(Client* client, Seperator* sep) +{ + if (!client) + return; + + if (sep && sep->arg[0]) + { + if (!client->GetPlayer()->get_character_flag(CF_ENABLE_CHANGE_LASTNAME)){ + client->Message(CHANNEL_COLOR_YELLOW, "You must be atleast level %i to change your last name.", rule_manager.GetGlobalRule(R_Player, MinLastNameLevel)->GetInt8()); + return; + } + client->RemovePendingLastName(); + + uchar* checkname = (uchar*)sep->arg[0]; + bool valid_name = true; + for (int32 i = 0; i < strlen(sep->arg[0]); i++) { + if (!alpha_check(checkname[i])) { + valid_name = false; + break; + } + } + + if (!valid_name) { + client->Message(CHANNEL_COLOR_YELLOW, "Your last name can only contain letters.", rule_manager.GetGlobalRule(R_Player, MinLastNameLevel)->GetInt8()); + return; + } + + string last_name = (string)sep->arg[0]; + int8 max_length = rule_manager.GetGlobalRule(R_Player, MaxLastNameLength)->GetInt8(); + int8 min_length = rule_manager.GetGlobalRule(R_Player, MinLastNameLength)->GetInt8(); + if (last_name.length() <= max_length && last_name.length() >= min_length){ + client->SetPendingLastName(last_name); + client->SendLastNameConfirmation(); + } + else + client->Message(CHANNEL_COLOR_YELLOW, "Your last name must be between %i and %i characters long.", min_length, max_length); + } +} + +/* + Function: Command_ConfirmLastName() + Purpose : Confirms setting of player surname + Params : Name text + Dev : theFoof + Example : +*/ +void Commands::Command_ConfirmLastName(Client* client, Seperator* sep) +{ + if (!client) + return; + + string* name = client->GetPendingLastName(); + if (name){ + Player* player = client->GetPlayer(); + player->SetLastName(name->c_str(), false); + client->SendTitleUpdate(); + player->SetCharSheetChanged(true); + client->RemovePendingLastName(); + } +} + +/* + Function: Command_Location() + Purpose : Display's Help for /location commands + Params : + Dev : Scatman + Example : /location = show's help for command +*/ +void Commands::Command_Location(Client* client) +{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Valid /location commands are:"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/location create [name] (include y). Include y defaults to false"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/location add [location id]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/location remove [location point id]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/location delete [location id]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/location list [locations|points] [location id if points used]"); +} + +/* + Function: Command_LocationAdd() + Purpose : Add's a location to an existing location config + Params : location_id + Dev : Scatman + Example : /location add {location_id} +*/ +void Commands::Command_LocationAdd(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0] && sep->IsNumber(0)) + { + int32 location_id = atoul(sep->arg[0]); + float x = client->GetPlayer()->GetX(); + float y = client->GetPlayer()->GetY(); + float z = client->GetPlayer()->GetZ(); + + if (database.AddLocationPoint(location_id, x, y, z)) + client->Message(CHANNEL_COLOR_YELLOW, "Point (%f, %f, %f) was successfully added to location %u", x, y, z, location_id); + else + client->Message(CHANNEL_COLOR_YELLOW, "A location with ID %u does not exist. Use /location create to create one", location_id); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /location add [location id]"); +} + +/* + Function: Command_LocationCreate() + Purpose : Creates a new location config + Params : location name, 0/1 for include_y (optional) + Dev : Scatman + Example : /location create Test 1 = creates a new location named Test with include_y True +*/ +void Commands::Command_LocationCreate(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0] && strlen(sep->arg[0]) > 0) + { + const char* name = sep->arg[0]; + bool include_y = false; + + if (sep->arg[1] && sep->IsNumber(1) && atoi(sep->arg[1]) > 0) + include_y = true; + + int32 location_id = database.CreateLocation(client->GetPlayer()->GetZone()->GetZoneID(), client->GetPlayer()->GetLocation(), name, include_y); + + if (location_id > 0) + client->Message(CHANNEL_COLOR_YELLOW, "Location '%s' was successfully created with location id %u", name, location_id); + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "There was an error creating the requested location"); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /location create [name] (include_y). Include y defaults to false"); +} + +/* + Function: Command_LocationDelete() + Purpose : Delete's a location config and all it's location points + Params : location_id + Dev : Scatman + Example : /location delete {location_id} +*/ +void Commands::Command_LocationDelete(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0] && sep->IsNumber(0)) + { + int32 location_id = atoul(sep->arg[0]); + + if (database.DeleteLocation(location_id)) + client->Message(CHANNEL_COLOR_YELLOW, "Location id %u and all its points were successfully deleted", location_id); + else + client->Message(CHANNEL_COLOR_YELLOW, "A location with ID %u does not exist", location_id); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /location delete [location id]"); +} + +/* + Function: Command_LocationList() + Purpose : Display's a list of location points + Params : location_id + Dev : Scatman + Example : /location list {location_id} +*/ +void Commands::Command_LocationList(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0]) + { + const char* option = sep->arg[0]; + + if (strncmp(option, "locations", strlen(option)) == 0) + database.ListLocations(client); + else if (strncmp(option, "points", strlen(option)) == 0 && sep->arg[1] && sep->IsNumber(1)) + { + int32 location_id = atoul(sep->arg[1]); + database.ListLocationPoints(client, location_id); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Useage: /location list [locations|points] [location ID if points used]"); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Useage: /location list [locations|points] [location ID if points used]"); +} + +/* + Function: Command_LocationRemove() + Purpose : Removes a single location point from a location config + Params : location_point_id (gotten from /location list {id}) + Dev : Scatman + Example : /location remove 1 = will remove location_point_id 1 +*/ +void Commands::Command_LocationRemove(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0] && sep->IsNumber(0)) + { + int32 location_point_id = atoul(sep->arg[0]); + + if (database.DeleteLocationPoint(location_point_id)) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Location point was successfully deleted"); + else + client->Message(CHANNEL_COLOR_YELLOW, "Location point with ID %u does not exist", location_point_id); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /location remove [location point id]"); +} + +/* + Function: Command_Merchant() + Purpose : Handles Merchant commands + Params : list + Dev : Scatman + Example : /merchant list +*/ +void Commands::Command_Merchant(Client* client, Seperator* sep, int handler) +{ + if( handler == COMMAND_MERCHANT ) + { + // /merchant command by itself shows help (to be extended) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /merchant list [merchant description]"); + return; + } + else if( handler == COMMAND_MERCHANT_LIST ) + { + // /merchant list command expects "description" param + if (sep && sep->arg[0]) + { + const char* merchant_description = sep->argplus[0]; + client->Message(CHANNEL_COLOR_YELLOW, "Listing merchants like '%s':", merchant_description); + map* merchant_info = world.GetMerchantInfo(); + map::iterator itr; + + for (itr = merchant_info->begin(); itr != merchant_info->end(); itr++) + { + string description = database.GetMerchantDescription(itr->first); + + if (ToLower(description).find(ToLower(string(merchant_description))) < 0xFFFFFFFF) + client->Message(CHANNEL_COLOR_YELLOW, "Merchant ID: %u, Description: %s", itr->first, description.c_str()); + } + } + else // no description + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /merchant list [merchant description]"); + } +} + +/* + Function: Command_Modify() + Purpose : to replace our other "set" commands + Params : System = the system to modify, Action = the action to perform, Target = what to change (target, or index) + Dev : John Adams + Example : /modify spell set name "Aegolism III" +*/ +void Commands::Command_Modify(Client* client) +{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /modify [system] [action] [field] [value] {target|id}"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Systems: character, faction, guild, item, skill, spawn, spell, zone"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Actions: set, create, delete, add, remove"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Value : field name in the table being modified"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Target : modify current target, or specified {system} ID"); +} + + +/* + Function: Command_ModifySpawn() + Purpose : replace "spawn set" commands + Params : Action : the action to perform (or special handler) + : Field : the DB field to change + : Value : what to set the value to + : Target : what object to change (my target, or spawn_id) + Dev : John Adams + Example : /modify spawn set name "Lady Vox" + : + Note : Special Handlers, like "zoneto" for Signs + : /modify spawn zoneto + : Will set a sign's zone x/y/z to my current coords +*/ +void Commands::Command_ModifySpawn(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_MODIFY"); + + // need at least 2 args for a valid command + if( sep && sep->arg[1] ) + { + // JA: just a quick implementation because I need it :) + if (strcmp(sep->arg[0], "zoneto") == 0) + { + if( sep->IsNumber(1) ) + { + int32 spawn_id = atoul(sep->arg[1]); + float x_coord = client->GetPlayer()->GetX(); + float y_coord = client->GetPlayer()->GetY(); + float z_coord = client->GetPlayer()->GetZ(); + float h_coord = client->GetPlayer()->GetHeading(); + + database.SaveSignZoneToCoords(spawn_id, x_coord, y_coord, z_coord, h_coord); + } + else + client->SimpleMessage(CHANNEL_COLOR_RED, "Usage: /modify spawn zoneto spawn_id - sets spawn_id to your coords"); + } + } + else + Command_Modify(client); +} + +/* + Function: Command_ModifyCharacter() + Purpose : to replace our other "set" commands + Params : Action : Add, Remove + : Field : copper, silver, gold, plat + : Value : min 1, max unlimited + : Target : Self, Player + Dev : Cynnar + Example : /modify character add gold 50 + : /modify character remove silver 25 +*/ +void Commands::Command_ModifyCharacter(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_MODIFY"); + + int64 value = 0; + + Player* player = client->GetPlayer(); + Client* targetClient = client; + if (player->HasTarget() && player->GetTarget()->IsPlayer()) { + player = (Player*)player->GetTarget(); + targetClient = player->GetClient(); + } + + // need at least 2 args for a valid command + if( sep && sep->arg[1] ) + + { + if (strcmp(sep->arg[0], "add") == 0) + + { + if (strcmp(sep->arg[1], "copper") == 0) + { + value = atoi64(sep->arg[2]); + player->AddCoins(value); + if (client->GetPlayer() == player) + { + client->Message(CHANNEL_COLOR_YELLOW, "You give yourself %llu copper coin%s", value, (value > 1 ? "s" : "")); + } + else { + client->Message(CHANNEL_COLOR_YELLOW, "You give %s %llu copper coin%s", player->GetName(), value, (value > 1 ? "s" : "")); + if(targetClient) { + targetClient->Message(CHANNEL_COLOR_YELLOW, "%s gave you %llu copper coin%s", client->GetPlayer()->GetName(), value, (value > 1 ? "s" : "")); + } + } + } + + else if (strcmp(sep->arg[1], "silver") == 0) + { + value = atoi64(sep->arg[2]) * 100; + player->AddCoins(value); + if (client->GetPlayer() == player) + { + client->Message(CHANNEL_COLOR_YELLOW, "You give yourself %llu silver coin%s", value / 100, (value > 1 ? "s" : "")); + } + else { + client->Message(CHANNEL_COLOR_YELLOW, "You give %s %llu silver coin%s", player->GetName(), value / 100, (value > 1 ? "s" : "")); + if(targetClient) { + targetClient->Message(CHANNEL_COLOR_YELLOW, "%s gave you %llu silver coin%s", client->GetPlayer()->GetName(), value / 100, (value > 1 ? "s" : "")); + } + } + } + + else if (strcmp(sep->arg[1], "gold") == 0) + { + value = atoi64(sep->arg[2]) * 10000; + player->AddCoins(value); + if (client->GetPlayer() == player) + { + client->Message(CHANNEL_COLOR_YELLOW, "You give yourself %llu gold coin%s", value / 10000, (value > 1 ? "s" : "")); + } + else { + client->Message(CHANNEL_COLOR_YELLOW, "You give %s %llu gold coin%s", player->GetName(), value / 10000, (value > 1 ? "s" : "")); + if(targetClient) { + targetClient->Message(CHANNEL_COLOR_YELLOW, "%s gave you %llu gold coin%s", client->GetPlayer()->GetName(), value / 10000, (value > 1 ? "s" : "")); + } + } + } + + else if (strcmp(sep->arg[1], "plat") == 0) + { + value = atoi64(sep->arg[2]) * 1000000; + player->AddCoins(value); + if (client->GetPlayer() == player) + { + client->Message(CHANNEL_COLOR_YELLOW, "You give yourself %llu platinum coin%s", value / 1000000, (value > 1 ? "s" : "")); + } + else { + client->Message(CHANNEL_COLOR_YELLOW, "You give %s %llu platinum coin%s", player->GetName(), value / 1000000, (value > 1 ? "s" : "")); + if(targetClient) { + targetClient->Message(CHANNEL_COLOR_YELLOW, "%s gave you %llu platinum coin%s", client->GetPlayer()->GetName(), value / 1000000, (value > 1 ? "s" : "")); + } + } + } + + else + { + client->SimpleMessage(CHANNEL_COLOR_RED, "Usage: /modify character [action] [field] [value]"); + client->SimpleMessage(CHANNEL_COLOR_RED, "Actions: add, remove"); + client->SimpleMessage(CHANNEL_COLOR_RED, "Value : copper, silver, gold, plat"); + client->SimpleMessage(CHANNEL_COLOR_RED, "Example: /modify character add copper 20"); + } + } + + else if (strcmp(sep->arg[0], "remove") == 0) + { + if (strcmp(sep->arg[1], "copper") == 0) + { + value = atoi64(sep->arg[2]); + player->RemoveCoins(value); + if (client->GetPlayer() == player) + { + client->Message(CHANNEL_COLOR_YELLOW, "You take %llu copper coin%s from yourself", value, (value > 1 ? "s" : "")); + } + else { + client->Message(CHANNEL_COLOR_YELLOW, "You take %llu copper coin%s from %s", value, (value > 1 ? "s" : ""), player->GetName()); + if(targetClient) { + targetClient->Message(CHANNEL_COLOR_YELLOW, "%s takes %llu copper coin%s from you", client->GetPlayer()->GetName(), value, (value > 1 ? "s" : "")); + } + } + } + + else if (strcmp(sep->arg[1], "silver") == 0) + { + value = atoi64(sep->arg[2]) * 100; + player->RemoveCoins(value); + if (client->GetPlayer() == player) + { + client->Message(CHANNEL_COLOR_YELLOW, "You take %llu silver coin%s from yourself", value / 100, (value > 1 ? "s" : "")); + } + else { + client->Message(CHANNEL_COLOR_YELLOW, "You take %llu silver coin%s from %s", value / 100, (value > 1 ? "s" : ""), player->GetName()); + if(targetClient) { + targetClient->Message(CHANNEL_COLOR_YELLOW, "%s takes %llu silver coin%s from you", client->GetPlayer()->GetName(), value / 100, (value > 1 ? "s" : "")); + } + } + } + + else if (strcmp(sep->arg[1], "gold") == 0) + { + value = atoi64(sep->arg[2]) * 10000; + player->RemoveCoins(value); + if (client->GetPlayer() == player) + { + client->Message(CHANNEL_COLOR_YELLOW, "You take %llu gold coin%s from yourself", value / 10000, (value > 1 ? "s" : "")); + } + else { + client->Message(CHANNEL_COLOR_YELLOW, "You take %llu gold coin%s from %s", value / 10000, (value > 1 ? "s" : ""), player->GetName()); + if(targetClient) { + targetClient->Message(CHANNEL_COLOR_YELLOW, "%s takes %llu gold coin%s from you", client->GetPlayer()->GetName(), value / 10000, (value > 1 ? "s" : "")); + } + } + } + + else if (strcmp(sep->arg[1], "plat") == 0) + { + value = atoi64(sep->arg[2]) * 1000000; + player->RemoveCoins(value); + if (client->GetPlayer() == player) + { + client->Message(CHANNEL_COLOR_YELLOW, "You take %llu platinum coin%s from yourself", value / 1000000, (value > 1 ? "s" : "")); + } + else { + client->Message(CHANNEL_COLOR_YELLOW, "You take %llu platinum coin%s from %s", value / 1000000, (value > 1 ? "s" : ""), player->GetName()); + if(targetClient) { + targetClient->Message(CHANNEL_COLOR_YELLOW, "%s takes %llu platinum coin%s from you", client->GetPlayer()->GetName(), value / 1000000, (value > 1 ? "s" : "")); + } + } + } + + else + { + client->SimpleMessage(CHANNEL_COLOR_RED, "Example: /modify character remove gold 15"); + + } + } + + else if (strcmp(sep->arg[0], "set") == 0) { + + if (strcmp(sep->arg[1], "tslevel") == 0) { + int8 level = atoi(sep->arg[2]); + + if (level > 0 && level < 256) { + if (player) { + if (client->GetPlayer() == player) + client->ChangeTSLevel(player->GetTSLevel(), level); + else if(targetClient) + targetClient->ChangeTSLevel(player->GetTSLevel(), level); + } + } + else + client->SimpleMessage(CHANNEL_ERROR, "Level must be between 1 - 255"); + } + + else if (strcmp(sep->arg[1], "tsclass") == 0) { + int8 tsclass = atoi(sep->arg[2]); + + player->SetTradeskillClass(tsclass); + player->GetInfoStruct()->set_tradeskill_class1(classes.GetTSBaseClass(player->GetTradeskillClass())); + player->GetInfoStruct()->set_tradeskill_class2(classes.GetSecondaryTSBaseClass(player->GetTradeskillClass())); + player->GetInfoStruct()->set_tradeskill_class3(player->GetTradeskillClass()); + player->SetCharSheetChanged(true); + } + else if (strcmp(sep->arg[1], "gender") == 0) { + int8 gender = atoi(sep->arg[2]); + client->GetPlayer()->GetInfoStruct()->set_gender(gender); + client->GetPlayer()->SetCharSheetChanged(true); + client->UpdateTimeStampFlag ( GENDER_UPDATE_FLAG ); + } + } + } + else + { + client->SimpleMessage(CHANNEL_COLOR_RED, "Usage: /modify character [action] [field] [value]"); + client->SimpleMessage(CHANNEL_COLOR_RED, "Actions: add, remove"); + client->SimpleMessage(CHANNEL_COLOR_RED, "Value : copper, silver, gold, plat"); + client->SimpleMessage(CHANNEL_COLOR_RED, "Example: /modify character remove gold 15"); + } + +} + + +void Commands::Command_ModifyFaction(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_MODIFY"); + + // need at least 2 args for a valid command + if( sep && sep->arg[1] ) + { + } + else + Command_Modify(client); +} + + +void Commands::Command_ModifyGuild(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_MODIFY"); + + // need at least 2 args for a valid command + if( sep && sep->arg[1] ) + { + } + else + Command_Modify(client); +} + + +void Commands::Command_ModifyItem(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_MODIFY"); + + // need at least 2 args for a valid command + if( sep && sep->arg[1] ) + { + } + else + Command_Modify(client); +} + +/* + Function: Command_ModifyQuest() + Purpose : to list players quest and completed quests + Params : Action : list, completed + : Target : Self, Player + Dev : Cynnar + Example : /modify quest list + : /modify quest completed +*/ + +void Commands::Command_ModifyQuest(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_MODIFY"); + + int64 value = 0; + + Player* player = client->GetPlayer(); + Client* targetClient = client; + if (player->HasTarget() && player->GetTarget()->IsPlayer()) { + player = (Player*)player->GetTarget(); + targetClient = player->GetClient(); + } + + // need at least 2 args for a valid command + + if (sep && sep->arg[1]) + { + + if (strcmp(sep->arg[0], "list") == 0) + { + map* quests = player->GetPlayerQuests(); + map::iterator itr; + client->Message(CHANNEL_COLOR_YELLOW, "%s's Quest List:.", client->GetPlayer()->GetName()); + if (quests->size() == 0) + client->Message(CHANNEL_COLOR_YELLOW, "%s has no quests.", client->GetPlayer()->GetName()); + else + { + for (itr = quests->begin(); itr != quests->end(); itr++) + { + Quest* quest = itr->second; + if(quest) { + client->Message(CHANNEL_COLOR_YELLOW, "%u) %s", itr->first, quest->GetName()); + } + } + } + + } + + else if (strcmp(sep->arg[0], "completed") == 0) + { + + map* quests = player->GetCompletedPlayerQuests(); + map::iterator itr; + client->Message(CHANNEL_COLOR_YELLOW, "%s's Completed Quest List:.", client->GetPlayer()->GetName()); + if (quests->size() == 0) + client->Message(CHANNEL_COLOR_YELLOW, "%s has no completed quests.", client->GetPlayer()->GetName()); + else + { + for (itr = quests->begin(); itr != quests->end(); itr++) + { + Quest* quest = itr->second; + if(quest) { + client->Message(CHANNEL_COLOR_YELLOW, "%u) %s", itr->first, quest->GetName()); + } + } + } + + } + // Add in a progress step, and a LogWrite() for tracking GM Commands. + // LogWrite(LUA__DEBUG, 0, "LUA", "Quest: %s, function: %s", quest->GetName(), function); + else if (strcmp(sep->arg[0], "remove") == 0) + { + int32 quest_id = 0; + + if (sep && sep->arg[1] && sep->IsNumber(1)) + quest_id = atoul(sep->arg[1]); + + if (quest_id > 0) + { + if (lua_interface && client->GetPlayer()->player_quests.count(quest_id) > 0) + { + Quest* quest = client->GetPlayer()->player_quests[quest_id]; + if (quest) + if (client->GetPlayer() == player) + { + client->Message(CHANNEL_COLOR_YELLOW, "The quest %s has been removed from your journal", quest->GetName()); + } + else + { + client->Message(CHANNEL_COLOR_YELLOW, "You have removed the quest %s from %s's journal", quest->GetName(), player->GetName()); + if(targetClient) { + targetClient->Message(CHANNEL_COLOR_YELLOW, "%s has removed the quest %s from your journal", client->GetPlayer()->GetName(), quest->GetName()); + } + } + LogWrite(COMMAND__INFO, 0, "GM Command", "%s removed the quest %s from %s", client->GetPlayer()->GetName(), quest->GetName(), player->GetName()); + lua_interface->CallQuestFunction(quest, "Deleted", client->GetPlayer()); + } + client->RemovePlayerQuest(quest_id); + client->GetCurrentZone()->SendQuestUpdates(client); + } + } + + else if (strcmp(sep->arg[0], "advance") == 0) + { + int32 quest_id = 0; + int32 step = 0; + + if (sep && sep->arg[1] && sep->IsNumber(1)) + { + quest_id = atoul(sep->arg[1]); + Quest* quest = client->GetPlayer()->player_quests[quest_id]; + + if(!quest) { + client->Message(CHANNEL_COLOR_RED, "Quest not found!"); + return; + } + if (sep && sep->arg[2] && sep->IsNumber(1)) + { + step = atoul(sep->arg[2]); + + if (quest_id > 0 && step > 0) + { + if (player && player->IsPlayer() && quest_id > 0 && step > 0 && (player->player_quests.count(quest_id) > 0)) + { + if (client) + { + client->AddPendingQuestUpdate(quest_id, step); + client->Message(CHANNEL_COLOR_YELLOW, "The quest %s has been advanced one step.", quest->GetName()); + LogWrite(COMMAND__INFO, 0, "GM Command", "%s advanced the quest %s one step", client->GetPlayer()->GetName(), quest->GetName()); + } + } + } + else + { + client->Message(CHANNEL_COLOR_RED, "Quest ID and Step Number must be greater than 0!"); + } + } + else + { + client->Message(CHANNEL_COLOR_RED, "Step Number must be a number!"); + } + } + else + { + client->Message(CHANNEL_COLOR_RED, "Quest ID must be a number!"); + } + } + + else + { + Command_Modify(client); + } + } + + else + { + client->SimpleMessage(CHANNEL_COLOR_RED, "Usage: /modify quest [action] [quest id] [step number]"); + client->SimpleMessage(CHANNEL_COLOR_RED, "Actions: list, completed, remove, advance"); + client->SimpleMessage(CHANNEL_COLOR_RED, "Example: /modify quest list"); + client->SimpleMessage(CHANNEL_COLOR_RED, "Example: /modify quest remove 156"); + client->SimpleMessage(CHANNEL_COLOR_RED, "Example: /modify quest advance 50 1"); + } + +} + + +void Commands::Command_ModifySkill(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_MODIFY"); + + // need at least 2 args for a valid command + if( sep && sep->arg[1] ) + { + if (strcmp(sep->arg[0], "add") == 0) { + const char* skill_name = sep->argplus[1]; + Skill* skill = master_skill_list.GetSkillByName(skill_name); + + if (skill) { + Player* player = 0; + Client* to_client = 0; + + if (client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsPlayer()) { + player = (Player*)client->GetPlayer()->GetTarget(); + to_client = player->GetClient(); + } + else { + player = client->GetPlayer(); + to_client = client; + } + + if (player) + { + if (!player->GetSkills()->HasSkill(skill->skill_id)) + { + player->AddSkill(skill->skill_id, 1, player->GetLevel() * 5, true); + if (to_client == client) { + client->Message(CHANNEL_STATUS, "Added skill '%s'.", skill_name); + } + else { + client->Message(CHANNEL_STATUS, "You gave skill '%s' to player '%s'.", skill_name, player->GetName()); + to_client->Message(CHANNEL_STATUS, "%s gave you skill '%s'.", client->GetPlayer()->GetName(), skill_name); + } + } + else + client->Message(CHANNEL_STATUS, "%s already has the skill '%s'.", to_client == client ? "You" : player->GetName(), skill_name); + } + } + else + client->Message(CHANNEL_STATUS, "The skill '%s' does not exist.", skill_name); + } + else if (strcmp(sep->arg[0], "remove") == 0) { + const char* skill_name = sep->argplus[1]; + Skill* skill = master_skill_list.GetSkillByName(skill_name); + + if (skill) { + Player* player = 0; + Client* to_client = 0; + + if (client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsPlayer()) { + player = (Player*)client->GetPlayer()->GetTarget(); + to_client = player->GetClient(); + } + else { + player = client->GetPlayer(); + to_client = client; + } + + if (player) + { + if (player->GetSkills()->HasSkill(skill->skill_id)) + { + player->RemoveSkillFromDB(skill, true); + if (client == to_client) { + client->Message(CHANNEL_STATUS, "Removed skill '%s'.", skill_name); + } + else { + client->Message(CHANNEL_STATUS, "Removed skill '%s' from player %s.", skill_name, player->GetName()); + if(to_client) { + to_client->Message(CHANNEL_STATUS, "%s has removed skill '%s' from you.", client->GetPlayer()->GetName(), skill_name); + } + } + } + else { + if (client == to_client) + client->Message(CHANNEL_STATUS, "You do not have the skill '%s'.", skill_name); + else + client->Message(CHANNEL_STATUS, "Player '%s' does not have the skill '%s'.", player->GetName(), skill_name); + } + } + } + else + client->Message(CHANNEL_STATUS, "The skill '%s' does not exist.", skill_name); + } + else if (strcmp(sep->arg[0], "set") == 0) { + if (!sep->IsNumber(2)) { + client->Message(CHANNEL_STATUS, "The last parameter must be a number."); + return; + } + + const char* skill_name = sep->arg[1]; + Skill* skill = master_skill_list.GetSkillByName(skill_name); + if (skill) { + int16 val = atoi(sep->arg[2]); + Player* player = 0; + Client* to_client = 0; + + if (client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsPlayer()) { + player = (Player*)client->GetPlayer()->GetTarget(); + to_client = player->GetClient(); + } + else { + player = client->GetPlayer(); + to_client = client; + } + + if (player) + { + if (player->GetSkills()->HasSkill(skill->skill_id)) { + player->GetSkills()->SetSkill(skill->skill_id, val); + if (client != to_client) + client->Message(CHANNEL_STATUS, "You set %s's '%s' skill to %i.", player->GetName(), skill_name, val); + if(to_client) { + to_client->Message(CHANNEL_STATUS, "Your '%s' skill has been set to %i.", skill_name, val); + } + } + else { + client->Message(CHANNEL_STATUS, "Target does not have the skill '%s'.", skill_name); + } + } + } + else { + client->Message(CHANNEL_STATUS, "The skill '%s' does not exist.", skill_name); + } + } + else { + client->SimpleMessage(CHANNEL_STATUS, "Usage: /modify skill [action] [skill]"); + client->SimpleMessage(CHANNEL_STATUS, "Actions: add, remove, set"); + client->SimpleMessage(CHANNEL_STATUS, "Example: /modify skill add parry"); + } + } + else { + client->SimpleMessage(CHANNEL_STATUS, "Usage: /modify skill [action] [skill]"); + client->SimpleMessage(CHANNEL_STATUS, "Actions: add, remove, set"); + client->SimpleMessage(CHANNEL_STATUS, "Example: /modify skill add parry"); + } +} + + +void Commands::Command_ModifySpell(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_MODIFY"); + + // need at least 2 args for a valid command + if( sep && sep->arg[1] ) + { + } + else + Command_Modify(client); +} + + +void Commands::Command_ModifyZone(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_MODIFY"); + + // need at least 2 args for a valid command + if( sep && sep->arg[1] ) + { + } + else + Command_Modify(client); +} + + +/* + Function: Command_MOTD() + Purpose : Displays server MOTD + Params : + Dev : LethalEncounter + Example : /motd +*/ +void Commands::Command_MOTD(Client* client) +{ + if (client) + ClientPacketFunctions::SendMOTD(client); +} + +/* + Function: Command_Pet() + Purpose : Handle Pet {Command} commands + Params : attack, backoff, preserve_master, preserve_self, follow, stay, getlost + Dev : + Example : /pet preserve_master +*/ +void Commands::Command_Pet(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_PET"); + //LogWrite(MISC__TODO, 1, "Command", "TODO-Command: Pet Commands"); + //client->Message(CHANNEL_COLOR_YELLOW, "Pets are not yet implemented."); + + + if (!sep || !sep->arg[0]) + return; // should have sep->arg[0] filled + + if (strcmp(sep->arg[0], "hide") == 0) { + // doing /pet hide will toggle the hide status on all the pets that can be hidden + Entity* pet = client->GetPlayer()->GetDeityPet(); + if (pet) { + if (pet->IsPrivateSpawn()) + client->GetPlayer()->HideDeityPet(false); + else + client->GetPlayer()->HideDeityPet(true); + } + + pet = client->GetPlayer()->GetCosmeticPet(); + if (pet) { + if (pet->IsPrivateSpawn()) + client->GetPlayer()->HideCosmeticPet(false); + else + client->GetPlayer()->HideCosmeticPet(true); + } + + return; + } + + // below is for all combat pets + if (!client->GetPlayer()->HasPet()) { + client->Message(CHANNEL_COLOR_YELLOW, "You do not have a pet."); + return; + } + + if (strcmp(sep->arg[0], "stay") == 0 || strcmp(sep->arg[0], "stayhere") == 0) { + client->Message(CHANNEL_COLOR_YELLOW, "You command your pet to stay."); + client->GetPlayer()->GetInfoStruct()->set_pet_movement(1); + client->GetPlayer()->SetCharSheetChanged(true); + if (client->GetPlayer()->GetPet()) + client->GetPlayer()->GetPet()->following = false; + if (client->GetPlayer()->GetCharmedPet()) + client->GetPlayer()->GetCharmedPet()->following = false; + } + else if (strcmp(sep->arg[0], "follow") == 0 || strcmp(sep->arg[0], "followme") == 0) { + client->Message(CHANNEL_COLOR_YELLOW, "You command your pet to follow."); + client->GetPlayer()->GetInfoStruct()->set_pet_movement(2); + client->GetPlayer()->SetCharSheetChanged(true); + } + else if (strcmp(sep->arg[0], "preserve_master") == 0 || strcmp(sep->arg[0], "guardme") == 0) { + if (client->GetPlayer()->GetInfoStruct()->get_pet_behavior() & 1) { + client->Message(CHANNEL_COLOR_YELLOW, "Your pet will no longer protect you."); + client->GetPlayer()->GetInfoStruct()->set_pet_behavior(client->GetPlayer()->GetInfoStruct()->get_pet_behavior()-1); + } + else { + client->Message(CHANNEL_COLOR_YELLOW, "You command your pet to protect you."); + client->GetPlayer()->GetInfoStruct()->set_pet_behavior(client->GetPlayer()->GetInfoStruct()->get_pet_behavior()+1); + } + client->GetPlayer()->SetCharSheetChanged(true); + } + else if (strcmp(sep->arg[0], "preserve_self") == 0 || strcmp(sep->arg[0], "guardhere") == 0) { // guardhere might be accurate, diff logic + if (client->GetPlayer()->GetInfoStruct()->get_pet_behavior() & 2) { + client->Message(CHANNEL_COLOR_YELLOW, "Your pet will no longer protect itself."); + client->GetPlayer()->GetInfoStruct()->set_pet_behavior(client->GetPlayer()->GetInfoStruct()->get_pet_behavior()-2); + } + else { + client->Message(CHANNEL_COLOR_YELLOW, "You command your pet to protect itself."); + client->GetPlayer()->GetInfoStruct()->set_pet_behavior(client->GetPlayer()->GetInfoStruct()->get_pet_behavior()+2); + } + client->GetPlayer()->SetCharSheetChanged(true); + } + else if (strcmp(sep->arg[0], "backoff") == 0) { + client->Message(CHANNEL_COLOR_YELLOW, "You command your pet to back down."); + if (client->GetPlayer()->GetPet()) + ((NPC*)client->GetPlayer()->GetPet())->Brain()->ClearHate(); + if (client->GetPlayer()->GetCharmedPet()) + ((NPC*)client->GetPlayer()->GetCharmedPet())->Brain()->ClearHate(); + client->GetPlayer()->GetInfoStruct()->set_pet_behavior(0); + client->GetPlayer()->SetCharSheetChanged(true); + } + else if (strcmp(sep->arg[0], "attack") == 0) { + if (client->GetPlayer()->HasTarget() && client->GetPlayer()->GetTarget()->IsEntity()) { + if (client->GetPlayer()->AttackAllowed((Entity*)client->GetPlayer()->GetTarget())){ + client->Message(CHANNEL_COLOR_YELLOW, "You command your pet to attack your target."); + if (client->GetPlayer()->GetPet()) + client->GetPlayer()->GetPet()->AddHate((Entity*)client->GetPlayer()->GetTarget(), 1); + if (client->GetPlayer()->GetCharmedPet()) + client->GetPlayer()->GetCharmedPet()->AddHate((Entity*)client->GetPlayer()->GetTarget(), 1); + } + else + client->Message(CHANNEL_COLOR_YELLOW, "You can not attack that."); + } + else + client->Message(CHANNEL_COLOR_YELLOW, "You do not have a target."); + + } + else if (strcmp(sep->arg[0], "getlost") == 0) { + client->GetPlayer()->DismissPet((NPC*)client->GetPlayer()->GetPet()); + client->GetPlayer()->DismissPet((NPC*)client->GetPlayer()->GetCharmedPet()); + + client->Message(CHANNEL_COLOR_YELLOW, "You tell your pet to get lost."); + } + else + client->Message(CHANNEL_COLOR_YELLOW, "Unknown pet command %s.", sep->arg[0]); + +} + +/* + Function: Command_PetName() + Purpose : Pet your name (???) + Params : + Dev : + Example : +*/ +void Commands::Command_PetName(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0]) { + const char* pet_name = sep->argplus[0]; + client->SetPetName(pet_name); + } + else { + client->GetPlayer()->GetInfoStruct()->set_pet_name(""); + } +} + +/* + Function: Command_NamePet() + Purpose : Name your pet + Params : + Dev : + Example : +*/ +void Commands::Command_NamePet(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_NAME_PET"); + LogWrite(MISC__TODO, 1, "Command", "TODO-Command: Name Pet Command"); + client->Message(CHANNEL_COLOR_YELLOW, "Pets are not yet implemented."); +} + +/* + Function: Command_Rename() + Purpose : Renames existing pet + Params : + Dev : + Example : +*/ +void Commands::Command_Rename(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_RENAME"); + LogWrite(MISC__TODO, 1, "Command", "TODO-Command: Rename Pet Command"); + client->Message(CHANNEL_COLOR_YELLOW, "Pets are not yet implemented."); +} + +/* + Function: Command_ConfirmRename() + Purpose : Confirms renaming your pet + Params : + Dev : + Example : +*/ +void Commands::Command_ConfirmRename(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_CONFIRMRENAME"); + LogWrite(MISC__TODO, 1, "Command", "TODO-Command: Confirm Rename Pet Command"); + client->Message(CHANNEL_COLOR_YELLOW, "Pets are not yet implemented."); +} + +/* + Function: Command_PetOptions() + Purpose : Sets various pet options + Params : + Dev : + Example : +*/ +void Commands::Command_PetOptions(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_PETOPTIONS"); + + Player* player = client->GetPlayer(); + Spawn* target = player->GetTarget(); + if (!target) + return; + + if (target == player->GetPet()) + client->SendPetOptionsWindow(player->GetPet()->GetName()); + else if (target == player->GetCharmedPet()) + client->SendPetOptionsWindow(player->GetCharmedPet()->GetName()); + else if (target == player->GetCosmeticPet()) + client->SendPetOptionsWindow(player->GetCosmeticPet()->GetName(), 0); + else if (target == player->GetDeityPet()) + client->SendPetOptionsWindow(player->GetDeityPet()->GetName(), 0); +} + +/* + Function: Command_Random() + Purpose : Handles /random dice roll in-game + Params : 1-100 + Dev : Scatman + Example : /randon 1 100 +*/ +void Commands::Command_Random(Client* client, Seperator* sep) +{ + char message[256] = {0}; + + if (sep) + { + if (sep->GetArgNumber() == 0 && sep->IsNumber(0)) + sprintf(message, "Random: %s rolls 1 to %i on the magic dice...and scores a %i!", client->GetPlayer()->GetName(), atoi(sep->arg[0]), MakeRandomInt(1, atoi(sep->arg[0]))); + else if (sep->GetArgNumber() > 0 && sep->IsNumber(0) && sep->IsNumber(1)) + sprintf(message, "Random: %s rolls from %i to %i on the magic dice...and scores a %i!", client->GetPlayer()->GetName(), atoi(sep->arg[0]), atoi(sep->arg[1]), MakeRandomInt(atoi(sep->arg[0]), atoi(sep->arg[1]))); + else + sprintf(message, "Random: %s rolls from 1 to 100 on the magic dice...and scores a %i!", client->GetPlayer()->GetName(), MakeRandomInt(1, 100)); + } + else + sprintf(message, "Random: %s rolls from 1 to 100 on the magic dice...and scores a %i!", client->GetPlayer()->GetName(), MakeRandomInt(1, 100)); + + client->GetPlayer()->GetZone()->HandleChatMessage(0, 0, CHANNEL_EMOTE, message); +} + +/* + Function: Command_Randomize() + Purpose : Sets randomize (appearance) values for NPCs + Params : Attrib Name + Dev : Scatman + Example : /randomize gender 1 -- will randomize the NPCs gender (male/female) +*/ +void Commands::Command_Randomize(Client* client, Seperator* sep) +{ + NPC* target = (NPC*)client->GetPlayer()->GetTarget(); + if (target) + { + if (target->IsNPC()) + { + if (sep && sep->arg[0] && !sep->IsNumber(0)) + { + const char* value = sep->arg[0]; + if (strncasecmp(value, "show", strlen(value)) == 0) + { + client->DisplayRandomizeFeatures(target->GetRandomize()); + } + else + { + if (sep->arg[1] && sep->IsNumber(1)) + { + int8 option = atoi(sep->arg[1]); + if (option == 0 || option == 1) + { + int32 feature = 0; + char feature_text[32] = {0}; + if (strncasecmp(value, "gender", strlen(value)) == 0) { + feature = RANDOMIZE_GENDER; + strncpy(feature_text, "gender", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "race", strlen(value)) == 0) { + feature = RANDOMIZE_RACE; + strncpy(feature_text, "race", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "model", strlen(value)) == 0) { + feature = RANDOMIZE_MODEL_TYPE; + strncpy(feature_text, "model", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "facial_hair", strlen(value)) == 0) { + feature = RANDOMIZE_FACIAL_HAIR_TYPE; + strncpy(feature_text, "facial hair", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "hair", strlen(value)) == 0) { + feature = RANDOMIZE_HAIR_TYPE; + strncpy(feature_text, "hair", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "wing", strlen(value)) == 0) { + feature = RANDOMIZE_WING_TYPE; + strncpy(feature_text, "wings", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "cheek", strlen(value)) == 0) { + feature = RANDOMIZE_CHEEK_TYPE; + strncpy(feature_text, "cheeks", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "chin", strlen(value)) == 0) { + feature = RANDOMIZE_CHIN_TYPE; + strncpy(feature_text, "chin", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "ear", strlen(value)) == 0) { + feature = RANDOMIZE_EAR_TYPE; + strncpy(feature_text, "ears", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "eye_brow", strlen(value)) == 0) { + feature = RANDOMIZE_EYE_BROW_TYPE; + strncpy(feature_text, "eye brows", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "eye", strlen(value)) == 0) { + feature = RANDOMIZE_EYE_TYPE; + strncpy(feature_text, "eyes", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "lip", strlen(value)) == 0) { + feature = RANDOMIZE_LIP_TYPE; + strncpy(feature_text, "lips", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "nose", strlen(value)) == 0) { + feature = RANDOMIZE_NOSE_TYPE; + strncpy(feature_text, "nose", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "eye_color", strlen(value)) == 0) { + feature = RANDOMIZE_EYE_COLOR; + strncpy(feature_text, "eye color", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "hair_color", strlen(value)) == 0) { + feature = RANDOMIZE_HAIR_TYPE_COLOR; + strncpy(feature_text, "hair color", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "hair_color1", strlen(value)) == 0) { + feature = RANDOMIZE_HAIR_COLOR1; + strncpy(feature_text, "hair color1", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "hair_color2", strlen(value)) == 0) { + feature = RANDOMIZE_HAIR_COLOR2; + strncpy(feature_text, "hair color2", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "hair_highlight", strlen(value)) == 0) { + feature = RANDOMIZE_HAIR_HIGHLIGHT; + strncpy(feature_text, "hair highlights", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "facial_hair_color", strlen(value)) == 0) { + feature = RANDOMIZE_HAIR_FACE_COLOR; + strncpy(feature_text, "facial hair color", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "facial_hair_color_highlight", strlen(value)) == 0) { + feature = RANDOMIZE_HAIR_FACE_HIGHLIGHT_COLOR; + strncpy(feature_text, "facial hair color highlights", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "hair_color_highlight", strlen(value)) == 0) { + feature = RANDOMIZE_HAIR_TYPE_HIGHLIGHT_COLOR; + strncpy(feature_text, "hair color highlights", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "skin_color", strlen(value)) == 0) { + feature = RANDOMIZE_SKIN_COLOR; + strncpy(feature_text, "skin color", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "wing_color1", strlen(value)) == 0) { + feature = RANDOMIZE_WING_COLOR1; + strncpy(feature_text, "wing color1", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "wing_color2", strlen(value)) == 0) { + feature = RANDOMIZE_WING_COLOR2; + strncpy(feature_text, "wing color2", sizeof(feature_text) - 1); + } + else { + client->Message(CHANNEL_COLOR_YELLOW, "'%s' is not a valid feature to randomize.", value); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Valid features are: 'gender', 'race', 'model', 'hair', 'facial_hair', 'legs', 'wings', 'cheek', 'chin', 'ear', 'eye', 'eye_brow', 'lip', 'nose', 'eye_color', 'hair_color1', 'hair_color2', 'hair_highlight', 'facial_hair_color', 'facial_hair_color_highlight', 'hair_color', 'hair_color_highlight', 'skin_color', 'wing_color1', 'wing_color2'."); + } + + if (feature > 0) { + if (option == 1) { + if (target->GetRandomize() & feature) + client->Message(CHANNEL_COLOR_YELLOW, "'%s' is already set to randomly generate their %s.", target->GetName(), feature_text); + else { + target->AddRandomize(feature); + ((NPC*)client->GetCurrentZone()->GetSpawn(target->GetDatabaseID()))->AddRandomize(feature); + database.UpdateRandomize(target->GetDatabaseID(), feature); + client->Message(CHANNEL_COLOR_YELLOW, "'%s' will now generate their %s randomly.", target->GetName(), feature_text); + } + } + else { + if (target->GetRandomize() & feature) { + target->AddRandomize(-feature); + ((NPC*)client->GetCurrentZone()->GetSpawn(target->GetDatabaseID()))->AddRandomize(-feature); + database.UpdateRandomize(target->GetDatabaseID(), -feature); + client->Message(CHANNEL_COLOR_YELLOW, "'%s' will no longer generate their %s randomly.", target->GetName(), feature_text); + } + else + client->Message(CHANNEL_COLOR_YELLOW, "'%s' already does not randomly generate their %s.", target->GetName(), feature_text); + } + } + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must specify either a 1(on) or 0(off) as the second parameter."); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must specify either a 1(on) or 0(off) as the second parameter."); + } + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /randomize [show | [feature [1|0]]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "'show' will display current configured features"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Valid features are: 'gender', 'race', 'model', 'hair', 'facial_hair', 'legs', 'wings', 'cheek', 'chin', 'ear', 'eye', 'eye_brow', 'lip', 'nose', 'eye_color', 'hair_color1', 'hair_color2', 'hair_highlight', 'facial_hair_color', 'facial_hair_color_highlight', 'hair_color', 'hair_color_highlight', 'skin_color', 'wing_color1', 'wing_color2'."); + } + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "The target you wish to randomize must be an NPC."); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must select the target you wish to randomize."); +} + +/* + Function: Command_ShowCloak() + Purpose : Character Settings combo box for Show Cloak + Params : true/false + Dev : John Adams + Example : +*/ +void Commands::Command_ShowCloak(Client* client, Seperator* sep) +{ + Player* player = client->GetPlayer(); + + if (sep && sep->arg[0]) + { + const char* value = sep->arg[0]; + if (strncasecmp(value, "true", strlen(value)) == 0) + { + player->set_character_flag(CF_SHOW_CLOAK); + player->reset_character_flag(CF2_SHOW_RANGED); + } + else if (strncasecmp(value, "false", strlen(value)) == 0) + player->reset_character_flag(CF_SHOW_CLOAK); + else + { + client->Message(CHANNEL_COLOR_YELLOW, "Not supposed to be here! Please /bug this: Error in %s (%u)", __FUNCTION__, __LINE__); + LogWrite(COMMAND__WARNING, 0, "Command", "Not supposed to be here! Please /bug this: Error in %s (%u)", __FUNCTION__, __LINE__); + } + } +} + +/* + Function: Command_ShowHelm() + Purpose : Character Settings combo box for Show Helm + Params : true/false + Dev : John Adams + Example : +*/ +void Commands::Command_ShowHelm(Client* client, Seperator* sep) +{ + Player* player = client->GetPlayer(); + + if(client->GetVersion() <= 561) { + return; // not allowed/supported + } + + if (sep && sep->arg[0]) + { + PrintSep(sep, "Command_ShowHelm"); + const char* value = sep->arg[0]; + if (strncasecmp(value, "true", strlen(value)) == 0) + { + player->toggle_character_flag(CF_HIDE_HELM); + player->toggle_character_flag(CF_HIDE_HOOD); + } + else if (strncasecmp(value, "false", strlen(value)) == 0) + player->toggle_character_flag(CF_HIDE_HELM); + else + { + client->Message(CHANNEL_COLOR_YELLOW, "Not supposed to be here! Please /bug this: Error in %s (%u)", __FUNCTION__, __LINE__); + LogWrite(COMMAND__WARNING, 0, "Command", "Not supposed to be here! Please /bug this: Error in %s (%u)", __FUNCTION__, __LINE__); + } + } +} + +/* + Function: Command_ShowHood() + Purpose : Character Settings combo box for Show Hood + Params : true/false + Dev : John Adams + Example : +*/ +void Commands::Command_ShowHood(Client* client, Seperator* sep) +{ + Player* player = client->GetPlayer(); + + if (sep && sep->arg[0]) + { + PrintSep(sep, "Command_ShowHood"); + const char* value = sep->arg[0]; + + if (strncasecmp(value, "true", strlen(value)) == 0) + { + player->toggle_character_flag(CF_HIDE_HOOD); + if(client->GetVersion() > 561) { // no hide helm support in DoF + player->toggle_character_flag(CF_HIDE_HELM); + } + } + else if (strncasecmp(value, "false", strlen(value)) == 0) { + player->toggle_character_flag(CF_HIDE_HOOD); + } + else + { + client->Message(CHANNEL_COLOR_YELLOW, "Not supposed to be here! Please /bug this: Error in %s (%u)", __FUNCTION__, __LINE__); + LogWrite(COMMAND__WARNING, 0, "Command", "Not supposed to be here! Please /bug this: Error in %s (%u)", __FUNCTION__, __LINE__); + } + } +} + +/* + Function: Command_ShowHoodHelm() + Purpose : Character Settings combo box for Show Helm or Hood + Params : true/false + Dev : John Adams + Example : +*/ +void Commands::Command_ShowHoodHelm(Client* client, Seperator* sep) +{ + Player* player = client->GetPlayer(); + + if(client->GetVersion() <= 561) { + return; // not allowed/supported + } + + if (sep && sep->arg[0]) + { + PrintSep(sep, "Command_ShowHoodHelm"); + const char* value = sep->arg[0]; + if (strncasecmp(value, "true", strlen(value)) == 0) + { + player->toggle_character_flag(CF_HIDE_HOOD); + player->toggle_character_flag(CF_HIDE_HELM); + } + else if (strncasecmp(value, "false", strlen(value)) == 0) + { + // don't think we ever wind up in here... + player->toggle_character_flag(CF_HIDE_HOOD); + player->toggle_character_flag(CF_HIDE_HELM); + } + else + { + client->Message(CHANNEL_COLOR_YELLOW, "Not supposed to be here! Please /bug this: Error in %s (%u)", __FUNCTION__, __LINE__); + LogWrite(COMMAND__WARNING, 0, "Command", "Not supposed to be here! Please /bug this: Error in %s (%u)", __FUNCTION__, __LINE__); + } + } +} + +/* + Function: Command_ShowRanged() + Purpose : Character Settings combo box for Show Ranged weapon + Params : true/false + Dev : John Adams + Example : +*/ +void Commands::Command_ShowRanged(Client* client, Seperator* sep) +{ + Player* player = client->GetPlayer(); + + if (sep && sep->arg[0]) + { + const char* value = sep->arg[0]; + if (strncasecmp(value, "true", strlen(value)) == 0) + { + player->set_character_flag(CF2_SHOW_RANGED); + player->reset_character_flag(CF_SHOW_CLOAK); + } + else if (strncasecmp(value, "false", strlen(value)) == 0) + player->reset_character_flag(CF2_SHOW_RANGED); + else + { + client->Message(CHANNEL_COLOR_YELLOW, "Not supposed to be here! Please /bug this: Error in %s (%u)", __FUNCTION__, __LINE__); + LogWrite(COMMAND__WARNING, 0, "Command", "Not supposed to be here! Please /bug this: Error in %s (%u)", __FUNCTION__, __LINE__); + } + } +} + +/* + Function: + Purpose : + Params : + Dev : + Example : +*/ +void Commands::Command_Skills(Client* client, Seperator* sep, int handler) +{ + Player* player = 0; + Client* to_client = 0; + + if(client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsPlayer()) + to_client = ((Player*)client->GetPlayer()->GetTarget())->GetClient(); + + switch(handler) + { + case COMMAND_SKILL: + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /skill [add|remove|list] [skill name]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Target player to add/remove skills for that player."); + break; + } + case COMMAND_SKILL_ADD: + { + if (sep && sep->arg[0]) + { + const char* skill_name = sep->argplus[0]; + Skill* skill = master_skill_list.GetSkillByName(skill_name); + + if (skill && to_client && !(client->GetPlayer() == to_client->GetPlayer())) // add skill to your target, if target is not you + { + player = to_client->GetPlayer(); + + if (player) + { + if (!player->GetSkills()->HasSkill(skill->skill_id)) + { + player->AddSkill(skill->skill_id, 1, player->GetLevel() * 5, true); + client->Message(CHANNEL_COLOR_YELLOW, "You gave skill '%s' to player '%s'.", skill_name, player->GetName()); + to_client->Message(CHANNEL_COLOR_YELLOW, "%s gave you skill '%s'.", client->GetPlayer()->GetName(), skill_name); + } + else + client->Message(CHANNEL_COLOR_YELLOW, "%s already has the skill '%s'.", player->GetName(), skill_name); + } + } + else if (skill) // add skill to yourself + { + player = client->GetPlayer(); + + if (player) + { + if (!player->GetSkills()->HasSkill(skill->skill_id)) + { + player->AddSkill(skill->skill_id, 1, player->GetLevel() * 5, true); + client->Message(CHANNEL_COLOR_YELLOW, "Added skill '%s'.", skill_name); + } + else + client->Message(CHANNEL_COLOR_YELLOW, "You already have the skill '%s'.", skill_name); + } + } + else + client->Message(CHANNEL_COLOR_YELLOW, "The skill '%s' does not exist.", skill_name); + } + else + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /skill add [skill name]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Target player to give skill to that player."); + } + break; + } + case COMMAND_SKILL_REMOVE: + { + if (sep && sep->arg[0]) + { + const char* skill_name = sep->argplus[0]; + Skill* skill = master_skill_list.GetSkillByName(skill_name); + + if (skill && to_client && !(client->GetPlayer() == to_client->GetPlayer())) // remove skill from your target, if target is not you + { + player = to_client->GetPlayer(); + + if (player) + { + if (player->GetSkills()->HasSkill(skill->skill_id)) + { + player->RemoveSkillFromDB(skill, true); + client->Message(CHANNEL_COLOR_YELLOW, "Removed skill '%s' from player %s.", skill_name, player->GetName()); + to_client->Message(CHANNEL_COLOR_YELLOW, "%s has removed skill '%s' from you.", client->GetPlayer()->GetName(), skill_name); + } + else + client->Message(CHANNEL_COLOR_YELLOW, "Player '%s' does not have the skill '%s'.", player->GetName(), skill_name); + } + } + else if(skill) // remove skill from yourself + { + Player* player = client->GetPlayer(); + + if (player) + { + if (player->GetSkills()->HasSkill(skill->skill_id)) + { + player->RemoveSkillFromDB(skill, true); + client->Message(CHANNEL_COLOR_YELLOW, "Removed skill '%s'.", skill_name); + } + else + client->Message(CHANNEL_COLOR_YELLOW, "You do not have the skill '%s'.", skill_name); + } + } + else + client->Message(CHANNEL_COLOR_YELLOW, "The skill '%s' does not exist.", skill_name); + } + else + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /skill remove [skill name]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Target player to give skill to that player."); + } + break; + } + case COMMAND_SKILL_LIST: + { + if (sep && sep->arg[0]) + { + const char* skill_name = sep->argplus[0]; + client->Message(CHANNEL_COLOR_YELLOW, "Listing skills like '%s':", skill_name); + map* skills = master_skill_list.GetAllSkills(); + map::iterator itr; + + if (skills && skills->size() > 0) + { + for (itr = skills->begin(); itr != skills->end(); itr++) + { + Skill* skill = itr->second; + string current_skill_name = ::ToLower(string(skill->name.data.c_str())); + + if (current_skill_name.find(::ToLower(string(skill_name))) < 0xFFFFFFFF) + client->Message(CHANNEL_COLOR_YELLOW, "%s (%u)", skill->name.data.c_str(), skill->skill_id); + } + } + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /skill list [skill name]"); + break; + } + } +} + +/* + Function: Command_SpawnTemplate() + Purpose : Create or Use defined Spawn Templates + Params : create, save, rempve, list + Dev : John Adams + Example : /spawn template create "Test" +*/ +void Commands::Command_SpawnTemplate(Client* client, Seperator* sep) +{ + if (sep == NULL || sep->arg[0] == NULL) + { + client->Message(CHANNEL_COLOR_YELLOW, "Examples:\n/spawn template save Test - saves a template of the targetted spawn as the name Test\n/spawn template list - shows a list of current templates\n/spawn template spawn [id|name] creates a new spawn based on template [id|name] at your current location."); + return; + } + + // got params, continue + const char * template_cmd = sep->arg[0]; + + if (strncasecmp(template_cmd, "list", strlen(template_cmd)) == 0) + { + if (!sep->IsSet(1)) + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /spawn template list [name|location_id]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Examples:\n/spawn template list [name] - will show all templates containing [name].\n/spawn template list [location_id] - will show all templates that [location_id] is assigned to."); + return; + } + + map* template_names = 0; + if (sep->IsNumber(1)) + { + int32 location_id = 0; + location_id = atoi(sep->arg[1]); + template_names = database.GetSpawnTemplateListByID(location_id); + } + else + { + const char* name = 0; + name = sep->argplus[1]; + template_names = database.GetSpawnTemplateListByName(name); + } + + if(!template_names) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "No templates found."); + else + { + map::iterator itr; + client->SimpleMessage(CHANNEL_COLOR_YELLOW," ID Name: "); + for(itr = template_names->begin(); itr != template_names->end(); itr++) + client->Message(CHANNEL_COLOR_YELLOW,"%03lu %s", itr->first, itr->second.c_str()); + safe_delete(template_names); + } + } + + else if (strncasecmp(template_cmd, "save", strlen(template_cmd)) == 0) + { + NPC* target = (NPC*)client->GetPlayer()->GetTarget(); + if ( target && (target->IsNPC() || target->IsObject() || target->IsSign() || target->IsWidget() || target->IsGroundSpawn()) ) + { + if (sep && sep->arg[1][0] && !sep->IsNumber(1)) + { + // first, lookup to see if template name already exists + const char* name = 0; + name = sep->argplus[1]; + map* template_names = database.GetSpawnTemplateListByName(name); + if(!template_names) + { + int32 new_template_id = database.SaveSpawnTemplate(target->GetSpawnLocationID(), name); + if( new_template_id > 0 ) + client->Message(CHANNEL_COLOR_YELLOW, "Spawn template '%s' added for spawn '%s' (%u) as TemplateID: %u", name, target->GetName(), target->GetSpawnLocationID(), new_template_id); + else + client->Message(CHANNEL_COLOR_RED, "ERROR: Failed to add new template '%s' for spawn '%s' (%u).", name, target->GetName(), target->GetSpawnLocationID()); + } + else + client->Message(CHANNEL_COLOR_RED, "ERROR: Failed to add new template '%s' - Already exists!", name); + } + else + client->Message(CHANNEL_COLOR_RED, "ERROR: Saving a new spawn template requires a valid template name!"); + } + else + client->Message(CHANNEL_COLOR_RED, "ERROR: You must target the spawn you wish to save as a template!"); + } + + else if (strncasecmp(template_cmd, "remove", strlen(template_cmd)) == 0) + { + if (sep && sep->arg[1][0] && sep->IsNumber(1)) + { + int32 template_id = 0; + template_id = atoi(sep->arg[1]); + if (database.RemoveSpawnTemplate(template_id)) + client->Message(CHANNEL_COLOR_YELLOW, "Spawn template ID: %u successfully removed.", template_id); + else + client->Message(CHANNEL_COLOR_RED, "ERROR: Failed to remove spawn template ID: %u", template_id); + } + else + client->Message(CHANNEL_COLOR_RED, "ERROR: Removing a spawn template requires a valid template ID!"); + } + + // Renamed create to spawn + else if (strncasecmp(template_cmd, "spawn", strlen(template_cmd)) == 0) + { + if (sep && sep->arg[1][0]) + { + int32 new_location = 0; + + if (sep->IsNumber(1)) + { + int32 template_id = 0; + template_id = atoi(sep->arg[1]); + + new_location = database.CreateSpawnFromTemplateByID(client, template_id); + if( new_location > 0 ) + client->Message(CHANNEL_COLOR_YELLOW, "New spawn location %u created from template ID: %u", new_location, template_id); + else + client->Message(CHANNEL_COLOR_RED, "ERROR: Failed to spawn the new spawn location from template ID: %u", template_id); + } + else + { + const char* name = 0; + name = sep->argplus[1]; + + new_location = database.CreateSpawnFromTemplateByName(client, name); + if( new_location > 0 ) + client->Message(CHANNEL_COLOR_YELLOW, "New spawn location %u created from template: '%s'", new_location, name); + else + client->Message(CHANNEL_COLOR_RED, "ERROR: Failed to spawn the new spawn location from template: '%s'", name); + } + } + else + client->Message(CHANNEL_COLOR_RED, "ERROR: Spawning a new spawn location requires a valid template name or ID!"); + } + + else + client->Message(CHANNEL_COLOR_RED, "ERROR: Unknown /spawn template command."); +} + +void Commands::Command_Speed(Client* client, Seperator* sep) { + if(sep && sep->arg[0][0] && sep->IsNumber(0)){ + float new_speed = atof(sep->arg[0]); + if (new_speed > 0.0f) + { + client->GetPlayer()->SetSpeed(new_speed, true); + client->GetPlayer()->SetCharSheetChanged(true); + database.insertCharacterProperty(client, CHAR_PROPERTY_SPEED, sep->arg[0]); + client->Message(CHANNEL_STATUS, "Setting speed to %.2f.", new_speed); + } + else + client->Message(CHANNEL_STATUS, "Invalid speed provided %s.", sep->arg[0]); + } + else{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /speed {new speed value}"); + } + +} + +/* + Function: Command_StationMarketPlace() + Purpose : just trying to eat the console spam for now + Params : + Dev : John +*/ +void Commands::Command_StationMarketPlace(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_SMP"); + // This will reduce the spam from once every 10 sec to once every 30 sec, + // can't seem to reduce it any more get rid of it completely... + if (sep && strcmp(sep->arg[0], "gkw") == 0) { + PacketStruct* packet = configReader.getStruct("WS_MarketFundsUpdate", client->GetVersion()); + if (packet) { + packet->setDataByName("account_id", client->GetAccountID()); + packet->setDataByName("character_id", client->GetCharacterID()); + packet->setDataByName("unknown1", 1, 5); + packet->setDataByName("unknown1", 26, 6); + packet->setDataByName("unknown2", 0xFFFFFFFF); + packet->setDataByName("unknown3", 248, 1); + client->QueuePacket(packet->serialize()); + } + safe_delete(packet); + } +} + +/* + Function: Command_StopDrinking() + Purpose : Stops player from auto-consuming drink + Params : + Dev : Zcoretri + Example : /stopdrinking +*/ +void Commands::Command_StopDrinking(Client* client) +{ + client->GetPlayer()->reset_character_flag(CF_DRINK_AUTO_CONSUME); + client->GetPlayer()->SetActiveDrinkUniqueID(0); + client->Message(CHANNEL_COLOR_YELLOW,"You stop drinking your current drink."); +} + +/* + Function: Command_StopEating() + Purpose : Stops player from auto-consuming food + Params : + Dev : Zcoretri + Example : /stopeating +*/ +void Commands::Command_StopEating(Client* client) +{ + client->GetPlayer()->reset_character_flag(CF_FOOD_AUTO_CONSUME); + client->GetPlayer()->SetActiveFoodUniqueID(0); + client->Message(CHANNEL_COLOR_YELLOW,"You stop eating your current food."); +} + +/* + Function: Command_Title() + Purpose : Help for /title command + Params : n/a + Dev : Zcoretri + Example : /title +*/ +void Commands::Command_Title(Client* client) +{ + client->Message(CHANNEL_COLOR_YELLOW, "Available subcommands: list, setprefix , setsuffix , fix"); +} + +/* + Function: Command_TitleList() + Purpose : List available titles for player + Params : n/a + Dev : Zcoretri + Example : /title list +*/ +void Commands::Command_TitleList(Client* client) +{ + // must call release read lock before leaving function on GetPlayerTitles + vector* titles = client->GetPlayer()->GetPlayerTitles()->GetAllTitles(); + vector::iterator itr; + Title* title; + sint32 i = 0; + + client->Message(CHANNEL_NARRATIVE, "Listing available titles:"); + for(itr = titles->begin(); itr != titles->end(); itr++) + { + title = *itr; + client->Message(CHANNEL_NARRATIVE, "%i: type=[%s] title=[%s]", i, title->GetPrefix() ? "Prefix":"Suffix", title->GetName()); + i++; + } + + client->GetPlayer()->GetPlayerTitles()->ReleaseReadLock(); +} + +/* + Function: Command_TitleSetPrefix() + Purpose : Set Prefix title for player + Params : Title ID + Dev : Zcoretri + Example : /title setprefix 1 +*/ +void Commands::Command_TitleSetPrefix(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0] && sep->IsNumber(0)) + { + sint32 index = atoul(sep->arg[0]); + + if(index > -1) + { + Title* title = client->GetPlayer()->GetPlayerTitles()->GetTitle(index); + if(!title) + { + client->Message(CHANNEL_COLOR_RED, "Missing index %i to set title", index); + return; + } + else if(!title->GetPrefix()) + { + client->Message(CHANNEL_COLOR_RED, "%s is not a prefix.", title->GetName()); + return; + } + } + else // make sure client doesn't pass some bogus negative index + index = -1; + + database.SaveCharPrefixIndex(index, client->GetCharacterID()); + client->SendTitleUpdate(); + } +} + +/* + Function: Command_TitleSetSuffix() + Purpose : Set Suffix title for player + Params : Title ID + Dev : Zcoretri + Example : /title setsuffix 1 +*/ +void Commands::Command_TitleSetSuffix(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0] && sep->IsNumber(0)) + { + sint32 index = atoul(sep->arg[0]); + if(index > -1) + { + Title* title = client->GetPlayer()->GetPlayerTitles()->GetTitle(index); + if(!title) + { + client->Message(CHANNEL_COLOR_RED, "Missing index %i to set title", index); + return; + } + else if(title->GetPrefix()) + { + client->Message(CHANNEL_COLOR_RED, "%s is not a suffix.", title->GetName()); + return; + } + } + else // make sure client doesn't pass some bogus negative index + index = -1; + + database.SaveCharSuffixIndex(index, client->GetCharacterID()); + client->SendTitleUpdate(); + } +} + +/* + Function: Command_TitleFix() + Purpose : Fix title for player (???) + Params : + Dev : Zcoretri + Example : +*/ +void Commands::Command_TitleFix(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_TITLE_FIX"); + LogWrite(MISC__TODO, 1, "Titles", "TODO-Command: TITLE_FIX"); +} + + +/* + Function: Command_Toggle_Anonymous() + Purpose : Toggles player Anonymous + Params : + Dev : paulgh + Example : /anon +*/ +void Commands::Command_Toggle_Anonymous(Client* client) +{ + Player* player = client->GetPlayer(); + + player->toggle_character_flag(CF_ANONYMOUS); + client->Message(CHANNEL_COLOR_YELLOW,"You are %s anonymous.", player->get_character_flag(CF_ANONYMOUS)?"now":"no longer"); + + if (player->get_character_flag(CF_ANONYMOUS)) + player->SetActivityStatus(player->GetActivityStatus() + ACTIVITY_STATUS_ANONYMOUS); + else + player->SetActivityStatus(player->GetActivityStatus() - ACTIVITY_STATUS_ANONYMOUS); +} + +/* + Function: Command_Toggle_AutoConsume() + Purpose : Toggles player food/drink auto consume + Params : unknown + Dev : paulgh + Example : /set_auto_consume +*/ +void Commands::Command_Toggle_AutoConsume(Client* client, Seperator* sep) +{ + Player* player = client->GetPlayer(); + + if (sep && sep->arg[0]) + PrintSep(sep, "COMMAND_SET_AUTO_CONSUME"); + if (sep && sep->arg[0] && sep->IsNumber(0)) + { + int8 slot = atoi(sep->arg[0]); + int8 flag = atoi(sep->arg[1]); + if (client->GetVersion() <= 373) { + slot += 4; + } + else if (client->GetVersion() <= 561) { + slot += 2; + } + if (slot == EQ2_FOOD_SLOT) + { + player->toggle_character_flag(CF_FOOD_AUTO_CONSUME); + player->SetCharSheetChanged(true); + client->QueuePacket(player->GetEquipmentList()->serialize(client->GetVersion(), player)); + if (flag == 1) + client->Message(CHANNEL_NARRATIVE, "You decide to eat immediately whenever you become hungry."); + else + { + client->Message(CHANNEL_NARRATIVE, "You decide to ignore the hunger."); + return; + } + } + else + { + player->toggle_character_flag(CF_DRINK_AUTO_CONSUME); + player->SetCharSheetChanged(true); + client->QueuePacket(player->GetEquipmentList()->serialize(client->GetVersion(), player)); + if (flag == 1) + client->Message(CHANNEL_NARRATIVE, "You decide to drink immediately whenever you become thirsty."); + else + { + client->Message(CHANNEL_NARRATIVE, "You decide to ignore the thirst."); + return; + } + } + + if(!client->CheckConsumptionAllowed(slot, false)) + return; + + Item* item = player->GetEquipmentList()->GetItem(slot); + if(item) + client->ConsumeFoodDrink(item, slot); + } +} + +/* + Function: Command_Toggle_BonusXP() + Purpose : Toggles player Bonus XP + Params : + Dev : John Adams + Example : /disable_char_bonus_exp +*/ +void Commands::Command_Toggle_BonusXP(Client* client) +{ + Player* player = client->GetPlayer(); + + player->toggle_character_flag(CF2_CHARACTER_BONUS_EXPERIENCE_ENABLED); + player->SetCharSheetChanged(true); +} + +/* + Function: Command_Toggle_CombatXP() + Purpose : Toggles player Adventure XP + Params : + Dev : John Adams + Example : /disable_combat_exp +*/ +void Commands::Command_Toggle_CombatXP(Client* client) +{ + Player* player = client->GetPlayer(); + + player->toggle_character_flag(CF_COMBAT_EXPERIENCE_ENABLED); + player->SetCharSheetChanged(true); +} + +/* + Function: Command_Toggle_GMHide() + Purpose : Toggles hiding player GM status + Params : + Dev : Scatman + Example : /gm_hide +*/ +void Commands::Command_Toggle_GMHide(Client* client) +{ + Player* player = client->GetPlayer(); + + player->toggle_character_flag(CF_HIDE_STATUS); + client->Message(CHANNEL_COLOR_YELLOW,"You are %s hiding your GM status.", player->get_character_flag(CF_HIDE_STATUS)?"now":"no longer"); +} + +/* + Function: Command_Toggle_GMVanish() + Purpose : Toggles hiding GM players from /who searches + Params : + Dev : Scatman + Example : /gm_vanish +*/ +void Commands::Command_Toggle_GMVanish(Client* client) +{ + Player* player = client->GetPlayer(); + + player->toggle_character_flag(CF_GM_HIDDEN); + client->Message(CHANNEL_COLOR_YELLOW,"You are %s invisible to who queries.", player->get_character_flag(CF_GM_HIDDEN)?"now":"no longer"); +} + +/* + Function: Command_Toggle_Illusions() + Purpose : Toggles player illusion form + Params : not sure sep is needed, testing + Dev : paulgh + Example : /hide_illusions +*/ +void Commands::Command_Toggle_Illusions(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0]) + PrintSep(sep, "COMMAND_TOGGLE_ILLUSIONS"); + client->GetPlayer()->toggle_character_flag(CF_SHOW_ILLUSION); +} + +/* + Function: Command_Toggle_LFG() + Purpose : Toggles player LFG Flag + Params : + Dev : paulgh + Example : /lfg +*/ +void Commands::Command_Toggle_LFG(Client* client) +{ + Player* player = client->GetPlayer(); + + player->toggle_character_flag(CF_LFG); + client->Message(CHANNEL_COLOR_YELLOW,"You are %s LFG.", player->get_character_flag(CF_LFG)?"now":"no longer"); + + if (player->get_character_flag(CF_LFG)) + player->SetActivityStatus(player->GetActivityStatus() + ACTIVITY_STATUS_LFG); + else + player->SetActivityStatus(player->GetActivityStatus() - ACTIVITY_STATUS_LFG); +} + +/* + Function: Command_Toggle_LFW() + Purpose : Toggles player LFW Flag + Params : + Dev : paulgh + Example : /lfw +*/ +void Commands::Command_Toggle_LFW(Client* client) +{ + Player* player = client->GetPlayer(); + + player->toggle_character_flag(CF_LFW); + client->Message(CHANNEL_COLOR_YELLOW,"You %s looking for work.", player->get_character_flag(CF_LFW)?"let others know you are":"stop"); + + if (player->get_character_flag(CF_LFW)) + player->SetActivityStatus(player->GetActivityStatus() + ACTIVITY_STATUS_LFW); + else + player->SetActivityStatus(player->GetActivityStatus() - ACTIVITY_STATUS_LFW); +} + +/* + Function: Command_Toggle_QuestXP() + Purpose : Toggles player Quest XP + Params : + Dev : John Adams + Example : /disable_quest_exp +*/ +void Commands::Command_Toggle_QuestXP(Client* client) +{ + Player* player = client->GetPlayer(); + + player->toggle_character_flag(CF_QUEST_EXPERIENCE_ENABLED); + player->SetCharSheetChanged(true); +} + +/* + Function: Command_Toggle_Roleplaying() + Purpose : Toggles player Roleplaying flag + Params : + Dev : paulgh + Example : /role +*/ +void Commands::Command_Toggle_Roleplaying(Client* client) +{ + Player* player = client->GetPlayer(); + + player->toggle_character_flag(CF_ROLEPLAYING); + client->Message(CHANNEL_COLOR_YELLOW,"You are %s roleplaying.", player->get_character_flag(CF_ROLEPLAYING)?"now":"no longer"); + + if (player->get_character_flag(CF_ROLEPLAYING)) + player->SetActivityStatus(player->GetActivityStatus() + ACTIVITY_STATUS_ROLEPLAYING); + else + player->SetActivityStatus(player->GetActivityStatus() - ACTIVITY_STATUS_ROLEPLAYING); +} + +void Commands::Command_Toggle_Duels(Client* client) +{ + Player* player = client->GetPlayer(); + + player->toggle_character_flag(CF_ALLOW_DUEL_INVITES); + client->Message(CHANNEL_COLOR_YELLOW,"You are %s accepting duel invites.", player->get_character_flag(CF_ALLOW_DUEL_INVITES)?"now":"no longer"); +} + +void Commands::Command_Toggle_Trades(Client* client) +{ + Player* player = client->GetPlayer(); + + player->toggle_character_flag(CF_ALLOW_TRADE_INVITES); + client->Message(CHANNEL_COLOR_YELLOW,"You are %s accepting trade invites.", player->get_character_flag(CF_ALLOW_TRADE_INVITES)?"now":"no longer"); +} + +void Commands::Command_Toggle_Guilds(Client* client) +{ + Player* player = client->GetPlayer(); + + player->toggle_character_flag(CF_ALLOW_GUILD_INVITES); + client->Message(CHANNEL_COLOR_YELLOW,"You are %s accepting guild invites.", player->get_character_flag(CF_ALLOW_GUILD_INVITES)?"now":"no longer"); +} + +void Commands::Command_Toggle_Groups(Client* client) +{ + Player* player = client->GetPlayer(); + + player->toggle_character_flag(CF_ALLOW_GROUP_INVITES); + client->Message(CHANNEL_COLOR_YELLOW,"You are %s accepting group invites.", player->get_character_flag(CF_ALLOW_GROUP_INVITES)?"now":"no longer"); +} + +void Commands::Command_Toggle_Raids(Client* client) +{ + Player* player = client->GetPlayer(); + + player->toggle_character_flag(CF_ALLOW_RAID_INVITES); + client->Message(CHANNEL_COLOR_YELLOW,"You are %s accepting raid invites.", player->get_character_flag(CF_ALLOW_RAID_INVITES)?"now":"no longer"); +} + +void Commands::Command_Toggle_LON(Client* client) +{ + Player* player = client->GetPlayer(); + + player->toggle_character_flag(CF2_ALLOW_LON_INVITES); + client->Message(CHANNEL_COLOR_YELLOW,"You are %s accepting LoN invites.", player->get_character_flag(CF2_ALLOW_LON_INVITES)?"now":"no longer"); +} + +void Commands::Command_Toggle_VoiceChat(Client* client) +{ + Player* player = client->GetPlayer(); + + player->toggle_character_flag(CF2_ALLOW_VOICE_INVITES); + client->Message(CHANNEL_COLOR_YELLOW,"You are %s accepting voice chat invites.", player->get_character_flag(CF2_ALLOW_VOICE_INVITES)?"now":"no longer"); +} + +/* + Function: Command_TradeStart() + Purpose : Starts item/coin trade between players + Params : + Dev : + Example : +*/ +#include "../Trade.h" +void Commands::Command_TradeStart(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_START_TRADE"); + + Entity* trader = client->GetPlayer(); + Entity* trader2 = 0; + if (sep && sep->IsSet(0) && sep->IsNumber(0)) { + Spawn* spawn = client->GetPlayer()->GetSpawnWithPlayerID(atoi(sep->arg[0])); + if (spawn) { + if (spawn->IsEntity()) + trader2 = (Entity*)spawn; + } + } + else if (client->GetPlayer()->GetTarget()) { + if (client->GetPlayer()->GetTarget()->IsEntity()) + trader2 = (Entity*)client->GetPlayer()->GetTarget(); + } + + // can only trade with player or bots + if (trader && trader2) { + if (trader == trader2) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You can't trade with yourself."); + } + else if (trader2->IsPlayer() || trader2->IsBot()) { + LogWrite(PLAYER__ERROR, 0, "Trade", "creating trade"); + if (trader->trade) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You are already trading."); + else if (trader2->trade) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Your target is already trading."); + else { + Trade* trade = new Trade(trader, trader2); + trader->trade = trade; + trader2->trade = trade; + } + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You can only trade with another player or a bot."); + } + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Unable to find target"); +} + +/* + Function: Command_Track() + Purpose : Starts/Stops Tracking for a player + Params : + Dev : Scatman + Example : /track +*/ +void Commands::Command_Track(Client* client) +{ + if (!client->GetPlayer()->GetIsTracking()) + client->GetPlayer()->GetZone()->AddPlayerTracking(client->GetPlayer()); + else + client->GetPlayer()->GetZone()->RemovePlayerTracking(client->GetPlayer(), TRACKING_STOP); +} + +/* + Function: Command_TradeAccept() + Purpose : Accepts item/coin trade between players + Params : + Dev : + Example : +*/ +void Commands::Command_TradeAccept(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_ACCEPT_TRADE"); + Trade* trade = client->GetPlayer()->trade; + if (trade) { + bool trade_complete = trade->SetTradeAccepted(client->GetPlayer()); + if (trade_complete) + safe_delete(trade); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You are not currently trading."); +} + +/* + Function: Command_TradeReject() + Purpose : Rejects item/coin trade between players + Params : + Dev : + Example : +*/ +void Commands::Command_TradeReject(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_REJECT_TRADE"); + Command_TradeCancel(client, sep); +} + +/* + Function: Command_TradeCancel() + Purpose : Cancels item/coin trade between players + Params : + Dev : + Example : +*/ +void Commands::Command_TradeCancel(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_CANCEL_TRADE"); + Trade* trade = client->GetPlayer()->trade; + if (trade) { + trade->CancelTrade(client->GetPlayer()); + safe_delete(trade); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You are not currently trading."); +} + +/* + Function: Command_TradeSetCoin() + Purpose : Sets coin trade between players + Params : + Dev : + Example : +*/ +void Commands::Command_TradeSetCoin(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_SET_TRADE_COIN"); + LogWrite(MISC__TODO, 1, "Command", "TODO-Command: Set Trade Coin"); + client->Message(CHANNEL_COLOR_YELLOW, "You cannot trade with other players (Not Implemented)"); +} + +/* + Function: Command_TradeAddCoin() + Purpose : Adds coin to trade between players + Params : Passes "handler" through so we can process copper, silver, gold and plat in the same function + Dev : + Example : +*/ +void Commands::Command_TradeAddCoin(Client* client, Seperator* sep, int handler) +{ + PrintSep(sep, "COMMAND_ADD_TRADE_{coin type}"); + Trade* trade = client->GetPlayer()->trade; + if (trade) { + if (sep && sep->IsSet(0) && sep->IsNumber(0)) { + int32 amount = atoi(sep->arg[0]); + int64 val = 0; + switch (handler) { + case COMMAND_ADD_TRADE_COPPER: + { + val = amount; + trade->AddCoinToTrade(client->GetPlayer(), val); + break; + } + + case COMMAND_ADD_TRADE_SILVER: + { + val = amount * 100; + trade->AddCoinToTrade(client->GetPlayer(), val); + break; + } + + case COMMAND_ADD_TRADE_GOLD: + { + val = amount * 10000; + trade->AddCoinToTrade(client->GetPlayer(), val); + break; + } + + case COMMAND_ADD_TRADE_PLAT: + { + val = amount * 1000000; + trade->AddCoinToTrade(client->GetPlayer(), val); + break; + } + + default: + { + LogWrite(COMMAND__ERROR, 0, "Command", "No coin type specified in func: '%s', line %u", __FUNCTION__, __LINE__); + break; + } + } + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Invalid coin amount."); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You are not currently trading."); +} + +/* + Function: Command_TradeRemoveCoin() + Purpose : Removes coin from trade between players + Params : Passes "handler" through so we can process copper, silver, gold and plat in the same function + Dev : + Example : +*/ +void Commands::Command_TradeRemoveCoin(Client* client, Seperator* sep, int handler) +{ + PrintSep(sep, "COMMAND_REMOVE_TRADE_{coin type}"); + + Trade* trade = client->GetPlayer()->trade; + if (trade) { + if (sep && sep->IsSet(0) && sep->IsNumber(0)) { + int32 amount = atoi(sep->arg[0]); + int64 val = 0; + switch (handler) { + case COMMAND_REMOVE_TRADE_COPPER: + { + val = amount; + trade->RemoveCoinFromTrade(client->GetPlayer(), val); + break; + } + + case COMMAND_REMOVE_TRADE_SILVER: + { + val = amount * 100; + trade->RemoveCoinFromTrade(client->GetPlayer(), val); + break; + } + + case COMMAND_REMOVE_TRADE_GOLD: + { + val = amount * 10000; + trade->RemoveCoinFromTrade(client->GetPlayer(), val); + break; + } + + case COMMAND_REMOVE_TRADE_PLAT: + { + val = amount * 1000000; + trade->RemoveCoinFromTrade(client->GetPlayer(), val); + break; + } + + default: + { + LogWrite(COMMAND__ERROR, 0, "Command", "No coin type specified in func: '%s', line %u", __FUNCTION__, __LINE__); + break; + } + } + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Invalid coin amount"); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You are not currently trading."); +} + +/* + Function: Command_TradeAddItem() + Purpose : Adds item to trade between players + Params : + Dev : + Example : +*/ +void Commands::Command_TradeAddItem(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_ADD_TRADE_ITEM"); + /* + arg[0] = item index + arg[1] = slot + arg[2] = quantity + */ + if (!client->GetPlayer()->trade) { + LogWrite(PLAYER__ERROR, 0, "Trade", "Player is not currently trading."); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You are not currently trading."); + return; + } + + if (sep && sep->IsSet(0) && sep->IsNumber(0) && sep->IsSet(1) && sep->IsNumber(1) && sep->IsSet(2) && sep->IsNumber(2)) { + Item* item = 0; + int32 index = atoi(sep->arg[0]); + item = client->GetPlayer()->GetPlayerItemList()->GetItemFromIndex(index); + if (item) { + int8 result = client->GetPlayer()->trade->AddItemToTrade(client->GetPlayer(), item, atoi(sep->arg[2]), atoi(sep->arg[1])); + if (result == 1) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Item is already being traded."); + else if (result == 2) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You can't trade NO-TRADE items."); + else if (result == 3) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You can't trade HEIRLOOM items."); + else if (result == 254) + client->Message(CHANNEL_COLOR_YELLOW, "You are trading with an older client with a %u trade slot restriction...", client->GetPlayer()->trade->MaxSlots()); + else if (result == 255) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Unknown error trying to add the item to the trade..."); + } + else { + LogWrite(PLAYER__ERROR, 0, "Trade", "Unable to get an item for the player (%s) from the index (%u)", client->GetPlayer()->GetName(), index); + client->Message(CHANNEL_ERROR, "Unable to find item at index %u", index); + } + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Invalid item."); +} + +/* + Function: Command_TradeRemoveItem() + Purpose : Removes item from trade between players + Params : + Dev : + Example : +*/ +void Commands::Command_TradeRemoveItem(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_REMOVE_TRADE_ITEM"); + /* + arg[0] = trade window slot + */ + + Trade* trade = client->GetPlayer()->trade; + if (trade) { + if (sep && sep->IsSet(0) && sep->IsNumber(0)) { + trade->RemoveItemFromTrade(client->GetPlayer(), atoi(sep->arg[0])); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Invalid item."); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You are not currently trading."); +} + +void Commands::Command_TryOn(Client* client, Seperator* sep) +{ + Item *item = 0; + sint32 crc; + + if (sep) { + // 1096+ sends more info so need to do a version check so older clients (SF) still work + if (client->GetVersion() < 1096) { + if (sep->arg[1] && sep->IsNumber(1) && sep->arg[2] && sep->IsNumber(2)) + { + item = master_item_list.GetItem(atoul(sep->arg[1])); + crc = atol(sep->arg[2]); + } + } + else { + // From the broker and links in chat + if (strcmp(sep->arg[0], "crc") == 0) { + if (sep->IsNumber(2) && sep->IsNumber(3)) { + item = master_item_list.GetItem(atoul(sep->arg[2])); + crc = atol(sep->arg[3]); + } + } + // From inventory + if (strcmp(sep->arg[0], "dbid") == 0) { + if (sep->IsNumber(2) && sep->IsNumber(4)) { + item = master_item_list.GetItem(atoul(sep->arg[2])); + crc = atol(sep->arg[4]); + } + } + } + if (item) + client->ShowDressingRoom(item, crc); + } +} + +void Commands::Command_JoinChannel(Client * client, Seperator *sep) { + const char *channel_name, *password = NULL; + + if (sep == NULL || !sep->IsSet(0)) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /joinchannel [password]"); + return; + } + + channel_name = sep->arg[0]; + if (sep->IsSet(1)) + password = sep->arg[1]; + + if (!chat.ChannelExists(channel_name) && !chat.CreateChannel(channel_name, password)) { + client->Message(CHANNEL_COLOR_RED, "Unable to create channel '%s'.", channel_name); + return; + } + + if (chat.IsInChannel(client, channel_name)) { + client->Message(CHANNEL_NARRATIVE, "You are already in '%s'.", channel_name); + return; + } + + if (chat.HasPassword(channel_name)) { + if (password == NULL) { + client->Message(CHANNEL_NARRATIVE, "Unable to join '%s': That channel is password protected.", channel_name); + return; + } + if (!chat.PasswordMatches(channel_name, password)) { + client->Message(CHANNEL_NARRATIVE, "Unable to join '%s': The password is not correc.t", channel_name); + return; + } + } + + if (!chat.JoinChannel(client, channel_name)) + client->Message(CHANNEL_COLOR_RED, "There was an internal error preventing you from joining '%s'.", channel_name); +} + +void Commands::Command_JoinChannelFromLoad(Client * client, Seperator *sep) { + printf("ScatDebug: Received 'joinfromchannel', using the same function as 'joinchannel' (not sure what the difference is)\n"); + Command_JoinChannel(client, sep); +} + +void Commands::Command_TellChannel(Client *client, Seperator *sep) { + if (sep == NULL || !sep->IsSet(0) || !sep->IsSet(1)) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /tellchannel "); + PrintSep(sep, "tellchannel"); + return; + } + + chat.TellChannel(client, sep->arg[0], sep->argplus[1]); +} + +void Commands::Command_Test(Client* client, EQ2_16BitString* command_parms) { + Seperator* sep = new Seperator(command_parms->data.c_str(), ' ', 50, 500, true); + if (sep->IsSet(0)) { + if (atoi(sep->arg[0]) == 1) { + PacketStruct* packet2 = configReader.getStruct("WS_SpellGainedMsg", client->GetVersion()); + if (packet2) { + packet2->setDataByName("spell_type", 2); + packet2->setDataByName("spell_id", 8308); + packet2->setDataByName("spell_name", "Sprint"); + packet2->setDataByName("add_silently", 0); + packet2->setDataByName("tier", 1); + packet2->setDataByName("blah1", 1); + packet2->setDataByName("blah2", 1); + EQ2Packet* outapp = packet2->serialize(); + DumpPacket(outapp); + client->QueuePacket(outapp); + safe_delete(packet2); + } + } + else if (atoi(sep->arg[0]) == 2) { + PacketStruct* packet2 = configReader.getStruct("WS_GuildUpdate", client->GetVersion()); + if (packet2) { + packet2->setDataByName("guild_name", "Test"); + packet2->setDataByName("guild_motd", "Test MOTD"); + packet2->setDataByName("guild_id", 1234); + packet2->setDataByName("guild_level", 1); + packet2->setDataByName("unknown", 2); + packet2->setDataByName("unknown2", 3); + packet2->setDataByName("exp_current", 1); + packet2->setDataByName("exp_to_next_level", 4); + EQ2Packet* outapp = packet2->serialize(); + DumpPacket(outapp); + client->QueuePacket(outapp); + safe_delete(packet2); + } + } + else if (atoi(sep->arg[0]) == 3) { + PacketStruct* packet2 = configReader.getStruct("WS_JoinGuildNotify", client->GetVersion()); + if (packet2) { + packet2->setDataByName("guild_id", 1234); + packet2->setDataByName("character_id", 1); + packet2->setDataByName("account_id", 2); + packet2->setDataByName("guild_level", 1); + packet2->setDataByName("name", "Test"); + packet2->setDataByName("unknown2", 0); + packet2->setDataByName("unknown3", 1); + packet2->setDataByName("adventure_class", 6); + packet2->setDataByName("adventure_level", 7); + packet2->setDataByName("tradeskill_class", 4); + packet2->setDataByName("tradeskill_level", 5); + packet2->setDataByName("rank", 0); + packet2->setDataByName("member_flags", 2); + packet2->setDataByName("join_date", 1591112273); + packet2->setDataByName("guild_status", 2); + packet2->setDataByName("last_login", 1591132273); + packet2->setDataByName("recruiter_id", 1); + packet2->setDataByName("points", 2345); + packet2->setDataByName("note", "note"); + packet2->setMediumStringByName("officer_note", "O note"); + packet2->setMediumStringByName("zone", "Blah"); + + EQ2Packet* outapp = packet2->serialize(); + DumpPacket(outapp); + client->QueuePacket(outapp); + safe_delete(packet2); + } + } + else if (atoi(sep->arg[0]) == 5) { + int16 offset = atoi(sep->arg[1]); + int32 value1 = atol(sep->arg[2]); + EQ2Packet* outapp = client->GetPlayer()->GetPlayerInfo()->serialize(client->GetVersion(), offset, value1); + client->QueuePacket(outapp); + } + else if (atoi(sep->arg[0]) == 6) { + Spawn* spawn = client->GetPlayer()->GetTarget(); + if (!spawn) + spawn = client->GetPlayer(); + else if (spawn->IsEntity()) + ((Entity*)spawn)->SetSpeed(atof(sep->arg[4])); + spawn->RunToLocation(atof(sep->arg[1]), atof(sep->arg[2]), atof(sep->arg[3])); + } + else if (atoi(sep->arg[0]) == 7) { + int32 id = 0; + Spawn* spawn = client->GetCurrentZone()->GetSpawn(7720001); + if (spawn) { + spawn->SetX(client->GetPlayer()->GetX() + .5, false); + spawn->SetY(client->GetPlayer()->GetY(), false); + spawn->SetZ(client->GetPlayer()->GetZ() + .5, false); + float heading = client->GetPlayer()->GetHeading() + 180; + if (heading > 360) + heading -= 360; + spawn->SetLevel(5); + spawn->SetAdventureClass(4); + spawn->SetHeading(heading, false); + spawn->SetSpawnOrigX(spawn->GetX()); + spawn->SetSpawnOrigY(spawn->GetY()); + spawn->SetSpawnOrigZ(spawn->GetZ()); + spawn->SetSpawnOrigHeading(spawn->GetHeading()); + spawn->SetLocation(client->GetPlayer()->GetLocation()); + spawn->appearance.targetable = 1; + if (spawn->IsNPC() && spawn->GetTotalHP() == 0) { + spawn->SetTotalHP(spawn->GetLevel() * 15); + spawn->SetHP(spawn->GetTotalHP()); + } + if (spawn->GetTotalPower() == 0) { + spawn->SetTotalPower(spawn->GetLevel() * 15); + spawn->SetPower(spawn->GetTotalPower()); + } + int16 offset = atoi(sep->arg[1]); + int32 value1 = atol(sep->arg[2]); + sprintf(spawn->appearance.name, "Offset %i", offset); + EQ2Packet* ret = spawn->spawn_serialize(client->GetPlayer(), client->GetVersion(), offset, value1); + DumpPacket(ret); + client->QueuePacket(ret); + } + } + else if (atoi(sep->arg[0]) == 8) { + int32 id = atoi(sep->arg[1]); + Spawn* spawn = client->GetCurrentZone()->GetSpawn(id); + if (spawn) { + spawn->SetX(client->GetPlayer()->GetX() + .5, false); + spawn->SetY(client->GetPlayer()->GetY(), false); + spawn->SetZ(client->GetPlayer()->GetZ() + .5, false); + float heading = client->GetPlayer()->GetHeading() + 180; + if (heading > 360) + heading -= 360; + spawn->SetLevel(5); + spawn->SetAdventureClass(4); + spawn->SetHeading(heading, false); + spawn->SetSpawnOrigX(spawn->GetX()); + spawn->SetSpawnOrigY(spawn->GetY()); + spawn->SetSpawnOrigZ(spawn->GetZ()); + spawn->SetSpawnOrigHeading(spawn->GetHeading()); + spawn->SetLocation(client->GetPlayer()->GetLocation()); + spawn->appearance.targetable = 1; + if (spawn->IsNPC() && spawn->GetTotalHP() == 0) { + spawn->SetTotalHP(spawn->GetLevel() * 15); + spawn->SetHP(spawn->GetTotalHP()); + } + if (spawn->GetTotalPower() == 0) { + spawn->SetTotalPower(spawn->GetLevel() * 15); + spawn->SetPower(spawn->GetTotalPower()); + } + int16 offset = atoi(sep->arg[2]); + int16 offset2 = atoi(sep->arg[3]); + int32 value1 = atol(sep->arg[4]); + int16 offset3 = 0; + int16 offset4 = 0; + int32 value2 = 0; + if (sep->IsSet(7)) { + offset3 = atoi(sep->arg[5]); + offset4 = atoi(sep->arg[6]); + value2 = atol(sep->arg[7]); + } + sprintf(spawn->appearance.name, "Offset %i to %i", offset, offset2); + spawn->AddPrimaryEntityCommand("attack", 10000, "attack","", 0, 0); + EQ2Packet* ret = spawn->spawn_serialize(client->GetPlayer(), client->GetVersion(), offset, value1, offset2, offset3, offset4, value2); + DumpPacket(ret); + client->QueuePacket(ret); + } + } + else if (atoi(sep->arg[0]) == 9) { + PacketStruct* packet2 = configReader.getStruct("WS_DeathWindow", client->GetVersion()); + if (packet2) { + packet2->setArrayLengthByName("location_count", 1); + packet2->setArrayDataByName("location_ida", 1234); + packet2->setArrayDataByName("unknown2a", 3); + packet2->setArrayDataByName("zone_name", "Queen's Colony"); + packet2->setArrayDataByName("location_name", "Myrrin's Tower"); + packet2->setArrayDataByName("distance", 134); + EQ2Packet* app = packet2->serialize(); + if (sep->IsSet(2)) { + int8 offset = atoi(sep->arg[1]); + uchar* ptr2 = app->pBuffer; + ptr2 += offset; + if (sep->IsNumber(2)) { + int32 value1 = atol(sep->arg[2]); + if (value1 > 0xFFFF) + memcpy(ptr2, (uchar*)&value1, 4); + else if (value1 > 0xFF) + memcpy(ptr2, (uchar*)&value1, 2); + else + memcpy(ptr2, (uchar*)&value1, 1); + } + else { + int8 len = strlen(sep->arg[2]); + memcpy(ptr2, (uchar*)&len, 1); + ptr2 += 1; + memcpy(ptr2, sep->arg[2], len); + } + } + DumpPacket(app); + client->QueuePacket(app); + safe_delete(packet2); + } + } + else if (atoi(sep->arg[0]) == 10) { + PacketStruct* packet2 = configReader.getStruct("WS_QuestJournalUpdate", client->GetVersion()); + if (packet2) { + packet2->setArrayLengthByName("num_quests", 1); + packet2->setArrayDataByName("active", 1); + packet2->setArrayDataByName("name", "Tasks aboard the Far Journey"); + packet2->setArrayDataByName("quest_type", "Hallmark"); + packet2->setArrayDataByName("quest_zone", "Hallmark"); + packet2->setArrayDataByName("journal_updated", 1); + packet2->setArrayDataByName("quest_id", 524); + packet2->setArrayDataByName("day", 19); + packet2->setArrayDataByName("month", 6); + packet2->setArrayDataByName("year", 20); + packet2->setArrayDataByName("level", 2); + packet2->setArrayDataByName("encounter_level", 4); + packet2->setArrayDataByName("difficulty", 3); + packet2->setArrayDataByName("visible", 1); + packet2->setDataByName("visible_quest_id", 524); + packet2->setDataByName("player_crc", 2900677088); + packet2->setDataByName("player_name", "LethalEncounter"); + EQ2Packet* app = packet2->serialize(); + if (sep->IsSet(2)) { + int8 offset = atoi(sep->arg[1]); + uchar* ptr2 = app->pBuffer; + ptr2 += offset; + if (sep->IsNumber(2)) { + int32 value1 = atol(sep->arg[2]); + if (value1 > 0xFFFF) + memcpy(ptr2, (uchar*)&value1, 4); + else if (value1 > 0xFF) + memcpy(ptr2, (uchar*)&value1, 2); + else + memcpy(ptr2, (uchar*)&value1, 1); + } + else { + int8 len = strlen(sep->arg[2]); + memcpy(ptr2, (uchar*)&len, 1); + ptr2 += 1; + memcpy(ptr2, sep->arg[2], len); + } + } + DumpPacket(app); + client->QueuePacket(app); + safe_delete(packet2); + } + } + else if (atoi(sep->arg[0]) == 11) { + PacketStruct* packet2 = configReader.getStruct("WS_QuestJournalReply", client->GetVersion()); + if (packet2) { + packet2->setDataByName("quest_id", 524); + packet2->setDataByName("player_crc", 2900677088); + packet2->setDataByName("name", "Tasks aboard the Far Journey"); + packet2->setDataByName("description", "I completed all the tasks assigned to me by Captain Varlos aboard the Far Journey"); + packet2->setDataByName("type", "Hallmark"); + packet2->setDataByName("complete_header", "To complete this quest, I must do the following tasks:"); + packet2->setDataByName("day", 19); + packet2->setDataByName("month", 6); + packet2->setDataByName("year", 20); + packet2->setDataByName("level", 1); + packet2->setDataByName("encounter_level", 1); + packet2->setDataByName("difficulty", 1); + packet2->setDataByName("time_obtained", Timer::GetUnixTimeStamp()); + //packet2->setDataByName("timer_start", Timer::GetUnixTimeStamp()); + //packet2->setDataByName("timer_duration", 300); + //packet2->setDataByName("timer_running", 1); + packet2->setArrayLengthByName("task_groups_completed", 9); + packet2->setArrayLengthByName("num_task_groups", 10); + packet2->setArrayDataByName("task_group", "I spoke to Waulon as Captain Varlos had asked of me."); + packet2->setArrayDataByName("task_group", "I found Waulon's hat in one of the boxes.", 1); + packet2->setArrayDataByName("task_group", "I returned Waulon's hat.", 2); + packet2->setArrayDataByName("task_group", "I have spoken to Ingrid.", 3); + packet2->setArrayDataByName("task_group", "I purchased a Shard of Luclin.", 4); + packet2->setArrayDataByName("task_group", "I gave the Shard of Luclin to Ingrid.", 5); + packet2->setArrayDataByName("task_group", "I have spoken to Captain Varlos.", 6); + packet2->setArrayDataByName("task_group", "I killed the rats that Captain Varlos requested.", 7); + packet2->setArrayDataByName("task_group", "Captain Varlos has ordered you to kill the escaped goblin.", 8); + packet2->setArrayDataByName("task_group", "I killed the escaped goblin.", 9); + /*packet2->setSubArrayLengthByName("num_tasks", 1); + packet2->setSubArrayDataByName("task", "I need to talk to Garven Tralk"); + packet2->setSubArrayLengthByName("num_updates", 1); + packet2->setSubArrayDataByName("update_currentval", 0); + packet2->setSubArrayDataByName("update_maxval", 1); + packet2->setSubArrayDataByName("icon", 11); + */ + packet2->setArrayDataByName("waypoint", 0xFFFFFFFF); + packet2->setDataByName("journal_updated", 1); + packet2->setDataByName("bullets", 1); + EQ2Packet* app = packet2->serialize(); + if (sep->IsSet(2)) { + int16 offset = atoi(sep->arg[1]); + uchar* ptr2 = app->pBuffer; + ptr2 += offset; + if (sep->IsNumber(2)) { + int32 value1 = atol(sep->arg[2]); + if (value1 > 0xFFFF) + memcpy(ptr2, (uchar*)&value1, 4); + else if (value1 > 0xFF) + memcpy(ptr2, (uchar*)&value1, 2); + else + memcpy(ptr2, (uchar*)&value1, 1); + } + else { + int16 len = strlen(sep->arg[2]); + memcpy(ptr2, (uchar*)&len, 2); + ptr2 += 2; + memcpy(ptr2, sep->arg[2], len); + } + } + DumpPacket(app); + client->QueuePacket(app); + safe_delete(packet2); + } + } + else if (atoi(sep->arg[0]) == 12) { + PacketStruct* packet2 = configReader.getStruct("WS_QuestJournalReply", client->GetVersion()); + if (packet2) { + packet2->setDataByName("quest_id", 5725); + packet2->setDataByName("player_crc", 2900677088); + packet2->setDataByName("name", "Archetype Selection"); + packet2->setDataByName("description", "I have reported my profession to Garven Tralk."); + packet2->setDataByName("type", "Hallmark"); + packet2->setDataByName("complete_header", "To complete this quest, I must do the following tasks:"); + packet2->setDataByName("day", 19); + packet2->setDataByName("month", 6); + packet2->setDataByName("year", 20); + packet2->setDataByName("level", 2); + packet2->setDataByName("encounter_level", 4); + packet2->setDataByName("difficulty", 3); + packet2->setDataByName("time_obtained", Timer::GetUnixTimeStamp()); + packet2->setDataByName("timer_start", Timer::GetUnixTimeStamp()); + packet2->setDataByName("timer_duration", 300); + packet2->setDataByName("timer_running", 1); + packet2->setArrayLengthByName("task_groups_completed", 0); + packet2->setArrayLengthByName("num_task_groups", 1); + packet2->setArrayDataByName("task_group", "I need to talk to Garven Tralk"); + packet2->setSubArrayLengthByName("num_tasks", 1); + packet2->setSubArrayDataByName("task", "I need to talk to Garven Tralk"); + packet2->setSubArrayLengthByName("num_updates", 1); + packet2->setSubArrayDataByName("update_currentval", 0); + packet2->setSubArrayDataByName("update_maxval", 1); + packet2->setSubArrayDataByName("icon", 11); + + packet2->setArrayDataByName("waypoint", 0xFFFFFFFF); + packet2->setDataByName("journal_updated", 1); + packet2->setSubstructDataByName("reward_data", "unknown1", 255); + packet2->setSubstructDataByName("reward_data", "reward", "Quest Reward!"); + packet2->setSubstructDataByName("reward_data", "unknown2", 0x3f); + packet2->setSubstructDataByName("reward_data", "coin", 150); + packet2->setSubstructDataByName("reward_data", "status_points", 5); + packet2->setSubstructDataByName("reward_data", "exp_bonus", 10); + packet2->setSubstructArrayLengthByName("reward_data", "num_rewards", 1); + packet2->setSubstructArrayDataByName("reward_data", "reward_id", 123); + Item* item = master_item_list.GetItem(152755); + packet2->setArrayDataByName("reward_id", item->details.item_id); + packet2->setItemArrayDataByName("item", item, client->GetPlayer(), 0, 0, client->GetClientItemPacketOffset()); + /*packet2->setSubstructDataByName("item", "unique_id", 567); + packet2->setSubstructDataByName("item", "broker_item_id", 0xFFFFFFFFFFFFFFFF); + packet2->setSubstructDataByName("item", "icon", 0xe7); + packet2->setSubstructDataByName("item", "tier", 4); + packet2->setSubstructDataByName("item", "flags", 0x60); + packet2->setSubstructArrayLengthByName("item", "stat_count", 1); + packet2->setSubstructDataByName("item", "stat_type", 1); + packet2->setSubstructDataByName("item", "stat_subtype", 2); + packet2->setSubstructDataByName("item", "value", 3); + packet2->setSubstructDataByName("item", "condition", 100); + packet2->setSubstructDataByName("item", "weight", 1); + packet2->setSubstructDataByName("item", "skill_req1", 0xacafa99e); + packet2->setSubstructDataByName("item", "skill_req2", 0xacafa99e); + packet2->setSubstructDataByName("item", "skill_min", 1); + packet2->setSubstructArrayLengthByName("item", "class_count", 3); + packet2->setSubstructDataByName("item", "adventure_class", 1); + packet2->setSubstructDataByName("item", "adventure_class", 11, 0, 1); + packet2->setSubstructDataByName("item", "adventure_class", 0x1f, 0, 2); + packet2->setSubstructDataByName("item", "tradeskill_class", 255); + packet2->setSubstructDataByName("item", "tradeskill_class", 255, 0, 1); + packet2->setSubstructDataByName("item", "tradeskill_class", 255, 0, 2); + packet2->setSubstructDataByName("item", "level", 0x1e); + packet2->setSubstructDataByName("item", "level", 100, 0, 1); + packet2->setSubstructDataByName("item", "level", 100, 0, 2); + packet2->setSubstructDataByName("item_footer", "name", "Footman Gloves");*/ + //packet2->PrintPacket(); + EQ2Packet* app = packet2->serialize(); + if (sep->IsSet(2)) { + int16 offset = atoi(sep->arg[1]); + uchar* ptr2 = app->pBuffer; + ptr2 += offset; + if (sep->IsNumber(2)) { + int32 value1 = atol(sep->arg[2]); + if (value1 > 0xFFFF) + memcpy(ptr2, (uchar*)&value1, 4); + else if (value1 > 0xFF) + memcpy(ptr2, (uchar*)&value1, 2); + else + memcpy(ptr2, (uchar*)&value1, 1); + } + else { + int16 len = strlen(sep->arg[2]); + memcpy(ptr2, (uchar*)&len, 2); + ptr2 += 2; + memcpy(ptr2, sep->arg[2], len); + } + } + DumpPacket(app); + client->QueuePacket(app); + safe_delete(packet2); + } + } + else if (atoi(sep->arg[0]) == 13) { + PacketStruct* packet2 = configReader.getStruct("WS_OnScreenMsg", client->GetVersion()); + if (packet2 && sep->IsSet(7)) { + packet2->setDataByName("unknown", atoi(sep->arg[1])); + char blah[128]; + sprintf(blah, "\\#6EFF6EYou get better at \12\\#C8FFC8%s\\#6EFF6E! (7/15)", sep->arg[2]); + packet2->setDataByName("text", blah); + packet2->setDataByName("message_type", sep->arg[3]); + packet2->setDataByName("size", atof(sep->arg[4])); + packet2->setDataByName("red", atoi(sep->arg[5])); + packet2->setDataByName("green", atoi(sep->arg[6])); + packet2->setDataByName("blue", atoi(sep->arg[7])); + EQ2Packet* app = packet2->serialize(); + DumpPacket(app); + client->QueuePacket(app); + safe_delete(packet2); + } + } + else if (atoi(sep->arg[0]) == 14) { + PacketStruct* packet2 = configReader.getStruct("WS_InstructionWindow", client->GetVersion()); + if (packet2 && sep->IsSet(3)) { + packet2->setDataByName("open_seconds_min", atof(sep->arg[1])); + packet2->setDataByName("open_seconds_max", atof(sep->arg[2])); + packet2->setDataByName("voice_sync", atoi(sep->arg[3])); + packet2->setDataByName("text", "Welcome to Norrath, the world of EverQuest II. Left click on the help button at any time for more detailed help and information."); + packet2->setDataByName("voice", "voiceover/english/narrator/boat_06p_tutorial02/narrator_001_63779ca0.mp3"); + packet2->setArrayLengthByName("num_goals", 1); + //packet2->setArrayDataByName("goal_text", ) + packet2->setSubArrayLengthByName("num_tasks", 1); + packet2->setSubArrayDataByName("task_text", "continue"); + packet2->setDataByName("complete_sound", "click"); + packet2->setDataByName("signal", "introduction"); + packet2->setDataByName("voice_key1", 0xcda65173); + packet2->setDataByName("voice_key2", 0x984bfc6d); + EQ2Packet* app = packet2->serialize(); + DumpPacket(app); + client->QueuePacket(app); + safe_delete(packet2); + packet2 = configReader.getStruct("WS_ShowWindow", client->GetVersion()); + packet2->setDataByName("window", "MainHUD.StartMenu"); + packet2->setDataByName("show", 1); + app = packet2->serialize(); + DumpPacket(app); + client->QueuePacket(app); + safe_delete(packet2); + + packet2 = configReader.getStruct("WS_FlashWindow", client->GetVersion()); + packet2->setDataByName("window", "MainHUD.StartMenu.help"); + packet2->setDataByName("flash_seconds", 10); + app = packet2->serialize(); + DumpPacket(app); + client->QueuePacket(app); + safe_delete(packet2); + } + } + else if (atoi(sep->arg[0]) == 15) { + PacketStruct* packet2 = configReader.getStruct("WS_UpdateLoot", client->GetVersion()); + if (packet2) { + packet2->setArrayLengthByName("loot_count", 1); + packet2->setArrayDataByName("name", "Test"); + packet2->setArrayDataByName("item_id", 1234); + packet2->setArrayDataByName("count", 1); + packet2->setArrayDataByName("icon", 258); + packet2->setArrayDataByName("ability_id", 0xFFFFFFFF); + Spawn* spawn = client->GetPlayer()->GetTarget(); + if (spawn) + packet2->setDataByName("object_id", client->GetPlayer()->GetIDWithPlayerSpawn(spawn)); + packet2->setDataByName("display", 1); + packet2->setDataByName("loot_type", 1); + packet2->setDataByName("lotto_timeout", 60); + EQ2Packet* app = packet2->serialize(); + DumpPacket(app); + client->QueuePacket(app); + safe_delete(packet2); + } + } + else if (atoi(sep->arg[0]) == 16 && sep->IsNumber(1)) { + char blah[32]; + sprintf(blah, "Testing: %i", atoi(sep->arg[1])); + client->SimpleMessage(atoi(sep->arg[1]), blah); + } + else if (atoi(sep->arg[0]) == 17 && sep->IsNumber(2)) { + if (sep->IsSet(2)) { + int16 offset = atoi(sep->arg[1]); + if (sep->IsNumber(2)) { + int32 value1 = atol(sep->arg[2]); + client->QueuePacket(client->GetPlayer()->GetPlayerInfo()->serialize(client->GetVersion(), offset, value1)); + cout << "Sent" << endl; + } + } + } + else if (atoi(sep->arg[0]) == 18) { + PacketStruct* packet2 = configReader.getStruct("WS_QuestRewardPackMsg", client->GetVersion()); + if (packet2) { + packet2->setSubstructDataByName("reward_data", "unknown1", 255); + packet2->setSubstructDataByName("reward_data", "reward", "Quest Reward!"); + packet2->setSubstructDataByName("reward_data", "max_coin", 0); + packet2->setSubstructDataByName("reward_data", "text", "Some custom text to mess things up?"); + packet2->setSubstructDataByName("reward_data", "status_points", 5); + //packet2->setSubstructDataByName("reward_data", "exp_bonus", 10); + packet2->setSubstructArrayLengthByName("reward_data", "num_rewards", 1); + Item* item = new Item(master_item_list.GetItem(9357)); + packet2->setArrayDataByName("reward_id", item->details.item_id); + //item->stack_count = 20; + packet2->setItemArrayDataByName("item", item, client->GetPlayer(), 0, 0, client->GetClientItemPacketOffset()); + safe_delete(item); + /*item = new Item(master_item_list.GetItem(36685)); + item->stack_count = 20; + packet2->setArrayDataByName("reward_id", item->details.item_id); + packet2->setItemArrayDataByName("item", item, client->GetPlayer(), 1, 0, -1); + safe_delete(item); + item = master_item_list.GetItem(1414); + packet2->setArrayDataByName("reward_id", item->details.item_id); + packet2->setItemArrayDataByName("item", item, client->GetPlayer(), 2, 0, -1); + item = master_item_list.GetItem(75057); + packet2->setArrayDataByName("reward_id", item->details.item_id); + packet2->setItemArrayDataByName("item", item, client->GetPlayer(), 3, 0, -1);*/ + EQ2Packet* app = packet2->serialize(); + if (sep->IsSet(2)) { + int16 offset = atoi(sep->arg[1]); + uchar* ptr2 = app->pBuffer; + ptr2 += offset; + if (sep->IsNumber(2)) { + int32 value1 = atol(sep->arg[2]); + if (value1 > 0xFFFF) + memcpy(ptr2, (uchar*)&value1, 4); + else if (value1 > 0xFF) + memcpy(ptr2, (uchar*)&value1, 2); + else + memcpy(ptr2, (uchar*)&value1, 1); + } + else { + int16 len = strlen(sep->arg[2]); + memcpy(ptr2, (uchar*)&len, 2); + ptr2 += 2; + memcpy(ptr2, sep->arg[2], len); + } + } + DumpPacket(app); + client->QueuePacket(app); + safe_delete(packet2); + } + } + else if (atoi(sep->arg[0]) == 19) { + PacketStruct* packet2 = configReader.getStruct("WS_UpdateLoot", client->GetVersion()); + Spawn* spawn = client->GetPlayer()->GetTarget(); + if (packet2 && spawn) { + Item* item = master_item_list.GetItem(130053); + packet2->setArrayLengthByName("loot_count", 1); + packet2->setArrayDataByName("loot_id", item->details.item_id); + packet2->setArrayDataByName("unknown2", 1); + packet2->setItemArrayDataByName("item", item, client->GetPlayer(), 0, 0, 2, true); + packet2->setDataByName("display", 1); + packet2->setDataByName("unknown2b", 1); + packet2->setDataByName("unknown3", 0x3c); + packet2->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(spawn)); + packet2->PrintPacket(); + EQ2Packet* app = packet2->serialize(); + if (sep->IsSet(2)) { + int16 offset = atoi(sep->arg[1]); + int16 offset2 = 0; + int32 value1 = 0; + if (sep->IsSet(3)) { + offset2 = atoi(sep->arg[2]); + value1 = atol(sep->arg[3]); + } + else + value1 = atol(sep->arg[2]); + int16 offset3 = 0; + int16 offset4 = 0; + int32 value2 = 0; + if (sep->IsSet(6)) { + offset3 = atoi(sep->arg[4]); + offset4 = atoi(sep->arg[5]); + value2 = atol(sep->arg[6]); + } + offset--; + if (offset2 > 0 && offset2 >= offset) { + offset2--; + uchar* ptr2 = app->pBuffer; + ptr2 += offset; + for (int i = offset; i <= offset2; i++) { + if (value1 > 0xFFFF) { + memcpy(ptr2, (uchar*)&value1, 4); + i += 3; + ptr2 += 3; + } + else if (value1 > 0xFF) { + memcpy(ptr2, (uchar*)&value1, 2); + i++; + ptr2++; + } + else + memcpy(ptr2, (uchar*)&value1, 1); + ptr2++; + } + } + if (offset4 > 0 && offset4 >= offset3) { + offset3--; + offset4--; + uchar* ptr2 = app->pBuffer; + ptr2 += offset3; + for (int i = offset3; i <= offset4; i++) { + if (value2 > 0xFFFF) { + memcpy(ptr2, (uchar*)&value2, 4); + i += 3; + ptr2 += 3; + } + else if (value2 > 0xFF) { + memcpy(ptr2, (uchar*)&value2, 2); + i++; + ptr2++; + } + else + memcpy(ptr2, (uchar*)&value2, 1); + ptr2++; + } + } + } + DumpPacket(app); + client->QueuePacket(app); + safe_delete(packet2); + } + } + else if (atoi(sep->arg[0]) == 21) { + PacketStruct* packet2 = configReader.getStruct("WS_OfferQuest", client->GetVersion()); + if (packet2) { + packet2->setDataByName("unknown0", 255); + packet2->setDataByName("reward", "New Quest"); + packet2->setDataByName("title", "Title"); + packet2->setDataByName("description", "description"); + packet2->setDataByName("quest_difficulty", 3); + packet2->setDataByName("unknown1", 5); + packet2->setDataByName("level", 3); + packet2->setDataByName("coin", 150); + packet2->setDataByName("status_points", 5); + packet2->setDataByName("exp_bonus", 10); + packet2->setArrayLengthByName("num_rewards", 1); + Item* item = new Item(master_item_list.GetItem(36212)); + packet2->setArrayDataByName("reward_id", item->details.item_id); + item->stack_count = 20; + packet2->setItemArrayDataByName("item", item, client->GetPlayer(), 0, 0, client->GetClientItemPacketOffset()); + safe_delete(item); + char accept[35] = { 0 }; + char decline[35] = { 0 }; + sprintf(accept, "q_accept_pending_quest %u", 0); + sprintf(decline, "q_deny_pending_quest %u", 0); + packet2->setDataByName("accept_command", accept); + packet2->setDataByName("decline_command", decline); + EQ2Packet* app = packet2->serialize(); + if (sep->IsSet(2)) { + int16 offset = atoi(sep->arg[1]); + uchar* ptr2 = app->pBuffer; + ptr2 += offset; + if (sep->IsNumber(2)) { + int32 value1 = atol(sep->arg[2]); + if (value1 > 0xFFFF) + memcpy(ptr2, (uchar*)&value1, 4); + else if (value1 > 0xFF) + memcpy(ptr2, (uchar*)&value1, 2); + else + memcpy(ptr2, (uchar*)&value1, 1); + } + DumpPacket(app); + client->QueuePacket(app); + safe_delete(packet2); + } + } + } + else if (atoi(sep->arg[0]) == 22) { //same as 21, but 8bit string + PacketStruct* packet2 = configReader.getStruct("WS_OfferQuest", client->GetVersion()); + if (packet2) { + packet2->setDataByName("unknown0", 255); + packet2->setDataByName("reward", "New Quest"); + packet2->setDataByName("title", "Title"); + packet2->setDataByName("description", "description"); + packet2->setDataByName("quest_difficulty", 3); + packet2->setDataByName("unknown1", 5); + packet2->setDataByName("level", 3); + packet2->setDataByName("coin", 150); + packet2->setDataByName("status_points", 5); + packet2->setDataByName("exp_bonus", 10); + packet2->setArrayLengthByName("num_rewards", 1); + Item* item = new Item(master_item_list.GetItem(36212)); + packet2->setArrayDataByName("reward_id", item->details.item_id); + item->stack_count = 20; + packet2->setItemArrayDataByName("item", item, client->GetPlayer(), 0, 0, client->GetClientItemPacketOffset()); + safe_delete(item); + char accept[35] = { 0 }; + char decline[35] = { 0 }; + sprintf(accept, "q_accept_pending_quest %u", 0); + sprintf(decline, "q_deny_pending_quest %u", 0); + packet2->setDataByName("accept_command", accept); + packet2->setDataByName("decline_command", decline); + EQ2Packet* app = packet2->serialize(); + if (sep->IsSet(2)) { + int16 offset = atoi(sep->arg[1]); + uchar* ptr2 = app->pBuffer; + ptr2 += offset; + if (sep->IsNumber(2)) { + int32 value1 = atol(sep->arg[2]); + if (value1 > 0xFFFF) + memcpy(ptr2, (uchar*)&value1, 4); + else if (value1 > 0xFF) + memcpy(ptr2, (uchar*)&value1, 2); + else + memcpy(ptr2, (uchar*)&value1, 1); + } + else { + int8 len = strlen(sep->arg[2]); + memcpy(ptr2, (uchar*)&len, 1); + ptr2 += 1; + memcpy(ptr2, sep->arg[2], len); + } + } + DumpPacket(app); + client->QueuePacket(app); + safe_delete(packet2); + } + } + else if (atoi(sep->arg[0]) == 23) { + if (client->GetPlayer()->GetTarget()) { + PacketStruct* packet2 = configReader.getStruct("WS_UpdateMerchant", client->GetVersion()); + if (packet2) { + Spawn* target = client->GetPlayer()->GetTarget(); + packet2->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(target)); + Item* item = new Item(master_item_list.GetItem(12565)); + int8 i = 0; + packet2->setArrayLengthByName("num_items", 1); + packet2->setArrayDataByName("item_name", item->name.c_str(), i); + packet2->setArrayDataByName("item_id", item->details.item_id, i); + packet2->setArrayDataByName("stack_size", item->stack_count, i); + packet2->setArrayDataByName("icon", item->GetIcon(client->GetVersion()), i); + int8 tmp_level = 0; + if (item->generic_info.adventure_default_level > 0) + tmp_level = item->generic_info.adventure_default_level; + else + tmp_level = item->generic_info.tradeskill_default_level; + packet2->setArrayDataByName("level", tmp_level, i); + packet2->setArrayDataByName("tier", item->details.tier, i); + packet2->setArrayDataByName("item_id2", item->details.item_id, i); + int8 item_difficulty = client->GetPlayer()->GetArrowColor(tmp_level); + if (item_difficulty != ARROW_COLOR_WHITE && item_difficulty != ARROW_COLOR_RED && item_difficulty != ARROW_COLOR_GRAY) + item_difficulty = ARROW_COLOR_WHITE; + item_difficulty -= 6; + if (item_difficulty < 0) + item_difficulty *= -1; + packet2->setArrayDataByName("item_difficulty", item_difficulty, i); + packet2->setArrayDataByName("quantity", 2, i); + packet2->setArrayDataByName("unknown5", 255, i); + packet2->setArrayDataByName("stack_size2", item->stack_count, i); + packet2->setArrayDataByName("description", item->description.c_str(), i); + packet2->setDataByName("type", 2); + EQ2Packet* app = packet2->serialize(); + if (sep->IsSet(2)) { + int16 offset = atoi(sep->arg[1]); + uchar* ptr2 = app->pBuffer; + ptr2 += offset; + if (sep->IsNumber(2)) { + int32 value1 = atol(sep->arg[2]); + if (value1 > 0xFFFF) + memcpy(ptr2, (uchar*)&value1, 4); + else if (value1 > 0xFF) + memcpy(ptr2, (uchar*)&value1, 2); + else + memcpy(ptr2, (uchar*)&value1, 1); + } + else { + int8 len = strlen(sep->arg[2]); + memcpy(ptr2, (uchar*)&len, 1); + ptr2 += 1; + memcpy(ptr2, sep->arg[2], len); + } + } + DumpPacket(app); + client->QueuePacket(app); + safe_delete(packet2); + } + } + } + else if (atoi(sep->arg[0]) == 24) { + PacketStruct* packet2 = configReader.getStruct("WS_EnableGameEvent", client->GetVersion()); + if (packet2) { + if (sep->IsSet(2)) { + packet2->setDataByName("event_name", sep->arg[1]); + packet2->setDataByName("enabled", atoi(sep->arg[2])); + EQ2Packet* app = packet2->serialize(); + DumpPacket(app); + client->QueuePacket(app); + safe_delete(packet2); + } + } + } + else if (atoi(sep->arg[0]) == 25) { + if (sep->IsSet(1)) { + Widget* new_spawn = new Widget(); + int32 id = atoul(sep->arg[1]); + new_spawn->SetWidgetID(id); + EQ2Packet* ret = new_spawn->serialize(client->GetPlayer(), client->GetVersion()); + client->QueuePacket(ret); + int8 index = client->GetPlayer()->GetIndexForSpawn(new_spawn); + PacketStruct* packet2 = configReader.getStruct("WS_DestroyGhostCmd", client->GetVersion()); + if (packet2) { + packet2->setDataByName("spawn_index", index); + packet2->setDataByName("delete", 1); + EQ2Packet* app = packet2->serialize(); + client->QueuePacket(app); + safe_delete(packet2); + } + } + } + else if (atoi(sep->arg[0]) == 26) { + if (sep->IsSet(4)) { + Widget* new_spawn = new Widget(); + int32 id = atoul(sep->arg[1]); + new_spawn->SetWidgetID(id); + float x = atof(sep->arg[2]); + float y = atof(sep->arg[3]); + float z = atof(sep->arg[4]); + new_spawn->SetLocation(client->GetPlayer()->GetLocation()); + new_spawn->SetWidgetX(x); + new_spawn->SetWidgetY(y); + new_spawn->SetWidgetZ(z); + new_spawn->SetX(x); + new_spawn->SetY(y); + new_spawn->SetZ(z); + EQ2Packet* ret = new_spawn->serialize(client->GetPlayer(), client->GetVersion()); + client->QueuePacket(ret); + } + } + else if (atoi(sep->arg[0]) == 27) { + Spawn* target = client->GetPlayer()->GetTarget(); + PacketStruct* packet2 = configReader.getStruct("WS_HearSimpleDamage", client->GetVersion()); + if (packet2 && target && sep->IsSet(4)) { + client->GetPlayer()->GetZone()->SendDamagePacket(client->GetPlayer(), target, atoul(sep->arg[1]), atoul(sep->arg[2]), atoul(sep->arg[3]), atoul(sep->arg[4]), sep->arg[5] != nullptr ? sep->arg[5] : ""); + } + } + else if (atoi(sep->arg[0]) == 28 && sep->IsNumber(1)) { + World::newValue = strtoull(sep->arg[1], NULL, 0); + } + else if (atoi(sep->arg[0]) == 29 && sep->IsNumber(1)) { + client->SendHearCast(client->GetPlayer(), client->GetPlayer()->GetTarget() ? client->GetPlayer()->GetTarget() : client->GetPlayer(), + strtoull(sep->arg[1], NULL, 0), atoul(sep->arg[2])); + } + else if (atoi(sep->arg[0]) == 30) { + PacketStruct* packet = configReader.getStruct("WS_UpdateSkillBook", client->GetVersion()); + if (packet) { + packet->setDataByName("unknown", World::newValue); + EQ2Packet* outapp = packet->serialize(); + DumpPacket(outapp); + client->QueuePacket(outapp); + safe_delete(packet); + } + } + else if (atoi(sep->arg[0]) == 31) { + client->SendRecipeList(); + } + else if (atoi(sep->arg[0]) == 32 && sep->IsNumber(1) && sep->IsNumber(2)) { + if(client->GetVersion() <= 561) { + int32 param = atoul(sep->arg[1]); + int32 paramvalue = atoul(sep->arg[2]); + client->Message(CHANNEL_COLOR_YELLOW, "Send control flag param %u param value %u", param, paramvalue); + ClientPacketFunctions::SendServerControlFlagsClassic(client, param, paramvalue); + } + else if(sep->IsNumber(3)) { + int8 param1 = atoul(sep->arg[1]); + int8 param2 = atoul(sep->arg[2]); + int8 paramval = atoul(sep->arg[3]); + client->Message(CHANNEL_COLOR_YELLOW, "Send control flag param1 %u param2 %u param value %u", param1, param2, paramval); + ClientPacketFunctions::SendServerControlFlags(client, param1, param2, paramval); + } + } + else if (atoi(sep->arg[0]) == 33 && sep->IsNumber(1) && sep->IsNumber(2)) { + client->GetCurrentZone()->SendHealPacket(client->GetPlayer(), client->GetPlayer()->GetTarget() ? client->GetPlayer()->GetTarget() : client->GetPlayer(), + atoul(sep->arg[1]), atoul(sep->arg[2]), "TestSpell"); + } + else if(atoi(sep->arg[0]) == 34 && sep->IsNumber(1) && sep->IsNumber(2)) { + PacketStruct* packet = configReader.getStruct("WS_QuestRewardPackMsg", client->GetVersion()); + packet->setSubstructDataByName("reward_data", "unknown1", atoi(sep->arg[1])); + Item* item = master_item_list.GetItem(atoul(sep->arg[2])); + if(item) { + packet->setSubstructArrayLengthByName("reward_data", "num_select_rewards", 1); + packet->setArrayDataByName("select_reward_id", item->details.item_id, 0); + packet->setItemArrayDataByName("select_item", item, client->GetPlayer(), 0, 0, World::newValue - 2); + } + + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + else { + PacketStruct* packet2 = configReader.getStruct("WS_ExaminePartialSpellInfo", client->GetVersion()); + if (packet2) { + packet2->setSubstructDataByName("info_header", "show_name", 1); + packet2->setSubstructDataByName("info_header", "unknown", 7); + packet2->setSubstructDataByName("info_header", "unknown2", 1); + packet2->setSubstructDataByName("info_header", "unknown3", 1); + packet2->setSubstructDataByName("info_header", "unknown4", 1); + packet2->setSubstructDataByName("info_header", "unknown5", 1); + packet2->setSubstructDataByName("info_header", "unknown6", "Testing"); + packet2->setSubstructDataByName("info_header", "unknown7", "Testing"); + packet2->setSubstructDataByName("info_header", "unknown8", 66); + packet2->setSubstructDataByName("info_header", "title", "Blah Title Blah"); + packet2->setSubstructDataByName("info_header", "title_text", "Blah Blah"); + packet2->setSubstructDataByName("info_header", "title_text2", "Blah Blah2"); + packet2->setSubstructDataByName("info_header", "show_popup", 1); + packet2->setSubstructDataByName("info_header", "packettype", 3); + packet2->setSubstructDataByName("spell_info", "skill_name", "Testing"); + packet2->setSubstructDataByName("spell_info", "level", 19); + packet2->setSubstructDataByName("spell_info", "tier", 1); + packet2->setSubstructDataByName("spell_info", "health_cost", 5); + packet2->setSubstructDataByName("spell_info", "min_class_skill_req", 3); + packet2->setSubstructDataByName("spell_info", "mana_cost", 6); + packet2->setSubstructDataByName("spell_info", "req_concentration", 7); + packet2->setSubstructDataByName("spell_info", "cast_time", 200); + packet2->setSubstructDataByName("spell_info", "recovery", 220); + packet2->setSubstructDataByName("spell_info", "recast", 280); + packet2->setSubstructDataByName("spell_info", "beneficial", 1); + packet2->setSubstructDataByName("spell_info", "maintained", 1); + packet2->setSubstructDataByName("spell_info", "spell_book_type", 1); + packet2->setSubstructDataByName("spell_info", "quality", 3); + + packet2->setSubstructDataByName("spell_info", "test_1a", 1); + packet2->setSubstructDataByName("spell_info", "test_1b", 1); + packet2->setSubstructDataByName("spell_info", "test_1c", 1); + packet2->setSubstructDataByName("spell_info", "test_1d", 1); + packet2->setSubstructDataByName("spell_info", "test_2a", 2); + packet2->setSubstructDataByName("spell_info", "test_2b", 2); + packet2->setSubstructDataByName("spell_info", "test_2c", 2); + packet2->setSubstructDataByName("spell_info", "test_2d", 2); + packet2->setSubstructDataByName("spell_info", "test_3", 3); + packet2->setSubstructDataByName("spell_info", "test_4", 4); + packet2->setSubstructDataByName("spell_info", "test_5", 5); + packet2->setSubstructDataByName("spell_info", "test_6", 6); + packet2->setSubstructDataByName("spell_info", "min_class_skill_req", 1); + packet2->setSubstructDataByName("spell_info", "min_class_skill_rec", 30123); + packet2->setSubstructDataByName("spell_info", "num_reagents", 1); + packet2->setSubstructArrayLengthByName("spell_info", "num_reagents", 1); + packet2->setArrayDataByName("reagent", "Alcohol"); + packet2->setArrayDataByName("consumed", "123"); + packet2->setSubstructDataByName("spell_info", "class_skill", 52); + packet2->setSubstructDataByName("spell_info", "id", 8308); + packet2->setSubstructDataByName("spell_info", "icon", 303); + packet2->setSubstructDataByName("spell_info", "icon2", 0xFFFF); + packet2->setSubstructDataByName("spell_info", "icontype", 317); + packet2->setSubstructDataByName("spell_info", "type", 2); + packet2->setSubstructDataByName("spell_info", "spell_text_color", 255); + packet2->setSubstructDataByName("spell_info", "duration1", 600); + packet2->setSubstructDataByName("spell_info", "duration2", 600); + packet2->setSubstructDataByName("spell_info", "name", "Sprint"); + packet2->setSubstructDataByName("spell_info", "description", "Test description"); + EQ2Packet* outapp = packet2->serialize(); + DumpPacket(outapp); + client->QueuePacket(outapp); + safe_delete(packet2); + } + } + return; + PacketStruct* p = configReader.getStruct("WS_EqTargetItemCmd", client->GetVersion()); + if (!p) return; + + //Seperator* sep2 = new Seperator(command_parms->data.c_str(), ' ', 50, 500, true); + //client->GetCurrentZone()->SendSpellFailedPacket(client, atoi(sep2->arg[0])); + //} + + + + + + + + + + + + + + /*Seperator* sep2 = new Seperator(command_parms->data.c_str(), ' ', 50, 500, true); + if (sep2 && sep2->arg[0] && sep2->IsNumber(0)) { + client->SetPendingFlightPath(atoi(sep2->arg[0])); + PacketStruct* packet = configReader.getStruct("WS_ReadyForTakeOff", client->GetVersion()); + if (packet) { + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + }*/ + + /*if (client->GetCurrentZone()->Grid != nullptr) { + int32 numFaces = 0; + int32 numGrids = client->GetPlayer()->Cell_Info.CurrentCell->FaceList.size(); + client->Message(CHANNEL_COLOR_YELLOW, "Num grids in cell: %u", numGrids); + + map >::iterator itr; + for (itr = client->GetPlayer()->Cell_Info.CurrentCell->FaceList.begin(); itr != client->GetPlayer()->Cell_Info.CurrentCell->FaceList.end(); itr++) + numFaces += (*itr).second.size(); + + client->Message(CHANNEL_COLOR_YELLOW, "Num faces in cell: %u", numFaces); + }*/ + + //uchar blah[] = { + // 1208 - OP_EQUpdateStoreCmd + // /*0x00,0x3A,*/0x2B,0x00,0x00,0x00,0xFF,0x78,0x02,0x53,0x2C,0x33,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + + // /*0x58,*/0x47,0x35,0xD3,0x45,0x42,0x42,0x51,0x00,0x40,0xD1,0xE7,0x57,0xB1,0x51,0xB1,0xBB,0xBB,0xB1,0x3B,0xB0,0xBB,0xB0,0x3B,0x59,0x85,0x5B,0x47,0x1D,0x9C,0x3B,0x3A,0x1B,0xB8,0xD9,0x64,0x14,0x85,0x9F,0x4C,0xC8,0x09,0x21,0xA4,0xB3,0x7F,0x45,0x90,0x0B,0x79,0x90,0x0F,0x31,0x28,0x80,0x42,0x28,0x82,0x62,0x28,0x81,0x52,0x28,0x83,0x38,0x94,0x43,0x05,0x54,0x42,0x02,0xAA,0xA0,0x1A,0x6A,0xA0,0x16,0xEA,0xA0,0x1E,0x1A,0xA0,0x11,0x9A,0xA0,0x19,0x5A,0xA0,0x15,0xDA,0xA0,0x1D,0x3A,0xA0,0x13,0xBA,0xA0,0x1B,0x7A,0xA0,0x17,0xFA,0xA0,0x1F,0x06,0x60,0x10,0x86,0x60,0x18,0x46,0x60,0x14,0xC6,0x60,0x1C,0x26,0x20,0x09,0x93,0x30,0x05,0xD3,0x30,0x03,0xB3,0x30,0x07,0xF3,0xB0,0x00,0x8B,0xB0,0x04,0xCB,0xB0,0x02,0xAB,0xB0,0x06,0xEB,0xB0,0x01,0x29,0xD8,0x84,0x2D,0xD8,0x86,0x1D,0xD8,0x85,0x3D,0xD8,0x87,0x03,0x38,0x84,0x23,0x38,0x86,0x13,0x38,0x85,0x33,0x38,0x87,0x0B,0xB8,0x84,0x34,0x5C,0xC1,0x35,0xDC,0xC0,0x2D,0xDC,0xC1,0x3D,0x3C,0xC0,0x23,0x3C,0xC1,0x33,0xBC,0xC0,0x2B,0xBC,0xC1,0x3B,0x7C,0xC0,0x27,0x7C,0xC1,0x37,0x64,0xE0,0xFF,0xD3,0xF0,0x0B,0x29,0x4A,0xD8,0x68 + + // 1193 - -- OP_ClientCmdMsg::OP_EqUpdateMerchantCmd -- + // /*0x00,0x3A,*/0x38,0x00,0x00,0x00,0xFF,0x77,0x02,0xA6,0xAA,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x3F,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x3F,0x00,0x00,0x00,0x00,0xA8,0x55,0xEC,0x8F,0x7F,0x70,0x31,0x08,0x40,0x71,0x3A,0x8B,0xB4,0x55,0xEC,0x8F,0x00 + /*}; + + Seperator* sep2 = new Seperator(command_parms->data.c_str(), ' ', 50, 500, true); + int8 val = 0; + int16 pos = 0; + int idx = 0; + while(sep2 && sep2->arg[idx+1] && sep2->IsNumber(idx) && sep2->IsNumber(idx+1)){ + pos = atoi(sep2->arg[idx]); + val = atoi(sep2->arg[idx+1]); + memset(blah+pos, val, 1); + idx+=2; + } + + DumpPacket(blah, sizeof(blah)); + client->QueuePacket(new EQ2Packet(OP_GroupCreatedMsg , blah, sizeof(blah)));*/ + + if (client->GetPlayer()->GetTarget()) { + PacketStruct* packet = configReader.getStruct("WS_SetControlGhost", client->GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(client->GetPlayer()->GetTarget())); + packet->setDataByName("size", 0); + packet->setDataByName("unknown2", 0); + EQ2Packet* app = packet->serialize(); + client->QueuePacket(app); + safe_delete(packet); + } + + PacketStruct* set_pov = configReader.getStruct("WS_SetPOVGhostCmd", client->GetVersion()); + if (set_pov) { + set_pov->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(client->GetPlayer()->GetTarget())); + EQ2Packet* app_pov = set_pov->serialize(); + client->QueuePacket(app_pov); + safe_delete(set_pov); + } + } + + Seperator* sep2 = new Seperator(command_parms->data.c_str(), ' ', 50, 500, true); + + PacketStruct* packet = configReader.getStruct("WS_OpenCharCust", client->GetVersion()); + if (packet) { + + + if (sep2 && sep2->arg[0] && sep2->IsNumber(0)) + packet->setDataByName("Type", atoi(sep2->arg[0])); + else + packet->setDataByName("Type", 2); + + if (sep2 && sep2->arg[1] && sep2->IsNumber(1)) + packet->setDataByName("race_id", atoi(sep2->arg[1])); + else + packet->setDataByName("race_id", 0); + + if (sep2 && sep2->arg[2] && sep2->IsNumber(2)) + packet->setDataByName("gender", atoi(sep2->arg[2])); + else + packet->setDataByName("gender", 2); + + if (sep2 && sep2->arg[3] && sep2->IsNumber(3)) + packet->setDataByName("unknown", atoi(sep2->arg[3]), 0); + else + packet->setDataByName("unknown", 0, 0); + + if (sep2 && sep2->arg[4] && sep2->IsNumber(4)) + packet->setDataByName("unknown", atoi(sep2->arg[4]), 1); + else + packet->setDataByName("unknown", 0, 1); + + if (sep2 && sep2->arg[5] && sep2->IsNumber(5)) + packet->setDataByName("unknown", atoi(sep2->arg[5]), 2); + else + packet->setDataByName("unknown", 0, 2); + + + client->QueuePacket(packet->serialize()); + } + safe_delete(packet); + safe_delete(sep2); +} + +void Commands::Command_LeaveChannel(Client *client, Seperator *sep) { + const char *channel_name; + + if (sep == NULL || !sep->IsSet(0)) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /leavechat "); + PrintSep(sep); + return; + } + + channel_name = sep->arg[0]; + + if (!chat.IsInChannel(client, channel_name)) + client->Message(CHANNEL_NARRATIVE, "Unable to leave '%s': You are not in the channel.", channel_name); + else if (!chat.LeaveChannel(client, channel_name)) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "There was an internal error preventing you from leaving that channel MUAHAHA"); +} + +/* + Function: + Purpose : + Params : + Dev : + Example : +*/ +void Commands::Command_WeaponStats(Client* client) +{ + Player* player = client->GetPlayer(); + Spawn* target = player->GetTarget(); + + Item* primary = player->GetEquipmentList()->GetItem(EQ2_PRIMARY_SLOT); + Item* secondary = player->GetEquipmentList()->GetItem(EQ2_SECONDARY_SLOT); + Item* ranged = player->GetEquipmentList()->GetItem(EQ2_RANGE_SLOT); + const char* charName = player->GetName(); + if(target && target->IsEntity()) { + primary = ((Entity*)target)->GetEquipmentList()->GetItem(EQ2_PRIMARY_SLOT); + secondary = ((Entity*)target)->GetEquipmentList()->GetItem(EQ2_SECONDARY_SLOT); + ranged = ((Entity*)target)->GetEquipmentList()->GetItem(EQ2_RANGE_SLOT); + charName = target->GetName(); + } + else { + target = nullptr; + } + client->Message(0, "WeaponStats for %s", charName); + client->SimpleMessage(0, "Primary:"); + if (primary) { + client->Message(0, "Name: %s", primary->name.c_str()); + client->Message(0, "Base Damage: %u - %u", primary->weapon_info->damage_low3, primary->weapon_info->damage_high3); + client->Message(0, "Mastery Damage: %u - %u", primary->weapon_info->damage_low2, primary->weapon_info->damage_high2); + client->Message(0, "Damage: %u - %u", primary->weapon_info->damage_low1, primary->weapon_info->damage_high1); + client->Message(0, "Actual Damage: %u - %u", target ? ((Entity*)target)->GetPrimaryWeaponMinDamage() : player->GetPrimaryWeaponMinDamage(), + target ? ((Entity*)target)->GetPrimaryWeaponMaxDamage() : player->GetPrimaryWeaponMaxDamage()); + client->Message(0, "Actual Delay: %u", target ? ((Entity*)target)->GetPrimaryWeaponDelay() : player->GetPrimaryWeaponDelay()); + client->Message(0, "Proc Percent: %d%%", 0); + client->Message(0, "Procs Per Minute: %d", 0); + } + else { + client->SimpleMessage(0, "Name: fist"); + client->Message(0, "Base Damage: %u - %u", target ? ((Entity*)target)->GetPrimaryWeaponMinDamage() : player->GetPrimaryWeaponMinDamage(), + target ? ((Entity*)target)->GetPrimaryWeaponMaxDamage() : player->GetPrimaryWeaponMaxDamage()); + client->Message(0, "Actual Damage: %u - %u", target ? ((Entity*)target)->GetPrimaryWeaponMinDamage() : player->GetPrimaryWeaponMinDamage(), + target ? ((Entity*)target)->GetPrimaryWeaponMaxDamage() : player->GetPrimaryWeaponMaxDamage()); + client->Message(0, "Actual Delay: %d", target ? ((Entity*)target)->GetPrimaryWeaponDelay() : player->GetPrimaryWeaponDelay() * 0.1); + client->Message(0, "Proc Percent: %d%%", 0); + client->Message(0, "Procs Per Minute: %d", 0); + } + client->SimpleMessage(0, " "); + client->SimpleMessage(0, " "); + if (secondary) { + client->SimpleMessage(0, "Secondary:"); + client->Message(0, "Name: %s", secondary->name.c_str()); + client->Message(0, "Base Damage: %u - %u", secondary->weapon_info->damage_low3, secondary->weapon_info->damage_high3); + client->Message(0, "Mastery Damage: %u - %u", secondary->weapon_info->damage_low2, secondary->weapon_info->damage_high2); + client->Message(0, "Damage: %u - %u", secondary->weapon_info->damage_low1, secondary->weapon_info->damage_high1); + client->Message(0, "Actual Damage: %u - %u", target ? ((Entity*)target)->GetSecondaryWeaponMinDamage() : player->GetSecondaryWeaponMinDamage(), + target ? ((Entity*)target)->GetSecondaryWeaponMaxDamage() : player->GetSecondaryWeaponMaxDamage()); + client->Message(0, "Actual Delay: %d", target ? ((Entity*)target)->GetSecondaryWeaponDelay() : player->GetSecondaryWeaponDelay() * 0.1); + client->Message(0, "Proc Percent: %d%%", 0); + client->Message(0, "Procs Per Minute: %d", 0); + client->SimpleMessage(0, " "); + client->SimpleMessage(0, " "); + } + client->SimpleMessage(0, "Ranged:"); + if (ranged) { + client->Message(0, "Name: %s", ranged->name.c_str()); + client->Message(0, "Base Damage: %u - %u", ranged->ranged_info->weapon_info.damage_low3, ranged->ranged_info->weapon_info.damage_high3); + client->Message(0, "Mastery Damage: %u - %u", ranged->ranged_info->weapon_info.damage_low2, ranged->ranged_info->weapon_info.damage_high2); + client->Message(0, "Damage: %u - %u", ranged->ranged_info->weapon_info.damage_low1, ranged->ranged_info->weapon_info.damage_high1); + client->Message(0, "Actual Damage: %u - %u", target ? ((Entity*)target)->GetRangedWeaponMinDamage() : player->GetRangedWeaponMinDamage(), + target ? ((Entity*)target)->GetRangedWeaponMaxDamage() : player->GetRangedWeaponMaxDamage()); + client->Message(0, "Actual Delay: %d", target ? ((Entity*)target)->GetRangeWeaponDelay() : player->GetRangeWeaponDelay() * 0.1); + client->Message(0, "Proc Percent: %d%%", 0); + client->Message(0, "Procs Per Minute: %d", 0); + } + else + client->SimpleMessage(0, "None"); + +} + +void Commands::Command_WhoChannel(Client *client, Seperator *sep) { + const char *channel_name; + + if (sep == NULL || !sep->IsSet(0)) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /chatwho "); + return; + } + + channel_name = sep->arg[0]; + + if (!chat.ChannelExists(channel_name)) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "That channel does not exist!"); + else + chat.SendChannelUserList(client, channel_name); +} + +void Commands::Command_ZoneSafeCoords(Client *client, Seperator *sep) +{ + ZoneServer* zone = 0; + int32 zone_id = client->GetPlayer()->GetZone()->GetZoneID(); + + if (zone_id > 0) + { + zone = zone_list.Get(zone_id, false, false, false); + if (zone) + { + zone->SetSafeX(client->GetPlayer()->GetX()); + zone->SetSafeY(client->GetPlayer()->GetY()); + zone->SetSafeZ(client->GetPlayer()->GetZ()); + zone->SetSafeHeading(client->GetPlayer()->GetHeading()); + if( database.SaveZoneSafeCoords(zone_id, zone->GetSafeX(), zone->GetSafeY(), zone->GetSafeZ(), zone->GetSafeHeading()) ) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Zone safe coordinates updated!"); + else + client->SimpleMessage(CHANNEL_COLOR_RED, "FAILED to update zone safe coordinates!"); + } + } +} + +/* + Function: + Purpose : + Params : + Dev : + Example : +*/ +void Commands::Command_ZoneDetails(Client* client, Seperator* sep) +{ + ZoneInfo* zone_info = new ZoneInfo; + + if (sep && sep->arg[0]) + { + if (sep->IsNumber(0)) + zone_info->id = atoi(sep->arg[0]); + else + zone_info->id = database.GetZoneID(sep->arg[0]); + + if (zone_info->id > 0) + { + database.LoadZoneInfo(zone_info); + client->Message(CHANNEL_COLOR_YELLOW, "id: %u, name: %s, file: %s, description: %s", zone_info->id, zone_info->name, zone_info->file, zone_info->description); + client->Message(CHANNEL_COLOR_YELLOW, "safe_x: %f, safe_y: %f, safe_z: %f, underworld: %f", zone_info->safe_x, zone_info->safe_y, zone_info->safe_z, zone_info->underworld); + client->Message(CHANNEL_COLOR_YELLOW, "min_status: %u, min_level: %u, max_level: %u, xp_modifier: %u", zone_info->min_status, zone_info->min_level, zone_info->max_level, zone_info->xp_modifier); + client->Message(CHANNEL_COLOR_YELLOW, "instance_type: %u, shutdown_timer: %u, ruleset_id: %u", zone_info->instance_type, zone_info->shutdown_timer, zone_info->ruleset_id); + client->Message(CHANNEL_COLOR_YELLOW, "default_reenter_time: %u, default_reset_time: %u, default_lockout_time: %u", zone_info->default_reenter_time, zone_info->default_reenter_time, zone_info->default_lockout_time); + client->Message(CHANNEL_COLOR_YELLOW, "force_group_to_zone: %u, expansion_id: %u, min_version: %u", zone_info->force_group_to_zone, zone_info->expansion_id, zone_info->min_version); + client->Message(CHANNEL_COLOR_YELLOW, "always_loaded: %u, city_zone: %u, start_zone: %u, weather_allowed: %u", zone_info->always_loaded, zone_info->city_zone, zone_info->start_zone, zone_info->weather_allowed); + client->Message(CHANNEL_COLOR_YELLOW, "zone_type: %s, sky_file: %s", zone_info->zone_type, zone_info->sky_file); + client->Message(CHANNEL_COLOR_YELLOW, "lua_script: %s", zone_info->lua_script); + client->Message(CHANNEL_COLOR_YELLOW, "zone_motd: %s", zone_info->zone_motd); + } + else + client->Message(CHANNEL_COLOR_YELLOW, "The zone name or ID '%s' does not exist.", sep->arg[0]); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /zone details [zone id|zone name]"); + + safe_delete(zone_info); +} + +/* + Function: + Purpose : + Params : + Dev : + Example : +*/ +void Commands::Command_ZoneSet(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0] && sep->arg[1] && sep->arg[2]) + { + ZoneServer* zone = 0; + int32 zone_id = 0; + + if (sep->IsNumber(0) && atoi(sep->arg[0]) > 0) + { + zone_id = atoi(sep->arg[0]); + zone = zone_list.Get(atoi(sep->arg[0]), false, false, false); + } + else + { + zone_id = database.GetZoneID(sep->arg[0]); + + if (zone_id > 0) + zone = zone_list.Get(sep->arg[0], false, false, false); + } + + if (zone_id > 0) + { + if (zone_set_values.count(string(sep->arg[1])) > 0) + SetZoneCommand(client, zone_id, zone, zone_set_values[sep->arg[1]], sep->arg[2]); + else + client->Message(CHANNEL_COLOR_YELLOW, "The attribute '%s' is not valid.", sep->arg[1]); + } + else + client->Message(CHANNEL_COLOR_YELLOW, "The zone name or ID '%s' does not exist.", sep->arg[0]); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /zone set [zone id|zone name] [attribute] [value]"); +} + +void Commands::Command_Rain(Client* client, Seperator* sep) { + if (sep == NULL || !sep->IsSet(0) || !sep->IsNumber(0)) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /rain "); + return; + } + + client->Message(CHANNEL_COLOR_YELLOW,"Setting rain to %.2f", atof(sep->arg[0])); + client->GetCurrentZone()->SetRain(atof(sep->arg[0])); + client->GetCurrentZone()->SetCurrentWeather(atof(sep->arg[0])); +} + +void Commands::Command_Wind(Client* client, Seperator* sep) { + if (sep == NULL || !sep->IsSet(0) || !sep->IsNumber(0)) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /wind "); + return; + } + + client->Message(CHANNEL_COLOR_YELLOW, "Setting wind to %.2f", atof(sep->arg[0])); + client->GetCurrentZone()->SetWind(atof(sep->arg[0])); +} + + +void Commands::Command_SendMerchantWindow(Client* client, Seperator* sep, bool sell) { + Spawn* spawn = client->GetPlayer()->GetTarget(); + if(client->GetVersion() < 561) { + sell = false; // doesn't support in the same way as AoM just open the normal buy/sell window + } + if(spawn) { + client->SetMerchantTransaction(spawn); + if (spawn->GetMerchantID() > 0 && spawn->IsClientInMerchantLevelRange(client)){ + client->SendHailCommand(spawn); + //MerchantFactionMultiplier* multiplier = world.GetMerchantMultiplier(spawn->GetMerchantID()); + //if(!multiplier || (multiplier && client->GetPlayer()->GetFactions()->GetFactionValue(multiplier->faction_id) >= multiplier->faction_min)){ + client->SendBuyMerchantList(sell); + if(!(spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY)) + client->SendSellMerchantList(sell); + if(!(spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY_BACK)) + client->SendBuyBackList(sell); + + if(client->GetVersion() > 561) { + PacketStruct* packet = configReader.getStruct("WS_UpdateMerchant", client->GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", 0xFFFFFFFF); + packet->setDataByName("type", 16); + EQ2Packet* outapp = packet->serialize(); + if (outapp) + client->QueuePacket(outapp); + safe_delete(packet); + } + } + } + if (spawn->GetMerchantType() & MERCHANT_TYPE_REPAIR) + client->SendRepairList(); + } + // client->SimpleMessage(CHANNEL_COLOR_RED, "Your faction is too low to use this merchant."); +} + + +void Commands::Command_Weather(Client* client, Seperator* sep) +{ + //PrintSep(sep, "Weather"); + + if( sep && sep->arg[0] ) + { + ZoneServer* zsZone = client->GetCurrentZone(); + const char* value = sep->arg[0]; + + // process single-param commands first + if( strncasecmp(value, "details", strlen(value)) == 0 ) + { + client->Message(CHANNEL_COLOR_YELLOW, "Weather Details for zone %s: ", zsZone->GetZoneName()); + client->Message(CHANNEL_COLOR_YELLOW, "enabled: %i, allowed: %i, type: %i, frequency: %u", zsZone->isWeatherEnabled(), zsZone->isWeatherAllowed(), zsZone->GetWeatherType(), zsZone->GetWeatherFrequency()); + client->Message(CHANNEL_COLOR_YELLOW, "severity: %.2f = %.2f, current: %.2f", zsZone->GetWeatherMinSeverity(), zsZone->GetWeatherMaxSeverity(), zsZone->GetCurrentWeather()); + client->Message(CHANNEL_COLOR_YELLOW, "pattern: %i, chance: %i, amount: %.2f, offset: %.2f", zsZone->GetWeatherPattern(), zsZone->GetWeatherChance(), zsZone->GetWeatherChangeAmount(), zsZone->GetWeatherDynamicOffset()); + } + else if( strncasecmp(value, "process", strlen(value)) == 0 ) + { + zsZone->SetWeatherLastChangedTime(Timer::GetUnixTimeStamp() - zsZone->GetWeatherFrequency()); + zsZone->ProcessWeather(); + } + else if( strncasecmp(value, "reset", strlen(value)) == 0 ) + { + zsZone->SetWeatherType(rule_manager.GetGlobalRule(R_Zone, WeatherType)->GetInt8()); + zsZone->SetWeatherFrequency(rule_manager.GetGlobalRule(R_Zone, WeatherChangeFrequency)->GetInt32()); + zsZone->SetWeatherMinSeverity(rule_manager.GetGlobalRule(R_Zone, MinWeatherSeverity)->GetFloat()); + zsZone->SetWeatherMaxSeverity(rule_manager.GetGlobalRule(R_Zone, MaxWeatherSeverity)->GetFloat()); + zsZone->SetCurrentWeather(zsZone->GetWeatherMinSeverity()); + zsZone->SetWeatherPattern(1); + zsZone->SetWeatherChance(rule_manager.GetGlobalRule(R_Zone, WeatherChangeChance)->GetInt8()); + zsZone->SetWeatherChangeAmount(rule_manager.GetGlobalRule(R_Zone, WeatherChangePerInterval)->GetFloat()); + zsZone->SetWeatherDynamicOffset(rule_manager.GetGlobalRule(R_Zone, WeatherDynamicMaxOffset)->GetFloat()); + zsZone->SetWeatherLastChangedTime(Timer::GetUnixTimeStamp() - zsZone->GetWeatherFrequency()); + zsZone->ProcessWeather(); + } + + // process commands with params + if( sep->arg[1] ) + { + if( strncasecmp(value, "enable", strlen(value)) == 0 && sep->IsNumber(1) ) + zsZone->SetWeatherEnabled( ( atoi(sep->arg[1]) == 1 ) ? true : false ); + else if( strncasecmp(value, "type", strlen(value)) == 0 && sep->IsNumber(1) && (atoi(sep->arg[1]) >= 0 && atoi(sep->arg[1]) <= 3) ) + zsZone->SetWeatherType(atoi(sep->arg[1])); + else if( strncasecmp(value, "frequency", strlen(value)) == 0 && sep->IsNumber(1)) + zsZone->SetWeatherFrequency( atoul(sep->arg[1]) ); + else if( strncasecmp(value, "range", strlen(value)) == 0 && sep->IsNumber(1) && sep->IsNumber(2) ) { + zsZone->SetWeatherMinSeverity(atof(sep->arg[1])); + zsZone->SetWeatherMaxSeverity(atof(sep->arg[2])); + zsZone->SetRain(zsZone->GetWeatherMinSeverity()); + } + else if( strncasecmp(value, "current", strlen(value)) == 0 && sep->IsNumber(1) ) { + zsZone->SetCurrentWeather(atof(sep->arg[1])); + zsZone->SetRain(zsZone->GetCurrentWeather()); + } + else if( strncasecmp(value, "pattern", strlen(value)) == 0 && sep->IsNumber(1) && (atoi(sep->arg[1]) >= 0 && atoi(sep->arg[1]) <= 2) ) + zsZone->SetWeatherPattern( atoi(sep->arg[1]) ); + else if( strncasecmp(value, "chance", strlen(value)) == 0 && sep->IsNumber(1) && (atoi(sep->arg[1]) >= 0 && atoi(sep->arg[1]) <= 100) ) + zsZone->SetWeatherChance( atoi(sep->arg[1]) ); + else if( strncasecmp(value, "amount", strlen(value)) == 0 && sep->IsNumber(1) && (atoi(sep->arg[1]) >= 0 && atoi(sep->arg[1]) <= 1) ) + zsZone->SetWeatherChangeAmount( atof(sep->arg[1]) ); + else if( strncasecmp(value, "offset", strlen(value)) == 0 && sep->IsNumber(1) && (atoi(sep->arg[1]) >= 0 && atoi(sep->arg[1]) <= 1) ) + zsZone->SetWeatherDynamicOffset( atof(sep->arg[1]) ); + } + } + else + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /weather [command] [param]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Commands: enable (0|1), type (0-3), frequency (sec), range|current (0.0 - 1.0)"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Commands: details, process, pattern (0|1), chance (%), amount (float), offset (float)"); + return; + } + +} + +void Commands::Command_Select(Client* client, Seperator* sep) { + if (sep && sep->arg[1]) { + Spawn* spawn = client->GetPlayer()->GetTarget(); + if (spawn && strlen(sep->arg[1]) > 0) + client->GetCurrentZone()->CallSpawnScript(spawn, SPAWN_SCRIPT_CUSTOM, client->GetPlayer(), sep->arg[1]); + } +} + +/* + Function: Command_ConsumeFood() + Purpose : Consume Food/Drink and apply mods + Params : Slot ID - EQ2_FOOD_SLOT 22, EQ2_DRINK_SLOT 23 + Dev : Zcoretri + Example : /consume_food 22 +*/ +void Commands::Command_ConsumeFood(Client* client, Seperator* sep) { + if (sep && sep->arg[0] && sep->IsNumber(0)) + { + Player* player = client->GetPlayer(); + int32 slot = atoul(sep->arg[0]); + if (client->GetVersion() <= 373) { + if(client->GetVersion() <= 561) { + if(slot <= 255) { + slot = 255 - slot; + } + else { + if(slot == 256) { // first "new" item to inventory is assigned index 256 by client + slot = client->GetPlayer()->item_list.GetFirstNewItem(); + } + else { + // otherwise the slot has to be mapped out depending on the amount of new items + index sent in + slot = client->GetPlayer()->item_list.GetNewItemByIndex((int16)slot - 255); + } + } + } + + Item* item = client->GetPlayer()->item_list.GetItemFromIndex(slot); + if(item && item->IsFood()) { + if(client->CheckConsumptionAllowed(slot)) { + if(item->IsFoodFood()) + slot = EQ2_FOOD_SLOT; + else if(item->IsFoodDrink()) + slot = EQ2_DRINK_SLOT; + else + return; // not valid item + + client->ConsumeFoodDrink(item, slot); + } + } + } + else { + if (client->GetVersion() > 373 && client->GetVersion() <= 561) { + slot += 2; + } + Item* item = player->GetEquipmentList()->GetItem(slot); + + if(client->CheckConsumptionAllowed(slot)) { + client->ConsumeFoodDrink(item, slot); + } + } + } +} + +void Commands::Command_Aquaman(Client* client, Seperator* sep) { + if (sep && sep->arg[0] && sep->IsNumber(0)) { + if (atoi(sep->arg[0]) == 1) { + client->GetPlayer()->GetInfoStruct()->set_vision(4); + client->GetPlayer()->GetInfoStruct()->set_breathe_underwater(1); + client->GetPlayer()->SetCharSheetChanged(true); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Maybe you ought to stick to the shallow end until you know how to swim."); + } + else { + client->GetPlayer()->GetInfoStruct()->set_vision(0); + client->GetPlayer()->GetInfoStruct()->set_breathe_underwater(0); + client->GetPlayer()->SetCharSheetChanged(true); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Aquaman mode turned off."); + } + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /aquaman [0|1]"); +} + +void Commands::Command_ReportBug(Client* client, Seperator* sep) +{ + if(sep) + { + string data; + + if(sep->arg[0]){ + data = string(sep->arg[0]); + } + + for(int i=1;iGetMaxArgNum();i++){ + if(sep->arg[i]) + data.append(" ").append(sep->arg[i]); + } + + if(!sep->IsSet(7)){ + data.append(" ").append(std::to_string(client->GetVersion())).append("\a"); + } + + const char* target_name = 0; + int32 spawn_id = 0; + + if(client->GetPlayer()->GetTarget()) + { + target_name = client->GetPlayer()->GetTarget()->GetName(); + spawn_id = client->GetPlayer()->GetTarget()->GetDatabaseID(); + } + else + target_name = "N/A"; + + LogWrite(COMMAND__DEBUG, 1, "Command", "%s", data.c_str()); + + if(world.ReportBug(data, client->GetPlayer()->GetName(), client->GetAccountID(), target_name, spawn_id, client->GetCurrentZone()->GetZoneID())) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Successfully submitted bug."); + else + client->SimpleMessage(CHANNEL_COLOR_RED, "Error submitting bug."); + } +} + +void Commands::Command_Attune_Inv(Client* client, Seperator* sep) { + PrintSep(sep, "Command_Attune_Inv"); + if (sep && sep->arg[0] && sep->IsNumber(0)) { + // Get the item index from the first parameter + int16 index = atoi(sep->arg[0]); + // Check to see if this is a valid item index for the player, if not exit out of the function + if (client->GetPlayer()->item_list.indexed_items.count(index) == 0) { + LogWrite(ITEM__DEBUG, 0, "Items", "%s has no item with an index of %i", client->GetPlayer()->GetName(), index); + return; + } + + // Get the item + Item* item = client->GetPlayer()->item_list.indexed_items[index]; + if(item) { + // Valid item lets check to make sure this item is attunable, if not return out + if (!item->CheckFlag(ATTUNEABLE)) { + LogWrite(ITEM__DEBUG, 0, "Items", "attune_inv called for an item that is not attunable (%s)", item->name.c_str()); + return; + } + + // Remove the attunable flag + item->generic_info.item_flags -= ATTUNEABLE; + // Set the attuned flag + item->generic_info.item_flags += ATTUNED; + // Flag this item for saving + item->save_needed = true; + + client->QueuePacket(item->serialize(client->GetVersion(), false, client->GetPlayer())); + + vector packets = client->GetPlayer()->EquipItem(index, client->GetVersion(), 0, -1); // appearance type?? + EQ2Packet* outapp = 0; + + for (int32 i=0;iQueuePacket(outapp); + } + } + + } +} + +void Commands::Command_Reset_Zone_Timer(Client* client, Seperator* sep) { + PrintSep(sep, "Command_Reset_Zone_Timer"); + /*if (sep && sep->arg[0] && sep->IsNumber(0)) { + int32 db_id = atoul(sep->arg[0]); + InstanceData* data = client->GetPlayer()->GetCharacterInstances().FindInstanceByDBID(db_id); + if (data) { + // TODO: add a check to timers to ensure it can be reset + + // Delete the character from the instance + database.DeleteCharacterFromInstance(client->GetPlayer()->GetCharacterID(), data->instance_id); + data->instance_id = 0; + + // Update the success time and set to 0 so the player can enter it again + database.UpdateCharacterInstance(client->GetPlayer()->GetCharacterID(), data->zone_name, 0, 1, 0); + data->last_success_timestamp = 0; + } + }*/ +} + +void Commands::Command_Player(Client* client, Seperator* sep) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, " -- /player syntax --"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/player coins"); +} + +void Commands::Command_Player_Coins(Client* client, Seperator* sep) { + // /player coins add 10 + // /player coins add plat 10 + Player* player = client->GetPlayer(); + Client* targetClient = client; + if (player->HasTarget() && player->GetTarget()->IsPlayer()) { + player = (Player*)player->GetTarget(); + targetClient = player->GetClient(); + } + + if (sep && sep->arg[0] && sep->arg[1]) { + const char* action = sep->arg[0]; + int64 value = 0; + + if (strncasecmp(action, "add", strlen(action)) == 0) { + if (sep->IsNumber(1)) { + value = atoi64(sep->arg[1]); + player->AddCoins(value); + + if (client->GetPlayer() == player) + client->Message(CHANNEL_COLOR_YELLOW, "You give yourself %llu coin%s", value, (value > 1 ? "s" : "")); + else { + client->Message(CHANNEL_COLOR_YELLOW, "You give %s %llu coin%s", player->GetName(), value, (value > 1 ? "s" : "")); + if(targetClient) { + targetClient->Message(CHANNEL_COLOR_YELLOW, "%s gave you %llu coin%s", client->GetPlayer()->GetName(), value, (value > 1 ? "s" : "")); + } + } + + return; + } + else if (sep->arg[2] && sep->IsNumber(2)) { + const char* type = sep->arg[1]; + if (strncasecmp(type, "copper", strlen(type)) == 0) { + value = atoi64(sep->arg[2]); + } + else if (strncasecmp(type, "silver", strlen(type)) == 0) { + value = atoi64(sep->arg[2]) * 100; + } + else if (strncasecmp(type, "gold", strlen(type)) == 0) { + value = atoi64(sep->arg[2]) * 10000; + } + else if (strncasecmp(type, "plat", strlen(type)) == 0) { + value = atoi64(sep->arg[2]) * 1000000; + } + player->AddCoins(value); + + if (client->GetPlayer() == player) + client->Message(CHANNEL_COLOR_YELLOW, "You give yourself %llu coin%s", value, (value > 1 ? "s" : "")); + else { + client->Message(CHANNEL_COLOR_YELLOW, "You give %s %llu coin%s", player->GetName(), value, (value > 1 ? "s" : "")); + if(targetClient) { + targetClient->Message(CHANNEL_COLOR_YELLOW, "%s gave you %llu coin%s", client->GetPlayer()->GetName(), value, (value > 1 ? "s" : "")); + } + } + return; + } + } + } + + client->SimpleMessage(CHANNEL_COLOR_YELLOW, " -- /player coins syntax --"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/player coins add [value] - adds the given number of coins to the player"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/player coins add copper [value] - adds the given amount of copper to the player"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/player coins add silver [value] - adds the given amount of silver to the player"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/player coins add gold [value] - adds the given amount of gold to the player"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/player coins add plat [value] - adds the given amount of platinum to the player"); +} + +void Commands::Command_AchievementAdd(Client* client, Seperator* sep) { + PrintSep(sep, "ACHIEVEMENT_ADD"); + if (sep && sep->IsSet(0)) { + int32 spell_id = atoul(sep->arg[1]); + int8 spell_tier = 0; + spell_tier = client->GetPlayer()->GetSpellTier(spell_id); + AltAdvanceData* data = master_aa_list.GetAltAdvancement(spell_id); + // addspellbookentry here + if (spell_tier >= data->maxRank) { + return; + } + if (!spell_tier) { + spell_tier = 1; + } + if (!client->GetPlayer()->HasSpell(spell_id, 0, true)) + { + Spell* spell = master_spell_list.GetSpell(spell_id, spell_tier); + client->GetPlayer()->AddSpellBookEntry(spell_id, 1, client->GetPlayer()->GetFreeSpellBookSlot(spell->GetSpellData()->spell_book_type), spell->GetSpellData()->spell_book_type, spell->GetSpellData()->linked_timer, true); + client->GetPlayer()->UnlockSpell(spell); + client->SendSpellUpdate(spell); + } + else + { + Spell* spell = master_spell_list.GetSpell(spell_id, spell_tier + 1); + int8 old_slot = client->GetPlayer()->GetSpellSlot(spell->GetSpellID()); + client->GetPlayer()->RemoveSpellBookEntry(spell->GetSpellID()); + client->GetPlayer()->AddSpellBookEntry(spell->GetSpellID(), spell->GetSpellTier(), old_slot, spell->GetSpellData()->spell_book_type, spell->GetSpellData()->linked_timer, true); + client->GetPlayer()->UnlockSpell(spell); + client->SendSpellUpdate(spell); + } + + + // cast spell here + if (!spell_tier) + spell_tier = 1; + Spell* spell = master_spell_list.GetSpell(spell_id, spell_tier); + if (spell) { + client->GetCurrentZone()->ProcessSpell(spell, client->GetPlayer(), client->GetPlayer()); + } + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /useability {spell_id} [spell_tier]"); + } + if(client->GetVersion() > 561) + master_aa_list.DisplayAA(client, 0, 0); +} + +void Commands::Command_Editor(Client* client, Seperator* sep) { + PacketStruct* packet = configReader.getStruct("WS_ChoiceWindow", client->GetVersion()); + if (packet) { + string url = string(rule_manager.GetGlobalRule(R_World, EditorURL)->GetString()); + + if (rule_manager.GetGlobalRule(R_World, EditorOfficialServer)->GetBool()) { + char command[255]; + url = "browser " + url; + int32 spawn_id = 0; + int32 zone_id = 0; + string type; + + if (client->GetCurrentZone()) + zone_id = client->GetCurrentZone()->GetZoneID(); + + if (client->GetPlayer()->GetTarget()) { + Spawn* target = client->GetPlayer()->GetTarget(); + if (target->IsWidget()) + type = "widgets"; + else if (target->IsSign()) + type = "signs"; + else if (target->IsObject()) + type = "objects"; + else if (target->IsNPC()) + type = "npcs"; + else if (target->IsGroundSpawn()) + type = "ground"; + else + type = "spawn"; + + spawn_id = client->GetPlayer()->GetTarget()->GetDatabaseID(); + } + + sprintf(command, url.c_str(), zone_id, type.c_str(), spawn_id); + + packet->setDataByName("accept_command", command); + } + else if (rule_manager.GetGlobalRule(R_World, EditorIncludeID)->GetBool()) { + char command[255]; + url = "browser " + url; + if (client->GetPlayer()->GetTarget()) + sprintf(command, url.c_str(), client->GetPlayer()->GetTarget()->GetDatabaseID()); + + packet->setDataByName("accept_command", command); + } + else { + string command = "browser " + url; + packet->setDataByName("accept_command", command.c_str()); + } + + packet->setDataByName("text", "Open the web editor?"); + packet->setDataByName("accept_text", "Open"); + + packet->setDataByName("cancel_text", "Cancel"); + // No clue if we even need the following 2 unknowns, just added them so the packet matches what live sends + packet->setDataByName("unknown2", 50); + packet->setDataByName("unknown4", 1); + + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } +} + +void Commands::Command_AcceptResurrection(Client* client, Seperator* sep) { + if(!client || !sep || client->GetPlayer()->GetID() != atoul(sep->arg[0])) + return; + client->GetResurrectMutex()->writelock(__FUNCTION__, __LINE__); + if(client->GetCurrentRez()->active) + client->AcceptResurrection(); + client->GetResurrectMutex()->releasewritelock(__FUNCTION__, __LINE__); +} + +void Commands::Command_DeclineResurrection(Client* client, Seperator* sep) { + if(!client || !sep || client->GetPlayer()->GetID() != atoul(sep->arg[0])) + return; + client->GetResurrectMutex()->writelock(__FUNCTION__, __LINE__); + if(client->GetCurrentRez()->active) + client->GetCurrentRez()->should_delete = true; + client->GetResurrectMutex()->releasewritelock(__FUNCTION__, __LINE__); +} +void Commands::Switch_AA_Profile(Client* client, Seperator* sep) { + PrintSep(sep, "Switch_AA_Profile"); + if (sep && sep->IsSet(0)) { + string type = sep->arg[0]; + int8 newtemplate = atoul(sep->arg[1]); + if(client->GetVersion() > 561) + master_aa_list.DisplayAA(client, newtemplate, 1); + } +} +void Commands::Get_AA_Xml(Client* client, Seperator* sep) { + PrintSep(sep, "Get_AA_Xml"); + if (sep && sep->IsSet(0)) { + string tabnum = sep->arg[0]; + string spellid = sep->arg[1]; + + + + } +} +void Commands::Add_AA(Client* client, Seperator* sep) { + PrintSep(sep, "Add_AA"); + if (sep && sep->IsSet(0)) { + int32 spell_id = atoul(sep->arg[1]); + int8 spell_tier = 0; + spell_tier = client->GetPlayer()->GetSpellTier(spell_id); + AltAdvanceData* data = master_aa_list.GetAltAdvancement(spell_id); + // addspellbookentry here + if(!data) { + LogWrite(COMMAND__ERROR, 0, "Command", "Error in Add_AA no data for spell_id %u spell_tier %u", spell_id, spell_tier); + return; + } + if (spell_tier >= data->maxRank) { + LogWrite(COMMAND__ERROR, 0, "Command", "Error in Add_AA spell_tier %u >= maxRank %u", spell_tier, data->maxRank); + return; + } + if (!spell_tier) { + spell_tier = 1; + } + if (!client->GetPlayer()->HasSpell(spell_id, 0, true)) + { + Spell* spell = master_spell_list.GetSpell(spell_id, spell_tier); + if(spell) + { + client->GetPlayer()->AddSpellBookEntry(spell_id, 1, client->GetPlayer()->GetFreeSpellBookSlot(spell->GetSpellData()->spell_book_type), spell->GetSpellData()->spell_book_type, spell->GetSpellData()->linked_timer, true); + client->GetPlayer()->UnlockSpell(spell); + client->SendSpellUpdate(spell); + } + } + else + { + Spell* spell = master_spell_list.GetSpell(spell_id, spell_tier + 1 ); + if(spell) + { + int8 old_slot = client->GetPlayer()->GetSpellSlot(spell->GetSpellID()); + client->GetPlayer()->RemoveSpellBookEntry(spell->GetSpellID()); + client->GetPlayer()->AddSpellBookEntry(spell->GetSpellID(), spell->GetSpellTier(), old_slot, spell->GetSpellData()->spell_book_type, spell->GetSpellData()->linked_timer, true); + client->GetPlayer()->UnlockSpell(spell); + client->SendSpellUpdate(spell); + } + } + + + // cast spell here + if (!spell_tier) + spell_tier = 1; + Spell* spell = master_spell_list.GetSpell(spell_id, spell_tier); + if (spell) { + client->GetCurrentZone()->ProcessSpell(spell, client->GetPlayer(), client->GetPlayer()); + } + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /useability {spell_id} [spell_tier]"); + } + if(client->GetVersion() > 561) + master_aa_list.DisplayAA(client, 0, 0); +} +void Commands::Commit_AA_Profile(Client* client, Seperator* sep) { + PrintSep(sep, "Commit_AA_Profile"); + if (sep && sep->IsSet(0)) { + + + } +} +void Commands::Begin_AA_Profile(Client* client, Seperator* sep) { + PrintSep(sep, "Begin_AA_Profile"); + if (sep && sep->IsSet(0)) { + if(client->GetVersion() > 561) + master_aa_list.DisplayAA(client, 100, 2); + } +} +void Commands::Back_AA(Client* client, Seperator* sep) { + if (sep && sep->IsSet(0)) { + PrintSep(sep, "Back_AA"); + + } +} +void Commands::Remove_AA(Client* client, Seperator* sep) { + if (sep && sep->IsSet(0)) { + PrintSep(sep, "Remove_AA"); + + } +} + +void Commands::Cancel_AA_Profile(Client* client, Seperator* sep) { + MasterAAList master_aa_list; + PrintSep(sep, "Cancel_AA_Profile"); + if(client->GetVersion() > 561) + master_aa_list.DisplayAA(client, 0, 0); + +} +void Commands::Save_AA_Profile(Client* client, Seperator* sep) { + if (sep && sep->IsSet(0)) { + PrintSep(sep, "Save_AA_Profile"); + + } +} + +void Commands::Command_TargetItem(Client* client, Seperator* sep) { + if (!sep || sep->GetArgNumber() < 1 || !client->GetPlayer()) return; + + if (!sep->IsNumber(0)) return; + + int32 request_id = atoul(sep->arg[0]); + + if (!sep->IsNumber(1)) return; + + sint32 item_id = atoi(sep->arg[1]); + + if (client->IsCurrentTransmuteID(request_id)) { + Transmute::HandleItemResponse(client, client->GetPlayer(), request_id, reinterpret_cast(item_id)); + } + else if (client->IsCurrentTransmuteID(item_id)) { + if (!sep->IsSet(2)) return; + + if (sep->IsNumber(2) && atoi(sep->arg[2]) == 1) { + Transmute::HandleConfirmResponse(client, client->GetPlayer(), reinterpret_cast(item_id)); + } + } +} + +void Commands::Command_FindSpawn(Client* client, Seperator* sep) { + if(sep) + client->GetCurrentZone()->FindSpawn(client, (char*)sep->argplus[0]); +} + +void Commands::Command_MoveCharacter(Client* client, Seperator* sep) { + if(sep && sep->arg[0][0] && sep->arg[1][0]) + { + char* name = sep->arg[0]; + char* zoneName = sep->arg[1]; + + char query[256]; + snprintf(query, 256, "UPDATE characters c, zones z set c.x = z.safe_x, c.y = z.safe_y, c.z = z.safe_z, c.heading = z.safe_heading, c.current_zone_id = z.id where c.name = '%s' and z.name='%s'", name, zoneName); + if (database.RunQuery(query, strnlen(query, 256))) + { + client->Message(CHANNEL_COLOR_YELLOW, "Ran query:%s", query); + } + else + client->Message(CHANNEL_COLOR_RED, "Query FAILED to run: %s", query); + } +} + +void Commands::Command_Mood(Client* client, Seperator* sep) { +Player* player = client->GetPlayer(); + + if( sep && sep->arg[0] ) + { + const char* value = sep->arg[0]; + InfoStruct* info = player->GetInfoStruct(); + int32 cid = client->GetCharacterID(); + char* characterName = database.GetCharacterName(cid); + char tmp[1024]; // our emote string "xyz appears zyx" + //char properties vals + char* pname = "mood"; + char* pval; // mood value + bool pt; //used to verify return from DB. + + //This should never be seen. + sprintf(tmp, " "); + if( strncasecmp(value, "angry", strlen(value)) == 0 ) + { + sprintf(tmp, "%s appears angry", characterName); + pval = "11852"; + player->SetMoodState(11852, 1); + info->set_mood(11852); + pt = database.insertCharacterProperty(client, pname, pval); + } + else if( strncasecmp(value, "afraid", strlen(value)) == 0 ) + { + sprintf(tmp, "%s appears afraid", characterName); + pval = "11851"; + player->SetMoodState(11851, 1); + info->set_mood(11851); + pt = database.insertCharacterProperty(client, pname, pval); + } + else if( strncasecmp(value, "happy", strlen(value)) == 0 ) + { + sprintf(tmp, "%s appears happy", characterName); + pval = "11854"; + player->SetMoodState(11854, 1); + info->set_mood(11854); + pt = database.insertCharacterProperty(client, pname, pval); + } + else if( strncasecmp(value, "sad", strlen(value)) == 0 ) { + sprintf(tmp, "%s appears sad", characterName); + pval = "11856"; + player->SetMoodState(11856, 1); + info->set_mood(11856); + pt = database.insertCharacterProperty(client, pname, pval); + } + else if( strncasecmp(value, "tired", strlen(value)) == 0 ) + { + sprintf(tmp, "%s appears tired", characterName); + pval = "11857"; + player->SetMoodState(11857, 1); + info->set_mood(11857); + pt = database.insertCharacterProperty(client, pname, pval); + } + else if( strncasecmp(value, "none", strlen(value)) == 0 ) + { + //using 11855 mood_idle for none, I assume thats what its for? + pval = "11855"; + player->SetMoodState(11855, 1); + info->set_mood(11855); + pt = database.insertCharacterProperty(client, pname, pval); + //return since we have nothing left to do. No emote for none. + return; + }else{ + client->SimpleMessage(CHANNEL_NARRATIVE, "Listing Available Moods:"); + client->SimpleMessage(CHANNEL_NARRATIVE, "none"); + client->SimpleMessage(CHANNEL_NARRATIVE, "afraid"); + client->SimpleMessage(CHANNEL_NARRATIVE, "angry"); + client->SimpleMessage(CHANNEL_NARRATIVE, "happy"); + client->SimpleMessage(CHANNEL_NARRATIVE, "sad"); + client->SimpleMessage(CHANNEL_NARRATIVE, "tired"); + return; + } + + client->GetPlayer()->GetZone()->HandleChatMessage(0, 0, CHANNEL_EMOTE, tmp); + return; + } + + client->SimpleMessage(CHANNEL_NARRATIVE, "Listing Available Moods:"); + client->SimpleMessage(CHANNEL_NARRATIVE, "none"); + client->SimpleMessage(CHANNEL_NARRATIVE, "afraid"); + client->SimpleMessage(CHANNEL_NARRATIVE, "angry"); + client->SimpleMessage(CHANNEL_NARRATIVE, "happy"); + client->SimpleMessage(CHANNEL_NARRATIVE, "sad"); + client->SimpleMessage(CHANNEL_NARRATIVE, "tired"); + return; +} + +/* + Function: Command_CancelEffect() + Purpose : Cancels (good) effect spells + Example : /cancel_effect spell_id - would cancel the spell with the value in spell effects list +*/ +void Commands::Command_CancelEffect(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0] && sep->IsNumber(0)) + { + int32 spell_id = atoul(sep->arg[0]); + + SpellEffects* effect = client->GetPlayer()->GetSpellEffect(spell_id); + if(!effect || effect->spell->spell->GetSpellData()->det_type) { + return; + } + + MaintainedEffects* meffect = effect->caster->GetMaintainedSpell(spell_id); + + if (!meffect || !meffect->spell || !meffect->spell->caster || !meffect->spell->caster->GetZone() || + !meffect->spell->caster->GetZone()->GetSpellProcess()->DeleteCasterSpell(meffect->spell, "canceled", false, client->GetPlayer())) + client->Message(CHANNEL_COLOR_RED, "The spell effect could not be cancelled."); + } +} + + +/* + Function: Command_CurePlayer() + Purpose : Identifies spell to cast for cure based on type + Example : /cureplayer ?? + https://eq2.fandom.com/wiki/Update:58 + New command /cureplayer [playername|group or raid position][trauma|arcane|noxious|elemental|curse] optional [spell|potion] + + Example: /cureplayer g0 noxious spell + Will attempt to cure yourself of a noxious detriment with only spells and without using potions (even if you have them). + Example: /cureplayer r4 noxious + Will attempt to cure the character in raid slot 4 of a noxious detriment using a spell or potion (whichever is available). +*/ +void Commands::Command_CurePlayer(Client* client, Seperator* sep) +{ + Entity* target = nullptr; + bool use_spells = true; + bool use_potions = true; + if (sep && sep->arg[0] && sep->arg[1]) { + if(sep->arg[2]) { + std::string type(sep->arg[2]); + boost::algorithm::to_lower(type); + if(type == "potion") + use_spells = false; + else if(type == "spell") + use_potions = false; + } + + if(strlen(sep->arg[0]) > 1 && isdigit(sep->arg[0][1])) { + int32 mapped_position = (int32)(sep->arg[0][1]) - 48; + + // TODO: RAID Support ('r' argument) + if(sep->arg[0][0] == 'g' && !mapped_position) { + target = (Entity*)client->GetPlayer(); + } + else { + GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo(); + if (gmi && gmi->group_id) { + PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id); + if(group) { + target = group->GetGroupMemberByPosition(client->GetPlayer(), mapped_position); + } + } + } + } + else { + Client* target_client = zone_list.GetClientByCharName(sep->arg[0]); + if(target_client && target_client->GetPlayer() && target_client->GetPlayer()->GetZone() == client->GetPlayer()->GetZone()) { + target = (Entity*)target_client->GetPlayer(); + } + } + + ItemEffectType type = EFFECT_CURE_TYPE_ALL; + bool successful_spell = false; + bool successful_potion = false; + if(target) { + SpellBookEntry* entry = nullptr; + std::string str(sep->arg[1]); + boost::algorithm::to_lower(str); + if(str == "arcane") { + type = EFFECT_CURE_TYPE_ARCANE; + // cure arcane spell missing in DB? + if(use_spells && rule_manager.GetGlobalRule(R_Spells, CureArcaneSpellID)->GetInt32()) { + entry = client->GetPlayer()->GetSpellBookSpell(rule_manager.GetGlobalRule(R_Spells, CureArcaneSpellID)->GetInt32()); // cure noxious + } + } + else if(str == "trauma") { + type = EFFECT_CURE_TYPE_TRAUMA; + // cure trauma spell missing in DB? + if(use_spells && rule_manager.GetGlobalRule(R_Spells, CureTraumaSpellID)->GetInt32()) { + entry = client->GetPlayer()->GetSpellBookSpell(rule_manager.GetGlobalRule(R_Spells, CureTraumaSpellID)->GetInt32()); // cure noxious + } + } + else if(str == "noxious") { + type = EFFECT_CURE_TYPE_NOXIOUS; + if(use_spells && rule_manager.GetGlobalRule(R_Spells, CureNoxiousSpellID)->GetInt32()) { + entry = client->GetPlayer()->GetSpellBookSpell(rule_manager.GetGlobalRule(R_Spells, CureNoxiousSpellID)->GetInt32()); // cure noxious + } + } + else if(str == "curse") { + type = EFFECT_CURE_TYPE_CURSE; + if(use_spells && rule_manager.GetGlobalRule(R_Spells, CureCurseSpellID)->GetInt32()) { + entry = client->GetPlayer()->GetSpellBookSpell(rule_manager.GetGlobalRule(R_Spells, CureCurseSpellID)->GetInt32()); // cure curse + } + } + else if(str == "magic") { + type = EFFECT_CURE_TYPE_MAGIC; + if(use_spells && rule_manager.GetGlobalRule(R_Spells, CureMagicSpellID)->GetInt32()) { + entry = client->GetPlayer()->GetSpellBookSpell(rule_manager.GetGlobalRule(R_Spells, CureMagicSpellID)->GetInt32()); // cure magic + } + } + + if(use_spells) { + // check if any of the specific cure types are available, if not then check the base cures + if(!entry && rule_manager.GetGlobalRule(R_Spells, CureSpellID)->GetInt32()) { + entry = client->GetPlayer()->GetSpellBookSpell(rule_manager.GetGlobalRule(R_Spells, CureSpellID)->GetInt32()); // cure + } + + if(entry && entry->spell_id) { + Spell* spell = master_spell_list.GetSpell(entry->spell_id, entry->tier); + target->GetZone()->ProcessSpell(spell, (Entity*)client->GetPlayer(), target, true, false); + successful_spell = true; + } + } + } + + if(!successful_spell && use_potions && target && type != NO_EFFECT_TYPE) { + vector* potential_items = client->GetItemsByEffectType(type, EFFECT_CURE_TYPE_ALL); + if (potential_items && potential_items->size() > 0) { + vector::iterator itr; + for (itr = potential_items->begin(); itr != potential_items->end(); itr++) + { + Item* item = *itr; + if(client->UseItem(item, target)) { + successful_potion = true; + break; + } + } + } + safe_delete(potential_items); + } + } +} + + +/* + Function: Command_ShareQuest() + Purpose : Share quest with the group + Example : /share_quest [quest_id] +*/ +void Commands::Command_ShareQuest(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0] && sep->IsNumber(0)) { + int32 quest_id = atoul(sep->arg[0]); + + bool hasQuest = client->GetPlayer()->HasAnyQuest(quest_id); + if(hasQuest) { + Quest* quest = master_quest_list.GetQuest(quest_id, false); + if(quest) { + GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo(); + if (gmi && gmi->group_id) { + PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id); + if (group) { + group->ShareQuestWithGroup(client, quest); + } + } + } + } + else { + client->SimpleMessage(CHANNEL_COLOR_RED, "Cannot find quest."); + } + } +} + + + +/* + Function: Command_Yell() + Purpose : Yell to break an encounter + Example : /yell + * Uses self target, encounter target or no target +*/ +void Commands::Command_Yell(Client* client, Seperator* sep) { + Entity* player = (Entity*)client->GetPlayer(); + Spawn* res = nullptr; + Spawn* prev = nullptr; + bool cycleAll = false; + if (player->GetTarget() == player) { + cycleAll = true; // self target breaks all encounters + } + else if (player->GetTarget()) { + res = player->GetTarget(); // selected target other than self only dis-engages that encounter + } + + if (res && !client->GetPlayer()->IsEngagedBySpawnID(res->GetID())) + return; + + bool groupPermissionYell = true; + + GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo(); + // If the player has a group and has a target + if (gmi) { + deque::iterator itr; + + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + + PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id); + if (group && !group->GetGroupOptions()->default_yell && !gmi->leader) { // default_yell_method = 0 means leader only + groupPermissionYell = false; + } + + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } + + if (!groupPermissionYell) { + LogWrite(COMMAND__ERROR, 0, "Command", "%s permission to yell denied due to group yell method set to leader only", client->GetPlayer()->GetName()); + return; + } + + bool alreadyYelled = false; + do { + if (!res && player->IsEngagedInEncounter(&res)) { // no target is set, dis-engage top of hated by list + + } + if (!res || prev == res) { + return; + } + + if (res->IsNPC() && ((NPC*)res)->Brain()) { + if (!alreadyYelled) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You yell for help!"); + string yellMsg = std::string(client->GetPlayer()->GetName()) + " yelled for help!"; + client->GetPlayer()->GetZone()->SimpleMessage(CHANNEL_COLOR_RED, yellMsg.c_str(), client->GetPlayer(), 35.0f, false); + client->GetPlayer()->GetZone()->SendYellPacket(client->GetPlayer()); + } + alreadyYelled = true; + + NPC* npc = (NPC*)res; + npc->Brain()->ClearEncounter(); + npc->SetLockedNoLoot(ENCOUNTER_STATE_BROKEN); + npc->UpdateEncounterState(ENCOUNTER_STATE_BROKEN); + } + prev = res; + res = nullptr; + } while (cycleAll); + + if (!player->IsEngagedInEncounter()) { + if (player->GetInfoStruct()->get_engaged_encounter()) { + player->GetInfoStruct()->set_engaged_encounter(0); + player->SetRegenValues((player->GetInfoStruct()->get_effective_level() > 0) ? player->GetInfoStruct()->get_effective_level() : player->GetLevel()); + client->GetPlayer()->SetCharSheetChanged(true); + player->info_changed = true; + } + } +} + + +/* + Function: Command_SetAutoLootMode() + Purpose : Set player auto loot mode (0 = disabled, 1 = need/lotto, 2 = decline). + Example : /setautolootmode [mode] +*/ +void Commands::Command_SetAutoLootMode(Client* client, Seperator* sep) { + if (sep && sep->IsNumber(0)) { + int8 mode = atoul(sep->arg[0]); + switch (mode) { + case AutoLootMode::METHOD_DISABLED: { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Disabled auto loot mode"); + break; + } + case AutoLootMode::METHOD_ACCEPT: { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Enabled auto loot mode for need and lotto."); + break; + } + default: { + mode = AutoLootMode::METHOD_DECLINE; + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Enabled auto loot mode to decline need and lotto."); + break; + } + } + client->GetPlayer()->GetInfoStruct()->set_group_auto_loot_method(mode); + database.insertCharacterProperty(client, CHAR_PROPERTY_AUTOLOOTMETHOD, (char*)std::to_string(mode).c_str()); + client->SendDefaultGroupOptions(); + } +} + +/* + Function: Command_AutoAttack() + Purpose : Attack / Auto Attack + Example : /attack, /autoattack type +*/ +void Commands::Command_AutoAttack(Client* client, Seperator* sep) { + int8 type = 1; + bool update = false; + Player* player = client->GetPlayer(); + if(!player) + return; + bool incombat = player->EngagedInCombat(); + if(sep && sep->arg[0] && sep->IsNumber(0)) + type = atoi(sep->arg[0]); + if(!client->GetPlayer()->Alive()){ + client->SimpleMessage(CHANNEL_COLOR_RED,"You cannot do that right now."); + return; + } + if(type == 0){ + if(incombat) + client->SimpleMessage(CHANNEL_GENERAL_COMBAT, "You stop fighting."); + player->StopCombat(type); + update = true; + } + else { + if(type == 2){ + player->InCombat(false); + if(incombat && player->GetRangeAttack()){ + player->StopCombat(type); + client->SimpleMessage(CHANNEL_GENERAL_COMBAT, "You stop fighting."); + update = true; + } + else{ + player->SetRangeAttack(true); + player->InCombat(true, true); + client->SimpleMessage(CHANNEL_GENERAL_COMBAT, "You start fighting."); + update = true; + } + } + else { + player->InCombat(false, true); + player->SetRangeAttack(false); + player->InCombat(true); + if(!incombat) { + client->SimpleMessage(CHANNEL_GENERAL_COMBAT, "You start fighting."); + update = true; + } + } + /*else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You cannot attack that!");*/ + } + + if(update) { + player->SetCharSheetChanged(true); + } +} + +/* + Function: Command_Assist() + Purpose : Assist target + Example : /assist [name] + * Uses target or character name +*/ +void Commands::Command_Assist(Client* client, Seperator* sep) { + Entity* player = (Entity*)client->GetPlayer(); + Spawn* res = nullptr; + if(sep && sep->arg[0]) { + if(!stricmp(sep->arg[0], "on")) { + database.insertCharacterProperty(client, CHAR_PROPERTY_ASSISTAUTOATTACK, "1"); + return; + } + else if(!stricmp(sep->arg[0], "off")) { + database.insertCharacterProperty(client, CHAR_PROPERTY_ASSISTAUTOATTACK, "0"); + return; + } + Client* otherClient = client->GetPlayer()->GetZone()->GetClientByName(sep->arg[0]); + if(otherClient) { + res = otherClient->GetPlayer(); + } + } + + if (player->GetTarget()) { + res = player->GetTarget(); // selected target other than self only dis-engages that encounter + } + if(res && res->GetTarget()) { + res = res->GetTarget(); + } + + if(res) { + client->TargetSpawn(res); + + if(client->GetPlayer()->GetInfoStruct()->get_assist_auto_attack() && !player->EngagedInCombat()) { + Command_AutoAttack(client, nullptr); + } + } +} + +/* + Function: Command_Target() + Purpose : Target spawn/player + Example : /target [name] + * Uses target or character name +*/ +void Commands::Command_Target(Client* client, Seperator* sep) { + Entity* player = (Entity*)client->GetPlayer(); + Spawn* res = nullptr; + if(sep && sep->arg[0] && player->GetZone()) { + float max_distance = rule_manager.GetGlobalRule(R_Player, MaxTargetCommandDistance)->GetFloat(); + + if(max_distance < 1.0f) { + max_distance = 10.0f; + } + + player->GetZone()->SetPlayerTargetByName(client, (char*)sep->argplus[0], max_distance); + } +} + + +/* + Function: Command_Target_Pet() + Purpose : Target pet + Example : /target_pet + * Use to target pet +*/ +void Commands::Command_Target_Pet(Client* client, Seperator* sep) { + Entity* player = (Entity*)client->GetPlayer(); + Spawn* res = client->GetPlayer()->GetPet(); + + if(res) { + client->TargetSpawn(res); + } +} + diff --git a/source/WorldServer/Commands/Commands.h b/source/WorldServer/Commands/Commands.h new file mode 100644 index 0000000..bea8f47 --- /dev/null +++ b/source/WorldServer/Commands/Commands.h @@ -0,0 +1,976 @@ +/* + 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 . +*/ +#ifndef __EQ2_COMMANDS__ +#define __EQ2_COMMANDS__ +#include "../../common/DataBuffer.h" +#include "../../common/MiscFunctions.h" +#include "../../common/types.h" +#include "../../common/opcodemgr.h" +#include +#include +#include +#include "../../common/debug.h" +using namespace std; + +class Client; +class Spawn; +class ZoneServer; +extern mapEQOpcodeManager; + +#define CHANNEL_COLOR_RED 3 +#define CHANNEL_COLOR_CHAT_RELATIONSHIP 4 +#define CHANNEL_COLOR_YELLOW 5 +#define CHANNEL_COLOR_NEW_LOOT 84 +#define CHANNEL_COLOR_NEWEST_LOOT 89 + +#define UPDATE_COLOR_WHITE 254 // For UpdateText + +#define CHANNEL_ALL_TEXT 0 +#define CHANNEL_GAME_TEXT 1 +#define CHANNEL_DEFAULT 2 +#define CHANNEL_ERROR 3 +#define CHANNEL_STATUS 4 +#define CHANNEL_MOTD 5 +#define CHANNEL_CHAT_TEXT 6 +#define CHANNEL_NEARBY_CHAT 7 +#define CHANNEL_SAY 8 +#define CHANNEL_SHOUT 9 +#define CHANNEL_EMOTE 10 +#define CHANNEL_YELL 11 +#define CHANNEL_NARRATIVE 12 //white +#define CHANNEL_NONPLAYER_SAY 13 +#define CHANNEL_GROUP_CHAT 14 +#define CHANNEL_GROUP_SAY 15 // Use this for group chat +#define CHANNEL_RAID_SAY 16 +#define CHANNEL_GUILD_CHAT 17 +#define CHANNEL_GUILD_SAY 18 // Use this for guild chat +#define CHANNEL_OFFICER_SAY 19 +#define CHANNEL_GUILD_MOTD 20 +#define CHANNEL_GUILD_MEMBER_ONLINE 21 +#define CHANNEL_GUILD_EVENT 22 +#define CHANNEL_GUILD_RECRUITING_PAGE 23 +#define CHANNEL_GUILD_RECRUITING_PAGE_OTHER 24 +#define CHANNEL_PRIVATE_CHAT 25 +#define CHANNEL_NONPLAYER_TELL 26 +#define CHANNEL_OBJECT_TEXT 27 +#define CHANNEL_PRIVATE_TELL 28 +#define CHANNEL_TELL_FROM_CS 29 +#define CHANNEL_ARENA 30 +#define CHANNEL_CHAT_CHANNEL_TEXT 31 +#define CHANNEL_OUT_OF_CHARACTER 32 +#define CHANNEL_AUCTION 33 +#define CHANNEL_CUSTOM_CHANNEL 34 // 34 is nothing, message with 34 as type will not show on client +#define CHANNEL_CHARACTER_TEXT 35 +#define CHANNEL_REWARD 36 +#define CHANNEL_DEATH 37 +#define CHANNEL_PET_CHAT 38 +#define CHANNEL_SKILL 39 +#define CHANNEL_FACTION 40 +// Combat related chat channels start here +#define CHANNEL_SPELLS 41 +#define CHANNEL_YOU_CAST 42 +#define CHANNEL_YOU_FAIL 43 +#define CHANNEL_CRITICAL_CAST 44 +#define CHANNEL_FRIENDLY_CAST 45 +#define CHANNEL_FRIENDLY_FAIL 46 +#define CHANNEL_OTHER_CAST 47 +#define CHANNEL_OTHER_FAIL 48 +#define CHANNEL_HOSTILE_CAST 49 +#define CHANNEL_HOSTILE_FAIL 50 +#define CHANNEL_WORN_OFF 51 +#define CHANNEL_SPELLS_OTHER 52 +#define CHANNEL_HEAL_SPELLS 53 +#define CHANNEL_HEALS 54 +#define CHANNEL_FRIENDLY_HEALS 55 +#define CHANNEL_OTHER_HEALS 56 +#define CHANNEL_HOSTILE_HEALS 57 +#define CHANNEL_CRITICAL_HEALS 58 +#define CHANNEL_COMBAT 59 +#define CHANNEL_GENERAL_COMBAT 60 +#define CHANNEL_HEROIC_OPPORTUNITY 61 +#define CHANNEL_NON_MELEE_DAMAGE 62 +#define CHANNEL_DAMAGE_SHIELD 63 +#define CHANNEL_WARD 64 +#define CHANNEL_DAMAGE_INTERCEPT 65 +#define CHANNEL_MELEE_COMBAT 66 +#define CHANNEL_WARNINGS 67 +#define CHANNEL_YOU_HIT 68 +#define CHANNEL_YOU_MISS 69 +#define CHANNEL_ATTACKER_HITS 70 +#define CHANNEL_ATTACKER_MISSES 71 +#define CHANNEL_YOUR_PET_HITS 72 +#define CHANNEL_YOUR_PET_MISSES 73 +#define CHANNEL_ATTACKER_HITS_PET 74 +#define CHANNEL_ATTACKER_MISSES_PET 75 +#define CHANNEL_OTHER_HIT 76 +#define CHANNEL_OTHER_MISSES 77 +#define CHANNEL_CRITICAL_HIT 78 +#define CHANNEL_HATE_ADJUSTMENTS 79 +#define CHANNEL_YOUR_HATE 80 +#define CHANNEL_OTHERS_HATE 81 +#define CHANNEL_DISPELS_AND_CURES 82 +#define CHANNEL_DISPEL_YOU 83 +#define CHANNEL_DISPEL_OTHER 84 +#define CHANNEL_CURE_YOU 85 +#define CHANNEL_CURE_OTHER 86 +// End of combat chat channels +#define CHANNEL_OTHER 87 +#define CHANNEL_MONEY_SPLIT 88 +#define CHANNEL_LOOT 89 +#define CHANNEL_LOOT_ROLLS 90 +#define CHANNEL_COMMAND_TEXT 91 +#define CHANNEL_BROADCAST 92 // Goes to all chat windows no matter what +#define CHANNEL_WHO 93 +#define CHANNEL_COMMANDS 94 +#define CHANNEL_MERCHANT 95 +#define CHANNEL_MERCHANT_BUY_SELL 96 +#define CHANNEL_CONSIDER_MESSAGE 97 +#define CHANNEL_CON_MINUS_2 98 +#define CHANNEL_CON_MINUS_1 99 +#define CHANNEL_CON_0 100 +#define CHANNEL_CON_1 101 +#define CHANNEL_CON_2 102 +#define CHANNEL_TRADESKILLS 103 +#define CHANNEL_HARVESTING 104 +#define CHANNEL_HARVESTING_WARNINGS 105 +// 106 is nothing, message sent with this channel won't display in the client +#define CHANNEL_VOICE_CHAT 107 +// 108+ will crash the client DO NOT USE + +/* Using this in the /zone details command so that we do not have to store a whole zone in memory while changing zone attributes. Also, + ran into a problem when deleting a zone pointer (for zones that were not running), it would try to shut down a zone which was not + running, causing world to crash. */ +struct ZoneInfo { + int32 id; + int8 expansion_id; + char name[64]; + char file[64]; + char description[256]; + float safe_x; + float safe_y; + float safe_z; + float underworld; + int8 min_recommended; + int8 max_recommended; + char zone_type[64]; + bool always_loaded; + bool city_zone; + sint16 min_status; + int16 min_level; + int16 max_level; + int8 start_zone; + int8 instance_type; + int32 default_reenter_time; + int32 default_reset_time; + int32 default_lockout_time; + int8 force_group_to_zone; + char lua_script[256]; + int32 shutdown_timer; + char zone_motd[256]; + float xp_modifier; + int16 min_version; + bool weather_allowed; + int32 ruleset_id; + char sky_file[64]; +}; + +class EQ2_CommandString : public DataBuffer{ +public: + EQ2_CommandString(){ handler = 0; } + EQ2_CommandString(uchar* buffer, int32 size){ + InitializeLoadData(buffer, size); + LoadData(handler); + LoadDataString(command); + } + EQ2_16BitString command; + int16 handler; +}; +class EQ2_RemoteCommandString : public DataBuffer{ +public: + EQ2_RemoteCommandString(){ handler = 0; } + EQ2_RemoteCommandString(char* name, int32 in_handler, sint16 in_status){ + command.data = string(name); + command.size = command.data.length(); + handler = in_handler; + required_status = in_status; + } + EQ2_RemoteCommandString(uchar* buffer, int32 size){ + required_status = 0; + InitializeLoadData(buffer, size); + LoadData(handler); + LoadDataString(command); + } + + EQ2_8BitString command; + int16 handler; + sint16 required_status; +}; +class RemoteCommands { +public: + RemoteCommands(){ num_commands = 0; buffer.clear(); } + int16 num_commands; + vector commands; + void addCommand(EQ2_RemoteCommandString add){ commands.push_back(add); num_commands++;} + void AddSubCommand(string command, EQ2_RemoteCommandString subcommand){ + subcommands[command][subcommand.command.data] = subcommand; + } + bool validSubCommand(string command, string subcommand){ + if(subcommands.count(command) > 0 && subcommands[command].count(subcommand) > 0) + return true; + return false; + } + void addZero(){ + num_commands++; + EQ2_RemoteCommandString add; + add.handler = 0; + add.required_status = 300; + add.command.size = 0; + commands.push_back(add); + } + void CheckAddSubCommand(string command, EQ2_RemoteCommandString subcommand){ + vector::iterator itr; + for(itr = commands.begin(); itr != commands.end();itr++){ + if((*itr).command.data == command){ + AddSubCommand(command, subcommand); + return; + } + } + // TODO: cannot seem to use LogWrite in this .h file! + printf("Unable to find parent command '%s' for subcommand: '%s'\n\tEvery subcommand must have a parent command!", command.c_str(), subcommand.command.data.c_str()); + } + void AddDataCommand(EQ2_RemoteCommandString* command){ + buffer.append((char*)&command->command.size, sizeof(command->command.size)); + if(command->command.size>0) + buffer.append(command->command.data); + } + int32 GetCommandHandler(const char* name){ + if(!name) + return 0xFFFFFFFF; + int8 name_size = strlen(name); + for(int32 i = 0; i < commands.size(); i++){ + if(commands[i].command.size > 0){ + if(strncasecmp(commands[i].command.data.c_str(), name, name_size) == 0) + return commands[i].handler; + } + } + return 0xFFFFFFFF; + } + string buffer; + EQ2Packet* serialize(int16 version = 0); + map > subcommands; +}; +class Commands{ +public: + Commands(); + ~Commands(); + bool SetSpawnCommand(Client* client, Spawn* target, int8 type, const char* value, bool send_update = true, bool temporary = false, string* temp_value = 0, int8 index = 0); + void UpdateDatabaseAppearance(Client* client, Spawn* target, string fieldName, sint8 r, sint8 g, sint8 b); + bool SetZoneCommand(Client* client, int32 zone_id, ZoneServer* zone, int8 type, const char* value); + RemoteCommands* GetRemoteCommands() { return remote_commands; } + void Process(int32 index, EQ2_16BitString* command_parms, Client* client, Spawn* targetOverride=NULL); + int32 GetCommandHandler(const char* name){ + return remote_commands->GetCommandHandler(name); + } + int32 GetSpawnSetType(string val); + + // JA: New Command handlers + void Command_AcceptAdvancement(Client* client, Seperator* sep); + void Command_AFK(Client* client, Seperator* sep); + void Command_Appearance(Client* client, Seperator* sep, int handler); + void Command_CancelMaintained(Client* client, Seperator* sep); + void Command_Claim(Client* client, Seperator* sep); + void Command_ClearAllQueued(Client* client); + void Command_Create(Client* client, Seperator* sep); + void Command_CreateFromRecipe(Client* client, Seperator* sep); + void Command_Distance(Client* client); + void Command_Duel(Client* client, Seperator* sep); + void Command_DuelBet(Client* client, Seperator* sep); + void Command_DuelAccept(Client* client, Seperator* sep); + void Command_DuelDecline(Client* client, Seperator* sep); + void Command_DuelSurrender(Client* client, Seperator* sep); + void Command_DuelToggle(Client* client, Seperator* sep); + void Command_EntityCommand(Client* client, Seperator* sep, int handler); + void Command_Follow(Client* client, Seperator* sep); + void Command_StopFollow(Client* client, Seperator* sep); + void Command_Grid(Client* client, Seperator* sep); + void Command_Guild(Client* client, Seperator* sep); + void Command_CreateGuild(Client* client); + void Command_SetGuildOfficerNote(Client* client, Seperator* sep); + void Command_SetGuildMemberNote(Client* client, Seperator* sep); + void Command_OfficerSay(Client* client, Seperator* sep); + void Command_GuildSay(Client* client, Seperator* sep); + void Command_Guilds(Client* client); + void Command_GuildsAdd(Client* client, Seperator* sep); + void Command_GuildsCreate(Client* client, Seperator* sep); + void Command_GuildsDelete(Client* client, Seperator* sep); + void Command_GuildsList(Client* client); + void Command_GuildsRemove(Client* client, Seperator* sep); + void Command_InspectPlayer(Client* client, Seperator* sep); + void Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteCommandString* command); + void Command_Languages(Client* client, Seperator* sep); + void Command_SetLanguage(Client* client, Seperator* sep); + void Command_LastName(Client* client, Seperator* sep); + void Command_ConfirmLastName(Client* client, Seperator* sep); + void Command_Location(Client* client); + void Command_LocationAdd(Client* client, Seperator* sep); + void Command_LocationCreate(Client* client, Seperator* sep); + void Command_LocationDelete(Client* client, Seperator* sep); + void Command_LocationList(Client* client, Seperator* sep); + void Command_LocationRemove(Client* client, Seperator* sep); + void Command_Merchant(Client* client, Seperator* sep, int handler); + //devn00b + void Command_Mood(Client* client, Seperator* sep); + + void Command_Modify(Client* client); // usage function + void Command_ModifyCharacter(Client* client, Seperator* sep); + void Command_ModifyFaction(Client* client, Seperator* sep); + void Command_ModifyGuild(Client* client, Seperator* sep); + void Command_ModifyItem(Client* client, Seperator* sep); + void Command_ModifyQuest(Client* client, Seperator* sep); + void Command_ModifySkill(Client* client, Seperator* sep); + void Command_ModifySpawn(Client* client, Seperator* sep); + void Command_ModifySpell(Client* client, Seperator* sep); + void Command_ModifyZone(Client* client, Seperator* sep); + + void Command_MOTD(Client* client); + void Command_Pet(Client* client, Seperator* sep); + void Command_PetName(Client* client, Seperator* sep); + void Command_NamePet(Client* client, Seperator* sep); + void Command_Rename(Client* client, Seperator* sep); + void Command_ConfirmRename(Client* client, Seperator* sep); + void Command_PetOptions(Client* client, Seperator* sep); + void Command_Random(Client* client, Seperator* sep); + void Command_Randomize(Client* client, Seperator* sep); + void Command_ReportBug(Client* client, Seperator* sep); + void Command_ShowCloak(Client* client, Seperator* sep); + void Command_ShowHelm(Client* client, Seperator* sep); + void Command_ShowHood(Client* client, Seperator* sep); + void Command_ShowHoodHelm(Client* client, Seperator* sep); + void Command_ShowRanged(Client* client, Seperator* sep); + void Command_Skills(Client* client, Seperator* sep, int handler); + void Command_SpawnTemplate(Client* client, Seperator* sep); + void Command_Speed(Client* client, Seperator* sep); + void Command_StationMarketPlace(Client* client, Seperator* sep); + void Command_StopEating(Client* client); + void Command_StopDrinking(Client* client); + void Command_Test(Client* client, EQ2_16BitString* command_parms); + void Command_Title(Client* client); + void Command_TitleList(Client* client); + void Command_TitleSetPrefix(Client* client, Seperator* sep); + void Command_TitleSetSuffix(Client* client, Seperator* sep); + void Command_TitleFix(Client* client, Seperator* sep); + void Command_Toggle_Anonymous(Client* client); + void Command_Toggle_AutoConsume(Client* client, Seperator* sep); + void Command_Toggle_BonusXP(Client* client); + void Command_Toggle_CombatXP(Client* client); + void Command_Toggle_GMHide(Client* client); + void Command_Toggle_GMVanish(Client* client); + void Command_Toggle_Illusions(Client* client, Seperator* sep); + void Command_Toggle_LFG(Client* client); + void Command_Toggle_LFW(Client* client); + void Command_Toggle_QuestXP(Client* client); + void Command_Toggle_Roleplaying(Client* client); + void Command_Toggle_Duels(Client* client); + void Command_Toggle_Trades(Client* client); + void Command_Toggle_Guilds(Client* client); + void Command_Toggle_Groups(Client* client); + void Command_Toggle_Raids(Client* client); + void Command_Toggle_LON(Client* client); + void Command_Toggle_VoiceChat(Client* client); + void Command_Track(Client* client); + void Command_TradeStart(Client* client, Seperator* sep); + void Command_TradeAccept(Client* client, Seperator* sep); + void Command_TradeReject(Client* client, Seperator* sep); + void Command_TradeCancel(Client* client, Seperator* sep); + void Command_TradeSetCoin(Client* client, Seperator* sep); + void Command_TradeAddCoin(Client* client, Seperator* sep, int handler); + void Command_TradeRemoveCoin(Client* client, Seperator* sep, int handler); + void Command_TradeAddItem(Client* client, Seperator* sep); + void Command_TradeRemoveItem(Client* client, Seperator* sep); + void Command_TryOn(Client* client, Seperator* sep); + void Command_JoinChannel(Client *client, Seperator *sep); + void Command_JoinChannelFromLoad(Client *client, Seperator *sep); + void Command_TellChannel(Client *client, Seperator *sep); + void Command_LeaveChannel(Client *client, Seperator *sep); + void Command_WeaponStats(Client *client); + void Command_WhoChannel(Client *client, Seperator *sep); + void Command_ZoneSafeCoords(Client *client, Seperator *sep); + void Command_ZoneDetails(Client *client, Seperator *sep); + void Command_ZoneSet(Client *client, Seperator *sep); + void Command_Rain(Client* client, Seperator* sep); + void Command_Wind(Client* client, Seperator* sep); + void Command_SendMerchantWindow(Client* client, Seperator* sep, bool sell = false); + void Command_Weather(Client* client, Seperator* sep); + void Command_Select(Client* client, Seperator* sep); + void Command_ConsumeFood(Client* client, Seperator* sep); + void Command_Aquaman(Client* client, Seperator* sep); + void Command_Attune_Inv(Client* client, Seperator* sep); + void Command_Player(Client* client, Seperator* sep); + void Command_Player_Coins(Client* client, Seperator* sep); + void Command_Reset_Zone_Timer(Client* client, Seperator* sep); + void Command_AchievementAdd(Client* client, Seperator* sep); + void Command_Editor(Client* client, Seperator* sep); + void Command_AcceptResurrection(Client* client, Seperator* sep); + void Command_DeclineResurrection(Client* client, Seperator* set); + void Command_TargetItem(Client* client, Seperator* set); + + void Command_FindSpawn(Client* client, Seperator* set); + + void Command_MoveCharacter(Client* client, Seperator* set); + + // Bot Commands + void Command_Bot(Client* client, Seperator* sep); + void Command_Bot_Create(Client* client, Seperator* sep); + void Command_Bot_Customize(Client* client, Seperator* sep); + void Command_Bot_Spawn(Client* client, Seperator* sep); + void Command_Bot_List(Client* client, Seperator* sep); + void Command_Bot_Inv(Client* client, Seperator* sep); + void Command_Bot_Settings(Client* client, Seperator* sep); + void Command_Bot_Help(Client* client, Seperator* sep); + + void Command_CancelEffect(Client* client, Seperator* sep); + void Command_CurePlayer(Client* client, Seperator* sep); + void Command_ShareQuest(Client* client, Seperator* sep); + void Command_Yell(Client* client, Seperator* sep); + void Command_SetAutoLootMode(Client* client, Seperator* sep); + void Command_AutoAttack(Client* client, Seperator* sep); + void Command_Assist(Client* client, Seperator* sep); + void Command_Target(Client* client, Seperator* sep); + void Command_Target_Pet(Client* client, Seperator* sep); + + // AA Commands + void Get_AA_Xml(Client* client, Seperator* sep); + void Add_AA(Client* client, Seperator* sep); + void Commit_AA_Profile(Client* client, Seperator* sep); + void Begin_AA_Profile(Client* client, Seperator* sep); + void Back_AA(Client* client, Seperator* sep); + void Remove_AA(Client* client, Seperator* sep); + void Switch_AA_Profile(Client* client, Seperator* sep); + void Cancel_AA_Profile(Client* client, Seperator* sep); + void Save_AA_Profile(Client* client, Seperator* sep); +private: + RemoteCommands* remote_commands; + map spawn_set_values; + map zone_set_values; +}; +#define SPAWN_SET_VALUE_LIST 0 +#define SPAWN_SET_VALUE_NAME 1 +#define SPAWN_SET_VALUE_LEVEL 2 +#define SPAWN_SET_VALUE_DIFFICULTY 3 +#define SPAWN_SET_VALUE_MODEL_TYPE 4 +#define SPAWN_SET_VALUE_CLASS 5 +#define SPAWN_SET_VALUE_GENDER 6 +#define SPAWN_SET_VALUE_SHOW_NAME 7 +#define SPAWN_SET_VALUE_ATTACKABLE 8 +#define SPAWN_SET_VALUE_SHOW_LEVEL 9 +#define SPAWN_SET_VALUE_TARGETABLE 10 +#define SPAWN_SET_VALUE_SHOW_COMMAND_ICON 11 +#define SPAWN_SET_VALUE_HAND_ICON 12 +#define SPAWN_SET_VALUE_HAIR_TYPE 13 +#define SPAWN_SET_VALUE_FACIAL_HAIR_TYPE 14 +#define SPAWN_SET_VALUE_WING_TYPE 15 +#define SPAWN_SET_VALUE_CHEST_TYPE 16 +#define SPAWN_SET_VALUE_LEGS_TYPE 17 +#define SPAWN_SET_VALUE_SOGA_HAIR_TYPE 18 +#define SPAWN_SET_VALUE_SOGA_FACIAL_HAIR_TYPE 19 +#define SPAWN_SET_VALUE_SOGA_MODEL_TYPE 20 +#define SPAWN_SET_VALUE_SIZE 21 +#define SPAWN_SET_VALUE_HP 22 +#define SPAWN_SET_VALUE_POWER 23 +#define SPAWN_SET_VALUE_HEROIC 24 +#define SPAWN_SET_VALUE_RESPAWN 25 +#define SPAWN_SET_VALUE_X 26 +#define SPAWN_SET_VALUE_Y 27 +#define SPAWN_SET_VALUE_Z 28 +#define SPAWN_SET_VALUE_HEADING 29 +#define SPAWN_SET_VALUE_LOCATION 30 +#define SPAWN_SET_VALUE_COMMAND_PRIMARY 31 +#define SPAWN_SET_VALUE_COMMAND_SECONDARY 32 +#define SPAWN_SET_VALUE_VISUAL_STATE 33 +#define SPAWN_SET_VALUE_ACTION_STATE 34 +#define SPAWN_SET_VALUE_MOOD_STATE 35 +#define SPAWN_SET_VALUE_INITIAL_STATE 36 +#define SPAWN_SET_VALUE_ACTIVITY_STATE 37 +#define SPAWN_SET_VALUE_COLLISION_RADIUS 38 +#define SPAWN_SET_VALUE_FACTION 39 +#define SPAWN_SET_VALUE_SPAWN_SCRIPT 40 +#define SPAWN_SET_VALUE_SPAWNENTRY_SCRIPT 41 +#define SPAWN_SET_VALUE_SPAWNLOCATION_SCRIPT 42 +#define SPAWN_SET_VALUE_SUB_TITLE 43 +#define SPAWN_SET_VALUE_EXPIRE 45 +#define SPAWN_SET_VALUE_EXPIRE_OFFSET 46 +#define SPAWN_SET_VALUE_X_OFFSET 47 +#define SPAWN_SET_VALUE_Y_OFFSET 48 +#define SPAWN_SET_VALUE_Z_OFFSET 49 +#define SPAWN_SET_VALUE_DEVICE_ID 50 +#define SPAWN_SET_VALUE_PITCH 51 +#define SPAWN_SET_VALUE_ROLL 52 +#define SPAWN_SET_VALUE_HIDE_HOOD 53 +#define SPAWN_SET_VALUE_EMOTE_STATE 54 +#define SPAWN_SET_VALUE_ICON 55 +#define SPAWN_SET_VALUE_PREFIX 56 +#define SPAWN_SET_VALUE_SUFFIX 57 +#define SPAWN_SET_VALUE_LASTNAME 58 +#define SPAWN_SET_VALUE_EXPANSION_FLAG 59 +#define SPAWN_SET_VALUE_MERCHANT_MIN_LEVEL 60 +#define SPAWN_SET_VALUE_MERCHANT_MAX_LEVEL 61 +#define SPAWN_SET_VALUE_HOLIDAY_FLAG 62 +#define SPAWN_SET_SKIN_COLOR 63 +#define SPAWN_SET_AAXP_REWARDS 64 + +#define SPAWN_SET_HAIR_COLOR1 65 +#define SPAWN_SET_HAIR_COLOR2 66 +#define SPAWN_SET_HAIR_TYPE_COLOR 67 +#define SPAWN_SET_HAIR_FACE_COLOR 68 +#define SPAWN_SET_HAIR_TYPE_HIGHLIGHT_COLOR 69 +#define SPAWN_SET_HAIR_FACE_HIGHLIGHT_COLOR 70 +#define SPAWN_SET_HAIR_HIGHLIGHT 71 +#define SPAWN_SET_MODEL_COLOR 72 +#define SPAWN_SET_EYE_COLOR 73 +#define SPAWN_SET_SOGA_SKIN_COLOR 74 +#define SPAWN_SET_SOGA_HAIR_COLOR1 75 +#define SPAWN_SET_SOGA_HAIR_COLOR2 76 +#define SPAWN_SET_SOGA_HAIR_TYPE_COLOR 77 +#define SPAWN_SET_SOGA_HAIR_FACE_COLOR 78 +#define SPAWN_SET_SOGA_HAIR_TYPE_HIGHLIGHT_COLOR 79 +#define SPAWN_SET_SOGA_HAIR_FACE_HIGHLIGHT_COLOR 80 +#define SPAWN_SET_SOGA_HAIR_HIGHLIGHT 81 +#define SPAWN_SET_SOGA_MODEL_COLOR 82 +#define SPAWN_SET_SOGA_EYE_COLOR 83 + +#define SPAWN_SET_CHEEK_TYPE 84 +#define SPAWN_SET_CHIN_TYPE 85 +#define SPAWN_SET_EAR_TYPE 86 +#define SPAWN_SET_EYE_BROW_TYPE 87 +#define SPAWN_SET_EYE_TYPE 88 +#define SPAWN_SET_LIP_TYPE 89 +#define SPAWN_SET_NOSE_TYPE 90 +#define SPAWN_SET_BODY_SIZE 91 +#define SPAWN_SET_BODY_AGE 92 +#define SPAWN_SET_SOGA_CHEEK_TYPE 93 +#define SPAWN_SET_SOGA_CHIN_TYPE 94 +#define SPAWN_SET_SOGA_EAR_TYPE 95 +#define SPAWN_SET_SOGA_EYE_BROW_TYPE 96 +#define SPAWN_SET_SOGA_EYE_TYPE 97 +#define SPAWN_SET_SOGA_LIP_TYPE 98 +#define SPAWN_SET_SOGA_NOSE_TYPE 99 +#define SPAWN_SET_SOGA_BODY_SIZE 100 +#define SPAWN_SET_SOGA_BODY_AGE 101 + +#define SPAWN_SET_ATTACK_TYPE 102 +#define SPAWN_SET_RACE_TYPE 103 +#define SPAWN_SET_LOOT_TIER 104 +#define SPAWN_SET_LOOT_DROP_TYPE 105 +#define SPAWN_SET_SCARED_STRONG_PLAYERS 106 + +#define ZONE_SET_VALUE_EXPANSION_ID 0 +#define ZONE_SET_VALUE_NAME 1 +#define ZONE_SET_VALUE_FILE 2 +#define ZONE_SET_VALUE_DESCRIPTION 3 +#define ZONE_SET_VALUE_SAFE_X 4 +#define ZONE_SET_VALUE_SAFE_Y 5 +#define ZONE_SET_VALUE_SAFE_Z 6 +#define ZONE_SET_VALUE_UNDERWORLD 7 +#define ZONE_SET_VALUE_MIN_RECOMMENDED 8 +#define ZONE_SET_VALUE_MAX_RECOMMENDED 9 +#define ZONE_SET_VALUE_ZONE_TYPE 10 +#define ZONE_SET_VALUE_ALWAYS_LOADED 11 +#define ZONE_SET_VALUE_CITY_ZONE 12 +#define ZONE_SET_VALUE_MIN_STATUS 13 +#define ZONE_SET_VALUE_MIN_LEVEL 14 +#define ZONE_SET_VALUE_START_ZONE 15 +#define ZONE_SET_VALUE_INSTANCE_TYPE 16 +#define ZONE_SET_VALUE_DEFAULT_REENTER_TIME 17 +#define ZONE_SET_VALUE_DEFAULT_RESET_TIME 18 +#define ZONE_SET_VALUE_DEFAULT_LOCKOUT_TIME 19 +#define ZONE_SET_VALUE_FORCE_GROUP_TO_ZONE 20 +#define ZONE_SET_VALUE_LUA_SCRIPT 21 +#define ZONE_SET_VALUE_SHUTDOWN_TIMER 22 +#define ZONE_SET_VALUE_ZONE_MOTD 23 +#define ZONE_SET_VALUE_MAX_LEVEL 24 +#define ZONE_SET_VALUE_WEATHER_ALLOWED 25 + +#define COMMAND_SPAWN 1 +#define COMMAND_RACE 2 +#define COMMAND_LEVEL 3 +#define COMMAND_CLASS 4 +#define COMMAND_NAME 6 +#define COMMAND_SAY 7 +#define COMMAND_TELL 8 +#define COMMAND_YELL 9 +#define COMMAND_SHOUT 10 +#define COMMAND_AUCTION 11 +#define COMMAND_OOC 12 +#define COMMAND_EMOTE 13 +#define COMMAND_GROUPSAY 14 +#define COMMAND_GROUPINVITE 15 +#define COMMAND_GROUPDISBAND 16 +#define COMMAND_GROUP 17 +#define COMMAND_CLAIM 18 +#define COMMAND_HAIL 19 +#define COMMAND_ZONE 20 +#define COMMAND_ADMINFLAG 21 +#define COMMAND_KICK 22 +#define COMMAND_BAN 23 +#define COMMAND_INVENTORY 24 +#define COMMAND_SUMMONITEM 25 +#define COMMAND_FOLLOW 26 +#define COMMAND_STOP_FOLLOW 27 +#define COMMAND_LASTNAME 28 +#define COMMAND_CONFIRMLASTNAME 29 +#define COMMAND_COLLECTION_ADDITEM 30 +#define COMMAND_COLLECTION_FILTER_MATCHITEM 31 +#define COMMAND_MOVE 32 +#define COMMAND_INFO 33 +#define COMMAND_USEABILITY 34 +#define COMMAND_ENABLE_ABILITY_QUE 35 +#define COMMAND_RELOAD_ITEMS 36 +#define COMMAND_AUTO_ATTACK 37 +#define COMMAND_WEATHER 38 +#define COMMAND_SPEED 39 +#define COMMAND_SPAWN_MOVE 40 +#define COMMAND_WHO 41 +#define COMMAND_VERSION 42 +#define COMMAND_SPAWN_ADD 43 +#define COMMAND_SPAWN_CREATE 44 +#define COMMAND_SPAWN_SET 45 +#define COMMAND_SPAWN_REMOVE 46 +#define COMMAND_SPAWN_LIST 47 +#define COMMAND_SIT 48 +#define COMMAND_STAND 49 +#define COMMAND_SPAWN_TARGET 50 +#define COMMAND_SPAWN_EQUIPMENT 51 +#define COMMAND_SPAWN_DETAILS 52 +#define COMMAND_SELECT_JUNCTION 53 +#define COMMAND_KILL 54 +#define COMMAND_SUMMON 55 +#define COMMAND_GOTO 56 +#define COMMAND_FLYMODE 57 +#define COMMAND_SETTIME 58 +#define COMMAND_RELOAD_SPELLS 59 +#define COMMAND_LOOT 60 +#define COMMAND_USE 61 +#define COMMAND_RELOADSPAWNSCRIPTS 62 +#define COMMAND_RELOADLUASYSTEM 63 +#define COMMAND_RELOADSTRUCTS 64 +#define COMMAND_RELOAD 65 +#define COMMAND_LOOT_LIST 66 +#define COMMAND_LOOT_SETCOIN 67 +#define COMMAND_LOOT_ADDITEM 68 +#define COMMAND_LOOT_REMOVEITEM 69 +#define COMMAND_BANK 70 +#define COMMAND_BANK_DEPOSIT 71 +#define COMMAND_BANK_WITHDRAWAL 72 +#define COMMAND_BANK_CANCEL 73 +#define COMMAND_ATTACK 74 +#define COMMAND_REPORT_BUG 75 +#define COMMAND_ACCEPT_QUEST 76 +#define COMMAND_DECLINE_QUEST 77 +#define COMMAND_DELETE_QUEST 78 +#define COMMAND_RELOAD_QUESTS 79 +#define COMMAND_SPAWN_COMBINE 80 +#define COMMAND_DEPOP 81 +#define COMMAND_REPOP 82 +#define COMMAND_LUADEBUG 83 +#define COMMAND_TEST 84 +#define COMMAND_ACCEPT_REWARD 85 +#define COMMAND_FROM_MERCHANT 86 +#define COMMAND_MERCHANT_BUY 87 +#define COMMAND_MERCHANT_SELL 88 +#define COMMAND_CANCEL_MERCHANT 89 +#define COMMAND_START_MERCHANT 90 +#define COMMAND_BUYBACK 91 +#define COMMAND_SEARCH_STORES 92 +#define COMMAND_INVULNERABLE 93 +#define COMMAND_SEARCH_STORES_PAGE 94 +#define COMMAND_BUY_FROM_BROKER 95 +#define COMMAND_GROUP_ACCEPT_INVITE 96 +#define COMMAND_GROUP_DECLINE_INVITE 97 +#define COMMAND_RELOAD_GROUNDSPAWNS 98 +#define COMMAND_RELOAD_SPAWNS 99 +#define COMMAND_LOCK 100 +#define COMMAND_GIVEITEM 101 +#define COMMAND_SET_COMBAT_VOICE 102 +#define COMMAND_SET_EMOTE_VOICE 103 +#define COMMAND_RELOAD_ZONESCRIPTS 104 +#define COMMAND_GROUP_LEAVE 105 +#define COMMAND_GROUP_MAKE_LEADER 106 +#define COMMAND_GROUP_KICK 107 +#define COMMAND_FRIEND_ADD 108 +#define COMMAND_FRIEND_REMOVE 109 +#define COMMAND_FRIENDS 110 +#define COMMAND_IGNORE_ADD 111 +#define COMMAND_IGNORE_REMOVE 112 +#define COMMAND_IGNORES 113 +#define COMMAND_MENDER_REPAIR 114 +#define COMMAND_MENDER_REPAIR_ALL 115 +#define COMMAND_REPAIR 116 +#define COMMAND_USE_ITEM 117 +#define COMMAND_WEAPONSTATS 118 +#define COMMAND_START_MAIL 119 +#define COMMAND_GET_MAIL_MESSAGE 120 +#define COMMAND_TAKE_MAIL_ATTACHMENTS 121 +#define COMMAND_REPORT_SPAM 122 +#define COMMAND_CANCEL_MAIL 123 +#define COMMAND_ADD_MAIL_PLAT 124 +#define COMMAND_ADD_MAIL_GOLD 125 +#define COMMAND_ADD_MAIL_SILVER 126 +#define COMMAND_ADD_MAIL_COPPER 127 +#define COMMAND_SET_MAIL_ITEM 128 +#define COMMAND_CANCEL_SEND_MAIL 129 +#define COMMAND_REMOVE_MAIL_PLAT 130 +#define COMMAND_REMOVE_MAIL_GOLD 131 +#define COMMAND_REMOVE_MAIL_SILVER 132 +#define COMMAND_REMOVE_MAIL_COPPER 133 +#define COMMAND_DELETE_MAIL_MESSAGE 134 +#define COMMAND_TRACK 135 +#define COMMAND_INSPECT_PLAYER 136 +#define COMMAND_PET 137 +#define COMMAND_PETNAME 138 +#define COMMAND_NAME_PET 139 +#define COMMAND_RENAME 140 +#define COMMAND_CONFIRMRENAME 141 +#define COMMAND_PETOPTIONS 142 +#define COMMAND_SPAWN_TEMPLATE 143 // JA: new /spawn template command +#define COMMAND_CANNEDEMOTE 144 +#define COMMAND_BROADCAST 145 +#define COMMAND_ANNOUNCE 146 +#define COMMAND_AFK 147 +#define COMMAND_TOGGLE_ANONYMOUS 148 +#define COMMAND_TOGGLE_LFW 149 +#define COMMAND_TOGGLE_LFG 150 +#define COMMAND_SHOW_RANGED 151 +#define COMMAND_TOGGLE_AUTOCONSUME 152 +#define COMMAND_SHOW_HELM 153 +#define COMMAND_SHOW_HOOD_OR_HELM 154 +#define COMMAND_SHOW_CLOAK 155 +#define COMMAND_STOP_EATING 156 +#define COMMAND_STOP_DRINKING 157 +#define COMMAND_TOGGLE_ILLUSIONS 158 +#define COMMAND_SHOW_HOOD 159 +#define COMMAND_TOGGLE_DUELS 160 +#define COMMAND_TOGGLE_TRADES 161 +#define COMMAND_TOGGLE_GUILDS 162 +#define COMMAND_TOGGLE_GROUPS 163 +#define COMMAND_TOGGLE_RAIDS 164 +#define COMMAND_TOGGLE_LON 165 + +#define COMMAND_TOGGLE_GM_HIDE 167 +#define COMMAND_TOGGLE_GM_VANISH 168 +#define COMMAND_SPAWN_GROUP 169 +#define COMMAND_TOGGLE_ROLEPLAYING 170 +#define COMMAND_TOGGLE_VCINVITE 171 +#define COMMAND_START_TRADE 172 +#define COMMAND_ACCEPT_TRADE 173 +#define COMMAND_REJECT_TRADE 174 +#define COMMAND_CANCEL_TRADE 175 +#define COMMAND_SET_TRADE_COIN 176 +#define COMMAND_ADD_TRADE_COPPER 177 +#define COMMAND_ADD_TRADE_SILVER 178 +#define COMMAND_ADD_TRADE_GOLD 179 +#define COMMAND_ADD_TRADE_PLAT 180 +#define COMMAND_REMOVE_TRADE_COPPER 181 +#define COMMAND_REMOVE_TRADE_SILVER 182 +#define COMMAND_REMOVE_TRADE_GOLD 183 +#define COMMAND_REMOVE_TRADE_PLAT 184 +#define COMMAND_ADD_TRADE_ITEM 185 +#define COMMAND_REMOVE_TRADE_ITEM 186 +#define COMMAND_TOGGLE_COMBAT_EXP 187 +#define COMMAND_TOGGLE_QUEST_EXP 188 +#define COMMAND_TOGGLE_BONUS_EXP 189 +#define COMMAND_ZONE_SHUTDOWN 190 +#define COMMAND_ZONE_SAFE 191 +#define COMMAND_ZONE_REVIVE 192 +#define COMMAND_RELOAD_ZONES 193 + +#define COMMAND_DUEL 200 +#define COMMAND_DUELBET 201 +#define COMMAND_DUEL_ACCEPT 202 +#define COMMAND_DUEL_DECLINE 203 +#define COMMAND_DUEL_SURRENDER 204 +#define COMMAND_DUEL_TOGGLE 205 + +#define COMMAND_ANIMTEST 211 +#define COMMAND_ITEMSEARCH 212 + +#define COMMAND_ACTION 232 // JA: What is this? Exists nowhere else... +#define COMMAND_SKILL_ADD 233 +#define COMMAND_SKILL_REMOVE 234 +#define COMMAND_SKILL_LIST 235 +#define COMMAND_SKILL 236 +#define COMMAND_ZONE_SET 237 +#define COMMAND_ZONE_DETAILS 238 +#define COMMAND_RANDOMIZE 239 +#define COMMAND_RELOAD_ENTITYCOMMANDS 240 +#define COMMAND_ENTITYCOMMAND 241 +#define COMMAND_ENTITYCOMMAND_LIST 242 +#define COMMAND_RELOAD_FACTIONS 243 +#define COMMAND_MERCHANT 244 +#define COMMAND_MERCHANT_LIST 245 +#define COMMAND_APPEARANCE 246 +#define COMMAND_APPEARANCE_LIST 247 +#define COMMAND_RELOAD_MAIL 248 +#define COMMAND_DISTANCE 249 +#define COMMAND_GUILDSAY 250 +#define COMMAND_OFFICERSAY 251 +#define COMMAND_GUILD 252 +#define COMMAND_SET_GUILD_MEMBER_NOTE 253 +#define COMMAND_SET_GUILD_OFFICER_NOTE 254 +#define COMMAND_RELOAD_GUILDS 255 +#define COMMAND_CREATE 256 +#define COMMAND_CREATE_GUILD 257 +#define COMMAND_GUILDS 258 +#define COMMAND_GUILDS_CREATE 259 +#define COMMAND_GUILDS_DELETE 260 +#define COMMAND_GUILDS_ADD 261 +#define COMMAND_GUILDS_REMOVE 262 +#define COMMAND_GUILDS_LIST 263 +#define COMMAND_LOTTO 264 +#define COMMAND_CLEAR_ALL_QUEUED 265 +#define COMMAND_SCRIBE_SCROLL_ITEM 266 +#define COMMAND_RELOAD_LOCATIONS 267 +#define COMMAND_LOCATION 268 +#define COMMAND_LOCATION_CREATE 269 +#define COMMAND_LOCATION_ADD 270 +#define COMMAND_GRID 271 +#define COMMAND_LOCATION_REMOVE 272 +#define COMMAND_LOCATION_DELETE 273 +#define COMMAND_LOCATION_LIST 274 +#define COMMAND_USE_EQUIPPED_ITEM 275 +#define COMMAND_CANCEL_MAINTAINED 276 +#define COMMAND_LOOT_CORPSE 277 +#define COMMAND_MOTD 278 +#define COMMAND_RANDOM 279 +#define COMMAND_TRY_ON 280 +#define COMMAND_TITLE 281 +#define COMMAND_GUILD_BANK 282 +#define COMMAND_GUILD_BANK_DEPOSIT 283 +#define COMMAND_GUILD_BANK_WITHDRAWAL 284 +#define COMMAND_GUILD_BANK_CANCEL 285 +#define COMMAND_TITLE_LIST 286 +#define COMMAND_TITLE_SETPREFIX 287 +#define COMMAND_TITLE_SETSUFFIX 288 +#define COMMAND_TITLE_FIX 289 +#define COMMAND_LANGUAGES 290 +#define COMMAND_SET_LANGUAGE 291 +#define COMMAND_ACCEPT_ADVANCEMENT 293 + +#define COMMAND_JOIN_CHANNEL 294 +#define COMMAND_JOIN_CHANNEL_FROM_LOAD 295 +#define COMMAND_TELL_CHANNEL 296 +#define COMMAND_LEAVE_CHANNEL 297 +#define COMMAND_WHO_CHANNEL 298 + +#define COMMAND_CREATEFROMRECIPE 299 +#define COMMAND_RAIN 300 +#define COMMAND_TO_MERCHANT 301 +#define COMMAND_SELECT 302 +#define COMMAND_SMP 303 +#define COMMAND_CONSUME_FOOD 304 +#define COMMAND_AQUAMAN 305 +#define COMMAND_ATTUNE_INV 306 +#define COMMAND_PLAYER 307 +#define COMMAND_PLAYER_COINS 308 +#define COMMAND_RESET_ZONE_TIMER 309 +#define COMMAND_ACHIEVEMENT_ADD 310 +#define COMMAND_EDITOR 311 +#define COMMAND_ACCEPT_RESURRECTION 312 +#define COMMAND_DECLINE_RESURRECTION 313 +#define COMMAND_WIND 314 +#define COMMAND_TARGETITEM 315 +#define COMMAND_READ 463 + +#define COMMAND_BOT 500 +#define COMMAND_BOT_CREATE 501 +#define COMMAND_BOT_CUSTOMIZE 502 +#define COMMAND_BOT_SPAWN 503 +#define COMMAND_BOT_LIST 504 +#define COMMAND_BOT_INV 505 +#define COMMAND_BOT_SETTINGS 506 +#define COMMAND_BOT_HELP 507 + +#define COMMAND_OPEN 508 +#define COMMAND_CASTSPELL 509 +#define COMMAND_DISARM 510 +#define COMMAND_KNOWLEDGEWINDOWSORT 511 +#define COMMAND_PLACE_HOUSE_ITEM 512 +#define COMMAND_GM 513 +#define COMMAND_HOUSE_UI 514 +#define COMMAND_HOUSE 515 +#define COMMAND_MOVE_ITEM 516 +#define COMMAND_PICKUP 517 +#define COMMAND_HOUSE_DEPOSIT 518 + +#define COMMAND_RELOAD_RULES 519 +#define COMMAND_RELOAD_TRANSPORTERS 520 +#define COMMAND_FINDSPAWN 521 +#define COMMAND_RELOAD_STARTABILITIES 522 + +#define COMMAND_WAYPOINT 523 + +#define COMMAND_RELOADREGIONSCRIPTS 524 + +#define COMMAND_MOVECHARACTER 525 + +#define COMMAND_CRAFTITEM 526 + +#define COMMAND_FROMBROKER 527 + +#define COMMAND_MENTOR 528 +#define COMMAND_UNMENTOR 529 + +#define COMMAND_CANCEL_EFFECT 530 +#define COMMAND_CUREPLAYER 531 + +#define COMMAND_RELOAD_VOICEOVERS 532 +#define COMMAND_SHARE_QUEST 533 + +#define COMMAND_SETAUTOLOOTMODE 534 +#define COMMAND_ASSIST 535 +#define COMMAND_TARGET 536 +#define COMMAND_TARGET_PET 537 + +#define COMMAND_SET_CONSUME_FOOD 538 + +#define GET_AA_XML 750 +#define ADD_AA 751 +#define COMMIT_AA_PROFILE 752 +#define BEGIN_AA_PROFILE 753 +#define BACK_AA 754 +#define REMOVE_AA 755 +#define SWITCH_AA_PROFILE 756 +#define CANCEL_AA_PROFILE 757 +#define SAVE_AA_PROFILE 758 + +#define COMMAND_MOOD 800 + + +#define COMMAND_MODIFY 1000 // INSERT INTO `commands`(`id`,`type`,`command`,`subcommand`,`handler`,`required_status`) VALUES ( NULL,'1','modify','','1000','200'); +#define COMMAND_MODIFY_CHARACTER 1001 +#define COMMAND_MODIFY_FACTION 1002 +#define COMMAND_MODIFY_GUILD 1003 +#define COMMAND_MODIFY_ITEM 1004 +#define COMMAND_MODIFY_QUEST 1005 +#define COMMAND_MODIFY_SKILL 1006 +#define COMMAND_MODIFY_SPAWN 1007 +#define COMMAND_MODIFY_SPELL 1008 +#define COMMAND_MODIFY_ZONE 1009 + +#endif diff --git a/source/WorldServer/Commands/CommandsDB.cpp b/source/WorldServer/Commands/CommandsDB.cpp new file mode 100644 index 0000000..a5c8d01 --- /dev/null +++ b/source/WorldServer/Commands/CommandsDB.cpp @@ -0,0 +1,316 @@ +/* + 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 . +*/ +#ifdef WIN32 + #include + #include +#endif +#include +#include +#include "../../common/Log.h" +#include "../WorldDatabase.h" +#include "Commands.h" +#include "ConsoleCommands.h" + + + +map* WorldDatabase::GetSpawnTemplateListByName(const char* name) +{ + LogWrite(COMMAND__DEBUG, 0, "Command", "Player listing spawn templates by template name ('%s')...", name); + + map* ret = 0; + string template_name = ""; + + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, name FROM spawn_templates WHERE name RLIKE '%s' LIMIT 0,10", getSafeEscapeString(name).c_str()); + if(result && mysql_num_rows(result) > 0) + { + ret = new map; + MYSQL_ROW row; + while(result && (row = mysql_fetch_row(result))) + { + template_name = string(row[1]); + (*ret)[atoul(row[0])] = template_name; + LogWrite(COMMAND__DEBUG, 5, "Command", "\t%u. '%s'", atoul(row[0]), template_name.c_str()); + } + } + return ret; +} + +map* WorldDatabase::GetSpawnTemplateListByID(int32 location_id) +{ + LogWrite(COMMAND__DEBUG, 0, "Command", "Player listing spawn templates by LocaionID: %u...", location_id); + + map* ret = 0; + string template_name = ""; + + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, name FROM spawn_templates WHERE spawn_location_id = %u", location_id); + if(result && mysql_num_rows(result) > 0) + { + ret = new map; + MYSQL_ROW row; + while(result && (row = mysql_fetch_row(result))) + { + template_name = string(row[1]); + (*ret)[atoul(row[0])] = template_name; + LogWrite(COMMAND__DEBUG, 5, "Command", "\t%u. '%s'", atoul(row[0]), template_name.c_str()); + } + } + return ret; +} + + +int32 WorldDatabase::SaveSpawnTemplate(int32 placement_id, const char* template_name) +{ + Query query; + + string str_name = getSafeEscapeString(template_name).c_str(); + LogWrite(COMMAND__DEBUG, 0, "Command", "Player saving spawn template '%s' for placement_id %u...", str_name.c_str(), placement_id); + + query.RunQuery2(Q_INSERT, "INSERT INTO spawn_templates (name, spawn_location_id) VALUES ('%s', %u)", str_name.c_str(), placement_id); + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(COMMAND__ERROR, 0, "Command", "Error in SaveSpawnTemplate query '%s': %s", query.GetQuery(), query.GetError()); + return 0; + } + int32 ret = query.GetLastInsertedID(); + LogWrite(COMMAND__DEBUG, 0, "Command", "Success! Returning TemplateID: %u...", ret); + return ret; +} + +bool WorldDatabase::RemoveSpawnTemplate(int32 template_id) +{ + Query query; + + LogWrite(COMMAND__DEBUG, 0, "Command", "Player removing spawn template ID %u...", template_id); + + query.RunQuery2(Q_DELETE, "DELETE FROM spawn_templates WHERE id = %u", template_id); + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(COMMAND__ERROR, 0, "Command", "Error in RemoveSpawnTemplate query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + + if (query.GetAffectedRows() > 0 ) + { + LogWrite(COMMAND__DEBUG, 0, "Command", "Success! Removed spawn template ID %u...", template_id); + return true; + } + + return false; +} + + +int32 WorldDatabase::CreateSpawnFromTemplateByID(Client* client, int32 template_id) +{ + Query query, query2, query3, query4, query5, query6; + MYSQL_ROW row; + int32 spawn_location_id = 0; + float new_x = client->GetPlayer()->GetX(); + float new_y = client->GetPlayer()->GetY(); + float new_z = client->GetPlayer()->GetZ(); + float new_heading = client->GetPlayer()->GetHeading(); + + LogWrite(COMMAND__DEBUG, 0, "Command", "Creating spawn point from templateID %u...", template_id); + LogWrite(COMMAND__DEBUG, 5, "Command", "\tCoords: %.2f %.2f %.2f...", new_x, new_y, new_z); + + // find the spawn_location_id in the template we plan to duplicate + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT spawn_location_id FROM spawn_templates WHERE id = %u", template_id); + if (result && (row = mysql_fetch_row(result))) { + if (row[0]) + spawn_location_id = atoi(row[0]); + } + + if( spawn_location_id > 0 ) + { + LogWrite(COMMAND__DEBUG, 5, "Command", "\tUsing LocationID: %u...", spawn_location_id); + + // insert a new spawn_location_name record + string name = "TemplateGenerated"; + query2.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_name (name) VALUES ('%s')", name.c_str()); + if(query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query2.GetQuery(), query2.GetError()); + return 0; + } + int32 new_location_id = query2.GetLastInsertedID(); + LogWrite(COMMAND__DEBUG, 5, "Command", "Created new Spawn Location: '%s' (%u)", name.c_str(), new_location_id); + + // get all spawn_location_entries that match the templates spawn_location_id value and insert as new + LogWrite(COMMAND__DEBUG, 5, "Command", "Finding existing spawn_location_entry(s) for location_id %u", spawn_location_id); + MYSQL_RES* result2 = query3.RunQuery2(Q_SELECT, "SELECT spawn_id, spawnpercentage FROM spawn_location_entry WHERE spawn_location_id = %u", spawn_location_id); + if(result2 && mysql_num_rows(result2) > 0){ + MYSQL_ROW row2; + while(result2 && (row2 = mysql_fetch_row(result2)) && row2[0]) + { + query4.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_entry (spawn_id, spawn_location_id, spawnpercentage) VALUES (%u, %u, %i)", + atoul(row2[0]), new_location_id, atoi(row2[1])); + if(query4.GetErrorNumber() && query4.GetError() && query4.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query4.GetQuery(), query4.GetError()); + return 0; + } + LogWrite(COMMAND__DEBUG, 5, "Command", "Insert Entry for spawn_id %u, location_id %u, percentage %i", atoul(row2[0]), new_location_id, atoi(row2[1])); + } + } + + // get all spawn_location_placements that match the templates spawn_location_id value and insert as new + // Note: /spawn templates within current zone_id only, because of spawn_id issues (cannot template an Antonic spawn in Commonlands) + LogWrite(COMMAND__DEBUG, 5, "Command", "Finding existing spawn_location_placement(s) for location_id %u", spawn_location_id); + MYSQL_RES* result3 = query5.RunQuery2(Q_SELECT, "SELECT zone_id, x_offset, y_offset, z_offset, respawn, expire_timer, expire_offset, grid_id FROM spawn_location_placement WHERE spawn_location_id = %u", spawn_location_id); + if(result3 && mysql_num_rows(result3) > 0){ + MYSQL_ROW row3; + while(result3 && (row3 = mysql_fetch_row(result3)) && row3[0]) + { + query6.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_placement (zone_id, spawn_location_id, x, y, z, heading, x_offset, y_offset, z_offset, respawn, expire_timer, expire_offset, grid_id) VALUES (%i, %u, %2f, %2f, %2f, %2f, %2f, %2f, %2f, %i, %i, %i, %u)", + atoi(row3[0]), new_location_id, new_x, new_y, new_z, new_heading, atof(row3[1]), atof(row3[2]), atof(row3[3]), atoi(row3[4]), atoi(row3[5]), atoi(row3[6]), atoul(row3[7])); + if(query6.GetErrorNumber() && query6.GetError() && query6.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query6.GetQuery(), query6.GetError()); + return 0; + } + LogWrite(COMMAND__DEBUG, 5, "Command", "Insert Placement at new coords for location_id %u", new_location_id); + } + } + LogWrite(COMMAND__DEBUG, 0, "Command", "Success! New spawn(s) from TemplateID %u created from location %u", template_id, spawn_location_id); + return new_location_id; + } + + return 0; +} + + +int32 WorldDatabase::CreateSpawnFromTemplateByName(Client* client, const char* template_name) +{ + Query query, query1, query2, query3, query4, query5, query6; + MYSQL_ROW row; + int32 template_id = 0; + int32 spawn_location_id = 0; + float new_x = client->GetPlayer()->GetX(); + float new_y = client->GetPlayer()->GetY(); + float new_z = client->GetPlayer()->GetZ(); + float new_heading = client->GetPlayer()->GetHeading(); + + LogWrite(COMMAND__DEBUG, 0, "Command", "Creating spawn point from template '%s'...", template_name); + LogWrite(COMMAND__DEBUG, 5, "Command", "\tCoords: %.2f %.2f %.2f...", new_x, new_y, new_z); + + // find the spawn_location_id in the template we plan to duplicate + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, spawn_location_id FROM spawn_templates WHERE name = '%s'", template_name); + if (result && (row = mysql_fetch_row(result))) { + if (row[0]) + { + template_id = atoul(row[0]); + spawn_location_id = atoi(row[1]); + } + } + + if( spawn_location_id > 0 ) + { + LogWrite(COMMAND__DEBUG, 5, "Command", "\tUsing LocationID: %u...", spawn_location_id); + + // insert a new spawn_location_name record + string name = "TemplateGenerated"; + query2.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_name (name) VALUES ('%s')", name.c_str()); + if(query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query2.GetQuery(), query2.GetError()); + return 0; + } + int32 new_location_id = query2.GetLastInsertedID(); + LogWrite(COMMAND__DEBUG, 5, "Command", "Created new Spawn Location: '%s' (%u)", name.c_str(), new_location_id); + + // get all spawn_location_entries that match the templates spawn_location_id value and insert as new + LogWrite(COMMAND__DEBUG, 5, "Command", "Finding existing spawn_location_entry(s) for location_id %u", spawn_location_id); + MYSQL_RES* result2 = query3.RunQuery2(Q_SELECT, "SELECT spawn_id, spawnpercentage FROM spawn_location_entry WHERE spawn_location_id = %u", spawn_location_id); + if(result2 && mysql_num_rows(result2) > 0){ + MYSQL_ROW row2; + while(result2 && (row2 = mysql_fetch_row(result2)) && row2[0]) + { + query4.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_entry (spawn_id, spawn_location_id, spawnpercentage) VALUES (%u, %u, %i)", + atoul(row2[0]), new_location_id, atoi(row2[1])); + if(query4.GetErrorNumber() && query4.GetError() && query4.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query4.GetQuery(), query4.GetError()); + return 0; + } + LogWrite(COMMAND__DEBUG, 5, "Command", "Insert Entry for spawn_id %u, location_id %u, percentage %i", atoul(row2[0]), new_location_id, atoi(row2[1])); + } + } + + // get all spawn_location_placements that match the templates spawn_location_id value and insert as new + // Note: /spawn templates within current zone_id only, because of spawn_id issues (cannot template an Antonic spawn in Commonlands) + LogWrite(COMMAND__DEBUG, 5, "Command", "Finding existing spawn_location_placement(s) for location_id %u", spawn_location_id); + MYSQL_RES* result3 = query5.RunQuery2(Q_SELECT, "SELECT zone_id, x_offset, y_offset, z_offset, respawn, expire_timer, expire_offset, grid_id FROM spawn_location_placement WHERE spawn_location_id = %u", spawn_location_id); + if(result3 && mysql_num_rows(result3) > 0){ + MYSQL_ROW row3; + while(result3 && (row3 = mysql_fetch_row(result3)) && row3[0]) + { + query6.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_placement (zone_id, spawn_location_id, x, y, z, heading, x_offset, y_offset, z_offset, respawn, expire_timer, expire_offset, grid_id) VALUES (%i, %u, %2f, %2f, %2f, %2f, %2f, %2f, %2f, %i, %i, %i, %u)", + atoi(row3[0]), new_location_id, new_x, new_y, new_z, new_heading, atof(row3[1]), atof(row3[2]), atof(row3[3]), atoi(row3[4]), atoi(row3[5]), atoi(row3[6]), atoul(row3[7])); + if(query6.GetErrorNumber() && query6.GetError() && query6.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query6.GetQuery(), query6.GetError()); + return 0; + } + LogWrite(COMMAND__DEBUG, 5, "Command", "Insert Placement at new coords for location_id %u", new_location_id); + } + } + LogWrite(COMMAND__DEBUG, 0, "Command", "Success! New spawn(s) from TemplateID %u created from location %u", template_id, spawn_location_id); + return new_location_id; + } + + return 0; +} + +bool WorldDatabase::SaveZoneSafeCoords(int32 zone_id, float x, float y, float z, float heading) +{ + Query query; + + LogWrite(COMMAND__DEBUG, 0, "Command", "Setting safe coords for zone %u (X: %.2f, Y: %.2f, Z: %.2f, H: %.2f)", zone_id, x, y, z, heading); + + query.RunQuery2(Q_UPDATE, "UPDATE zones SET safe_x = %f, safe_y = %f, safe_z = %f, safe_heading = %f WHERE id = %u", x, y, z, heading, zone_id); + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(DATABASE__ERROR, 0, "DBCore", "Error in SaveZoneSafeCoords query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + + if (query.GetAffectedRows() > 0 ) + { + LogWrite(COMMAND__DEBUG, 0, "Command", "Success! Set new safe coordinates in zone ID %u...", zone_id); + return true; + } + + LogWrite(COMMAND__ERROR, 0, "Command", "FAILED! Set new safe coordinates in zone ID %u...", zone_id); + return false; +} + +bool WorldDatabase::SaveSignZoneToCoords(int32 spawn_id, float x, float y, float z, float heading) +{ + Query query; + + LogWrite(COMMAND__DEBUG, 0, "Command", "Setting Zone-To coords for Spawn ID %u (X: %.2f, Y: %.2f, Z: %.2f, H: %.2f)", spawn_id, x, y, z, heading); + + query.RunQuery2(Q_UPDATE, "UPDATE spawn_signs SET zone_x = %f, zone_y = %f, zone_z = %f, zone_heading = %f WHERE spawn_id = %u", x, y, z, heading, spawn_id); + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(DATABASE__ERROR, 0, "DBCore", "Error in SaveSignZoneToCoords query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + + if (query.GetAffectedRows() > 0 ) + { + LogWrite(COMMAND__DEBUG, 0, "Command", "Success! Set new Zone-To coordinates in zone ID %u...", spawn_id); + return true; + } + + LogWrite(COMMAND__ERROR, 0, "Command", "FAILED! Set new Zone-To coordinates in zone ID %u...", spawn_id); + return false; +} diff --git a/source/WorldServer/Commands/ConsoleCommands.cpp b/source/WorldServer/Commands/ConsoleCommands.cpp new file mode 100644 index 0000000..648ec93 --- /dev/null +++ b/source/WorldServer/Commands/ConsoleCommands.cpp @@ -0,0 +1,549 @@ +/* + 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 . +*/ + +#include +using namespace std; +#include +#include +#include + +#include "../../common/debug.h" +#include "../../common/Log.h" +#include "../../common/seperator.h" +#include "ConsoleCommands.h" +#include "../World.h" +#include "../Rules/Rules.h" +#include "../WorldDatabase.h" + +extern volatile bool RunLoops; +bool ContinueLoops = false; +extern Variables variables; +extern ZoneList zone_list; +extern RuleManager rule_manager; +extern WorldDatabase database; + +void ProcessConsoleInput(const char * cmdInput) +{ + static ConsoleCommand Commands[] = { + + // account controls + { &ConsoleBanCommand, "ban", "[player] {duration} {reason}", "Ban player with {optional} duration and reason." }, + { &ConsoleUnbanCommand, "unban", "[player]", "Unban a player." }, + { &ConsoleKickCommand, "kick", "[player] {reason}", "Kick player with {optional} reason." }, + + // chat controls + { &ConsoleAnnounceCommand, "announce", "[message]", "Sends Announcement message to all channels/clients." }, + { &ConsoleBroadcastCommand, "broadcast","[message]", "Sends Broadcast message to all channels/clients." }, + { &ConsoleChannelCommand, "channel", "[channel] [message]", "Sends Channel message to channel." }, + { &ConsoleTellCommand, "tell", "[player] [message]", "Sends Private message to player." }, + + // world system controls + { &ConsoleGuildCommand, "guild", "[params]", "" }, + { &ConsolePlayerCommand, "player", "[params]", "" }, + { &ConsoleSetAdminPlayer, "makeadmin", "[charname] [status=0]", "" }, + { &ConsoleZoneCommand, "zone", "[command][value]", "command = help to get help" }, + { &ConsoleWorldCommand, "world", "[params]", "" }, + { &ConsoleGetMOTDCommand, "getmotd", "", "Display current MOTD" }, + { &ConsoleSetMOTDCommand, "setmotd", "[new motd]", "Sets a new MOTD" }, + + /// misc controls + { &ConsoleWhoCommand, "who", "{zone id | player}", "Shows who is online globally, or in a given zone." }, + { &ConsoleReloadCommand, "reload", "[all | [type]]", "Reload main systems." }, + { &ConsoleRulesCommand, "rules", "{zone} {id}", "Show Global Ruleset (or Zone ruleset {optional})" }, + { &ConsoleShutdownCommand, "shutdown", "[delay]", "Gracefully shutdown world in [delay] sesconds." }, + { &ConsoleCancelShutdownCommand,"cancel", "", "Cancel shutdown command." }, + { &ConsoleExitCommand, "exit", "", "Brutally kills the world without mercy." }, + { &ConsoleExitCommand, "quit", "", "Brutally kills the world without mercy." }, + { &ConsoleTestCommand, "test", "", "Dev testing command." }, + { NULL, NULL, NULL, NULL }, + }; + + Seperator *sep = new Seperator(cmdInput, ' ', 20, 100, true); + bool found = false; + uint32 i; + + if (!sep) + return; + + if (!strcasecmp(sep->arg[0], "help") || sep->arg[0][0] == 'h' || sep->arg[0][0] == 'H' || sep->arg[0][0] == '?') { + found = true; + printf("======================================================================================================\n"); + printf("| %10s | %30s | %52s |\n", "Name", "Params", "Description"); + printf("======================================================================================================\n"); + for (i = 0; Commands[i].Name != NULL; i++) { + printf("| %10s | %30s | %52s |\n", Commands[i].Name, Commands[i].ParameterFormat, Commands[i].Description); + } + printf("======================================================================================================\n"); + printf("-[ Help formatted for 120 chars wide screen ]-\n"); + } + else { + for (i = 0; Commands[i].Name != NULL; ++i) { + if (!strcasecmp(Commands[i].Name, sep->arg[0])) { + found = true; + if (!Commands[i].CommandPointer(sep)) + printf("\nError, incorrect syntax for '%s'.\n Correct syntax is: '%s'.\n\n", Commands[i].Name, Commands[i].ParameterFormat ); + } + } + } + + if (!found) + printf("Invalid Command '%s'! Type '?' or 'help' to get a command list.\n\n", sep->arg[0]); + + fflush(stdout); + + delete sep; +} + +/************************************************* COMMANDS *************************************************/ + +bool ConsoleBanCommand(Seperator *sep) +{ + if( strlen(sep->arg[1]) == 0 ) + return false; + + return true; +} + +bool ConsoleUnbanCommand(Seperator *sep) +{ + if( strlen(sep->arg[1]) == 0 ) + return false; + + return true; +} + +bool ConsoleKickCommand(Seperator *sep) +{ + if( strlen(sep->arg[1]) == 0 ) + return false; + + return true; +} + + +bool ConsoleAnnounceCommand(Seperator *sep) +{ + if( strlen(sep->arg[1]) == 0 ) + return false; + + return true; +} + +bool ConsoleBroadcastCommand(Seperator *sep) +{ + if( strlen(sep->arg[1]) == 0 ) + return false; + + char message[4096]; + snprintf(message, sizeof(message), "%s %s", "BROADCAST:", sep->argplus[1]); + zone_list.HandleGlobalBroadcast(message); + return true; +} + +bool ConsoleChannelCommand(Seperator *sep) +{ + if( strlen(sep->arg[1]) == 0 ) + return false; + + return true; +} + +bool ConsoleTellCommand(Seperator *sep) +{ + if( strlen(sep->arg[1]) == 0 ) + return false; + + return true; +} + + +bool ConsoleWhoCommand(Seperator *sep) +{ + + // zone_list.ProcessWhoQuery(who, client); + + if (!strcasecmp(sep->arg[1], "zone")) { + printf("Who's Online in Zone"); + if (sep->IsNumber(2)) { + printf("ID %s:\n", sep->arg[2]); + printf("===============================================================================\n"); + printf("| %10s | %62s |\n", "CharID", "Name"); + printf("===============================================================================\n"); + } + else { + printf(" '%s':\n", sep->arg[2]); + printf("===============================================================================\n"); + printf("| %10s | %62s |\n", "CharID", "Name"); + printf("===============================================================================\n"); + } + } + else { + printf("Who's Online (Global):\n"); + printf("===============================================================================\n"); + printf("| %10s | %20s | %39s |\n", "CharID", "Name", "Zone"); + printf("===============================================================================\n"); + } + printf("Not Implemented... yet :)\n"); + printf("===============================================================================\n"); + return true; +} + +bool ConsoleGuildCommand(Seperator *sep) +{ + if( strlen(sep->arg[1]) == 0 ) + return false; + + return true; +} + +bool ConsolePlayerCommand(Seperator *sep) +{ + if( strlen(sep->arg[1]) == 0 ) + return false; + + return true; +} + +bool ConsoleSetAdminPlayer(Seperator *sep) +{ + if(!sep->arg[1] || strlen(sep->arg[1]) == 0) + return false; + + sint16 status = 0; + if(sep->IsNumber(2)) + status = atoi(sep->arg[2]); + + Client* client = zone_list.GetClientByCharName(sep->arg[1]); + + if(!client) { + printf("Client not found by char name, must be logged in\n"); + return true; + } + + if(!client->GetPlayer()) { + + printf("Player is not available for client class, try again\n"); + return true; + } + + client->SetAdminStatus(status); + if(status) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Admin status updated."); + database.UpdateAdminStatus(client->GetPlayer()->GetName(), status); + printf("Admin status for %s is updated to %i\n", client->GetPlayer()->GetName(), status); + + return true; +} + +bool ConsoleWorldCommand(Seperator *sep) +{ + if( strlen(sep->arg[1]) == 0 ) + return false; + + return true; +} + +bool ConsoleZoneCommand(Seperator *sep) +{ + if( strlen(sep->arg[1]) == 0 ) // has to be at least 1 arg (command) + return false; + + ZoneServer* zone = 0; + + if( strlen(sep->arg[2]) == 0 ) + { + // process commands without values + if (!strcasecmp(sep->arg[1], "active") ) + { + // not correct, but somehow need to access the Private zlist from World.h ??? + list zlist; + + list::iterator zone_iter; + ZoneServer* tmp = 0; + int zonesListed = 0; + + printf("> List Active Zones...\n"); + printf("======================================================================================================\n"); + printf("| %7s | %30s | %10s | %42s |\n", "ID", "Name", "Instance", "Description"); + printf("======================================================================================================\n"); + + for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++){ + tmp = *zone_iter; + zonesListed++; + printf("| %7d | %30s | %10d | %42s |\n", tmp->GetZoneID(), tmp->GetZoneName(), tmp->GetInstanceID(),tmp->GetZoneDescription()); + } + return true; + } + else if (!strcasecmp(sep->arg[1], "help") || sep->arg[1][0] == '?') + { + printf("======================================================================================================\n"); + printf("| %10s | %30s | %52s |\n", "Command", "Value", "Description"); + printf("======================================================================================================\n"); + printf("| %10s | %30s | %52s |\n", "active", "n/a", "List currently active zones"); + printf("| %10s | %30s | %52s |\n", "list", "[name]", "Lookup zone by name"); + printf("| %10s | %30s | %52s |\n", "status", "[zone_id | name | ALL]", "List zone stats"); + printf("| %10s | %30s | %52s |\n", "lock", "[zone_id | name]", "Locks a zone"); + printf("| %10s | %30s | %52s |\n", "unlock", "[zone_id | name]", "Unlocks a zone"); + printf("| %10s | %30s | %52s |\n", "shutdown", "[zone_id | name | ALL]", "Gracefully shuts down a zone"); + printf("| %10s | %30s | %52s |\n", "kill", "[zone_id | name | ALL]", "Terminates a zone"); + printf("======================================================================================================\n"); + return true; + } + else + return false; + } + else + { + if( !strcasecmp(sep->arg[1], "list") ) + { + const char* name = 0; + name = sep->argplus[2]; + map* zone_names = database.GetZoneList(name); + + if(!zone_names) + { + printf("> No zones found.\n"); + } + else + { + printf("> List zones matching '%s'...\n", sep->arg[2]); + printf("====================================================\n"); + printf("| %3s | %42s |\n", "ID", "Name"); + printf("====================================================\n"); + map::iterator itr; + + for(itr = zone_names->begin(); itr != zone_names->end(); itr++) + printf("| %3u | %42s |\n", itr->first, itr->second.c_str()); + safe_delete(zone_names); + printf("====================================================\n"); + } + return true; + } + + if( !strcasecmp(sep->arg[1], "lock") ) + { + if( sep->IsNumber(2) ) + printf("> Locking zone ID %i...\n", atoul(sep->arg[2])); + else + printf("> Locking zone '%s'...\n", sep->arg[2]); + return true; + } + + if( !strcasecmp(sep->arg[1], "unlock") ) + { + if( strlen(sep->arg[2]) > 0 && sep->IsNumber(2) ) + printf("> Unlocking zone ID %i...\n", atoi(sep->arg[2])); + else + printf("> Unlocking zone '%s'...\n", sep->arg[2]); + return true; + } + + if( !strcasecmp(sep->arg[1], "status") ) + { + if( sep->IsNumber(2) ) + { + zone = zone_list.Get(atoi(sep->arg[2]), false, false, false); + if( zone ) + { + printf("> Zone status for zone ID %i...\n", atoi(sep->arg[2])); + printf("============================================================================================\n"); + printf("| %30s | %10s | %42s |\n", "Zone", "Param", "Value"); + printf("============================================================================================\n"); + printf("| %30s | %10s | %42s |\n", zone->GetZoneName(), "locked", zone->GetZoneLockState() ? "true" : "false"); + } + else + { + printf("> Zone ID %i not running, so not locked.\n", atoi(sep->arg[2])); + } + } + else if( !strcasecmp(sep->arg[2], "ALL") ) + { + printf("> Zone status for ALL active zones...\n"); + } + else + { + printf("> Zone status for zone '%s'...\n", sep->arg[2]); + } + return true; + } + + if( !strcasecmp(sep->arg[1], "shutdown") ) + { + if( sep->IsNumber(2) ) + printf("> Shutdown zone ID %i...\n", atoi(sep->arg[2])); + else if( !strcasecmp(sep->arg[2], "ALL") ) + printf("> Shutdown ALL active zones...\n"); + else + printf("> Shutdown zone '%s'...\n", sep->arg[2]); + return true; + } + + if( !strcasecmp(sep->arg[1], "kill") ) + { + if( sep->IsNumber(2) ) + printf("> Kill zone ID %i...\n", atoi(sep->arg[2])); + else if( !strcasecmp(sep->arg[2], "ALL") ) + printf("> Kill ALL active zones...\n"); + else + printf("> Kill zone '%s'...\n", sep->arg[2]); + return true; + } + } + return false; +} + +bool ConsoleGetMOTDCommand(Seperator *sep) +{ + const char* motd = 0; + Variable* var = variables.FindVariable("motd"); + if( var == NULL || strlen (var->GetValue()) == 0){ + printf("No MOTD."); + } + else{ + motd = var->GetValue(); + printf("%s\n", motd); + } + return true; +} + +bool ConsoleSetMOTDCommand(Seperator *sep) +{ + if( strlen(sep->arg[1]) == 0 ) + return false; + + return true; +} + +bool ConsoleReloadCommand(Seperator *sep) +{ + #ifdef _WIN32 + HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleTextAttribute(console, FOREGROUND_YELLOW_BOLD); + #else + printf("\033[1;33m"); + #endif + printf("Usage: "); + + #ifdef _WIN32 + SetConsoleTextAttribute(console, FOREGROUND_WHITE_BOLD); + #else + printf("\033[1;37m"); + #endif + printf("reload [type]\n"); + + #ifdef _WIN32 + SetConsoleTextAttribute(console, 8); + #else + printf("\033[0m"); + #endif + printf("Valid [type] paramters are:\n"); + printf("===============================================================================\n"); + printf("| %21s | %51s |\n", "all", "Reloads all systems (why not just restart?)"); + printf("| %21s | %51s |\n", "structs", "Reloads structs (XMLs)"); + printf("| %21s | %51s |\n", "items", "Reload Items data"); + printf("| %21s | %51s |\n", "luasystem", "Reload LUA System Scripts"); + printf("| %21s | %51s |\n", "spawnscripts", "Reload SpawnScripts"); + printf("| %21s | %51s |\n", "quests", "Reload Quest Data and Scripts"); + printf("| %21s | %51s |\n", "spawns", "Reload ALL Spawns from DB"); + printf("| %21s | %51s |\n", "groundspawn_items", "Reload Groundspawn Items lists"); + printf("| %21s | %51s |\n", "zonescripts", "Reload Zone Scripts"); + printf("| %21s | %51s |\n", "entity_commands", "Reload Entity Commands"); + printf("| %21s | %51s |\n", "factions", "Reload Factions"); + printf("| %21s | %51s |\n", "mail", "Reload in-game Mail data"); + printf("| %21s | %51s |\n", "guilds", "Reload Guilds"); + printf("| %21s | %51s |\n", "locations", "Reload Locations data"); + printf("===============================================================================\n"); + if( strlen(sep->arg[1]) > 0 ) { + // handle reloads here + if (!strcasecmp(sep->arg[1], "spawns")) + zone_list.ReloadSpawns(); + } + + return true; +} + +bool ConsoleShutdownCommand(Seperator *sep) +{ + if ( IsNumber(sep->arg[1]) ) { + int8 shutdown_delay = atoi(sep->arg[1]); + printf("Shutdown World in %i second(s)...\n", shutdown_delay); + // shutting down gracefully, warn players. + char message[4096]; + snprintf(message, sizeof(message), "BROADCAST: Server is shutting down in %s second(s)", sep->arg[1]); + zone_list.HandleGlobalBroadcast(message); + Sleep(shutdown_delay * 1000); + } + else { + printf("Shutdown World immediately... you probably won't even see this message, huh!\n"); + } + if( !ContinueLoops ) + RunLoops = false; + return true; +} + +bool ConsoleCancelShutdownCommand(Seperator *sep) +{ + printf("Cancel World Shutdown...\n"); + ContinueLoops = true; + return true; +} + +bool ConsoleExitCommand(Seperator *sep) +{ + // I wanted this to be a little more Terminate-y... killkillkill + printf("Terminate World immediately...\n"); + RunLoops = false; + return true; +} + +bool ConsoleRulesCommand(Seperator *sep) +{ + /*if( strlen(sep->arg[1]) == 0 ) + return false;*/ + + printf("Current Active Ruleset"); + if (!strcasecmp(sep->arg[1], "zone")) + { + if (sep->IsNumber(2)) { + printf(" in Zone ID: %s\n", sep->arg[2]); + } + else + return false; + } + else + { + printf(" (global):\n"); + } + printf("===============================================================================\n"); + printf("| %20s | %20s | %29s |\n", "Category", "Type", "Value"); + printf("===============================================================================\n"); + + return true; +} + +bool ConsoleTestCommand(Seperator *sep) +{ + // devs put whatever test code in here + printf("Testing Server Guild Rules values:\n"); + printf("AutoJoin: %i\n", rule_manager.GetGlobalRule(R_World, GuildAutoJoin)->GetInt8()); + printf("Guild ID: %i\n", rule_manager.GetGlobalRule(R_World, GuildAutoJoinID)->GetInt32()); + printf("Rank: %i\n", rule_manager.GetGlobalRule(R_World, GuildAutoJoinDefaultRankID)->GetInt8()); + return true; +} diff --git a/source/WorldServer/Commands/ConsoleCommands.h b/source/WorldServer/Commands/ConsoleCommands.h new file mode 100644 index 0000000..1796c7d --- /dev/null +++ b/source/WorldServer/Commands/ConsoleCommands.h @@ -0,0 +1,62 @@ +/* + 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 . +*/ + +#ifndef _CONSOLECOMMANDS_H +#define _CONSOLECOMMANDS_H + +#include "../../common/seperator.h" + +struct ConsoleCommand +{ + bool(*CommandPointer)(Seperator *); + const char * Name; // 10 chars + const char * ParameterFormat; // 30 chars + const char * Description; // 40 chars + // = 70 chars +}; + + void ProcessConsoleInput(const char * command); + + bool ConsoleBanCommand(Seperator *sep); + bool ConsoleUnbanCommand(Seperator *sep); + bool ConsoleKickCommand(Seperator *sep); + + bool ConsoleAnnounceCommand(Seperator *sep); + bool ConsoleBroadcastCommand(Seperator *sep); + bool ConsoleChannelCommand(Seperator *sep); + bool ConsoleTellCommand(Seperator *sep); + + bool ConsoleGuildCommand(Seperator *sep); + bool ConsolePlayerCommand(Seperator *sep); + bool ConsoleSetAdminPlayer(Seperator *sep); + bool ConsoleWorldCommand(Seperator *sep); + bool ConsoleZoneCommand(Seperator *sep); + bool ConsoleGetMOTDCommand(Seperator *sep); + bool ConsoleSetMOTDCommand(Seperator *sep); + bool ConsoleWhoCommand(Seperator *sep); + bool ConsoleReloadCommand(Seperator *sep); + + bool ConsoleShutdownCommand(Seperator *sep); + bool ConsoleCancelShutdownCommand(Seperator *sep); + bool ConsoleExitCommand(Seperator *sep); + bool ConsoleRulesCommand(Seperator *sep); + bool ConsoleTestCommand(Seperator *sep); + +#endif \ No newline at end of file diff --git a/source/WorldServer/Entity.cpp b/source/WorldServer/Entity.cpp new file mode 100644 index 0000000..90f5944 --- /dev/null +++ b/source/WorldServer/Entity.cpp @@ -0,0 +1,3986 @@ +/* + 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 . +*/ +#include "Entity.h" +#include +#include "Items/Items.h" +#include "zoneserver.h" +#include "World.h" +#include "../common/Log.h" +#include "Spells.h" +#include "SpellProcess.h" +#include "classes.h" +#include "LuaInterface.h" +#include "ClientPacketFunctions.h" +#include "Skills.h" +#include "Rules/Rules.h" +#include "LuaInterface.h" + +extern World world; +extern MasterItemList master_item_list; +extern MasterSpellList master_spell_list; +extern MasterSkillList master_skill_list; +extern RuleManager rule_manager; +extern Classes classes; +extern LuaInterface* lua_interface; + +Entity::Entity(){ + MapInfoStruct(); + max_speed = 6; + base_speed = 0.0f; + last_x = -1; + last_y = -1; + last_z = -1; + last_heading = -1; + regen_hp_rate = 0; + regen_power_rate = 0; + in_combat = false; + casting = false; + //memset(&info_struct, 0, sizeof(InfoStruct)); + memset(&features, 0, sizeof(CharFeatures)); + memset(&equipment, 0, sizeof(EQ2_Equipment)); + pet = 0; + charmedPet = 0; + deityPet = 0; + cosmeticPet = 0; + speed = 0; + speed_multiplier = 1.0f; + m_threatTransfer = 0; + group_member_info = 0; + trade = 0; + deity = 0; + MProcList.SetName("Entity::m_procList"); + MDetriments.SetName("Entity::MDetriments"); + MMaintainedSpells.SetName("Entity::MMaintainedSpells"); + MSpellEffects.SetName("Entity::MSpellEffects"); + m_procList.clear(); + control_effects.clear(); + for (int i = 0; i < CONTROL_MAX_EFFECTS; i++) + control_effects[i] = NULL; + + immunities.clear(); + + info_struct.ResetEffects(this); + + MCommandMutex.SetName("Entity::MCommandMutex"); + hasSeeInvisSpell = false; + hasSeeHideSpell = false; + + owner = 0; + m_petType = 0; + m_petSpellID = 0; + m_petSpellTier = 0; + m_petDismissing = false; + + if (!IsPlayer() && GetInfoStruct()->get_max_concentration_base() == 0) + GetInfoStruct()->set_max_concentration_base(5); +} + +Entity::~Entity(){ + MutexList::iterator itr2 = bonus_list.begin(); + while(itr2.Next()) + safe_delete(itr2.value); + ClearProcs(); + safe_delete(m_threatTransfer); + map*>::iterator itr3; + for (itr3 = control_effects.begin(); itr3 != control_effects.end(); itr3++) + safe_delete(itr3->second); + control_effects.clear(); + map*>::iterator itr4; + for (itr4 = immunities.begin(); itr4 != immunities.end(); itr4++) + safe_delete(itr4->second); + immunities.clear(); + if(!IsPlayer()) + DeleteSpellEffects(true); + safe_delete(m_threatTransfer); +} + +void Entity::DeleteSpellEffects(bool removeClient) +{ + map deletedPtrs; + + for(int i=0;i<45;i++){ + if(i<30){ + if(GetInfoStruct()->maintained_effects[i].spell_id != 0xFFFFFFFF) + { + if(deletedPtrs.find(GetInfoStruct()->maintained_effects[i].spell) == deletedPtrs.end()) + { + deletedPtrs[GetInfoStruct()->maintained_effects[i].spell] = true; + lua_interface->RemoveSpell(GetInfoStruct()->maintained_effects[i].spell, false, removeClient, "", removeClient); + if (IsPlayer()) + GetInfoStruct()->maintained_effects[i].icon = 0xFFFF; + } + + GetInfoStruct()->maintained_effects[i].spell_id = 0xFFFFFFFF; + GetInfoStruct()->maintained_effects[i].spell = nullptr; + } + } + if(GetInfoStruct()->spell_effects[i].spell_id != 0xFFFFFFFF) + { + if(deletedPtrs.find(GetInfoStruct()->spell_effects[i].spell) == deletedPtrs.end()) { + if(GetInfoStruct()->spell_effects[i].spell && GetInfoStruct()->spell_effects[i].spell->spell && + GetInfoStruct()->spell_effects[i].spell->spell->GetSpellData()->spell_book_type == SPELL_BOOK_TYPE_NOT_SHOWN) { + deletedPtrs[GetInfoStruct()->spell_effects[i].spell] = true; + lua_interface->RemoveSpell(GetInfoStruct()->spell_effects[i].spell, false, removeClient, "", removeClient); + } + } + GetInfoStruct()->spell_effects[i].spell_id = 0xFFFFFFFF; + GetInfoStruct()->spell_effects[i].spell = nullptr; + } + } +} + +void Entity::RemoveSpells(bool unfriendlyOnly) +{ + + for(int i=0;i<45;i++){ + if(i<30){ + if(GetInfoStruct()->maintained_effects[i].spell_id != 0xFFFFFFFF) + { + if(!unfriendlyOnly || (unfriendlyOnly && GetInfoStruct()->maintained_effects[i].spell && + !GetInfoStruct()->maintained_effects[i].spell->spell->GetSpellData()->friendly_spell)) + GetZone()->GetSpellProcess()->AddSpellCancel(GetInfoStruct()->maintained_effects[i].spell); + } + } + if(GetInfoStruct()->spell_effects[i].spell_id != 0xFFFFFFFF) + { + if(!unfriendlyOnly || (unfriendlyOnly && GetInfoStruct()->spell_effects[i].spell && + !GetInfoStruct()->spell_effects[i].spell->spell->GetSpellData()->friendly_spell)) + RemoveSpellEffect(GetInfoStruct()->spell_effects[i].spell); + } + } +} + +void Entity::MapInfoStruct() +{ +/** GETS **/ + get_string_funcs["name"] = l::bind(&InfoStruct::get_name, &info_struct); + get_int8_funcs["class1"] = l::bind(&InfoStruct::get_class1, &info_struct); + get_int8_funcs["class2"] = l::bind(&InfoStruct::get_class2, &info_struct); + get_int8_funcs["class3"] = l::bind(&InfoStruct::get_class3, &info_struct); + get_int8_funcs["race"] = l::bind(&InfoStruct::get_race, &info_struct); + get_int8_funcs["gender"] = l::bind(&InfoStruct::get_gender, &info_struct); + get_int16_funcs["level"] = l::bind(&InfoStruct::get_level, &info_struct); + get_int16_funcs["max_level"] = l::bind(&InfoStruct::get_max_level, &info_struct); + get_int16_funcs["effective_level"] = l::bind(&InfoStruct::get_effective_level, &info_struct); + get_int16_funcs["tradeskill_level"] = l::bind(&InfoStruct::get_tradeskill_level, &info_struct); + get_int16_funcs["tradeskill_max_level"] = l::bind(&InfoStruct::get_tradeskill_max_level, &info_struct); + get_int8_funcs["cur_concentration"] = l::bind(&InfoStruct::get_cur_concentration, &info_struct); + get_int8_funcs["max_concentration"] = l::bind(&InfoStruct::get_max_concentration, &info_struct); + get_int8_funcs["max_concentration_base"] = l::bind(&InfoStruct::get_max_concentration_base, &info_struct); + get_int16_funcs["cur_attack"] = l::bind(&InfoStruct::get_cur_attack, &info_struct); + get_int16_funcs["attack_base"] = l::bind(&InfoStruct::get_attack_base, &info_struct); + get_int16_funcs["cur_mitigation"] = l::bind(&InfoStruct::get_cur_mitigation, &info_struct); + get_int16_funcs["max_mitigation"] = l::bind(&InfoStruct::get_max_mitigation, &info_struct); + get_int16_funcs["mitigation_base"] = l::bind(&InfoStruct::get_mitigation_base, &info_struct); + get_int16_funcs["avoidance_display"] = l::bind(&InfoStruct::get_avoidance_display, &info_struct); + get_float_funcs["cur_avoidance"] = l::bind(&InfoStruct::get_cur_avoidance, &info_struct); + get_int16_funcs["base_avoidance_pct"] = l::bind(&InfoStruct::get_base_avoidance_pct, &info_struct); + get_int16_funcs["avoidance_base"] = l::bind(&InfoStruct::get_avoidance_base, &info_struct); + get_int16_funcs["max_avoidance"] = l::bind(&InfoStruct::get_max_avoidance, &info_struct); + get_float_funcs["parry"] = l::bind(&InfoStruct::get_parry, &info_struct); + get_float_funcs["parry_base"] = l::bind(&InfoStruct::get_parry_base, &info_struct); + get_float_funcs["deflection"] = l::bind(&InfoStruct::get_deflection, &info_struct); + get_int16_funcs["deflection_base"] = l::bind(&InfoStruct::get_deflection_base, &info_struct); + get_float_funcs["block"] = l::bind(&InfoStruct::get_block, &info_struct); + get_int16_funcs["block_base"] = l::bind(&InfoStruct::get_block_base, &info_struct); + get_float_funcs["str"] = l::bind(&InfoStruct::get_str, &info_struct); + get_float_funcs["sta"] = l::bind(&InfoStruct::get_sta, &info_struct); + get_float_funcs["agi"] = l::bind(&InfoStruct::get_agi, &info_struct); + get_float_funcs["wis"] = l::bind(&InfoStruct::get_wis, &info_struct); + get_float_funcs["intel"] = l::bind(&InfoStruct::get_intel, &info_struct); + get_float_funcs["str_base"] = l::bind(&InfoStruct::get_str_base, &info_struct); + get_float_funcs["sta_base"] = l::bind(&InfoStruct::get_sta_base, &info_struct); + get_float_funcs["agi_base"] = l::bind(&InfoStruct::get_agi_base, &info_struct); + get_float_funcs["wis_base"] = l::bind(&InfoStruct::get_wis_base, &info_struct); + get_float_funcs["intel_base"] = l::bind(&InfoStruct::get_intel_base, &info_struct); + get_int16_funcs["heat"] = l::bind(&InfoStruct::get_heat, &info_struct); + get_int16_funcs["cold"] = l::bind(&InfoStruct::get_cold, &info_struct); + get_int16_funcs["magic"] = l::bind(&InfoStruct::get_magic, &info_struct); + get_int16_funcs["mental"] = l::bind(&InfoStruct::get_mental, &info_struct); + get_int16_funcs["divine"] = l::bind(&InfoStruct::get_divine, &info_struct); + get_int16_funcs["disease"] = l::bind(&InfoStruct::get_disease, &info_struct); + get_int16_funcs["poison"] = l::bind(&InfoStruct::get_poison, &info_struct); + get_int16_funcs["disease_base"] = l::bind(&InfoStruct::get_disease_base, &info_struct); + get_int16_funcs["cold_base"] = l::bind(&InfoStruct::get_cold_base, &info_struct); + get_int16_funcs["divine_base"] = l::bind(&InfoStruct::get_divine_base, &info_struct); + get_int16_funcs["magic_base"] = l::bind(&InfoStruct::get_magic_base, &info_struct); + get_int16_funcs["mental_base"] = l::bind(&InfoStruct::get_mental_base, &info_struct); + get_int16_funcs["heat_base"] = l::bind(&InfoStruct::get_heat_base, &info_struct); + get_int16_funcs["poison_base"] = l::bind(&InfoStruct::get_poison_base, &info_struct); + get_int16_funcs["elemental_base"] = l::bind(&InfoStruct::get_elemental_base, &info_struct); + get_int16_funcs["noxious_base"] = l::bind(&InfoStruct::get_noxious_base, &info_struct); + get_int16_funcs["arcane_base"] = l::bind(&InfoStruct::get_arcane_base, &info_struct); + get_int32_funcs["coin_copper"] = l::bind(&InfoStruct::get_coin_copper, &info_struct); + get_int32_funcs["coin_silver"] = l::bind(&InfoStruct::get_coin_silver, &info_struct); + get_int32_funcs["coin_gold"] = l::bind(&InfoStruct::get_coin_gold, &info_struct); + get_int32_funcs["coin_plat"] = l::bind(&InfoStruct::get_coin_plat, &info_struct); + get_int32_funcs["bank_coin_copper"] = l::bind(&InfoStruct::get_bank_coin_copper, &info_struct); + get_int32_funcs["bank_coin_silver"] = l::bind(&InfoStruct::get_bank_coin_silver, &info_struct); + get_int32_funcs["bank_coin_gold"] = l::bind(&InfoStruct::get_bank_coin_gold, &info_struct); + get_int32_funcs["bank_coin_plat"] = l::bind(&InfoStruct::get_bank_coin_plat, &info_struct); + get_int32_funcs["status_points"] = l::bind(&InfoStruct::get_status_points, &info_struct); + get_string_funcs["deity"] = l::bind(&InfoStruct::get_deity, &info_struct); + get_int32_funcs["weight"] = l::bind(&InfoStruct::get_weight, &info_struct); + get_int32_funcs["max_weight"] = l::bind(&InfoStruct::get_max_weight, &info_struct); + get_int8_funcs["tradeskill_class1"] = l::bind(&InfoStruct::get_tradeskill_class1, &info_struct); + get_int8_funcs["tradeskill_class2"] = l::bind(&InfoStruct::get_tradeskill_class2, &info_struct); + get_int8_funcs["tradeskill_class3"] = l::bind(&InfoStruct::get_tradeskill_class3, &info_struct); + get_int32_funcs["account_age_base"] = l::bind(&InfoStruct::get_account_age_base, &info_struct); + // int8 account_age_bonus_[19]; + get_int16_funcs["absorb"] = l::bind(&InfoStruct::get_absorb, &info_struct); + get_int32_funcs["xp"] = l::bind(&InfoStruct::get_xp, &info_struct); + get_int32_funcs["xp_needed"] = l::bind(&InfoStruct::get_xp_needed, &info_struct); + get_float_funcs["xp_debt"] = l::bind(&InfoStruct::get_xp_debt, &info_struct); + get_int16_funcs["xp_yellow"] = l::bind(&InfoStruct::get_xp_yellow, &info_struct); + get_int16_funcs["xp_yellow_vitality_bar"] = l::bind(&InfoStruct::get_xp_yellow_vitality_bar, &info_struct); + get_int16_funcs["xp_blue_vitality_bar"] = l::bind(&InfoStruct::get_xp_blue_vitality_bar, &info_struct); + get_int16_funcs["xp_blue"] = l::bind(&InfoStruct::get_xp_blue, &info_struct); + get_int32_funcs["ts_xp"] = l::bind(&InfoStruct::get_ts_xp, &info_struct); + get_int32_funcs["ts_xp_needed"] = l::bind(&InfoStruct::get_ts_xp_needed, &info_struct); + get_int16_funcs["tradeskill_exp_yellow"] = l::bind(&InfoStruct::get_tradeskill_exp_yellow, &info_struct); + get_int16_funcs["tradeskill_exp_blue"] = l::bind(&InfoStruct::get_tradeskill_exp_blue, &info_struct); + get_int32_funcs["flags"] = l::bind(&InfoStruct::get_flags, &info_struct); + get_int32_funcs["flags2"] = l::bind(&InfoStruct::get_flags2, &info_struct); + get_float_funcs["xp_vitality"] = l::bind(&InfoStruct::get_xp_vitality, &info_struct); + get_float_funcs["tradeskill_xp_vitality"] = l::bind(&InfoStruct::get_tradeskill_xp_vitality, &info_struct); + get_int16_funcs["mitigation_skill1"] = l::bind(&InfoStruct::get_mitigation_skill1, &info_struct); + get_int16_funcs["mitigation_skill2"] = l::bind(&InfoStruct::get_mitigation_skill2, &info_struct); + get_int16_funcs["mitigation_skill3"] = l::bind(&InfoStruct::get_mitigation_skill3, &info_struct); + get_int16_funcs["mitigation_pve"] = l::bind(&InfoStruct::get_mitigation_pve, &info_struct); + get_int16_funcs["mitigation_pvp"] = l::bind(&InfoStruct::get_mitigation_pvp, &info_struct); + get_float_funcs["ability_modifier"] = l::bind(&InfoStruct::get_ability_modifier, &info_struct); + get_float_funcs["critical_mitigation"] = l::bind(&InfoStruct::get_critical_mitigation, &info_struct); + get_float_funcs["block_chance"] = l::bind(&InfoStruct::get_block_chance, &info_struct); + get_float_funcs["uncontested_parry"] = l::bind(&InfoStruct::get_uncontested_parry, &info_struct); + get_float_funcs["uncontested_block"] = l::bind(&InfoStruct::get_uncontested_block, &info_struct); + get_float_funcs["uncontested_dodge"] = l::bind(&InfoStruct::get_uncontested_dodge, &info_struct); + get_float_funcs["uncontested_riposte"] = l::bind(&InfoStruct::get_uncontested_riposte, &info_struct); + get_float_funcs["crit_chance"] = l::bind(&InfoStruct::get_crit_chance, &info_struct); + get_float_funcs["crit_bonus"] = l::bind(&InfoStruct::get_crit_bonus, &info_struct); + get_float_funcs["potency"] = l::bind(&InfoStruct::get_potency, &info_struct); + get_float_funcs["hate_mod"] = l::bind(&InfoStruct::get_hate_mod, &info_struct); + get_float_funcs["reuse_speed"] = l::bind(&InfoStruct::get_reuse_speed, &info_struct); + get_float_funcs["casting_speed"] = l::bind(&InfoStruct::get_casting_speed, &info_struct); + get_float_funcs["recovery_speed"] = l::bind(&InfoStruct::get_recovery_speed, &info_struct); + get_float_funcs["spell_reuse_speed"] = l::bind(&InfoStruct::get_spell_reuse_speed, &info_struct); + get_float_funcs["spell_multi_attack"] = l::bind(&InfoStruct::get_spell_multi_attack, &info_struct); + get_float_funcs["dps"] = l::bind(&InfoStruct::get_dps, &info_struct); + get_float_funcs["dps_multiplier"] = l::bind(&InfoStruct::get_dps_multiplier, &info_struct); + get_float_funcs["attackspeed"] = l::bind(&InfoStruct::get_attackspeed, &info_struct); + get_float_funcs["haste"] = l::bind(&InfoStruct::get_haste, &info_struct); + get_float_funcs["multi_attack"] = l::bind(&InfoStruct::get_multi_attack, &info_struct); + get_float_funcs["flurry"] = l::bind(&InfoStruct::get_flurry, &info_struct); + get_float_funcs["melee_ae"] = l::bind(&InfoStruct::get_melee_ae, &info_struct); + get_float_funcs["strikethrough"] = l::bind(&InfoStruct::get_strikethrough, &info_struct); + get_float_funcs["accuracy"] = l::bind(&InfoStruct::get_accuracy, &info_struct); + get_float_funcs["offensivespeed"] = l::bind(&InfoStruct::get_offensivespeed, &info_struct); + get_float_funcs["rain"] = l::bind(&InfoStruct::get_rain, &info_struct); + get_float_funcs["wind"] = l::bind(&InfoStruct::get_wind, &info_struct); + get_sint8_funcs["alignment"] = l::bind(&InfoStruct::get_alignment, &info_struct); + get_int32_funcs["pet_id"] = l::bind(&InfoStruct::get_pet_id, &info_struct); + get_string_funcs["pet_name"] = l::bind(&InfoStruct::get_pet_name, &info_struct); + get_float_funcs["pet_health_pct"] = l::bind(&InfoStruct::get_pet_health_pct, &info_struct); + get_float_funcs["pet_power_pct"] = l::bind(&InfoStruct::get_pet_power_pct, &info_struct); + get_int8_funcs["pet_movement"] = l::bind(&InfoStruct::get_pet_movement, &info_struct); + get_int8_funcs["pet_behavior"] = l::bind(&InfoStruct::get_pet_behavior, &info_struct); + get_int32_funcs["vision"] = l::bind(&InfoStruct::get_vision, &info_struct); + get_int8_funcs["breathe_underwater"] = l::bind(&InfoStruct::get_breathe_underwater, &info_struct); + get_string_funcs["biography"] = l::bind(&InfoStruct::get_biography, &info_struct); + get_float_funcs["drunk"] = l::bind(&InfoStruct::get_drunk, &info_struct); + + get_sint16_funcs["power_regen"] = l::bind(&InfoStruct::get_power_regen, &info_struct); + get_sint16_funcs["hp_regen"] = l::bind(&InfoStruct::get_hp_regen, &info_struct); + + get_int8_funcs["power_regen_override"] = l::bind(&InfoStruct::get_power_regen_override, &info_struct); + get_int8_funcs["hp_regen_override"] = l::bind(&InfoStruct::get_hp_regen_override, &info_struct); + + get_int8_funcs["water_type"] = l::bind(&InfoStruct::get_water_type, &info_struct); + get_int8_funcs["flying_type"] = l::bind(&InfoStruct::get_flying_type, &info_struct); + + get_int8_funcs["no_interrupt"] = l::bind(&InfoStruct::get_no_interrupt, &info_struct); + + get_int8_funcs["interaction_flag"] = l::bind(&InfoStruct::get_interaction_flag, &info_struct); + get_int8_funcs["tag1"] = l::bind(&InfoStruct::get_tag1, &info_struct); + get_int16_funcs["mood"] = l::bind(&InfoStruct::get_mood, &info_struct); + + get_int32_funcs["range_last_attack_time"] = l::bind(&InfoStruct::get_range_last_attack_time, &info_struct); + get_int32_funcs["primary_last_attack_time"] = l::bind(&InfoStruct::get_primary_last_attack_time, &info_struct); + get_int32_funcs["secondary_last_attack_time"] = l::bind(&InfoStruct::get_secondary_last_attack_time, &info_struct); + + get_int16_funcs["primary_attack_delay"] = l::bind(&InfoStruct::get_primary_attack_delay, &info_struct); + get_int16_funcs["secondary_attack_delay"] = l::bind(&InfoStruct::get_secondary_attack_delay, &info_struct); + get_int16_funcs["ranged_attack_delay"] = l::bind(&InfoStruct::get_ranged_attack_delay, &info_struct); + + get_int8_funcs["primary_weapon_type"] = l::bind(&InfoStruct::get_primary_weapon_type, &info_struct); + get_int8_funcs["secondary_weapon_type"] = l::bind(&InfoStruct::get_secondary_weapon_type, &info_struct); + get_int8_funcs["ranged_weapon_type"] = l::bind(&InfoStruct::get_ranged_weapon_type, &info_struct); + + get_int32_funcs["primary_weapon_damage_low"] = l::bind(&InfoStruct::get_primary_weapon_damage_low, &info_struct); + get_int32_funcs["primary_weapon_damage_high"] = l::bind(&InfoStruct::get_primary_weapon_damage_high, &info_struct); + get_int32_funcs["secondary_weapon_damage_low"] = l::bind(&InfoStruct::get_secondary_weapon_damage_low, &info_struct); + get_int32_funcs["secondary_weapon_damage_high"] = l::bind(&InfoStruct::get_secondary_weapon_damage_high, &info_struct); + get_int32_funcs["ranged_weapon_damage_low"] = l::bind(&InfoStruct::get_ranged_weapon_damage_low, &info_struct); + get_int32_funcs["ranged_weapon_damage_high"] = l::bind(&InfoStruct::get_ranged_weapon_damage_high, &info_struct); + + get_int8_funcs["wield_type"] = l::bind(&InfoStruct::get_wield_type, &info_struct); + get_int8_funcs["attack_type"] = l::bind(&InfoStruct::get_attack_type, &info_struct); + + get_int16_funcs["primary_weapon_delay"] = l::bind(&InfoStruct::get_primary_weapon_delay, &info_struct); + get_int16_funcs["secondary_weapon_delay"] = l::bind(&InfoStruct::get_secondary_weapon_delay, &info_struct); + get_int16_funcs["ranged_weapon_delay"] = l::bind(&InfoStruct::get_ranged_weapon_delay, &info_struct); + + get_int8_funcs["override_primary_weapon"] = l::bind(&InfoStruct::get_override_primary_weapon, &info_struct); + get_int8_funcs["override_secondary_weapon"] = l::bind(&InfoStruct::get_override_secondary_weapon, &info_struct); + get_int8_funcs["override_ranged_weapon"] = l::bind(&InfoStruct::get_override_ranged_weapon, &info_struct); + + get_int8_funcs["friendly_target_npc"] = l::bind(&InfoStruct::get_friendly_target_npc, &info_struct); + get_int32_funcs["last_claim_time"] = l::bind(&InfoStruct::get_last_claim_time, &info_struct); + + get_int8_funcs["engaged_encounter"] = l::bind(&InfoStruct::get_engaged_encounter, &info_struct); + + get_int8_funcs["first_world_login"] = l::bind(&InfoStruct::get_first_world_login, &info_struct); + + get_int8_funcs["reload_player_spells"] = l::bind(&InfoStruct::get_reload_player_spells, &info_struct); + + get_int8_funcs["group_loot_method"] = l::bind(&InfoStruct::get_group_loot_method, &info_struct); + get_int8_funcs["group_loot_items_rarity"] = l::bind(&InfoStruct::get_group_loot_items_rarity, &info_struct); + get_int8_funcs["group_auto_split"] = l::bind(&InfoStruct::get_group_auto_split, &info_struct); + get_int8_funcs["group_default_yell"] = l::bind(&InfoStruct::get_group_default_yell, &info_struct); + get_int8_funcs["group_autolock"] = l::bind(&InfoStruct::get_group_autolock, &info_struct); + get_int8_funcs["group_lock_method"] = l::bind(&InfoStruct::get_group_lock_method, &info_struct); + get_int8_funcs["group_solo_autolock"] = l::bind(&InfoStruct::get_group_solo_autolock, &info_struct); + get_int8_funcs["group_auto_loot_method"] = l::bind(&InfoStruct::get_group_auto_loot_method, &info_struct); + get_int8_funcs["assist_auto_attack"] = l::bind(&InfoStruct::get_assist_auto_attack, &info_struct); + + get_string_funcs["action_state"] = l::bind(&InfoStruct::get_action_state, &info_struct); + get_string_funcs["combat_action_state"] = l::bind(&InfoStruct::get_combat_action_state, &info_struct); + + get_float_funcs["max_spell_reduction"] = l::bind(&InfoStruct::get_max_spell_reduction, &info_struct); + get_int8_funcs["max_spell_reduction_override"] = l::bind(&InfoStruct::get_max_spell_reduction_override, &info_struct); + +/** SETS **/ + set_string_funcs["name"] = l::bind(&InfoStruct::set_name, &info_struct, l::_1); + set_int8_funcs["class1"] = l::bind(&InfoStruct::set_class1, &info_struct, l::_1); + set_int8_funcs["class2"] = l::bind(&InfoStruct::set_class2, &info_struct, l::_1); + set_int8_funcs["class3"] = l::bind(&InfoStruct::set_class3, &info_struct, l::_1); + set_int8_funcs["race"] = l::bind(&InfoStruct::set_race, &info_struct, l::_1); + set_int8_funcs["gender"] = l::bind(&InfoStruct::set_gender, &info_struct, l::_1); + set_int16_funcs["level"] = l::bind(&InfoStruct::set_level, &info_struct, l::_1); + set_int16_funcs["max_level"] = l::bind(&InfoStruct::set_max_level, &info_struct, l::_1); + set_int16_funcs["effective_level"] = l::bind(&InfoStruct::set_effective_level, &info_struct, l::_1); + set_int16_funcs["tradeskill_level"] = l::bind(&InfoStruct::set_tradeskill_level, &info_struct, l::_1); + set_int16_funcs["tradeskill_max_level"] = l::bind(&InfoStruct::set_tradeskill_max_level, &info_struct, l::_1); + set_int8_funcs["cur_concentration"] = l::bind(&InfoStruct::set_cur_concentration, &info_struct, l::_1); + set_int8_funcs["max_concentration"] = l::bind(&InfoStruct::set_max_concentration, &info_struct, l::_1); + set_int8_funcs["max_concentration_base"] = l::bind(&InfoStruct::set_max_concentration_base, &info_struct, l::_1); + set_int16_funcs["cur_attack"] = l::bind(&InfoStruct::set_cur_attack, &info_struct, l::_1); + set_int16_funcs["attack_base"] = l::bind(&InfoStruct::set_attack_base, &info_struct, l::_1); + set_int16_funcs["cur_mitigation"] = l::bind(&InfoStruct::set_cur_mitigation, &info_struct, l::_1); + set_int16_funcs["max_mitigation"] = l::bind(&InfoStruct::set_max_mitigation, &info_struct, l::_1); + set_int16_funcs["mitigation_base"] = l::bind(&InfoStruct::set_mitigation_base, &info_struct, l::_1); + set_int16_funcs["avoidance_display"] = l::bind(&InfoStruct::set_avoidance_display, &info_struct, l::_1); + set_float_funcs["cur_avoidance"] = l::bind(&InfoStruct::set_cur_avoidance, &info_struct, l::_1); + set_int16_funcs["base_avoidance_pct"] = l::bind(&InfoStruct::set_base_avoidance_pct, &info_struct, l::_1); + set_int16_funcs["avoidance_base"] = l::bind(&InfoStruct::set_avoidance_base, &info_struct, l::_1); + set_int16_funcs["max_avoidance"] = l::bind(&InfoStruct::set_max_avoidance, &info_struct, l::_1); + set_float_funcs["parry"] = l::bind(&InfoStruct::set_parry, &info_struct, l::_1); + set_float_funcs["parry_base"] = l::bind(&InfoStruct::set_parry_base, &info_struct, l::_1); + set_float_funcs["deflection"] = l::bind(&InfoStruct::set_deflection, &info_struct, l::_1); + set_int16_funcs["deflection_base"] = l::bind(&InfoStruct::set_deflection_base, &info_struct, l::_1); + set_float_funcs["block"] = l::bind(&InfoStruct::set_block, &info_struct, l::_1); + set_int16_funcs["block_base"] = l::bind(&InfoStruct::set_block_base, &info_struct, l::_1); + set_float_funcs["str"] = l::bind(&InfoStruct::set_str, &info_struct, l::_1); + set_float_funcs["sta"] = l::bind(&InfoStruct::set_sta, &info_struct, l::_1); + set_float_funcs["agi"] = l::bind(&InfoStruct::set_agi, &info_struct, l::_1); + set_float_funcs["wis"] = l::bind(&InfoStruct::set_wis, &info_struct, l::_1); + set_float_funcs["intel"] = l::bind(&InfoStruct::set_intel, &info_struct, l::_1); + set_float_funcs["str_base"] = l::bind(&InfoStruct::set_str_base, &info_struct, l::_1); + set_float_funcs["sta_base"] = l::bind(&InfoStruct::set_sta_base, &info_struct, l::_1); + set_float_funcs["agi_base"] = l::bind(&InfoStruct::set_agi_base, &info_struct, l::_1); + set_float_funcs["wis_base"] = l::bind(&InfoStruct::set_wis_base, &info_struct, l::_1); + set_float_funcs["intel_base"] = l::bind(&InfoStruct::set_intel_base, &info_struct, l::_1); + set_int16_funcs["heat"] = l::bind(&InfoStruct::set_heat, &info_struct, l::_1); + set_int16_funcs["cold"] = l::bind(&InfoStruct::set_cold, &info_struct, l::_1); + set_int16_funcs["magic"] = l::bind(&InfoStruct::set_magic, &info_struct, l::_1); + set_int16_funcs["mental"] = l::bind(&InfoStruct::set_mental, &info_struct, l::_1); + set_int16_funcs["divine"] = l::bind(&InfoStruct::set_divine, &info_struct, l::_1); + set_int16_funcs["disease"] = l::bind(&InfoStruct::set_disease, &info_struct, l::_1); + set_int16_funcs["poison"] = l::bind(&InfoStruct::set_poison, &info_struct, l::_1); + set_int16_funcs["disease_base"] = l::bind(&InfoStruct::set_disease_base, &info_struct, l::_1); + set_int16_funcs["cold_base"] = l::bind(&InfoStruct::set_cold_base, &info_struct, l::_1); + set_int16_funcs["divine_base"] = l::bind(&InfoStruct::set_divine_base, &info_struct, l::_1); + set_int16_funcs["magic_base"] = l::bind(&InfoStruct::set_magic_base, &info_struct, l::_1); + set_int16_funcs["mental_base"] = l::bind(&InfoStruct::set_mental_base, &info_struct, l::_1); + set_int16_funcs["heat_base"] = l::bind(&InfoStruct::set_heat_base, &info_struct, l::_1); + set_int16_funcs["poison_base"] = l::bind(&InfoStruct::set_poison_base, &info_struct, l::_1); + set_int16_funcs["elemental_base"] = l::bind(&InfoStruct::set_elemental_base, &info_struct, l::_1); + set_int16_funcs["noxious_base"] = l::bind(&InfoStruct::set_noxious_base, &info_struct, l::_1); + set_int16_funcs["arcane_base"] = l::bind(&InfoStruct::set_arcane_base, &info_struct, l::_1); + set_int32_funcs["coin_copper"] = l::bind(&InfoStruct::set_coin_copper, &info_struct, l::_1); + set_int32_funcs["coin_silver"] = l::bind(&InfoStruct::set_coin_silver, &info_struct, l::_1); + set_int32_funcs["coin_gold"] = l::bind(&InfoStruct::set_coin_gold, &info_struct, l::_1); + set_int32_funcs["coin_plat"] = l::bind(&InfoStruct::set_coin_plat, &info_struct, l::_1); + set_int32_funcs["bank_coin_copper"] = l::bind(&InfoStruct::set_bank_coin_copper, &info_struct, l::_1); + set_int32_funcs["bank_coin_silver"] = l::bind(&InfoStruct::set_bank_coin_silver, &info_struct, l::_1); + set_int32_funcs["bank_coin_gold"] = l::bind(&InfoStruct::set_bank_coin_gold, &info_struct, l::_1); + set_int32_funcs["bank_coin_plat"] = l::bind(&InfoStruct::set_bank_coin_plat, &info_struct, l::_1); + set_int32_funcs["status_points"] = l::bind(&InfoStruct::set_status_points, &info_struct, l::_1); + set_string_funcs["deity"] = l::bind(&InfoStruct::set_deity, &info_struct, l::_1); + set_int32_funcs["weight"] = l::bind(&InfoStruct::set_weight, &info_struct, l::_1); + set_int32_funcs["max_weight"] = l::bind(&InfoStruct::set_max_weight, &info_struct, l::_1); + set_int8_funcs["tradeskill_class1"] = l::bind(&InfoStruct::set_tradeskill_class1, &info_struct, l::_1); + set_int8_funcs["tradeskill_class2"] = l::bind(&InfoStruct::set_tradeskill_class2, &info_struct, l::_1); + set_int8_funcs["tradeskill_class3"] = l::bind(&InfoStruct::set_tradeskill_class3, &info_struct, l::_1); + set_int32_funcs["account_age_base"] = l::bind(&InfoStruct::set_account_age_base, &info_struct, l::_1); + // int8 account_age_bonus_[19]; + set_int16_funcs["absorb"] = l::bind(&InfoStruct::set_absorb, &info_struct, l::_1); + set_int32_funcs["xp"] = l::bind(&InfoStruct::set_xp, &info_struct, l::_1); + set_int32_funcs["xp_needed"] = l::bind(&InfoStruct::set_xp_needed, &info_struct, l::_1); + set_float_funcs["xp_debt"] = l::bind(&InfoStruct::set_xp_debt, &info_struct, l::_1); + set_int16_funcs["xp_yellow"] = l::bind(&InfoStruct::set_xp_yellow, &info_struct, l::_1); + set_int16_funcs["xp_yellow_vitality_bar"] = l::bind(&InfoStruct::set_xp_yellow_vitality_bar, &info_struct, l::_1); + set_int16_funcs["xp_blue_vitality_bar"] = l::bind(&InfoStruct::set_xp_blue_vitality_bar, &info_struct, l::_1); + set_int16_funcs["xp_blue"] = l::bind(&InfoStruct::set_xp_blue, &info_struct, l::_1); + set_int32_funcs["ts_xp"] = l::bind(&InfoStruct::set_ts_xp, &info_struct, l::_1); + set_int32_funcs["ts_xp_needed"] = l::bind(&InfoStruct::set_ts_xp_needed, &info_struct, l::_1); + set_int16_funcs["tradeskill_exp_yellow"] = l::bind(&InfoStruct::set_tradeskill_exp_yellow, &info_struct, l::_1); + set_int16_funcs["tradeskill_exp_blue"] = l::bind(&InfoStruct::set_tradeskill_exp_blue, &info_struct, l::_1); + set_int32_funcs["flags"] = l::bind(&InfoStruct::set_flags, &info_struct, l::_1); + set_int32_funcs["flags2"] = l::bind(&InfoStruct::set_flags2, &info_struct, l::_1); + set_float_funcs["xp_vitality"] = l::bind(&InfoStruct::set_xp_vitality, &info_struct, l::_1); + set_float_funcs["tradeskill_xp_vitality"] = l::bind(&InfoStruct::set_tradeskill_xp_vitality, &info_struct, l::_1); + set_int16_funcs["mitigation_skill1"] = l::bind(&InfoStruct::set_mitigation_skill1, &info_struct, l::_1); + set_int16_funcs["mitigation_skill2"] = l::bind(&InfoStruct::set_mitigation_skill2, &info_struct, l::_1); + set_int16_funcs["mitigation_skill3"] = l::bind(&InfoStruct::set_mitigation_skill3, &info_struct, l::_1); + set_int16_funcs["mitigation_pve"] = l::bind(&InfoStruct::set_mitigation_pve, &info_struct, l::_1); + set_int16_funcs["mitigation_pvp"] = l::bind(&InfoStruct::set_mitigation_pvp, &info_struct, l::_1); + set_float_funcs["ability_modifier"] = l::bind(&InfoStruct::set_ability_modifier, &info_struct, l::_1); + set_float_funcs["critical_mitigation"] = l::bind(&InfoStruct::set_critical_mitigation, &info_struct, l::_1); + set_float_funcs["block_chance"] = l::bind(&InfoStruct::set_block_chance, &info_struct, l::_1); + set_float_funcs["uncontested_parry"] = l::bind(&InfoStruct::set_uncontested_parry, &info_struct, l::_1); + set_float_funcs["uncontested_block"] = l::bind(&InfoStruct::set_uncontested_block, &info_struct, l::_1); + set_float_funcs["uncontested_dodge"] = l::bind(&InfoStruct::set_uncontested_dodge, &info_struct, l::_1); + set_float_funcs["uncontested_riposte"] = l::bind(&InfoStruct::set_uncontested_riposte, &info_struct, l::_1); + set_float_funcs["crit_chance"] = l::bind(&InfoStruct::set_crit_chance, &info_struct, l::_1); + set_float_funcs["crit_bonus"] = l::bind(&InfoStruct::set_crit_bonus, &info_struct, l::_1); + set_float_funcs["potency"] = l::bind(&InfoStruct::set_potency, &info_struct, l::_1); + set_float_funcs["hate_mod"] = l::bind(&InfoStruct::set_hate_mod, &info_struct, l::_1); + set_float_funcs["reuse_speed"] = l::bind(&InfoStruct::set_reuse_speed, &info_struct, l::_1); + set_float_funcs["casting_speed"] = l::bind(&InfoStruct::set_casting_speed, &info_struct, l::_1); + set_float_funcs["recovery_speed"] = l::bind(&InfoStruct::set_recovery_speed, &info_struct, l::_1); + set_float_funcs["spell_reuse_speed"] = l::bind(&InfoStruct::set_spell_reuse_speed, &info_struct, l::_1); + set_float_funcs["spell_multi_attack"] = l::bind(&InfoStruct::set_spell_multi_attack, &info_struct, l::_1); + set_float_funcs["dps"] = l::bind(&InfoStruct::set_dps, &info_struct, l::_1); + set_float_funcs["dps_multiplier"] = l::bind(&InfoStruct::set_dps_multiplier, &info_struct, l::_1); + set_float_funcs["attackspeed"] = l::bind(&InfoStruct::set_attackspeed, &info_struct, l::_1); + set_float_funcs["haste"] = l::bind(&InfoStruct::set_haste, &info_struct, l::_1); + set_float_funcs["multi_attack"] = l::bind(&InfoStruct::set_multi_attack, &info_struct, l::_1); + set_float_funcs["flurry"] = l::bind(&InfoStruct::set_flurry, &info_struct, l::_1); + set_float_funcs["melee_ae"] = l::bind(&InfoStruct::set_melee_ae, &info_struct, l::_1); + set_float_funcs["strikethrough"] = l::bind(&InfoStruct::set_strikethrough, &info_struct, l::_1); + set_float_funcs["accuracy"] = l::bind(&InfoStruct::set_accuracy, &info_struct, l::_1); + set_float_funcs["offensivespeed"] = l::bind(&InfoStruct::set_offensivespeed, &info_struct, l::_1); + set_float_funcs["rain"] = l::bind(&InfoStruct::set_rain, &info_struct, l::_1); + set_float_funcs["wind"] = l::bind(&InfoStruct::set_wind, &info_struct, l::_1); + set_sint8_funcs["alignment"] = l::bind(&InfoStruct::set_alignment, &info_struct, l::_1); + set_int32_funcs["pet_id"] = l::bind(&InfoStruct::set_pet_id, &info_struct, l::_1); + set_string_funcs["pet_name"] = l::bind(&InfoStruct::set_pet_name, &info_struct, l::_1); + set_float_funcs["pet_health_pct"] = l::bind(&InfoStruct::set_pet_health_pct, &info_struct, l::_1); + set_float_funcs["pet_power_pct"] = l::bind(&InfoStruct::set_pet_power_pct, &info_struct, l::_1); + set_int8_funcs["pet_movement"] = l::bind(&InfoStruct::set_pet_movement, &info_struct, l::_1); + set_int8_funcs["pet_behavior"] = l::bind(&InfoStruct::set_pet_behavior, &info_struct, l::_1); + set_int32_funcs["vision"] = l::bind(&InfoStruct::set_vision, &info_struct, l::_1); + set_int8_funcs["breathe_underwater"] = l::bind(&InfoStruct::set_breathe_underwater, &info_struct, l::_1); + set_string_funcs["biography"] = l::bind(&InfoStruct::set_biography, &info_struct, l::_1); + set_float_funcs["drunk"] = l::bind(&InfoStruct::set_drunk, &info_struct, l::_1); + + set_sint16_funcs["power_regen"] = l::bind(&InfoStruct::set_power_regen, &info_struct, l::_1); + set_sint16_funcs["hp_regen"] = l::bind(&InfoStruct::set_hp_regen, &info_struct, l::_1); + + set_int8_funcs["power_regen_override"] = l::bind(&InfoStruct::set_power_regen_override, &info_struct, l::_1); + set_int8_funcs["hp_regen_override"] = l::bind(&InfoStruct::set_hp_regen_override, &info_struct, l::_1); + + set_int8_funcs["water_type"] = l::bind(&InfoStruct::set_water_type, &info_struct, l::_1); + set_int8_funcs["flying_type"] = l::bind(&InfoStruct::set_flying_type, &info_struct, l::_1); + + set_int8_funcs["no_interrupt"] = l::bind(&InfoStruct::set_no_interrupt, &info_struct, l::_1); + + set_int8_funcs["interaction_flag"] = l::bind(&InfoStruct::set_interaction_flag, &info_struct, l::_1); + set_int8_funcs["tag1"] = l::bind(&InfoStruct::set_tag1, &info_struct, l::_1); + set_int16_funcs["mood"] = l::bind(&InfoStruct::set_mood, &info_struct, l::_1); + + set_int32_funcs["range_last_attack_time"] = l::bind(&InfoStruct::set_range_last_attack_time, &info_struct, l::_1); + set_int32_funcs["primary_last_attack_time"] = l::bind(&InfoStruct::set_primary_last_attack_time, &info_struct, l::_1); + set_int32_funcs["secondary_last_attack_time"] = l::bind(&InfoStruct::set_secondary_last_attack_time, &info_struct, l::_1); + + set_int16_funcs["primary_attack_delay"] = l::bind(&InfoStruct::set_primary_attack_delay, &info_struct, l::_1); + set_int16_funcs["secondary_attack_delay"] = l::bind(&InfoStruct::set_secondary_attack_delay, &info_struct, l::_1); + set_int16_funcs["ranged_attack_delay"] = l::bind(&InfoStruct::set_ranged_attack_delay, &info_struct, l::_1); + + set_int8_funcs["primary_weapon_type"] = l::bind(&InfoStruct::set_primary_weapon_type, &info_struct, l::_1); + set_int8_funcs["secondary_weapon_type"] = l::bind(&InfoStruct::set_secondary_weapon_type, &info_struct, l::_1); + set_int8_funcs["ranged_weapon_type"] = l::bind(&InfoStruct::set_ranged_weapon_type, &info_struct, l::_1); + + set_int32_funcs["primary_weapon_damage_low"] = l::bind(&InfoStruct::set_primary_weapon_damage_low, &info_struct, l::_1); + set_int32_funcs["primary_weapon_damage_high"] = l::bind(&InfoStruct::set_primary_weapon_damage_high, &info_struct, l::_1); + set_int32_funcs["secondary_weapon_damage_low"] = l::bind(&InfoStruct::set_secondary_weapon_damage_low, &info_struct, l::_1); + set_int32_funcs["secondary_weapon_damage_high"] = l::bind(&InfoStruct::set_secondary_weapon_damage_high, &info_struct, l::_1); + set_int32_funcs["ranged_weapon_damage_low"] = l::bind(&InfoStruct::set_ranged_weapon_damage_low, &info_struct, l::_1); + set_int32_funcs["ranged_weapon_damage_high"] = l::bind(&InfoStruct::set_ranged_weapon_damage_high, &info_struct, l::_1); + + set_int8_funcs["wield_type"] = l::bind(&InfoStruct::set_wield_type, &info_struct, l::_1); + set_int8_funcs["attack_type"] = l::bind(&InfoStruct::set_attack_type, &info_struct, l::_1); + + set_int16_funcs["primary_weapon_delay"] = l::bind(&InfoStruct::set_primary_weapon_delay, &info_struct, l::_1); + set_int16_funcs["secondary_weapon_delay"] = l::bind(&InfoStruct::set_secondary_weapon_delay, &info_struct, l::_1); + set_int16_funcs["ranged_weapon_delay"] = l::bind(&InfoStruct::set_ranged_weapon_delay, &info_struct, l::_1); + + set_int8_funcs["override_primary_weapon"] = l::bind(&InfoStruct::set_override_primary_weapon, &info_struct, l::_1); + set_int8_funcs["override_secondary_weapon"] = l::bind(&InfoStruct::set_override_secondary_weapon, &info_struct, l::_1); + set_int8_funcs["override_ranged_weapon"] = l::bind(&InfoStruct::set_override_ranged_weapon, &info_struct, l::_1); + + set_int8_funcs["friendly_target_npc"] = l::bind(&InfoStruct::set_friendly_target_npc, &info_struct, l::_1); + set_int32_funcs["last_claim_time"] = l::bind(&InfoStruct::set_last_claim_time, &info_struct, l::_1); + + set_int8_funcs["engaged_encounter"] = l::bind(&InfoStruct::set_engaged_encounter, &info_struct, l::_1); + + set_int8_funcs["first_world_login"] = l::bind(&InfoStruct::set_first_world_login, &info_struct, l::_1); + + set_int8_funcs["reload_player_spells"] = l::bind(&InfoStruct::set_reload_player_spells, &info_struct, l::_1); + + set_int8_funcs["group_loot_method"] = l::bind(&InfoStruct::set_group_loot_method, &info_struct, l::_1); + set_int8_funcs["group_loot_items_rarity"] = l::bind(&InfoStruct::set_group_loot_items_rarity, &info_struct, l::_1); + set_int8_funcs["group_auto_split"] = l::bind(&InfoStruct::set_group_auto_split, &info_struct, l::_1); + set_int8_funcs["group_default_yell"] = l::bind(&InfoStruct::set_group_default_yell, &info_struct, l::_1); + set_int8_funcs["group_autolock"] = l::bind(&InfoStruct::set_group_autolock, &info_struct, l::_1); + set_int8_funcs["group_lock_method"] = l::bind(&InfoStruct::set_group_lock_method, &info_struct, l::_1); + set_int8_funcs["group_solo_autolock"] = l::bind(&InfoStruct::set_group_solo_autolock, &info_struct, l::_1); + set_int8_funcs["group_auto_loot_method"] = l::bind(&InfoStruct::set_group_auto_loot_method, &info_struct, l::_1); + set_int8_funcs["assist_auto_attack"] = l::bind(&InfoStruct::set_assist_auto_attack, &info_struct, l::_1); + + set_string_funcs["action_state"] = l::bind(&InfoStruct::set_action_state, &info_struct, l::_1); + set_string_funcs["combat_action_state"] = l::bind(&InfoStruct::set_combat_action_state, &info_struct, l::_1); + + set_float_funcs["max_spell_reduction"] = l::bind(&InfoStruct::set_max_spell_reduction, &info_struct, l::_1); + set_int8_funcs["max_spell_reduction_override"] = l::bind(&InfoStruct::set_max_spell_reduction_override, &info_struct, l::_1); +} + +bool Entity::HasMoved(bool include_heading){ + if(GetX() == last_x && GetY() == last_y && GetZ() == last_z && ((!include_heading) || (include_heading && GetHeading() == last_heading))) + return false; + bool ret_val = true; + if(last_x == -1 && last_y == -1 && last_z == -1 && last_heading == -1){ + ret_val = false; + } + last_x = GetX(); + last_y = GetY(); + last_z = GetZ(); + last_heading = GetHeading(); + return ret_val; +} + +int16 Entity::GetStr(){ + return GetInfoStruct()->get_str(); +} + +int16 Entity::GetSta(){ + return GetInfoStruct()->get_sta(); +} + +int16 Entity::GetInt(){ + return GetInfoStruct()->get_intel(); +} + +int16 Entity::GetWis(){ + return GetInfoStruct()->get_wis(); +} + +int16 Entity::GetAgi(){ + return GetInfoStruct()->get_agi(); +} + +int16 Entity::GetPrimaryStat(){ + int8 base_class = classes.GetBaseClass(GetAdventureClass()); + if (base_class == FIGHTER) + return GetInfoStruct()->get_str(); + else if (base_class == PRIEST) + return GetInfoStruct()->get_wis(); + else if (base_class == MAGE) + return GetInfoStruct()->get_intel(); + else + return GetInfoStruct()->get_agi(); +} + +int16 Entity::GetHeatResistance(){ + return GetInfoStruct()->get_heat(); +} + +int16 Entity::GetColdResistance(){ + return GetInfoStruct()->get_cold(); +} + +int16 Entity::GetMagicResistance(){ + return GetInfoStruct()->get_magic(); +} + +int16 Entity::GetMentalResistance(){ + return GetInfoStruct()->get_mental(); +} + +int16 Entity::GetDivineResistance(){ + return GetInfoStruct()->get_divine(); +} + +int16 Entity::GetDiseaseResistance(){ + return GetInfoStruct()->get_disease(); +} + +int16 Entity::GetPoisonResistance(){ + return GetInfoStruct()->get_poison(); +} + +int8 Entity::GetConcentrationCurrent() { + return GetInfoStruct()->get_cur_concentration(); +} + +int8 Entity::GetConcentrationMax() { + return GetInfoStruct()->get_max_concentration(); +} + +int16 Entity::GetStrBase(){ + return GetInfoStruct()->get_str_base(); +} + +int16 Entity::GetStaBase(){ + return GetInfoStruct()->get_sta_base(); +} + +int16 Entity::GetIntBase(){ + return GetInfoStruct()->get_intel_base(); +} + +int16 Entity::GetWisBase(){ + return GetInfoStruct()->get_wis_base(); +} + +int16 Entity::GetAgiBase(){ + return GetInfoStruct()->get_agi_base(); +} + +int16 Entity::GetHeatResistanceBase(){ + return GetInfoStruct()->get_heat_base(); +} + +int16 Entity::GetColdResistanceBase(){ + return GetInfoStruct()->get_cold_base(); +} + +int16 Entity::GetMagicResistanceBase(){ + return GetInfoStruct()->get_magic_base(); +} + +int16 Entity::GetMentalResistanceBase(){ + return GetInfoStruct()->get_mental_base(); +} + +int16 Entity::GetDivineResistanceBase(){ + return GetInfoStruct()->get_divine_base(); +} + +int16 Entity::GetDiseaseResistanceBase(){ + return GetInfoStruct()->get_disease_base(); +} + +int16 Entity::GetPoisonResistanceBase(){ + return GetInfoStruct()->get_poison_base(); +} + +sint8 Entity::GetAlignment(){ + return GetInfoStruct()->get_alignment(); +} + +bool Entity::IsCasting(){ + return casting; +} + +void Entity::IsCasting(bool val){ + casting = val; +} + +int32 Entity::GetRangeLastAttackTime(){ + return GetInfoStruct()->get_range_last_attack_time(); +} + +void Entity::SetRangeLastAttackTime(int32 time){ + GetInfoStruct()->set_range_last_attack_time(time); +} + +int16 Entity::GetRangeAttackDelay(){ + return GetInfoStruct()->get_ranged_attack_delay(); +// if(IsPlayer()){ +// Item* item = ((Player*)this)->GetEquipmentList()->GetItem(EQ2_RANGE_SLOT); +// if(item && item->IsRanged()) +// return item->ranged_info->weapon_info.delay*100; +// } +// return 3000; +} + +int32 Entity::GetPrimaryLastAttackTime(){ + return GetInfoStruct()->get_primary_last_attack_time(); +} + +int16 Entity::GetPrimaryAttackDelay(){ + return GetInfoStruct()->get_primary_attack_delay(); +} + +void Entity::SetPrimaryAttackDelay(int16 new_delay){ + GetInfoStruct()->set_primary_attack_delay(new_delay); +} + +void Entity::SetPrimaryLastAttackTime(int32 new_time){ + GetInfoStruct()->set_primary_last_attack_time(new_time); +} + +int32 Entity::GetSecondaryLastAttackTime(){ + return GetInfoStruct()->get_secondary_last_attack_time(); +} + +int16 Entity::GetSecondaryAttackDelay(){ + return GetInfoStruct()->get_secondary_attack_delay(); +} + +void Entity::SetSecondaryAttackDelay(int16 new_delay){ + GetInfoStruct()->set_secondary_attack_delay(new_delay); +} + +void Entity::SetSecondaryLastAttackTime(int32 new_time){ + GetInfoStruct()->set_secondary_last_attack_time(new_time); +} + +void Entity::GetWeaponDamage(Item* item, int32* low_damage, int32* high_damage) { + if(!low_damage || !high_damage) + return; + int32 selected_low_dmg = item->weapon_info->damage_low3; + int32 selected_high_dmg = item->weapon_info->damage_high3; + + if(IsPlayer()) { + float skillMultiplier = rule_manager.GetGlobalRule(R_Player, LevelMasterySkillMultiplier)->GetFloat(); + if(skillMultiplier <= 0.0f) { + skillMultiplier = 1.0f; + } + int32 min_level_skill = (int32)((float)item->generic_info.adventure_default_level*skillMultiplier); + int32 rec_level_skill = (int32)((float)item->details.recommended_level*skillMultiplier); + if(min_level_skill > rec_level_skill) { + rec_level_skill = rec_level_skill; + } + + Skill* masterySkill = ((Player*)this)->skill_list.GetSkill(item->generic_info.skill_req2); + if(masterySkill) { + LogWrite(PLAYER__ERROR, 0, "Player", "Item has skill %s %u requirement", masterySkill->name.data.c_str(), item->generic_info.skill_req2); + int16 skillID = master_item_list.GetItemStatIDByName(masterySkill->name.data); + int32 skill_chance = (int32)CalculateSkillWithBonus((char*)masterySkill->name.data.c_str(), master_item_list.GetItemStatIDByName(masterySkill->name.data), false); + if(skill_chance >= min_level_skill && skill_chance < rec_level_skill) { + int32 diff_skill = rec_level_skill - skill_chance; + if(diff_skill < 1) { + selected_low_dmg = item->weapon_info->damage_low2; + selected_high_dmg = item->weapon_info->damage_high2; + } + else { + diff_skill += 1; + double logResult = log((double)diff_skill) / skillMultiplier; + if(logResult > 1.0f) { + logResult = .95f; + } + + selected_low_dmg = (int32)((double)item->weapon_info->damage_low2 * (1.0 - logResult)); + if(selected_low_dmg < item->weapon_info->damage_low3) { + selected_low_dmg = item->weapon_info->damage_low3; + } + selected_high_dmg = (int32)((double)item->weapon_info->damage_high2 * (1.0 - logResult)); + if(selected_high_dmg < item->weapon_info->damage_high3) { + selected_high_dmg = item->weapon_info->damage_high3; + } + } + } + else if(skill_chance >= rec_level_skill) { + selected_low_dmg = item->weapon_info->damage_low2; + selected_high_dmg = item->weapon_info->damage_high2; + } + } + } + + *low_damage = selected_low_dmg; + *high_damage = selected_high_dmg; +} + +void Entity::ChangePrimaryWeapon(){ + if(GetInfoStruct()->get_override_primary_weapon()) { + return; + } + + int32 str_offset_dmg = GetStrengthDamage(); + Item* item = equipment_list.GetItem(EQ2_PRIMARY_SLOT); + if(item && item->details.item_id > 0 && item->IsWeapon()){ + int32 selected_low_dmg = item->weapon_info->damage_low3; + int32 selected_high_dmg = item->weapon_info->damage_high3; + GetWeaponDamage(item, &selected_low_dmg, &selected_high_dmg); + GetInfoStruct()->set_primary_weapon_delay(item->weapon_info->delay * 100); + GetInfoStruct()->set_primary_weapon_damage_low(selected_low_dmg + str_offset_dmg); + GetInfoStruct()->set_primary_weapon_damage_high(selected_high_dmg + str_offset_dmg); + GetInfoStruct()->set_primary_weapon_type(item->GetWeaponType()); + GetInfoStruct()->set_wield_type(item->weapon_info->wield_type); + } + else{ + int16 effective_level = GetInfoStruct()->get_effective_level(); + if ( !effective_level ) + effective_level = GetLevel(); + + GetInfoStruct()->set_primary_weapon_delay(2000); + GetInfoStruct()->set_primary_weapon_damage_low((int32)1 + (effective_level * .2) + str_offset_dmg); + GetInfoStruct()->set_primary_weapon_damage_high((int32)(5 + effective_level * (effective_level/5)) + str_offset_dmg); + if(GetInfoStruct()->get_attack_type() > 0) { + GetInfoStruct()->set_primary_weapon_type(GetInfoStruct()->get_attack_type()); + } + else { + GetInfoStruct()->set_primary_weapon_type(1); + } + GetInfoStruct()->set_wield_type(2); + } +} + +void Entity::ChangeSecondaryWeapon(){ + if(GetInfoStruct()->get_override_secondary_weapon()) { + return; + } + + int32 str_offset_dmg = GetStrengthDamage(); + + Item* item = equipment_list.GetItem(EQ2_SECONDARY_SLOT); + if(item && item->details.item_id > 0 && item->IsWeapon()){ + int32 selected_low_dmg = item->weapon_info->damage_low3; + int32 selected_high_dmg = item->weapon_info->damage_high3; + GetWeaponDamage(item, &selected_low_dmg, &selected_high_dmg); + GetInfoStruct()->set_secondary_weapon_delay(item->weapon_info->delay * 100); + GetInfoStruct()->set_secondary_weapon_damage_low(selected_low_dmg + str_offset_dmg); + GetInfoStruct()->set_secondary_weapon_damage_high(selected_high_dmg + str_offset_dmg); + GetInfoStruct()->set_secondary_weapon_type(item->GetWeaponType()); + } + else{ + int16 effective_level = GetInfoStruct()->get_effective_level(); + if ( !effective_level ) + effective_level = GetLevel(); + + GetInfoStruct()->set_secondary_weapon_delay(2000); + GetInfoStruct()->set_secondary_weapon_damage_low((int32)(1 + effective_level * .2) + str_offset_dmg); + GetInfoStruct()->set_secondary_weapon_damage_high((int32)(5 + effective_level * (effective_level/6)) + str_offset_dmg); + GetInfoStruct()->set_secondary_weapon_type(1); + } +} + +void Entity::ChangeRangedWeapon(){ + if(GetInfoStruct()->get_override_ranged_weapon()) { + return; + } + + int32 str_offset_dmg = GetStrengthDamage(); + + Item* item = equipment_list.GetItem(EQ2_RANGE_SLOT); + if(item && item->details.item_id > 0 && item->IsRanged()){ + GetInfoStruct()->set_ranged_weapon_delay(item->ranged_info->weapon_info.delay*100); + GetInfoStruct()->set_ranged_weapon_damage_low(item->ranged_info->weapon_info.damage_low3 + str_offset_dmg); + GetInfoStruct()->set_ranged_weapon_damage_high(item->ranged_info->weapon_info.damage_high3 + str_offset_dmg); + GetInfoStruct()->set_ranged_weapon_type(item->GetWeaponType()); + } +} + +void Entity::UpdateWeapons() { + ChangePrimaryWeapon(); + ChangeSecondaryWeapon(); + ChangeRangedWeapon(); +} + +int32 Entity::GetStrengthDamage() { + int32 str_offset = 1; + if(IsNPC()) { + str_offset = rule_manager.GetGlobalRule(R_Combat, StrengthNPC)->GetInt32(); + if(str_offset < 1) + str_offset = 1; + } + else { + str_offset = rule_manager.GetGlobalRule(R_Combat, StrengthOther)->GetInt32(); + if(str_offset < 1) + str_offset = 1; + + } + int32 str_offset_dmg = (int32)((GetInfoStruct()->get_str() / str_offset)); + return str_offset_dmg; +} + +int32 Entity::GetPrimaryWeaponMinDamage(){ + return GetInfoStruct()->get_primary_weapon_damage_low(); +} + +int32 Entity::GetPrimaryWeaponMaxDamage(){ + return GetInfoStruct()->get_primary_weapon_damage_high(); +} + +int16 Entity::GetPrimaryWeaponDelay(){ + return GetInfoStruct()->get_primary_weapon_delay(); +} + +int16 Entity::GetSecondaryWeaponDelay(){ + return GetInfoStruct()->get_secondary_weapon_delay(); +} + +int32 Entity::GetSecondaryWeaponMinDamage(){ + return GetInfoStruct()->get_secondary_weapon_damage_low(); +} + +int32 Entity::GetSecondaryWeaponMaxDamage(){ + return GetInfoStruct()->get_secondary_weapon_damage_high(); +} + +int8 Entity::GetPrimaryWeaponType(){ + return GetInfoStruct()->get_primary_weapon_type(); +} + +int8 Entity::GetSecondaryWeaponType(){ + return GetInfoStruct()->get_secondary_weapon_type(); +} + +int32 Entity::GetRangedWeaponMinDamage(){ + return GetInfoStruct()->get_ranged_weapon_damage_low(); +} + +int32 Entity::GetRangedWeaponMaxDamage(){ + return GetInfoStruct()->get_ranged_weapon_damage_high(); +} + +int8 Entity::GetRangedWeaponType(){ + return GetInfoStruct()->get_ranged_weapon_type(); +} + +bool Entity::IsDualWield(){ + return GetInfoStruct()->get_wield_type() == 1; +} + +int8 Entity::GetWieldType(){ + return GetInfoStruct()->get_wield_type(); +} + +int16 Entity::GetRangeWeaponDelay(){ + return GetInfoStruct()->get_ranged_weapon_delay(); +} + +void Entity::SetRangeWeaponDelay(int16 new_delay){ + GetInfoStruct()->set_ranged_weapon_delay(new_delay * 100); +} +void Entity::SetRangeAttackDelay(int16 new_delay){ + GetInfoStruct()->set_ranged_attack_delay(new_delay); +} + +void Entity::SetPrimaryWeaponDelay(int16 new_delay){ + GetInfoStruct()->set_primary_weapon_delay(new_delay * 100); +} + +void Entity::SetSecondaryWeaponDelay(int16 new_delay){ + GetInfoStruct()->set_primary_weapon_delay(new_delay * 100); +} + +bool Entity::BehindTarget(Spawn* target){ + return BehindSpawn(target, GetX(), GetZ()); +} + +bool Entity::FlankingTarget(Spawn* target) { + return IsFlankingSpawn(target, GetX(), GetZ()); +} + +float Entity::GetDodgeChance(){ + float ret = 0; + + return ret; +} + +bool Entity::EngagedInCombat(){ + return in_combat; +} + +void Entity::InCombat(bool val){ + bool changeCombatState = false; + if((in_combat && !val) || (!in_combat && val)) + changeCombatState = true; + + in_combat = val; + + bool update_regen = false; + if(GetInfoStruct()->get_engaged_encounter()) { + if(!IsAggroed() || !IsEngagedInEncounter()) { + GetInfoStruct()->set_engaged_encounter(0); + update_regen = true; + } + } + + if(changeCombatState || update_regen) + SetRegenValues((GetInfoStruct()->get_effective_level() > 0) ? GetInfoStruct()->get_effective_level() : GetLevel()); +} + +void Entity::DoRegenUpdate(){ + if(!Alive() || GetHP() == 0)//dead + return; + sint32 hp = GetHP(); + sint32 power = GetPower(); + + if(hp < GetTotalHP()){ + sint16 temp = GetInfoStruct()->get_hp_regen(); + + if((hp + temp) > GetTotalHP()) + SetHP(GetTotalHP()); + else + SetHP(hp + temp); + } + if(GetPower() < GetTotalPower()){ + sint16 temp = GetInfoStruct()->get_power_regen(); + + if((power + temp) > GetTotalPower()) + SetPower(GetTotalPower()); + else + SetPower(power + temp); + } +} + +void Entity::AddMaintainedSpell(LuaSpell* luaspell){ + if (!luaspell) + return; + + Spell* spell = luaspell->spell; + MaintainedEffects* effect = GetFreeMaintainedSpellSlot(); + + if (effect){ + MMaintainedSpells.writelock(__FUNCTION__, __LINE__); + effect->spell = luaspell; + effect->spell_id = spell->GetSpellData()->id; + LogWrite(NPC__SPELLS, 5, "NPC", "AddMaintainedSpell Spell ID: %u, Concentration: %u", spell->GetSpellData()->id, spell->GetSpellData()->req_concentration); + effect->conc_used = spell->GetSpellData()->req_concentration; + effect->total_time = spell->GetSpellDuration() / 10; + effect->tier = spell->GetSpellData()->tier; + if (spell->GetSpellData()->duration_until_cancel) + effect->expire_timestamp = 0xFFFFFFFF; + else + effect->expire_timestamp = Timer::GetCurrentTime2() + (spell->GetSpellDuration() * 100); + MMaintainedSpells.releasewritelock(__FUNCTION__, __LINE__); + } +} + +void Entity::AddSpellEffect(LuaSpell* luaspell, int32 override_expire_time){ + if (!luaspell || !luaspell->caster) + return; + + Spell* spell = luaspell->spell; + SpellEffects* old_effect = GetSpellEffect(spell->GetSpellID(), luaspell->caster); + SpellEffects* effect = 0; + if (old_effect){ + GetZone()->RemoveTargetFromSpell(old_effect->spell, this); + RemoveSpellEffect(old_effect->spell); + } + + LogWrite(SPELL__DEBUG, 0, "Spell", "%s AddSpellEffect %s (%u).", spell->GetName(), GetName(), GetID()); + + if(!effect) + effect = GetFreeSpellEffectSlot(); + + if(effect){ + MSpellEffects.writelock(__FUNCTION__, __LINE__); + effect->spell = luaspell; + effect->spell_id = spell->GetSpellData()->id; + effect->caster = luaspell->caster; + effect->total_time = spell->GetSpellDuration()/10; + if (spell->GetSpellData()->duration_until_cancel) + effect->expire_timestamp = 0xFFFFFFFF; + else if(override_expire_time) + effect->expire_timestamp = Timer::GetCurrentTime2() + override_expire_time; + else + effect->expire_timestamp = Timer::GetCurrentTime2() + (spell->GetSpellDuration()*100); + effect->icon = spell->GetSpellData()->icon; + effect->icon_backdrop = spell->GetSpellData()->icon_backdrop; + effect->tier = spell->GetSpellTier(); + MSpellEffects.releasewritelock(__FUNCTION__, __LINE__); + changed = true; + info_changed = true; + AddChangedZoneSpawn(); + + if(luaspell->caster && luaspell->caster->IsPlayer() && luaspell->caster != this) + ((Player*)luaspell->caster)->GetClient()->TriggerSpellSave(); + } +} + +void Entity::RemoveMaintainedSpell(LuaSpell* luaspell){ + if (!luaspell) + return; + + bool found = false; + MMaintainedSpells.writelock(__FUNCTION__, __LINE__); + for (int i = 0; i<30; i++){ + // If we already found the spell then we are bumping all other up one so there are no gaps + // This check needs to be first so found can never be true on the first iteration (i = 0) + if (found) { + GetInfoStruct()->maintained_effects[i].slot_pos = i - 1; + GetInfoStruct()->maintained_effects[i - 1] = GetInfoStruct()->maintained_effects[i]; + + } + // Compare spells, if we found a match set the found flag + if (GetInfoStruct()->maintained_effects[i].spell == luaspell) + found = true; + + } + // if we found the spell in the array then we need to set the last element to empty + if (found) { + memset(&GetInfoStruct()->maintained_effects[29], 0, sizeof(MaintainedEffects)); + GetInfoStruct()->maintained_effects[29].spell_id = 0xFFFFFFFF; + GetInfoStruct()->maintained_effects[29].icon = 0xFFFF; + GetInfoStruct()->maintained_effects[29].spell = nullptr; + } + MMaintainedSpells.releasewritelock(__FUNCTION__, __LINE__); +} + +void Entity::RemoveSpellEffect(LuaSpell* spell) { + bool found = false; + MSpellEffects.writelock(__FUNCTION__, __LINE__); + for(int i=0;i<45;i++) { + if (found) { + GetInfoStruct()->spell_effects[i-1] = GetInfoStruct()->spell_effects[i]; + } + if (GetInfoStruct()->spell_effects[i].spell == spell) + found = true; + } + if (found) { + LogWrite(SPELL__DEBUG, 0, "Spell", "%s RemoveSpellEffect %s (%u).", spell->spell->GetName(), GetName(), GetID()); + GetZone()->GetSpellProcess()->RemoveTargetFromSpell(spell, this); + memset(&GetInfoStruct()->spell_effects[44], 0, sizeof(SpellEffects)); + GetInfoStruct()->spell_effects[44].spell_id = 0xFFFFFFFF; + changed = true; + info_changed = true; + AddChangedZoneSpawn(); + + if(IsPlayer()) { + ((Player*)this)->SetCharSheetChanged(true); + } + } + + MSpellEffects.releasewritelock(__FUNCTION__, __LINE__); +} + +MaintainedEffects* Entity::GetFreeMaintainedSpellSlot(){ + MaintainedEffects* ret = 0; + InfoStruct* info = GetInfoStruct(); + MMaintainedSpells.readlock(__FUNCTION__, __LINE__); + for (int i = 0; imaintained_effects[i].spell_id == 0xFFFFFFFF){ + ret = &info->maintained_effects[i]; + ret->spell_id = 0; + ret->slot_pos = i; + break; + } + } + MMaintainedSpells.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +MaintainedEffects* Entity::GetMaintainedSpell(int32 spell_id){ + MaintainedEffects* ret = 0; + InfoStruct* info = GetInfoStruct(); + MMaintainedSpells.readlock(__FUNCTION__, __LINE__); + for (int i = 0; imaintained_effects[i].spell_id == spell_id){ + ret = &info->maintained_effects[i]; + break; + } + } + MMaintainedSpells.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +SpellEffects* Entity::GetFreeSpellEffectSlot(){ + SpellEffects* ret = 0; + InfoStruct* info = GetInfoStruct(); + MSpellEffects.readlock(__FUNCTION__, __LINE__); + for(int i=0;i<45;i++){ + if(info->spell_effects[i].spell_id == 0xFFFFFFFF){ + ret = &info->spell_effects[i]; + ret->spell_id = 0; + break; + } + } + MSpellEffects.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +SpellEffects* Entity::GetSpellEffect(int32 id, Entity* caster) { + SpellEffects* ret = 0; + InfoStruct* info = GetInfoStruct(); + MSpellEffects.readlock(__FUNCTION__, __LINE__); + for(int i = 0; i < 45; i++) { + if(info->spell_effects[i].spell_id == id) { + if (!caster || info->spell_effects[i].caster == caster){ + ret = &info->spell_effects[i]; + break; + } + } + } + MSpellEffects.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +SpellEffects* Entity::GetSpellEffectBySpellType(int8 spell_type) { + SpellEffects* ret = 0; + InfoStruct* info = GetInfoStruct(); + MSpellEffects.readlock(__FUNCTION__, __LINE__); + for(int i = 0; i < 45; i++) { + if(info->spell_effects[i].spell_id != 0xFFFFFFFF && info->spell_effects[i].spell->spell->GetSpellData()->spell_type == spell_type) { + ret = &info->spell_effects[i]; + break; + } + } + MSpellEffects.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +SpellEffects* Entity::GetSpellEffectWithLinkedTimer(int32 id, int32 linked_timer, sint32 type_group_spell_id, Entity* caster) { + SpellEffects* ret = 0; + InfoStruct* info = GetInfoStruct(); + MSpellEffects.readlock(__FUNCTION__, __LINE__); + for(int i = 0; i < 45; i++) { + if(info->spell_effects[i].spell_id != 0xFFFFFFFF) + { + if( (info->spell_effects[i].spell_id == id && linked_timer == 0 && type_group_spell_id == 0) || + (linked_timer > 0 && info->spell_effects[i].spell->spell->GetSpellData()->linked_timer == linked_timer) || + (type_group_spell_id > 0 && info->spell_effects[i].spell->spell->GetSpellData()->type_group_spell_id == type_group_spell_id)) + { + if (type_group_spell_id >= -1 && (!caster || info->spell_effects[i].caster == caster)){ + ret = &info->spell_effects[i]; + break; + } + } + } + } + MSpellEffects.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +LuaSpell* Entity::HasLinkedTimerID(LuaSpell* spell, Spawn* target, bool stackWithOtherPlayers) { + if(!spell->spell->GetSpellData()->linked_timer && !spell->spell->GetSpellData()->type_group_spell_id) + return nullptr; + LuaSpell* ret = nullptr; + InfoStruct* info = GetInfoStruct(); + MSpellEffects.readlock(__FUNCTION__, __LINE__); + //this for loop primarily handles self checks and 'friendly' checks + for(int i = 0; i < NUM_MAINTAINED_EFFECTS; i++) { + if(info->maintained_effects[i].spell_id != 0xFFFFFFFF) + { + if( ((info->maintained_effects[i].spell_id == spell->spell->GetSpellID() && spell->spell->GetSpellData()->type_group_spell_id >= 0) || + (info->maintained_effects[i].spell->spell->GetSpellData()->linked_timer > 0 && info->maintained_effects[i].spell->spell->GetSpellData()->linked_timer == spell->spell->GetSpellData()->linked_timer) || + (spell->spell->GetSpellData()->type_group_spell_id > 0 && spell->spell->GetSpellData()->type_group_spell_id == info->maintained_effects[i].spell->spell->GetSpellData()->type_group_spell_id)) && + ((spell->spell->GetSpellData()->friendly_spell) || + (!spell->spell->GetSpellData()->friendly_spell && spell->spell->GetSpellData()->type_group_spell_id >= -1 && spell->caster == info->maintained_effects[i].spell->caster) ) && + (target == nullptr || info->maintained_effects[i].spell->initial_target == target->GetID())) { + ret = info->maintained_effects[i].spell; + break; + } + } + } + MSpellEffects.releasereadlock(__FUNCTION__, __LINE__); + + if(!ret && !stackWithOtherPlayers && target && target->IsEntity()) + { + SpellEffects* effect = ((Entity*)target)->GetSpellEffectWithLinkedTimer(spell->spell->GetSpellID(), spell->spell->GetSpellData()->linked_timer, spell->spell->GetSpellData()->type_group_spell_id, nullptr); + if(effect) + ret = effect->spell; + } + + return ret; +} + +InfoStruct* Entity::GetInfoStruct(){ + return &info_struct; +} + +Skill* Entity::GetSkillByName(const char* name, bool check_update){ + // NPC::GetSkillByName in NPC.cpp exists for NPC's + // Player::GetSkillByName in Player.cpp exists for Player's + return 0; +} + +Skill* Entity::GetSkillByID(int32 id, bool check_update){ + // NPC::GetSkillByID in NPC.cpp exists for NPC's + // Player::GetSkillByID in Player.cpp exists for Player's + return 0; +} + +float Entity::GetMaxSpeed(){ + return max_speed; +} + +void Entity::SetMaxSpeed(float val){ + max_speed = val; +} + +float Entity::CalculateSkillStatChance(char* skillName, int16 item_stat, float max_cap, float modifier, bool add_to_skill) +{ + float skillAndItemsChance = 0.0f; + float maxBonusCap = (float)GetLevel()*rule_manager.GetGlobalRule(R_Combat, MaxSkillBonusByLevel)->GetFloat(); + Skill* skill = GetSkillByName(skillName, false); + if(skill){ + MStats.lock(); + float item_chance_or_skill = stats[item_stat]; + MStats.unlock(); + if(item_chance_or_skill > maxBonusCap) { + item_chance_or_skill = maxBonusCap; + } + + if(add_to_skill) + { + skillAndItemsChance = (((float)skill->current_val+item_chance_or_skill)/10.0f); // do we know 25 is accurate? 10 gives more 'skill' space, most cap at 70% with items + } + else + { + skillAndItemsChance = ((float)skill->current_val/10.0f); // do we know 25 is accurate? 10 gives more 'skill' space, most cap at 70% with items + + if(modifier > maxBonusCap) { + modifier = maxBonusCap; + } + // take chance percentage and add the item stats % (+1 = 1% or .01f) + skillAndItemsChance += (skillAndItemsChance*((item_chance_or_skill + modifier)/100.0f)); + } + } + + if ( max_cap > 0.0f && skillAndItemsChance > max_cap ) + skillAndItemsChance = max_cap; + + return skillAndItemsChance; +} + +float Entity::CalculateSkillWithBonus(char* skillName, int16 item_stat, bool chance_skill_increase) +{ + float skillAndItemsChance = 0.0f; + float maxBonusCap = GetRuleSkillMaxBonus(); + + Skill* skill = GetSkillByName(skillName, chance_skill_increase); + if(skill){ + float item_chance_or_skill = 0.0f; + if(item_stat != 0xFFFF) { + MStats.lock(); + item_chance_or_skill = stats[item_stat]; + MStats.unlock(); + } + if(item_chance_or_skill > maxBonusCap) { // would we be using their effective mentored level or actual level? + item_chance_or_skill = maxBonusCap; + } + skillAndItemsChance = skill->current_val+item_chance_or_skill; + } + + return skillAndItemsChance; +} + +float Entity::GetRuleSkillMaxBonus() { + return (float)GetLevel()*rule_manager.GetGlobalRule(R_Combat, MaxSkillBonusByLevel)->GetFloat(); +} + +void Entity::CalculateBonuses(){ + if(lua_interface->IsLuaSystemReloading()) + return; + + InfoStruct* info = &info_struct; + + int16 effective_level = info->get_effective_level() != 0 ? info->get_effective_level() : GetLevel(); + + CalculateMaxReduction(); + + info->set_block(info->get_block_base()); + + info->set_cur_attack(info->get_attack_base()); + info->set_base_avoidance_pct(info->get_avoidance_base()); + + info->set_disease(info->get_disease_base()); + info->set_divine(info->get_divine_base()); + info->set_heat(info->get_heat_base()); + info->set_magic(info->get_magic_base()); + info->set_mental(info->get_mental_base()); + info->set_cold(info->get_cold_base()); + info->set_poison(info->get_poison_base()); + info->set_elemental_base(info->get_heat()); + info->set_noxious_base(info->get_poison()); + info->set_arcane_base(info->get_magic()); + + info->set_sta(info->get_sta_base()); + info->set_agi(info->get_agi_base()); + info->set_str(info->get_str_base()); + info->set_wis(info->get_wis_base()); + info->set_intel(info->get_intel_base()); + info->set_ability_modifier(0); + info->set_critical_mitigation(0); + + info->set_block_chance(0); + info->set_crit_chance(0); + info->set_crit_bonus(0); + info->set_potency(0); + info->set_hate_mod(0); + info->set_reuse_speed(0); + info->set_casting_speed(0); + info->set_recovery_speed(0); + info->set_spell_reuse_speed(0); + info->set_spell_multi_attack(0); + info->set_dps(0); + info->set_dps_multiplier(0); + info->set_haste(0); + info->set_attackspeed(0); + info->set_multi_attack(0); + info->set_flurry(0); + info->set_melee_ae(0); + + info->set_strikethrough(0); + + info->set_accuracy(0); + + info->set_offensivespeed(0); + + MStats.lock(); + stats.clear(); + MStats.unlock(); + + ItemStatsValues* values = equipment_list.CalculateEquipmentBonuses(this); + CalculateSpellBonuses(values); + + info->set_cur_mitigation(info->get_mitigation_base()); + + int32 calc_mit_cap = effective_level * rule_manager.GetGlobalRule(R_Combat, CalculatedMitigationCapLevel)->GetInt32(); + info->set_max_mitigation(calc_mit_cap); + + int16 mit_percent = (int16)(CalculateMitigation() * 1000.0f); + info->set_mitigation_pve(mit_percent); + mit_percent = (int16)(CalculateMitigation(DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE,0,0,true) * 1000.0f); + info->set_mitigation_pvp(mit_percent); + + info->add_sta((float)values->sta); + info->add_str((float)values->str); + info->add_agi((float)values->agi); + info->add_wis((float)values->wis); + info->add_intel((float)values->int_); + + info->add_disease(values->vs_disease); + info->add_divine(values->vs_divine); + info->add_heat(values->vs_heat); + info->add_magic(values->vs_magic); + int32 sta_hp_bonus = 0.0; + int32 prim_power_bonus = 0.0; + float bonus_mod = 0.0; + if (IsPlayer()) { + bonus_mod = CalculateBonusMod(); + sta_hp_bonus = info->get_sta() * bonus_mod; + prim_power_bonus = GetPrimaryStat() * bonus_mod; + } + prim_power_bonus = floor(float(prim_power_bonus)); + sta_hp_bonus = floor(float(sta_hp_bonus)); + SetTotalHP(GetTotalHPBaseInstance() + values->health + sta_hp_bonus); + SetTotalPower(GetTotalPowerBaseInstance() + values->power + prim_power_bonus); + if(GetHP() > GetTotalHP()) + SetHP(GetTotalHP()); + if(GetPower() > GetTotalPower()) + SetPower(GetTotalPower()); + + info->add_mental(values->vs_mental); + + info->add_poison(values->vs_poison); + + info->set_max_concentration(info->get_max_concentration_base() + values->concentration); + + info->add_cold(values->vs_cold); + + info->add_mitigation_skill1(values->vs_slash); + info->add_mitigation_skill2(values->vs_pierce); + info->add_mitigation_skill3(values->vs_crush); + info->add_ability_modifier(values->ability_modifier); + info->add_critical_mitigation(values->criticalmitigation); + info->add_block_chance(values->extrashieldblockchance); + info->add_crit_chance(values->beneficialcritchance); + info->add_crit_bonus(values->critbonus); + info->add_potency(values->potency); + info->add_hate_mod(values->hategainmod); + info->add_reuse_speed(values->abilityreusespeed); + info->add_casting_speed(values->abilitycastingspeed); + info->add_recovery_speed(values->abilityrecoveryspeed); + info->add_spell_reuse_speed(values->spellreusespeed); + info->add_spell_multi_attack(values->spellmultiattackchance); + info->add_dps(values->dps); + info->add_dps_multiplier(CalculateDPSMultiplier()); + info->add_haste(values->attackspeed); + info->add_multi_attack(values->multiattackchance); + info->add_flurry(values->flurry); + info->add_melee_ae(values->aeautoattackchance); + info->add_strikethrough(values->strikethrough); + info->add_accuracy(values->accuracy); + info->add_offensivespeed(values->offensivespeed); + info->add_uncontested_block(values->uncontested_block); + info->add_uncontested_parry(values->uncontested_parry); + info->add_uncontested_dodge(values->uncontested_dodge); + info->add_uncontested_riposte(values->uncontested_riposte); + + info->set_ability_modifier(values->ability_modifier); + + float full_pct_hit = 100.0f; + + MStats.lock(); + float parryStat = stats[ITEM_STAT_PARRY]; + MStats.unlock(); + float parry_pct = CalculateSkillStatChance("Parry", ITEM_STAT_PARRYCHANCE, 70.0f, parryStat); + parry_pct += parry_pct * (info->get_cur_avoidance()/100.0f); + if(parry_pct > 70.0f) + parry_pct = 70.0f; + + info->set_parry(parry_pct); + + full_pct_hit -= parry_pct; + + float block_pct = 0.0f; + + if(GetAdventureClass() != BRAWLER) + { + Item* item = equipment_list.GetItem(EQ2_SECONDARY_SLOT); + if(item && item->details.item_id > 0 && item->IsShield()){ + // if high is set and greater than low use high, otherwise use low + int16 mitigation = item->armor_info->mitigation_high > item->armor_info->mitigation_low ? item->armor_info->mitigation_high : item->armor_info->mitigation_low; + // we frankly don't know the formula for Block, only that it uses the 'Protection' of the shield, which is the mitigation_low/mitigation_high in the armor_info + if(mitigation) + { + /*DOF Prima Guide: Shields now have the following base chances + to block: Tower (10%), Kite (10%), Round + (5%), Buckler (3%). Your chances to block + scale up or down based on the con of your + opponent.*/ + Skill* skill = master_skill_list.GetSkill(item->generic_info.skill_req1); + float baseBlock = 0.0f; + if(skill) + { + if(skill->short_name.data == "towershield" || skill->short_name.data == "kiteshield") + baseBlock = 10.0f; + else if (skill->short_name.data == "roundshield") + baseBlock = 5.0f; + else if (skill->short_name.data == "buckler") + baseBlock = 3.0f; + } + if(effective_level > mitigation) + block_pct = log10f((float)mitigation/((float)effective_level*10.0f)); + else + block_pct = log10f(((float)effective_level/(float)mitigation)) * log10f(effective_level) * 2.0f; + + if(block_pct < 0.0f) + block_pct *= -1.0f; + + block_pct += baseBlock; + + block_pct += block_pct * (info->get_cur_avoidance()/100.0f); + if(block_pct > 70.0f) + block_pct = 70.0f; + } + } + } + else + { + MStats.lock(); + float deflectionStat = stats[ITEM_STAT_DEFLECTION]; + MStats.unlock(); + block_pct = CalculateSkillStatChance("Deflection", ITEM_STAT_MINIMUMDEFLECTIONCHANCE, 70.0f, deflectionStat+1.0f); + block_pct += block_pct * (info->get_cur_avoidance()/100.0f); + } + + float block_actual = 0.0f; + if(full_pct_hit > 0.0f) + block_actual = block_pct * (full_pct_hit / 100.0f); + + info->set_block(block_actual); + full_pct_hit -= block_actual; + + MStats.lock(); + float defenseStat = stats[ITEM_STAT_DEFENSE]; + float baseAvoidanceStat = stats[ITEM_STAT_BASEAVOIDANCEBONUS]; + MStats.unlock(); + + float dodge_pct = (baseAvoidanceStat/100.0f) + CalculateSkillStatChance("Defense", ITEM_STAT_DODGECHANCE, 100.0f, defenseStat); + dodge_pct += dodge_pct * (info->get_cur_avoidance()/100.0f); + + float dodge_actual = dodge_pct * (full_pct_hit / 100.0f) + CalculateLevelStatBonus(GetAgi()); + + info->set_avoidance_base(dodge_actual); + + float total_avoidance = parry_pct + block_actual + dodge_actual; + info->set_avoidance_display(total_avoidance); + + SetRegenValues(effective_level); + + CalculateApplyWeight(); + + UpdateWeapons(); + + safe_delete(values); +} + +float Entity::CalculateLevelStatBonus(int16 stat_value) { + int16 effective_level = GetInfoStruct()->get_effective_level() != 0 ? GetInfoStruct()->get_effective_level() : GetLevel(); + float result = (log10f(effective_level * stat_value) / 50.0f); // todo: break this down by stat type and give independent modifiers + return result; +} + +void Entity::CalculateApplyWeight() { + if (IsPlayer()) { + int32 prev_weight = GetInfoStruct()->get_weight(); + int32 inv_weight = ((Player*)this)->item_list.GetWeight(); + + // calculate coin + int32 coin_copper = GetInfoStruct()->get_coin_copper(); + int32 coin_silver = GetInfoStruct()->get_coin_silver(); + int32 coin_gold = GetInfoStruct()->get_coin_gold(); + int32 coin_plat = GetInfoStruct()->get_coin_plat(); + + float weight_per_stone = rule_manager.GetGlobalRule(R_Player, CoinWeightPerStone)->GetFloat(); + if(weight_per_stone < 0.0f) { + weight_per_stone = 0.0f; + } + + double weight_copper = ((double)coin_copper / weight_per_stone); + double weight_silver = ((double)coin_silver / weight_per_stone); + double weight_gold = ((double)coin_gold / weight_per_stone); + double weight_platinum = ((double)coin_plat / weight_per_stone); + int32 total_weight = (int32)(weight_copper + weight_silver + weight_gold + weight_platinum); + LogWrite(PLAYER__DEBUG, 0, "Debug", "Coin Weight Calculated to: %u. Weight_Copper: %f, Weight_Silver: %f, Weight_Gold: %f, Weight_Platinum: %f", total_weight, weight_copper, weight_silver, weight_gold, weight_platinum); + + total_weight += (int32)((double)inv_weight / 10.0); + + GetInfoStruct()->set_weight(total_weight); + + SetSpeedMultiplier(GetHighestSnare()); + ((Player*)this)->SetSpeed(GetSpeed()); + if(((Player*)this)->GetClient()) { + ((Player*)this)->GetClient()->SendControlGhost(); + } + info_changed = true; + changed = true; + AddChangedZoneSpawn(); + ((Player*)this)->SetCharSheetChanged(true); + } + int32 max_weight = 0; + float weight_str_multiplier = rule_manager.GetGlobalRule(R_Player, MaxWeightStrengthMultiplier)->GetFloat(); + int32 base_weight = rule_manager.GetGlobalRule(R_Player, BaseWeight)->GetInt32(); + if(weight_str_multiplier < 0.0f) { + weight_str_multiplier = 0.0f; + } + + if(GetInfoStruct()->get_str() <= 0.0f) { + max_weight = base_weight; // rule for base strength + } + else { + max_weight = (int32)((double)GetInfoStruct()->get_str() * weight_str_multiplier); // rule multipler for strength + max_weight += base_weight; // rule for base strength + } + GetInfoStruct()->set_max_weight(max_weight); +} + +void Entity::SetRegenValues(int16 effective_level) +{ + bool classicRegen = rule_manager.GetGlobalRule(R_Spawn, ClassicRegen)->GetBool(); + bool override_ = (IsPlayer() && !GetInfoStruct()->get_engaged_encounter()); + + if(!GetInfoStruct()->get_hp_regen_override()) + { + sint16 regen_hp_rate = 0; + sint16 temp = 0; + + MStats.lock(); + + if(!IsAggroed() || override_) + { + if(classicRegen) + { + // classic regen only gives OUT OF COMBAT, doesn't combine in+out of combat + regen_hp_rate = (int)(effective_level*.75)+1; + temp = regen_hp_rate + stats[ITEM_STAT_HPREGEN]; + temp += stats[ITEM_STAT_HPREGENPPT]; + } + else + { + regen_hp_rate = (int)(effective_level*.75)+(int)(effective_level/10) + 1; + temp = regen_hp_rate + stats[ITEM_STAT_HPREGEN]; + temp += stats[ITEM_STAT_HPREGENPPT] + stats[ITEM_STAT_COMBATHPREGENPPT]; + } + } + else + { + regen_hp_rate = (sint16)(effective_level / 10) + 1; + temp = regen_hp_rate + stats[ITEM_STAT_COMBATHPREGENPPT]; + } + MStats.unlock(); + + GetInfoStruct()->set_hp_regen(temp); + } + + if(!GetInfoStruct()->get_power_regen_override()) + { + sint16 regen_power_rate = 0; + sint16 temp = 0; + + MStats.lock(); + if(!IsAggroed() || override_) + { + if(classicRegen) + { + regen_power_rate = effective_level + 1; + temp = regen_power_rate + stats[ITEM_STAT_MANAREGEN]; + temp += stats[ITEM_STAT_MPREGENPPT]; + } + else + { + regen_power_rate = effective_level + (int)(effective_level/10) + 1; + temp = regen_power_rate + stats[ITEM_STAT_MANAREGEN]; + temp += stats[ITEM_STAT_MPREGENPPT] + stats[ITEM_STAT_COMBATMPREGENPPT]; + } + } + else + { + regen_power_rate = (sint16)(effective_level / 10) + 1; + temp = regen_power_rate + stats[ITEM_STAT_COMBATMPREGENPPT]; + } + MStats.unlock(); + + GetInfoStruct()->set_power_regen(temp); + } +} + +EquipmentItemList* Entity::GetEquipmentList(){ + return &equipment_list; +} + +EquipmentItemList* Entity::GetAppearanceEquipmentList(){ + return &appearance_equipment_list; +} + +void Entity::SetEquipment(Item* item, int8 slot){ + std::lock_guard lk(MEquipment); + if(!item && slot < NUM_SLOTS){ + SetInfo(&equipment.equip_id[slot], 0); + SetInfo(&equipment.color[slot].red, 0); + SetInfo(&equipment.color[slot].green, 0); + SetInfo(&equipment.color[slot].blue, 0); + SetInfo(&equipment.highlight[slot].red, 0); + SetInfo(&equipment.highlight[slot].green, 0); + SetInfo(&equipment.highlight[slot].blue, 0); + } + else{ + if ( slot >= NUM_SLOTS ) + slot = item->details.slot_id; + + if( slot >= NUM_SLOTS ) + return; + + SetInfo(&equipment.equip_id[slot], item->generic_info.appearance_id); + SetInfo(&equipment.color[slot].red, item->generic_info.appearance_red); + SetInfo(&equipment.color[slot].green, item->generic_info.appearance_green); + SetInfo(&equipment.color[slot].blue, item->generic_info.appearance_blue); + SetInfo(&equipment.highlight[slot].red, item->generic_info.appearance_highlight_red); + SetInfo(&equipment.highlight[slot].green, item->generic_info.appearance_highlight_green); + SetInfo(&equipment.highlight[slot].blue, item->generic_info.appearance_highlight_blue); + } +} + +bool Entity::CheckSpellBonusRemoval(LuaSpell* spell, int16 type){ + MutexList::iterator itr = bonus_list.begin(); + while(itr.Next()){ + if(itr.value->luaspell == spell && itr.value->type == type){ + bonus_list.Remove(itr.value, true); + return true; + } + } + return false; +} + +void Entity::AddSpellBonus(LuaSpell* spell, int16 type, float value, int64 class_req, vector race_req, vector faction_req){ + CheckSpellBonusRemoval(spell, type); + BonusValues* bonus = new BonusValues; + if(spell && spell->spell) { + bonus->spell_id = spell->spell->GetSpellID(); + } + else { + bonus->spell_id = 0; + } + bonus->luaspell = spell; + bonus->type = type; + bonus->value = value; + bonus->class_req = class_req; + bonus->race_req = race_req; + bonus->faction_req = faction_req; + bonus->tier = (spell && spell->spell) ? spell->spell->GetSpellTier() : 0; + bonus_list.Add(bonus); + + if(IsNPC() || IsPlayer()) + CalculateBonuses(); +} + +BonusValues* Entity::GetSpellBonus(int32 spell_id) { + BonusValues *ret = 0; + MutexList::iterator itr = bonus_list.begin(); + while (itr.Next()) { + if (itr.value->spell_id == spell_id) { + ret = itr.value; + break; + } + } + + return ret; +} + +vector* Entity::GetAllSpellBonuses(LuaSpell* spell) { + vector* list = new vector; + MutexList::iterator itr = bonus_list.begin(); + while (itr.Next()) { + if (itr.value->luaspell == spell) + list->push_back(itr.value); + } + return list; +} + +void Entity::RemoveSpellBonus(const LuaSpell* spell, bool remove_all){ + // spell can be null! + MutexList::iterator itr = bonus_list.begin(); + while(itr.Next()){ + if(itr.value->luaspell == spell || remove_all) + bonus_list.Remove(itr.value, true); + } + + if(IsNPC() || IsPlayer()) + CalculateBonuses(); +} + +void Entity::CalculateSpellBonuses(ItemStatsValues* stats){ + if(stats){ + MutexList::iterator itr = bonus_list.begin(); + vector bv; + //First check if we meet the requirement for each bonus + bool race_match = false; + while(itr.Next()) { + if (itr.value->race_req.size() > 0) { + for (int8 i = 0; i < itr.value->race_req.size(); i++) { + if (GetRace() == itr.value->race_req[i]) { + race_match = true; + } + } + } + else + race_match = true; // if the race_req.size = 0 then there is no race requirement and the race_match will be true + int64 const class1 = pow(2.0, (GetAdventureClass() - 1)); + int64 const class2 = pow(2.0, (classes.GetSecondaryBaseClass(GetAdventureClass()) - 1)); + int64 const class3 = pow(2.0, (classes.GetBaseClass(GetAdventureClass()) - 1)); + if (itr.value->class_req == 0 || (itr.value->class_req & class1) == class1 || (itr.value->class_req & class2) == class2 || (itr.value->class_req & class3) == class3 && race_match ) + bv.push_back(itr.value); + } + //Sort the bonuses by spell id and luaspell + BonusValues* bonus = nullptr; + map > > sort; + for (int8 i = 0; i < bv.size(); i++){ + bonus = bv.at(i); + sort[bonus->spell_id][bonus->luaspell].push_back(bonus); + } + //Now check for the highest tier of each spell id and apply those bonuses + map >::iterator tier_itr; + map > >::iterator sort_itr; + for (sort_itr = sort.begin(); sort_itr != sort.end(); sort_itr++){ + LuaSpell* key = nullptr; + sint8 highest_tier = -1; + //Find the highest tier for this spell id + for (tier_itr = sort_itr->second.begin(); tier_itr != sort_itr->second.end(); tier_itr++){ + LuaSpell* current_spell = tier_itr->first; + sint8 current_tier = 0; + if (current_spell && current_spell->spell && ((current_tier = current_spell->spell->GetSpellTier()) > highest_tier)) { + highest_tier = current_tier; + key = current_spell; + } + } + //We've found the highest tier for this spell id, so add the bonuses + vector* final_bonuses = &sort_itr->second[key]; + for (int8 i = 0; i < final_bonuses->size(); i++) + world.AddBonuses(nullptr, stats, final_bonuses->at(i)->type, final_bonuses->at(i)->value, this); + } + } +} + +void Entity::AddMezSpell(LuaSpell* spell) { + if (!spell) + return; + + if (!control_effects[CONTROL_EFFECT_TYPE_MEZ]) + control_effects[CONTROL_EFFECT_TYPE_MEZ] = new MutexList; + + MutexList* mez_spells = control_effects[CONTROL_EFFECT_TYPE_MEZ]; + + if (IsPlayer() && !IsStunned() && !IsMezImmune() && mez_spells->size(true) == 0){ + ((Player*)this)->SetPlayerControlFlag(1, 16, true); + if (!IsRooted()) + ((Player*)this)->SetPlayerControlFlag(1, 8, true); + if (!IsStifled() && !IsFeared()) + GetZone()->LockAllSpells((Player*)this); + } + + if (IsNPC() && !IsMezImmune()) + { + HaltMovement(); + } + + mez_spells->Add(spell); +} + +void Entity::RemoveMezSpell(LuaSpell* spell) { + MutexList* mez_spells = control_effects[CONTROL_EFFECT_TYPE_MEZ]; + if (!mez_spells || mez_spells->size(true) == 0) + return; + + mez_spells->Remove(spell); + if (mez_spells->size(true) == 0){ + if (IsPlayer() && !IsMezImmune() && !IsStunned()){ + if (!IsStifled() && !IsFeared()) + GetZone()->UnlockAllSpells((Player*)this); + ((Player*)this)->SetPlayerControlFlag(1, 16, false); + if (!IsRooted()) + ((Player*)this)->SetPlayerControlFlag(1, 8, false); + } + + if(!IsPlayer()) { + GetZone()->movementMgr->StopNavigation((Entity*)this); + ((Spawn*)this)->StopMovement(); + } + } +} + +void Entity::RemoveAllMezSpells() { + MutexList* mez_spells = control_effects[CONTROL_EFFECT_TYPE_MEZ]; + if (!mez_spells) + return; + + MutexList::iterator itr = mez_spells->begin(); + while (itr.Next()){ + LuaSpell* spell = itr.value; + if (!spell) + continue; + GetZone()->RemoveTargetFromSpell(spell, this); + RemoveDetrimentalSpell(spell); + RemoveSpellEffect(spell); + if (IsPlayer()) + ((Player*)this)->RemoveSkillBonus(spell->spell->GetSpellID()); + } + + mez_spells->clear(); + if (IsPlayer() && !IsMezImmune() && !IsStunned()){ + if (!IsStifled() && !IsFeared()) + GetZone()->UnlockAllSpells((Player*)this); + ((Player*)this)->SetPlayerControlFlag(1, 16, false); + if (!IsRooted()) + ((Player*)this)->SetPlayerControlFlag(1, 8, false); + } +} + +void Entity::AddStifleSpell(LuaSpell* spell) { + if (!spell) + return; + + if (!control_effects[CONTROL_EFFECT_TYPE_STIFLE]) + control_effects[CONTROL_EFFECT_TYPE_STIFLE] = new MutexList; + + if (IsPlayer() && control_effects[CONTROL_EFFECT_TYPE_STIFLE]->size(true) == 0 && !IsStifleImmune() && !IsMezzedOrStunned()) + GetZone()->LockAllSpells((Player*)this); + + control_effects[CONTROL_EFFECT_TYPE_STIFLE]->Add(spell); +} + +void Entity::RemoveStifleSpell(LuaSpell* spell) { + MutexList* stifle_list = control_effects[CONTROL_EFFECT_TYPE_STIFLE]; + if (!stifle_list || stifle_list->size(true) == 0) + return; + + stifle_list->Remove(spell); + + if (IsPlayer() && stifle_list->size(true) == 0 && !IsStifleImmune() && !IsMezzedOrStunned()) + GetZone()->UnlockAllSpells((Player*)this); +} + +void Entity::AddDazeSpell(LuaSpell* spell) { + if (!spell) + return; + + if (!control_effects[CONTROL_EFFECT_TYPE_DAZE]) + control_effects[CONTROL_EFFECT_TYPE_DAZE] = new MutexList; + + control_effects[CONTROL_EFFECT_TYPE_DAZE]->Add(spell); +} + +void Entity::RemoveDazeSpell(LuaSpell* spell) { + MutexList* daze_list = control_effects[CONTROL_EFFECT_TYPE_DAZE]; + if (!daze_list || daze_list->size(true) == 0) + return; + + daze_list->Remove(spell); +} + +void Entity::AddStunSpell(LuaSpell* spell) { + if (!spell) + return; + + if (!control_effects[CONTROL_EFFECT_TYPE_STUN]) + control_effects[CONTROL_EFFECT_TYPE_STUN] = new MutexList; + + if (IsPlayer() && control_effects[CONTROL_EFFECT_TYPE_STUN]->size(true) == 0 && !IsStunImmune()){ + if (!IsMezzed()){ + ((Player*)this)->SetPlayerControlFlag(1, 16, true); + if (!IsRooted()) + ((Player*)this)->SetPlayerControlFlag(1, 8, true); + if (!IsStifled() && !IsFeared()) + GetZone()->LockAllSpells((Player*)this); + } + } + + control_effects[CONTROL_EFFECT_TYPE_STUN]->Add(spell); +} + +void Entity::RemoveStunSpell(LuaSpell* spell) { + MutexList* stun_list = control_effects[CONTROL_EFFECT_TYPE_STUN]; + if (!stun_list || stun_list->size(true) == 0) + return; + + stun_list->Remove(spell); + if (stun_list->size(true) == 0){ + if (IsPlayer() && !IsMezzed() && !IsStunImmune()){ + ((Player*)this)->SetPlayerControlFlag(1, 16, false); + if (!IsRooted()) + ((Player*)this)->SetPlayerControlFlag(1, 8, false); + if (!IsStifled() && !IsFeared()) + GetZone()->UnlockAllSpells((Player*)this); + } + + if(!IsPlayer()) { + GetZone()->movementMgr->StopNavigation((Entity*)this); + ((Spawn*)this)->StopMovement(); + } + } +} + +void Entity::HideDeityPet(bool val) { + if (!deityPet) + return; + + if (val) { + deityPet->AddAllowAccessSpawn(deityPet); + GetZone()->HidePrivateSpawn(deityPet); + } + else + deityPet->MakeSpawnPublic(); +} + +void Entity::HideCosmeticPet(bool val) { + if (!cosmeticPet) + return; + + if (val) { + cosmeticPet->AddAllowAccessSpawn(cosmeticPet); + GetZone()->HidePrivateSpawn(cosmeticPet); + } + else + cosmeticPet->MakeSpawnPublic(); +} + +void Entity::DismissAllPets(bool from_death, bool spawnListLocked) +{ + DismissPet(GetPet(), from_death, spawnListLocked); + DismissPet(GetCharmedPet(), from_death, spawnListLocked); + DismissPet(GetDeityPet(), from_death, spawnListLocked); + DismissPet(GetCosmeticPet(), from_death, spawnListLocked); +} + +void Entity::DismissPet(Entity* pet, bool from_death, bool spawnListLocked) { + if (!pet) + return; + + Entity* PetOwner = pet->GetOwner(); + + if(pet->IsNPC()) + { + ((NPC*)pet)->SetDismissing(true); + + // Remove the spell maintained spell + Spell* spell = master_spell_list.GetSpell(pet->GetPetSpellID(), pet->GetPetSpellTier()); + if (spell) + GetZone()->GetSpellProcess()->DeleteCasterSpell(this, spell, from_death == true ? (string)"pet_death" : (string)"canceled"); + } + + if (pet->GetPetType() == PET_TYPE_CHARMED) { + if(PetOwner) + PetOwner->SetCharmedPet(0); + + if (!from_death) { + // set the pet flag to false, owner to 0, and give the mob its old brain back + pet->SetPet(false); + pet->SetOwner(0); + if(pet->IsNPC()) + ((NPC*)pet)->SetBrain(new Brain((NPC*)pet)); + + pet->SetDismissing(false); + } + } + else if (PetOwner && pet->GetPetType() == PET_TYPE_COMBAT) + PetOwner->SetCombatPet(0); + else if (PetOwner && pet->GetPetType() == PET_TYPE_DEITY) + PetOwner->SetDeityPet(0); + else if (PetOwner && pet->GetPetType() == PET_TYPE_COSMETIC) + PetOwner->SetCosmeticPet(0); + + // if owner is player and no combat pets left reset the pet info + if (PetOwner && PetOwner->IsPlayer()) { + if (!PetOwner->GetPet() && !PetOwner->GetCharmedPet()) + ((Player*)PetOwner)->ResetPetInfo(); + } + + // remove the spawn from the world + if (!from_death && pet->GetPetType() != PET_TYPE_CHARMED) + GetZone()->RemoveSpawn(pet); +} + +float Entity::CalculateBonusMod() { + int8 level = GetLevel(); + if (level <= 20) + return 3.0; + else if (level >= 90) + return 10.0; + else + return (level - 20) * .1 + 3.0; +} + +float Entity::CalculateDPSMultiplier(){ + float dps = GetInfoStruct()->get_dps(); + + if (dps > 0){ + if (dps <= 100) + return (dps / 100 + 1); + else if (dps <= 200) + return (((dps - 100) * .25 + 100) / 100 + 1); + else if (dps <= 300) + return (((dps - 200) * .1 + 125) / 100 + 1); + else if (dps <= 900) + return (((dps - 300) * .05 + 135) / 100 + 1); + else + return (((dps - 900) * .01 + 165) / 100 + 1); + } + return 1; +} + +void Entity::AddWard(int32 spellID, WardInfo* ward) { + if (m_wardList.count(spellID) == 0) { + m_wardList[spellID] = ward; + } +} + +WardInfo* Entity::GetWard(int32 spellID) { + WardInfo* ret = 0; + + if (m_wardList.count(spellID) > 0) + ret = m_wardList[spellID]; + + return ret; +} + +void Entity::RemoveWard(int32 spellID) { + if (m_wardList.count(spellID) > 0) { + // Delete the ward info + safe_delete(m_wardList[spellID]); + // Remove from the ward list + m_wardList.erase(spellID); + } +} + +int32 Entity::CheckWards(Entity* attacker, int32 damage, int8 damage_type) { + map::iterator itr; + WardInfo* ward = 0; + LuaSpell* spell = 0; + + while (m_wardList.size() > 0 && damage > 0) { + // Get the ward with the lowest base damage + for (itr = m_wardList.begin(); itr != m_wardList.end(); itr++) { + if(itr->second->RoundTriggered) + continue; + + if (!ward || itr->second->BaseDamage < ward->BaseDamage) { + if ((itr->second->AbsorbAllDamage || itr->second->DamageLeft > 0) && + (itr->second->WardType == WARD_TYPE_ALL || + (itr->second->WardType == WARD_TYPE_PHYSICAL && damage_type >= DAMAGE_PACKET_DAMAGE_TYPE_SLASH && damage_type <= DAMAGE_PACKET_DAMAGE_TYPE_PIERCE) || + (itr->second->WardType == WARD_TYPE_MAGICAL && ((itr->second->DamageType == 0 && damage_type >= DAMAGE_PACKET_DAMAGE_TYPE_PIERCE) || (damage_type >= DAMAGE_PACKET_DAMAGE_TYPE_PIERCE && itr->second->DamageType == damage_type))))) + ward = itr->second; + } + } + + if (!ward) + break; + + spell = ward->Spell; + + // damage to redirect at the source (like intercept) + int32 redirectDamage = 0; + if (ward->RedirectDamagePercent) + redirectDamage = (int32)(double)damage * ((double)ward->RedirectDamagePercent / 100.0); + + // percentage the spell absorbs of all possible damage + int32 damageToAbsorb = 0; + if (ward->DamageAbsorptionPercentage > 0) + damageToAbsorb = (int32)(double)damage * ((double)ward->DamageAbsorptionPercentage/100.0); + else + damageToAbsorb = damage; + + int32 maxDamageAbsorptionAllowed = 0; + + // spells like Divine Aura have caps on health, eg. anything more than 50% damage is not absorbed + if (ward->DamageAbsorptionMaxHealthPercent > 0) + maxDamageAbsorptionAllowed = (int32)(double)GetTotalHP() * ((double)ward->DamageAbsorptionMaxHealthPercent / 100.0); + + if (maxDamageAbsorptionAllowed > 0 && damageToAbsorb >= maxDamageAbsorptionAllowed) + damageToAbsorb = 0; // its over or equal to 50% of the total hp allowed, thus this damage is not absorbed + + int32 baseDamageRemaining = damage - damageToAbsorb; + + bool hasSpellBeenRemoved = false; + if (ward->AbsorbAllDamage) + { + ward->LastAbsorbedDamage = ward->DamageLeft; + + if (!redirectDamage) + GetZone()->SendHealPacket(ward->Spell->caster, this, HEAL_PACKET_TYPE_ABSORB, damage, spell->spell->GetName()); + + damage = 0; + } + else if (damageToAbsorb >= ward->DamageLeft) { + // Damage is greater than or equal to the amount left on the ward + + ward->LastAbsorbedDamage = ward->DamageLeft; + // remove what damage we can absorb + damageToAbsorb -= ward->DamageLeft; + + // move back what couldn't be absorbed to the base dmg and apply to the overall damage + baseDamageRemaining += damageToAbsorb; + damage = baseDamageRemaining; + ward->DamageLeft = 0; + spell->damage_remaining = 0; + + if(!redirectDamage) + GetZone()->SendHealPacket(spell->caster, this, HEAL_PACKET_TYPE_ABSORB, ward->DamageLeft, spell->spell->GetName()); + + if (!ward->keepWard) { + hasSpellBeenRemoved = true; + RemoveWard(spell->spell->GetSpellID()); + GetZone()->GetSpellProcess()->DeleteCasterSpell(spell, "purged"); + } + } + else { + ward->LastAbsorbedDamage = damageToAbsorb; + // Damage is less then the amount left on the ward + ward->DamageLeft -= damageToAbsorb; + + spell->damage_remaining = ward->DamageLeft; + if (spell->caster->IsPlayer()) + ClientPacketFunctions::SendMaintainedExamineUpdate(((Player*)spell->caster)->GetClient(), spell->slot_pos, ward->DamageLeft, 1); + + if (!redirectDamage) + GetZone()->SendHealPacket(ward->Spell->caster, this, HEAL_PACKET_TYPE_ABSORB, damage, spell->spell->GetName()); + + // remaining damage not absorbed by percentage must be set + damage = baseDamageRemaining; + } + + if (redirectDamage) + { + ward->LastRedirectDamage = redirectDamage; + if (this->IsPlayer()) + { + Client* client = this->GetClient(); + if(client) { + client->Message(CHANNEL_COMBAT, "%s intercepted some of the damage intended for you!", spell->caster->GetName()); + } + } + if (spell->caster && spell->caster->IsPlayer()) + { + Client* client = ((Player*)spell->caster)->GetClient(); + if(client) { + client->Message(CHANNEL_COMBAT, "YOU intercept some of the damage intended for %s!", this->GetName()); + } + } + + if (attacker && spell->caster) + attacker->DamageSpawn(spell->caster, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, damage_type, redirectDamage, redirectDamage, 0, 0, false, false, false, false, spell); + } + + bool shouldRemoveSpell = false; + ward->HitCount++; // increment hit count + ward->RoundTriggered = true; + + if (ward->MaxHitCount && spell->num_triggers && spell->caster->GetZone()) + { + spell->num_triggers--; + if(spell->caster->IsPlayer()) { + ClientPacketFunctions::SendMaintainedExamineUpdate(((Player*)spell->caster)->GetClient(), spell->slot_pos, spell->num_triggers, 0); + } + } + + if (ward->MaxHitCount && ward->HitCount >= ward->MaxHitCount) // there isn't a max hit requirement with the hit count, so just go based on hit count + shouldRemoveSpell = true; + + if (shouldRemoveSpell && !hasSpellBeenRemoved) + { + RemoveWard(spell->spell->GetSpellID()); + GetZone()->GetSpellProcess()->DeleteCasterSpell(spell, "purged"); + } + + // Reset ward pointer + ward = 0; + } + + for (itr = m_wardList.begin(); itr != m_wardList.end(); itr++) { + itr->second->RoundTriggered = false; + } + + return damage; +} + +float Entity::CalculateCastingSpeedMod() { + float cast_speed = info_struct.get_casting_speed(); + + if(cast_speed > 0) + return 100 * max((float) 0.5, (float) (1 + (1 - (1 / (1 + (cast_speed * .01)))))); + else if (cast_speed < 0) + return 100 * min((float) 1.5, (float) (1 + (1 - (1 / (1 + (cast_speed * -.01)))))); + return 0; +} + +float Entity::GetSpeed() { + float ret = speed > GetBaseSpeed() ? speed : GetBaseSpeed(); + if(IsPlayer()) { + ret = GetBaseSpeed(); + } + MStats.lock(); + + if ((IsStealthed() || IsInvis()) && stats.count(ITEM_STAT_STEALTHINVISSPEEDMOD)) { + ret += stats[ITEM_STAT_STEALTHINVISSPEEDMOD]; + } + + if (!GetInfoStruct()->get_engaged_encounter()) { + if (stats.count(ITEM_STAT_SPEED) && stats.count(ITEM_STAT_MOUNTSPEED)) { + ret += max(stats[ITEM_STAT_SPEED], stats[ITEM_STAT_MOUNTSPEED]); + } + else if (stats.count(ITEM_STAT_SPEED)) { + ret += stats[ITEM_STAT_SPEED]; + } + else if (stats.count(ITEM_STAT_MOUNTSPEED)) { + ret += stats[ITEM_STAT_MOUNTSPEED]; + } + } + else if (GetInfoStruct()->get_engaged_encounter()) { + + if (GetMaxSpeed() > 0.0f) + ret = GetMaxSpeed(); + + if (stats.count(ITEM_STAT_OFFENSIVESPEED)) { + ret += stats[ITEM_STAT_OFFENSIVESPEED]; + } + } + + MStats.unlock(); + ret *= speed_multiplier; + return ret; +} + +float Entity::GetAirSpeed() { + float ret = speed; + + if (!GetInfoStruct()->get_engaged_encounter()) + ret += stats[ITEM_STAT_MOUNTAIRSPEED]; + + ret *= speed_multiplier; + return ret; +} + +void Entity::SetThreatTransfer(ThreatTransfer* transfer) { + safe_delete(m_threatTransfer); + m_threatTransfer = transfer; +} +int8 Entity::GetTraumaCount() { + return det_count_list[DET_TYPE_TRAUMA]; +} + +int8 Entity::GetArcaneCount() { + return det_count_list[DET_TYPE_ARCANE]; +} + +int8 Entity::GetNoxiousCount() { + return det_count_list[DET_TYPE_NOXIOUS]; +} + +int8 Entity::GetElementalCount() { + return det_count_list[DET_TYPE_ELEMENTAL]; +} + +int8 Entity::GetCurseCount() { + return det_count_list[DET_TYPE_CURSE]; +} + +Mutex* Entity::GetDetrimentMutex() { + return &MDetriments; +} + +Mutex* Entity::GetMaintainedMutex() { + return &MMaintainedSpells; +} + +Mutex* Entity::GetSpellEffectMutex() { + return &MSpellEffects; +} + +bool Entity::HasCurableDetrimentType(int8 det_type) { + DetrimentalEffects* det; + bool ret = false; + MDetriments.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < detrimental_spell_effects.size(); i++){ + det = &detrimental_spell_effects.at(i); + if(det && det->det_type == det_type && !det->incurable){ + ret = true; + break; + } + } + MDetriments.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +void Entity::ClearAllDetriments() { + MDetriments.writelock(__FUNCTION__, __LINE__); + detrimental_spell_effects.clear(); + det_count_list.clear(); + MDetriments.releasewritelock(__FUNCTION__, __LINE__); +} + +void Entity::CureDetrimentByType(int8 cure_count, int8 det_type, string cure_name, Entity* caster, int8 cure_level) { + if (cure_count <= 0 || GetDetTypeCount(det_type) <= 0) + return; + + vector* det_list = &detrimental_spell_effects; + DetrimentalEffects* det; + vector remove_list; + LuaSpell* spell = 0; + vector* levels; + int8 caster_class1 = 0; + int8 caster_class2 = 0; + int8 caster_class3 = 0; + InfoStruct* info_struct = 0; + bool pass_level_check = false; + + MDetriments.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; isize(); i++){ + det = &det_list->at(i); + if (det && det->det_type == det_type && !det->incurable){ + levels = det->spell->spell->GetSpellLevels(); + info_struct = det->caster->GetInfoStruct(); + pass_level_check = false; + bool has_level_checks = false; + for (int32 x = 0; x < levels->size(); x++){ + has_level_checks = true; + // class checks are worthless we can't guarantee the caster is that class + if (!cure_level || cure_level >= (levels->at(x)->spell_level / 10)){ + pass_level_check = true; + break; + } + } + + if (pass_level_check || !has_level_checks){ + remove_list.push_back(det->spell); + cure_count--; + if (cure_count == 0) + break; + } + } + } + MDetriments.releasereadlock(__FUNCTION__, __LINE__); + + for (int32 i = 0; ispell->GetName()); + GetZone()->SendDispellPacket(caster, this, cure_name, (string)remove_list.at(i)->spell->GetName(), DISPELL_TYPE_CURE); + GetZone()->GetSpellProcess()->DeleteCasterSpell(spell, "cured", false, this); + } + remove_list.clear(); +} + +void Entity::CureDetrimentByControlEffect(int8 cure_count, int8 control_type, string cure_name, Entity* caster, int8 cure_level) { + if (cure_count <= 0 || GetDetCount() <= 0) + return; + + vector* det_list = &detrimental_spell_effects; + DetrimentalEffects* det; + vector remove_list; + LuaSpell* spell = 0; + vector* levels; + int8 caster_class1 = 0; + int8 caster_class2 = 0; + int8 caster_class3 = 0; + InfoStruct* info_struct = 0; + bool pass_level_check = false; + + MDetriments.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; isize(); i++){ + det = &det_list->at(i); + if (det && det->control_effect == control_type && !det->incurable){ + levels = det->spell->spell->GetSpellLevels(); + info_struct = det->caster->GetInfoStruct(); + pass_level_check = false; + for (int32 x = 0; x < levels->size(); x++){ + if (!cure_level || cure_level >= (levels->at(x)->spell_level / 10)){ + pass_level_check = true; + break; + } + } + if (pass_level_check){ + remove_list.push_back(det->spell); + cure_count--; + if (cure_count == 0) + break; + } + } + } + MDetriments.releasereadlock(__FUNCTION__, __LINE__); + + for (int32 i = 0; iSendDispellPacket(caster, this, cure_name, (string)remove_list.at(i)->spell->GetName(), DISPELL_TYPE_CURE); + if (GetZone()) + GetZone()->RemoveTargetFromSpell(spell, this); + RemoveSpellEffect(spell); + RemoveDetrimentalSpell(spell); + } + remove_list.clear(); +} + +void Entity::RemoveDetrimentalSpell(LuaSpell* spell) { + if(!spell || (spell->spell && spell->spell->GetSpellData() && spell->spell->GetSpellData()->det_type == 0)) + return; + MDetriments.writelock(__FUNCTION__, __LINE__); + vector* det_list = &detrimental_spell_effects; + vector::iterator itr; + for(itr = det_list->begin(); itr != det_list->end(); itr++){ + if((*itr).spell == spell){ + det_count_list[(*itr).det_type]--; + det_list->erase(itr); + if(IsPlayer()) + ((Player*)this)->SetCharSheetChanged(true); + break; + } + } + MDetriments.releasewritelock(__FUNCTION__, __LINE__); +} + +int8 Entity::GetDetTypeCount(int8 det_type){ + return det_count_list[det_type]; +} + +int8 Entity::GetDetCount() { + int8 det_count = 0; + map::iterator itr; + + for(itr=det_count_list.begin(); itr != det_count_list.end(); itr++) + det_count += (*itr).second; + + return det_count; +} + +vector* Entity::GetDetrimentalSpellEffects() { + return &detrimental_spell_effects; +} + +void Entity::AddDetrimentalSpell(LuaSpell* luaspell, int32 override_expire_timestamp){ + if(!luaspell || !luaspell->caster) + return; + + Spell* spell = luaspell->spell; + DetrimentalEffects* det = GetDetrimentalEffect(spell->GetSpellID(), luaspell->caster); + DetrimentalEffects new_det; + if(det) + RemoveDetrimentalSpell(det->spell); + + SpellData* data = spell->GetSpellData(); + if(!data) + return; + + new_det.caster = luaspell->caster; + new_det.spell = luaspell; + if (spell->GetSpellData()->duration_until_cancel) + new_det.expire_timestamp = 0xFFFFFFFF; + else if(override_expire_timestamp) + new_det.expire_timestamp = override_expire_timestamp; + else + new_det.expire_timestamp = Timer::GetCurrentTime2() + (spell->GetSpellDuration()*100); + new_det.icon = data->icon; + new_det.icon_backdrop = data->icon_backdrop; + new_det.tier = data->tier; + new_det.det_type = data->det_type; + new_det.incurable = data->incurable; + new_det.spell_id = spell->GetSpellID(); + new_det.control_effect = data->control_effect_type; + new_det.total_time = spell->GetSpellDuration()/10; + + MDetriments.writelock(__FUNCTION__, __LINE__); + detrimental_spell_effects.push_back(new_det); + det_count_list[new_det.det_type]++; + MDetriments.releasewritelock(__FUNCTION__, __LINE__); +} + +DetrimentalEffects* Entity::GetDetrimentalEffect(int32 spell_id, Entity* caster){ + vector* det_list = &detrimental_spell_effects; + DetrimentalEffects* ret = 0; + MDetriments.readlock(__FUNCTION__, __LINE__); + for(int32 i=0; isize(); i++){ + if (det_list->at(i).spell_id == spell_id && det_list->at(i).caster == caster) + ret = &det_list->at(i); + } + MDetriments.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +void Entity::CancelAllStealth() { + bool did_change = false; + MutexList* stealth_list = control_effects[CONTROL_EFFECT_TYPE_STEALTH]; + if (stealth_list){ + MutexList::iterator itr = stealth_list->begin(); + while (itr.Next()){ + if (itr.value->caster == this) + GetZone()->GetSpellProcess()->AddSpellCancel(itr.value); + else{ + GetZone()->RemoveTargetFromSpell(itr.value, this); + RemoveSpellEffect(itr.value); + } + did_change = true; + } + } + MutexList* invis_list = control_effects[CONTROL_EFFECT_TYPE_INVIS]; + if (invis_list){ + MutexList::iterator invis_itr = invis_list->begin(); + while (invis_itr.Next()){ + if (invis_itr.value->caster == this) + GetZone()->GetSpellProcess()->AddSpellCancel(invis_itr.value); + else{ + GetZone()->RemoveTargetFromSpell(invis_itr.value, this); + RemoveSpellEffect(invis_itr.value); + } + did_change = true; + } + } + + if (did_change){ + info_changed = true; + changed = true; + AddChangedZoneSpawn(); + if (IsPlayer()) + ((Player*)this)->SetCharSheetChanged(true); + } +} + +bool Entity::IsStealthed(){ + MutexList* stealth_list = control_effects[CONTROL_EFFECT_TYPE_STEALTH]; + return (!stealth_list || stealth_list->size(true) == 0) == false; +} + +bool Entity::CanSeeInvis(Entity* target) { + if (!target) + return true; + + if (!target->IsStealthed() && !target->IsInvis()) + return true; + if (target->IsStealthed() && HasSeeHideSpell()) + return true; + else if (target->IsInvis() && HasSeeInvisSpell()) + return true; + + return false; +} + +bool Entity::IsInvis(){ + MutexList* invis_list = control_effects[CONTROL_EFFECT_TYPE_INVIS]; + return (!invis_list || invis_list->size(true) == 0) == false; +} + +void Entity::AddStealthSpell(LuaSpell* spell) { + if (!spell) + return; + + if (!control_effects[CONTROL_EFFECT_TYPE_STEALTH]) + control_effects[CONTROL_EFFECT_TYPE_STEALTH] = new MutexList; + + control_effects[CONTROL_EFFECT_TYPE_STEALTH]->Add(spell); + if (control_effects[CONTROL_EFFECT_TYPE_STEALTH]->size(true) == 1){ + info_changed = true; + changed = true; + AddChangedZoneSpawn(); + if (IsPlayer() && ((Player*)this)->GetClient()) + { + ((Player*)this)->SetCharSheetChanged(true); + GetZone()->SendAllSpawnsForVisChange(((Player*)this)->GetClient()); + } + } +} + +void Entity::AddInvisSpell(LuaSpell* spell) { + if (!spell) + return; + + if (!control_effects[CONTROL_EFFECT_TYPE_INVIS]) + control_effects[CONTROL_EFFECT_TYPE_INVIS] = new MutexList; + + control_effects[CONTROL_EFFECT_TYPE_INVIS]->Add(spell); + if (control_effects[CONTROL_EFFECT_TYPE_INVIS]->size(true) == 1){ + info_changed = true; + changed = true; + AddChangedZoneSpawn(); + if (IsPlayer() && ((Player*)this)->GetClient()) + { + ((Player*)this)->SetCharSheetChanged(true); + GetZone()->SendAllSpawnsForVisChange(((Player*)this)->GetClient()); + } + } +} + +void Entity::RemoveInvisSpell(LuaSpell* spell) { + MutexList* invis_list = control_effects[CONTROL_EFFECT_TYPE_INVIS]; + if (!invis_list || invis_list->size(true) == 0) + return; + + invis_list->Remove(spell); + RemoveSpellEffect(spell); + if (invis_list->size(true) == 0){ + info_changed = true; + changed = true; + AddChangedZoneSpawn(); + if (IsPlayer() && ((Player*)this)->GetClient()) + { + ((Player*)this)->SetCharSheetChanged(true); + GetZone()->SendAllSpawnsForVisChange(((Player*)this)->GetClient()); + } + } +} + +void Entity::RemoveStealthSpell(LuaSpell* spell) { + MutexList* stealth_list = control_effects[CONTROL_EFFECT_TYPE_STEALTH]; + if (!stealth_list || stealth_list->size(true) == 0) + return; + + stealth_list->Remove(spell); + RemoveSpellEffect(spell); + if (stealth_list->size() == 0){ + info_changed = true; + changed = true; + AddChangedZoneSpawn(); + if (IsPlayer() && ((Player*)this)->GetClient()) + { + ((Player*)this)->SetCharSheetChanged(true); + GetZone()->SendAllSpawnsForVisChange(((Player*)this)->GetClient()); + } + } +} + +void Entity::AddRootSpell(LuaSpell* spell) { + if (!spell) + return; + + if (!control_effects[CONTROL_EFFECT_TYPE_ROOT]) + control_effects[CONTROL_EFFECT_TYPE_ROOT] = new MutexList; + + if (control_effects[CONTROL_EFFECT_TYPE_ROOT]->size(true) == 0 && !IsRootImmune()) { + if (IsPlayer()){ + if (!IsMezzedOrStunned()) + ((Player*)this)->SetPlayerControlFlag(1, 8, true); // heading movement only + } + else + SetSpeedMultiplier(0.0f); + } + + control_effects[CONTROL_EFFECT_TYPE_ROOT]->Add(spell); +} + +void Entity::RemoveRootSpell(LuaSpell* spell) { + MutexList* root_list = control_effects[CONTROL_EFFECT_TYPE_ROOT]; + if (!root_list || root_list->size(true) == 0) + return; + + root_list->Remove(spell); + if (root_list->size(true) == 0 && !IsRootImmune()) { + if (IsPlayer()){ + if (!IsMezzedOrStunned()) + ((Player*)this)->SetPlayerControlFlag(1, 8, false); // heading movement only + } + else { + // GetHighestSnare() will return 1.0f if no snares returning the spawn to full speed + SetSpeedMultiplier(GetHighestSnare()); + } + + if(!IsPlayer()) { + GetZone()->movementMgr->StopNavigation((Entity*)this); + ((Spawn*)this)->StopMovement(); + } + } +} + +void Entity::AddFearSpell(LuaSpell* spell){ + if (!spell) + return; + + if (!control_effects[CONTROL_EFFECT_TYPE_FEAR]) + control_effects[CONTROL_EFFECT_TYPE_FEAR] = new MutexList; + + if (IsPlayer() && control_effects[CONTROL_EFFECT_TYPE_FEAR]->size(true) == 0 && !IsFearImmune()){ + ((Player*)this)->SetPlayerControlFlag(4, 4, true); // feared + if (!IsMezzedOrStunned() && !IsStifled()) + GetZone()->LockAllSpells((Player*)this); + } + + if (!IsFearImmune() && IsNPC()) + { + HaltMovement(); + } + + control_effects[CONTROL_EFFECT_TYPE_FEAR]->Add(spell); +} + +void Entity::RemoveFearSpell(LuaSpell* spell){ + MutexList* fear_list = control_effects[CONTROL_EFFECT_TYPE_FEAR]; + if (!fear_list || fear_list->size(true) == 0) + return; + + fear_list->Remove(spell); + + if (IsPlayer() && fear_list->size(true) == 0 && !IsFearImmune()){ + ((Player*)this)->SetPlayerControlFlag(4, 4, false); // feared disabled + if (!IsMezzedOrStunned() && !IsStifled()) + GetZone()->LockAllSpells((Player*)this); + } + + if (IsNPC()) + { + HaltMovement(); + } +} + +void Entity::AddSnareSpell(LuaSpell* spell) { + if (!spell) + return; + + if (!control_effects[CONTROL_EFFECT_TYPE_SNARE]) + control_effects[CONTROL_EFFECT_TYPE_SNARE] = new MutexList; + + control_effects[CONTROL_EFFECT_TYPE_SNARE]->Add(spell); + + // Don't set speed multiplier if there is a root or no snare values + MutexList* roots = control_effects[CONTROL_EFFECT_TYPE_ROOT]; + if ((!roots || roots->size(true) == 0) && snare_values.size() > 0) + SetSpeedMultiplier(GetHighestSnare()); +} + +void Entity::RemoveSnareSpell(LuaSpell* spell) { + MutexList* snare_list = control_effects[CONTROL_EFFECT_TYPE_SNARE]; + if (!snare_list || snare_list->size(true) == 0) + return; + + snare_list->Remove(spell); + snare_values.erase(spell); + + //LogWrite(PLAYER__ERROR, 0, "Debug", "snare_values.size() = %u", snare_values.size()); + + // only change speeds if there are no roots + MutexList* roots = control_effects[CONTROL_EFFECT_TYPE_ROOT]; + if (!roots || roots->size(true) == 0) { + float multiplier = GetHighestSnare(); + //LogWrite(PLAYER__ERROR, 0, "Debug", "GetHighestSnare() = %f", multiplier); + SetSpeedMultiplier(multiplier); + } +} + +void Entity::SetSnareValue(LuaSpell* spell, float snare_val) { + if (!spell) + return; + + snare_values[spell] = snare_val; +} + +float Entity::GetHighestSnare() { + // For simplicity this will return the highest snare value, which is actually the lowest value + float ret = 1.0f; + float weight_diff = 0.0f; + if (IsPlayer() && rule_manager.GetGlobalRule(R_Player, WeightInflictsSpeed)->GetBool()) { + float weight_pct_impact = rule_manager.GetGlobalRule(R_Player, WeightPercentImpact)->GetFloat(); + float weight_pct_cap = rule_manager.GetGlobalRule(R_Player, WeightPercentCap)->GetFloat(); + if(weight_pct_impact > 1.0f) { + weight_pct_impact = 1.0f; + } + if(weight_pct_cap < weight_pct_impact) { + weight_pct_impact = weight_pct_cap; + } + int32 weight = GetInfoStruct()->get_weight(); + int32 max_weight = GetInfoStruct()->get_max_weight(); + if(weight > max_weight) { + int32 diff = weight - max_weight; + weight_diff = (float)diff * weight_pct_impact; // percentage impact rule on weight "per stone", default 1% + if(weight_diff > weight_pct_cap) // cap weight impact rule + weight_diff = weight_pct_cap; // cap weight impact rule + } + } + if (snare_values.size() == 0) + return ((ret - weight_diff) < 0.0f ) ? 0.0f : (ret - weight_diff); + + map::iterator itr; + for (itr = snare_values.begin(); itr != snare_values.end(); itr++) { + if (itr->second < ret) + ret = itr->second; + } + + return ((ret - weight_diff) < 0.0f ) ? 0.0f : (ret - weight_diff); +} + +bool Entity::IsSnared() { + if (control_effects.size() < 1 || !control_effects[CONTROL_EFFECT_TYPE_SNARE]) + return false; + + MutexList* snare_list = control_effects[CONTROL_EFFECT_TYPE_SNARE]; + return (!snare_list || snare_list->size(true) == 0) == false; +} + +bool Entity::IsMezzed(){ + if (control_effects.size() < 1 || !control_effects[CONTROL_EFFECT_TYPE_MEZ]) + return false; + + MutexList* mez_spells = control_effects[CONTROL_EFFECT_TYPE_MEZ]; + return (!mez_spells || mez_spells->size(true) == 0 || IsMezImmune()) == false; +} + +bool Entity::IsStifled(){ + if (!control_effects[CONTROL_EFFECT_TYPE_STIFLE]) + return false; + + MutexList* stifle_list = control_effects[CONTROL_EFFECT_TYPE_STIFLE]; + return (!stifle_list || stifle_list->size(true) == 0 || IsStifleImmune()) == false; +} + +bool Entity::IsDazed(){ + if (control_effects.size() < 1 || !control_effects[CONTROL_EFFECT_TYPE_DAZE]) + return false; + + MutexList* daze_list = control_effects[CONTROL_EFFECT_TYPE_DAZE]; + return (!daze_list || daze_list->size(true) == 0 || IsDazeImmune()) == false; +} + +bool Entity::IsStunned(){ + if (!control_effects[CONTROL_EFFECT_TYPE_STUN]) + return false; + + MutexList* stun_list = control_effects[CONTROL_EFFECT_TYPE_STUN]; + return (!stun_list || stun_list->size(true) == 0 || IsStunImmune()) == false; +} + +bool Entity::IsRooted(){ + if (control_effects.size() < 1 || !control_effects[CONTROL_EFFECT_TYPE_ROOT]) + return false; + + MutexList* root_list = control_effects[CONTROL_EFFECT_TYPE_ROOT]; + return (!root_list || root_list->size(true) == 0 || IsRootImmune()) == false; +} + +bool Entity::IsFeared(){ + if (control_effects.size() < 1 || !control_effects[CONTROL_EFFECT_TYPE_FEAR]) + return false; + + MutexList* fear_list = control_effects[CONTROL_EFFECT_TYPE_FEAR]; + return (!fear_list || fear_list->size(true) == 0 || IsFearImmune()) == false; +} + +void Entity::AddWaterwalkSpell(LuaSpell* spell){ + if (!spell) + return; + + if (!control_effects[CONTROL_EFFECT_TYPE_WALKUNDERWATER]) + control_effects[CONTROL_EFFECT_TYPE_WALKUNDERWATER] = new MutexList; + + control_effects[CONTROL_EFFECT_TYPE_WALKUNDERWATER]->Add(spell); + if (control_effects[CONTROL_EFFECT_TYPE_WALKUNDERWATER]->size(true) == 1 && IsPlayer()) + ((Player*)this)->SetPlayerControlFlag(3, 128, true); // enable walking underwater +} + +void Entity::RemoveWaterwalkSpell(LuaSpell* spell){ + MutexList* waterwalk_list = control_effects[CONTROL_EFFECT_TYPE_WALKUNDERWATER]; + if (!waterwalk_list || waterwalk_list->size(true) == 0) + return; + + waterwalk_list->Remove(spell); + if (waterwalk_list->size(true) == 0 && IsPlayer()) + ((Player*)this)->SetPlayerControlFlag(3, 128, false); // disable walking underwater +} + +void Entity::AddWaterjumpSpell(LuaSpell* spell){ + if (!spell) + return; + + if (!control_effects[CONTROL_EFFECT_TYPE_JUMPUNDERWATER]) + control_effects[CONTROL_EFFECT_TYPE_JUMPUNDERWATER] = new MutexList; + + control_effects[CONTROL_EFFECT_TYPE_JUMPUNDERWATER]->Add(spell); + if (control_effects[CONTROL_EFFECT_TYPE_JUMPUNDERWATER]->size(true) == 1 && IsPlayer()) + ((Player*)this)->SetPlayerControlFlag(4, 1, true); // enable moonjumps underwater +} + +void Entity::RemoveWaterjumpSpell(LuaSpell* spell){ + MutexList* waterjump_list = control_effects[CONTROL_EFFECT_TYPE_JUMPUNDERWATER]; + if (!waterjump_list || waterjump_list->size(true) == 0) + return; + + waterjump_list->Remove(spell); + if (waterjump_list->size(true) == 0 && IsPlayer()) + ((Player*)this)->SetPlayerControlFlag(4, 1, false); // disable moonjumps underwater +} + +void Entity::AddAOEImmunity(LuaSpell* spell){ + if (!spell) + return; + + if (!immunities[IMMUNITY_TYPE_AOE]) + immunities[IMMUNITY_TYPE_AOE] = new MutexList; + + immunities[IMMUNITY_TYPE_AOE]->Add(spell); +} + +void Entity::RemoveAOEImmunity(LuaSpell* spell){ + MutexList* aoe_list = immunities[IMMUNITY_TYPE_AOE]; + if (!aoe_list || aoe_list->size(true) == 0) + return; + aoe_list->Remove(spell); +} + +bool Entity::IsAOEImmune(){ + return (immunities[IMMUNITY_TYPE_AOE] && immunities[IMMUNITY_TYPE_AOE]->size(true)); +} + +void Entity::AddStunImmunity(LuaSpell* spell){ + if (!spell) + return; + + if (!immunities[IMMUNITY_TYPE_STUN]) + immunities[IMMUNITY_TYPE_STUN] = new MutexList; + + if (IsPlayer() && IsStunned() && !IsMezzed()){ + ((Player*)this)->SetPlayerControlFlag(4, 64, false); + if (!IsFeared() && !IsStifled()) + ((Player*)this)->UnlockAllSpells(); + } + + immunities[IMMUNITY_TYPE_STUN]->Add(spell); +} + +void Entity::RemoveStunImmunity(LuaSpell* spell){ + MutexList* stun_list = immunities[IMMUNITY_TYPE_STUN]; + if (!stun_list || stun_list->size(true) == 0) + return; + + stun_list->Remove(spell); + + if (IsPlayer() && IsStunned() && !IsMezzed()){ + ((Player*)this)->SetPlayerControlFlag(4, 64, true); + if (!IsFeared() && !IsStifled()) + ((Player*)this)->UnlockAllSpells(); + } +} + +bool Entity::IsStunImmune(){ + return (immunities[IMMUNITY_TYPE_STUN] && immunities[IMMUNITY_TYPE_STUN]->size(true) > 0); +} + +void Entity::AddStifleImmunity(LuaSpell* spell){ + if (!spell) + return; + + if (!immunities[IMMUNITY_TYPE_STIFLE]) + immunities[IMMUNITY_TYPE_STIFLE] = new MutexList; + + if (IsPlayer() && immunities[IMMUNITY_TYPE_STIFLE]->size(true) == 0){ + if (IsStifled() && !IsMezzedOrStunned() && !IsFeared()) + ((Player*)this)->UnlockAllSpells(); + } + + immunities[IMMUNITY_TYPE_STIFLE]->Add(spell); +} + +void Entity::RemoveStifleImmunity(LuaSpell* spell){ + MutexList* stifle_list = immunities[IMMUNITY_TYPE_STIFLE]; + if (!stifle_list || stifle_list->size(true) == 0) + return; + + stifle_list->Remove(spell); + + if (IsPlayer() && IsStifled() && !IsMezzedOrStunned() && !IsFeared()) + ((Player*)this)->UnlockAllSpells(); +} + +bool Entity::IsStifleImmune(){ + return (immunities[IMMUNITY_TYPE_STIFLE] && immunities[IMMUNITY_TYPE_STIFLE]->size(true) > 0); +} + +void Entity::AddMezImmunity(LuaSpell* spell){ + if (!spell) + return; + + if (!immunities[IMMUNITY_TYPE_MEZ]) + immunities[IMMUNITY_TYPE_MEZ] = new MutexList; + + if (IsPlayer() && IsMezzed() && !IsStunned()){ + ((Player*)this)->SetPlayerControlFlag(4, 64, false); + if (!IsFeared() && !IsStifled()) + ((Player*)this)->UnlockAllSpells(); + } + + immunities[IMMUNITY_TYPE_MEZ]->Add(spell); +} + +void Entity::RemoveMezImmunity(LuaSpell* spell){ + MutexList* mez_list = immunities[IMMUNITY_TYPE_MEZ]; + if (!mez_list || mez_list->size(true) == 0) + return; + + mez_list->Remove(spell); + + if (IsPlayer() && IsMezzed() && !IsStunned()){ + ((Player*)this)->SetPlayerControlFlag(4, 64, true); + if (!IsFeared() && !IsStifled()) + ((Player*)this)->LockAllSpells(); + } +} + +bool Entity::IsMezImmune(){ + return (immunities[IMMUNITY_TYPE_MEZ] && immunities[IMMUNITY_TYPE_MEZ]->size(true) > 0); +} + +void Entity::AddRootImmunity(LuaSpell* spell){ + if (!spell) + return; + + if (!immunities[IMMUNITY_TYPE_ROOT]) + immunities[IMMUNITY_TYPE_ROOT] = new MutexList; + + if (IsPlayer() && IsRooted()) + ((Player*)this)->SetPlayerControlFlag(1, 8, false); + + immunities[IMMUNITY_TYPE_ROOT]->Add(spell); +} + +void Entity::RemoveRootImmunity(LuaSpell* spell){ + MutexList* root_list = immunities[IMMUNITY_TYPE_ROOT]; + if (!root_list || root_list->size(true) == 0) + return; + + root_list->Remove(spell); + + if (IsPlayer() && IsRooted()) + ((Player*)this)->SetPlayerControlFlag(1, 8, true); +} + +bool Entity::IsRootImmune(){ + return (immunities[IMMUNITY_TYPE_ROOT] && immunities[IMMUNITY_TYPE_ROOT]->size(true) > 0); +} + +void Entity::AddFearImmunity(LuaSpell* spell){ + if (!spell) + return; + + if (!immunities[IMMUNITY_TYPE_FEAR]) + immunities[IMMUNITY_TYPE_FEAR] = new MutexList; + + if (IsPlayer() && IsFeared()){ + if (!IsMezzedOrStunned() && !IsStifled()) + ((Player*)this)->UnlockAllSpells(); + ((Player*)this)->SetPlayerControlFlag(4, 4, false); + } + + immunities[IMMUNITY_TYPE_FEAR]->Add(spell); +} + +void Entity::RemoveFearImmunity(LuaSpell* spell){ + MutexList* fear_list = immunities[IMMUNITY_TYPE_FEAR]; + if (!fear_list || fear_list->size(true) == 0) + return; + + fear_list->Remove(spell); + + if (IsPlayer() && IsFeared()){ + if (!IsMezzedOrStunned() && !IsStifled()) + ((Player*)this)->LockAllSpells(); + ((Player*)this)->SetPlayerControlFlag(4, 4, true); + } +} + +bool Entity::IsFearImmune(){ + return (immunities[IMMUNITY_TYPE_FEAR] && immunities[IMMUNITY_TYPE_FEAR]->size(true) > 0); +} + +void Entity::AddDazeImmunity(LuaSpell* spell){ + if (!spell) + return; + + if (!immunities[IMMUNITY_TYPE_DAZE]) + immunities[IMMUNITY_TYPE_DAZE] = new MutexList; + + immunities[IMMUNITY_TYPE_DAZE]->Add(spell); +} + +void Entity::RemoveDazeImmunity(LuaSpell* spell){ + MutexList* daze_list = immunities[IMMUNITY_TYPE_DAZE]; + if (!daze_list || daze_list->size(true) == 0) + return; + + daze_list->Remove(spell); +} + +bool Entity::IsDazeImmune(){ + return (immunities[IMMUNITY_TYPE_DAZE] && immunities[IMMUNITY_TYPE_DAZE]->size(true) > 0); +} + +void Entity::AddImmunity(LuaSpell* spell, int16 type){ + if (!spell) + return; + + if (!immunities[type]) + immunities[type] = new MutexList; + + immunities[type]->Add(spell); +} + +void Entity::RemoveImmunity(LuaSpell* spell, int16 type){ + MutexList* list = immunities[type]; + if (!list || list->size(true) == 0) + return; + + list->Remove(spell); +} + +bool Entity::IsImmune(int16 type){ + return (immunities[type] && immunities[type]->size(true) > 0); +} + +void Entity::RemoveEffectsFromLuaSpell(LuaSpell* spell){ + if (!spell) + return; + + //Attempt to remove all applied effects from this spell when spell has been removed from just this target. Should improve performance/easier maitenance + int32 effect_bitmask = spell->effect_bitmask; + if (effect_bitmask == 0) + return; + + if (effect_bitmask & EFFECT_FLAG_STUN) + RemoveStunSpell(spell); + if (effect_bitmask & EFFECT_FLAG_ROOT) + RemoveRootSpell(spell); + if (effect_bitmask & EFFECT_FLAG_MEZ) + RemoveMezSpell(spell); + if (effect_bitmask & EFFECT_FLAG_STIFLE) + RemoveStifleSpell(spell); + if (effect_bitmask & EFFECT_FLAG_DAZE) + RemoveDazeSpell(spell); + if (effect_bitmask & EFFECT_FLAG_FEAR) + RemoveFearSpell(spell); + if (effect_bitmask & EFFECT_FLAG_SPELLBONUS) + RemoveSpellBonus(spell); + if (effect_bitmask & EFFECT_FLAG_SKILLBONUS) + RemoveSkillBonus(spell->spell->GetSpellID()); + if (effect_bitmask & EFFECT_FLAG_STEALTH) + RemoveStealthSpell(spell); + if (effect_bitmask & EFFECT_FLAG_INVIS) + RemoveInvisSpell(spell); + if (effect_bitmask & EFFECT_FLAG_SNARE) + RemoveSnareSpell(spell); + if (effect_bitmask & EFFECT_FLAG_WATERWALK) + RemoveWaterwalkSpell(spell); + if (effect_bitmask & EFFECT_FLAG_WATERJUMP) + RemoveWaterjumpSpell(spell); + if (effect_bitmask & EFFECT_FLAG_FLIGHT) + RemoveFlightSpell(spell); + if (effect_bitmask & EFFECT_FLAG_GLIDE) + RemoveGlideSpell(spell); + if (effect_bitmask & EFFECT_FLAG_AOE_IMMUNE) + RemoveAOEImmunity(spell); + if (effect_bitmask & EFFECT_FLAG_STUN_IMMUNE) + RemoveStunImmunity(spell); + if (effect_bitmask & EFFECT_FLAG_MEZ_IMMUNE) + RemoveMezImmunity(spell); + if (effect_bitmask & EFFECT_FLAG_DAZE_IMMUNE) + RemoveDazeImmunity(spell); + if (effect_bitmask & EFFECT_FLAG_ROOT_IMMUNE) + RemoveRootImmunity(spell); + if (effect_bitmask & EFFECT_FLAG_STIFLE_IMMUNE) + RemoveStifleImmunity(spell); + if (effect_bitmask & EFFECT_FLAG_FEAR_IMMUNE) + RemoveFearImmunity(spell); + if (effect_bitmask & EFFECT_FLAG_SAFEFALL) + RemoveSafefallSpell(spell); +} + +void Entity::RemoveSkillBonus(int32 spell_id){ + //This is a virtual, just making it so we don't have to do extra checks for player/npcs + return; +} + +void Entity::AddFlightSpell(LuaSpell* spell){ + if (!spell) + return; + + if (!control_effects[CONTROL_EFFECT_TYPE_FLIGHT]) + control_effects[CONTROL_EFFECT_TYPE_FLIGHT] = new MutexList; + + if (IsPlayer() && control_effects[CONTROL_EFFECT_TYPE_FLIGHT]->size(true) == 0) + ((Player*)this)->SetPlayerControlFlag(5, 32, true); + + control_effects[CONTROL_EFFECT_TYPE_FLIGHT]->Add(spell); +} + +void Entity::RemoveFlightSpell(LuaSpell* spell){ + MutexList* flight_list = control_effects[CONTROL_EFFECT_TYPE_FLIGHT]; + if (!flight_list || flight_list->size(true) == 0) + return; + + flight_list->Remove(spell); + if (IsPlayer() && flight_list->size(true) == 0) + ((Player*)this)->SetPlayerControlFlag(5, 32, false); +} + +void Entity::AddGlideSpell(LuaSpell* spell){ + if (!spell) + return; + + if (!control_effects[CONTROL_EFFECT_TYPE_GLIDE]) + control_effects[CONTROL_EFFECT_TYPE_GLIDE] = new MutexList; + + if (IsPlayer() && control_effects[CONTROL_EFFECT_TYPE_GLIDE]->size(true) == 0) + ((Player*)this)->SetPlayerControlFlag(4, 16, true); + + control_effects[CONTROL_EFFECT_TYPE_GLIDE]->Add(spell); +} + +void Entity::RemoveGlideSpell(LuaSpell* spell){ + MutexList* glide_list = control_effects[CONTROL_EFFECT_TYPE_GLIDE]; + if (!glide_list || glide_list->size(true) == 0) + return; + + glide_list->Remove(spell); + if (IsPlayer() && glide_list->size(true) == 0) + ((Player*)this)->SetPlayerControlFlag(4, 16, false); +} + +void Entity::AddSafefallSpell(LuaSpell* spell){ + if (!spell) + return; + + if (!control_effects[CONTROL_EFFECT_TYPE_SAFEFALL]) + control_effects[CONTROL_EFFECT_TYPE_SAFEFALL] = new MutexList; + + if (IsPlayer() && control_effects[CONTROL_EFFECT_TYPE_SAFEFALL]->size(true) == 0) + ((Player*)this)->SetPlayerControlFlag(4, 32, true); + + control_effects[CONTROL_EFFECT_TYPE_SAFEFALL]->Add(spell); +} + +void Entity::RemoveSafefallSpell(LuaSpell* spell){ + MutexList* safe_list = control_effects[CONTROL_EFFECT_TYPE_SAFEFALL]; + if (!safe_list || safe_list->size(true) == 0) + return; + + safe_list->Remove(spell); + if (IsPlayer() && safe_list->size(true) == 0) + ((Player*)this)->SetPlayerControlFlag(4, 32, false); +} + +void Entity::UpdateGroupMemberInfo(bool inGroupMgrLock, bool groupMembersLocked) { + if (!group_member_info || group_id == 0) + return; + + if(!inGroupMgrLock) + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + + PlayerGroup* group = world.GetGroupManager()->GetGroup(group_id); + + if (group) + group->UpdateGroupMemberInfo(this, groupMembersLocked); + + if(!inGroupMgrLock) + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); +} + +#include "WorldDatabase.h" +extern WorldDatabase database; +void Entity::CustomizeAppearance(PacketStruct* packet) { + + bool is_soga = packet->getType_int8_ByName("is_soga") == 1 ? true : false; + int16 model_id = database.GetAppearanceID(packet->getType_EQ2_16BitString_ByName("race_file").data); + EQ2_Color skin_color = packet->getType_EQ2_Color_ByName("skin_color"); + EQ2_Color skin_color2 = packet->getType_EQ2_Color_ByName("skin_color2"); + EQ2_Color eye_color = packet->getType_EQ2_Color_ByName("eye_color"); + EQ2_Color hair_color1 = packet->getType_EQ2_Color_ByName("hair_color1"); + EQ2_Color hair_color2 = packet->getType_EQ2_Color_ByName("hair_color2"); + EQ2_Color hair_highlight = packet->getType_EQ2_Color_ByName("hair_highlight"); + int16 hair_id = database.GetAppearanceID(packet->getType_EQ2_16BitString_ByName("hair_file").data); + EQ2_Color hair_type_color = packet->getType_EQ2_Color_ByName("hair_type_color"); + EQ2_Color hair_type_highlight_color = packet->getType_EQ2_Color_ByName("hair_type_highlight_color"); + int16 face_id = database.GetAppearanceID(packet->getType_EQ2_16BitString_ByName("face_file").data); + EQ2_Color hair_face_color = packet->getType_EQ2_Color_ByName("hair_face_color"); + EQ2_Color hair_face_highlight_color = packet->getType_EQ2_Color_ByName("hair_face_highlight_color"); + int16 wing_id = database.GetAppearanceID(packet->getType_EQ2_16BitString_ByName("wing_file").data); + EQ2_Color wing_color1 = packet->getType_EQ2_Color_ByName("wing_color1"); + EQ2_Color wing_color2 = packet->getType_EQ2_Color_ByName("wing_color2"); + int16 chest_id = database.GetAppearanceID(packet->getType_EQ2_16BitString_ByName("chest_file").data); + EQ2_Color shirt_color = packet->getType_EQ2_Color_ByName("shirt_color"); + EQ2_Color unknown_chest_color = packet->getType_EQ2_Color_ByName("unknown_chest_color"); + int16 legs_id = database.GetAppearanceID(packet->getType_EQ2_16BitString_ByName("legs_file").data); + EQ2_Color pants_color = packet->getType_EQ2_Color_ByName("pants_color"); + EQ2_Color unknown_legs_color = packet->getType_EQ2_Color_ByName("unknown_legs_color"); + EQ2_Color unknown2 = packet->getType_EQ2_Color_ByName("unknown2"); + + float eyes2[3]; + eyes2[0] = packet->getType_float_ByName("eyes2", 0) * 100; + eyes2[1] = packet->getType_float_ByName("eyes2", 1) * 100; + eyes2[2] = packet->getType_float_ByName("eyes2", 2) * 100; + + float ears[3]; + ears[0] = packet->getType_float_ByName("ears", 0) * 100; + ears[1] = packet->getType_float_ByName("ears", 1) * 100; + ears[2] = packet->getType_float_ByName("ears", 2) * 100; + + float eye_brows[3]; + eye_brows[0] = packet->getType_float_ByName("eye_brows", 0) * 100; + eye_brows[1] = packet->getType_float_ByName("eye_brows", 1) * 100; + eye_brows[2] = packet->getType_float_ByName("eye_brows", 2) * 100; + + float cheeks[3]; + cheeks[0] = packet->getType_float_ByName("cheeks", 0) * 100; + cheeks[1] = packet->getType_float_ByName("cheeks", 1) * 100; + cheeks[2] = packet->getType_float_ByName("cheeks", 2) * 100; + + float lips[3]; + lips[0] = packet->getType_float_ByName("lips", 0) * 100; + lips[1] = packet->getType_float_ByName("lips", 1) * 100; + lips[2] = packet->getType_float_ByName("lips", 2) * 100; + + float chin[3]; + chin[0] = packet->getType_float_ByName("chin", 0) * 100; + chin[1] = packet->getType_float_ByName("chin", 1) * 100; + chin[2] = packet->getType_float_ByName("chin", 2) * 100; + + float nose[3]; + nose[0] = packet->getType_float_ByName("nose", 0) * 100; + nose[1] = packet->getType_float_ByName("nose", 1) * 100; + nose[2] = packet->getType_float_ByName("nose", 2) * 100; + + sint8 body_size = (sint8)(packet->getType_float_ByName("body_size") * 100); + sint8 body_age = (sint8)(packet->getType_float_ByName("body_age") * 100); + + if (is_soga) { + appearance.soga_model_type = model_id; + features.soga_skin_color = skin_color; + features.soga_eye_color = eye_color; + features.soga_hair_color1 = hair_color1; + features.soga_hair_color2 = hair_color2; + features.soga_hair_highlight_color = hair_highlight; + features.soga_hair_type = hair_id; + features.soga_hair_type_color = hair_type_color; + features.soga_hair_type_highlight_color = hair_type_highlight_color; + features.soga_hair_face_type = face_id; + features.soga_hair_face_color = hair_face_color; + features.soga_hair_face_highlight_color = hair_face_highlight_color; + features.wing_type = wing_id; + features.wing_color1 = wing_color1; + features.wing_color2 = wing_color2; + features.soga_chest_type = chest_id; + features.shirt_color = shirt_color; + features.soga_legs_type = legs_id; + features.pants_color = pants_color; + features.soga_eye_type[0] = eyes2[0]; + features.soga_eye_type[1] = eyes2[1]; + features.soga_eye_type[2] = eyes2[2]; + features.soga_ear_type[0] = ears[0]; + features.soga_ear_type[0] = ears[1]; + features.soga_ear_type[0] = ears[2]; + features.soga_eye_brow_type[0] = eye_brows[0]; + features.soga_eye_brow_type[1] = eye_brows[1]; + features.soga_eye_brow_type[2] = eye_brows[2]; + features.soga_cheek_type[0] = cheeks[0]; + features.soga_cheek_type[1] = cheeks[1]; + features.soga_cheek_type[2] = cheeks[2]; + features.soga_lip_type[0] = lips[0]; + features.soga_lip_type[1] = lips[1]; + features.soga_lip_type[2] = lips[2]; + features.soga_chin_type[0] = chin[0]; + features.soga_chin_type[1] = chin[1]; + features.soga_chin_type[2] = chin[2]; + features.soga_nose_type[0] = nose[0]; + features.soga_nose_type[1] = nose[1]; + features.soga_nose_type[2] = nose[2]; + } + else { + appearance.model_type = model_id; + features.skin_color = skin_color; + features.eye_color = eye_color; + features.hair_color1 = hair_color1; + features.hair_color2 = hair_color2; + features.hair_highlight_color = hair_highlight; + features.hair_type = hair_id; + features.hair_type_color = hair_type_color; + features.hair_type_highlight_color = hair_type_highlight_color; + features.hair_face_type = face_id; + features.hair_face_color = hair_face_color; + features.hair_face_highlight_color = hair_face_highlight_color; + features.wing_type = wing_id; + features.wing_color1 = wing_color1; + features.wing_color2 = wing_color2; + features.chest_type = chest_id; + features.shirt_color = shirt_color; + features.legs_type = legs_id; + features.pants_color = pants_color; + features.eye_type[0] = eyes2[0]; + features.eye_type[1] = eyes2[1]; + features.eye_type[2] = eyes2[2]; + features.ear_type[0] = ears[0]; + features.ear_type[0] = ears[1]; + features.ear_type[0] = ears[2]; + features.eye_brow_type[0] = eye_brows[0]; + features.eye_brow_type[1] = eye_brows[1]; + features.eye_brow_type[2] = eye_brows[2]; + features.cheek_type[0] = cheeks[0]; + features.cheek_type[1] = cheeks[1]; + features.cheek_type[2] = cheeks[2]; + features.lip_type[0] = lips[0]; + features.lip_type[1] = lips[1]; + features.lip_type[2] = lips[2]; + features.chin_type[0] = chin[0]; + features.chin_type[1] = chin[1]; + features.chin_type[2] = chin[2]; + features.nose_type[0] = nose[0]; + features.nose_type[1] = nose[1]; + features.nose_type[2] = nose[2]; + } + + features.body_size = body_size; + features.body_age = body_age; + features.soga_body_size = body_size; + features.soga_body_age = body_age; + info_changed = true; + changed = true; +} + +void Entity::AddSkillBonus(int32 spell_id, int32 skill_id, float value) { + // handled in npc or player + return; +} + +bool Entity::HasControlEffect(int8 type) +{ + if (type >= CONTROL_MAX_EFFECTS) + return false; + + MutexList* spell_list = control_effects[type]; + if (!spell_list || spell_list->size(true) == 0) + return false; + + return true; +} + +void Entity::HaltMovement() +{ + this->ClearRunningLocations(); + + if (GetZone()) + GetZone()->movementMgr->StopNavigation(this); + + RunToLocation(GetX(), GetY(), GetZ()); +} + +std::string Entity::GetInfoStructString(std::string field) +{ + map>::const_iterator itr = get_string_funcs.find(field); + if(itr != get_string_funcs.end()) + { + auto func = (itr->second)(); + return func; + } + return std::string(""); +} + +int8 Entity::GetInfoStructInt8(std::string field) +{ + map>::const_iterator itr = get_int8_funcs.find(field); + if(itr != get_int8_funcs.end()) + { + auto func = (itr->second)(); + return func; + } + return 0; +} + +int16 Entity::GetInfoStructInt16(std::string field) +{ + map>::const_iterator itr = get_int16_funcs.find(field); + if(itr != get_int16_funcs.end()) + { + auto func = (itr->second)(); + return func; + } + return 0; +} + +int32 Entity::GetInfoStructInt32(std::string field) +{ + map>::const_iterator itr = get_int32_funcs.find(field); + if(itr != get_int32_funcs.end()) + { + auto func = (itr->second)(); + return func; + } + return 0; +} + +int64 Entity::GetInfoStructInt64(std::string field) +{ + map>::const_iterator itr = get_int64_funcs.find(field); + if(itr != get_int64_funcs.end()) + { + auto func = (itr->second)(); + return func; + } + return 0; +} + +sint8 Entity::GetInfoStructSInt8(std::string field) +{ + map>::const_iterator itr = get_sint8_funcs.find(field); + if(itr != get_sint8_funcs.end()) + { + auto func = (itr->second)(); + return func; + } + return 0; +} + +sint16 Entity::GetInfoStructSInt16(std::string field) +{ + map>::const_iterator itr = get_sint16_funcs.find(field); + if(itr != get_sint16_funcs.end()) + { + auto func = (itr->second)(); + return func; + } + return 0; +} + +sint32 Entity::GetInfoStructSInt32(std::string field) +{ + map>::const_iterator itr = get_sint32_funcs.find(field); + if(itr != get_sint32_funcs.end()) + { + auto func = (itr->second)(); + return func; + } + return 0; +} + +sint64 Entity::GetInfoStructSInt64(std::string field) +{ + map>::const_iterator itr = get_sint64_funcs.find(field); + if(itr != get_sint64_funcs.end()) + { + auto func = (itr->second)(); + return func; + } + return 0; +} + +float Entity::GetInfoStructFloat(std::string field) +{ + map>::const_iterator itr = get_float_funcs.find(field); + if(itr != get_float_funcs.end()) + { + auto func = (itr->second)(); + return func; + } + return 0.0f; +} + +int64 Entity::GetInfoStructUInt(std::string field) +{ + map>::const_iterator itr = get_int8_funcs.find(field); + if(itr != get_int8_funcs.end()) + { + auto func = (itr->second)(); + return func; + } + map>::const_iterator itr2 = get_int16_funcs.find(field); + if(itr2 != get_int16_funcs.end()) + { + auto func = (itr2->second)(); + return func; + } + map>::const_iterator itr3 = get_int32_funcs.find(field); + if(itr3 != get_int32_funcs.end()) + { + auto func = (itr3->second)(); + return func; + } + map>::const_iterator itr4 = get_int64_funcs.find(field); + if(itr4 != get_int64_funcs.end()) + { + auto func = (itr4->second)(); + return func; + } + return 0; +} + +sint64 Entity::GetInfoStructSInt(std::string field) +{ + map>::const_iterator itr = get_sint8_funcs.find(field); + if(itr != get_sint8_funcs.end()) + { + auto func = (itr->second)(); + return func; + } + map>::const_iterator itr2 = get_sint16_funcs.find(field); + if(itr2 != get_sint16_funcs.end()) + { + auto func = (itr2->second)(); + return func; + } + map>::const_iterator itr3 = get_sint32_funcs.find(field); + if(itr3 != get_sint32_funcs.end()) + { + auto func = (itr3->second)(); + return func; + } + map>::const_iterator itr4 = get_sint64_funcs.find(field); + if(itr4 != get_sint64_funcs.end()) + { + auto func = (itr4->second)(); + return func; + } + return 0; +} + + +bool Entity::SetInfoStructString(std::string field, std::string value) +{ + map>::const_iterator itr = set_string_funcs.find(field); + if(itr != set_string_funcs.end()) + { + (itr->second)(value); + return true; + } + return false; +} + + +bool Entity::SetInfoStructUInt(std::string field, int64 value) +{ + map>::const_iterator itr = set_int8_funcs.find(field); + if(itr != set_int8_funcs.end()) + { + (itr->second)((int8)value); + return true; + } + map>::const_iterator itr2 = set_int16_funcs.find(field); + if(itr2 != set_int16_funcs.end()) + { + (itr2->second)((int16)value); + return true; + } + map>::const_iterator itr3 = set_int32_funcs.find(field); + if(itr3 != set_int32_funcs.end()) + { + (itr3->second)((int32)value); + return true; + } + map>::const_iterator itr4 = set_int64_funcs.find(field); + if(itr4 != set_int64_funcs.end()) + { + (itr4->second)(value); + return true; + } + return false; +} + +bool Entity::SetInfoStructSInt(std::string field, sint64 value) +{ + map>::const_iterator itr = set_sint8_funcs.find(field); + if(itr != set_sint8_funcs.end()) + { + (itr->second)((sint8)value); + return true; + } + map>::const_iterator itr2 = set_sint16_funcs.find(field); + if(itr2 != set_sint16_funcs.end()) + { + (itr2->second)((sint16)value); + return true; + } + map>::const_iterator itr3 = set_sint32_funcs.find(field); + if(itr3 != set_sint32_funcs.end()) + { + (itr3->second)((sint32)value); + return true; + } + map>::const_iterator itr4 = set_sint64_funcs.find(field); + if(itr4 != set_sint64_funcs.end()) + { + (itr4->second)(value); + return true; + } + return false; +} + +bool Entity::SetInfoStructFloat(std::string field, float value) +{ + map>::const_iterator itr = set_float_funcs.find(field); + if(itr != set_float_funcs.end()) + { + (itr->second)(value); + return true; + } + return false; +} + +Entity* Entity::GetOwner() { + Entity* ent = nullptr; + + if(!GetZone()) { + return ent; + } + Spawn* spawn = GetZone()->GetSpawnByID(owner); + if ( spawn && spawn->IsEntity() ) + ent = (Entity*)spawn; + + return ent; +} + +bool Entity::IsEngagedInEncounter(Spawn** res) { + if(res) { + *res = nullptr; + } + bool ret = false; + set::iterator itr; + MHatedBy.lock(); + if(IsPlayer()) { + for (itr = HatedBy.begin(); itr != HatedBy.end(); itr++) { + Spawn* spawn = GetZone()->GetSpawnByID(*itr); + if (spawn && spawn->IsNPC() && ((NPC*)spawn)->Brain() && (spawn->GetLockedNoLoot() == ENCOUNTER_STATE_LOCKED || spawn->GetLockedNoLoot() == ENCOUNTER_STATE_OVERMATCHED)) { + if((ret = ((NPC*)spawn)->Brain()->IsPlayerInEncounter(((Player*)this)->GetCharacterID()))) { + if(res) + *res = spawn; + break; + } + } + } + } + else { + for (itr = HatedBy.begin(); itr != HatedBy.end(); itr++) { + Spawn* spawn = GetZone()->GetSpawnByID(*itr); + if (spawn && spawn->IsNPC() && ((NPC*)spawn)->Brain() && (spawn->GetLockedNoLoot() == ENCOUNTER_STATE_LOCKED || spawn->GetLockedNoLoot() == ENCOUNTER_STATE_OVERMATCHED)) { + if((ret = ((NPC*)spawn)->Brain()->IsEntityInEncounter(GetID()))) { + if(res) + *res = spawn; + break; + } + } + } + + } + MHatedBy.unlock(); + + return ret; +} + +bool Entity::IsEngagedBySpawnID(int32 id) { + bool ret = false; + Spawn* spawn = GetZone()->GetSpawnByID(id); + if (spawn && spawn->IsNPC() && ((NPC*)spawn)->Brain()) { + ret = ((NPC*)spawn)->Brain()->IsEntityInEncounter(GetID()); + } + + return ret; +} + +void Entity::SendControlEffectDetailsToClient(Client* client) { + client->Message(CHANNEL_COLOR_YELLOW, "Current control effects on %s", GetName()); + client->Message(CHANNEL_COLOR_YELLOW, "-------------------------------"); + for (int i = 0; i < CONTROL_MAX_EFFECTS; i++) { + if(control_effects[i]) { + MutexList* spells = control_effects[i]; + if(spells->size() > 0) { + MutexList::iterator itr = spells->begin(); + while(itr.Next()){ + LuaSpell* spell = itr->value; + if(spell && spell->spell && spell->spell->GetSpellData()) { + client->Message(CHANNEL_COLOR_YELLOW, "Spell %s (%u) control effect %s", spell->spell->GetName(), spell->spell->GetSpellData()->id, GetControlEffectName(i).c_str()); + } + } + } + } + } + client->Message(CHANNEL_COLOR_YELLOW, "-------------------------------"); +} + +void Entity::TerminateTrade() { + Trade* tmpTradePtr = trade; + if (tmpTradePtr) { + tmpTradePtr->CancelTrade(this); + safe_delete(tmpTradePtr); + } +} + +void Entity::CalculateMaxReduction() { + if(GetInfoStruct()->get_max_spell_reduction_override()) { + return; // override enabled, don't touch the max reduction + } + int16 effective_level = GetInfoStruct()->get_effective_level() != 0 ? GetInfoStruct()->get_effective_level() : GetLevel(); + // Define thresholds and corresponding maximum reduction factors + const int thresholds[] = {1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100, 105, 110, 115, 120}; // Thresholds for level differences + const float maxReductions[] = {0.2f, 0.2f, 0.15f, 0.15f, 0.1f,0.1f,0.1f,0.1f,0.1f,0.075f,0.075f,0.075f,0.05f,0.05f,0.05f,0.05f,0.05f,0.05f,0.045f,0.045f,0.045f,0.045f,0.045f,0.045f,0.045f}; // Maximum reduction factors for each threshold + int numThresholds = sizeof(thresholds) / sizeof(thresholds[0]); + + // Find the appropriate maximum reduction factor based on level difference + float maxReduction = .1f; // Default maximum reduction factor + for (int i = 0; i < numThresholds; ++i) { + if (effective_level >= thresholds[i]) { + maxReduction = maxReductions[i]; + } + else { + break; // No need to check further + } + } + + GetInfoStruct()->set_max_spell_reduction(maxReduction); +} \ No newline at end of file diff --git a/source/WorldServer/Entity.h b/source/WorldServer/Entity.h new file mode 100644 index 0000000..e97ff0d --- /dev/null +++ b/source/WorldServer/Entity.h @@ -0,0 +1,2145 @@ +/* + 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 . +*/ +#ifndef __EQ2_ENTITY__ +#define __EQ2_ENTITY__ +#include "Spawn.h" +#include "../common/Mutex.h" +#include "Skills.h" +#include "MutexList.h" +#include "MutexVector.h" +#include "Trade.h" +#include +#include +#include +#include +#include + +namespace l = boost::lambda; + +class Entity; +class NPC; +class Trade; +struct LuaSpell; +struct GroupMemberInfo; + +struct BonusValues{ + int32 spell_id; + int8 tier; + int16 type; + sint32 value; + int64 class_req; + vector race_req; + vector faction_req; + LuaSpell* luaspell; +}; + +struct MaintainedEffects{ + char name[60]; //name of the spell + int32 target; + int8 target_type; + int32 spell_id; + int32 slot_pos; + int16 icon; + int16 icon_backdrop; + int8 conc_used; + int8 tier; + float total_time; + int32 expire_timestamp; + LuaSpell* spell; +}; + +struct SpellEffects{ + int32 spell_id; + Entity* caster; + float total_time; + int32 expire_timestamp; + int16 icon; + int16 icon_backdrop; + int8 tier; + LuaSpell* spell; +}; + +struct DetrimentalEffects { + int32 spell_id; + Entity* caster; + int32 expire_timestamp; + int16 icon; + int16 icon_backdrop; + int8 tier; + int8 det_type; + bool incurable; + LuaSpell* spell; + int8 control_effect; + float total_time; +}; + +enum RACE_ALIGNMENT { + ALIGNMENT_EVIL=0, + ALIGNMENT_GOOD=1 + // neutral? +}; +struct InfoStruct{ + InfoStruct() + { + name_ = std::string(""); + class1_ = 0; + class2_ = 0; + class3_ = 0; + race_ = 0; + gender_ = 0; + level_ = 0; + max_level_ = 0; + effective_level_ = 0; + tradeskill_level_ = 0; + tradeskill_max_level_ = 0; + cur_concentration_ = 0; + max_concentration_ = 0; + cur_attack_ = 0; + attack_base_ = 0; + cur_mitigation_ = 0; + max_mitigation_ = 0; + mitigation_base_ = 0; + avoidance_display_ = 0; + cur_avoidance_ = 0.0f; + base_avoidance_pct_ = 0; + avoidance_base_ = 0; + max_avoidance_ = 0; + parry_ = 0.0f; + parry_base_ = 0.0f; + deflection_ = 0; + deflection_base_ = 0; + block_ = 0; + block_base_ = 0; + str_ = 0.0f; + sta_ = 0.0f; + agi_ = 0.0f; + wis_ = 0.0f; + intel_ = 0.0f; + str_base_ = 0.0f; + sta_base_ = 0.0f; + agi_base_ = 0.0f; + wis_base_ = 0.0f; + intel_base_ = 0.0f; + heat_ = 0; + cold_ = 0; + magic_ = 0; + mental_ = 0; + divine_ = 0; + disease_ = 0; + poison_ = 0; + disease_base_ = 0; + cold_base_ = 0; + divine_base_ = 0; + magic_base_ = 0; + mental_base_ = 0; + heat_base_ = 0; + poison_base_ = 0; + elemental_base_ = 0; + noxious_base_ = 0; + arcane_base_ = 0; + coin_copper_ = 0; + coin_silver_ = 0; + coin_gold_ = 0; + coin_plat_ = 0; + bank_coin_copper_ = 0; + bank_coin_silver_ = 0; + bank_coin_gold_ = 0; + bank_coin_plat_ = 0; + status_points_ = 0; + deity_ = std::string(""); + weight_ = 0; + max_weight_ = 0; + tradeskill_class1_ = 0; + tradeskill_class2_ = 0; + tradeskill_class3_ = 0; + account_age_base_ = 0; + + memset(account_age_bonus_,0,19); + absorb_ = 0; + xp_ = 0; + xp_needed_ = 0; + xp_debt_ = 0.0f; + xp_yellow_ = 0; + xp_yellow_vitality_bar_ = 0; + xp_blue_vitality_bar_ = 0; + xp_blue_ = 0; + ts_xp_ = 0; + ts_xp_needed_ = 0; + tradeskill_exp_yellow_ = 0; + tradeskill_exp_blue_ = 0; + flags_ = 0; + flags2_ = 0; + xp_vitality_ = 0; + tradeskill_xp_vitality_ = 0; + mitigation_skill1_ = 0; + mitigation_skill2_ = 0; + mitigation_skill3_ = 0; + mitigation_pve_ = 0; + mitigation_pvp_ = 0; + ability_modifier_ = 0; + critical_mitigation_ = 0; + block_chance_ = 0; + uncontested_parry_ = 0; + uncontested_block_ = 0; + uncontested_dodge_ = 0; + uncontested_riposte_ = 0; + crit_chance_ = 0; + crit_bonus_ = 0; + potency_ = 0; + hate_mod_ = 0; + reuse_speed_ = 0; + casting_speed_ = 0; + recovery_speed_ = 0; + spell_reuse_speed_ = 0; + spell_multi_attack_ = 0; + dps_ = 0; + dps_multiplier_ = 0; + attackspeed_ = 0; + haste_ = 0; + multi_attack_ = 0; + flurry_ = 0; + melee_ae_ = 0; + strikethrough_ = 0; + accuracy_ = 0; + offensivespeed_ = 0; + rain_ = 0; + wind_ = 0; + alignment_ = 0; + pet_id_ = 0; + pet_name_ = std::string(""); + pet_health_pct_ = 0.0f; + pet_power_pct_ = 0.0f; + pet_movement_ = 0; + pet_behavior_ = 0; + vision_ = 0; + breathe_underwater_ = 0; + biography_ = std::string(""); + drunk_ = 0; + power_regen_ = 0; + hp_regen_ = 0; + + power_regen_override_ = 0; + hp_regen_override_ = 0; + + water_type_ = 0; + flying_type_ = 0; + + no_interrupt_ = 0; + + interaction_flag_ = 0; + tag1_ = 0; + mood_ = 0; + + range_last_attack_time_ = 0; + primary_last_attack_time_ = 0; + secondary_last_attack_time_ = 0; + primary_attack_delay_ = 0; + secondary_attack_delay_ = 0; + ranged_attack_delay_ = 0; + primary_weapon_type_ = 0; + secondary_weapon_type_ = 0; + ranged_weapon_type_ = 0; + primary_weapon_damage_low_ = 0; + primary_weapon_damage_high_ = 0; + secondary_weapon_damage_low_ = 0; + secondary_weapon_damage_high_ = 0; + ranged_weapon_damage_low_ = 0; + ranged_weapon_damage_high_ = 0; + wield_type_ = 0; + attack_type_ = 0; + primary_weapon_delay_ = 0; + secondary_weapon_delay_ = 0; + ranged_weapon_delay_ = 0; + + override_primary_weapon_ = 0; + override_secondary_weapon_ = 0; + override_ranged_weapon_ = 0; + + friendly_target_npc_ = 0; + last_claim_time_ = 0; + + engaged_encounter_ = 0; + + first_world_login_ = 0; + reload_player_spells_ = 0; + + group_loot_method_ = 1; + group_loot_items_rarity_ = 0; + group_auto_split_ = 1; + group_default_yell_ = 1; + group_autolock_ = 0; + group_lock_method_ = 0; + group_solo_autolock_ = 0; + group_auto_loot_method_ = 0; + assist_auto_attack_ = 0; + + action_state_ = std::string(""); + combat_action_state_ = std::string(""); + + max_spell_reduction_ = .1f; + max_spell_reduction_override_ = 0; + } + + + void SetInfoStruct(InfoStruct* oldStruct) + { + if(!oldStruct) + return; + std::lock_guard lk(classMutex); + + name_ = std::string(oldStruct->get_name()); + class1_ = oldStruct->get_class1(); + class2_ = oldStruct->get_class2(); + class3_ = oldStruct->get_class3(); + race_ = oldStruct->get_race(); + gender_ = oldStruct->get_gender(); + level_ = oldStruct->get_level(); + max_level_ = oldStruct->get_max_level(); + effective_level_ = oldStruct->get_effective_level(); + tradeskill_level_ = oldStruct->get_tradeskill_level(); + tradeskill_max_level_ = oldStruct->get_tradeskill_max_level(); + cur_concentration_ = oldStruct->get_cur_concentration(); + max_concentration_ = oldStruct->get_max_concentration(); + cur_attack_ = oldStruct->get_cur_attack(); + attack_base_ = oldStruct->get_attack_base(); + cur_mitigation_ = oldStruct->get_cur_mitigation(); + max_mitigation_ = oldStruct->get_max_mitigation(); + mitigation_base_ = oldStruct->get_mitigation_base(); + avoidance_display_ = oldStruct->get_avoidance_display(); + cur_avoidance_ = oldStruct->get_cur_avoidance(); + base_avoidance_pct_ = oldStruct->get_base_avoidance_pct(); + avoidance_base_ = oldStruct->get_avoidance_base(); + max_avoidance_ = oldStruct->get_max_avoidance(); + parry_ = oldStruct->get_parry(); + parry_base_ = oldStruct->get_parry_base(); + deflection_ = oldStruct->get_deflection(); + deflection_base_ = oldStruct->get_deflection_base(); + block_ = oldStruct->get_block(); + block_base_ = oldStruct->get_block_base(); + str_ = oldStruct->get_str(); + sta_ = oldStruct->get_sta(); + agi_ = oldStruct->get_agi(); + wis_ = oldStruct->get_wis(); + intel_ = oldStruct->get_intel(); + str_base_ = oldStruct->get_str_base(); + sta_base_ = oldStruct->get_sta_base(); + agi_base_ = oldStruct->get_agi_base(); + wis_base_ = oldStruct->get_wis_base(); + intel_base_ = oldStruct->get_intel_base(); + heat_ = oldStruct->get_heat(); + cold_ = oldStruct->get_cold(); + magic_ = oldStruct->get_magic(); + mental_ = oldStruct->get_mental(); + divine_ = oldStruct->get_divine(); + disease_ = oldStruct->get_disease(); + poison_ = oldStruct->get_poison(); + disease_base_ = oldStruct->get_disease_base(); + cold_base_ = oldStruct->get_cold_base(); + divine_base_ = oldStruct->get_divine_base(); + magic_base_ = oldStruct->get_magic_base(); + mental_base_ = oldStruct->get_mental_base(); + heat_base_ = oldStruct->get_heat_base(); + poison_base_ = oldStruct->get_poison_base(); + elemental_base_ = oldStruct->get_elemental_base(); + noxious_base_ = oldStruct->get_noxious_base(); + arcane_base_ = oldStruct->get_arcane_base(); + coin_copper_ = oldStruct->get_coin_copper(); + coin_silver_ = oldStruct->get_coin_silver(); + coin_gold_ = oldStruct->get_coin_gold(); + coin_plat_ = oldStruct->get_coin_plat(); + bank_coin_copper_ = oldStruct->get_bank_coin_copper(); + bank_coin_silver_ = oldStruct->get_bank_coin_silver(); + bank_coin_gold_ = oldStruct->get_bank_coin_gold(); + bank_coin_plat_ = oldStruct->get_bank_coin_plat(); + status_points_ = oldStruct->get_status_points(); + deity_ = std::string(""); + weight_ = oldStruct->get_weight(); + max_weight_ = oldStruct->get_max_weight(); + tradeskill_class1_ = oldStruct->get_tradeskill_class1(); + tradeskill_class2_ = oldStruct->get_tradeskill_class2(); + tradeskill_class3_ = oldStruct->get_tradeskill_class3(); + account_age_base_ = oldStruct->get_account_age_base(); + + memset(account_age_bonus_,0,19); + absorb_ = oldStruct->get_absorb(); + xp_ = oldStruct->get_xp(); + xp_needed_ = oldStruct->get_xp_needed(); + xp_debt_ = oldStruct->get_xp_debt(); + xp_yellow_ = oldStruct->get_xp_yellow(); + xp_yellow_vitality_bar_ = oldStruct->get_xp_yellow_vitality_bar(); + xp_blue_vitality_bar_ = oldStruct->get_xp_blue_vitality_bar(); + xp_blue_ = oldStruct->get_xp_blue(); + ts_xp_ = oldStruct->get_ts_xp(); + ts_xp_needed_ = oldStruct->get_ts_xp_needed(); + tradeskill_exp_yellow_ = oldStruct->get_tradeskill_exp_yellow(); + tradeskill_exp_blue_ = oldStruct->get_tradeskill_exp_blue(); + flags_ = oldStruct->get_flags(); + flags2_ = oldStruct->get_flags2(); + xp_vitality_ = oldStruct->get_xp_vitality(); + tradeskill_xp_vitality_ = oldStruct->get_tradeskill_xp_vitality(); + mitigation_skill1_ = oldStruct->get_mitigation_skill1(); + mitigation_skill2_ = oldStruct->get_mitigation_skill2(); + mitigation_skill3_ = oldStruct->get_mitigation_skill3(); + mitigation_pve_ = oldStruct->get_mitigation_pve(); + mitigation_pvp_ = oldStruct->get_mitigation_pvp(); + ability_modifier_ = oldStruct->get_ability_modifier(); + critical_mitigation_ = oldStruct->get_critical_mitigation(); + block_chance_ = oldStruct->get_block_chance(); + uncontested_parry_ = oldStruct->get_uncontested_parry(); + uncontested_block_ = oldStruct->get_uncontested_block(); + uncontested_dodge_ = oldStruct->get_uncontested_dodge(); + uncontested_riposte_ = oldStruct->get_uncontested_riposte(); + crit_chance_ = oldStruct->get_crit_chance(); + crit_bonus_ = oldStruct->get_crit_bonus(); + potency_ = oldStruct->get_potency(); + hate_mod_ = oldStruct->get_hate_mod(); + reuse_speed_ = oldStruct->get_reuse_speed(); + casting_speed_ = oldStruct->get_casting_speed(); + recovery_speed_ = oldStruct->get_recovery_speed(); + spell_reuse_speed_ = oldStruct->get_spell_reuse_speed(); + spell_multi_attack_ = oldStruct->get_spell_multi_attack(); + dps_ = oldStruct->get_dps(); + dps_multiplier_ = oldStruct->get_dps_multiplier(); + attackspeed_ = oldStruct->get_attackspeed(); + haste_ = oldStruct->get_haste(); + multi_attack_ = oldStruct->get_multi_attack(); + flurry_ = oldStruct->get_flurry(); + melee_ae_ = oldStruct->get_melee_ae(); + strikethrough_ = oldStruct->get_strikethrough(); + accuracy_ = oldStruct->get_accuracy(); + offensivespeed_ = oldStruct->get_offensivespeed(); + rain_ = oldStruct->get_rain(); + wind_ = oldStruct->get_wind(); + alignment_ = oldStruct->get_alignment(); + pet_id_ = oldStruct->get_pet_id(); + pet_name_ = std::string(oldStruct->get_pet_name()); + pet_health_pct_ = oldStruct->get_pet_health_pct(); + pet_power_pct_ = oldStruct->get_pet_power_pct(); + pet_movement_ = oldStruct->get_pet_movement(); + pet_behavior_ = oldStruct->get_pet_behavior(); + vision_ = oldStruct->get_vision(); + breathe_underwater_ = oldStruct->get_breathe_underwater(); + biography_ = std::string(oldStruct->get_biography()); + drunk_ = oldStruct->get_drunk(); + power_regen_ = oldStruct->get_power_regen(); + hp_regen_ = oldStruct->get_hp_regen(); + + power_regen_override_ = oldStruct->get_power_regen_override(); + hp_regen_override_ = oldStruct->get_hp_regen_override(); + + water_type_ = oldStruct->get_water_type(); + flying_type_ = oldStruct->get_flying_type(); + + no_interrupt_ = oldStruct->get_no_interrupt(); + + interaction_flag_ = oldStruct->get_interaction_flag(); + tag1_ = oldStruct->get_tag1(); + mood_ = oldStruct->get_mood(); + + range_last_attack_time_ = oldStruct->get_range_last_attack_time(); + primary_last_attack_time_ = oldStruct->get_primary_last_attack_time();; + secondary_last_attack_time_ = oldStruct->get_secondary_last_attack_time();; + primary_attack_delay_ = oldStruct->get_primary_attack_delay(); + secondary_attack_delay_ = oldStruct->get_secondary_attack_delay(); + ranged_attack_delay_ = oldStruct->get_ranged_attack_delay(); + primary_weapon_type_ = oldStruct->get_primary_weapon_type(); + secondary_weapon_type_ = oldStruct->get_secondary_weapon_type(); + ranged_weapon_type_ = oldStruct->get_ranged_weapon_type(); + primary_weapon_damage_low_ = oldStruct->get_primary_weapon_damage_low(); + primary_weapon_damage_high_ = oldStruct->get_primary_weapon_damage_high(); + secondary_weapon_damage_low_ = oldStruct->get_secondary_weapon_damage_low(); + secondary_weapon_damage_high_ = oldStruct->get_secondary_weapon_damage_high(); + ranged_weapon_damage_low_ = oldStruct->get_ranged_weapon_damage_low(); + ranged_weapon_damage_high_ = oldStruct->get_ranged_weapon_damage_high(); + wield_type_ = oldStruct->get_wield_type(); + attack_type_ = oldStruct->get_attack_type(); + primary_weapon_delay_ = oldStruct->get_primary_weapon_delay(); + secondary_weapon_delay_ = oldStruct->get_secondary_weapon_delay(); + ranged_weapon_delay_ = oldStruct->get_ranged_weapon_delay(); + + override_primary_weapon_ = oldStruct->get_override_primary_weapon(); + override_secondary_weapon_ = oldStruct->get_override_secondary_weapon(); + override_ranged_weapon_ = oldStruct->get_override_ranged_weapon(); + friendly_target_npc_ = oldStruct->get_friendly_target_npc(); + last_claim_time_ = oldStruct->get_last_claim_time(); + + engaged_encounter_ = oldStruct->get_engaged_encounter(); + + first_world_login_ = oldStruct->get_first_world_login(); + reload_player_spells_ = oldStruct->get_reload_player_spells(); + + action_state_ = oldStruct->get_action_state(); + combat_action_state_ = oldStruct->get_combat_action_state(); + + group_loot_method_ = oldStruct->get_group_loot_method(); + group_loot_items_rarity_ = oldStruct->get_group_loot_items_rarity(); + group_auto_split_ = oldStruct->get_group_auto_split(); + group_default_yell_ = oldStruct->get_group_default_yell(); + group_autolock_ = oldStruct->get_group_autolock(); + group_lock_method_ = oldStruct->get_group_lock_method(); + group_solo_autolock_ = oldStruct->get_group_solo_autolock(); + group_auto_loot_method_ = oldStruct->get_group_auto_loot_method(); + assist_auto_attack_ = oldStruct->get_assist_auto_attack(); + + max_spell_reduction_ = oldStruct->get_max_spell_reduction(); + max_spell_reduction_override_ = oldStruct->get_max_spell_reduction_override(); + } + //mutable std::shared_mutex mutex_; + std::string get_name() { std::lock_guard lk(classMutex); return name_; } + int8 get_class1() { std::lock_guard lk(classMutex); return class1_; } + int8 get_class2() { std::lock_guard lk(classMutex); return class2_; } + int8 get_class3() { std::lock_guard lk(classMutex); return class3_; } + int8 get_race() { std::lock_guard lk(classMutex); return race_; } + int8 get_gender() { std::lock_guard lk(classMutex); return gender_; } + int16 get_level() { std::lock_guard lk(classMutex); return level_; } + int16 get_max_level() { std::lock_guard lk(classMutex); return max_level_; } + int16 get_effective_level() { std::lock_guard lk(classMutex); return effective_level_; } + int16 get_tradeskill_level() { std::lock_guard lk(classMutex); return tradeskill_level_; } + int16 get_tradeskill_max_level() { std::lock_guard lk(classMutex); return tradeskill_max_level_; } + + int8 get_cur_concentration() { std::lock_guard lk(classMutex); return cur_concentration_; } + int8 get_max_concentration() { std::lock_guard lk(classMutex); return max_concentration_; } + int8 get_max_concentration_base() { std::lock_guard lk(classMutex); return max_concentration_base_; } + int16 get_cur_attack() { std::lock_guard lk(classMutex); return cur_attack_; } + int16 get_attack_base() { std::lock_guard lk(classMutex); return attack_base_; } + int16 get_cur_mitigation() { std::lock_guard lk(classMutex); return cur_mitigation_; } + int16 get_max_mitigation() { std::lock_guard lk(classMutex); return max_mitigation_; } + + int16 get_mitigation_base() { std::lock_guard lk(classMutex); return mitigation_base_; } + int16 get_avoidance_display() { std::lock_guard lk(classMutex); return avoidance_display_; } + float get_cur_avoidance() { std::lock_guard lk(classMutex); return cur_avoidance_; } + int16 get_base_avoidance_pct() { std::lock_guard lk(classMutex); return base_avoidance_pct_; } + int16 get_avoidance_base() { std::lock_guard lk(classMutex); return avoidance_base_; } + + float get_parry() { std::lock_guard lk(classMutex); return parry_; } + float get_parry_base() { std::lock_guard lk(classMutex); return parry_base_; } + + int16 get_max_avoidance() { std::lock_guard lk(classMutex); return max_avoidance_; } + + float get_deflection() { std::lock_guard lk(classMutex); return deflection_; } + int16 get_deflection_base() { std::lock_guard lk(classMutex); return deflection_base_; } + + float get_block() { std::lock_guard lk(classMutex); return block_; } + int16 get_block_base() { std::lock_guard lk(classMutex); return block_base_; } + + float get_str() { std::lock_guard lk(classMutex); return str_; } + float get_sta() { std::lock_guard lk(classMutex); return sta_; } + float get_agi() { std::lock_guard lk(classMutex); return agi_; } + float get_wis() { std::lock_guard lk(classMutex); return wis_; } + float get_intel() { std::lock_guard lk(classMutex); return intel_; } + float get_str_base() { std::lock_guard lk(classMutex); return str_base_; } + float get_sta_base() { std::lock_guard lk(classMutex); return sta_base_; } + float get_agi_base() { std::lock_guard lk(classMutex); return agi_base_; } + float get_wis_base() { std::lock_guard lk(classMutex); return wis_base_; } + float get_intel_base() { std::lock_guard lk(classMutex); return intel_base_; } + int16 get_heat() { std::lock_guard lk(classMutex); return heat_; } + int16 get_cold() { std::lock_guard lk(classMutex); return cold_; } + int16 get_magic() { std::lock_guard lk(classMutex); return magic_; } + int16 get_mental() { std::lock_guard lk(classMutex); return mental_; } + int16 get_divine() { std::lock_guard lk(classMutex); return divine_; } + int16 get_disease() { std::lock_guard lk(classMutex); return disease_; } + int16 get_poison() { std::lock_guard lk(classMutex); return poison_; } + int16 get_disease_base() { std::lock_guard lk(classMutex); return disease_base_; } + int16 get_cold_base() { std::lock_guard lk(classMutex); return cold_base_; } + int16 get_divine_base() { std::lock_guard lk(classMutex); return divine_base_; } + int16 get_magic_base() { std::lock_guard lk(classMutex); return magic_base_; } + int16 get_mental_base() { std::lock_guard lk(classMutex); return mental_base_; } + int16 get_heat_base() { std::lock_guard lk(classMutex); return heat_base_; } + int16 get_poison_base() { std::lock_guard lk(classMutex); return poison_base_; } + int16 get_elemental_base() { std::lock_guard lk(classMutex); return elemental_base_; } + int16 get_noxious_base() { std::lock_guard lk(classMutex); return noxious_base_; } + int16 get_arcane_base() { std::lock_guard lk(classMutex); return arcane_base_; } + int32 get_coin_copper() { std::lock_guard lk(classMutex); return coin_copper_; } + int32 get_coin_silver() { std::lock_guard lk(classMutex); return coin_silver_; } + int32 get_coin_gold() { std::lock_guard lk(classMutex); return coin_gold_; } + int32 get_coin_plat() { std::lock_guard lk(classMutex); return coin_plat_; } + int32 get_bank_coin_copper() { std::lock_guard lk(classMutex); return bank_coin_copper_; } + int32 get_bank_coin_silver() { std::lock_guard lk(classMutex); return bank_coin_silver_; } + int32 get_bank_coin_gold() { std::lock_guard lk(classMutex); return bank_coin_gold_; } + int32 get_bank_coin_plat() { std::lock_guard lk(classMutex); return bank_coin_plat_; } + int32 get_status_points() { std::lock_guard lk(classMutex); return status_points_; } + std::string get_deity() { std::lock_guard lk(classMutex); return deity_; } + int32 get_weight() { std::lock_guard lk(classMutex); return weight_; } + int32 get_max_weight() { std::lock_guard lk(classMutex); return max_weight_; } + + + //SpellEffects* & get_spell_effects() { std::lock_guard lk(classMutex); return spell_effects_; } + //MaintainedEffects* & get_maintained_effects() { std::lock_guard lk(classMutex); return maintained_effects_; } + int8 get_tradeskill_class1() { std::lock_guard lk(classMutex); return tradeskill_class1_; } + int8 get_tradeskill_class2() { std::lock_guard lk(classMutex); return tradeskill_class2_; } + int8 get_tradeskill_class3() { std::lock_guard lk(classMutex); return tradeskill_class3_; } + + int32 get_account_age_base() { std::lock_guard lk(classMutex); return account_age_base_; } + + int8 get_account_age_bonus(int8 field) { std::lock_guard lk(classMutex); return account_age_bonus_[field]; } + int16 get_absorb() { std::lock_guard lk(classMutex); return absorb_; } + int32 get_xp() { std::lock_guard lk(classMutex); return xp_; } + int32 get_xp_needed() { std::lock_guard lk(classMutex); return xp_needed_; } + float get_xp_debt() { std::lock_guard lk(classMutex); return xp_debt_; } + int16 get_xp_yellow() { std::lock_guard lk(classMutex); return xp_yellow_; } + int16 get_xp_yellow_vitality_bar() { std::lock_guard lk(classMutex); return xp_yellow_vitality_bar_; } + int16 get_xp_blue_vitality_bar() { std::lock_guard lk(classMutex); return xp_blue_vitality_bar_; } + int16 get_xp_blue() { std::lock_guard lk(classMutex); return xp_blue_; } + int32 get_ts_xp() { std::lock_guard lk(classMutex); return ts_xp_; } + int32 get_ts_xp_needed() { std::lock_guard lk(classMutex); return ts_xp_needed_; } + int16 get_tradeskill_exp_yellow() { std::lock_guard lk(classMutex); return tradeskill_exp_yellow_; } + int16 get_tradeskill_exp_blue() { std::lock_guard lk(classMutex); return tradeskill_exp_blue_; } + int32 get_flags() { std::lock_guard lk(classMutex); return flags_; } + int32 get_flags2() { std::lock_guard lk(classMutex); return flags2_; } + float get_xp_vitality() { std::lock_guard lk(classMutex); return xp_vitality_; } + float get_tradeskill_xp_vitality() { std::lock_guard lk(classMutex); return tradeskill_xp_vitality_; } + + int16 get_mitigation_skill1() { std::lock_guard lk(classMutex); return mitigation_skill1_; } + int16 get_mitigation_skill2() { std::lock_guard lk(classMutex); return mitigation_skill2_; } + int16 get_mitigation_skill3() { std::lock_guard lk(classMutex); return mitigation_skill3_; } + + int16 get_mitigation_pve() { std::lock_guard lk(classMutex); return mitigation_pve_; } + int16 get_mitigation_pvp() { std::lock_guard lk(classMutex); return mitigation_pvp_; } + + float get_ability_modifier() { std::lock_guard lk(classMutex); return ability_modifier_; } + float get_critical_mitigation() { std::lock_guard lk(classMutex); return critical_mitigation_; } + float get_block_chance() { std::lock_guard lk(classMutex); return block_chance_; } + float get_uncontested_parry() { std::lock_guard lk(classMutex); return uncontested_parry_; } + float get_uncontested_block() { std::lock_guard lk(classMutex); return uncontested_block_; } + float get_uncontested_dodge() { std::lock_guard lk(classMutex); return uncontested_dodge_; } + float get_uncontested_riposte() { std::lock_guard lk(classMutex); return uncontested_riposte_; } + float get_crit_chance() { std::lock_guard lk(classMutex); return crit_chance_; } + float get_crit_bonus() { std::lock_guard lk(classMutex); return crit_bonus_; } + float get_potency() { std::lock_guard lk(classMutex); return potency_; } + float get_hate_mod() { std::lock_guard lk(classMutex); return hate_mod_; } + float get_reuse_speed() { std::lock_guard lk(classMutex); return reuse_speed_; } + float get_casting_speed() { std::lock_guard lk(classMutex); return casting_speed_; } + float get_recovery_speed() { std::lock_guard lk(classMutex); return recovery_speed_; } + float get_spell_reuse_speed() { std::lock_guard lk(classMutex); return spell_reuse_speed_; } + float get_spell_multi_attack() { std::lock_guard lk(classMutex); return spell_multi_attack_; } + float get_dps() { std::lock_guard lk(classMutex); return dps_; } + float get_dps_multiplier() { std::lock_guard lk(classMutex); return dps_multiplier_; } + float get_attackspeed() { std::lock_guard lk(classMutex); return attackspeed_; } + float get_haste() { std::lock_guard lk(classMutex); return haste_; } + float get_multi_attack() { std::lock_guard lk(classMutex); return multi_attack_; } + float get_flurry() { std::lock_guard lk(classMutex); return flurry_; } + float get_melee_ae() { std::lock_guard lk(classMutex); return melee_ae_; } + float get_strikethrough() { std::lock_guard lk(classMutex); return strikethrough_; } + float get_accuracy() { std::lock_guard lk(classMutex); return accuracy_; } + float get_offensivespeed() { std::lock_guard lk(classMutex); return offensivespeed_; } + float get_rain() { std::lock_guard lk(classMutex); return rain_; } + float get_wind() { std::lock_guard lk(classMutex); return wind_; } + sint8 get_alignment() { std::lock_guard lk(classMutex); return alignment_; } + int32 get_pet_id() { std::lock_guard lk(classMutex); return pet_id_; } + + std::string get_pet_name() { std::lock_guard lk(classMutex); return pet_name_; } + float get_pet_health_pct() { std::lock_guard lk(classMutex); return pet_health_pct_; } + float get_pet_power_pct() { std::lock_guard lk(classMutex); return pet_power_pct_; } + int8 get_pet_movement() { std::lock_guard lk(classMutex); return pet_movement_; } + int8 get_pet_behavior() { std::lock_guard lk(classMutex); return pet_behavior_; } + int32 get_vision() { std::lock_guard lk(classMutex); return vision_; } + int8 get_breathe_underwater() { std::lock_guard lk(classMutex); return breathe_underwater_; } + std::string get_biography() { std::lock_guard lk(classMutex); return biography_; } + float get_drunk() { std::lock_guard lk(classMutex); return drunk_; } + + sint16 get_power_regen() { std::lock_guard lk(classMutex); return power_regen_; } + sint16 get_hp_regen() { std::lock_guard lk(classMutex); return hp_regen_; } + + int8 get_power_regen_override() { std::lock_guard lk(classMutex); return power_regen_override_; } + int8 get_hp_regen_override() { std::lock_guard lk(classMutex); return hp_regen_override_; } + + int8 get_water_type() { std::lock_guard lk(classMutex); return water_type_; } + int8 get_flying_type() { std::lock_guard lk(classMutex); return flying_type_; } + + int8 get_no_interrupt() { std::lock_guard lk(classMutex); return no_interrupt_; } + + int8 get_interaction_flag() { std::lock_guard lk(classMutex); return interaction_flag_; } + int8 get_tag1() { std::lock_guard lk(classMutex); return tag1_; } + int16 get_mood() { std::lock_guard lk(classMutex); return mood_; } + + int32 get_range_last_attack_time() { std::lock_guard lk(classMutex); return range_last_attack_time_; } + int32 get_primary_last_attack_time() { std::lock_guard lk(classMutex); return primary_last_attack_time_; } + int32 get_secondary_last_attack_time() { std::lock_guard lk(classMutex); return secondary_last_attack_time_; } + + int16 get_primary_attack_delay() { std::lock_guard lk(classMutex); return primary_attack_delay_; } + int16 get_secondary_attack_delay() { std::lock_guard lk(classMutex); return secondary_attack_delay_; } + int16 get_ranged_attack_delay() { std::lock_guard lk(classMutex); return ranged_attack_delay_; } + + int8 get_primary_weapon_type() { std::lock_guard lk(classMutex); return primary_weapon_type_; } + int8 get_secondary_weapon_type() { std::lock_guard lk(classMutex); return secondary_weapon_type_; } + int8 get_ranged_weapon_type() { std::lock_guard lk(classMutex); return ranged_weapon_type_; } + + int32 get_primary_weapon_damage_low() { std::lock_guard lk(classMutex); return primary_weapon_damage_low_; } + int32 get_primary_weapon_damage_high() { std::lock_guard lk(classMutex); return primary_weapon_damage_high_; } + int32 get_secondary_weapon_damage_low() { std::lock_guard lk(classMutex); return secondary_weapon_damage_low_; } + int32 get_secondary_weapon_damage_high() { std::lock_guard lk(classMutex); return secondary_weapon_damage_high_; } + int32 get_ranged_weapon_damage_low() { std::lock_guard lk(classMutex); return ranged_weapon_damage_low_; } + int32 get_ranged_weapon_damage_high() { std::lock_guard lk(classMutex); return ranged_weapon_damage_high_; } + + int8 get_wield_type() { std::lock_guard lk(classMutex); return wield_type_; } + int8 get_attack_type() { std::lock_guard lk(classMutex); return attack_type_; } + + int16 get_primary_weapon_delay() { std::lock_guard lk(classMutex); return primary_weapon_delay_; } + int16 get_secondary_weapon_delay() { std::lock_guard lk(classMutex); return secondary_weapon_delay_; } + int16 get_ranged_weapon_delay() { std::lock_guard lk(classMutex); return ranged_weapon_delay_; } + + int8 get_override_primary_weapon() { std::lock_guard lk(classMutex); return override_primary_weapon_; } + int8 get_override_secondary_weapon() { std::lock_guard lk(classMutex); return override_secondary_weapon_; } + int8 get_override_ranged_weapon() { std::lock_guard lk(classMutex); return override_ranged_weapon_; } + + int8 get_friendly_target_npc() { std::lock_guard lk(classMutex); return friendly_target_npc_; } + int32 get_last_claim_time() { std::lock_guard lk(classMutex); return last_claim_time_; } + + int8 get_engaged_encounter() { std::lock_guard lk(classMutex); return engaged_encounter_; } + + int8 get_first_world_login() { std::lock_guard lk(classMutex); return first_world_login_; } + + int8 get_reload_player_spells() { std::lock_guard lk(classMutex); return reload_player_spells_; } + + int8 get_group_loot_method() { std::lock_guard lk(classMutex); return group_loot_method_; } + int8 get_group_loot_items_rarity() { std::lock_guard lk(classMutex); return group_loot_items_rarity_; } + int8 get_group_auto_split() { std::lock_guard lk(classMutex); return group_auto_split_; } + int8 get_group_default_yell() { std::lock_guard lk(classMutex); return group_default_yell_; } + int8 get_group_autolock() { std::lock_guard lk(classMutex); return group_autolock_; } + int8 get_group_lock_method() { std::lock_guard lk(classMutex); return group_lock_method_; } + int8 get_group_solo_autolock() { std::lock_guard lk(classMutex); return group_solo_autolock_; } + int8 get_group_auto_loot_method() { std::lock_guard lk(classMutex); return group_auto_loot_method_; } + int8 get_assist_auto_attack() { std::lock_guard lk(classMutex); return assist_auto_attack_; } + + std::string get_action_state() { std::lock_guard lk(classMutex); return action_state_; } + + std::string get_combat_action_state() { std::lock_guard lk(classMutex); return combat_action_state_; } + + float get_max_spell_reduction() { std::lock_guard lk(classMutex); return max_spell_reduction_; } + int8 get_max_spell_reduction_override() { std::lock_guard lk(classMutex); return max_spell_reduction_override_; } + + void set_name(std::string value) { std::lock_guard lk(classMutex); name_ = value; } + + void set_deity(std::string value) { std::lock_guard lk(classMutex); deity_ = value; } + + void set_class1(int8 value) { std::lock_guard lk(classMutex); class1_ = value; } + void set_class2(int8 value) { std::lock_guard lk(classMutex); class2_ = value; } + void set_class3(int8 value) { std::lock_guard lk(classMutex); class3_ = value; } + + void set_race(int8 value) { std::lock_guard lk(classMutex); race_ = value; } + void set_gender(int8 value) { std::lock_guard lk(classMutex); gender_ = value; } + void set_level(int16 value) { std::lock_guard lk(classMutex); level_ = value; } + void set_max_level(int16 value) { std::lock_guard lk(classMutex); max_level_ = value; } + void set_effective_level(int16 value) { std::lock_guard lk(classMutex); effective_level_ = value; } + + void set_cur_concentration(int8 value) { std::lock_guard lk(classMutex); cur_concentration_ = value; } + void set_max_concentration(int8 value) { std::lock_guard lk(classMutex); max_concentration_ = value; } + void set_max_concentration_base(int8 value) { std::lock_guard lk(classMutex); max_concentration_base_ = value; } + + void add_cur_concentration(int8 value) { std::lock_guard lk(classMutex); cur_concentration_ += value; } + void add_max_concentration(int8 value) { std::lock_guard lk(classMutex); max_concentration_ += value; } + + void set_cur_attack(int16 value) { std::lock_guard lk(classMutex); cur_attack_ = value; } + void set_attack_base(int16 value) { std::lock_guard lk(classMutex); attack_base_ = value; } + void set_cur_mitigation(int16 value) { std::lock_guard lk(classMutex); cur_mitigation_ = value; } + void set_max_mitigation(int16 value) { std::lock_guard lk(classMutex); max_mitigation_ = value; } + void set_mitigation_base(int16 value) { std::lock_guard lk(classMutex); mitigation_base_ = value; } + void add_mitigation_base(int16 value) { std::lock_guard lk(classMutex); mitigation_base_ += value; } + + void set_avoidance_display(int16 value) { std::lock_guard lk(classMutex); avoidance_display_ = value; } + void set_cur_avoidance(float value) { std::lock_guard lk(classMutex); cur_avoidance_ = value; } + void set_base_avoidance_pct(int16 value) { std::lock_guard lk(classMutex); base_avoidance_pct_ = value; } + void set_avoidance_base(int16 value) { std::lock_guard lk(classMutex); avoidance_base_ = value; } + void set_max_avoidance(int16 value) { std::lock_guard lk(classMutex); max_avoidance_ = value; } + void set_parry(float value) { std::lock_guard lk(classMutex); parry_ = value; } + void set_parry_base(float value) { std::lock_guard lk(classMutex); parry_base_ = value; } + void set_deflection(int16 value) { std::lock_guard lk(classMutex); deflection_ = value; } + void set_deflection_base(float value) { std::lock_guard lk(classMutex); deflection_base_ = value; } + void set_block(float value) { std::lock_guard lk(classMutex); block_ = value; } + void set_block_base(int16 value) { std::lock_guard lk(classMutex); block_base_ = value; } + + void set_str(float value) { std::lock_guard lk(classMutex); str_ = value; } + void set_sta(float value) { std::lock_guard lk(classMutex); sta_ = value; } + void set_agi(float value) { std::lock_guard lk(classMutex); agi_ = value; } + void set_wis(float value) { std::lock_guard lk(classMutex); wis_ = value; } + void set_intel(float value) { std::lock_guard lk(classMutex); intel_ = value; } + + void add_str(float value) { std::lock_guard lk(classMutex); if(str_ + value < 0.0f) str_ = 0.0f; else str_ += value; } + void add_sta(float value) { std::lock_guard lk(classMutex); if(sta_ + value < 0.0f) sta_ = 0.0f; else sta_ += value; } + void add_agi(float value) { std::lock_guard lk(classMutex); if(agi_ + value < 0.0f) agi_ = 0.0f; else agi_ += value; } + void add_wis(float value) { std::lock_guard lk(classMutex); if(wis_ + value < 0.0f) wis_ = 0.0f; else wis_ += value; } + void add_intel(float value) { std::lock_guard lk(classMutex); if(intel_ + value < 0.0f) intel_ = 0.0f; else intel_ += value; } + + void set_str_base(float value) { std::lock_guard lk(classMutex); str_base_ = value; } + void set_sta_base(float value) { std::lock_guard lk(classMutex); sta_base_ = value; } + void set_agi_base(float value) { std::lock_guard lk(classMutex); agi_base_ = value; } + void set_wis_base(float value) { std::lock_guard lk(classMutex); wis_base_ = value; } + void set_intel_base(float value) { std::lock_guard lk(classMutex); intel_base_ = value; } + + void set_heat(int16 value) { std::lock_guard lk(classMutex); heat_ = value; } + void set_cold(int16 value) { std::lock_guard lk(classMutex); cold_ = value; } + void set_magic(int16 value) { std::lock_guard lk(classMutex); magic_ = value; } + void set_mental(int16 value) { std::lock_guard lk(classMutex); mental_ = value; } + void set_divine(int16 value) { std::lock_guard lk(classMutex); divine_ = value; } + void set_disease(int16 value) { std::lock_guard lk(classMutex); disease_ = value; } + void set_poison(int16 value) { std::lock_guard lk(classMutex); poison_ = value; } + + void add_heat(sint16 value) { std::lock_guard lk(classMutex); if((sint32)heat_ + value < 0) heat_ = 0; else heat_ += value; } + void add_cold(sint16 value) { std::lock_guard lk(classMutex); if((sint32)cold_ + value < 0) cold_ = 0; else cold_ += value; } + void add_magic(sint16 value) { std::lock_guard lk(classMutex); if((sint32)magic_ + value < 0) magic_ = 0; else magic_ += value; } + void add_mental(sint16 value) { std::lock_guard lk(classMutex); if((sint32)mental_ + value < 0) mental_ = 0; else mental_ += value; } + void add_divine(sint16 value) { std::lock_guard lk(classMutex); if((sint32)divine_ + value < 0) divine_ = 0; else divine_ += value; } + void add_disease(sint16 value) { std::lock_guard lk(classMutex); if((sint32)disease_ + value < 0) disease_ = 0; else disease_ += value; } + void add_poison(sint16 value) { std::lock_guard lk(classMutex); if((sint32)poison_ + value < 0) poison_ = 0; else poison_ += value; } + + void set_disease_base(int16 value) { std::lock_guard lk(classMutex); disease_base_ = value; } + void set_cold_base(int16 value) { std::lock_guard lk(classMutex); cold_base_ = value; } + void set_divine_base(int16 value) { std::lock_guard lk(classMutex); divine_base_ = value; } + void set_magic_base(int16 value) { std::lock_guard lk(classMutex); magic_base_ = value; } + void set_mental_base(int16 value) { std::lock_guard lk(classMutex); mental_base_ = value; } + void set_heat_base(int16 value) { std::lock_guard lk(classMutex); heat_base_ = value; } + void set_poison_base(int16 value) { std::lock_guard lk(classMutex); poison_base_ = value; } + void set_elemental_base(int16 value) { std::lock_guard lk(classMutex); elemental_base_ = value; } + void set_noxious_base(int16 value) { std::lock_guard lk(classMutex); noxious_base_ = value; } + void set_arcane_base(int16 value) { std::lock_guard lk(classMutex); arcane_base_ = value; } + + void set_tradeskill_level(int16 value) { std::lock_guard lk(classMutex); tradeskill_level_ = value; } + void set_tradeskill_max_level(int16 value) { std::lock_guard lk(classMutex); tradeskill_max_level_ = value; } + + void set_tradeskill_class1(int8 value) { std::lock_guard lk(classMutex); tradeskill_class1_ = value; } + void set_tradeskill_class2(int8 value) { std::lock_guard lk(classMutex); tradeskill_class2_ = value; } + void set_tradeskill_class3(int8 value) { std::lock_guard lk(classMutex); tradeskill_class3_ = value; } + + void set_account_age_base(int32 value) { std::lock_guard lk(classMutex); account_age_base_ = value; } + + void set_xp_vitality(float value) { std::lock_guard lk(classMutex); xp_vitality_ = value; } + + void add_xp_vitality(float value) { std::lock_guard lk(classMutex); xp_vitality_ += value; } + + void set_tradeskill_xp_vitality(float value) { std::lock_guard lk(classMutex); tradeskill_xp_vitality_ = value; } + + void set_absorb(int16 value) { std::lock_guard lk(classMutex); absorb_ = value; } + + void set_xp(int32 value) { std::lock_guard lk(classMutex); xp_ = value; } + void set_xp_needed(int32 value) { std::lock_guard lk(classMutex); xp_needed_ = value; } + + void set_xp_debt(float value) { std::lock_guard lk(classMutex); if(std::isnan(value)) value = 0.0f; xp_debt_ = value; } + + void set_xp_yellow(int16 value) { std::lock_guard lk(classMutex); xp_yellow_ = value; } + void set_xp_blue(int16 value) { std::lock_guard lk(classMutex); xp_blue_ = value; } + + void set_xp_yellow_vitality_bar(int16 value) { std::lock_guard lk(classMutex); xp_yellow_vitality_bar_ = value; } + void set_xp_blue_vitality_bar(int16 value) { std::lock_guard lk(classMutex); xp_blue_vitality_bar_ = value; } + + void set_ts_xp(int32 value) { std::lock_guard lk(classMutex); ts_xp_ = value; } + void set_ts_xp_needed(int32 value) { std::lock_guard lk(classMutex); ts_xp_needed_ = value; } + + void set_tradeskill_exp_yellow(int16 value) { std::lock_guard lk(classMutex); tradeskill_exp_yellow_ = value; } + void set_tradeskill_exp_blue(int16 value) { std::lock_guard lk(classMutex); tradeskill_exp_blue_ = value; } + + void set_flags(int32 value) { std::lock_guard lk(classMutex); flags_ = value; } + void set_flags2(int32 value) { std::lock_guard lk(classMutex); flags2_ = value; } + + void set_coin_plat(int32 value) { std::lock_guard lk(classMutex); coin_plat_ = value; } + void set_coin_gold(int32 value) { std::lock_guard lk(classMutex); coin_gold_ = value; } + void set_coin_silver(int32 value) { std::lock_guard lk(classMutex); coin_silver_ = value; } + void set_coin_copper(int32 value) { std::lock_guard lk(classMutex); coin_copper_ = value; } + + void add_coin_plat(int32 value) { std::lock_guard lk(classMutex); if((sint64)coin_plat_ + value < 0) coin_plat_ = 0; else coin_plat_ += value; } + void add_coin_gold(int32 value) { std::lock_guard lk(classMutex); if((sint64)coin_gold_ + value < 0) coin_gold_ = 0; else coin_gold_ += value; } + void add_coin_silver(int32 value) { std::lock_guard lk(classMutex); if((sint64)coin_silver_ + value < 0) coin_silver_ = 0; else coin_silver_ += value; } + void add_coin_copper(int32 value) { std::lock_guard lk(classMutex); if((sint64)coin_copper_ + value < 0) coin_copper_ = 0; else coin_copper_ += value; } + + void set_bank_coin_plat(int32 value) { std::lock_guard lk(classMutex); bank_coin_plat_ = value; } + void set_bank_coin_gold(int32 value) { std::lock_guard lk(classMutex); bank_coin_gold_ = value; } + void set_bank_coin_silver(int32 value) { std::lock_guard lk(classMutex); bank_coin_silver_ = value; } + void set_bank_coin_copper(int32 value) { std::lock_guard lk(classMutex); bank_coin_copper_ = value; } + + void add_bank_coin_plat(int32 value) { std::lock_guard lk(classMutex); if((sint64)bank_coin_plat_ + value < 0) bank_coin_plat_ = 0; else bank_coin_plat_ += value; } + void add_bank_coin_gold(int32 value) { std::lock_guard lk(classMutex); if((sint64)bank_coin_gold_ + value < 0) bank_coin_gold_ = 0; else bank_coin_gold_ += value; } + void add_bank_coin_silver(int32 value) { std::lock_guard lk(classMutex); if((sint64)bank_coin_silver_ + value < 0) bank_coin_silver_ = 0; else bank_coin_silver_ += value; } + void add_bank_coin_copper(int32 value) { std::lock_guard lk(classMutex); if((sint64)bank_coin_copper_ + value < 0) bank_coin_copper_ = 0; else bank_coin_copper_ += value; } + + void set_status_points(int32 value) { std::lock_guard lk(classMutex); status_points_ = value; } + void add_status_points(int32 value) { std::lock_guard lk(classMutex); if((sint64)status_points_ + value < 0) status_points_ = 0; else status_points_ += value; } + bool subtract_status_points(int32 value) { std::lock_guard lk(classMutex); if(value > status_points_) return false; status_points_ -= value; return true; } + + void set_mitigation_skill1(int16 value) { std::lock_guard lk(classMutex); mitigation_skill1_ = value; } + void set_mitigation_skill2(int16 value) { std::lock_guard lk(classMutex); mitigation_skill2_ = value; } + void set_mitigation_skill3(int16 value) { std::lock_guard lk(classMutex); mitigation_skill3_ = value; } + + void set_mitigation_pve(int16 value) { std::lock_guard lk(classMutex); mitigation_pve_ = value; } + void set_mitigation_pvp(int16 value) { std::lock_guard lk(classMutex); mitigation_pvp_ = value; } + + void add_mitigation_skill1(int16 value) { std::lock_guard lk(classMutex); if((sint32)mitigation_skill1_ + value < 0) mitigation_skill1_ = 0; else mitigation_skill1_ += value; } + void add_mitigation_skill2(int16 value) { std::lock_guard lk(classMutex); if((sint32)mitigation_skill2_ + value < 0) mitigation_skill2_ = 0; else mitigation_skill2_ += value; } + void add_mitigation_skill3(int16 value) { std::lock_guard lk(classMutex); if((sint32)mitigation_skill3_ + value < 0) mitigation_skill3_ = 0; else mitigation_skill3_ += value; } + + void set_ability_modifier(float value) { std::lock_guard lk(classMutex); ability_modifier_ = value; } + + void add_ability_modifier(float value) { std::lock_guard lk(classMutex); if(ability_modifier_ + value < 0.0f) ability_modifier_ = 0.0f; else ability_modifier_ += value; } + + void set_critical_mitigation(float value) { std::lock_guard lk(classMutex); critical_mitigation_ = value; } + + void add_critical_mitigation(float value) { std::lock_guard lk(classMutex); if(critical_mitigation_ + value < 0.0f) critical_mitigation_ = 0.0f; else critical_mitigation_ += value; } + + void set_block_chance(float value) { std::lock_guard lk(classMutex); block_chance_ = value; } + void set_uncontested_parry(float value) { std::lock_guard lk(classMutex); uncontested_parry_ = value; } + void set_uncontested_block(float value) { std::lock_guard lk(classMutex); uncontested_block_ = value; } + void set_uncontested_dodge(float value) { std::lock_guard lk(classMutex); uncontested_dodge_ = value; } + void set_uncontested_riposte(float value) { std::lock_guard lk(classMutex); uncontested_riposte_ = value; } + void set_crit_chance(float value) { std::lock_guard lk(classMutex); crit_chance_ = value; } + void set_crit_bonus(float value) { std::lock_guard lk(classMutex); crit_bonus_ = value; } + void set_potency(float value) { std::lock_guard lk(classMutex); potency_ = value; } + void set_hate_mod(float value) { std::lock_guard lk(classMutex); hate_mod_ = value; } + void set_reuse_speed(float value) { std::lock_guard lk(classMutex); reuse_speed_ = value; } + void set_casting_speed(float value) { std::lock_guard lk(classMutex); casting_speed_ = value; } + void set_recovery_speed(float value) { std::lock_guard lk(classMutex); recovery_speed_ = value; } + void set_spell_reuse_speed(float value) { std::lock_guard lk(classMutex); spell_reuse_speed_ = value; } + void set_spell_multi_attack(float value) { std::lock_guard lk(classMutex); spell_multi_attack_ = value; } + void set_dps(float value) { std::lock_guard lk(classMutex); dps_ = value; } + void set_dps_multiplier(float value) { std::lock_guard lk(classMutex); dps_multiplier_ = value; } + void set_attackspeed(float value) { std::lock_guard lk(classMutex); attackspeed_ = value; } + void set_haste(float value) { std::lock_guard lk(classMutex); haste_ = value; } + void set_multi_attack(float value) { std::lock_guard lk(classMutex); multi_attack_ = value; } + void set_flurry(float value) { std::lock_guard lk(classMutex); flurry_ = value; } + void set_melee_ae(float value) { std::lock_guard lk(classMutex); melee_ae_ = value; } + void set_strikethrough(float value) { std::lock_guard lk(classMutex); strikethrough_ = value; } + void set_accuracy(float value) { std::lock_guard lk(classMutex); accuracy_ = value; } + void set_offensivespeed(float value) { std::lock_guard lk(classMutex); offensivespeed_ = value; } + + // crash client if float values above 1.0 are sent + void set_rain(float value) { std::lock_guard lk(classMutex); if(value > 1.0f) value = 1.0f; else if(value < 0.0f) value = 0.0f; rain_ = value; } + void set_wind(float value) { std::lock_guard lk(classMutex); if(value > 1.0f) value = 1.0f; else if(value < 0.0f) value = 0.0f; wind_ = value; } + + void add_block_chance(float value) { std::lock_guard lk(classMutex); if(block_chance_ + value < 0.0f) block_chance_ = 0.0f; else block_chance_ += value; } + void add_uncontested_parry(float value) { std::lock_guard lk(classMutex); if(uncontested_parry_ + value < 0.0f) uncontested_parry_ = 0.0f; else uncontested_parry_ += value; } + void add_uncontested_block(float value) { std::lock_guard lk(classMutex); if(uncontested_block_ + value < 0.0f) uncontested_block_ = 0.0f; else uncontested_block_ += value; } + void add_uncontested_dodge(float value) { std::lock_guard lk(classMutex); if(uncontested_dodge_ + value < 0.0f) uncontested_dodge_ = 0.0f; else uncontested_dodge_ += value; } + void add_uncontested_riposte(float value) { std::lock_guard lk(classMutex); if(uncontested_riposte_ + value < 0.0f) uncontested_riposte_ = 0.0f; else uncontested_riposte_ += value; } + void add_crit_chance(float value) { std::lock_guard lk(classMutex); if(crit_chance_ + value < 0.0f) crit_chance_ = 0.0f; else crit_chance_ += value; } + void add_crit_bonus(float value) { std::lock_guard lk(classMutex); if(crit_bonus_ + value < 0.0f) crit_bonus_ = 0.0f; else crit_bonus_ += value; } + void add_potency(float value) { std::lock_guard lk(classMutex); if(potency_ + value < 0.0f) potency_ = 0.0f; else potency_ += value; } + void add_hate_mod(float value) { std::lock_guard lk(classMutex); if(hate_mod_ + value < 0.0f) hate_mod_ = 0.0f; else hate_mod_ += value; } + void add_reuse_speed(float value) { std::lock_guard lk(classMutex); reuse_speed_ += value; } + void add_casting_speed(float value) { std::lock_guard lk(classMutex); casting_speed_ += value; } + void add_recovery_speed(float value) { std::lock_guard lk(classMutex); recovery_speed_ += value; } + void add_spell_reuse_speed(float value) { std::lock_guard lk(classMutex); spell_reuse_speed_ += value; } + void add_spell_multi_attack(float value) { std::lock_guard lk(classMutex); spell_multi_attack_ += value; } + void add_dps(float value) { std::lock_guard lk(classMutex); if(dps_ + value < 0.0f) dps_ = 0.0f; else dps_ += value; } + void add_dps_multiplier(float value) { std::lock_guard lk(classMutex); if(dps_multiplier_ + value < 0.0f) dps_multiplier_ = 0.0f; else dps_multiplier_ += value; } + void add_attackspeed(float value) { std::lock_guard lk(classMutex); if(attackspeed_ + value < 0.0f) attackspeed_ = 0.0f; else attackspeed_ += value; } + void add_haste(float value) { std::lock_guard lk(classMutex); if(haste_ + value < 0.0f) haste_ = 0.0f; else haste_ += value; } + void add_multi_attack(float value) { std::lock_guard lk(classMutex); if(multi_attack_ + value < 0.0f) multi_attack_ = 0.0f; else multi_attack_ += value; } + void add_flurry(float value) { std::lock_guard lk(classMutex); if(flurry_ + value < 0.0f) flurry_ = 0.0f; else flurry_ += value; } + void add_melee_ae(float value) { std::lock_guard lk(classMutex); if(melee_ae_ + value < 0.0f) melee_ae_ = 0.0f; else melee_ae_ += value; } + void add_strikethrough(float value) { std::lock_guard lk(classMutex); if(strikethrough_ + value < 0.0f) strikethrough_ = 0.0f; else strikethrough_ += value; } + void add_accuracy(float value) { std::lock_guard lk(classMutex); if(accuracy_ + value < 0.0f) accuracy_ = 0.0f; else accuracy_ += value; } + void add_offensivespeed(float value) { std::lock_guard lk(classMutex); if(offensivespeed_ + value < 0.0f) offensivespeed_ = 0.0f; else offensivespeed_ += value; } + void add_rain(float value) { std::lock_guard lk(classMutex); if(rain_ + value < 0.0f) rain_ = 0.0f; else rain_ += value; } + void add_wind(float value) { std::lock_guard lk(classMutex); if(wind_ + value < 0.0f) wind_ = 0.0f; else wind_ += value; } + + void set_alignment(int8 value) { std::lock_guard lk(classMutex); alignment_ = value; } + + void set_pet_id(int32 value) { std::lock_guard lk(classMutex); pet_id_ = value; } + void set_pet_name(std::string value) { std::lock_guard lk(classMutex); pet_name_ = value; } + + void set_pet_movement(int8 value) { std::lock_guard lk(classMutex); pet_movement_ = value; } + void set_pet_behavior(int8 value) { std::lock_guard lk(classMutex); pet_behavior_ = value; } + void set_pet_health_pct(float value) { std::lock_guard lk(classMutex); pet_health_pct_ = value; } + void set_pet_power_pct(float value) { std::lock_guard lk(classMutex); pet_power_pct_ = value; } + + void set_weight(int32 value) { std::lock_guard lk(classMutex); weight_ = value; } + void set_max_weight(int32 value) { std::lock_guard lk(classMutex); max_weight_ = value; } + + void set_vision(int32 value) { std::lock_guard lk(classMutex); vision_ = value; } + void set_breathe_underwater(int8 value) { std::lock_guard lk(classMutex); breathe_underwater_ = value; } + void set_drunk(float value) { std::lock_guard lk(classMutex); drunk_ = value; } + + void set_biography(std::string value) { std::lock_guard lk(classMutex); biography_ = value; } + + void set_power_regen(sint16 value) { std::lock_guard lk(classMutex); power_regen_ = value; } + void set_hp_regen(sint16 value) { std::lock_guard lk(classMutex); hp_regen_ = value; } + + void set_power_regen_override(int8 value) { std::lock_guard lk(classMutex); power_regen_override_ = value; } + void set_hp_regen_override(int8 value) { std::lock_guard lk(classMutex); hp_regen_override_ = value; } + + void set_water_type(int8 value) { std::lock_guard lk(classMutex); water_type_ = value; } + void set_flying_type(int8 value) { std::lock_guard lk(classMutex); flying_type_ = value; } + + void set_no_interrupt(int8 value) { std::lock_guard lk(classMutex); no_interrupt_ = value; } + + void set_interaction_flag(int8 value) { std::lock_guard lk(classMutex); interaction_flag_ = value; } + void set_tag1(int8 value) { std::lock_guard lk(classMutex); tag1_ = value; } + void set_mood(int16 value) { std::lock_guard lk(classMutex); mood_ = value; } + + void set_range_last_attack_time(int32 value) { std::lock_guard lk(classMutex); range_last_attack_time_ = value; } + void set_primary_last_attack_time(int32 value) { std::lock_guard lk(classMutex); primary_last_attack_time_ = value; } + void set_secondary_last_attack_time(int32 value) { std::lock_guard lk(classMutex); secondary_last_attack_time_ = value; } + + void set_primary_attack_delay(int16 value) { std::lock_guard lk(classMutex); primary_attack_delay_ = value; } + void set_secondary_attack_delay(int16 value) { std::lock_guard lk(classMutex); secondary_attack_delay_ = value; } + void set_ranged_attack_delay(int16 value) { std::lock_guard lk(classMutex); ranged_attack_delay_ = value; } + + void set_primary_weapon_type(int8 value) { std::lock_guard lk(classMutex); primary_weapon_type_ = value; } + void set_secondary_weapon_type(int8 value) { std::lock_guard lk(classMutex); secondary_weapon_type_ = value; } + void set_ranged_weapon_type(int8 value) { std::lock_guard lk(classMutex); ranged_weapon_type_ = value; } + + void set_primary_weapon_damage_low(int32 value) { std::lock_guard lk(classMutex); primary_weapon_damage_low_ = value; } + void set_primary_weapon_damage_high(int32 value) { std::lock_guard lk(classMutex); primary_weapon_damage_high_ = value; } + void set_secondary_weapon_damage_low(int32 value) { std::lock_guard lk(classMutex); secondary_weapon_damage_low_ = value; } + void set_secondary_weapon_damage_high(int32 value) { std::lock_guard lk(classMutex); secondary_weapon_damage_high_ = value; } + void set_ranged_weapon_damage_low(int32 value) { std::lock_guard lk(classMutex); ranged_weapon_damage_low_ = value; } + void set_ranged_weapon_damage_high(int32 value) { std::lock_guard lk(classMutex); ranged_weapon_damage_high_ = value; } + + void set_wield_type(int8 value) { std::lock_guard lk(classMutex); wield_type_ = value; } + void set_attack_type(int8 value) { std::lock_guard lk(classMutex); attack_type_ = value; } + + void set_primary_weapon_delay(int16 value) { std::lock_guard lk(classMutex); primary_weapon_delay_ = value; } + void set_secondary_weapon_delay(int16 value) { std::lock_guard lk(classMutex); secondary_weapon_delay_ = value; } + void set_ranged_weapon_delay(int16 value) { std::lock_guard lk(classMutex); ranged_weapon_delay_ = value; } + + void set_override_primary_weapon(int8 value) { std::lock_guard lk(classMutex); override_secondary_weapon_ = value; } + void set_override_secondary_weapon(int8 value) { std::lock_guard lk(classMutex); override_secondary_weapon_ = value; } + void set_override_ranged_weapon(int8 value) { std::lock_guard lk(classMutex); override_ranged_weapon_ = value; } + void set_friendly_target_npc(int8 value) { std::lock_guard lk(classMutex); friendly_target_npc_ = value; } + void set_last_claim_time(int32 value) { std::lock_guard lk(classMutex); last_claim_time_ = value; } + + void set_engaged_encounter(int8 value) { std::lock_guard lk(classMutex); engaged_encounter_ = value; } + + void set_first_world_login(int8 value) { std::lock_guard lk(classMutex); first_world_login_ = value; } + + void set_reload_player_spells(int8 value) { std::lock_guard lk(classMutex); reload_player_spells_ = value; } + + void set_group_loot_method(int8 value) { std::lock_guard lk(classMutex); group_loot_method_ = value; } + void set_group_loot_items_rarity(int8 value) { std::lock_guard lk(classMutex); group_loot_items_rarity_ = value; } + void set_group_auto_split(int8 value) { std::lock_guard lk(classMutex); group_auto_split_ = value; } + void set_group_default_yell(int8 value) { std::lock_guard lk(classMutex); group_default_yell_ = value; } + void set_group_autolock(int8 value) { std::lock_guard lk(classMutex); group_autolock_ = value; } + void set_group_lock_method(int8 value) { std::lock_guard lk(classMutex); group_lock_method_ = value; } + void set_group_solo_autolock(int8 value) { std::lock_guard lk(classMutex); group_solo_autolock_ = value; } + void set_group_auto_loot_method(int8 value) { std::lock_guard lk(classMutex); group_auto_loot_method_ = value; } + + void set_assist_auto_attack(int8 value) { std::lock_guard lk(classMutex); assist_auto_attack_ = value; } + + void set_action_state(std::string value) { std::lock_guard lk(classMutex); action_state_ = value; } + + void set_combat_action_state(std::string value) { std::lock_guard lk(classMutex); combat_action_state_ = value; } + + void set_max_spell_reduction(float value) { std::lock_guard lk(classMutex); max_spell_reduction_ = value; } + + void set_max_spell_reduction_override(int8 value) { std::lock_guard lk(classMutex); max_spell_reduction_override_ = value; } + + void ResetEffects(Spawn* spawn) + { + for(int i=0;i<45;i++){ + if(i<30){ + maintained_effects[i].spell_id = 0xFFFFFFFF; + if (spawn->IsPlayer()) + maintained_effects[i].icon = 0xFFFF; + + maintained_effects[i].spell = nullptr; + } + spell_effects[i].spell_id = 0xFFFFFFFF; + spell_effects[i].spell = nullptr; + } + } + + // maintained via their own mutex + SpellEffects spell_effects[45]; + MaintainedEffects maintained_effects[30]; +private: + std::string name_; + int8 class1_; + int8 class2_; + int8 class3_; + int8 race_; + int8 gender_; + int16 level_; + int16 max_level_; + int16 effective_level_; + int16 tradeskill_level_; + int16 tradeskill_max_level_; + + int8 cur_concentration_; + int8 max_concentration_; + int8 max_concentration_base_; + int16 cur_attack_; + int16 attack_base_; + int16 cur_mitigation_; + int16 max_mitigation_; + int16 mitigation_base_; + int16 avoidance_display_; + float cur_avoidance_; + int16 base_avoidance_pct_; + int16 avoidance_base_; + int16 max_avoidance_; + float parry_; + float parry_base_; + float deflection_; + int16 deflection_base_; + float block_; + int16 block_base_; + float riposte_; + float riposte_base_; + float str_; //int16 + float sta_; //int16 + float agi_;//int16 + float wis_;//int16 + float intel_;//int16 + float str_base_;//int16 + float sta_base_;//int16 + float agi_base_;//int16 + float wis_base_;//int16 + float intel_base_;//int16 + int16 heat_; + int16 cold_; + int16 magic_; + int16 mental_; + int16 divine_; + int16 disease_; + int16 poison_; + int16 disease_base_; + int16 cold_base_; + int16 divine_base_; + int16 magic_base_; + int16 mental_base_; + int16 heat_base_; + int16 poison_base_; + int16 elemental_base_; + int16 noxious_base_; + int16 arcane_base_; + int32 coin_copper_; + int32 coin_silver_; + int32 coin_gold_; + int32 coin_plat_; + int32 bank_coin_copper_; + int32 bank_coin_silver_; + int32 bank_coin_gold_; + int32 bank_coin_plat_; + + int32 status_points_; + std::string deity_; + int32 weight_; + int32 max_weight_; + int8 tradeskill_class1_; + int8 tradeskill_class2_; + int8 tradeskill_class3_; + int32 account_age_base_; + int8 account_age_bonus_[19]; + int16 absorb_; + int32 xp_; + int32 xp_needed_; + float xp_debt_; + int16 xp_yellow_; + int16 xp_yellow_vitality_bar_; + int16 xp_blue_vitality_bar_; + int16 xp_blue_; + int32 ts_xp_; + int32 ts_xp_needed_; + int16 tradeskill_exp_yellow_; + int16 tradeskill_exp_blue_; + int32 flags_; + int32 flags2_; + float xp_vitality_; + float tradeskill_xp_vitality_; + int16 mitigation_skill1_; + int16 mitigation_skill2_; + int16 mitigation_skill3_; + int16 mitigation_pve_; + int16 mitigation_pvp_; + float ability_modifier_; + float critical_mitigation_; + float block_chance_; + float uncontested_parry_; + float uncontested_block_; + float uncontested_dodge_; + float uncontested_riposte_; + + float crit_chance_; + float crit_bonus_; + float potency_; + float hate_mod_; + float reuse_speed_; + float casting_speed_; + float recovery_speed_; + float spell_reuse_speed_; + float spell_multi_attack_; + float dps_; + float dps_multiplier_; + float attackspeed_; + float haste_; + float multi_attack_; + float flurry_; + float melee_ae_; + float strikethrough_; + float accuracy_; + float offensivespeed_; + float rain_; + float wind_; + sint8 alignment_; + + int32 pet_id_; + std::string pet_name_; + float pet_health_pct_; + float pet_power_pct_; + int8 pet_movement_; + int8 pet_behavior_; + + int32 vision_; + int8 breathe_underwater_; + std::string biography_; + float drunk_; + + sint16 power_regen_; + sint16 hp_regen_; + + int8 power_regen_override_; + int8 hp_regen_override_; + + int8 water_type_; + int8 flying_type_; + + int8 no_interrupt_; + + int8 interaction_flag_; + int8 tag1_; + int16 mood_; + + int32 range_last_attack_time_; + int32 primary_last_attack_time_; + int32 secondary_last_attack_time_; + int16 primary_attack_delay_; + int16 secondary_attack_delay_; + int16 ranged_attack_delay_; + int8 primary_weapon_type_; + int8 secondary_weapon_type_; + int8 ranged_weapon_type_; + int32 primary_weapon_damage_low_; + int32 primary_weapon_damage_high_; + int32 secondary_weapon_damage_low_; + int32 secondary_weapon_damage_high_; + int32 ranged_weapon_damage_low_; + int32 ranged_weapon_damage_high_; + int8 wield_type_; + int8 attack_type_; + int16 primary_weapon_delay_; + int16 secondary_weapon_delay_; + int16 ranged_weapon_delay_; + + int8 override_primary_weapon_; + int8 override_secondary_weapon_; + int8 override_ranged_weapon_; + + int8 friendly_target_npc_; + int32 last_claim_time_; + + int8 engaged_encounter_; + + int8 first_world_login_; + int8 reload_player_spells_; + + int8 group_loot_method_; + int8 group_loot_items_rarity_; + int8 group_auto_split_; + int8 group_default_yell_; + int8 group_autolock_; + int8 group_lock_method_; + int8 group_solo_autolock_; + int8 group_auto_loot_method_; + + int8 assist_auto_attack_; + + std::string action_state_; + std::string combat_action_state_; + + float max_spell_reduction_; + int8 max_spell_reduction_override_; + + // when PacketStruct is fixed for C++17 this should become a shared_mutex and handle read/write lock + std::mutex classMutex; +}; + +struct WardInfo { + LuaSpell* Spell; + int32 BaseDamage; + int32 DamageLeft; + int8 WardType; + int8 DamageType; + bool keepWard; + int32 DamageAbsorptionPercentage; + int32 DamageAbsorptionMaxHealthPercent; + int32 RedirectDamagePercent; + + int32 LastRedirectDamage; + int32 LastAbsorbedDamage; + + int32 HitCount; + int32 MaxHitCount; + + bool AbsorbAllDamage; // damage is always absorbed, usually spells based on hits, when we pass damage in AddWard as 0 this will be set to true + + bool RoundTriggered; +}; + +#define WARD_TYPE_ALL 0 +#define WARD_TYPE_PHYSICAL 1 +#define WARD_TYPE_MAGICAL 2 + +struct Proc { + LuaSpell* spell; + Item* item; + float chance; + int32 spellid; + int8 health_ratio; + bool below_health; + bool target_health; + int8 damage_type; + bool extended_version; +}; + +#define PROC_TYPE_OFFENSIVE 1 +#define PROC_TYPE_DEFENSIVE 2 +#define PROC_TYPE_PHYSICAL_OFFENSIVE 3 +#define PROC_TYPE_PHYSICAL_DEFENSIVE 4 +#define PROC_TYPE_MAGICAL_OFFENSIVE 5 +#define PROC_TYPE_MAGICAL_DEFENSIVE 6 +#define PROC_TYPE_BLOCK 7 +#define PROC_TYPE_PARRY 8 +#define PROC_TYPE_RIPOSTE 9 +#define PROC_TYPE_EVADE 10 +#define PROC_TYPE_HEALING 11 +#define PROC_TYPE_BENEFICIAL 12 +#define PROC_TYPE_DEATH 13 +#define PROC_TYPE_KILL 14 +#define PROC_TYPE_DAMAGED 15 +#define PROC_TYPE_DAMAGED_MELEE 16 +#define PROC_TYPE_DAMAGED_MAGIC 17 +#define PROC_TYPE_RANGED_ATTACK 18 +#define PROC_TYPE_RANGED_DEFENSE 19 + +struct ThreatTransfer { + int32 Target; + float Amount; + LuaSpell* Spell; +}; + +#define DET_TYPE_TRAUMA 1 +#define DET_TYPE_ARCANE 2 +#define DET_TYPE_NOXIOUS 3 +#define DET_TYPE_ELEMENTAL 4 +#define DET_TYPE_CURSE 5 + +#define DISPELL_TYPE_CURE 0 +#define DISPELL_TYPE_DISPELL 1 + +#define CONTROL_EFFECT_TYPE_MEZ 1 +#define CONTROL_EFFECT_TYPE_STIFLE 2 +#define CONTROL_EFFECT_TYPE_DAZE 3 +#define CONTROL_EFFECT_TYPE_STUN 4 +#define CONTROL_EFFECT_TYPE_ROOT 5 +#define CONTROL_EFFECT_TYPE_FEAR 6 +#define CONTROL_EFFECT_TYPE_WALKUNDERWATER 7 +#define CONTROL_EFFECT_TYPE_JUMPUNDERWATER 8 +#define CONTROL_EFFECT_TYPE_INVIS 9 +#define CONTROL_EFFECT_TYPE_STEALTH 10 +#define CONTROL_EFFECT_TYPE_SNARE 11 +#define CONTROL_EFFECT_TYPE_FLIGHT 12 +#define CONTROL_EFFECT_TYPE_GLIDE 13 +#define CONTROL_EFFECT_TYPE_SAFEFALL 14 +#define CONTROL_MAX_EFFECTS 15 // always +1 to highest control effect + +#define IMMUNITY_TYPE_MEZ 1 +#define IMMUNITY_TYPE_STIFLE 2 +#define IMMUNITY_TYPE_DAZE 3 +#define IMMUNITY_TYPE_STUN 4 +#define IMMUNITY_TYPE_ROOT 5 +#define IMMUNITY_TYPE_FEAR 6 +#define IMMUNITY_TYPE_AOE 7 +#define IMMUNITY_TYPE_TAUNT 8 +#define IMMUNITY_TYPE_RIPOSTE 9 +#define IMMUNITY_TYPE_STRIKETHROUGH 10 + +//class Spell; +//class ZoneServer; + +//The entity class is for NPCs and Players, spawns which are able to fight +class Entity : public Spawn{ +public: + Entity(); + virtual ~Entity(); + + void DeleteSpellEffects(bool removeClient = false); + void RemoveSpells(bool unfriendlyOnly = false); + void MapInfoStruct(); + virtual float GetDodgeChance(); + virtual void AddMaintainedSpell(LuaSpell* spell); + virtual void AddSpellEffect(LuaSpell* spell, int32 override_expire_time = 0); + virtual void RemoveMaintainedSpell(LuaSpell* spell); + virtual void RemoveSpellEffect(LuaSpell* spell); + virtual void AddSkillBonus(int32 spell_id, int32 skill_id, float value); + void AddDetrimentalSpell(LuaSpell* spell, int32 override_expire_timestamp = 0); + DetrimentalEffects* GetDetrimentalEffect(int32 spell_id, Entity* caster); + virtual MaintainedEffects* GetMaintainedSpell(int32 spell_id); + void RemoveDetrimentalSpell(LuaSpell* spell); + void SetDeity(int8 new_deity){ + deity = new_deity; + } + int8 GetDeity(){ return deity; } + EquipmentItemList* GetEquipmentList(); + EquipmentItemList* GetAppearanceEquipmentList(); + + bool IsEntity(){ return true; } + float CalculateSkillStatChance(char* skill, int16 item_stat, float max_cap = 0.0f, float modifier = 0.0f, bool add_to_skill = false); + float CalculateSkillWithBonus(char* skillName, int16 item_stat, bool chance_skill_increase); + float GetRuleSkillMaxBonus(); + void CalculateBonuses(); + float CalculateLevelStatBonus(int16 stat_value); + void CalculateApplyWeight(); + void SetRegenValues(int16 effective_level); + float CalculateBonusMod(); + float CalculateDPSMultiplier(); + float CalculateCastingSpeedMod(); + + InfoStruct* GetInfoStruct(); + + int16 GetStr(); + int16 GetSta(); + int16 GetInt(); + int16 GetWis(); + int16 GetAgi(); + int16 GetPrimaryStat(); + + int16 GetHeatResistance(); + int16 GetColdResistance(); + int16 GetMagicResistance(); + int16 GetMentalResistance(); + int16 GetDivineResistance(); + int16 GetDiseaseResistance(); + int16 GetPoisonResistance(); + + int16 GetStrBase(); + int16 GetStaBase(); + int16 GetIntBase(); + int16 GetWisBase(); + int16 GetAgiBase(); + + int16 GetHeatResistanceBase(); + int16 GetColdResistanceBase(); + int16 GetMagicResistanceBase(); + int16 GetMentalResistanceBase(); + int16 GetDivineResistanceBase(); + int16 GetDiseaseResistanceBase(); + int16 GetPoisonResistanceBase(); + + int8 GetConcentrationCurrent(); + int8 GetConcentrationMax(); + + sint8 GetAlignment(); + void SetAlignment(sint8 new_value); + + bool HasMoved(bool include_heading); + void SetHPRegen(int16 new_val); + int16 GetHPRegen(); + void DoRegenUpdate(); + MaintainedEffects* GetFreeMaintainedSpellSlot(); + SpellEffects* GetFreeSpellEffectSlot(); + SpellEffects* GetSpellEffect(int32 id, Entity* caster = 0); + SpellEffects* GetSpellEffectBySpellType(int8 spell_type); + SpellEffects* GetSpellEffectWithLinkedTimer(int32 id, int32 linked_timer = 0, sint32 type_group_spell_id = 0, Entity* caster = 0); + LuaSpell* HasLinkedTimerID(LuaSpell* spell, Spawn* target = nullptr, bool stackWithOtherPlayers = true); + + //flags + int32 GetFlags() { return info_struct.get_flags(); } + int32 GetFlags2() { return info_struct.get_flags2(); } + bool query_flags(int flag) { + if (flag > 63) return false; + if (flag < 32) return ((info_struct.get_flags() & (1 << flag))?true:false); + return ((info_struct.get_flags2() & (1 << (flag - 32)))?true:false); + } + float GetMaxSpeed(); + void SetMaxSpeed(float val); + //combat stuff: + int32 GetRangeLastAttackTime(); + void SetRangeLastAttackTime(int32 time); + int16 GetRangeAttackDelay(); + int16 GetRangeWeaponDelay(); + void SetRangeWeaponDelay(int16 new_delay); + void SetRangeAttackDelay(int16 new_delay); + int32 GetPrimaryLastAttackTime(); + int16 GetPrimaryAttackDelay(); + void SetPrimaryAttackDelay(int16 new_delay); + void SetPrimaryLastAttackTime(int32 new_time); + void SetPrimaryWeaponDelay(int16 new_delay); + int32 GetSecondaryLastAttackTime(); + int16 GetSecondaryAttackDelay(); + void SetSecondaryAttackDelay(int16 new_delay); + void SetSecondaryLastAttackTime(int32 new_time); + void SetSecondaryWeaponDelay(int16 new_delay); + int32 GetPrimaryWeaponMinDamage(); + int32 GetPrimaryWeaponMaxDamage(); + int32 GetSecondaryWeaponMinDamage(); + int32 GetSecondaryWeaponMaxDamage(); + int32 GetRangedWeaponMinDamage(); + int32 GetRangedWeaponMaxDamage(); + int8 GetPrimaryWeaponType(); + int8 GetSecondaryWeaponType(); + int8 GetRangedWeaponType(); + int8 GetWieldType(); + int16 GetPrimaryWeaponDelay(); + int16 GetSecondaryWeaponDelay(); + bool IsDualWield(); + bool BehindTarget(Spawn* target); + bool FlankingTarget(Spawn* target); + + void GetWeaponDamage(Item* item, int32* low_damage, int32* high_damage); + void ChangePrimaryWeapon(); + void ChangeSecondaryWeapon(); + void ChangeRangedWeapon(); + void UpdateWeapons(); + int32 GetStrengthDamage(); + virtual Skill* GetSkillByName(const char* name, bool check_update = false); + virtual Skill* GetSkillByID(int32 id, bool check_update = false); + bool AttackAllowed(Entity* target, float distance = 0, bool range_attack = false); + Item* GetAmmoFromSlot(bool is_ammo, bool is_thrown); + bool PrimaryWeaponReady(); + bool SecondaryWeaponReady(); + bool RangeWeaponReady(); + void MeleeAttack(Spawn* victim, float distance, bool primary, bool multi_attack = false); + void RangeAttack(Spawn* victim, float distance, Item* weapon, Item* ammo, bool multi_attack = false); + bool SpellAttack(Spawn* victim, float distance, LuaSpell* luaspell, int8 damage_type, int32 low_damage, int32 high_damage, int8 crit_mod = 0, bool no_calcs = false, int8 override_packet_type = 0, bool take_power = false); + bool ProcAttack(Spawn* victim, int8 damage_type, int32 low_damage, int32 high_damage, string name, string success_msg, string effect_msg); + bool SpellHeal(Spawn* target, float distance, LuaSpell* luaspell, string heal_type, int32 low_heal, int32 high_heal, int8 crit_mod = 0, bool no_calcs = false, string custom_spell_name=""); + int8 DetermineHit(Spawn* victim, int8 type, int8 damage_type, float ToHitBonus, bool is_caster_spell, LuaSpell* lua_spell = nullptr); + float GetDamageTypeResistPercentage(int8 damage_type); + Skill* GetSkillByWeaponType(int8 type, int8 damage_type, bool update); + bool DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_damage, int32 high_damage, const char* spell_name, int8 crit_mod = 0, bool is_tick = false, bool no_damage_calcs = false, bool ignore_attacker = false, bool take_power = false, LuaSpell* spell = 0); + float CalculateMitigation(int8 type = DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE, int8 damage_type = 0, int16 attacker_level = 0, bool for_pvp = false); + void AddHate(Entity* attacker, sint32 hate); + bool CheckInterruptSpell(Entity* attacker); + bool CheckFizzleSpell(LuaSpell* spell); + void KillSpawn(Spawn* dead, int8 type = 0, int8 damage_type = 0, int16 kill_blow_type = 0); + void HandleDeathExperienceDebt(Spawn* killer); + void SetAttackDelay(bool primary = false, bool ranged = false); + float CalculateAttackSpeedMod(); + virtual void ProcessCombat(); + + bool EngagedInCombat(); + virtual void InCombat(bool val); + + bool IsCasting(); + void IsCasting(bool val); + void SetMount(int16 mount_id, int8 red = 0xFF, int8 green = 0xFF, int8 blue = 0xFF, bool setUpdateFlags = true) + { + if (mount_id == 0) { + EQ2_Color color; + color.red = 0; + color.green = 0; + color.blue = 0; + SetMountColor(&color); + SetMountSaddleColor(&color); + } + else + { + EQ2_Color color; + color.red = red; + color.green = green; + color.blue = blue; + SetMountColor(&color); + SetMountSaddleColor(&color); + } + SetInfo(&features.mount_model_type, mount_id, setUpdateFlags); + } + + void SetEquipment(Item* item, int8 slot = 255); + void SetEquipment(int8 slot, int16 type, int8 red, int8 green, int8 blue, int8 h_r, int8 h_g, int8 h_b){ + std::lock_guard lk(MEquipment); + if(slot >= NUM_SLOTS) + return; + + SetInfo(&equipment.equip_id[slot], type); + SetInfo(&equipment.color[slot].red, red); + SetInfo(&equipment.color[slot].green, green); + SetInfo(&equipment.color[slot].blue, blue); + SetInfo(&equipment.highlight[slot].red, h_r); + SetInfo(&equipment.highlight[slot].green, h_g); + SetInfo(&equipment.highlight[slot].blue, h_b); + } + void SetHairType(int16 new_val, bool setUpdateFlags = true){ + SetInfo(&features.hair_type, new_val, setUpdateFlags); + } + void SetHairColor1(EQ2_Color new_val, bool setUpdateFlags = true){ + SetInfo(&features.hair_color1, new_val, setUpdateFlags); + } + void SetHairColor2(EQ2_Color new_val, bool setUpdateFlags = true){ + SetInfo(&features.hair_color2, new_val, setUpdateFlags); + } + void SetSogaHairColor1(EQ2_Color new_val, bool setUpdateFlags = true){ + SetInfo(&features.soga_hair_color1, new_val, setUpdateFlags); + } + void SetSogaHairColor2(EQ2_Color new_val, bool setUpdateFlags = true){ + SetInfo(&features.soga_hair_color2, new_val, setUpdateFlags); + } + void SetHairHighlightColor(EQ2_Color new_val, bool setUpdateFlags = true){ + SetInfo(&features.hair_highlight_color, new_val, setUpdateFlags); + } + void SetSogaHairHighlightColor(EQ2_Color new_val, bool setUpdateFlags = true){ + SetInfo(&features.soga_hair_highlight_color, new_val, setUpdateFlags); + } + void SetHairColor(EQ2_Color new_val, bool setUpdateFlags = true){ + SetInfo(&features.hair_type_color, new_val, setUpdateFlags); + } + void SetSogaHairColor(EQ2_Color new_val, bool setUpdateFlags = true){ + SetInfo(&features.soga_hair_type_color, new_val, setUpdateFlags); + } + void SetHairTypeHighlightColor(EQ2_Color new_val, bool setUpdateFlags = true){ + SetInfo(&features.hair_type_highlight_color, new_val, setUpdateFlags); + } + void SetSogaHairTypeHighlightColor(EQ2_Color new_val, bool setUpdateFlags = true){ + SetInfo(&features.soga_hair_type_highlight_color, new_val, setUpdateFlags); + } + void SetFacialHairType(int16 new_val, bool setUpdateFlags = true){ + SetInfo(&features.hair_face_type, new_val, setUpdateFlags); + } + void SetFacialHairColor(EQ2_Color new_val, bool setUpdateFlags = true){ + SetInfo(&features.hair_face_color, new_val, setUpdateFlags); + } + void SetSogaFacialHairColor(EQ2_Color new_val, bool setUpdateFlags = true){ + SetInfo(&features.soga_hair_face_color, new_val, setUpdateFlags); + } + void SetFacialHairHighlightColor(EQ2_Color new_val, bool setUpdateFlags = true){ + SetInfo(&features.hair_face_highlight_color, new_val, setUpdateFlags); + } + void SetSogaFacialHairHighlightColor(EQ2_Color new_val, bool setUpdateFlags = true){ + SetInfo(&features.soga_hair_face_highlight_color, new_val, setUpdateFlags); + } + void SetWingType(int16 new_val, bool setUpdateFlags = true){ + SetInfo(&features.wing_type, new_val, setUpdateFlags); + } + void SetWingColor1(EQ2_Color new_val, bool setUpdateFlags = true){ + SetInfo(&features.wing_color1, new_val, setUpdateFlags); + } + void SetWingColor2(EQ2_Color new_val, bool setUpdateFlags = true){ + SetInfo(&features.wing_color2, new_val, setUpdateFlags); + } + void SetChestType(int16 new_val, bool setUpdateFlags = true){ + SetInfo(&features.chest_type, new_val, setUpdateFlags); + } + void SetLegsType(int16 new_val, bool setUpdateFlags = true){ + SetInfo(&features.legs_type, new_val, setUpdateFlags); + } + void SetSogaHairType(int16 new_val, bool setUpdateFlags = true){ + SetInfo(&features.soga_hair_type, new_val, setUpdateFlags); + } + void SetSogaFacialHairType(int16 new_val, bool setUpdateFlags = true){ + SetInfo(&features.soga_hair_face_type, new_val, setUpdateFlags); + } + void SetSogaChestType(int16 new_val, bool setUpdateFlags = true){ + SetInfo(&features.soga_chest_type, new_val, setUpdateFlags); + } + void SetSogaLegType(int16 new_val, bool setUpdateFlags = true){ + SetInfo(&features.soga_legs_type, new_val, setUpdateFlags); + } + void SetSkinColor(EQ2_Color color){ + SetInfo(&features.skin_color, color); + } + void SetSogaSkinColor(EQ2_Color color){ + SetInfo(&features.soga_skin_color, color); + } + void SetModelColor(EQ2_Color color){ + SetInfo(&features.model_color, color); + } + void SetSogaModelColor(EQ2_Color color){ + SetInfo(&features.soga_model_color, color); + } + void SetCombatVoice(int16 val, bool setUpdateFlags = true) { + SetInfo(&features.combat_voice, val, setUpdateFlags); + } + void SetEmoteVoice(int16 val, bool setUpdateFlags = true) { + SetInfo(&features.emote_voice, val, setUpdateFlags); + } + int16 GetCombatVoice(){ return features.combat_voice; } + int16 GetEmoteVoice(){ return features.emote_voice; } + int16 GetMount(){ return features.mount_model_type; } + void SetMountSaddleColor(EQ2_Color* color){ + SetInfo(&features.mount_saddle_color, *color); + } + void SetMountColor(EQ2_Color* color){ + SetInfo(&features.mount_color, *color); + } + void SetEyeColor(EQ2_Color eye_color){ + SetInfo(&features.eye_color, eye_color); + } + void SetSogaEyeColor(EQ2_Color eye_color){ + SetInfo(&features.soga_eye_color, eye_color); + } + int16 GetHairType(){ + return features.hair_type; + } + int16 GetFacialHairType(){ + return features.hair_face_type; + } + int16 GetWingType(){ + return features.wing_type; + } + int16 GetChestType(){ + return features.chest_type; + } + int16 GetLegsType(){ + return features.legs_type; + } + int16 GetSogaHairType(){ + return features.soga_hair_type; + } + int16 GetSogaFacialHairType(){ + return features.soga_hair_face_type; + } + int16 GetSogaChestType(){ + return features.soga_chest_type; + } + int16 GetSogaLegType(){ + return features.soga_legs_type; + } + EQ2_Color* GetSkinColor(){ + return &features.skin_color; + } + EQ2_Color* GetModelColor(){ + return &features.model_color; + } + EQ2_Color* GetSogaModelColor(){ + return &features.soga_model_color; + } + EQ2_Color* GetEyeColor(){ + return &features.eye_color; + } + EQ2_Color* GetMountSaddleColor(){ + return &features.mount_saddle_color; + } + EQ2_Color* GetMountColor(){ + return &features.mount_color; + } + // should only be accessed through MEquipment mutex + EQ2_Equipment equipment; + CharFeatures features; + + void AddSpellBonus(LuaSpell* spell, int16 type, float value, int64 class_req =0, vector race_req = vector(), vector faction_req = vector()); + BonusValues* GetSpellBonus(int32 spell_id); + vector* GetAllSpellBonuses(LuaSpell* spell); + bool CheckSpellBonusRemoval(LuaSpell* spell, int16 type); + void RemoveSpellBonus(const LuaSpell* spell, bool remove_all = false); + void RemoveAllSpellBonuses(); + void CalculateSpellBonuses(ItemStatsValues* stats); + void AddMezSpell(LuaSpell* spell); + void RemoveMezSpell(LuaSpell* spell); + void RemoveAllMezSpells(); + bool IsMezzed(); + void AddStifleSpell(LuaSpell* spell); + void RemoveStifleSpell(LuaSpell* spell); + bool IsStifled(); + void AddDazeSpell(LuaSpell* spell); + void RemoveDazeSpell(LuaSpell* spell); + bool IsDazed(); + void AddStunSpell(LuaSpell* spell); + void RemoveStunSpell(LuaSpell* spell); + bool IsStunned(); + bool IsMezzedOrStunned() {return IsMezzed() || IsStunned();} + void AddRootSpell(LuaSpell* spell); + void RemoveRootSpell(LuaSpell* spell); + bool IsRooted(); + void AddFearSpell(LuaSpell* spell); + void RemoveFearSpell(LuaSpell* spell); + bool IsFeared(); + void AddSnareSpell(LuaSpell* spell); + void RemoveSnareSpell(LuaSpell* spell); + void SetSnareValue(LuaSpell* spell, float snare_val); + bool IsSnared(); + float GetHighestSnare(); + + bool HasControlEffect(int8 type); + + void HaltMovement(); + + + void SetCombatPet(Entity* pet) { this->pet = pet; } + void SetCharmedPet(Entity* pet) { charmedPet = pet; } + void SetDeityPet(Entity* pet) { deityPet = pet; } + void SetCosmeticPet(Entity* pet) { cosmeticPet = pet; } + Entity* GetPet() { return pet; } + Entity* GetCharmedPet() { return charmedPet; } + Entity* GetDeityPet() { return deityPet; } + Entity* GetCosmeticPet() { return cosmeticPet; } + /// Check to see if the entity has a combat pet + /// True if the entity has a combat pet + bool HasPet() { return (pet || charmedPet) ? true : false; } + + void HideDeityPet(bool val); + void HideCosmeticPet(bool val); + void DismissPet(Entity* pet, bool from_death = false, bool spawnListLocked = false); + void DismissAllPets(bool from_death = false, bool spawnListLocked = false); + + void SetOwner(Entity* owner) { if (owner) { this->owner = owner->GetID(); } else { owner = 0; } } + Entity* GetOwner(); + int8 GetPetType() { return m_petType; } + void SetPetType(int8 val) { m_petType = val; } + void SetPetSpellID(int32 val) { m_petSpellID = val; } + int32 GetPetSpellID() { return m_petSpellID; } + void SetPetSpellTier(int8 val) { m_petSpellTier = val; } + int8 GetPetSpellTier() { return m_petSpellTier; } + bool IsDismissing() { return m_petDismissing; } + void SetDismissing(bool val) { m_petDismissing = val; } + + /// Creates a loot chest to drop in the world + /// Pointer to the chest + NPC* DropChest(); + + /// Add a ward to the entities ward list + /// Spell id of the ward to add + /// WardInfo* of the ward we are adding + void AddWard(int32 spellID, WardInfo* ward); + + /// Gets ward info for the given spell id + /// The spell id of the ward we want to get + /// WardInfo for the given spell id + WardInfo* GetWard(int32 spellID); + + /// Removes the ward with the given spell id + /// The spell id of the ward to remove + void RemoveWard(int32 spellID); + + /// Subtracts the given damage from the wards + /// The damage to subtract from the wards + /// The amount of damage left after wards + int32 CheckWards(Entity* attacker, int32 damage, int8 damage_type); + + map stats; + + /// Adds a proc to the list of current procs + /// The type of proc to add + /// The percent chance the proc has to go off + /// The item the proc is coming from if any + /// The spell the proc is coming from if any + void AddProc(int8 type, float chance, Item* item = 0, LuaSpell* spell = 0, int8 damage_type = 0, int8 hp_ratio = 0, bool below_health = false, bool target_health = false, bool extended_version = false); + + /// Removes a proc from the list of current procs + /// Item the proc is from + /// Spell the proc is from + void RemoveProc(Item* item = 0, LuaSpell* spell = 0); + + /// Cycles through the proc list and executes them if they can go off + /// The proc type to check + /// The target of the proc if it goes off + void CheckProcs(int8 type, Spawn* target); + + /// Clears the entire proc list + void ClearProcs(); + + float GetSpeed(); + float GetAirSpeed(); + float GetBaseSpeed() { return base_speed; } + void SetSpeed(float val, bool override_ = false) { if ((base_speed == 0.0f && val > 0.0f) || override_) base_speed = val; speed = val; } + void SetSpeedMultiplier(float val) { speed_multiplier = val; } + + void SetThreatTransfer(ThreatTransfer* transfer); + ThreatTransfer* GetThreatTransfer() { return m_threatTransfer; } + int8 GetTraumaCount(); + int8 GetArcaneCount(); + int8 GetNoxiousCount(); + int8 GetElementalCount(); + int8 GetCurseCount(); + int8 GetDetTypeCount(int8 det_type); + int8 GetDetCount(); + bool HasCurableDetrimentType(int8 det_type); + Mutex* GetDetrimentMutex(); + Mutex* GetMaintainedMutex(); + Mutex* GetSpellEffectMutex(); + void ClearAllDetriments(); + void CureDetrimentByType(int8 cure_count, int8 det_type, string cure_name, Entity* caster, int8 cure_level = 0); + void CureDetrimentByControlEffect(int8 cure_count, int8 det_type, string cure_name, Entity* caster, int8 cure_level = 0); + vector* GetDetrimentalSpellEffects(); + void RemoveEffectsFromLuaSpell(LuaSpell* spell); + virtual void RemoveSkillBonus(int32 spell_id); + + virtual bool CanSeeInvis(Entity* target); + void CancelAllStealth(); + bool IsStealthed(); + bool IsInvis(); + void AddInvisSpell(LuaSpell* spell); + void AddStealthSpell(LuaSpell* spell); + void RemoveStealthSpell(LuaSpell* spell); + void RemoveInvisSpell(LuaSpell* spell); + void AddWaterwalkSpell(LuaSpell* spell); + void AddWaterjumpSpell(LuaSpell* spell); + void RemoveWaterwalkSpell(LuaSpell* spell); + void RemoveWaterjumpSpell(LuaSpell* spell); + void AddAOEImmunity(LuaSpell* spell); + bool IsAOEImmune(); + void RemoveAOEImmunity(LuaSpell* spell); + void AddStunImmunity(LuaSpell* spell); + void RemoveStunImmunity(LuaSpell* spell); + bool IsStunImmune(); + void AddStifleImmunity(LuaSpell* spell); + void RemoveStifleImmunity(LuaSpell* spell); + bool IsStifleImmune(); + void AddMezImmunity(LuaSpell* spell); + void RemoveMezImmunity(LuaSpell* spell); + bool IsMezImmune(); + void AddRootImmunity(LuaSpell* spell); + void RemoveRootImmunity(LuaSpell* spell); + bool IsRootImmune(); + void AddFearImmunity(LuaSpell* spell); + void RemoveFearImmunity(LuaSpell* spell); + bool IsFearImmune(); + void AddDazeImmunity(LuaSpell* spell); + void RemoveDazeImmunity(LuaSpell* spell); + bool IsDazeImmune(); + void AddImmunity(LuaSpell* spell, int16 type); + void RemoveImmunity(LuaSpell* spell, int16 type); + bool IsImmune(int16 type); + void AddFlightSpell(LuaSpell* spell); + void RemoveFlightSpell(LuaSpell* spell); + void AddSafefallSpell(LuaSpell* spell); + void RemoveSafefallSpell(LuaSpell* spell); + void AddGlideSpell(LuaSpell* spell); + void RemoveGlideSpell(LuaSpell* spell); + + GroupMemberInfo* GetGroupMemberInfo() { return group_member_info; } + void SetGroupMemberInfo(GroupMemberInfo* info) { group_member_info = info; } + void UpdateGroupMemberInfo(bool inGroupMgrLock=false, bool groupMembersLocked=false); + + void CustomizeAppearance(PacketStruct* packet); + + Trade* trade; + + // Keep track of entities that hate this spawn. + set HatedBy; + std::mutex MHatedBy; + + bool IsAggroed() { + int32 size = 0; + + MHatedBy.lock(); + size = HatedBy.size(); + MHatedBy.unlock(); + + return size > 0; + } + + Mutex MCommandMutex; + + bool HasSeeInvisSpell() { return hasSeeInvisSpell; } + void SetSeeInvisSpell(bool val) { hasSeeInvisSpell = val; } + + bool HasSeeHideSpell() { return hasSeeHideSpell; } + void SetSeeHideSpell(bool val) { hasSeeHideSpell = val; } + + void SetInfoStruct(InfoStruct* struct_) { info_struct.SetInfoStruct(struct_); } + + std::string GetInfoStructString(std::string field); + int8 GetInfoStructInt8(std::string field); + int16 GetInfoStructInt16(std::string field); + int32 GetInfoStructInt32(std::string field); + int64 GetInfoStructInt64(std::string field); + sint8 GetInfoStructSInt8(std::string field); + sint16 GetInfoStructSInt16(std::string field); + sint32 GetInfoStructSInt32(std::string field); + sint64 GetInfoStructSInt64(std::string field); + int64 GetInfoStructUInt(std::string field); + sint64 GetInfoStructSInt(std::string field); + float GetInfoStructFloat(std::string field); + + + bool SetInfoStructString(std::string field, std::string value); + bool SetInfoStructUInt(std::string field, int64 value); + bool SetInfoStructSInt(std::string field, sint64 value); + bool SetInfoStructFloat(std::string field, float value); + + float CalculateSpellDamageReduction(float spellDamage, int16 competitorLevel); + sint32 CalculateHateAmount(Spawn* target, sint32 amt); + sint32 CalculateHealAmount(Spawn* target, sint32 amt, int8 crit_mod, bool* crit, bool skip_crit_mod = false); + sint32 CalculateDamageAmount(Spawn* target, sint32 damage, int8 base_type, int8 damage_type, LuaSpell* spell); + sint32 CalculateDamageAmount(Spawn* target, sint32 damage, int8 base_type, int8 damage_type, int8 spell_target_type); + sint32 CalculateFormulaByStat(sint32 value, int16 stat); + int32 CalculateFormulaByStat(int32 value, int16 stat); + int32 CalculateFormulaBonus(int32 value, float percent_bonus); + float CalculateSpellDamageReduction(float spellDamage, float resistancePercentage, int16 attackerLevel); + + float GetStat(int32 item_stat) { + float item_chance_or_skill = 0.0f; + MStats.lock(); + item_chance_or_skill = stats[item_stat]; + MStats.unlock(); + return item_chance_or_skill; + } + + bool IsEngagedInEncounter(Spawn** res = nullptr); + bool IsEngagedBySpawnID(int32 id); + void SendControlEffectDetailsToClient(Client* client); + + std::string GetControlEffectName(int8 control_effect_type) { + switch(control_effect_type) { + case CONTROL_EFFECT_TYPE_MEZ: { + return "Mesmerize"; + break; + } + case CONTROL_EFFECT_TYPE_STIFLE:{ + return "Stifle"; + break; + } + case CONTROL_EFFECT_TYPE_DAZE:{ + return "Daze"; + break; + } + case CONTROL_EFFECT_TYPE_STUN:{ + return "Stun"; + break; + } + case CONTROL_EFFECT_TYPE_ROOT:{ + return "Root"; + break; + } + case CONTROL_EFFECT_TYPE_FEAR:{ + return "Fear"; + break; + } + case CONTROL_EFFECT_TYPE_WALKUNDERWATER:{ + return "WalkUnderwater"; + break; + } + case CONTROL_EFFECT_TYPE_JUMPUNDERWATER:{ + return "JumpUnderwater"; + break; + } + case CONTROL_EFFECT_TYPE_INVIS:{ + return "Invisible"; + break; + } + case CONTROL_EFFECT_TYPE_STEALTH:{ + return "Stealth"; + break; + } + case CONTROL_EFFECT_TYPE_SNARE:{ + return "Snare"; + break; + } + case CONTROL_EFFECT_TYPE_FLIGHT:{ + return "Flight"; + break; + } + case CONTROL_EFFECT_TYPE_GLIDE:{ + return "Glide"; + break; + } + case CONTROL_EFFECT_TYPE_SAFEFALL:{ + return "SafeFall"; + break; + } + default: { + return "Undefined"; + break; + } + } + } + + void TerminateTrade(); + + void CalculateMaxReduction(); + // when PacketStruct is fixed for C++17 this should become a shared_mutex and handle read/write lock + std::mutex MEquipment; + std::mutex MStats; + + Mutex MMaintainedSpells; + Mutex MSpellEffects; +protected: + bool in_combat; + int8 m_petType; + int32 owner; + // m_petSpellID holds the spell id used to create/control this pet + int32 m_petSpellID; + int8 m_petSpellTier; + bool m_petDismissing; +private: + MutexList bonus_list; + map*> control_effects; + map*> immunities; + float max_speed; + int8 deity; + sint16 regen_hp_rate; + sint16 regen_power_rate; + float last_x; + float last_y; + float last_z; + float last_heading; + bool casting; + InfoStruct info_struct; + map det_count_list; + Mutex MDetriments; + vector detrimental_spell_effects; + // Pointers for the 4 types of pets (Summon, Charm, Deity, Cosmetic) + Entity* pet; + Entity* charmedPet; + Entity* deityPet; + Entity* cosmeticPet; + + // int32 = spell id, WardInfo* = pointer to ward info + map m_wardList; + + // int8 = type, vector = list of pointers to proc info + map > m_procList; + Mutex MProcList; + + /// Actually calls the lua script to cast the proc + /// Proc to be cast + /// Type of proc going off + /// Target of the proc + bool CastProc(Proc* proc, int8 type, Spawn* target); + + float base_speed; + float speed; + float speed_multiplier; + + map snare_values; + + ThreatTransfer* m_threatTransfer; + + GroupMemberInfo* group_member_info; + + bool hasSeeInvisSpell; + bool hasSeeHideSpell; + + // GETs + map > get_float_funcs; + map > get_int64_funcs; + map > get_int32_funcs; + map > get_int16_funcs; + map > get_int8_funcs; + + map > get_sint64_funcs; + map > get_sint32_funcs; + map > get_sint16_funcs; + map > get_sint8_funcs; + + map > get_string_funcs; + + // SETs + map > set_float_funcs; + map > set_int64_funcs; + map > set_int32_funcs; + map > set_int16_funcs; + map > set_int8_funcs; + + map > set_sint64_funcs; + map > set_sint32_funcs; + map > set_sint16_funcs; + map > set_sint8_funcs; + + map > set_string_funcs; +}; + +#endif diff --git a/source/WorldServer/Factions.cpp b/source/WorldServer/Factions.cpp new file mode 100644 index 0000000..00d6bcc --- /dev/null +++ b/source/WorldServer/Factions.cpp @@ -0,0 +1,183 @@ +/* + 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 . +*/ +#include "Factions.h" +#include "client.h" + +extern MasterFactionList master_faction_list; +extern ConfigReader configReader; + + +PlayerFaction::PlayerFaction(){ + MFactionUpdateNeeded.SetName("PlayerFaction::MFactionUpdateNeeded"); +} + +sint32 PlayerFaction::GetMaxValue(sint8 con){ + if(con < 0) + return con * 10000; + else + return (con * 10000) + 9999; +} + +sint32 PlayerFaction::GetMinValue(sint8 con){ + if(con <= 0) + return (con * 10000) - 9999; + else + return (con * 10000); +} + +bool PlayerFaction::ShouldAttack(int32 faction_id){ + return (GetCon(faction_id) <= -4); +} + +sint8 PlayerFaction::GetCon(int32 faction_id){ + if(faction_id <= 10){ + if(faction_id == 0) + return 0; + return (faction_id-5); + } + + sint32 value = GetFactionValue(faction_id); + if(value >= -9999 && value <= 9999) + return 0; + else{ + if(value>= 40000) + return 4; + else if(value <= -40000) + return -4; + return (sint8)(value/10000); + } +} + +int8 PlayerFaction::GetPercent(int32 faction_id){ + if(faction_id <= 10) + return 0; + sint8 con = GetCon(faction_id); + sint32 value = GetFactionValue(faction_id); + if(con != 0){ + if(value <= 0) + value *= -1; + if(con < 0) + con *= -1; + value -= con * 10000; + value *= 100; + return value / 10000; + } + else{ + value += 10000; + value *= 100; + return value / 20000; + } +} + +EQ2Packet* PlayerFaction::FactionUpdate(int16 version){ + EQ2Packet* ret = 0; + Faction* faction = 0; + PacketStruct* packet = configReader.getStruct("WS_FactionUpdate", version); + MFactionUpdateNeeded.lock(); + if(packet){ + packet->setArrayLengthByName("num_factions", faction_update_needed.size()); + for(int32 i=0;isetArrayDataByName("faction_id", faction->id, i); + packet->setArrayDataByName("name", faction->name.c_str(), i); + packet->setArrayDataByName("description", faction->description.c_str(), i); + packet->setArrayDataByName("category", faction->type.c_str(), i); + packet->setArrayDataByName("con", GetCon(faction->id), i); + packet->setArrayDataByName("percentage", GetPercent(faction->id), i); + packet->setArrayDataByName("value", GetFactionValue(faction->id), i); + } + } + ret = packet->serialize(); + safe_delete(packet); + } + faction_update_needed.clear(); + MFactionUpdateNeeded.unlock(); + return ret; +} + +sint32 PlayerFaction::GetFactionValue(int32 faction_id){ + if(faction_id <= 10) + return 0; + + //devn00b: This always seems to return 1, even if the player infact has no faction. since we handle this via a check in zoneserver.cpp (processfaction) + //if(faction_values.count(faction_id) == 0) + //return master_faction_list.GetDefaultFactionValue(faction_id); //faction_values[faction_id] = master_faction_list.GetDefaultFactionValue(faction_id); + + return faction_values[faction_id]; +} + +bool PlayerFaction::ShouldIncrease(int32 faction_id){ + if(faction_id <= 10 || master_faction_list.GetIncreaseAmount(faction_id) == 0) + return false; + return true; +} + +bool PlayerFaction::ShouldDecrease(int32 faction_id){ + if(faction_id <= 10 || master_faction_list.GetDecreaseAmount(faction_id) == 0) + return false; + return true; +} + +bool PlayerFaction::IncreaseFaction(int32 faction_id, int32 amount){ + if(faction_id <= 10) + return true; + bool ret = true; + if(amount == 0) + amount = master_faction_list.GetIncreaseAmount(faction_id); + faction_values[faction_id] += amount; + if(faction_values[faction_id] >= 50000){ + faction_values[faction_id] = 50000; + ret = false; + } + MFactionUpdateNeeded.lock(); + faction_update_needed.push_back(faction_id); + MFactionUpdateNeeded.unlock(); + return ret; +} + +bool PlayerFaction::DecreaseFaction(int32 faction_id, int32 amount){ + if(faction_id <= 10) + return true; + bool ret = true; + if(amount == 0) + amount = master_faction_list.GetDecreaseAmount(faction_id); + if(amount != 0){ + faction_values[faction_id] -= amount; + if(faction_values[faction_id] <= -50000){ + faction_values[faction_id] = -50000; + ret = false; + } + } + else + ret = false; + MFactionUpdateNeeded.lock(); + faction_update_needed.push_back(faction_id); + MFactionUpdateNeeded.unlock(); + return ret; +} + +bool PlayerFaction::SetFactionValue(int32 faction_id, sint32 value){ + faction_values[faction_id] = value; + MFactionUpdateNeeded.lock(); + faction_update_needed.push_back(faction_id); + MFactionUpdateNeeded.unlock(); + return true; +} diff --git a/source/WorldServer/Factions.h b/source/WorldServer/Factions.h new file mode 100644 index 0000000..ae88107 --- /dev/null +++ b/source/WorldServer/Factions.h @@ -0,0 +1,139 @@ +/* + 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 . +*/ +#ifndef EQ2_FACTIONS +#define EQ2_FACTIONS + +#include "../common/ConfigReader.h" +#include "../common/Mutex.h" + +struct Faction { + int32 id; + string name; + string type; + string description; + int16 negative_change; + int16 positive_change; + sint32 default_value; +}; + +class MasterFactionList{ +public: + MasterFactionList(){ + + } + ~MasterFactionList(){ + Clear(); + } + void Clear() { + map::iterator iter; + for(iter = global_faction_list.begin();iter != global_faction_list.end(); iter++){ + safe_delete(iter->second); + } + + hostile_factions.clear(); + friendly_factions.clear(); + } + sint32 GetDefaultFactionValue(int32 faction_id){ + if(global_faction_list.count(faction_id) > 0 && global_faction_list[faction_id]) + return global_faction_list[faction_id]->default_value; + return 0; + } + Faction* GetFaction(char* name){ + return faction_name_list[name]; + } + Faction* GetFaction(int32 id){ + if(global_faction_list.count(id) > 0) + return global_faction_list[id]; + return 0; + } + void AddFaction(Faction* faction){ + global_faction_list[faction->id] = faction; + faction_name_list[faction->name] = faction; + } + sint32 GetIncreaseAmount(int32 faction_id){ + if(global_faction_list.count(faction_id) > 0 && global_faction_list[faction_id]) + return global_faction_list[faction_id]->positive_change; + return 0; + } + sint32 GetDecreaseAmount(int32 faction_id){ + if(global_faction_list.count(faction_id) > 0 && global_faction_list[faction_id]) + return global_faction_list[faction_id]->negative_change; + return 0; + } + int32 GetFactionCount(){ + return global_faction_list.size(); + } + void AddHostileFaction(int32 faction_id, int32 hostile_faction_id){ + hostile_factions[faction_id].push_back(hostile_faction_id); + } + void AddFriendlyFaction(int32 faction_id, int32 friendly_faction_id){ + friendly_factions[faction_id].push_back(friendly_faction_id); + } + vector* GetFriendlyFactions(int32 faction_id){ + if(friendly_factions.count(faction_id) > 0) + return &friendly_factions[faction_id]; + else + return 0; + } + vector* GetHostileFactions(int32 faction_id){ + if(hostile_factions.count(faction_id) > 0) + return &hostile_factions[faction_id]; + else + return 0; + } + const char* GetFactionNameByID(int32 faction_id) { + if (faction_id > 0 && global_faction_list.count(faction_id) > 0) + return global_faction_list[faction_id]->name.c_str(); + return 0; + } +private: + map > friendly_factions; + map > hostile_factions; + map global_faction_list; + map faction_name_list; +}; + +class PlayerFaction{ +public: + PlayerFaction(); + sint32 GetMaxValue(sint8 con); + sint32 GetMinValue(sint8 con); + EQ2Packet* FactionUpdate(int16 version); + sint32 GetFactionValue(int32 faction_id); + bool ShouldIncrease(int32 faction_id); + bool ShouldDecrease(int32 faction_id); + bool IncreaseFaction(int32 faction_id, int32 amount = 0); + bool DecreaseFaction(int32 faction_id, int32 amount = 0); + bool SetFactionValue(int32 faction_id, sint32 value); + sint8 GetCon(int32 faction_id); + int8 GetPercent(int32 faction_id); + map* GetFactionValues(){ + return &faction_values; + } + bool ShouldAttack(int32 faction_id); + +private: + Mutex MFactionUpdateNeeded; + vector faction_update_needed; + map faction_values; + map faction_percent; +}; +#endif + diff --git a/source/WorldServer/GroundSpawn.cpp b/source/WorldServer/GroundSpawn.cpp new file mode 100644 index 0000000..93a4a09 --- /dev/null +++ b/source/WorldServer/GroundSpawn.cpp @@ -0,0 +1,575 @@ +/* + 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 . +*/ +#include "GroundSpawn.h" +#include "World.h" +#include "Spells.h" +#include "Rules/Rules.h" +#include "../common/MiscFunctions.h" +#include "../common/Log.h" + +extern ConfigReader configReader; +extern MasterSpellList master_spell_list; +extern World world; +extern RuleManager rule_manager; + +GroundSpawn::GroundSpawn(){ + packet_num = 0; + appearance.difficulty = 0; + spawn_type = 2; + appearance.pos.state = 129; + number_harvests = 0; + num_attempts_per_harvest = 0; + groundspawn_id = 0; + MHarvest.SetName("GroundSpawn::MHarvest"); + MHarvestUse.SetName("GroundSpawn::MHarvestUse"); + randomize_heading = true; // we by default randomize heading of groundspawns DB overrides +} + +GroundSpawn::~GroundSpawn(){ + +} + +EQ2Packet* GroundSpawn::serialize(Player* player, int16 version){ + return spawn_serialize(player, version); +} + +int8 GroundSpawn::GetNumberHarvests(){ + return number_harvests; +} + +void GroundSpawn::SetNumberHarvests(int8 val){ + number_harvests = val; +} + +int8 GroundSpawn::GetAttemptsPerHarvest(){ + return num_attempts_per_harvest; +} + +void GroundSpawn::SetAttemptsPerHarvest(int8 val){ + num_attempts_per_harvest = val; +} + +int32 GroundSpawn::GetGroundSpawnEntryID(){ + return groundspawn_id; +} + +void GroundSpawn::SetGroundSpawnEntryID(int32 val){ + groundspawn_id = val; +} + +void GroundSpawn::SetCollectionSkill(const char* val){ + if(val) + collection_skill = string(val); +} + +const char* GroundSpawn::GetCollectionSkill(){ + return collection_skill.c_str(); +} + +void GroundSpawn::ProcessHarvest(Client* client) { + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Process harvesting for player '%s' (%u)", client->GetPlayer()->GetName(), client->GetPlayer()->GetID()); + + MHarvest.lock(); + + vector* groundspawn_entries = GetZone()->GetGroundSpawnEntries(groundspawn_id); + vector* groundspawn_items = GetZone()->GetGroundSpawnEntryItems(groundspawn_id); + + Item* master_item = 0; + Item* master_rare = 0; + Item* item = 0; + Item* item_rare = 0; + + int16 lowest_skill_level = 0; + int16 table_choice = 0; + int32 item_choice = 0; + int32 rare_choice = 0; + int8 harvest_type = 0; + int32 item_harvested = 0; + int8 reward_total = 1; + int32 rare_harvested = 0; + int8 rare_item = 0; + bool is_collection = false; + + if (!groundspawn_entries || !groundspawn_items) { + LogWrite(GROUNDSPAWN__ERROR, 3, "GSpawn", "No groundspawn entries or items assigned to groundspawn id: %u", groundspawn_id); + client->Message(CHANNEL_COLOR_RED, "Error: There are no groundspawn entries or items assigned to groundspawn id: %u", groundspawn_id); + MHarvest.unlock(); + return; + } + + if (number_harvests == 0) { + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Total harvests depleated for groundspawn id: %u", groundspawn_id); + client->SimpleMessage(CHANNEL_COLOR_RED, "Error: This spawn has nothing more to harvest!"); + MHarvest.unlock(); + return; + } + + Skill* skill = 0; + if (collection_skill == "Collecting") { + skill = client->GetPlayer()->GetSkillByName("Gathering"); + is_collection = true; + } + else + skill = client->GetPlayer()->GetSkillByName(collection_skill.c_str()); // Fix: #576 - don't skill up yet with GetSkillByName(skill, true), we might be trying to harvest low level + + if (!skill) { + LogWrite(GROUNDSPAWN__WARNING, 3, "GSpawn", "Player '%s' lacks the skill: '%s'", client->GetPlayer()->GetName(), collection_skill.c_str()); + client->Message(CHANNEL_COLOR_RED, "Error: You do not have the '%s' skill!", collection_skill.c_str()); + MHarvest.unlock(); + return; + } + + int16 totalSkill = skill->current_val; + int32 skillID = master_item_list.GetItemStatIDByName(collection_skill); + int16 max_skill_req_groundspawn = 0; + if(skillID != 0xFFFFFFFF) + { + ((Entity*)client->GetPlayer())->MStats.lock(); + totalSkill += ((Entity*)client->GetPlayer())->stats[skillID]; + ((Entity*)client->GetPlayer())->MStats.unlock(); + } + + for (int8 i = 0; i < num_attempts_per_harvest; i++) { + vector mod_groundspawn_entries; + + if (groundspawn_entries) { + vector highest_match; + vector::iterator itr; + + GroundSpawnEntry* entry = 0; // current data + GroundSpawnEntry* selected_table = 0; // selected table data + + // first, iterate through groundspawn_entries, discard tables player cannot use + for (itr = groundspawn_entries->begin(); itr != groundspawn_entries->end(); itr++) { + entry = *itr; + + if(entry->min_skill_level > max_skill_req_groundspawn) + max_skill_req_groundspawn = entry->min_skill_level; + + // if player lacks skill, skip table + if (entry->min_skill_level > totalSkill) + continue; + // if bonus, but player lacks level, skip table + if (entry->bonus_table && (client->GetPlayer()->GetLevel() < entry->min_adventure_level)) + continue; + + // build modified entries table + mod_groundspawn_entries.push_back(entry); + LogWrite(GROUNDSPAWN__DEBUG, 5, "GSpawn", "Keeping groundspawn_entry: %i", entry->min_skill_level); + } + + // if anything remains, find lowest min_skill_level in remaining set(s) + if (mod_groundspawn_entries.size() > 0) { + vector::iterator itr; + GroundSpawnEntry* entry = 0; + + for (itr = mod_groundspawn_entries.begin(); itr != mod_groundspawn_entries.end(); itr++) { + entry = *itr; + + // find the low range of available tables for random roll + if (lowest_skill_level > entry->min_skill_level || lowest_skill_level == 0) + lowest_skill_level = entry->min_skill_level; + } + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Lowest Skill Level: %i", lowest_skill_level); + } + else { + // if no tables chosen, you must lack the skills + // TODO: move this check to LUA when harvest command is first selected + client->Message(CHANNEL_COLOR_RED, "You lack the skills to harvest this node!"); + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "All groundspawn_entry tables tossed! No Skills? Something broke?"); + MHarvest.unlock(); + return; + } + + // now roll to see which table to use + table_choice = MakeRandomInt(lowest_skill_level, totalSkill); + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Random INT for Table by skill level: %i", table_choice); + + int16 highest_score = 0; + for (itr = mod_groundspawn_entries.begin(); itr != mod_groundspawn_entries.end(); itr++) { + entry = *itr; + + // determines the highest min_skill_level in the current set of tables (if multiple tables) + if (table_choice >= entry->min_skill_level && (highest_score == 0 || highest_score < table_choice)) { + // removes old highest for the new one + highest_match.clear(); + highest_score = entry->min_skill_level; + } + // if the score = level, push into highest_match set + if (highest_score == entry->min_skill_level) + highest_match.push_back(entry); + } + + // if there is STILL more than 1 table player qualifies for, rand() and pick one + if (highest_match.size() > 1) { + int16 rand_index = rand() % highest_match.size(); + selected_table = highest_match.at(rand_index); + } + else if (highest_match.size() > 0) + selected_table = highest_match.at(0); + + // by this point, we should have 1 table who's min skill matches the score (selected_table) + if (selected_table) { + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Using Table: %i, %i, %i, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %i", + selected_table->min_skill_level, + selected_table->min_adventure_level, + selected_table->bonus_table, + selected_table->harvest1, + selected_table->harvest3, + selected_table->harvest5, + selected_table->harvest_imbue, + selected_table->harvest_rare, + selected_table->harvest10, + selected_table->harvest_coin); + + + // roll 1-100 for chance-to-harvest percentage + float chance = MakeRandomFloat(0, 100); + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Random FLOAT for harvest percentages: %.2f", chance); + + // starting with the lowest %, select a harvest type + reward qty + if (chance <= selected_table->harvest10 && is_collection == false) { + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Harvest 10 items + Rare Item from table : %i", selected_table->min_skill_level); + harvest_type = 6; + reward_total = 10; + } + else if (chance <= selected_table->harvest_rare && is_collection == false) { + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Harvest Rare Item from table : %i", selected_table->min_skill_level); + harvest_type = 5; + } + else if (chance <= selected_table->harvest_imbue && is_collection == false) { + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Harvest Imbue Item from table : %i", selected_table->min_skill_level); + harvest_type = 4; + } + else if (chance <= selected_table->harvest5 && is_collection == false) { + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Harvest 5 Items from table : %i", selected_table->min_skill_level); + harvest_type = 3; + reward_total = 5; + } + else if (chance <= selected_table->harvest3 && is_collection == false) { + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Harvest 3 Items from table : %i", selected_table->min_skill_level); + harvest_type = 2; + reward_total = 3; + } + else if (chance <= selected_table->harvest1 || totalSkill >= skill->max_val || is_collection) { + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Harvest 1 Item from table : %i", selected_table->min_skill_level); + harvest_type = 1; + } + else + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Harvest nothing..."); + + float node_maxskill_multiplier = rule_manager.GetGlobalRule(R_Player, HarvestSkillUpMultiplier)->GetFloat(); + if(node_maxskill_multiplier <= 0.0f) { + node_maxskill_multiplier = 1.0f; + } + int16 skillup_max_skill_allowed = (int16)((float)max_skill_req_groundspawn*node_maxskill_multiplier); + if (!is_collection && skill && skill->current_val < skillup_max_skill_allowed) { + skill = client->GetPlayer()->GetSkillByName(collection_skill.c_str(), true); // Fix: #576 - skill up after min skill and adv level checks + } + } + + // once you know how many and what type of item to harvest, pick an item from the list + if (harvest_type) { + vector mod_groundspawn_items; + vector mod_groundspawn_rares; + vector mod_groundspawn_imbue; + + vector::iterator itr; + GroundSpawnEntryItem* entry = 0; + + // iterate through groundspawn_items, discard items player cannot roll for + for (itr = groundspawn_items->begin(); itr != groundspawn_items->end(); itr++) { + entry = *itr; + + // if this is a Rare, or an Imbue, but is_rare flag is 0, skip item + if ((harvest_type == 5 || harvest_type == 4) && entry->is_rare == 0) + continue; + // if it is a 1, 3, or 5 and is_rare = 1, skip + else if (harvest_type < 4 && entry->is_rare == 1) + continue; + + // if the grid_id on the item matches player grid, or is 0, keep the item + if (!entry->grid_id || (entry->grid_id == client->GetPlayer()->GetLocation())) { + // build modified entries table + if ((entry->is_rare == 1 && harvest_type == 5) || (entry->is_rare == 1 && harvest_type == 6)) { + // if the matching item is rare, or harvest10 push to mod rares + mod_groundspawn_rares.push_back(entry); + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Keeping groundspawn_rare_item: %u", entry->item_id); + } + if (entry->is_rare == 0 && harvest_type != 4 && harvest_type != 5) { + // if the matching item is normal,or harvest 10 push to mod items + mod_groundspawn_items.push_back(entry); + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Keeping groundspawn_common_item: %u", entry->item_id); + } + if (entry->is_rare == 2 && harvest_type == 4) { + // if the matching item is imbue item, push to mod imbue + mod_groundspawn_imbue.push_back(entry); + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Keeping groundspawn_imbue_item: %u", entry->item_id); + } + } + } + + // if any items remain in the list, random to see which one gets awarded + if (mod_groundspawn_items.size() > 0) { + // roll to see which item index to use + item_choice = rand() % mod_groundspawn_items.size(); + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Random INT for which item to award: %i", item_choice); + + // set item_id to be awarded + item_harvested = mod_groundspawn_items[item_choice]->item_id; + + // if reward is rare, set flag + rare_item = mod_groundspawn_items[item_choice]->is_rare; + + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Item ID to award: %u, Rare = %i", item_harvested, item_rare); + + // if 10+rare, handle additional "rare" reward + if (harvest_type == 6) { + // make sure there is a rare table to choose from! + if (mod_groundspawn_rares.size() > 0) { + // roll to see which rare index to use + rare_choice = rand() % mod_groundspawn_rares.size(); + + // set (rare) item_id to be awarded + rare_harvested = mod_groundspawn_rares[rare_choice]->item_id; + + // we're picking a rare here, so obviously this is true ;) + rare_item = 1; + + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "RARE Item ID to award: %u", rare_harvested); + } + else { + // all rare entries were eliminated above, or none are assigned. Either way, shouldn't be here! + LogWrite(GROUNDSPAWN__ERROR, 3, "GSpawn", "Groundspawn Entry for '%s' (%i) has no RARE items!", GetName(), GetID()); + } + } + } + else if (mod_groundspawn_rares.size() > 0) { + // roll to see which rare index to use + item_choice = rand() % mod_groundspawn_rares.size(); + + // set (rare) item_id to be awarded + item_harvested = mod_groundspawn_rares[item_choice]->item_id; + + // we're picking a rare here, so obviously this is true ;) + rare_item = 1; + + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "RARE Item ID to award: %u", rare_harvested); + } + else if (mod_groundspawn_imbue.size() > 0) { + // roll to see which rare index to use + item_choice = rand() % mod_groundspawn_imbue.size(); + + // set (rare) item_id to be awarded + item_harvested = mod_groundspawn_imbue[item_choice]->item_id; + + // we're picking a rare here, so obviously this is true ;) + rare_item = 0; + + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "imbue Item ID to award: %u", rare_harvested); + } + + + + + else { + // all item entries were eliminated above, or none are assigned. Either way, shouldn't be here! + LogWrite(GROUNDSPAWN__ERROR, 0, "GSpawn", "Groundspawn Entry for '%s' (%i) has no items!", GetName(), GetID()); + } + + // if an item was harvested, send updates to client, add item to inventory + if (item_harvested) { + char tmp[200] = { 0 }; + + // set Normal item harvested + master_item = master_item_list.GetItem(item_harvested); + if (master_item) { + // set details of Normal item + item = new Item(master_item); + // set how many of this item the player receives + item->details.count = reward_total; + + // chat box update for normal item (todo: verify output text) + client->Message(CHANNEL_HARVESTING, "You %s %i %s from the %s.", GetHarvestMessageName(true).c_str(), item->details.count, item->CreateItemLink(client->GetVersion(), true).c_str(), GetName()); + // add Normal item to player inventory + bool itemDeleted = false; + client->AddItem(item, &itemDeleted); + + if(!itemDeleted) { + //Check if the player has a harvesting quest for this + client->GetPlayer()->CheckQuestsHarvestUpdate(item, reward_total); + + // if this is a 10+rare, handle sepErately + if (harvest_type == 6 && rare_item == 1) { + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Item ID %u is Normal. Qty %i", item_harvested, item->details.count); + + // send Normal harvest message to client + sprintf(tmp, "\\#64FFFFYou have %s:\12\\#C8FFFF%i %s", GetHarvestMessageName().c_str(), item->details.count, item->name.c_str()); + client->SendPopupMessage(10, tmp, "ui_harvested_normal", 2.25, 0xFF, 0xFF, 0xFF); + client->GetPlayer()->UpdatePlayerStatistic(STAT_PLAYER_ITEMS_HARVESTED, item->details.count); + + // set Rare item harvested + master_rare = master_item_list.GetItem(rare_harvested); + if (master_rare) { + // set details of Rare item + item_rare = new Item(master_rare); + // count of Rare is always 1 + item_rare->details.count = 1; + + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Item ID %u is RARE!", rare_harvested); + + // send Rare harvest message to client + sprintf(tmp, "\\#FFFF6ERare item found!\12%s: \\#C8FFFF%i %s", GetHarvestMessageName().c_str(), item_rare->details.count, item_rare->name.c_str()); + client->Message(CHANNEL_HARVESTING, "You have found a rare item!"); + client->SendPopupMessage(11, tmp, "ui_harvested_rare", 2.25, 0xFF, 0xFF, 0xFF); + client->GetPlayer()->UpdatePlayerStatistic(STAT_PLAYER_RARES_HARVESTED, item_rare->details.count); + + // chat box update for rare item (todo: verify output text) + client->Message(CHANNEL_HARVESTING, "You %s %i %s from the %s.", GetHarvestMessageName(true).c_str(), item_rare->details.count, item->CreateItemLink(client->GetVersion(), true).c_str(), GetName()); + // add Rare item to player inventory + client->AddItem(item_rare); + //Check if the player has a harvesting quest for this + client->GetPlayer()->CheckQuestsHarvestUpdate(item_rare, 1); + } + } + else if (rare_item == 1) { + // if harvest signaled rare or imbue type + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Item ID %u is RARE! Qty: %i", item_harvested, item->details.count); + + // send Rare harvest message to client + sprintf(tmp, "\\#FFFF6ERare item found!\12%s: \\#C8FFFF%i %s", GetHarvestMessageName().c_str(), item->details.count, item->name.c_str()); + client->Message(CHANNEL_HARVESTING, "You have found a rare item!"); + client->SendPopupMessage(11, tmp, "ui_harvested_rare", 2.25, 0xFF, 0xFF, 0xFF); + client->GetPlayer()->UpdatePlayerStatistic(STAT_PLAYER_RARES_HARVESTED, item->details.count); + } + else { + // send Normal harvest message to client + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Item ID %u is Normal. Qty %i", item_harvested, item->details.count); + sprintf(tmp, "\\#64FFFFYou have %s:\12\\#C8FFFF%i %s", GetHarvestMessageName().c_str(), item->details.count, item->name.c_str()); + client->SendPopupMessage(10, tmp, "ui_harvested_normal", 2.25, 0xFF, 0xFF, 0xFF); + client->GetPlayer()->UpdatePlayerStatistic(STAT_PLAYER_ITEMS_HARVESTED, item->details.count); + } + + } + } + else { + // error! + LogWrite(GROUNDSPAWN__ERROR, 0, "GSpawn", "Error: Item ID Not Found - %u", item_harvested); + client->Message(CHANNEL_COLOR_RED, "Error: Unable to find item id %u", item_harvested); + } + // decrement # of pulls on this node before it despawns + number_harvests--; + } + else { + // if no item harvested + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "No item_harvested"); + client->Message(CHANNEL_HARVESTING, "You failed to %s anything from %s.", GetHarvestMessageName(true, true).c_str(), GetName()); + } + } + else { + // if no harvest type + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "No harvest_type"); + client->Message(CHANNEL_HARVESTING, "You failed to %s anything from %s.", GetHarvestMessageName(true, true).c_str(), GetName()); + } + } + } // cycle through num_attempts_per_harvest + MHarvest.unlock(); + + LogWrite(GROUNDSPAWN__DEBUG, 0, "GSpawn", "Process harvest complete for player '%s' (%u)", client->GetPlayer()->GetName(), client->GetPlayer()->GetID()); +} + +string GroundSpawn::GetHarvestMessageName(bool present_tense, bool failure){ + string ret = ""; + if((collection_skill == "Gathering" ||collection_skill == "Collecting") && !present_tense) + ret = "gathered"; + else if(collection_skill == "Gathering" || collection_skill == "Collecting") + ret = "gather"; + else if(collection_skill == "Mining" && !present_tense) + ret = "mined"; + else if(collection_skill == "Mining") + ret = "mine"; + else if (collection_skill == "Fishing" && !present_tense) + ret = "fished"; + else if(collection_skill == "Fishing") + ret = "fish"; + else if(collection_skill == "Trapping" && !present_tense && !failure) + ret = "acquired"; + else if(collection_skill == "Trapping" && failure) + ret = "trap"; + else if(collection_skill == "Trapping") + ret = "acquire"; + else if(collection_skill == "Foresting" && !present_tense) + ret = "forested"; + else if(collection_skill == "Foresting") + ret = "forest"; + else if (collection_skill == "Collecting") + ret = "collect"; + return ret; +} + +string GroundSpawn::GetHarvestSpellType(){ + string ret = ""; + if(collection_skill == "Gathering" || collection_skill == "Collecting") + ret = "gather"; + else if(collection_skill == "Mining") + ret = "mine"; + else if(collection_skill == "Trapping") + ret = "trap"; + else if(collection_skill == "Foresting") + ret = "chop"; + else if(collection_skill == "Fishing") + ret = "fish"; + return ret; +} + +string GroundSpawn::GetHarvestSpellName() { + string ret = ""; + if (collection_skill == "Collecting") + ret = "Gathering"; + else + ret = collection_skill; + return ret; +} + +void GroundSpawn::HandleUse(Client* client, string type){ + if(!client || type.length() == 0) + return; + //The following check disables the use of the groundspawn if spawn access is not granted + if (client) { + bool meets_quest_reqs = MeetsSpawnAccessRequirements(client->GetPlayer()); + if (!meets_quest_reqs && (GetQuestsRequiredOverride() & 2) == 0) + return; + else if (meets_quest_reqs && appearance.show_command_icon != 1) + return; + } + + MHarvestUse.lock(); + if (type == GetHarvestSpellType() && MeetsSpawnAccessRequirements(client->GetPlayer())) { + Spell* spell = master_spell_list.GetSpellByName(GetHarvestSpellName().c_str()); + if (spell) + client->GetCurrentZone()->ProcessSpell(spell, client->GetPlayer(), client->GetPlayer()->GetTarget(), true, true); + } + else if (appearance.show_command_icon == 1 && MeetsSpawnAccessRequirements(client->GetPlayer())) { + EntityCommand* entity_command = FindEntityCommand(type); + if (entity_command) + client->GetCurrentZone()->ProcessEntityCommand(entity_command, client->GetPlayer(), client->GetPlayer()->GetTarget()); + } + MHarvestUse.unlock(); +} diff --git a/source/WorldServer/GroundSpawn.h b/source/WorldServer/GroundSpawn.h new file mode 100644 index 0000000..f830686 --- /dev/null +++ b/source/WorldServer/GroundSpawn.h @@ -0,0 +1,86 @@ +/* + 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 . +*/ +#ifndef __EQ2_GroundSpawn__ +#define __EQ2_GroundSpawn__ + +#include "Spawn.h" +#include "client.h" +#include "../common/Mutex.h" + +class GroundSpawn : public Spawn { +public: + GroundSpawn(); + virtual ~GroundSpawn(); + GroundSpawn* Copy(){ + GroundSpawn* new_spawn = new GroundSpawn(); + new_spawn->size = size; + new_spawn->SetPrimaryCommands(&primary_command_list); + new_spawn->SetSecondaryCommands(&secondary_command_list); + new_spawn->database_id = database_id; + new_spawn->primary_command_list_id = primary_command_list_id; + new_spawn->secondary_command_list_id = secondary_command_list_id; + memcpy(&new_spawn->appearance, &appearance, sizeof(AppearanceData)); + new_spawn->faction_id = faction_id; + new_spawn->target = 0; + new_spawn->SetTotalHP(GetTotalHP()); + new_spawn->SetTotalPower(GetTotalPower()); + new_spawn->SetHP(GetHP()); + new_spawn->SetPower(GetPower()); + new_spawn->SetNumberHarvests(number_harvests); + new_spawn->SetAttemptsPerHarvest(num_attempts_per_harvest); + new_spawn->SetGroundSpawnEntryID(groundspawn_id); + new_spawn->SetCollectionSkill(collection_skill.c_str()); + SetQuestsRequired(new_spawn); + new_spawn->forceMapCheck = forceMapCheck; + new_spawn->SetOmittedByDBFlag(IsOmittedByDBFlag()); + new_spawn->SetLootTier(GetLootTier()); + new_spawn->SetLootDropType(GetLootDropType()); + new_spawn->SetRandomizeHeading(GetRandomizeHeading()); + return new_spawn; + } + bool IsGroundSpawn(){ return true; } + EQ2Packet* serialize(Player* player, int16 version); + int8 GetNumberHarvests(); + void SetNumberHarvests(int8 val); + int8 GetAttemptsPerHarvest(); + void SetAttemptsPerHarvest(int8 val); + int32 GetGroundSpawnEntryID(); + void SetGroundSpawnEntryID(int32 val); + void ProcessHarvest(Client* client); + void SetCollectionSkill(const char* val); + const char* GetCollectionSkill(); + string GetHarvestMessageName(bool present_tense = false, bool failure = false); + string GetHarvestSpellType(); + string GetHarvestSpellName(); + void HandleUse(Client* client, string type); + + void SetRandomizeHeading(bool val) { randomize_heading = val; } + bool GetRandomizeHeading() { return randomize_heading; } +private: + int8 number_harvests; + int8 num_attempts_per_harvest; + int32 groundspawn_id; + string collection_skill; + Mutex MHarvest; + Mutex MHarvestUse; + bool randomize_heading; +}; +#endif + diff --git a/source/WorldServer/Guilds/Guild.cpp b/source/WorldServer/Guilds/Guild.cpp new file mode 100644 index 0000000..0c74ad1 --- /dev/null +++ b/source/WorldServer/Guilds/Guild.cpp @@ -0,0 +1,2347 @@ +/* + 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 . +*/ + +#include +#include +#include +#include "Guild.h" +#include "../Player.h" +#include "../client.h" +#include "../World.h" +#include "../zoneserver.h" +#include "../WorldDatabase.h" +#include "../../common/Log.h" + +extern ConfigReader configReader; +extern ZoneList zone_list; +extern WorldDatabase database; +extern World world; + +/*************************************************************************************************************************************************** + * GUILD + ***************************************************************************************************************************************************/ + +Guild::Guild() { + id = 0; + memset(name, 0, sizeof(name)); + level = 1; + formed_date = 0; + memset(motd, 0, sizeof(motd)); + exp_current = 111; + exp_to_next_level = 2521; + recruiting_min_level = 1; + recruiting_play_style = GUILD_RECRUITING_PLAYSTYLE_NONE; + recruiting_flags.Put(GUILD_RECRUITING_FLAG_TRAINING, 0); + recruiting_flags.Put(GUILD_RECRUITING_FLAG_FIGHTERS, 0); + recruiting_flags.Put(GUILD_RECRUITING_FLAG_PRIESTS, 0); + recruiting_flags.Put(GUILD_RECRUITING_FLAG_SCOUTS, 0); + recruiting_flags.Put(GUILD_RECRUITING_FLAG_MAGES, 0); + recruiting_flags.Put(GUILD_RECRUITING_FLAG_TRADESKILLERS, 0); + recruiting_desc_tags.Put(0, GUILD_RECRUITING_DESC_TAG_NONE); + recruiting_desc_tags.Put(1, GUILD_RECRUITING_DESC_TAG_NONE); + recruiting_desc_tags.Put(2, GUILD_RECRUITING_DESC_TAG_NONE); + recruiting_desc_tags.Put(3, GUILD_RECRUITING_DESC_TAG_NONE); + banks[0].name = "Bank 1"; + banks[1].name = "Bank 2"; + banks[2].name = "Bank 3"; + banks[3].name = "Bank 4"; + save_needed = false; + member_save_needed = false; + events_save_needed = false; + ranks_save_needed = false; + event_filters_save_needed = false; + points_history_save_needed = false; + recruiting_save_needed = false; + mMembers.SetName("Guild::members"); +} + +Guild::~Guild() { + map::iterator guild_member_itr; + + mMembers.writelock(__FUNCTION__, __LINE__); + for (guild_member_itr = members.begin(); guild_member_itr != members.end(); guild_member_itr++) { + deque::iterator point_history_itr; + for (point_history_itr = guild_member_itr->second->point_history.begin(); point_history_itr != guild_member_itr->second->point_history.end(); point_history_itr++) + safe_delete(*point_history_itr); + safe_delete_array(guild_member_itr->second->recruiter_picture_data); + safe_delete(guild_member_itr->second); + } + mMembers.releasewritelock(__FUNCTION__, __LINE__); + + deque::iterator guild_events_itr; + for (guild_events_itr = guild_events.begin(); guild_events_itr != guild_events.end(); guild_events_itr++) + safe_delete(*guild_events_itr); + MutexMap*>::iterator permissions_itr = permissions.begin(); + while (permissions_itr.Next()) + safe_delete(permissions_itr.second); + MutexMap*>::iterator filter_itr = event_filters.begin(); + while (filter_itr.Next()) + safe_delete(filter_itr.second); + for (int32 i = 0; i < 4; i++) { + deque::iterator bank_event_itr; + for (bank_event_itr = banks[i].events.begin(); bank_event_itr != banks[i].events.end(); bank_event_itr++) + safe_delete(*bank_event_itr); + } +} + +void Guild::SetName(const char* name, bool send_packet) { + + assert(name); + + strncpy(this->name, name, sizeof(this->name)); + if (send_packet) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "Setting Guild Name to '%s'...", name); + SendGuildUpdate(); + save_needed = true; + } +} + +void Guild::SetLevel(int8 level, bool send_packet) { + + this->level = level; + if (send_packet) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "Setting Guild Level to %i...", level); + SendGuildUpdate(); + save_needed = true; + } +} + +void Guild::SetMOTD(const char *motd, bool send_packet) { + + assert(motd); + + strncpy(this->motd, motd, sizeof(this->motd)); + if (send_packet) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "Setting Guild MOTD text:\n'%s'", motd); + SendGuildUpdate(); + save_needed = true; + } +} + +void Guild::SetEXPCurrent(int64 exp, bool send_packet) { + + exp_current = exp; + if (send_packet) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "Setting Guild Current XP to %u", exp); + SendGuildUpdate(); + save_needed = true; + } +} + +void Guild::AddEXPCurrent(sint64 exp, bool send_packet) { + + bool ret = false; + char message[128]; + char adjective[16]; + + if (exp > 0 && level < GUILD_MAX_LEVEL) { + exp_current += exp; + if (exp_current >= exp_to_next_level) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "Guild %s Level UP! New Level: %i (current XP: %ul)", name, level, exp_current); + int64 left_over = exp_current - exp_to_next_level; + level++; + exp_to_next_level *= 2; + exp_current = left_over; + AddNewGuildEvent(GUILD_EVENT_GUILD_LEVEL_UP, "The guild gained a level and is now level %u.", Timer::GetUnixTimeStamp(), true, level); + SendMessageToGuild(GUILD_EVENT_GUILD_LEVEL_UP, "The guild gained a level and is now level %u.", level); + + if (level % 10 == 0) { + if (level == 10) + strncpy(adjective, "bold", sizeof(adjective)); + else if (level == 20) + strncpy(adjective, "daring", sizeof(adjective)); + else if (level == 30) + strncpy(adjective, "gallant", sizeof(adjective)); + else if (level == 40) + strncpy(adjective, "noble", sizeof(adjective)); + else if (level == 50) + strncpy(adjective, "heroic", sizeof(adjective)); + else if (level == 60) + strncpy(adjective, "lordly", sizeof(adjective)); + else if (level == 70) + strncpy(adjective, "legendary", sizeof(adjective)); + else if (level == 80) + strncpy(adjective, "epic", sizeof(adjective)); + else + strncpy(adjective, "too uber for cheerios", sizeof(adjective) - 1); + sprintf(message, "The %s guild <%s> has attained level %u", adjective, name, level); + zone_list.HandleGlobalAnnouncement(message); + } + } + save_needed = true; + ret = true; + } + else if (exp < 0) { + } + if (ret && send_packet) + SendGuildUpdate(); +} + +void Guild::SetEXPToNextLevel(int64 exp, bool send_packet) { + + exp_to_next_level = exp; + if (send_packet) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "Guild XP to next level: %ul", exp_to_next_level); + SendGuildUpdate(); + save_needed = true; + } +} + +void Guild::SetRecruitingShortDesc(const char* new_desc, bool send_packet) { + + assert(new_desc); + + recruiting_short_desc = string(new_desc); + if (send_packet) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "Set Guild Recruiting short desc:\n%s", recruiting_short_desc.c_str()); + SendGuildUpdate(); + recruiting_save_needed = true; + } +} + +void Guild::SetRecruitingFullDesc(const char* new_desc, bool send_packet) { + + assert(new_desc); + + recruiting_full_desc = string(new_desc); + if (send_packet) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "Set Guild Recruiting full desc:\n%s", recruiting_full_desc.c_str()); + SendGuildUpdate(); + recruiting_save_needed = true; + } +} + +void Guild::SetRecruitingMinLevel(int8 new_level, bool send_packet) { + + recruiting_min_level = new_level; + if (send_packet) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "Set Guild Recruiting min_level: %i", recruiting_min_level); + SendGuildUpdate(); + recruiting_save_needed = true; + } +} + +void Guild::SetRecruitingPlayStyle(int8 new_play_style, bool send_packet) { + + recruiting_play_style = new_play_style; + if (send_packet) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "Set Guild Recruiting play style: %i", recruiting_play_style); + SendGuildUpdate(); + recruiting_save_needed = true; + } +} + +bool Guild::SetRecruitingDescTag(int8 index, int8 tag, bool send_packet) { + + bool ret = false; + if (index <= 3) { + recruiting_desc_tags.Put(index, tag); + ret = true; + } + if (ret && send_packet) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "Set Guild Recruiting descriptive tag index: %i, tag: %i", index, tag); + SendGuildUpdate(); + recruiting_save_needed = true; + } + return ret; +} + +int8 Guild::GetRecruitingDescTag(int8 index) { + + int8 ret = 0; + if (index <= 3) + ret = recruiting_desc_tags.Get(index); + LogWrite(GUILD__DEBUG, 1, "Guilds", "Get Guild Recruiting descriptive tag index: %i, value: %i", index, ret); + return ret; +} + +bool Guild::SetPermission(int8 rank, int8 permission, int8 value, bool send_packet) { + + bool ret = false; + if (value == 0 || value == 1) { + if (permissions.count(rank) == 0) + permissions.Put(rank, new MutexMap); + permissions.Get(rank)->Put(permission, value); + ret = true; + } + if (ret && send_packet) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "Set Guild Permissions - Rank: %i, Permission: %i, Value: %i", rank, permission, value); + SendGuildUpdate(); + ranks_save_needed = true; + } + return ret; +} + +int8 Guild::GetPermission(int8 rank, int8 permission) { + + int8 ret = 0; + if (permissions.count(rank) > 0 && permissions.Get(rank)->count(permission) > 0) + ret = permissions.Get(rank)->Get(permission); + LogWrite(GUILD__DEBUG, 1, "Guilds", "Get Guild Permissions - Rank: %i, Permission: %i, Value: %i", rank, permission, ret); + return ret; +} + +bool Guild::SetEventFilter(int8 event_id, int8 category, int8 value, bool send_packet) { + + bool ret = false; + if ((category == GUILD_EVENT_FILTER_CATEGORY_RETAIN_HISTORY || category == GUILD_EVENT_FILTER_CATEGORY_BROADCAST) && (value == 0 || value == 1)) { + if (event_filters.count(event_id) == 0) + event_filters.Put(event_id, new MutexMap); + event_filters.Get(event_id)->Put(category, value); + ret = true; + } + if (ret && send_packet) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "Set Guild Event Filter - EventID: %i, Category: %i, Value: %i", event_id, category, value); + SendGuildUpdate(); + event_filters_save_needed = true; + } + return ret; +} + +int8 Guild::GetEventFilter(int8 event_id, int8 category) { + + int8 ret = 0; + if (event_filters.count(event_id) > 0 && event_filters.Get(event_id)->count(category) > 0) { + ret = event_filters.Get(event_id)->Get(category); + LogWrite(GUILD__DEBUG, 1, "Guilds", "Get Guild Event Filter - EventID: %i, Category: %i, Value: %i", event_id, category, ret); + } + return ret; +} + +int32 Guild::GetNumUniqueAccounts() { + + map::iterator itr; + map accounts; + + mMembers.readlock(__FUNCTION__, __LINE__); + if (members.size() > 0) { + for (itr = members.begin(); itr != members.end(); itr++) { + if (accounts.count(itr->second->account_id) == 0) + accounts[itr->second->account_id] = true; + } + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 1, "Guilds", "Found %i Unique Account(s) in Guild", accounts.size()); + return accounts.size(); +} + +int32 Guild::GetNumRecruiters() { + + map::iterator itr; + int32 ret = 0; + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if (itr->second->recruiter_id > 0 && (itr->second->member_flags & GUILD_MEMBER_FLAGS_RECRUITING_FOR_GUILD)) + ret++; + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 1, "Guilds", "Found %i Recruiter(s) in Guild", ret); + return ret; +} + +int32 Guild::GetNextRecruiterID() { + + map::iterator itr; + map tmp; + int32 i, ret = 0; + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if (itr->second->recruiter_id > 0) + tmp[itr->second->recruiter_id] = true; + } + for (i = 1; i < 0xFFFFFFFF; i++) { + if (tmp.count(i) == 0) { + ret = i; + break; + } + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 1, "Guilds", "Next Guild Recruiter ID: %i", ret); + return ret; +} + +int64 Guild::GetNextEventID() { + + GuildEvent *ge; + int64 ret = 1; + + if (guild_events.size() > 0) { + ge = guild_events.front(); + ret = ge->event_id + 1; + } + LogWrite(GUILD__DEBUG, 1, "Guilds", "Next Guild Event ID: %i", ret); + return ret; +} + +GuildMember * Guild::GetGuildMemberOnline(Client *client) { + + map::iterator itr; + GuildMember *ret = 0; + + assert(client); + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if (itr->second->character_id == client->GetCharacterID()) { + ret = itr->second; + break; + } + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 3, "Guilds", "Guild Member Online: %s", ret->name); + return ret; +} + +GuildMember * Guild::GetGuildMember(Player *player) { + + assert(player); + return GetGuildMember(player->GetCharacterID()); +} + +GuildMember * Guild::GetGuildMember(int32 character_id) { + + GuildMember *ret = 0; + + mMembers.readlock(__FUNCTION__, __LINE__); + if (members.count(character_id) > 0) + ret = members[character_id]; + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 1, "Guilds", "%s: %i", __FUNCTION__, character_id); + return ret; +} + +GuildMember * Guild::GetGuildMember(const char *player_name) { + + map::iterator itr; + GuildMember *ret = 0; + + assert(player_name); + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if (!strncmp(player_name, itr->second->name, sizeof(itr->second->name))) { + ret = itr->second; + break; + } + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 1, "Guilds", "%s: %s", __FUNCTION__, player_name); + return ret; +} + +vector * Guild::GetGuildRecruiters() { + + map::iterator itr; + vector *ret = 0; + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if (itr->second->recruiter_id > 0 && (itr->second->member_flags & GUILD_MEMBER_FLAGS_RECRUITING_FOR_GUILD)) { + if (!ret) + ret = new vector; + ret->push_back(itr->second); + LogWrite(GUILD__DEBUG, 1, "Guilds", "Get Guild Recruiter '%s' (%i)", itr->second->name, itr->second->recruiter_id); + } + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +GuildEvent * Guild::GetGuildEvent(int64 event_id) { + + deque::iterator itr; + GuildEvent* ret = 0; + + for (itr = guild_events.begin(); itr != guild_events.end(); itr++) { + if ((*itr)->event_id == event_id) { + ret = *itr; + break; + } + } + LogWrite(GUILD__DEBUG, 1, "Guilds", "Get Guild Event: %s (%lli)", ret->description.c_str(), ret->event_id); + return ret; +} + +bool Guild::SetRankName(int8 rank, const char *name, bool send_packet) { + + assert(name); + + ranks.Put(rank, string(name)); + + if (send_packet) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "Set Guild Rank Name: %s (%i)", name, rank); + SendGuildUpdate(); + ranks_save_needed = true; + } + return true; +} + +const char * Guild::GetRankName(int8 rank) { + + if (ranks.count(rank) > 0) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "Get Guild Rank Name: %s (%i)", ranks.Get(rank).c_str(), rank); + return ranks.Get(rank).c_str(); + } + return 0; +} + +bool Guild::SetRecruitingFlag(int8 flag, int8 value, bool send_packet) { + + bool ret = false; + + if (recruiting_flags.count(flag) > 0 && (value == 0 || value == 1)) { + recruiting_flags.Put(flag, value); + ret = true; + } + if (ret && send_packet) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "Set Guild Recruiting Flag: %i, Value: %i", flag, value); + SendGuildUpdate(); + recruiting_save_needed = true; + } + return ret; +} + +int8 Guild::GetRecruitingFlag(int8 flag) { + + int8 value = 0; + if (recruiting_flags.count(flag) > 0) + value = recruiting_flags.Get(flag); + LogWrite(GUILD__DEBUG, 0, "Guilds", "Get Guild Recruiting Flag: %i, Value: %i", flag, value); + return value; +} + +bool Guild::SetGuildRecruiter(Client* client, const char* name, bool value, bool send_packet) { + + GuildMember *gm; + const char *awarder_name; + + assert(client); + assert(name); + + if (!(gm = GetGuildMember(name))) + return false; + + awarder_name = client->GetPlayer()->GetName(); + if (value) { + gm->recruiter_id = GetNextRecruiterID(); + AddNewGuildEvent(GUILD_EVENT_BECOMES_RECRUITER, "%s awarded %s Guild Recruiting Permission.", Timer::GetUnixTimeStamp(), true, awarder_name, name); + SendMessageToGuild(GUILD_EVENT_BECOMES_RECRUITER, "%s awarded %s Guild Recruiting Permission.", awarder_name, name); + LogWrite(GUILD__DEBUG, 0, "Guilds", "%s makes %s a guild recruiter.", awarder_name, name); + } + else { + gm->recruiter_id = 0; + AddNewGuildEvent(GUILD_EVENT_NO_LONGER_RECRUITER, "%s revoked %s's Guild Recruiting Permission.", Timer::GetUnixTimeStamp(), true, awarder_name, name); + SendMessageToGuild(GUILD_EVENT_NO_LONGER_RECRUITER, "%s revoked %s's Guild Recruiting Permission.", awarder_name, name); + LogWrite(GUILD__DEBUG, 0, "Guilds", "%s removes %s from guild recruiters.", awarder_name, name); + } + + if (send_packet) { + SendGuildMember(gm); + member_save_needed = true; + } + + return true; +} + +bool Guild::SetGuildRecruiterDescription(Client *client, const char *description, bool send_packet) { + + GuildMember *gm; + + assert(client); + assert(description); + + if (!(gm = GetGuildMember(client->GetPlayer()))) + return false; + + gm->recruiter_description = string(description); + + if (send_packet) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "Set guild recruiter description:\n%s", gm->recruiter_description.c_str()); + SendGuildRecruiterInfo(client, client->GetPlayer()); + member_save_needed = true; + } + + return true; +} + +bool Guild::ToggleGuildRecruiterAdventureClass(Client *client, bool send_packet) { + + GuildMember *gm; + + assert(client); + + if (!(gm = GetGuildMember(client->GetPlayer()))) + return false; + + gm->recruiting_show_adventure_class = (gm->recruiting_show_adventure_class == 0 ? 1 : 0); + + if (send_packet) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "Toggle guild recruiter adventure class = %i", gm->recruiting_show_adventure_class); + SendGuildRecruiterInfo(client, client->GetPlayer()); + } + + return true; +} + +bool Guild::SetGuildMemberNote(const char *name, const char *note, bool send_packet) { + + GuildMember *gm; + + assert(name); + assert(note); + + if (!(gm = GetGuildMember(name))) + return false; + + gm->note = string(note); + + if (send_packet) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "Set guild member note:\n%s", gm->note.c_str()); + SendGuildMember(gm); + member_save_needed = true; + } + + return true; +} + +bool Guild::SetGuildOfficerNote(const char *name, const char *note, bool send_packet) { + + GuildMember *gm; + + assert(name); + assert(note); + + if (!(gm = GetGuildMember(name))) + return false; + + gm->officer_note = string(note); + + if (send_packet) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "Set guild officer note:\n%s", gm->officer_note.c_str()); + SendGuildMember(gm); + member_save_needed = true; + } + + return true; +} + +bool Guild::AddNewGuildMember(Client *client, const char *invited_by, int8 rank) { + + Player *player; + GuildMember *gm; + + assert(client); + + player = client->GetPlayer(); + assert(player); + + if (members.count(player->GetCharacterID()) == 0 && !player->GetGuild() && ((Player*)player)->GetClient()) { + gm = new GuildMember; + + gm->account_id = ((Player*)player)->GetClient()->GetAccountID(); + gm->character_id = player->GetCharacterID(); + strncpy(gm->name, player->GetName(), sizeof(gm->name)); + gm->guild_status = 0; + gm->points = 0.0; + gm->adventure_class = player->GetAdventureClass(); + gm->adventure_level = player->GetLevel(); + gm->tradeskill_class = player->GetTradeskillClass(); + gm->tradeskill_level = player->GetTSLevel(); + gm->rank = rank; + gm->zone = string(player->GetZone()->GetZoneDescription()); + gm->join_date = Timer::GetUnixTimeStamp(); + gm->last_login_date = gm->join_date; + gm->recruiter_id = 0; + gm->member_flags = GUILD_MEMBER_FLAGS_NOTIFY_LOGINS; + gm->recruiting_show_adventure_class = 1; + gm->recruiter_picture_data_size = 0; + gm->recruiter_picture_data = 0; + mMembers.writelock(__FUNCTION__, __LINE__); + members[player->GetCharacterID()] = gm; + mMembers.releasewritelock(__FUNCTION__, __LINE__); + player->SetGuild(this); + string subtitle; + subtitle.append("<").append(GetName()).append(">"); + player->SetSubTitle(subtitle.c_str()); + + if (invited_by) + client->SimpleMessage(CHANNEL_NARRATIVE, "You accept the invite into the guild."); + else { + client->SimpleMessage(CHANNEL_NARRATIVE, "You have formed the guild."); + LogWrite(GUILD__DEBUG, 0, "Guilds", "New Guild formed: %s", GetName()); + } + + client->PlaySound("ui_joined"); + SendGuildUpdate(client); + SendGuildMember(gm); + SendGuildMOTD(client); + SendGuildEventList(client); + SendGuildBankEventList(client); + SendAllGuildEvents(client); + SendGuildMemberList(client); + if(client->GetVersion() > 561) + client->GetCurrentZone()->SendUpdateTitles(client->GetPlayer()); + + if (invited_by) { + AddNewGuildEvent(GUILD_EVENT_MEMBER_JOINS, "%s has accepted %s's invitation to join %s.", Timer::GetUnixTimeStamp(), true, player->GetName(), invited_by, GetName()); + SendMessageToGuild(GUILD_EVENT_MEMBER_JOINS, "%s has accepted %s's invitation to join %s.", player->GetName(), invited_by, GetName()); + LogWrite(GUILD__DEBUG, 0, "Guilds", "%s invited %s to join guild: %s", invited_by, player->GetName(), GetName()); + } + + member_save_needed = true; + } + + return true; +} + +bool Guild::AddGuildMember(GuildMember *guild_member) { + + assert(guild_member); + + mMembers.writelock(__FUNCTION__, __LINE__); + assert(members.count(guild_member->character_id) == 0); + members[guild_member->character_id] = guild_member; + mMembers.releasewritelock(__FUNCTION__, __LINE__); + + // spammy + LogWrite(GUILD__DEBUG, 5, "Guilds", "Added Player: %s (%i) to Guild: %s", guild_member->name, guild_member->character_id, GetName()); + + return true; +} + +void Guild::RemoveGuildMember(int32 character_id, bool send_packet) { + GuildMember *gm = 0; + Client *client; + + mMembers.writelock(__FUNCTION__, __LINE__); + if (members.count(character_id) > 0) { + gm = members[character_id]; + members.erase(gm->character_id); + } + mMembers.releasewritelock(__FUNCTION__, __LINE__); + + if (gm) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "Remove Player: %s (%i) from Guild: %s", members[character_id]->name, character_id, GetName()); + + if ((client = zone_list.GetClientByCharID(character_id))) { + client->GetPlayer()->SetGuild(0); + client->GetPlayer()->SetSubTitle(""); + client->GetCurrentZone()->SendUpdateTitles(client->GetPlayer()); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You were removed from the guild."); + } + + database.DeleteGuildMember(this, gm->character_id); + + if (send_packet) { + SendGuildMemberLeave(gm->character_id); + SendGuildUpdate(); + SendGuildMemberList(); + } + + AddNewGuildEvent(GUILD_EVENT_MEMBER_LEAVES, "%s left the guild.", Timer::GetUnixTimeStamp(), true, gm->name); + SendMessageToGuild(GUILD_EVENT_MEMBER_LEAVES, "%s left the guild.", gm->name); + + safe_delete_array(gm->recruiter_picture_data); + safe_delete(gm); + } +} + +void Guild::RemoveAllGuildMembers() { + map::iterator itr; + Client *client; + + mMembers.writelock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if ((client = zone_list.GetClientByCharID(itr->second->character_id))) { + client->GetPlayer()->SetGuild(0); + client->GetPlayer()->SetSubTitle(""); + } + safe_delete(itr->second); + } + members.clear(); + mMembers.releasewritelock(__FUNCTION__, __LINE__); + + LogWrite(GUILD__DEBUG, 0, "Guilds", "Removed ALL members from Guild: %s", GetName()); + +} + +bool Guild::DemoteGuildMember(Client *client, const char *name, bool send_packet) { + + GuildMember *gm; + const char *demoter_name; + bool ret = false; + + assert(client); + assert(name); + + mMembers.readlock(__FUNCTION__, __LINE__); + if ((gm = GetGuildMember(name)) && gm->rank != GUILD_RANK_RECRUIT) { + demoter_name = client->GetPlayer()->GetName(); + gm->rank++; + + AddNewGuildEvent(GUILD_EVENT_MEMBER_DEMOTED, "%s has demoted %s to %s.", Timer::GetUnixTimeStamp(), true, demoter_name, name, ranks.Get(gm->rank).c_str()); + SendMessageToGuild(GUILD_EVENT_MEMBER_DEMOTED, "%s has demoted %s to %s.", demoter_name, name, ranks.Get(gm->rank).c_str()); + LogWrite(GUILD__DEBUG, 0, "Guilds", "%s demoted %s to %s in Guild: %s", demoter_name, name, ranks.Get(gm->rank).c_str(), GetName()); + + ret = true; + + if (send_packet) { + SendGuildMember(gm); + SendGuildMemberList(); + member_save_needed = true; + } + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +bool Guild::PromoteGuildMember(Client *client, const char *name, bool send_packet) { + + GuildMember *gm; + const char *promoter_name; + bool ret = false; + + assert(client); + assert(name); + + mMembers.readlock(__FUNCTION__, __LINE__); + if ((gm = GetGuildMember(name)) && gm->rank != GUILD_RANK_LEADER) { + promoter_name = client->GetPlayer()->GetName(); + gm->rank--; + + AddNewGuildEvent(GUILD_EVENT_MEMBER_PROMOTED, "%s has promoted %s to %s.", Timer::GetUnixTimeStamp(), true, promoter_name, name, ranks.Get(gm->rank).c_str()); + SendMessageToGuild(GUILD_EVENT_MEMBER_PROMOTED, "%s has promoted %s to %s.", promoter_name, name, ranks.Get(gm->rank).c_str()); + LogWrite(GUILD__DEBUG, 0, "Guilds", "%s promoted %s to %s in Guild: %s", promoter_name, name, ranks.Get(gm->rank).c_str(), GetName()); + + if (send_packet) { + SendGuildMember(gm); + SendGuildMemberList(); + member_save_needed = true; + } + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +bool Guild::KickGuildMember(Client *client, const char *name, bool send_packet) { + + GuildMember *gm; + Client *kicked_client; + const char *kicker_name; + + assert(client); + assert(name); + + if (!(gm = GetGuildMember(name))) + return false; + + kicker_name = client->GetPlayer()->GetName(); + + if (!strncmp(kicker_name, gm->name, sizeof(gm->name))) { + AddNewGuildEvent(GUILD_EVENT_MEMBER_LEAVES, "%s left the guild.", Timer::GetUnixTimeStamp(), true, gm->name); + SendMessageToGuild(GUILD_EVENT_MEMBER_LEAVES, "%s left the guild.", gm->name); + LogWrite(GUILD__DEBUG, 0, "Guilds", "Player: %s has left guild: %s", gm->name, GetName()); + } + else { + AddNewGuildEvent(GUILD_EVENT_MEMBER_LEAVES, "%s kicked %s from the guild.", Timer::GetUnixTimeStamp(), true, kicker_name, gm->name); + SendMessageToGuild(GUILD_EVENT_MEMBER_LEAVES, "%s kicked %s from the guild.", kicker_name, gm->name); + LogWrite(GUILD__DEBUG, 0, "Guilds", "Player: %s was kicked from guild: %s by %s.", gm->name, GetName(), kicker_name); + } + + if ((kicked_client = zone_list.GetClientByCharID(gm->character_id))) { + kicked_client->GetPlayer()->SetGuild(0); + kicked_client->GetPlayer()->SetSubTitle(""); + if (!strncmp(client->GetPlayer()->GetName(), gm->name, sizeof(gm->name))) + kicked_client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You left the guild."); + else + kicked_client->Message(CHANNEL_COLOR_YELLOW, "You were kicked from the guild by %s.", kicker_name); + } + + mMembers.writelock(__FUNCTION__, __LINE__); + members.erase(gm->character_id); + mMembers.releasewritelock(__FUNCTION__, __LINE__); + + database.DeleteGuildMember(this, gm->character_id); + + if (send_packet) { + SendGuildMemberLeave(gm->character_id); + SendGuildUpdate(); + SendGuildMemberList(); + } + + safe_delete_array(gm->recruiter_picture_data); + safe_delete(gm); + + return true; +} + +bool Guild::InvitePlayer(Client *client, const char *name, bool send_packet) { + + Client *client_invite; + Player *player_invite; + PacketStruct *packet; + + assert(client); + assert(name); + + if (!(client_invite = zone_list.GetClientByCharName(string(name)))) { + client->Message(CHANNEL_NARRATIVE, "%s couldn't be found.", name); + LogWrite(GUILD__WARNING, 0, "Guilds", "Attempted to invite %s to guild %s: Player Not Found", name, GetName()); + return false; + } + + player_invite = client_invite->GetPlayer(); + + if (player_invite->GetGuild()) { + client->Message(CHANNEL_NARRATIVE, "%s is already in a guild.", player_invite->GetName()); + LogWrite(GUILD__WARNING, 0, "Guilds", "Attempted to invite %s to guild %s: Already in a guild", player_invite->GetName(), GetName()); + return false; + } + + if (client_invite->GetPendingGuildInvite()->guild) { + client->Message(CHANNEL_NARRATIVE, "%s is already considering joining a guild.", player_invite->GetName()); + LogWrite(GUILD__WARNING, 0, "Guilds", "Attempted to invite %s to guild %s: Pending Invite elsewhere", player_invite->GetName(), GetName()); + return false; + } + + if (!(packet = configReader.getStruct("WS_ReceiveOffer", client_invite->GetVersion()))) + return false; + + packet->setDataByName("type", 2); + packet->setMediumStringByName("name", client->GetPlayer()->GetName()); + packet->setDataByName("unknown2", 1); + packet->setMediumStringByName("guild_name", GetName()); + client_invite->QueuePacket(packet->serialize()); + + client->Message(CHANNEL_NARRATIVE, "You have invited %s to join %s.", player_invite->GetName(), GetName()); + LogWrite(GUILD__DEBUG, 0, "Guilds", "%s invited %s to guild %s", client->GetPlayer()->GetName(), player_invite->GetName(), GetName()); + + client_invite->SetPendingGuildInvite(this, client->GetPlayer()); + + safe_delete(packet); + return true; +} + +bool Guild::AddPointsToAll(Client *client, float points, const char *comment, bool send_packet) { + + map::iterator itr; + vector character_ids; + GuildMember *gm; + Client *client_to; + + assert(client); + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + gm = itr->second; + + if (!permissions.Get(gm->rank)->Get(GUILD_PERMISSIONS_RECEIVE_POINTS)) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "PlayerID: %i not allowed to receive points! Skipping...", gm->character_id); + continue; + } + + gm->points += points; + character_ids.push_back(gm->character_id); + + AddPointHistory(gm, Timer::GetUnixTimeStamp(), client->GetPlayer()->GetName(), points, comment); + if ((client_to = zone_list.GetClientByCharID(gm->character_id))) + { + client_to->Message(CHANNEL_GUILD_CHAT, "%s increased your guild member points by %.1f.", client->GetPlayer()->GetName(), points); + LogWrite(GUILD__DEBUG, 0, "Guilds", "Guild: %s", GetName()); + LogWrite(GUILD__DEBUG, 0, "Guilds", "\tAwarded By: %s +%.1f pts to Player: %s", client->GetPlayer()->GetName(), points, gm->name); + } + + SendGuildMember(gm); //tmp + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + + client->Message(CHANNEL_GUILD_CHAT, "Points modified for %u guild members.", character_ids.size()); + if (send_packet) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "%s modified points for %u members. Reason: %s", client->GetPlayer()->GetName(), character_ids.size(), points, comment); + SendGuildModification(points, &character_ids); + member_save_needed = true; + points_history_save_needed = true; + } + + return true; +} + +bool Guild::AddPointsToAllOnline(Client *client, float points, const char *comment, bool send_packet) { + + map::iterator itr; + vector character_ids; + GuildMember *gm; + Client *client_to; + + assert(client); + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + gm = itr->second; + + if (!(client_to = zone_list.GetClientByCharID(gm->character_id))) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "PlayerID: %i not online to receive points! Skipping...", gm->character_id); + continue; + } + + if (!permissions.Get(gm->rank)->Get(GUILD_PERMISSIONS_RECEIVE_POINTS)) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "PlayerID: %i not allowed to receive points! Skipping...", gm->character_id); + continue; + } + + gm->points += points; + character_ids.push_back(gm->character_id); + + AddPointHistory(gm, Timer::GetUnixTimeStamp(), client->GetPlayer()->GetName(), points, comment); + client_to->Message(CHANNEL_GUILD_CHAT, "%s increased your guild member points by %.1f.", client->GetPlayer()->GetName(), points); + LogWrite(GUILD__DEBUG, 0, "Guilds", "Guild: %s", GetName()); + LogWrite(GUILD__DEBUG, 0, "Guilds", "\tAwarded By: %s +%.1f pts to Player: %s", client->GetPlayer()->GetName(), points, gm->name); + + SendGuildMember(gm); //tmp + + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + + client->Message(CHANNEL_GUILD_CHAT, "Points modified for %u guild members.", character_ids.size()); + if (send_packet) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "%s modified points for %u members. Reason: %s", client->GetPlayer()->GetName(), character_ids.size(), points, comment); + SendGuildModification(points, &character_ids); + member_save_needed = true; + points_history_save_needed = true; + } + + return true; +} + +bool Guild::AddPointsToGroup(Client *client, float points, const char *comment, bool send_packet) { + + deque::iterator itr; + deque* group_members; + vector character_ids; + GroupMemberInfo *gmi; + GuildMember *gm; + + assert(client); + + if (!client->GetPlayer()->GetGroupMemberInfo()) { + client->SimpleMessage(CHANNEL_GUILD_CHAT, "Cannot assign points because you aren't in a group."); + LogWrite(GUILD__DEBUG, 0, "Guilds", "%s tried to assign points for group and failed: Not in a group", client->GetPlayer()->GetName()); + return false; + } + + mMembers.readlock(__FUNCTION__, __LINE__); + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + + PlayerGroup* group = world.GetGroupManager()->GetGroup(client->GetPlayer()->GetGroupMemberInfo()->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* group_members = group->GetMembers(); + for (itr = group_members->begin(); itr != group_members->end(); itr++) { + gmi = *itr; + + if (!gmi->client) + continue; + + if (gmi->client->GetPlayer()->GetGuild() != this) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "PlayerID: %i not in guild to receive group points! Skipping...", gmi->client->GetPlayer()->GetCharacterID()); + continue; + } + + if (!(gm = members[gmi->client->GetCharacterID()]) || !permissions.Get(gm->rank)->Get(GUILD_PERMISSIONS_RECEIVE_POINTS)) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "PlayerID: %i not allowed to receive points! Skipping...", gm->character_id); + continue; + } + + gm->points += points; + character_ids.push_back(gm->character_id); + + AddPointHistory(gm, Timer::GetUnixTimeStamp(), client->GetPlayer()->GetName(), points, comment); + gmi->client->Message(CHANNEL_GUILD_CHAT, "%s increased your guild member points by %.1f.", client->GetPlayer()->GetName(), points); + LogWrite(GUILD__DEBUG, 0, "Guilds", "Guild: %s", GetName()); + LogWrite(GUILD__DEBUG, 0, "Guilds", "\tAwarded By: %s +%.1f pts to Player: %s", client->GetPlayer()->GetName(), points, gm->name); + + SendGuildMember(gm); //tmp + + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + mMembers.releasereadlock(__FUNCTION__, __LINE__); + + client->Message(CHANNEL_GUILD_CHAT, "Points modified for %u guild members.", character_ids.size()); + if (send_packet) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "%s modified points for %u members. Reason: %s", client->GetPlayer()->GetName(), character_ids.size(), points, comment); + SendGuildModification(points, &character_ids); + member_save_needed = true; + points_history_save_needed = true; + } + + return true; +} + +bool Guild::AddPointsToRaid(Client *client, float points, const char *comment, bool send_packet) { + + assert(client); + LogWrite(MISC__TODO, 1, "TODO", "Implement Raiding\n%s, %s, %i", __FILE__, __FUNCTION__, __LINE__); + client->SimpleMessage(CHANNEL_GUILD_CHAT, "Cannot assign points because you aren't in a raid."); + return false; +} + +bool Guild::AddPointsToGuildMember(Client *client, float points, const char *name, const char *comment, bool send_packet) { + + vector character_ids; + GuildMember *gm; + Client *client_to; + + assert(client); + assert(name); + + if (!(gm = GetGuildMember(name))) + return false; + + mMembers.readlock(__FUNCTION__, __LINE__); + if (!permissions.Get(gm->rank)->Get(GUILD_PERMISSIONS_RECEIVE_POINTS)) { + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 0, "Guilds", "PlayerID: %i not allowed to receive points! Skipping...", gm->character_id); + client->Message(CHANNEL_GUILD_CHAT, "%s does not have permission to receive guild points.", gm->name); + return false; + } + + gm->points += points; + character_ids.push_back(gm->character_id); + + if ((client_to = zone_list.GetClientByCharID(gm->character_id))) { + client_to->Message(CHANNEL_GUILD_CHAT, "%s increased your guild member points by %.1f.", client->GetPlayer()->GetName(), points); + LogWrite(GUILD__DEBUG, 0, "Guilds", "Guild: %s", GetName()); + LogWrite(GUILD__DEBUG, 0, "Guilds", "\tAwarded By: %s +%.1f pts to Player: %s", client->GetPlayer()->GetName(), points, gm->name); + } + + LogWrite(GUILD__DEBUG, 0, "Guilds", "%s modified points for 1 guild member. Reason: %s", client->GetPlayer()->GetName(), comment); + client->SimpleMessage(CHANNEL_GUILD_CHAT, "Points modified for 1 guild member."); + AddPointHistory(gm, Timer::GetUnixTimeStamp(), client->GetPlayer()->GetName(), points, comment); + + if (send_packet) { + SendGuildMember(gm); //tmp + + SendGuildModification(points, &character_ids); + member_save_needed = true; + points_history_save_needed = true; + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + + return true; +} + +bool Guild::AddPointHistory(GuildMember *guild_member, int32 date, const char *modified_by, float points, const char *comment, bool new_point_history) { + PointHistory *ph, *ph_delete; + deque *ph_list; + + assert(guild_member); + assert(modified_by); + + ph = new PointHistory; + ph->date = date; + ph->modified_by = string(modified_by); + ph->points = points; + if (comment) + ph->comment = string(comment); + ph->saved_needed = new_point_history; + + mMembers.readlock(__FUNCTION__, __LINE__); + ph_list = &guild_member->point_history; + if (ph_list->size() == GUILD_MAX_POINT_HISTORY) { + ph_delete = ph_list->back(); + database.DeleteGuildPointHistory(this, guild_member->character_id, ph_delete); + safe_delete(ph_delete); + ph_list->pop_back(); + } + + ph_list->push_front(ph); + mMembers.releasereadlock(__FUNCTION__, __LINE__); + + return true; +} + +void Guild::ViewGuildMemberPoints(Client *client, const char * name) { + + deque *ph_list; + deque::iterator itr; + PointHistory *ph; + GuildMember *gm; + PacketStruct *packet; + int32 i; + + assert(client); + assert(name); + + if (!(gm = GetGuildMember(name))) + return; + + if (!(packet = configReader.getStruct("WS_RequestGuildEventDetails", client->GetVersion()))) + return; + + mMembers.readlock(__FUNCTION__, __LINE__); + ph_list = &gm->point_history; + i = 0; + + packet->setDataByName("account_id", client->GetAccountID()); + packet->setDataByName("character_id", client->GetCharacterID()); + packet->setDataByName("guild_id", id); + packet->setArrayLengthByName("num_events", ph_list->size()); + + // this log entry may be excessive... test it out. + LogWrite(GUILD__DEBUG, 0, "Guilds", "account_id: %i, character_id: %i, guild_id: %i, num_events: %i", client->GetAccountID(), client->GetCharacterID(), id, ph_list->size()); + + for (itr = ph_list->begin(); itr != ph_list->end(); itr++) { + ph = *itr; + packet->setArrayDataByName("date", ph->date, i); + packet->setArrayDataByName("modified_by", ph->modified_by.c_str(), i); + packet->setArrayDataByName("comment", ph->comment.c_str(), i); + packet->setArrayDataByName("points", ph->points, i); + // this log entry may be excessive... test it out. + LogWrite(GUILD__DEBUG, 0, "Guilds", "date: %i, modified_by: %i, comment: %i, points: %.1f", ph->date, ph->modified_by.c_str(), ph->comment.c_str(), ph->points); + i++; + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + //DumpPacket(packet->serialize()); + + client->QueuePacket(packet->serialize()); + safe_delete(packet); +} + +bool Guild::ChangeMemberFlag(Client *client, int8 member_flag, int8 value, bool send_packet) { + + GuildMember *gm; + bool ret = false; + + assert (client); + + if (!(gm = GetGuildMemberOnline(client))) + return false; + + mMembers.readlock(__FUNCTION__, __LINE__); + switch (member_flag) { + case GUILD_MEMBER_FLAGS_RECRUITING_FOR_GUILD: { + if (value > 0 && !(gm->member_flags & GUILD_MEMBER_FLAGS_RECRUITING_FOR_GUILD)) { + gm->member_flags += GUILD_MEMBER_FLAGS_RECRUITING_FOR_GUILD; + ret = true; + } + else if (value == 0 && gm->member_flags & GUILD_MEMBER_FLAGS_RECRUITING_FOR_GUILD) { + gm->member_flags -= GUILD_MEMBER_FLAGS_RECRUITING_FOR_GUILD; + ret = true; + } + break; + } + case GUILD_MEMBER_FLAGS_NOTIFY_LOGINS: { + if (value > 0 && !(gm->member_flags & GUILD_MEMBER_FLAGS_NOTIFY_LOGINS)) { + gm->member_flags += GUILD_MEMBER_FLAGS_NOTIFY_LOGINS; + client->SimpleMessage(CHANNEL_GUILD_CHAT, "Guild online notifications are now enabled."); + ret = true; + } + else if (value == 0 && gm->member_flags & GUILD_MEMBER_FLAGS_NOTIFY_LOGINS) { + gm->member_flags -= GUILD_MEMBER_FLAGS_NOTIFY_LOGINS; + client->SimpleMessage(CHANNEL_GUILD_CHAT, "Guild online notifications are now disabled."); + ret = true; + } + break; + } + case GUILD_MEMBER_FLAGS_DONT_GENERATE_EVENTS: { + if (value > 1 && !(gm->member_flags & GUILD_MEMBER_FLAGS_DONT_GENERATE_EVENTS)) { + gm->member_flags += GUILD_MEMBER_FLAGS_DONT_GENERATE_EVENTS; + client->SimpleMessage(CHANNEL_GUILD_CHAT, "Guild events are now disabled for this character."); + ret = true; + } + else if (value == 0 && gm->member_flags & GUILD_MEMBER_FLAGS_DONT_GENERATE_EVENTS) { + gm->member_flags -= GUILD_MEMBER_FLAGS_DONT_GENERATE_EVENTS; + client->SimpleMessage(CHANNEL_GUILD_CHAT, "Guild events are now enabled for this character."); + ret = true; + } + break; + } + default: + break; + } + + if (ret && send_packet) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "Guild Member Flag '%i' changed to %i", member_flag, value); + member_save_needed = true; + SendGuildMember(client, gm); + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} +bool Guild::UpdateGuildStatus(Player *player ,int32 Status) { + GuildMember *gm; + assert(player); + assert(members.count(player->GetCharacterID()) > 0); + mMembers.readlock(__FUNCTION__, __LINE__); + gm = members[player->GetCharacterID()]; + gm->guild_status += Status; + mMembers.releasereadlock(__FUNCTION__, __LINE__); + member_save_needed = true; + return true; +} +bool Guild::UpdateGuildMemberInfo(Player *player) { + + GuildMember *gm; + + assert(player); + assert(members.count(player->GetCharacterID()) > 0); + + LogWrite(GUILD__DEBUG, 0, "Guilds", "Updating Guild Member Info for Player: %i", player->GetCharacterID()); + + mMembers.readlock(__FUNCTION__, __LINE__); + gm = members[player->GetCharacterID()]; + gm->adventure_class = player->GetAdventureClass(); + gm->adventure_level = player->GetLevel(); + gm->tradeskill_class = player->GetTradeskillClass(); + gm->tradeskill_level = player->GetTSLevel(); + gm->zone = string(player->GetZone()->GetZoneDescription()); + gm->last_login_date = database.GetCharacterTimeStamp(player->GetCharacterID()); + mMembers.releasereadlock(__FUNCTION__, __LINE__); + + return true; +} + +void Guild::AddGuildEvent(int64 event_id, int32 type, const char *description, int32 date, int8 locked) { + + LogWrite(GUILD__DEBUG, 3, "Guilds", "Guild: %s", GetName()); + LogWrite(GUILD__DEBUG, 3, "Guilds", "Add Guild Event: %lli, %i, %s", event_id, type, string(description).c_str()); + + GuildEvent *ge; + + assert(description); +// assert(event_filters.Get(type)->Get(GUILD_EVENT_FILTER_CATEGORY_RETAIN_HISTORY)); + assert(guild_events.size() < GUILD_MAX_EVENTS); + + ge = new GuildEvent; + ge->event_id = event_id; + ge->type = type; + ge->description = string(description); + ge->date = date; + ge->locked = locked; + ge->save_needed = false; + guild_events.push_back(ge); +} + +void Guild::AddNewGuildEvent(int32 type, const char *description, int32 date, bool send_packet, ...) { + + deque::reverse_iterator itr; + GuildEvent *ge, *current_ge; + char buffer[4096]; + va_list argptr; + + assert(description); + + va_start(argptr, send_packet); + vsnprintf(buffer, sizeof(buffer), description, argptr); + va_end(argptr); + + ge = new GuildEvent; + ge->event_id = GetNextEventID(); + ge->type = type; + ge->description = string(buffer); + ge->date = date; + ge->locked = 0; + + if (!event_filters.Get(type)->Get(GUILD_EVENT_FILTER_CATEGORY_RETAIN_HISTORY)) { + database.SaveHiddenGuildEvent(this, ge); + return; + } + + if (guild_events.size() == GUILD_MAX_EVENTS) { + for (itr = guild_events.rbegin(); itr != guild_events.rend(); itr++) { + current_ge = *itr; + + if (current_ge->locked == 0) { + database.ArchiveGuildEvent(this, current_ge); + safe_delete(current_ge); + guild_events.erase(--itr.base()); + guild_events.push_front(ge); + break; + } + } + } + else + guild_events.push_front(ge); + + if (send_packet) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "Some Add New Guild Event thing happened, not sure what..."); + SendNewGuildEvent(ge); + ge->save_needed = true; + events_save_needed = true; + } +} + +bool Guild::LockGuildEvent(int64 event_id, bool lock, bool send_packet) { + + bool ret = false; + GuildEvent* ge = GetGuildEvent(event_id); + if (ge) { + if (lock) { + ge->locked = 1; + if (send_packet) + SendGuildEventAction(GUILD_EVENT_ACTION_LOCK, ge); + } + else { + ge->locked = 0; + if (send_packet) + SendGuildEventAction(GUILD_EVENT_ACTION_UNLOCK, ge); + } + ret = true; + } + if (ret && send_packet) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "Toggle guild event lock, EventID: %lli, value: %i", event_id, lock); + ge->save_needed = true; + events_save_needed = true; + } + return ret; +} + +bool Guild::DeleteGuildEvent(int64 event_id, bool send_packet) { + + bool ret = false; + deque::iterator itr; + for (itr = guild_events.begin(); itr != guild_events.end(); itr++) { + GuildEvent* ge = *itr; + if (ge->event_id == event_id) { + if (send_packet) + SendGuildEventAction(GUILD_EVENT_ACTION_DELETE, ge); + database.DeleteGuildEvent(this, ge->event_id); + safe_delete(ge); + guild_events.erase(itr); + ret = true; + break; + } + } + LogWrite(GUILD__DEBUG, 0, "Guilds", "Delete guild event, EventID: %lli", event_id); + return ret; +} + +int32 Guild::GetPermissionsPacketValue(int8 rank, int32 start, int32 end) { + + int32 ret = 0; + for (int32 i = start; i <= end; i++) { + if (permissions.count(rank) > 0 && permissions.Get(rank)->count(i) > 0 && permissions.Get(rank)->Get(i)) { + if (i >= 0 && i <= 31) + ret += (int32)pow(2.0, (double)i); + else if (i >= 32 && i <= 63) + ret += (int32)pow(2.0, (double)(i - 32)); + } + } + return ret; +} + +int32 Guild::GetEventFilterPacketValue(int8 category, int32 start, int32 end) { + + int32 ret = 0; + for (int32 i = start; i <= end; i++) { + if (event_filters.count(i) > 0 && event_filters.Get(i)->count(category) > 0 && event_filters.Get(i)->Get(category)) { + if (i >= 0 && i <= 31) + ret += (int32)pow(2.0, (double)i); + else if (i >= 32 && i <= 63) + ret += (int32)pow(2.0, (double)(i - 32)); + else if (i >= 64 && i <= 95) + ret += (int32)pow(2.0, (double)(i - 64)); + } + } + return ret; +} + +int8 Guild::GetRecruitingLookingForPacketValue() { + + int8 ret = 0; + MutexMap::iterator itr = recruiting_flags.begin(); + while (itr.Next()) { + if (itr.second) + ret += (int8)pow(2.0, (double)itr.first); + } + return ret; +} + +void Guild::SendGuildMOTD(Client* client) { + + if (client && strlen(motd) > 0) + client->Message(CHANNEL_GUILD_MOTD, "Guild MOTD: %s", motd); + + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild MOTD.\n'%s'", motd); +} + +void Guild::SendGuildEventList() { + + map::iterator itr; + Client *client; + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if ((client = zone_list.GetClientByCharID(itr->second->character_id))) + SendGuildEventList(client); + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild Event List (%s).", __FUNCTION__); +} + +void Guild::SendGuildEventList(Client* client) { + + if (client) { + PacketStruct* packet = configReader.getStruct("WS_GuildEventList", client->GetVersion()); + if (packet) { + packet->setDataByName("account_id", client->GetAccountID()); + packet->setArrayLengthByName("num_events", guild_events.size()); + deque::iterator itr; + int32 i = 0; + for (itr = guild_events.begin(); itr != guild_events.end(); itr++) { + packet->setArrayDataByName("event_id", (*itr)->event_id, i); + packet->setArrayDataByName("locked", (*itr)->locked, i); + i++; + } + client->QueuePacket(packet->serialize()); + //DumpPacket(packet->serialize()); + safe_delete(packet); + } + } + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild Event List (%s).", __FUNCTION__); +} + +void Guild::SendGuildEventDetails() { + + map::iterator itr; + Client *client; + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if ((client = zone_list.GetClientByCharID(itr->second->character_id))) + SendGuildEventDetails(client); + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild Event Details (%s).", __FUNCTION__); +} + +void Guild::SendGuildEventDetails(Client* client) { + + if (client) { + PacketStruct* packet = configReader.getStruct("WS_GuildEventDetails", client->GetVersion()); + if (packet) { + deque::iterator itr; + int32 i = 0; + for (itr = guild_events.begin(); itr != guild_events.end(); itr++) { + packet->setArrayDataByName("event_id", (*itr)->event_id, i); + i++; + } + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild Event Details (%s).", __FUNCTION__); +} + +void Guild::SendAllGuildEvents() { + + map::iterator itr; + Client *client; + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if ((client = zone_list.GetClientByCharID(itr->second->character_id))) + SendAllGuildEvents(client); + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 0, "Guilds", "Sent ALL guild Events (%s).", __FUNCTION__); +} + +void Guild::SendAllGuildEvents(Client* client) { + + if (client) { + deque::iterator itr; + for (itr = guild_events.begin(); itr != guild_events.end(); itr++) + SendOldGuildEvent(client, *itr); + } + LogWrite(GUILD__DEBUG, 0, "Guilds", "Sent ALL guild Events (%s).", __FUNCTION__); +} + +void Guild::SendOldGuildEvent(Client* client, GuildEvent* guild_event) { + + if (client && guild_event) { + PacketStruct* packet = configReader.getStruct("WS_RequestGuildInfo", client->GetVersion()); + if (packet) { + packet->setDataByName("account_id", client->GetAccountID()); + packet->setDataByName("event_id", guild_event->event_id); + packet->setDataByName("date", guild_event->date); + packet->setDataByName("type", guild_event->type); + packet->setMediumStringByName("description", guild_event->description.c_str()); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + LogWrite(GUILD__DEBUG, 3, "Guilds", "Sent OLD guild Events."); +} + +void Guild::SendNewGuildEvent(GuildEvent* guild_event) { + + map::iterator itr; + Client *client; + + assert (guild_event); + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if ((client = zone_list.GetClientByCharID(itr->second->character_id))) + SendNewGuildEvent(client, guild_event); + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 0, "Guilds", "Sent NEW guild Events. (%s)", __FUNCTION__); +} + +void Guild::SendNewGuildEvent(Client* client, GuildEvent* guild_event) { + + if (client && guild_event) { + PacketStruct* packet = configReader.getStruct("WS_GuildEventAdd", client->GetVersion()); + if (packet) { + packet->setDataByName("account_id", client->GetAccountID()); + packet->setDataByName("event_id", guild_event->event_id); + packet->setDataByName("type", guild_event->type); + packet->setDataByName("date", guild_event->date); + packet->setDataByName("description", guild_event->description.c_str()); + //DumpPacket(packet->serialize()); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + LogWrite(GUILD__DEBUG, 0, "Guilds", "Sent NEW guild Events. (%s)", __FUNCTION__); +} + +void Guild::SendGuildEventAction(int8 action, GuildEvent* guild_event) { + + map::iterator itr; + Client *client; + + assert(guild_event); + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if ((client = zone_list.GetClientByCharID(itr->second->character_id))) + SendGuildEventAction(client, action, guild_event); + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild events Action. (%s)", __FUNCTION__); +} + +void Guild::SendGuildEventAction(Client* client, int8 action, GuildEvent* guild_event) { + + if (guild_event) { + PacketStruct* packet = configReader.getStruct("WS_GuildEventAction", client->GetVersion()); + if (packet) { + packet->setDataByName("account_id", client->GetAccountID()); + packet->setDataByName("event_id", guild_event->event_id); + packet->setDataByName("action", action); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild events Action. (%s)", __FUNCTION__); +} + +void Guild::SendGuildBankEventList() { + + map::iterator itr; + Client *client; + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if ((client = zone_list.GetClientByCharID(itr->second->character_id))) + SendGuildBankEventList(client); + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild bank events list. (%s)", __FUNCTION__); +} + +void Guild::SendGuildBankEventList(Client* client) { + + if (client) { + for (int32 i = 0; i < 4; i++) { + PacketStruct* packet = configReader.getStruct("WS_GuildBankEventList", client->GetVersion()); + if (packet) { + packet->setDataByName("account_id", client->GetAccountID()); + packet->setDataByName("bank_number", i); + packet->setArrayLengthByName("num_events", banks[i].events.size()); + deque::iterator itr; + for (itr = banks[i].events.begin(); itr != banks[i].events.end(); itr++) + packet->setArrayDataByName("event_id", (*itr)->event_id, i); + //DumpPacket(packet->serialize()); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + } + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild bank events list. (%s)", __FUNCTION__); +} + +void Guild::SendGuildUpdate() { + + map::iterator itr; + Client *client; + + LogWrite(GUILD__DEBUG, 1, "Guilds", "SendGuildUpdate to all guild member clients online... (%s)", __FUNCTION__); + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if ((client = zone_list.GetClientByCharID(itr->second->character_id))) + SendGuildUpdate(client); + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); +} + +void Guild::SendGuildUpdate(Client* client) { + + if (client) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "SendGuildUpdate to client online... (%s)", __FUNCTION__); + + PacketStruct* packet = configReader.getStruct("WS_GuildUpdate", client->GetVersion()); + if (packet) { + packet->setMediumStringByName("guild_name", GetName()); + packet->setMediumStringByName("guild_motd", motd); + packet->setDataByName("guild_id", id); + packet->setDataByName("guild_level", level); + packet->setDataByName("unknown", 1); + packet->setDataByName("formed_date", formed_date); + packet->setDataByName("unique_accounts", GetNumUniqueAccounts()); + packet->setDataByName("num_members", members.size()); + packet->setDataByName("exp_current", exp_current); + packet->setDataByName("exp_to_next_level", exp_to_next_level); + packet->setDataByName("event_filter_retain1", GetEventFilterPacketValue(GUILD_EVENT_FILTER_CATEGORY_RETAIN_HISTORY, 0, 31)); + packet->setDataByName("event_filter_retain2", GetEventFilterPacketValue(GUILD_EVENT_FILTER_CATEGORY_RETAIN_HISTORY, 32, 63)); + packet->setDataByName("event_filter_retain3", GetEventFilterPacketValue(GUILD_EVENT_FILTER_CATEGORY_RETAIN_HISTORY, 63, 92)); + packet->setDataByName("event_filter_retain4", 0); + packet->setDataByName("event_filter_broadcast1", GetEventFilterPacketValue(GUILD_EVENT_FILTER_CATEGORY_BROADCAST, 0, 31)); + packet->setDataByName("event_filter_broadcast2", GetEventFilterPacketValue(GUILD_EVENT_FILTER_CATEGORY_BROADCAST, 32, 63)); + packet->setDataByName("event_filter_broadcast3", GetEventFilterPacketValue(GUILD_EVENT_FILTER_CATEGORY_BROADCAST, 64, 92)); + packet->setDataByName("event_filter_broadcast4", 0); + packet->setDataByName("recruiting_looking_for", GetRecruitingLookingForPacketValue()); + packet->setDataByName("recruiting_desc_tag1", GetRecruitingDescTag(0)); + packet->setDataByName("recruiting_desc_tag2", GetRecruitingDescTag(1)); + packet->setDataByName("recruiting_desc_tag3", GetRecruitingDescTag(2)); + packet->setDataByName("recruiting_desc_tag4", GetRecruitingDescTag(3)); + packet->setDataByName("recruiting_playstyle", recruiting_play_style); + packet->setDataByName("recruiting_min_level", recruiting_min_level); + packet->setMediumStringByName("recuiting_short_description", recruiting_short_desc.c_str()); + packet->setMediumStringByName("recruiting_full_description", recruiting_full_desc.c_str()); + packet->setMediumStringByName("rank0_name", ranks.Get(GUILD_RANK_LEADER).c_str()); + packet->setDataByName("rank0_permissions1", GetPermissionsPacketValue(GUILD_RANK_LEADER, 0, 31)); + packet->setDataByName("rank0_permissions2", GetPermissionsPacketValue(GUILD_RANK_LEADER, 32, 44)); + packet->setDataByName("rank0_permissions_unused", 0); + packet->setMediumStringByName("rank1_name", ranks.Get(GUILD_RANK_SENIOR_OFFICER).c_str()); + packet->setDataByName("rank1_permissions1", GetPermissionsPacketValue(GUILD_RANK_SENIOR_OFFICER, 0, 31)); + packet->setDataByName("rank1_permissions2", GetPermissionsPacketValue(GUILD_RANK_SENIOR_OFFICER, 32, 44)); + packet->setDataByName("rank1_permissions_unused", 0); + packet->setMediumStringByName("rank2_name", ranks.Get(GUILD_RANK_OFFICER).c_str()); + packet->setDataByName("rank2_permissions1", GetPermissionsPacketValue(GUILD_RANK_OFFICER, 0, 31)); + packet->setDataByName("rank2_permissions2", GetPermissionsPacketValue(GUILD_RANK_OFFICER, 32, 44)); + packet->setDataByName("rank2_permissions_unused", 0); + packet->setMediumStringByName("rank3_name", ranks.Get(GUILD_RANK_SENIOR_MEMBER).c_str()); + packet->setDataByName("rank3_permissions1", GetPermissionsPacketValue(GUILD_RANK_SENIOR_MEMBER, 0, 31)); + packet->setDataByName("rank3_permissions2", GetPermissionsPacketValue(GUILD_RANK_SENIOR_MEMBER, 32, 44)); + packet->setDataByName("rank3_permissions_unused", 0); + packet->setMediumStringByName("rank4_name", ranks.Get(GUILD_RANK_MEMBER).c_str()); + packet->setDataByName("rank4_permissions1", GetPermissionsPacketValue(GUILD_RANK_MEMBER, 0, 31)); + packet->setDataByName("rank4_permissions2", GetPermissionsPacketValue(GUILD_RANK_MEMBER, 32, 44)); + packet->setDataByName("rank4_permissions_unused", 0); + packet->setMediumStringByName("rank5_name", ranks.Get(GUILD_RANK_JUNIOR_MEMBER).c_str()); + packet->setDataByName("rank5_permissions1", GetPermissionsPacketValue(GUILD_RANK_JUNIOR_MEMBER, 0, 31)); + packet->setDataByName("rank5_permissions2", GetPermissionsPacketValue(GUILD_RANK_JUNIOR_MEMBER, 32, 44)); + packet->setDataByName("rank5_permissions_unused", 0); + packet->setMediumStringByName("rank6_name", ranks.Get(GUILD_RANK_INITIATE).c_str()); + packet->setDataByName("rank6_permissions1", GetPermissionsPacketValue(GUILD_RANK_INITIATE, 0, 31)); + packet->setDataByName("rank6_permissions2", GetPermissionsPacketValue(GUILD_RANK_INITIATE, 32, 44)); + packet->setDataByName("rank6_permissions_unused", 0); + packet->setMediumStringByName("rank7_name", ranks.Get(GUILD_RANK_RECRUIT).c_str()); + packet->setDataByName("rank7_permissions1", GetPermissionsPacketValue(GUILD_RANK_RECRUIT, 0, 31)); + packet->setDataByName("rank7_permissions2", GetPermissionsPacketValue(GUILD_RANK_RECRUIT, 32, 44)); + packet->setDataByName("rank7_permissions_unused", 0); + packet->setMediumStringByName("bank1_name", banks[0].name.c_str()); + packet->setMediumStringByName("bank2_name", banks[1].name.c_str()); + packet->setMediumStringByName("bank3_name", banks[2].name.c_str()); + packet->setMediumStringByName("bank4_name", banks[3].name.c_str()); + //DumpPacket(packet->serialize()); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } +} + +void Guild::SendGuildMemberList() { + + map::iterator itr; + Client *client; + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if ((client = zone_list.GetClientByCharID(itr->second->character_id))) + SendGuildMemberList(client); + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild member list to all clients."); +} + +void Guild::SendGuildMemberList(Client* client) { + + map::iterator itr; + GuildMember *gm; + + if (client) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild member list to a client."); + PacketStruct* packet = configReader.getStruct("WS_GuildMembershipResponse", client->GetVersion()); + if (packet) { + packet->setDataByName("guild_id", id); + packet->setDataByName("character_id_to", client->GetCharacterID()); + + mMembers.readlock(__FUNCTION__, __LINE__); + packet->setArrayLengthByName("num_members", members.size()); + int32 i = 0; + for (itr = members.begin(); itr != members.end(); itr++) { + gm = itr->second; + packet->setArrayDataByName("account_id", gm->account_id, i); + packet->setArrayDataByName("character_id", gm->character_id, i); + packet->setArrayDataByName("name", gm->name, i); + packet->setArrayDataByName("unknown2", 0, i); + packet->setArrayDataByName("unknown3", 1, i); + packet->setArrayDataByName("adventure_class", gm->adventure_class, i); + packet->setArrayDataByName("adventure_level", gm->adventure_level, i); + packet->setArrayDataByName("tradeskill_class", gm->tradeskill_class, i); + packet->setArrayDataByName("tradeskill_level", gm->tradeskill_level, i); + packet->setArrayDataByName("rank", gm->rank, i); + packet->setArrayDataByName("member_flags", gm->member_flags, i); + packet->setArrayDataByName("join_date", gm->join_date, i); + packet->setArrayDataByName("guild_status", gm->guild_status, i); + packet->setArrayDataByName("last_login", gm->last_login_date, i); + packet->setArrayDataByName("recruiter_id", gm->recruiter_id, i); + packet->setArrayDataByName("points", gm->points, i); + if (zone_list.GetClientByCharID(gm->character_id)) + packet->setArrayDataByName("zone", gm->zone.c_str(), i); + packet->setArrayDataByName("note", gm->note.c_str(), i); + packet->setArrayDataByName("officer_note", gm->officer_note.c_str(), i); + i++; + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + //DumpPacket(packet->serialize()); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } +} + +void Guild::SendGuildMember(Player* player, bool include_zone) { + + map::iterator itr; + Client *client; + GuildMember *gm; + + assert(player); + + mMembers.readlock(__FUNCTION__, __LINE__); + if (members.count(player->GetCharacterID()) > 0) { + gm = members[player->GetCharacterID()]; + + for (itr = members.begin(); itr != members.end(); itr++) { + if ((client = zone_list.GetClientByCharID(itr->second->character_id))) + SendGuildMember(client, gm, include_zone); + } + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild member."); +} + +void Guild::SendGuildMember(GuildMember* gm, bool include_zone) { + + map::iterator itr; + Client *client; + + assert(gm); + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if ((client = zone_list.GetClientByCharID(itr->second->character_id))) + SendGuildMember(client, gm, include_zone); + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild member."); +} + +void Guild::SendGuildMember(Client* client, GuildMember* gm, bool include_zone) { + + if (client && gm) { + PacketStruct* packet = configReader.getStruct("WS_JoinGuildNotify", client->GetVersion()); + if (packet) { + packet->setDataByName("guild_id", id); + packet->setDataByName("character_id", gm->character_id); + packet->setDataByName("account_id", gm->account_id); + packet->setMediumStringByName("name", gm->name); + packet->setDataByName("unknown2", 0); + packet->setDataByName("unknown3", 1); + packet->setDataByName("adventure_class", gm->adventure_class); + packet->setDataByName("adventure_level", gm->adventure_level); + packet->setDataByName("tradeskill_class", gm->tradeskill_class); + packet->setDataByName("tradeskill_level", gm->tradeskill_level); + packet->setDataByName("rank", gm->rank); + packet->setDataByName("member_flags", gm->member_flags); + packet->setDataByName("join_date", gm->join_date); + packet->setDataByName("guild_status", gm->guild_status); + packet->setDataByName("last_login", gm->last_login_date); + packet->setDataByName("recruiter_id", gm->recruiter_id); + packet->setDataByName("points", gm->points); + packet->setMediumStringByName("note", gm->note.c_str()); + packet->setMediumStringByName("officer_note", gm->officer_note.c_str()); + if (include_zone && zone_list.GetClientByCharID(gm->character_id)) { + packet->setMediumStringByName("zone", gm->zone.c_str()); + //DumpPacket(packet->serialize()); + } + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild member to a client."); +} + +void Guild::SendGuildModification(float points, vector* character_ids) { + + map::iterator itr; + Client *client; + + if (character_ids) { + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if ((client = zone_list.GetClientByCharID(itr->second->character_id))) + SendGuildModification(client, points, character_ids); + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + } + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild modification to all clients."); +} + +void Guild::SendGuildModification(Client* client, float points, vector* character_ids) { + + if (client && character_ids) { + PacketStruct* packet = configReader.getStruct("WS_ModifyGuild", client->GetVersion()); + if (packet) { + packet->setDataByName("guild_id", id); + packet->setDataByName("unknown2", 0xFFFFFFFF); + packet->setDataByName("points", points); + packet->setArrayLengthByName("num_character_ids", character_ids->size()); + for (int32 i = 0; i < character_ids->size(); i++) + packet->setArrayDataByName("character_id", character_ids->at(i), i); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild modification to a client."); +} + +void Guild::GuildMemberLogin(Client *client, bool first_login) { + + map::iterator itr; + Client *client_to; + char buf[128]; + + assert(client); + + UpdateGuildMemberInfo(client->GetPlayer()); + if (first_login) + SendGuildMOTD(client); + SendGuildUpdate(client); + if (first_login) + SendGuildMember(client->GetPlayer(), false); + SendGuildRecruiterInfo(client, client->GetPlayer()); + SendGuildEventList(client); + SendGuildBankEventList(client); + SendGuildMember(client->GetPlayer()); + SendGuildEventDetails(client); + uchar blah5[] = {/*0xFF,0x09,0x01,*/0x01,0x00,0x00,0x00,0x00,0x00,0x00}; + uchar blah6[] = {/*0xFF,0x09,0x01,*/0x01,0x00,0x00,0x00,0x01,0x00,0x00}; + uchar blah7[] = {/*0xFF,0x09,0x01,*/0x01,0x00,0x00,0x00,0x02,0x00,0x00}; + uchar blah8[] = {/*0xFF,0x09,0x01,*/0x01,0x00,0x00,0x00,0x03,0x00,0x00}; + + //DumpPacket(blah5, sizeof(blah5)); + //DumpPacket(blah6, sizeof(blah6)); + //DumpPacket(blah7, sizeof(blah7)); + //DumpPacket(blah8, sizeof(blah8)); + + client->QueuePacket(new EQ2Packet(OP_RequestGuildBankEventDetailsMs, blah5, sizeof(blah5))); + client->QueuePacket(new EQ2Packet(OP_RequestGuildBankEventDetailsMs, blah6, sizeof(blah6))); + client->QueuePacket(new EQ2Packet(OP_RequestGuildBankEventDetailsMs, blah7, sizeof(blah7))); + client->QueuePacket(new EQ2Packet(OP_RequestGuildBankEventDetailsMs, blah8, sizeof(blah8))); + if (first_login) + SendAllGuildEvents(client); + SendGuildMemberList(client); + if (first_login) { + snprintf(buf, sizeof(buf), "Guildmate: %s has logged in", client->GetPlayer()->GetName()); + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if ((client_to = zone_list.GetClientByCharID(itr->second->character_id))) + client_to->SimpleMessage(CHANNEL_GUILD_MEMBER_ONLINE, buf); + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + if (first_login){ + uchar blah1[] = {/*0xFF,0x09,0x01,*/0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + uchar blah2[] = {/*0xFF,0x09,0x01,*/0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00}; + uchar blah3[] = {/*0xFF,0x09,0x01,*/0x01,0x00,0x00,0x00,0x02,0x00,0x00,0x00}; + uchar blah4[] = {/*0xFF,0x09,0x01,*/0x01,0x00,0x00,0x00,0x03,0x00,0x00,0x00}; + + //DumpPacket(blah1, sizeof(blah1)); + //DumpPacket(blah2, sizeof(blah2)); + //DumpPacket(blah3, sizeof(blah3)); + //DumpPacket(blah4, sizeof(blah4)); + + client->QueuePacket(new EQ2Packet(OP_GuildBankUpdateMsg, blah1, sizeof(blah1))); + client->QueuePacket(new EQ2Packet(OP_GuildBankUpdateMsg, blah2, sizeof(blah2))); + client->QueuePacket(new EQ2Packet(OP_GuildBankUpdateMsg, blah3, sizeof(blah3))); + client->QueuePacket(new EQ2Packet(OP_GuildBankUpdateMsg, blah4, sizeof(blah4))); + } + LogWrite(GUILD__DEBUG, 0, "Guilds", "Guild Member logged in."); +} + +void Guild::GuildMemberLogoff(Player *player) { + + map::iterator itr; + GuildMember *gm; + Client *client; + char buf[128]; + + assert(player); + + mMembers.readlock(__FUNCTION__, __LINE__); + if ((gm = GetGuildMember(player))) { + snprintf(buf, sizeof(buf), "Guildmate: %s has logged out", player->GetName()); + gm->zone.clear(); + + for (itr = members.begin(); itr != members.end(); itr++) { + if ((client = zone_list.GetClientByCharID(itr->second->character_id))) { + SendGuildMember(client, gm, false); + client->SimpleMessage(CHANNEL_GUILD_MEMBER_ONLINE, buf); + } + } + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 0, "Guilds", "Guild Member logged out."); +} + +void Guild::SendGuildMemberLeave(int32 character_id) { + + map::iterator itr; + Client *client; + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if ((client = zone_list.GetClientByCharID(itr->second->character_id))) + SendGuildMemberLeave(client, character_id); + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild member left the guild to all clients."); +} + +void Guild::SendGuildMemberLeave(Client* client, int32 character_id) { + + PacketStruct* packet = configReader.getStruct("WS_LeaveGuildNotify", client->GetVersion()); + if (packet) { + packet->setDataByName("guild_id", id); + packet->setDataByName("character_id", character_id); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild member left the guild to a client."); +} + +void Guild::SendGuildRecruitingDetails(Client* client) { + + if (client) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild recruiting details to a client."); + PacketStruct* packet = configReader.getStruct("WS_GuildRecruitingDetails", client->GetVersion()); + if (packet) { + vector* recruiters = GetGuildRecruiters(); + vector::iterator itr; + packet->setDataByName("guild_id", id); + packet->setDataByName("recruiting_full_description", recruiting_full_desc.c_str()); + if (recruiters) { + int32 i = 0; + packet->setArrayLengthByName("num_recruiters", recruiters->size()); + for (itr = recruiters->begin(); itr != recruiters->end(); itr++) { + GuildMember* gm = *itr; + packet->setArrayDataByName("adventure_class", gm->adventure_class, i); + packet->setArrayDataByName("adventure_level", gm->adventure_level, i); + packet->setArrayDataByName("tradeskill_class", gm->tradeskill_class, i); + packet->setArrayDataByName("tradeskill_level", gm->tradeskill_level, i); + packet->setArrayDataByName("show_adventure_class", gm->recruiting_show_adventure_class, i); + packet->setArrayDataByName("unknown2", 4, i); + packet->setArrayDataByName("unknown3", 2, i); + packet->setSubArrayLengthByName("num_bytes", gm->recruiter_picture_data_size, i); + if (gm->recruiter_picture_data_size > 0) { + for (int16 j = 0; j < gm->recruiter_picture_data_size; j++) + packet->setSubArrayDataByName("picture_byte", gm->recruiter_picture_data[j], i, j); + } + packet->setArrayDataByName("char_name", gm->name, i); + packet->setArrayDataByName("recruiter_description", gm->recruiter_description.c_str(), i); + i++; + } + safe_delete(recruiters); + } + //DumpPacket(packet->serialize()); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } +} + +void Guild::SendGuildRecruitingImages(Client* client) { + if (client) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild recruiting images to a client."); + PacketStruct* packet = configReader.getStruct("WS_GuildRecruitingImage", client->GetVersion()); + if (packet) { + vector* recruiters = GetGuildRecruiters(); + packet->setDataByName("guild_id", id); + if (recruiters && recruiters->size() > 0) { + GuildMember* gm = recruiters->at(0); + packet->setArrayLengthByName("num_bytes", gm->recruiter_picture_data_size); + if (gm->recruiter_picture_data_size > 0) { + for (int16 i = 0; i < gm->recruiter_picture_data_size; i++) + packet->setArrayDataByName("picture_byte", gm->recruiter_picture_data[i], i); + } + safe_delete(recruiters); + } + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } +} + +void Guild::SendGuildRecruiterInfo(Client* client, Player* player) { + + if (client && player) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild recruiter info to a client."); + GuildMember* gm = GetGuildMember(player); + if (gm) { + PacketStruct* packet = configReader.getStruct("WS_GuildRecruitingMemberInfo", client->GetVersion()); + if (packet) { + packet->setDataByName("character_id", gm->character_id); + packet->setDataByName("unknown", 1); + packet->setDataByName("adventure_class", gm->adventure_class); + packet->setDataByName("adventure_level", gm->adventure_level); + packet->setDataByName("tradeskill_class", gm->tradeskill_class); + packet->setDataByName("tradeskill_level", gm->tradeskill_level); + packet->setDataByName("show_adventure_class", gm->recruiting_show_adventure_class); + packet->setDataByName("unknown3", 0); + + // hack! + gm->recruiter_picture_data_size = 0; + packet->setArrayLengthByName("num_bytes", gm->recruiter_picture_data_size); + if (gm->recruiter_picture_data_size > 0) { + for (int16 i = 0; i < gm->recruiter_picture_data_size; i++) + packet->setArrayDataByName("picture_byte", gm->recruiter_picture_data[i], i); + } + + packet->setMediumStringByName("recruiter_name", gm->name); + packet->setMediumStringByName("recruiter_description", gm->recruiter_description.c_str()); + //DumpPacket(packet->serialize()); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + } +} + +void Guild::HandleGuildSay(Client* sender, const char* message) { + + map::iterator itr; + GuildMember *gm; + Client *client; + + assert(sender); + assert(message); + + if (!(gm = GetGuildMemberOnline(sender))) + return; + + if (!permissions.Get(gm->rank)->Get(GUILD_PERMISSIONS_SPEAK_IN_GUILD_CHAT)) { + sender->SimpleMessage(CHANNEL_NARRATIVE, "You do not have permission to speak in guild chat."); + return; + } + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if (!(client = zone_list.GetClientByCharID(itr->second->character_id))) + continue; + + if (permissions.Get(itr->second->rank)->Get(GUILD_PERMISSIONS_SEE_GUILD_CHAT)) + client->GetCurrentZone()->HandleChatMessage(client, sender->GetPlayer(), client->GetPlayer()->GetName(), CHANNEL_GUILD_SAY, message, 0, 0, false, sender->GetPlayer()->GetCurrentLanguage()); + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 0, "Guilds", "Guild Say"); +} + +void Guild::HandleOfficerSay(Client* sender, const char* message) { + + map::iterator itr; + GuildMember *gm; + Client *client; + + assert(sender); + assert(message); + + if (!(gm = GetGuildMemberOnline(sender))) + return; + + if (!permissions.Get(gm->rank)->Get(GUILD_PERMISSIONS_SPEAK_IN_OFFICER_CHAT)) { + sender->SimpleMessage(CHANNEL_NARRATIVE, "You do not have permission to speak in officer chat."); + return; + } + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if (!(client = zone_list.GetClientByCharID(itr->second->character_id))) + continue; + + if (permissions.Get(itr->second->rank)->Get(GUILD_PERMISSIONS_SEE_OFFICER_CHAT)) + client->GetCurrentZone()->HandleChatMessage(client, sender->GetPlayer(), client->GetPlayer()->GetName(), CHANNEL_OFFICER_SAY, message, 0, 0, false, sender->GetPlayer()->GetCurrentLanguage()); + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 0, "Guilds", "Officer Say"); +} + +void Guild::SendMessageToGuild(int8 event_type, const char* message, ...) { + + map::iterator itr; + Client *client; + va_list argptr; + char buffer[4096]; + + va_start(argptr, message); + vsnprintf(buffer, sizeof(buffer), message, argptr); + va_end(argptr); + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if (!(client = zone_list.GetClientByCharID(itr->second->character_id))) + continue; + + if (event_filters.Get(itr->second->rank)->Get(GUILD_EVENT_FILTER_CATEGORY_BROADCAST)) + client->SimpleMessage(CHANNEL_GUILD_EVENT, buffer); + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 0, "Guilds", "Sent message to entire guild."); +} + +string Guild::GetEpicMobDeathMessage(const char* player_name, const char* mob_name) { + + char message[256]; + int8 choice; + + assert(player_name); + assert(mob_name); + + choice = (rand() % 5) + 1; + if (choice == 1) + snprintf(message, sizeof(message), "%s was slain by %s in a thunderous engagement!", mob_name, player_name); + else if (choice == 2) + snprintf(message, sizeof(message), "%s was slain by %s in a titanic struggle!", mob_name, player_name); + else if (choice == 3) + snprintf(message, sizeof(message), "%s was slain by %s's heroic might!", mob_name, player_name); + else if (choice == 4) + snprintf(message, sizeof(message), "%s slew %s in an earth shaking battle!", player_name, mob_name); + else + snprintf(message, sizeof(message), "%s slew %s in a heroic clash!", player_name, mob_name); + + LogWrite(GUILD__DEBUG, 0, "Guilds", "Guild Epic Mob Death message sent."); + return string(message); +} + +/*************************************************************************************************************************************************************** + * GUILDLIST + ***************************************************************************************************************************************************************/ + +GuildList::GuildList() { +} + +GuildList::~GuildList() { + + MutexMap::iterator itr = guild_list.begin(); + while (itr.Next()) + safe_delete(itr.second); +} + +bool GuildList::AddGuild(Guild* guild) { + + bool ret = false; + if (guild && guild_list.count(guild->GetID()) == 0) { + guild_list.Put(guild->GetID(), guild); + ret = true; + } + return ret; +} + +Guild* GuildList::GetGuild(int32 guild_id) { + + Guild* ret = 0; + if (guild_list.count(guild_id) > 0) + ret = guild_list.Get(guild_id); + return ret; +} + +Guild* GuildList::GetGuild(const char* guild_name) { + + Guild* ret = 0; + MutexMap::iterator itr = guild_list.begin(); + while (itr.Next()) { + if (strncasecmp(itr.second->GetName(), guild_name, strlen(guild_name)) == 0) { + ret = itr.second; + break; + } + } + return ret; +} + +bool GuildList::RemoveGuild(Guild* guild, bool delete_data) { + + bool ret = false; + if (guild && guild_list.count(guild->GetID()) > 0) { + guild_list.erase(guild->GetID(), false, delete_data); + ret = true; + } + return ret; +} + +bool GuildList::RemoveGuild(int32 guild_id, bool delete_data) { + + bool ret = false; + if (guild_list.count(guild_id) > 0) { + guild_list.erase(guild_id, false, delete_data); + ret = true; + } + return ret; +} diff --git a/source/WorldServer/Guilds/Guild.h b/source/WorldServer/Guilds/Guild.h new file mode 100644 index 0000000..9ce4923 --- /dev/null +++ b/source/WorldServer/Guilds/Guild.h @@ -0,0 +1,450 @@ +/* + 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 . +*/ + +#ifndef GUILD_H_ +#define GUILD_H_ + +#include +#include +#include +#include "../../common/Mutex.h" +#include "../MutexMap.h" +using namespace std; + +class ZoneServer; +class Client; +class Player; + +#define GUILD_RANK_LEADER 0 +#define GUILD_RANK_SENIOR_OFFICER 1 +#define GUILD_RANK_OFFICER 2 +#define GUILD_RANK_SENIOR_MEMBER 3 +#define GUILD_RANK_MEMBER 4 +#define GUILD_RANK_JUNIOR_MEMBER 5 +#define GUILD_RANK_INITIATE 6 +#define GUILD_RANK_RECRUIT 7 + +#define GUILD_PERMISSIONS_INVITE 0 +#define GUILD_PERMISSIONS_RMEOVE_MEMBER 1 +#define GUILD_PERMISSIONS_PROMOTE_MEMBER 2 +#define GUILD_PERMISSIONS_DEMOTE_MEMBER 3 +#define GUILD_PERMISSIONS_CHANGE_MOTD 6 +#define GUILD_PERMISSIONS_CHANGE_PERMISSIONS 7 +#define GUILD_PERMISSIONS_CHANGE_RANK_NAMES 8 +#define GUILD_PERMISSIONS_SEE_OFFICER_NOTES 9 +#define GUILD_PERMISSIONS_EDIT_OFFICER_NOTES 10 +#define GUILD_PERMISSIONS_SEE_OFFICER_CHAT 11 +#define GUILD_PERMISSIONS_SPEAK_IN_OFFICER_CHAT 12 +#define GUILD_PERMISSIONS_SEE_GUILD_CHAT 13 +#define GUILD_PERMISSIONS_SPEAK_IN_GUILD_CHAT 14 +#define GUILD_PERMISSIONS_EDIT_PERSONAL_NOTES 15 +#define GUILD_PERMISSIONS_EDIT_PERSONAL_NOTES_OTHERS 16 +#define GUILD_PERMISSIONS_EDIT_EVENT_FILTERS 17 +#define GUILD_PERMISSIONS_EDIT_EVENTS 18 +#define GUILD_PERMISSIONS_PURCHASE_STATUS_ITEMS 19 +#define GUILD_PERMISSIONS_DISPLAY_GUILD_NAME 20 +#define GUILD_PERMISSIONS_SEND_EMAIL_TO_GUILD 21 +#define GUILD_PERMISSIONS_BANK1_SEE_CONTENTS 22 +#define GUILD_PERMISSIONS_BANK2_SEE_CONTENTS 23 +#define GUILD_PERMISSIONS_BANK3_SEE_CONTENTS 24 +#define GUILD_PERMISSIONS_BANK4_SEE_CONTENTS 25 +#define GUILD_PERMISSIONS_BANK1_DEPOSIT 26 +#define GUILD_PERMISSIONS_BANK2_DEPOSIT 27 +#define GUILD_PERMISSIONS_BANK3_DEPOSIT 28 +#define GUILD_PERMISSIONS_BANK4_DEPOSIT 29 +#define GUILD_PERMISSIONS_BANK1_WITHDRAWL 30 +#define GUILD_PERMISSIONS_BANK2_WITHDRAWL 31 +#define GUILD_PERMISSIONS_BANK3_WITHDRAWL 32 +#define GUILD_PERMISSIONS_BANK4_WITHDRAWL 33 +#define GUILD_PERMISSIONS_EDIT_RECRUITING_SETTINGS 35 +#define GUILD_PERMISSIONS_MAKE_OTHERS_RECRUITERS 36 +#define GUILD_PERMISSIONS_SEE_RECRUITING_SETTINGS 37 +#define GUILD_PERMISSIONS_ASSIGN_POINTS 43 +#define GUILD_PERMISSIONS_RECEIVE_POINTS 44 + +#define GUILD_EVENT_FILTER_CATEGORY_RETAIN_HISTORY 0 +#define GUILD_EVENT_FILTER_CATEGORY_BROADCAST 1 + +#define GUILD_EVENT_GUILD_LEVEL_UP 0 +#define GUILD_EVENT_GUILD_LEVEL_DOWN 1 +#define GUILD_EVENT_DISCOVERS_ITEM 2 +#define GUILD_EVENT_GAINS_ADV_LEVEL_1_10 3 +#define GUILD_EVENT_GAINS_ADV_LEVEL_11_20 4 +#define GUILD_EVENT_GAINS_ADV_LEVEL_21_30 5 +#define GUILD_EVENT_GAINS_ADV_LEVEL_31_40 6 +#define GUILD_EVENT_GAINS_ADV_LEVEL_41_50 7 +#define GUILD_EVENT_GAINS_TS_LEVEL_1_10 8 +#define GUILD_EVENT_GAINS_TS_LEVEL_11_20 9 +#define GUILD_EVENT_GAINS_TS_LEVEL_21_30 10 +#define GUILD_EVENT_GAINS_TS_LEVEL_31_40 11 +#define GUILD_EVENT_GAINS_TS_LEVEL_41_50 12 +#define GUILD_EVENT_MEMBER_JOINS 13 +#define GUILD_EVENT_MEMBER_LEAVES 14 +#define GUILD_EVENT_MEMBER_PROMOTED 15 +#define GUILD_EVENT_MEMBER_DEMOTED 16 +#define GUILD_EVENT_COMPLETES_HERITAGE_QUEST 19 +#define GUILD_EVENT_KILLS_EPIC_MONSTER 20 +#define GUILD_EVENT_LOOTS_ARTIFACT 21 +#define GUILD_EVENT_LOOTS_FABELED_ITEM 22 +#define GUILD_EVENT_LOOTS_LEGENDARY_ITEM 23 +#define GUILD_EVENT_COMPLETES_WRIT 24 +#define GUILD_EVENT_LOOTS_MYTHICAL_ITEM 25 +#define GUILD_EVENT_GAINS_ADV_LEVEL_10 26 +#define GUILD_EVENT_GAINS_ADV_LEVEL_20 27 +#define GUILD_EVENT_GAINS_ADV_LEVEL_30 28 +#define GUILD_EVENT_GAINS_ADV_LEVEL_40 29 +#define GUILD_EVENT_GAINS_ADV_LEVEL_50 30 +#define GUILD_EVENT_GAINS_TS_LEVEL_10 31 +#define GUILD_EVENT_GAINS_TS_LEVEL_20 32 +#define GUILD_EVENT_GAINS_TS_LEVEL_30 33 +#define GUILD_EVENT_GAINS_TS_LEVEL_40 34 +#define GUILD_EVENT_GAINS_TS_LEVEL_50 35 +#define GUILD_EVENT_GAINS_ADV_LEVEL_51_60 37 +#define GUILD_EVENT_GAINS_TS_LEVEL_51_60 38 +#define GUILD_EVENT_GAINS_ADV_LEVEL_60 39 +#define GUILD_EVENT_GAINS_TS_LEVEL_60 40 +#define GUILD_EVENT_GAINS_ADV_LEVEL_61_70 41 +#define GUILD_EVENT_GAINS_TS_LEVEL_61_70 42 +#define GUILD_EVENT_GAINS_ADV_LEVEL_70 43 +#define GUILD_EVENT_GAINS_TS_LEVEL_70 44 +#define GUILD_EVENT_GAINS_AA_10 45 +#define GUILD_EVENT_GAINS_AA_20 46 +#define GUILD_EVENT_GAINS_AA_30 47 +#define GUILD_EVENT_GAINS_AA_40 48 +#define GUILD_EVENT_GAINS_AA_50 49 +#define GUILD_EVENT_GAINS_AA_1_10 50 +#define GUILD_EVENT_GAINS_AA_11_20 51 +#define GUILD_EVENT_GAINS_AA_21_30 52 +#define GUILD_EVENT_GAINS_AA_31_40 53 +#define GUILD_EVENT_GAINS_AA_41_50 54 +#define GUILD_EVENT_BECOMES_RECRUITER 55 +#define GUILD_EVENT_NO_LONGER_RECRUITER 56 +#define GUILD_EVENT_HERALDY_CHANGE 57 +#define GUILD_EVENT_GAINS_AA_60 58 +#define GUILD_EVENT_GAINS_AA_70 59 +#define GUILD_EVENT_GAINS_AA_80 60 +#define GUILD_EVENT_GAINS_AA_90 61 +#define GUILD_EVENT_GAINS_AA_100 62 +#define GUILD_EVENT_GAINS_AA_51_60 63 +#define GUILD_EVENT_GAINS_AA_61_70 64 +#define GUILD_EVENT_GAINS_AA_71_80 65 +#define GUILD_EVENT_GAINS_AA_81_90 66 +#define GUILD_EVENT_GAINS_AA_91_100 67 +#define GUILD_EVENT_GAINS_ADV_LEVEL_80 68 +#define GUILD_EVENT_GAINS_TS_LEVEL_80 69 +#define GUILD_EVENT_GAINS_ADV_LEVEL_71_80 70 +#define GUILD_EVENT_GAINS_TS_LEVEL_71_80 71 +#define GUILD_EVENT_GAINS_AA_110 72 +#define GUILD_EVENT_GAINS_AA_120 73 +#define GUILD_EVENT_GAINS_AA_130 74 +#define GUILD_EVENT_GAINS_AA_140 75 +#define GUILD_EVENT_GAINS_AA_101_110 76 +#define GUILD_EVENT_GAINS_AA_111_120 77 +#define GUILD_EVENT_GAINS_AA_121_130 78 +#define GUILD_EVENT_GAINS_AA_131_140 79 +#define GUILD_EVENT_GAINS_AA_150 80 +#define GUILD_EVENT_GAINS_AA_141_150 81 +#define GUILD_EVENT_GAINS_AA_160 82 +#define GUILD_EVENT_GAINS_AA_170 83 +#define GUILD_EVENT_GAINS_AA_180 84 +#define GUILD_EVENT_GAINS_AA_190 85 +#define GUILD_EVENT_GAINS_AA_200 86 +#define GUILD_EVENT_GAINS_AA_151_160 87 +#define GUILD_EVENT_GAINS_AA_161_170 88 +#define GUILD_EVENT_GAINS_AA_171_180 89 +#define GUILD_EVENT_GAINS_AA_181_190 90 +#define GUILD_EVENT_GAINS_AA_191_200 91 +#define GUILD_EVENT_EARNS_ACHIEVEMENT 92 + +#define GUILD_RECRUITING_FLAG_TRAINING 0 +#define GUILD_RECRUITING_FLAG_FIGHTERS 1 +#define GUILD_RECRUITING_FLAG_PRIESTS 2 +#define GUILD_RECRUITING_FLAG_SCOUTS 3 +#define GUILD_RECRUITING_FLAG_MAGES 4 +#define GUILD_RECRUITING_FLAG_TRADESKILLERS 5 + +#define GUILD_RECRUITING_PLAYSTYLE_NONE 0 +#define GUILD_RECRUITING_PLAYSTYLE_CASUAL 1 +#define GUILD_RECRUITING_PLAYSTYLE_HARDCORE 2 + +#define GUILD_RECRUITING_DESC_TAG_NONE 0 +#define GUILD_RECRUITING_DESC_TAG_GOOD 1 +#define GUILD_RECRUITING_DESC_TAG_EVIL 2 +#define GUILD_RECRUITING_DESC_TAG_CHATTY 3 +#define GUILD_RECRUITING_DESC_TAG_ORGANIZED 4 +#define GUILD_RECRUITING_DESC_TAG_ROLEPLAY 5 +#define GUILD_RECRUITING_DESC_TAG_ENJOY_QUESTS 6 +#define GUILD_RECRUITING_DESC_TAG_ENJOY_RAIDS 7 +#define GUILD_RECRUITING_DESC_TAG_ODD_HOURS 8 +#define GUILD_RECRUITING_DESC_TAG_CRAFTER_ORIENTED 9 +#define GUILD_RECRUITING_DESC_TAG_FAMILY_FRIENDLY 10 +#define GUILD_RECRUITING_DESC_TAG_MATURE_HUMOR 11 +#define GUILD_RECRUITING_DESC_TAG_INMATES_RUN 12 +#define GUILD_RECRUITING_DESC_TAG_VERY_FUNNY 13 +#define GUILD_RECRUITING_DESC_TAG_HUMOR_CAUES_PAIN 14 +#define GUILD_RECRUITING_DESC_TAG_SERIOUS 15 + +#define GUILD_MEMBER_FLAGS_RECRUITING_FOR_GUILD 1 +#define GUILD_MEMBER_FLAGS_NOTIFY_LOGINS 2 +#define GUILD_MEMBER_FLAGS_DONT_GENERATE_EVENTS 4 + +#define GUILD_EVENT_ACTION_LOCK 0 +#define GUILD_EVENT_ACTION_UNLOCK 1 +#define GUILD_EVENT_ACTION_DELETE 2 + +#define GUILD_MAX_LEVEL 80 +#define GUILD_MAX_POINT_HISTORY 50 +#define GUILD_MAX_EVENTS 500 +#define GUILD_MAX_LOCKED_EVENTS 200 + +struct PointHistory { + int32 date; + string modified_by; + string comment; + float points; + bool saved_needed; +}; + +struct GuildMember { + int32 character_id; + int32 account_id; + int32 recruiter_id; //00 00 00 00 if not a guild recruiter + char name[64]; + int32 guild_status; + float points; + int8 adventure_class; + int8 adventure_level; + int8 tradeskill_class; + int8 tradeskill_level; + int8 rank; + int8 member_flags; + string zone; + int32 join_date; + int32 last_login_date; + string note; + string officer_note; + string recruiter_description; + unsigned char* recruiter_picture_data; + int16 recruiter_picture_data_size; + int8 recruiting_show_adventure_class; + deque point_history; +}; + +struct GuildEvent { + int64 event_id; + int32 date; + int32 type; + string description; + int8 locked; + bool save_needed; +}; + +struct GuildBankEvent { + int64 event_id; + int32 date; + int32 type; + string description; +}; + +struct Bank { + string name; + deque events; +}; + +class Guild { +public: + Guild(); + virtual ~Guild(); + void SetID(int32 id_in) {id = id_in;} + void SetName(const char* name, bool send_packet = true); + void SetLevel(int8 level, bool send_packet = true); + void SetFormedDate(int32 formed_date_in) {formed_date = formed_date_in;} + void SetMOTD(const char *motd, bool send_packet = true); + int32 GetID() const {return id;} + const char* GetName() const {return name;} + int8 GetLevel() const {return level;} + int32 GetFormedDate() const {return formed_date;} + const char * GetMOTD() const {return motd;} + void SetEXPCurrent(int64 exp, bool send_packet = true); + void AddEXPCurrent(sint64 exp, bool send_packet = true); + int64 GetEXPCurrent() const {return exp_current;} + void SetEXPToNextLevel(int64 exp, bool send_packet = true); + int64 GetEXPToNextLevel() const {return exp_to_next_level;} + void SetRecruitingShortDesc(const char* new_desc, bool send_packet = true); + string GetRecruitingShortDesc() const {return recruiting_short_desc;} + void SetRecruitingFullDesc(const char* new_desc, bool send_packet = true); + string GetRecruitingFullDesc() const {return recruiting_full_desc;} + void SetRecruitingMinLevel(int8 new_level, bool send_packet = true); + int8 GetRecruitingMinLevel() const {return recruiting_min_level;} + void SetRecruitingPlayStyle(int8 new_play_style, bool send_packet = true); + int8 GetRecruitingPlayStyle() const {return recruiting_play_style;} + bool SetRecruitingDescTag(int8 index, int8 tag, bool send_packet = true); + int8 GetRecruitingDescTag(int8 index); + bool SetPermission(int8 rank, int8 permission, int8 value, bool send_packet = true); + int8 GetPermission(int8 rank, int8 permission); + bool SetEventFilter(int8 event_id, int8 category, int8 value, bool send_packet = true); + int8 GetEventFilter(int8 event_id, int8 category); + int32 GetNumUniqueAccounts(); + int32 GetNumRecruiters(); + int32 GetNextRecruiterID(); + int64 GetNextEventID(); + GuildMember* GetGuildMemberOnline(Client* client); + GuildMember* GetGuildMember(Player* player); + GuildMember* GetGuildMember(int32 character_id); + GuildMember* GetGuildMember(const char* player_name); + vector* GetGuildRecruiters(); + GuildEvent* GetGuildEvent(int64 event_id); + bool SetRankName(int8 rank, const char* name, bool send_packet = true); + const char* GetRankName(int8 rank); + bool SetRecruitingFlag(int8 flag, int8 value, bool send_packet = true); + int8 GetRecruitingFlag(int8 flag); + bool SetGuildRecruiter(Client* client, const char* name, bool value, bool send_packet = true); + bool SetGuildRecruiterDescription(Client* client, const char* description, bool send_packet = true); + bool ToggleGuildRecruiterAdventureClass(Client* client, bool send_packet = true); + bool SetGuildMemberNote(const char* name, const char* note, bool send_packet = true); + bool SetGuildOfficerNote(const char* name, const char* note, bool send_packet = true); + bool AddNewGuildMember(Client* client, const char* invited_by = 0, int8 rank = GUILD_RANK_RECRUIT); + bool AddGuildMember(GuildMember* guild_member); + void RemoveGuildMember(int32 character_id, bool send_packet = true); + void RemoveAllGuildMembers(); + bool DemoteGuildMember(Client* client, const char* name, bool send_packet = true); + bool PromoteGuildMember(Client* client, const char* name, bool send_packet = true); + bool KickGuildMember(Client* client, const char* name, bool send_packet = true); + bool InvitePlayer(Client* client, const char* name, bool send_packet = true); + bool AddPointsToAll(Client* client, float points, const char* comment = 0, bool send_packet = true); + bool AddPointsToAllOnline(Client* client, float points, const char* comment = 0, bool send_packet = true); + bool AddPointsToGroup(Client* client, float points, const char* comment = 0, bool send_packet = true); + bool AddPointsToRaid(Client* client, float points, const char* comment = 0, bool send_packet = true); + bool AddPointsToGuildMember(Client* client, float points, const char* name, const char* comment = 0, bool send_packet = true); + bool AddPointHistory(GuildMember* guild_member, int32 date, const char* modified_by, float points, const char* comment = 0, bool new_point_history = true); + void ViewGuildMemberPoints(Client* client, const char* name); + bool ChangeMemberFlag(Client* client, int8 member_flag, int8 value, bool send_packet = true); + bool UpdateGuildMemberInfo(Player* player); + bool UpdateGuildStatus(Player *player, int32 Status); + void AddGuildEvent(int64 event_id, int32 type, const char* description, int32 date, int8 locked); + void AddNewGuildEvent(int32 type, const char* description, int32 date, bool send_packet = true, ...); + bool LockGuildEvent(int64 event_id, bool lock, bool send_packet = true); + bool DeleteGuildEvent(int64 event_id, bool send_packet = true); + void SendGuildMOTD(Client* client); + void SendGuildEventList(); + void SendGuildEventList(Client* client); + void SendGuildEventDetails(); + void SendGuildEventDetails(Client* client); + void SendAllGuildEvents(); + void SendAllGuildEvents(Client* client); + void SendOldGuildEvent(Client* client, GuildEvent* guild_event); + void SendNewGuildEvent(GuildEvent* guild_event); + void SendNewGuildEvent(Client* client, GuildEvent* guild_event); + void SendGuildEventAction(int8 action, GuildEvent* guild_event); + void SendGuildEventAction(Client* client, int8 action, GuildEvent* guild_event); + void SendGuildBankEventList(); + void SendGuildBankEventList(Client* client); + void SendGuildUpdate(); + void SendGuildUpdate(Client* client); + void SendGuildMemberList(); + void SendGuildMemberList(Client* client); + void SendGuildMember(Player* player, bool include_zone = true); + void SendGuildMember(GuildMember* gm, bool include_zone = true); + void SendGuildMember(Client* client, GuildMember* gm, bool include_zone = true); + void SendGuildModification(float points, vector* character_ids); + void SendGuildModification(Client* client, float points, vector* character_ids); + void GuildMemberLogin(Client *client, bool first_login = false); + void GuildMemberLogoff(Player *player); + void SendGuildMemberLeave(int32 character_id); + void SendGuildMemberLeave(Client* client, int32 character_id); + void SendGuildRecruitingDetails(Client* client); + void SendGuildRecruitingImages(Client* client); + void SendGuildRecruiterInfo(Client* client, Player* player); + void HandleGuildSay(Client* sender, const char* message); + void HandleOfficerSay(Client* sender, const char* message); + void SendMessageToGuild(int8 event_type, const char* message, ...); + void SetSaveNeeded(bool val) {save_needed = val;} + bool GetSaveNeeded() {return save_needed;} + void SetMemberSaveNeeded(bool val) {member_save_needed = val;} + bool GetMemberSaveNeeded() {return member_save_needed;} + void SetEventsSaveNeeded(bool val) {events_save_needed = val;} + bool GetEventsSaveNeeded() {return events_save_needed;} + void SetRanksSaveNeeded(bool val) {ranks_save_needed = val;} + bool GetRanksSaveNeeded() {return ranks_save_needed;} + void SetEventFiltersSaveNeeded(bool val) {event_filters_save_needed = val;} + bool GetEventFiltersSaveNeeded() {return event_filters_save_needed;} + void SetPointsHistorySaveNeeded(bool val) {points_history_save_needed = val;} + bool GetPointsHistorySaveNeeded() {return points_history_save_needed;} + void SetRecruitingSaveNeeded(bool val) {recruiting_save_needed = val;} + bool GetRecruitingSaveNeeded() {return recruiting_save_needed;} + map* GetGuildMembers() {return &members;} + Mutex * GetGuildMembersMutex() {return &mMembers;} + deque* GetGuildEvents() {return &guild_events;} + MutexMap*>* GetPermissions() {return &permissions;} + MutexMap* GetGuildRanks() {return &ranks;} + MutexMap* GetRecruitingFlags() {return &recruiting_flags;} + MutexMap* GetRecruitingDescTags() {return &recruiting_desc_tags;} + int8 GetRecruitingLookingForPacketValue(); + static string GetEpicMobDeathMessage(const char* player_name, const char* mob_name); + +private: + int32 id; + char name[64]; + int8 level; + int32 formed_date; + char motd[256]; + int64 exp_current; + int64 exp_to_next_level; + string recruiting_short_desc; + string recruiting_full_desc; + int8 recruiting_min_level; + int8 recruiting_play_style; + MutexMap ranks; + map members; + Mutex mMembers; + deque guild_events; + MutexMap*> permissions; + MutexMap*> event_filters; + MutexMap recruiting_flags; + MutexMap recruiting_desc_tags; + Bank banks[4]; + int32 GetPermissionsPacketValue(int8 rank, int32 start, int32 end); + int32 GetEventFilterPacketValue(int8 category, int32 start, int32 end); + bool save_needed; + bool member_save_needed; + bool events_save_needed; + bool event_filters_save_needed; + bool ranks_save_needed; + bool points_history_save_needed; + bool recruiting_save_needed; +}; + +class GuildList { +public: + GuildList(); + virtual ~GuildList(); + bool AddGuild(Guild* guild); + Guild* GetGuild(int32 guild_id); + Guild* GetGuild(const char* guild_name); + bool RemoveGuild(Guild* guild, bool delete_data = false); + bool RemoveGuild(int32 guild_id, bool delete_data = false); + int32 GetNumGuilds() {return guild_list.size();} + MutexMap* GetGuilds() {return &guild_list;} + +private: + MutexMap guild_list; +}; + +#endif diff --git a/source/WorldServer/Guilds/GuildDB.cpp b/source/WorldServer/Guilds/GuildDB.cpp new file mode 100644 index 0000000..5c0ddae --- /dev/null +++ b/source/WorldServer/Guilds/GuildDB.cpp @@ -0,0 +1,582 @@ +/* + 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 . +*/ +#ifdef WIN32 + #include + #include +#endif +#include +#include +#include +#include +#include +#include +#include +#include "../../common/Log.h" +#include "../WorldDatabase.h" +#include "Guild.h" + +extern GuildList guild_list; +extern RuleManager rule_manager; + +void WorldDatabase::LoadGuilds() { + int32 num_guilds = 0; + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `id`, `name`, `motd`, `level`, `xp`, `xp_needed`, `formed_on` FROM `guilds`"); + while (result && (row = mysql_fetch_row(result))) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "%u. %s", atoul(row[0]), row[1]); + Guild* guild = new Guild; + guild->SetID(atoul(row[0])); + guild->SetName(row[1]); + if (row[2]) + guild->SetMOTD(row[2], false); + guild->SetLevel(atoi(row[3]), false); + guild->SetEXPCurrent(atoul(row[4]), false); + guild->SetEXPToNextLevel(atoul(row[5]), false); + guild->SetFormedDate(atoul(row[6])); + + LogWrite(GUILD__DEBUG, 3, "Guilds", "\tLoaded %i guild members.", LoadGuildMembers(guild)); + LogWrite(GUILD__DEBUG, 3, "Guilds", "\tLoading Guild Ranks..."); + LoadGuildRanks(guild); + LogWrite(GUILD__DEBUG, 3, "Guilds", "\tLoading Guild Event Filters..."); + LoadGuildEventFilters(guild); + LogWrite(GUILD__DEBUG, 3, "Guilds", "\tLoading Guild Events..."); + LoadGuildEvents(guild); + LogWrite(GUILD__DEBUG, 3, "Guilds", "\tLoading Guild Recruiting..."); + LoadGuildRecruiting(guild); + guild_list.AddGuild(guild); + num_guilds++; + } + LogWrite(GUILD__INFO, 0, "Guilds", "\tLoaded %u Guild(s)", num_guilds); +} + +int32 WorldDatabase::LoadGuildMembers(Guild* guild) { + int32 num_members = 0; + Query query; + MYSQL_ROW row; + char *name; + int32 char_id; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `char_id`, `recruiter_id`, `guild_status`, `points`, `rank_id`, `member_flags`, `join_date`, `note`, `officer_note`, `recruiting_message`, `recruiter_picture_data` FROM `guild_members` WHERE `guild_id`=%u", guild->GetID()); + + while (result && (row = mysql_fetch_row(result))) { + char_id = atoul(row[0]); + if (!(name = GetCharacterName(char_id))) { + LogWrite(GUILD__ERROR, 0, "Guilds", "WorldDatabase::LoadGuildMembers Cannot find guild member with character id %u.", char_id); + continue; + } + + GuildMember* gm = new GuildMember; + gm->character_id = char_id; + gm->recruiter_id = atoul(row[1]); + gm->guild_status = atoul(row[2]); + gm->points = atof(row[3]); + gm->rank = atoi(row[4]); + gm->member_flags = atoi(row[5]); + gm->join_date = atoul(row[6]); + if (row[7]) + gm->note = string(row[7]); + if (row[8]) + gm->officer_note = string(row[8]); + if (row[9]) + gm->recruiter_description = string(row[9]); + int16 recruiter_picture_data_size = 0; + if (row[10] && (recruiter_picture_data_size = strlen(row[10])) > 0) { + gm->recruiter_picture_data_size = recruiter_picture_data_size / 2; + gm->recruiter_picture_data = new unsigned char[gm->recruiter_picture_data_size]; + unsigned char* cpy = gm->recruiter_picture_data; + const char* str = row[10]; + char high, low; + for (const char* ptr = str; *ptr; ptr += 2) { + high = tolower(*ptr); + low = tolower(*(ptr+1)); + if (isdigit(high)) + high = high - '0'; + else if (high >= 'a' && high <= 'f') + high = (high - 'a') + 10; + else { + LogWrite(GUILD__ERROR, 0, "Guilds", "Guild mate with id %u has corrupt picture data. Data not loading.", gm->character_id); + safe_delete_array(gm->recruiter_picture_data); + gm->recruiter_picture_data_size = 0; + break; + } + if (isdigit(low)) + low = low - '0'; + else if (low >= 'a' && low <= 'f') + low = (low - 'a') + 10; + else { + LogWrite(GUILD__ERROR, 0, "Guilds", "Guild mate with id %u has corrupt picture data. Data not loading.", gm->character_id); + safe_delete_array(gm->recruiter_picture_data); + gm->recruiter_picture_data_size = 0; + break; + } + *cpy++ = low | (high << 4); + } + /*for (int16 i = 0; i < gm->recruiter_picture_data_size; i++) + if (i<10) + printf("int:%u hex:%x\n", gm->recruiter_picture_data[i], gm->recruiter_picture_data[i]);*/ + } + else { + gm->recruiter_picture_data_size = 0; + gm->recruiter_picture_data = 0; + } + strncpy(gm->name, name, sizeof(gm->name)); + gm->account_id = GetCharacterAccountID(char_id); + gm->adventure_class = GetCharacterClass(char_id); + gm->adventure_level = GetCharacterLevel(char_id); + gm->tradeskill_class = 0; + gm->tradeskill_level = 0; + gm->last_login_date = GetCharacterTimeStamp(char_id); + gm->zone = GetZoneDescription(GetCharacterCurrentZoneID(char_id)); + gm->recruiting_show_adventure_class = 1; + LoadGuildPointsHistory(guild, gm); + guild->AddGuildMember(gm); + safe_delete_array(name); + num_members++; + } + return num_members; +} + +void WorldDatabase::LoadGuildEvents(Guild* guild) { + if (guild) { + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `event_id`, `event_date`, `event_type`, `description`, `locked` FROM `guild_events` WHERE `guild_id`=%u AND `display`=1 AND `archived`=0 ORDER BY `event_date` DESC LIMIT 0, %u", guild->GetID(), GUILD_MAX_EVENTS); + while (result && (row = mysql_fetch_row(result))) + guild->AddGuildEvent(atoi64(row[0]), atoul(row[2]), row[3], atoul(row[1]), atoi(row[4])); + } +} + +void WorldDatabase::LoadGuildRanks(Guild* guild) { + + if (guild) { + LogWrite(GUILD__DEBUG, 3, "Guilds", "Loading Ranks for guild id: %u", guild->GetID()); + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `rank_id`, `rank_name`, `permission1`, `permission2` FROM `guild_ranks` WHERE `guild_id`=%u", guild->GetID()); + while (result && (row = mysql_fetch_row(result))) { + int8 rank_id = atoi(row[0]); + int32 permission1 = atoul(row[2]); + int32 permission2 = atoul(row[3]); + guild->SetRankName(rank_id, row[1], false); + LogWrite(GUILD__DEBUG, 5, "Guilds", "\tLoading rank_id: %i", rank_id); + LogWrite(GUILD__DEBUG, 5, "Guilds", "\tPermission1: %ul, Permission2: %ul", permission1, permission2); + for (int32 i = 0; i <= 44; i++) { + int32 bitwise_val; + if (i < 32) { + bitwise_val = (int32)pow(2.0, (double)(i)); + guild->SetPermission(rank_id, i, permission1 & bitwise_val ? 1 : 0, false); + LogWrite(GUILD__DEBUG, 5, "Guilds", "\tSetting Permission %u to %u", i, permission1 & bitwise_val ? 1 : 0); + } + else { + bitwise_val = (int32)pow(2.0, (double)(i - 32)); + guild->SetPermission(rank_id, i, permission2 & bitwise_val ? 1 : 0, false); + LogWrite(GUILD__DEBUG, 5, "Guilds", "\tSetting Permission %u to %u", i, permission2 & bitwise_val ? 1 : 0); + } + } + } + } +} + +void WorldDatabase::LoadGuildEventFilters(Guild* guild) { + if (guild) { + Query query; + MYSQL_ROW row; + bool event_filter_added = false; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `event_id`, `retain`, `broadcast` FROM `guild_event_filters` WHERE `guild_id`=%u", guild->GetID()); + while (result && (row = mysql_fetch_row(result))) { + guild->SetEventFilter(atoi(row[0]), GUILD_EVENT_FILTER_CATEGORY_RETAIN_HISTORY, atoi(row[1]), false); + guild->SetEventFilter(atoi(row[0]), GUILD_EVENT_FILTER_CATEGORY_BROADCAST, atoi(row[2]), false); + if (!event_filter_added) + event_filter_added = true; + } + + if (!event_filter_added) + LoadGuildDefaultEventFilters(guild); + } +} + +void WorldDatabase::LoadGuildPointsHistory(Guild* guild, GuildMember* guild_member) { + Query query; + MYSQL_ROW row; + MYSQL_RES* result; + + assert(guild); + assert(guild_member); + + result = query.RunQuery2(Q_SELECT, "SELECT `points_date`, `modified_by`, `comment`, `points` FROM `guild_points_history` WHERE `guild_id`=%u AND `char_id`=%u ORDER BY `points_date` DESC", guild->GetID(), guild_member->character_id); + while (result && (row = mysql_fetch_row(result))) + guild->AddPointHistory(guild_member, atoul(row[0]), row[1], atof(row[3]), row[2], false); +} + +void WorldDatabase::LoadGuildRecruiting(Guild* guild) { + if (guild) { + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `short_desc`, `full_desc`, `min_level`, `play_style`, `looking_for`, `descriptive_tag1`, `descriptive_tag2`, `descriptive_tag3`, `descriptive_tag4` FROM `guild_recruiting` WHERE `guild_id`=%u", guild->GetID()); + while (result && (row = mysql_fetch_row(result))) { + if (row[0]) + guild->SetRecruitingShortDesc(row[0], false); + if (row[1]) + guild->SetRecruitingFullDesc(row[1], false); + guild->SetRecruitingMinLevel(atoi(row[2]), false); + guild->SetRecruitingPlayStyle(atoi(row[3]), false); + for (int32 i = 0; i <= 5; i++) { + int32 bitwise_val = (int32)pow(2.0, (double)i); + guild->SetRecruitingFlag(i, atoi(row[4]) & bitwise_val ? 1 : 0, false); + } + guild->SetRecruitingDescTag(0, atoi(row[5]), false); + guild->SetRecruitingDescTag(1, atoi(row[6]), false); + guild->SetRecruitingDescTag(2, atoi(row[7]), false); + guild->SetRecruitingDescTag(3, atoi(row[8]), false); + } + } +} + +void WorldDatabase::SaveGuild(Guild* guild, bool new_guild) { + Query query; + + assert(guild); + + if (new_guild) { + LogWrite(GUILD__DEBUG, 3, "Guilds", "Saving NEW Guild '%s' (%u) data...", guild->GetName(), guild->GetID()); + query.RunQuery2(Q_INSERT, "INSERT INTO `guilds` (`name`, `motd`, `level`, `xp`, `xp_needed`, `formed_on`) " + "VALUES ('%s', '%s', %i, %llu, %llu, %u)", + getSafeEscapeString(guild->GetName()).c_str(), getSafeEscapeString(guild->GetMOTD()).c_str(), guild->GetLevel(), guild->GetEXPCurrent(), guild->GetEXPToNextLevel(), guild->GetFormedDate()); + guild->SetID(query.GetLastInsertedID()); + } + else { + LogWrite(GUILD__DEBUG, 3, "Guilds", "Saving Guild '%s' (%u) data...", guild->GetName(), guild->GetID()); + query.RunQuery2(Q_UPDATE, "UPDATE `guilds` " + "SET `name`='%s', `motd`='%s', `level`=%i, `xp`=%llu, `xp_needed`=%llu, `formed_on`=%u WHERE `id`=%u", + getSafeEscapeString(guild->GetName()).c_str(), getSafeEscapeString(guild->GetMOTD()).c_str(), guild->GetLevel(), guild->GetEXPCurrent(), guild->GetEXPToNextLevel(), guild->GetFormedDate(), guild->GetID()); + } + guild->SetSaveNeeded(false); +} + +void WorldDatabase::SaveGuildMembers(Guild* guild) { + map* members; + map::iterator itr; + Mutex *mMembers; + GuildMember *gm; + Query query, query2; + + assert(guild); + + members = guild->GetGuildMembers(); + mMembers = guild->GetGuildMembersMutex(); + + mMembers->readlock(__FUNCTION__, __LINE__); + for (itr = members->begin(); itr != members->end(); itr++) { + gm = itr->second; + LogWrite(GUILD__DEBUG, 5, "Guilds", "Saving Guild Member '%s' (%u) data...", gm->name, gm->character_id); + query.RunQuery2(Q_INSERT, "INSERT INTO `guild_members` (`guild_id`, `char_id`, `recruiter_id`, `guild_status`, `points`, `rank_id`, `member_flags`, `join_date`, `note`, `officer_note`, `recruiting_message`, `recruiter_picture_data`) VALUES (%u, %u, %u, %u, %f, %u, %u, %u, '%s', '%s', '%s', NULL) ON DUPLICATE KEY UPDATE `guild_id`=%u, `recruiter_id`=%u, `guild_status`=%u, `points`=%f, `rank_id`=%u, `member_flags`=%u, `join_date`=%u, `note`='%s', `officer_note`='%s', `recruiting_message`='%s', `recruiter_picture_data`=NULL", guild->GetID(), gm->character_id, gm->recruiter_id, gm->guild_status, gm->points, gm->rank, gm->member_flags, gm->join_date, getSafeEscapeString(gm->note.c_str()).c_str(), getSafeEscapeString(gm->officer_note.c_str()).c_str(), getSafeEscapeString(gm->recruiter_description.c_str()).c_str(), guild->GetID(), gm->recruiter_id, gm->guild_status, gm->points, gm->rank, gm->member_flags, gm->join_date, getSafeEscapeString(gm->note.c_str()).c_str(), getSafeEscapeString(gm->officer_note.c_str()).c_str(), getSafeEscapeString(gm->recruiter_description.c_str()).c_str()); + if (gm && gm->recruiter_picture_data_size > 0 && gm->recruiter_picture_data) { + stringstream ss_hex; + stringstream ss_query; + ss_hex.flags(ios::hex); + for (int16 i = 0; i < gm->recruiter_picture_data_size; i++) + ss_hex << setfill('0') << setw(2) << (int)gm->recruiter_picture_data[i]; + ss_query << "UPDATE `guild_members` SET `recruiter_picture_data`='" << ss_hex.str() << "' WHERE `char_id`=" << gm->character_id; + query2.RunQuery2(ss_query.str(), Q_UPDATE); + } + } + guild->SetMemberSaveNeeded(false); + mMembers->releasereadlock(__FUNCTION__, __LINE__); +} + +void WorldDatabase::SaveGuildEvents(Guild* guild) { + if (guild) { + deque* guild_events = guild->GetGuildEvents(); + deque::iterator itr; + for (itr = guild_events->begin(); itr != guild_events->end(); itr++) { + GuildEvent* ge = *itr; + if (!ge->save_needed) + continue; + + LogWrite(GUILD__DEBUG, 5, "Guilds", "Saving Guild Events for guild '%s' (%u)...", guild->GetName(), guild->GetID()); + + Query query; + query.RunQuery2(Q_INSERT, "INSERT INTO `guild_events` (`guild_id`, `event_id`, `event_date`, `event_type`, `description`, `display`, `locked`, `archived`) " + "VALUES (%u, %llu, %u, %u, '%s', 1, %u, 0) " + "ON DUPLICATE KEY UPDATE `locked`=%i", + guild->GetID(), ge->event_id, ge->date, ge->type, getSafeEscapeString(ge->description.c_str()).c_str(), ge->locked, ge->locked); + ge->save_needed = false; + } + guild->SetEventsSaveNeeded(false); + } +} + +void WorldDatabase::SaveGuildRanks(Guild* guild) { + if (guild) { + MutexMap*>* permissions = guild->GetPermissions(); + MutexMap* ranks = guild->GetGuildRanks(); + MutexMap::iterator ranks_itr = ranks->begin(); + while (ranks_itr.Next()) { + int32 permission1 = 0; + int32 permission2 = 0; + for (int32 i = 0; i <= 44; i++) { + if (permissions->count(ranks_itr.first) > 0 && permissions->Get(ranks_itr.first)->count(i) > 0 && permissions->Get(ranks_itr.first)->Get(i)) { + if (i < 32) + permission1 += (int32)pow(2.0, (double)i); + else + permission2 += (int32)pow(2.0, (double)(i - 32)); + } + } + LogWrite(GUILD__DEBUG, 5, "Guilds", "Saving Guild Ranks for guild '%s' (%u)...", guild->GetName(), guild->GetID()); + Query query; + query.RunQuery2(Q_INSERT, "INSERT INTO `guild_ranks` (`guild_id`, `rank_id`, `rank_name`, `permission1`, `permission2`) " + "VALUES (%u, %u, '%s', %u, %u) " + "ON DUPLICATE KEY UPDATE `rank_name`='%s', `permission1`=%u, permission2=%u", + guild->GetID(), ranks_itr.first, getSafeEscapeString(ranks_itr.second.c_str()).c_str(), permission1, permission2, getSafeEscapeString(ranks_itr.second.c_str()).c_str(), permission1, permission2); + } + guild->SetRanksSaveNeeded(false); + } +} + +void WorldDatabase::SaveGuildEventFilters(Guild* guild) { + int32 i; + assert(guild); + + for (i = 0; i < 93; i++) { + LogWrite(GUILD__DEBUG, 5, "Guilds", "Saving Guild EventFilters for guild '%s' (%u)...", guild->GetName(), guild->GetID()); + Query query; + query.RunQuery2(Q_INSERT, "INSERT INTO `guild_event_filters` (`guild_id`, `event_id`, `retain`, `broadcast`) " + "VALUES (%u, %u, %u, %u) " + "ON DUPLICATE KEY UPDATE `retain`=%u, `broadcast`=%u", + guild->GetID(), i, guild->GetEventFilter(i, GUILD_EVENT_FILTER_CATEGORY_RETAIN_HISTORY), guild->GetEventFilter(i, GUILD_EVENT_FILTER_CATEGORY_BROADCAST), guild->GetEventFilter(i, GUILD_EVENT_FILTER_CATEGORY_RETAIN_HISTORY), guild->GetEventFilter(i, GUILD_EVENT_FILTER_CATEGORY_BROADCAST)); + } + + guild->SetEventFiltersSaveNeeded(false); +} + +void WorldDatabase::SaveGuildPointsHistory(Guild* guild) { + map *members; + map::iterator itr; + Mutex *mMembers; + deque *ph_list; + deque::iterator ph_itr; + PointHistory* ph; + + assert (guild); + + members = guild->GetGuildMembers(); + mMembers = guild->GetGuildMembersMutex(); + + mMembers->readlock(__FUNCTION__, __LINE__); + for (itr = members->begin(); itr != members->end(); itr++) { + ph_list = &itr->second->point_history; + + for (ph_itr = ph_list->begin(); ph_itr != ph_list->end(); ph_itr++) { + ph = *ph_itr; + if (!ph->saved_needed) + continue; + + LogWrite(GUILD__DEBUG, 5, "Guilds", "Saving Guild Point History for guild '%s' (%u)...", guild->GetName(), guild->GetID()); + Query query; + query.RunQuery2(Q_INSERT, "INSERT INTO `guild_points_history` (`guild_id`, `char_id`, `points_date`, `modified_by`, `comment`, `points`) " + "VALUES (%u, %u, %u, '%s', '%s', %f)", + guild->GetID(), itr->first, ph->date, getSafeEscapeString(ph->modified_by.c_str()).c_str(), getSafeEscapeString(ph->comment.c_str()).c_str(), ph->points); + ph->saved_needed = false; + } + } + guild->SetPointsHistorySaveNeeded(false); + mMembers->releasereadlock(__FUNCTION__, __LINE__); +} + +void WorldDatabase::SaveGuildRecruiting(Guild* guild) { + if (guild) { + LogWrite(GUILD__DEBUG, 3, "Guilds", "Saving Recruiting info for guild '%s' (%u)...", guild->GetName(), guild->GetID()); + Query query; + query.RunQuery2(Q_INSERT, "INSERT INTO `guild_recruiting` (`guild_id`, `short_desc`, `full_desc`, `min_level`, `play_style`, `looking_for`, `descriptive_tag1`, `descriptive_tag2`, `descriptive_tag3`, `descriptive_tag4`) VALUES (%u, '%s', '%s', %u, %u, %u, %u, %u, %u, %u) ON DUPLICATE KEY UPDATE `short_desc`='%s', `full_desc`='%s', `min_level`=%u, `play_style`=%u, `looking_for`=%u, `descriptive_tag1`=%u, `descriptive_tag2`=%u, `descriptive_tag3`=%u, `descriptive_tag4`=%u", guild->GetID(), getSafeEscapeString(guild->GetRecruitingShortDesc().c_str()).c_str(), getSafeEscapeString(guild->GetRecruitingFullDesc().c_str()).c_str(), guild->GetRecruitingMinLevel(), guild->GetRecruitingPlayStyle(), guild->GetRecruitingLookingForPacketValue(), guild->GetRecruitingDescTag(0), guild->GetRecruitingDescTag(1), guild->GetRecruitingDescTag(2), guild->GetRecruitingDescTag(3), getSafeEscapeString(guild->GetRecruitingShortDesc().c_str()).c_str(), getSafeEscapeString(guild->GetRecruitingFullDesc().c_str()).c_str(), guild->GetRecruitingMinLevel(), guild->GetRecruitingPlayStyle(), guild->GetRecruitingLookingForPacketValue(), guild->GetRecruitingDescTag(0), guild->GetRecruitingDescTag(1), guild->GetRecruitingDescTag(2), guild->GetRecruitingDescTag(3)); + guild->SetRecruitingSaveNeeded(false); + } +} + +void WorldDatabase::DeleteGuild(Guild* guild) { + if (guild) { + LogWrite(GUILD__DEBUG, 3, "Guilds", "Deleting Guild '%s' (%u)...", guild->GetName(), guild->GetID()); + Query query; + query.RunQuery2(Q_DELETE, "DELETE FROM `guilds` WHERE `id`=%u", guild->GetID()); + } +} + +void WorldDatabase::DeleteGuildMember(Guild* guild, int32 character_id) { + if (guild) { + LogWrite(GUILD__DEBUG, 3, "Guilds", "Deleting Character (%u) from guild '%s' (%u)...", character_id, guild->GetName(), guild->GetID()); + Query query; + query.RunQuery2(Q_DELETE, "DELETE FROM `guild_members` WHERE `guild_id`=%u AND `char_id`=%u", guild->GetID(), character_id); + } +} + +void WorldDatabase::DeleteGuildEvent(Guild* guild, int64 event_id) { + if (guild) { + LogWrite(GUILD__DEBUG, 3, "Guilds", "Deleting Event (%u) from guild '%s' (%u)...", event_id, guild->GetName(), guild->GetID()); + Query query; + query.RunQuery2(Q_DELETE, "DELETE FROM `guild_events` WHERE `guild_id`=%u AND `event_id`=%u", guild->GetID(), event_id); + } +} + +void WorldDatabase::DeleteGuildPointHistory(Guild* guild, int32 character_id, PointHistory* point_history) { + if (guild && point_history) { + LogWrite(GUILD__DEBUG, 3, "Guilds", "Deleting PointHistory for Character (%u) from guild '%s' (%u)...", character_id, guild->GetName(), guild->GetID()); + Query query; + query.RunQuery2(Q_DELETE, "DELETE FROM `guild_points_history` WHERE `guild_id`=%u AND `char_id`=%u AND `points_date`=%u", guild->GetID(), character_id, point_history->date); + } +} + +void WorldDatabase::ArchiveGuildEvent(Guild* guild, GuildEvent* guild_event) { + if (guild && guild_event) { + LogWrite(GUILD__DEBUG, 3, "Guilds", "Archiving Event (%u) for guild '%s' (%u)...", guild_event->event_id, guild->GetName(), guild->GetID()); + Query query; + query.RunQuery2(Q_UPDATE, "UPDATE `guild_events` SET `archived`=1 WHERE `guild_id`=%u AND `event_id`=%u", guild->GetID(), guild_event->event_id); + } +} + +void WorldDatabase::SaveHiddenGuildEvent(Guild* guild, GuildEvent* guild_event) { + if (guild && guild_event) { + LogWrite(GUILD__DEBUG, 3, "Guilds", "Saving Hidden Event (%u) for guild '%s' (%u)...", guild_event->event_id, guild->GetName(), guild->GetID()); + Query query; + query.RunQuery2(Q_INSERT, "INSERT INTO `guild_events` (`guild_id`, `event_id`, `event_date`, `event_type`, `description`, `display`, `locked`, `archived`) VALUES (%u, %u, %u, %u, '%s', 0, %u, 0)", guild->GetID(), guild_event->event_id, guild_event->type, guild_event->date, getSafeEscapeString(guild_event->description.c_str()).c_str(), guild_event->locked); + } +} + +int32 WorldDatabase::GetGuildIDByCharacterID(int32 char_id) { + if(char_id > 0) + { + LogWrite(GUILD__DEBUG, 3, "Guilds", "Look up guild ID for player ID: '%u'...", char_id); + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT name FROM guilds, guild_members WHERE guilds.id = guild_members.guild_id AND char_id = %u ", char_id); + while (result && (row = mysql_fetch_row(result))) { + if( row[0] ) + return atoul(row[0]); + } + } + return 0; +} + +void WorldDatabase::LoadGuildDefaultRanks(Guild* guild) { + if (guild) { + LogWrite(GUILD__DEBUG, 3, "Guilds", "Load/Set Default Ranks for guild '%s' (%u)...", guild->GetName(), guild->GetID()); + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT DISTINCT `rank_id`, `rank_name`, `permission1`, `permission2` FROM `guild_ranks_defaults`"); + while (result && (row = mysql_fetch_row(result))) { + int8 rank_id = atoi(row[0]); + int32 permission1 = atoul(row[2]); + int32 permission2 = atoul(row[3]); + + LogWrite(GUILD__DEBUG, 3, "Guilds", "\tSetting RankID %i, permission1: %u, permission2: %u", rank_id, permission1, permission2); + + guild->SetRankName(rank_id, row[1], false); + for (int32 i = 0; i <= 44; i++) { + int32 bitwise_val; + if (i < 32) { + bitwise_val = (int32)pow(2.0, (double)i); + guild->SetPermission(rank_id, i, permission1 & bitwise_val ? 1 : 0, false); + } + else { + bitwise_val = (int32)pow(2.0, (double)(i - 32)); + guild->SetPermission(rank_id, i, permission2 & bitwise_val ? 1 : 0, false); + } + } + } + } +} + +void WorldDatabase::LoadGuildDefaultEventFilters(Guild* guild) { + if (guild) { + LogWrite(GUILD__DEBUG, 3, "Guilds", "Load/Set Default Event Filters for guild '%s' (%u)...", guild->GetName(), guild->GetID()); + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT DISTINCT `event_id`, `retain`, `broadcast` FROM `guild_event_defaults`"); + while (result && (row = mysql_fetch_row(result))) { + + LogWrite(GUILD__DEBUG, 3, "Guilds", "\tSetting Event Filter %i, retain: %i, broadcast: %i", atoi(row[0]), atoi(row[1]), atoi(row[2])); + + guild->SetEventFilter(atoi(row[0]), GUILD_EVENT_FILTER_CATEGORY_RETAIN_HISTORY, atoi(row[1]), false); + guild->SetEventFilter(atoi(row[0]), GUILD_EVENT_FILTER_CATEGORY_BROADCAST, atoi(row[2]), false); + } + } +} + +bool WorldDatabase::AddNewPlayerToServerGuild(int32 account_id, int32 char_id) +{ + // Check if this servers rule allow auto-joining Server guild + int8 autojoin = rule_manager.GetGlobalRule(R_World, GuildAutoJoin)->GetInt8(); + if( autojoin ) + { + // if so, what is the guild ID of the default server guild? + int32 guild_id = rule_manager.GetGlobalRule(R_World, GuildAutoJoinID)->GetInt32(); + Guild* guild = 0; + guild = guild_list.GetGuild(guild_id); + if (!guild) + { + // guild was not valid, abort! + LogWrite(GUILD__ERROR, 1, "Guilds", "Guild ID %u not found! Cannot autojoin members!", guild_id); + return false; + } + else + { + // guild was found, so what default Rank to make the players? if not set, use 7 (recruit) + int8 rank_id = rule_manager.GetGlobalRule(R_World, GuildAutoJoinDefaultRankID)->GetInt8(); + if(!rank_id) + rank_id = 7; + + // assuming all is good, insert the new guild member here... + GuildMember *gm = new GuildMember(); + + gm->account_id = account_id; + gm->character_id = char_id; + char* name = GetCharacterName(gm->character_id); + strncpy(gm->name, name, sizeof(gm->name)); + gm->guild_status = 0; + gm->points = 0.0; + //gm->adventure_class = player->GetAdventureClass(); + //gm->adventure_level = player->GetLevel(); + //gm->tradeskill_class = player->GetTradeskillClass(); + //gm->tradeskill_level = player->GetTSLevel(); + gm->rank = rank_id; + gm->zone = string(""); + gm->join_date = Timer::GetUnixTimeStamp(); + gm->last_login_date = gm->join_date; + gm->recruiter_id = 0; + gm->member_flags = GUILD_MEMBER_FLAGS_NOTIFY_LOGINS; + gm->recruiting_show_adventure_class = 1; + gm->recruiter_picture_data_size = 0; + gm->recruiter_picture_data = 0; + + guild->AddGuildMember(gm); + + Query query; + query.RunQuery2(Q_INSERT, "INSERT INTO `guild_members` (`guild_id`, `char_id`, `join_date`, `rank_id`) VALUES (%u, %u, %u, %i)", + guild_id, char_id, gm->join_date, rank_id); + + LogWrite(GUILD__DEBUG, 3, "Guilds", "Auto-join player (%u) to server guild '%s' (%u) at rank %i...", char_id, guild->GetName(), guild_id, rank_id); + + // success! + return true; + } + } + // do not auto-join server guild + return false; +} + + diff --git a/source/WorldServer/HeroicOp/HeroicOp.cpp b/source/WorldServer/HeroicOp/HeroicOp.cpp new file mode 100644 index 0000000..9ed1902 --- /dev/null +++ b/source/WorldServer/HeroicOp/HeroicOp.cpp @@ -0,0 +1,315 @@ +/* + 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 . +*/ + +#include "HeroicOp.h" +#include "../../common/Log.h" +#include "../Rules/Rules.h" + +extern MasterHeroicOPList master_ho_list; +extern RuleManager rule_manager; + +HeroicOP::HeroicOP() { + m_complete = 0; + m_currentStage = 0; + m_wheel = 0; + m_target = 0; + m_startTime = 0; + m_totalTime = 0; + m_shifted = false; + for (int8 i = 0; i < 6; i++) + countered[i] = 0; +} + +HeroicOP::~HeroicOP() { + starters.clear(); +} + +void HeroicOP::SetWheel(HeroicOPWheel* val) { + if (!m_wheel) + m_wheel = val; + else + LogWrite(SPELL__ERROR, 0, "HO", "Attempted to set the wheel on a heroic op with a wheel already set"); +} + +void HeroicOP::SetTarget(int32 val) { + m_target = val; +} + +bool HeroicOP::UpdateHeroicOP(int16 icon) { + bool ret = false; + vector::iterator itr; + vector temp; + HeroicOPStarter* starter = 0; + + LogWrite(SPELL__DEBUG, 0, "HO", "Current Stage %u, wheel exists: %u, looking for icon %u", m_currentStage, m_wheel ? 1 : 0, icon); + // If no wheel is set we are dealing with a starter chain still. + if (!m_wheel) { + // Loop through the starter chains + for (itr = starters.begin(); itr != starters.end(); itr++) { + starter = *itr; + // See if the icon matches the ability at our current stage, if not add it to a list to be removed + if (starter->abilities[m_currentStage] == icon) + ret = true; + else + temp.push_back(*itr); + } + + if (ret) { + // ret = true so we had a match, first thing to do is remove those that didn't match + vector::iterator remove_itr; + for (remove_itr = temp.begin(); remove_itr != temp.end(); remove_itr++) + { + std::vector::iterator it = std::find(starters.begin(), starters.end(), *remove_itr); + starters.erase(it); + } + + // now advance the current stage + m_currentStage++; + + // Temp pointer to hold the completed chain, if any + HeroicOPStarter* complete_starter = 0; + + // now loop through those that are left and check the next stage abilities for a 0xFFFF + for (itr = starters.begin(); itr != starters.end(); itr++) { + starter = *itr; + // Found one that is 0xFFFF, means the starter chain is done, get a wheel and reset the stage to 0 + if ((starter->abilities[m_currentStage] == 0xFFFF)) { + LogWrite(SPELL__DEBUG, 0, "HO", "Current Stage %u, starter reset (new stage 0)", m_currentStage); + // reset the stage + ResetStage(); + // geth the wheel + m_wheel = master_ho_list.GetWheel(starter); + // store the starter chain that is completed + complete_starter = starter; + // set the start time to now + SetStartTime(Timer::GetCurrentTime2()); + // set the total time to complete the real to was the admin set in rules (default 10.0f) + SetTotalTime(rule_manager.GetGlobalRule(R_Zone, HOTime)->GetFloat()); + // We set a wheel so we are done, kill the loop + break; + } + } + + // Check to see if the completed start chain pointer was set + if (complete_starter) { + LogWrite(SPELL__DEBUG, 0, "HO", "Current Stage %u, complete_starter set", m_currentStage); + // clear the starter list + starters.clear(); + // add the completed starter back in, we do this in case we need this starter again we can just do starters.at(0), for example shifting the wheel + starters.push_back(complete_starter); + } + } + } + else { + LogWrite(SPELL__DEBUG, 0, "HO", "Current Stage %u, wheel order: %u", m_currentStage, m_wheel->order); + // Wheel was set so we need to check the order it needs to be completed in. + if (m_wheel->order == 0) { + // No order + + // Flag used to see if we can shift the wheel + bool can_shift = true; + // Check the icons and flag the ability as countered if there is a match + for (int8 i = 0; i < 6; i++) { + if (countered[i] == 1) { + // progress made on this wheel so we can't shift it + can_shift = false; + } + if (m_wheel->abilities[i] == icon) { + countered[i] = 1; + ret = true; + } + } + + if (ret) { + // As we found a match lets loop through to see if we completed the ho + bool finished = true; + for (int8 i = 0; i < 6; i++) { + // if the ability is not 0xFFFF and countered is 0 then we still have more to go + if (m_wheel->abilities[i] != 0xFFFF && countered[i] == 0) { + finished = false; + break; + } + } + + // is we finished the ho set the complete flag + if (finished) + SetComplete(2); + } + + if (!ret && can_shift && m_wheel->shift_icon == icon) { + // can shift, icon matched shift icon, and no progress made + ret = ShiftWheel(); + } + } + else { + // In order + + // Check to see if we can shift the wheel + if (countered[0] == 0 && icon == m_wheel->shift_icon) { + // Can only shift the icon if nothing has completed yet (countered[0] = 0) + ret = ShiftWheel(); + } + // Check the current stage and compare it to the icon + else if (m_wheel->abilities[m_currentStage] == icon) { + // Is a match so flag this stage as done + countered[m_currentStage] = 1; + // Advance the stage + m_currentStage++; + // Set the return value to true + ret = true; + // Check the next stage, if it is over 6 or equal to 0xFFFF flag the HO as complete + if (m_currentStage > 6 || m_wheel->abilities[m_currentStage] == 0xFFFF) + SetComplete(2); + } + } + } + + return ret; +} + +void HeroicOP::AddStarterChain(HeroicOPStarter* starter) { + starters.push_back(starter); +} + +bool HeroicOP::ShiftWheel() { + // Can only shift once so if we already have return out + if (HasShifted()) + return false; + + // Clear the wheel + m_wheel = 0; + + // Get a new Wheel + SetWheel(master_ho_list.GetWheel(starters.at(0))); + + // Set the ho as shifted + m_shifted = true; + + return true; +} + +MasterHeroicOPList::MasterHeroicOPList() { +} + +MasterHeroicOPList::~MasterHeroicOPList() { + map > >::iterator itr; + map >::iterator itr2; + vector::iterator itr3; + vector temp; + vector::iterator itr4; + + // loop through the m_hoList to delete the pointers + for (itr = m_hoList.begin(); itr != m_hoList.end(); itr++) { + // loop through the second map of the m_hoList + for (itr2 = itr->second.begin(); itr2 != itr->second.end(); itr2++) { + // loop through the vector of the second map and delete the pointers + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++) + safe_delete(*itr3); + + // clear the vector + itr2->second.clear(); + // put the starter in a temp list to delete later + temp.push_back(itr2->first); + } + // clear the seond map + itr->second.clear(); + } + // clear the m_hoList + m_hoList.clear(); + + // Delete the starters + for (itr4 = temp.begin(); itr4 != temp.end(); itr4++) + safe_delete(*itr4); + + // clear the temp vector + temp.clear(); +} + +void MasterHeroicOPList::AddStarter(int8 start_class, HeroicOPStarter* starter) { + if (m_hoList.count(start_class) == 0 || m_hoList[start_class].count(starter) == 0) { + m_hoList[start_class][starter]; // This adds the starter with out giving it a vector of wheels yet. + } +} + +void MasterHeroicOPList::AddWheel(int32 starter_id, HeroicOPWheel* wheel) { + map > >::iterator itr; + map >::iterator itr2; + bool found = false; + + // Loop through the list and add the wheel to the correct starter + for (itr = m_hoList.begin(); itr != m_hoList.end(); itr++) { + for (itr2 = itr->second.begin(); itr2 != itr->second.end(); itr2++) { + if (itr2->first->id == starter_id) { + // Found a match, add the wheel, set the flag to break the first loop, and break this loop + itr2->second.push_back(wheel); + found = true; + break; + } + } + // If we found a match break the first loop + if (found) + break; + } + + // No match found give an error. + if (!found) + LogWrite(SPELL__DEBUG, 0, "HO", "Attempted to add a wheel to a starter (%u) that doesn't exsist", starter_id); +} + +HeroicOP* MasterHeroicOPList::GetHeroicOP(int8 class_id) { + if (m_hoList.count(class_id) == 0) { + LogWrite(SPELL__ERROR, 0, "HO", "No HO's found for the given class (%i)", class_id); + return 0; + } + + map >::iterator itr; + HeroicOP* ret = new HeroicOP(); + + // Loop through the starters for this class and add them to the HO + for (itr = m_hoList[class_id].begin(); itr != m_hoList[class_id].end(); itr++) + ret->AddStarterChain(itr->first); + + return ret; +} + +HeroicOPWheel* MasterHeroicOPList::GetWheel(HeroicOPStarter* starter) { + if (!starter) + return 0; + + if (m_hoList.count(starter->start_class) == 0) { + LogWrite(SPELL__ERROR, 0, "HO", "Start class (%u) not found", starter->start_class); + return 0; + } + + if (m_hoList[starter->start_class].count(starter) == 0) { + LogWrite(SPELL__ERROR, 0, "HO", "Wheel not found for the provided starter (%u)", starter->id); + return 0; + } + + int index = MakeRandomInt(0, m_hoList[starter->start_class][starter].size() - 1); + + if(index < m_hoList[starter->start_class][starter].size()) + return m_hoList[starter->start_class][starter].at(index); + else + LogWrite(SPELL__ERROR, 0, "HO", "Wheel index %u for heroic_ops starter ID %u NOT Found!! Wheel starter_class %u, wheel size: %u. Wheels that match starter_link_id -> Starter 'id' missing.", index, starter->id, starter->start_class, m_hoList[starter->start_class][starter].size()); + + return nullptr; +} + diff --git a/source/WorldServer/HeroicOp/HeroicOp.h b/source/WorldServer/HeroicOp/HeroicOp.h new file mode 100644 index 0000000..a5c9fcf --- /dev/null +++ b/source/WorldServer/HeroicOp/HeroicOp.h @@ -0,0 +1,156 @@ +/* + 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 . +*/ + +#ifndef __HEROICOP_H__ +#define __HEROICOP_H__ + +#include +#include + +#include "../../common/types.h" + +using namespace std; + +struct HeroicOPStarter { + int32 id; + int8 start_class; + int16 starter_icon; + int16 abilities[6]; +}; + +struct HeroicOPWheel { + int8 order; + int16 shift_icon; + float chance; + int16 abilities[6]; + int32 spell_id; +}; + +class HeroicOP { +public: + HeroicOP(); + ~HeroicOP(); + + /// Sets the complete flag for this Heroic OP + /// The value to set the complete flag to, 1 = failed 2 = finished + void SetComplete(int8 val) { m_complete = val; } + + /// Sets the current stage of the starter chain or the wheel chain is at + /// The stage to set this Heroic OP to + void SetStage(int8 val) { m_currentStage = val; } + + /// Sets the wheel for this Heroic OP + /// The wheel we are setting the Heroic OP to + void SetWheel(HeroicOPWheel* val); + + /// Sets the start time for the wheel + /// Value to set the start time to + void SetStartTime(int32 val) { m_startTime = val; } + + /// Sets the total time to complete the wheel + /// Value to set the total time to + void SetTotalTime(float val) { m_totalTime = val; } + + /// Sets the target of this HO + /// The ID of the spawn + void SetTarget(int32 val); + + /// Gets the complete flag for this Heroic OP + /// 0 = not complete, 1 = complete, 2+= failed + int8 GetComplete() { return m_complete; } + + /// Gets the wheel for this heroic op + HeroicOPWheel* GetWheel() { return m_wheel; } + + /// Gets a pointer to the list of starter chains + vector* GetStarterChains() { return &starters; } + + /// Gets the current stage the HO is on + int8 GetStage() { return m_currentStage; } + + /// Gets the start time for the wheel + int32 GetStartTime() { return m_startTime; } + + /// Gets the total time players have to complete the wheel + float GetTotalTime() { return m_totalTime; } + + /// Gets the ID of this HO's target + int32 GetTarget() { return m_target; } + + /// + bool HasShifted() { return m_shifted; } + + /// Checks to see if the given icon will advance the Heroic OP + /// The icon that is trying to advance the Heroic OP + /// True if the icon advanced the HO + bool UpdateHeroicOP(int16 icon); + + /// Reset the stage to 0 + void ResetStage() { m_currentStage = 0; } + + /// Adds a starter chain to the Heroic OP + /// The starter chain to add + void AddStarterChain(HeroicOPStarter* starter); + + /// Attempts to shift the wheel + bool ShiftWheel(); + + int8 countered[6]; + +private: + int8 m_complete; + int8 m_currentStage; + int32 m_startTime; + float m_totalTime; + int32 m_target; + bool m_shifted; + HeroicOPWheel* m_wheel; + vector starters; +}; + +class MasterHeroicOPList { +public: + MasterHeroicOPList(); + ~MasterHeroicOPList(); + + /// Adds the starter chain to the list + /// Class id for the starter chain + /// Starter chain to add + void AddStarter(int8 start_class, HeroicOPStarter* starter); + + /// Add the wheel chain to the list + /// Id of the starter this wheel belongs to + /// Wheel to add + void AddWheel(int32 starter_id, HeroicOPWheel* wheel); + + /// Creates a new HO + /// Class ID starting the HO + HeroicOP* GetHeroicOP(int8 class_id); + + /// Gets a random wheel from the given starter + /// The starter to determine what wheels to choose from + HeroicOPWheel* GetWheel(HeroicOPStarter* starter); + +private: + // map > > + map > > m_hoList; +}; + +#endif \ No newline at end of file diff --git a/source/WorldServer/HeroicOp/HeroicOpDB.cpp b/source/WorldServer/HeroicOp/HeroicOpDB.cpp new file mode 100644 index 0000000..3701080 --- /dev/null +++ b/source/WorldServer/HeroicOp/HeroicOpDB.cpp @@ -0,0 +1,79 @@ +/* + 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 . +*/ + +#include "../WorldDatabase.h" +#include "../../common/Log.h" +#include "HeroicOp.h" + +extern MasterHeroicOPList master_ho_list; + +void WorldDatabase::LoadHOStarters() { + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `id`, `starter_class`, `starter_icon`, `ability1`, `ability2`, `ability3`, `ability4`, `ability5`, `ability6` FROM `heroic_ops` WHERE `ho_type`='Starter'"); + + if (result && mysql_num_rows(result) > 0) { + int32 count = 0; + while ((row = mysql_fetch_row(result))) { + HeroicOPStarter* starter = new HeroicOPStarter; + starter->id = atoul(row[0]); + starter->start_class = atoi(row[1]); + starter->starter_icon = atoi(row[2]); + starter->abilities[0] = atoi(row[3]); + starter->abilities[1] = atoi(row[4]); + starter->abilities[2] = atoi(row[5]); + starter->abilities[3] = atoi(row[6]); + starter->abilities[4] = atoi(row[7]); + starter->abilities[5] = atoi(row[8]); + master_ho_list.AddStarter(starter->start_class, starter); + count++; + } + + LogWrite(WORLD__INFO, 0, "World", "- Loaded %u starter chains", count); + } +} + +void WorldDatabase::LoadHOWheel() { + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `starter_link_id`, `chain_order`, `shift_icon`, `spell_id`, `chance`, `ability1`, `ability2`, `ability3`, `ability4`, `ability5`, `ability6` FROM `heroic_ops` WHERE `ho_type`='Wheel'"); + + if (result && mysql_num_rows(result) > 0) { + int32 count = 0; + while ((row = mysql_fetch_row(result))) { + HeroicOPWheel* wheel = new HeroicOPWheel; + wheel->order = atoi(row[1]); + wheel->shift_icon = atoi(row[2]); + wheel->spell_id = atoul(row[3]); + wheel->chance = atof(row[4]); + wheel->abilities[0] = atoi(row[5]); + wheel->abilities[1] = atoi(row[6]); + wheel->abilities[2] = atoi(row[7]); + wheel->abilities[3] = atoi(row[8]); + wheel->abilities[4] = atoi(row[9]); + wheel->abilities[5] = atoi(row[10]); + + master_ho_list.AddWheel(atoul(row[0]), wheel); + count++; + } + + LogWrite(WORLD__INFO, 0, "World", "- Loaded %u HO wheels", count); + } +} diff --git a/source/WorldServer/HeroicOp/HeroicOpPackets.cpp b/source/WorldServer/HeroicOp/HeroicOpPackets.cpp new file mode 100644 index 0000000..3f144d0 --- /dev/null +++ b/source/WorldServer/HeroicOp/HeroicOpPackets.cpp @@ -0,0 +1,158 @@ +/* + 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 . +*/ + +#include "../ClientPacketFunctions.h" +#include "../../common/Log.h" +#include "HeroicOp.h" +#include "../Spells.h" + +extern ConfigReader configReader; +extern MasterSpellList master_spell_list; + +void ClientPacketFunctions::SendHeroicOPUpdate(Client* client, HeroicOP* ho) { + if (!client) { + LogWrite(PACKET__ERROR, 0, "Packets", "SendHeroicOPUpdate() called without a valid client"); + return; + } + + if (!ho) { + LogWrite(PACKET__ERROR, 0, "Packets", "SendHeroicOPUpdate() called without a valid HO"); + return; + } + + PacketStruct* packet = configReader.getStruct("WS_HeroicOpportunity", client->GetVersion()); + Spell* spell = 0; + if (packet) { + packet->setDataByName("id", client->GetPlayer()->GetIDWithPlayerSpawn(client->GetPlayer())); + if (ho->GetWheel()) { + spell = master_spell_list.GetSpell(ho->GetWheel()->spell_id, 1); + if (!spell) { + LogWrite(SPELL__ERROR, 0, "HO", "Unable to get the spell (%u)", ho->GetWheel()->spell_id); + return; + } + + packet->setDataByName("name", spell->GetName()); + packet->setDataByName("description", spell->GetDescription()); + packet->setDataByName("order", ho->GetWheel()->order); + packet->setDataByName("time_total", ho->GetTotalTime()); + packet->setDataByName("time_left", max(0.0f, (float)(((ho->GetStartTime() + (ho->GetTotalTime() * 1000)) - Timer::GetCurrentTime2()) / 1000))); + // This is not displayed in the wheel so set it to 0xFFFF + packet->setDataByName("starter_icon", 0xFFFF); + + if (ho->HasShifted()) + packet->setDataByName("shift_icon", 0xFFFF); + else + packet->setDataByName("shift_icon", ho->GetWheel()->shift_icon); + + // If completed set special values + if (ho->GetComplete() > 0) { + packet->setDataByName("wheel_type", 2); + packet->setDataByName("unknown", ho->GetComplete()); + } + + char temp[20]; + char ability[20]; + + // Set the icons for the whee; + for (int8 i = 1; i < 7; i++) { + strcpy(ability, "icon"); + itoa(i, temp, 10); + strcat(ability, temp); + packet->setDataByName(ability, ho->GetWheel()->abilities[i-1]); + } + + // Flag the icons that are completed + for (int8 i = 1; i < 7; i++) { + strcpy(ability, "countered"); + itoa(i, temp, 10); + strcat(ability, temp); + packet->setDataByName(ability, ho->countered[i-1]); + } + + } + else { + if (ho->GetComplete() > 0) { + // This will make the ui element vanish + packet->setDataByName("wheel_type", 5); + packet->setDataByName("unknown", 8); + } + else { + packet->setDataByName("wheel_type", 4); + } + + packet->setDataByName("icon1", 0xFFFF); + packet->setDataByName("icon2", 0xFFFF); + packet->setDataByName("icon3", 0xFFFF); + packet->setDataByName("icon4", 0xFFFF); + packet->setDataByName("icon5", 0xFFFF); + packet->setDataByName("icon6", 0xFFFF); + packet->setDataByName("shift_icon", 0xFFFF); + + int8 index = 1; + char temp[20]; + char ability[20]; + vector::iterator itr; + for (itr = ho->GetStarterChains()->begin(); itr != ho->GetStarterChains()->end(); itr++, index++) { + if (index > 6 ) + break; + + strcpy(ability, "icon"); + itoa(index, temp, 10); + strcat(ability, temp); + + packet->setDataByName(ability, (*itr)->abilities[ho->GetStage()]); + + // Only set this once + if (index == 1) + packet->setDataByName("starter_icon", (*itr)->starter_icon); + } + } + client->QueuePacket(packet->serialize()); + } + safe_delete(packet); +} + + +/* + + + + + + + + + + + + + + + + + + + + + + + + +*/ \ No newline at end of file diff --git a/source/WorldServer/Housing/HousingDB.cpp b/source/WorldServer/Housing/HousingDB.cpp new file mode 100644 index 0000000..c90c9b5 --- /dev/null +++ b/source/WorldServer/Housing/HousingDB.cpp @@ -0,0 +1,131 @@ +#include "../WorldDatabase.h" +#include "../World.h" + +extern World world; + +void WorldDatabase::LoadHouseZones() { + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT * FROM `houses`"); + + if (result && mysql_num_rows(result) > 0) { + while ((row = mysql_fetch_row(result))) { + world.AddHouseZone(atoul(row[0]), row[1], atoi64(row[2]), atoul(row[3]), atoi64(row[4]), atoul(row[5]), atoi(row[6]), atoi(row[7]), atoi(row[8]), atoul(row[9]), atoul(row[10]), atof(row[11]), atof(row[12]), atof(row[13]), atof(row[14])); + } + } +} + +int64 WorldDatabase::AddPlayerHouse(int32 char_id, int32 house_id, int32 instance_id, int32 upkeep_due) { + Query query; + string insert = string("INSERT INTO character_houses (char_id, house_id, instance_id, upkeep_due) VALUES (%u, %u, %u, %u) "); + query.RunQuery2(Q_INSERT, insert.c_str(), char_id, house_id, instance_id, upkeep_due); + + int64 unique_id = query.GetLastInsertedID(); + return unique_id; +} + +void WorldDatabase::SetHouseUpkeepDue(int32 char_id, int32 house_id, int32 instance_id, int32 upkeep_due) { + Query query; + string update = string("UPDATE character_houses set upkeep_due=%u where char_id = %u and house_id = %u and instance_id = %u"); + query.RunQuery2(Q_UPDATE, update.c_str(), upkeep_due, char_id, house_id, instance_id); +} + + +void WorldDatabase::UpdateHouseEscrow(int32 house_id, int32 instance_id, int64 amount_coins, int32 amount_status) { + Query query; + string update = string("UPDATE character_houses set escrow_coins = %llu, escrow_status = %u where house_id = %u and instance_id = %u"); + query.RunQuery2(Q_UPDATE, update.c_str(), amount_coins, amount_status, house_id, instance_id); +} + + +void WorldDatabase::RemovePlayerHouse(int32 char_id, int32 house_id) { +} + +void WorldDatabase::LoadPlayerHouses() { + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT h.id, h.char_id, h.house_id, h.instance_id, h.upkeep_due, h.escrow_coins, h.escrow_status, c.name FROM character_houses h, characters c WHERE h.char_id = c.id"); + + if (result && mysql_num_rows(result) > 0) { + while ((row = mysql_fetch_row(result))) { + world.AddPlayerHouse(atoul(row[1]), atoul(row[2]), atoi64(row[0]), atoul(row[3]), atoul(row[4]), atoi64(row[5]), atoul(row[6]), row[7]); + } + } +} + +void WorldDatabase::LoadDeposits(PlayerHouse* ph) +{ + if (!ph) + return; + ph->deposits.clear(); + ph->depositsMap.clear(); + + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "select timestamp, amount, last_amount, status, last_status, name from character_house_deposits where house_id = %u and instance_id = %u order by timestamp asc limit 255", ph->house_id, ph->instance_id); + + if (result && mysql_num_rows(result) > 0) { + while ((row = mysql_fetch_row(result))) { + Deposit d; + d.timestamp = atoul(row[0]); + + int64 outVal = strtoull(row[1], NULL, 0); + d.amount = outVal; + + outVal = strtoull(row[2], NULL, 0); + d.last_amount = outVal; + + d.status = atoul(row[3]); + d.last_status = atoul(row[4]); + + d.name = string(row[5]); + + ph->deposits.push_back(d); + ph->depositsMap.insert(make_pair(d.name, d)); + } + } +} + +void WorldDatabase::LoadHistory(PlayerHouse* ph) +{ + if (!ph) + return; + ph->history.clear(); + + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "select timestamp, amount, status, reason, name, pos_flag from character_house_history where house_id = %u and instance_id = %u order by timestamp asc limit 255", ph->house_id, ph->instance_id); + + if (result && mysql_num_rows(result) > 0) { + while ((row = mysql_fetch_row(result))) { + HouseHistory h; + h.timestamp = atoul(row[0]); + + int64 outVal = strtoull(row[1], NULL, 0); + h.amount = outVal; + + h.status = atoul(row[2]); + + h.reason = string(row[3]); + h.name = string(row[4]); + + h.pos_flag = atoul(row[5]); + + ph->history.push_back(h); + } + } +} + + +void WorldDatabase::AddHistory(PlayerHouse* house, char* name, char* reason, int32 timestamp, int64 amount, int32 status, int8 pos_flag) +{ + if (!house) + return; + + HouseHistory h(Timer::GetUnixTimeStamp(), amount, string(name), string(reason), status, pos_flag); + house->history.push_back(h); + + Query query; + string insert = string("INSERT INTO character_house_history (timestamp, house_id, instance_id, name, amount, status, reason, pos_flag) VALUES (%u, %u, %u, '%s', %llu, %u, '%s', %u) "); + query.RunQuery2(Q_INSERT, insert.c_str(), timestamp, house->house_id, house->instance_id, name, amount, status, reason, pos_flag); +} \ No newline at end of file diff --git a/source/WorldServer/Housing/HousingPackets.cpp b/source/WorldServer/Housing/HousingPackets.cpp new file mode 100644 index 0000000..9b30783 --- /dev/null +++ b/source/WorldServer/Housing/HousingPackets.cpp @@ -0,0 +1,454 @@ +#include "../ClientPacketFunctions.h" +#include "../World.h" +#include "../client.h" +#include "../WorldDatabase.h" +#include "../Rules/Rules.h" + +extern ConfigReader configReader; +extern World world; +extern WorldDatabase database; +extern RuleManager rule_manager; + +void ClientPacketFunctions::SendHousePurchase(Client* client, HouseZone* hz, int32 spawnID) { + PacketStruct* packet = configReader.getStruct("WS_PlayerHousePurchase", client->GetVersion()); + if (packet) { + int8 disable_alignment_req = rule_manager.GetGlobalRule(R_Player, DisableHouseAlignmentRequirement)->GetInt8(); + packet->setDataByName("house_name", hz->name.c_str()); + packet->setDataByName("house_id", hz->id); + packet->setDataByName("spawn_id", spawnID); + packet->setDataByName("purchase_coins", hz->cost_coin); + packet->setDataByName("purchase_status", hz->cost_status); + packet->setDataByName("upkeep_coins", hz->upkeep_coin); + packet->setDataByName("upkeep_status", hz->upkeep_status); + packet->setDataByName("vendor_vault_slots", hz->vault_slots); + string req; + if (hz->alignment > 0 && !disable_alignment_req) { + req = "You must be of "; + if (hz->alignment == 1) + req.append("Good"); + else + req.append("Evil"); + req.append(" alignment"); + } + if (hz->guild_level > 0) { + if (req.length() > 0) { + req.append(", and a guild level of "); + char temp[5]; + sprintf(temp, "%i", hz->guild_level); + req.append(temp); + //req.append(std::to_string(static_cast(hz->guild_level))); + } + else { + req.append("Requires a guild of level "); + char temp[5]; + sprintf(temp, "%i", hz->guild_level); + req.append(temp); + //req.append(std::to_string(static_cast(hz->guild_level))) + req.append(" or above"); + } + } + if (req.length() > 0) { + req.append(" in order to purchase a home within the "); + req.append(hz->name); + req.append("."); + } + + packet->setDataByName("additional_reqs", req.c_str()); + + bool enable_buy = true; + if (hz->alignment > 0 && client->GetPlayer()->GetAlignment() != hz->alignment && !disable_alignment_req) + enable_buy = false; + if (hz->guild_level > 0 && (!client->GetPlayer()->GetGuild() || (client->GetPlayer()->GetGuild() && client->GetPlayer()->GetGuild()->GetLevel() < hz->guild_level))) + enable_buy = false; + + packet->setDataByName("enable_buy", enable_buy ? 1 : 0); + //packet->PrintPacket(); + client->QueuePacket(packet->serialize()); + } + + safe_delete(packet); +} + +void ClientPacketFunctions::SendHousingList(Client* client) { + if(client->GetVersion() <= 561) { + return; // not supported + } + + std::vector houses = world.GetAllPlayerHouses(client->GetCharacterID()); + // this packet must be sent first otherwise it blocks out the enter house option after paying upkeep + PacketStruct* packet = configReader.getStruct("WS_CharacterHousingList", client->GetVersion()); + if(!packet) { + return; + } + packet->setArrayLengthByName("num_houses", houses.size()); + for (int i = 0; i < houses.size(); i++) + { + PlayerHouse* ph = (PlayerHouse*)houses[i]; + HouseZone* hz = world.GetHouseZone(ph->house_id); + string name; + name = ph->player_name; + name.append("'s "); + name.append(hz->name); + packet->setArrayDataByName("house_id", ph->unique_id, i); + string zone_name = database.GetZoneName(hz->zone_id); + if(zone_name.length() > 0) + packet->setArrayDataByName("zone", zone_name.c_str(), i); + packet->setArrayDataByName("house_city", hz->name.c_str(), i); + packet->setArrayDataByName("house_address", "", i); // need this pulled from live + packet->setArrayDataByName("house_description", name.c_str(), i); + packet->setArrayDataByName("index", i, i); // they send 2, 4, 6, 8 as the index ID's on the client.. + + // this seems to be some kind of timestamp, if we keep updating then in conjunction with upkeep_due + // in SendBaseHouseWindow/WS_PlayerHouseBaseScreen being a >0 number we can access 'enter house' + + int32 upkeep_due = 0; + + if (((sint64)ph->upkeep_due - (sint64)Timer::GetUnixTimeStamp()) > 0) + upkeep_due = ph->upkeep_due - Timer::GetUnixTimeStamp(); + + if ( client->GetVersion() >= 63119 ) + packet->setArrayDataByName("unknown2a", 0xFFFFFFFF, i); + else + packet->setArrayDataByName("unknown2", 0xFFFFFFFF, i); + } + client->QueuePacket(packet->serialize()); + safe_delete(packet); +} + +void ClientPacketFunctions::SendBaseHouseWindow(Client* client, HouseZone* hz, PlayerHouse* ph, int32 spawnID) { + // if we don't send this then the enter house option won't be available if upkeep is paid + if (!hz || !ph) + { + client->SimpleMessage(CHANNEL_COLOR_RED, "HouseZone or PlayerHouse missing and cannot send SendBaseHouseWindow"); + return; + } + + string name; + name = ph->player_name; + name.append("'s "); + name.append(hz->name); + + if (spawnID) + SendHousingList(client); + + int32 upkeep_due = 0; + + if (((sint64)ph->upkeep_due - (sint64)Timer::GetUnixTimeStamp()) > 0) + upkeep_due = ph->upkeep_due - Timer::GetUnixTimeStamp(); + + // need this to enable the "enter house" button + PacketStruct* packet = nullptr; + + + if(client->GetVersion() > 561 && client->GetCurrentZone()->GetInstanceType() != PERSONAL_HOUSE_INSTANCE + && client->GetCurrentZone()->GetInstanceType() != GUILD_HOUSE_INSTANCE) { + packet = configReader.getStruct("WS_UpdateHouseAccessDataMsg", client->GetVersion()); + + if(!packet) { + return; // we need this for these clients or enter house will not work properly + } + if (packet) { + packet->setDataByName("house_id", 0xFFFFFFFFFFFFFFFF); + packet->setDataByName("success", (upkeep_due > 0) ? 0xFFFFFFFF : 0); + packet->setDataByName("unknown2", 0xFFFFFFFF); + packet->setDataByName("unknown3", 0xFFFFFFFF); + } + client->QueuePacket(packet->serialize()); + } + safe_delete(packet); + + packet = configReader.getStruct("WS_PlayerHouseBaseScreen", client->GetVersion()); + if (packet) { + packet->setDataByName("house_id", ph->unique_id); + packet->setDataByName("spawn_id", spawnID); + packet->setDataByName("character_id", client->GetPlayer()->GetCharacterID()); + packet->setDataByName("house_name", name.c_str()); + packet->setDataByName("zone_name", hz->name.c_str()); + packet->setDataByName("upkeep_cost_coins", hz->upkeep_coin); + packet->setDataByName("upkeep_cost_status", hz->upkeep_status); + + packet->setDataByName("upkeep_due", upkeep_due); + + packet->setDataByName("escrow_balance_coins", ph->escrow_coins); + packet->setDataByName("escrow_balance_status", ph->escrow_status); + // temp - set priv level to owner for now + packet->setDataByName("privlage_level", 4); + // temp - set house type to personal house for now + packet->setDataByName("house_type", 0); + + if(client->GetCurrentZone()->GetInstanceType() == PERSONAL_HOUSE_INSTANCE + || client->GetCurrentZone()->GetInstanceType() == GUILD_HOUSE_INSTANCE) { + packet->setDataByName("inside_house", 1); + packet->setDataByName("public_access_level", 1); + } + packet->setDataByName("num_access", 0); + packet->setDataByName("num_history", 0); + + // allows deposits/history to be seen -- at this point seems plausible supposed to be 'inside_house'..? + packet->setDataByName("unknown3", (ph->deposits.size() || ph->history.size()) ? 1 : 0); + + packet->setArrayLengthByName("num_deposit", ph->deposits.size()); + list::iterator itr; + int d = 0; + for (itr = ph->deposits.begin(); itr != ph->deposits.end(); itr++) + { + packet->setArrayDataByName("deposit_name", itr->name.c_str(), d); + packet->setArrayDataByName("deposit_total_coin", itr->amount, d); + packet->setArrayDataByName("deposit_time_stamp", itr->timestamp, d); + packet->setArrayDataByName("deposit_last_coin", itr->last_amount, d); + packet->setArrayDataByName("deposit_total_status", itr->status, d); + packet->setArrayDataByName("deposit_last_status", itr->last_status, d); + d++; + } + + + packet->setArrayLengthByName("num_history", ph->history.size()); + list::iterator hitr; + d = 0; + for (hitr = ph->history.begin(); hitr != ph->history.end(); hitr++) + { + packet->setArrayDataByName("history_name", hitr->name.c_str(), d); + packet->setArrayDataByName("history_coins", hitr->amount, d); + packet->setArrayDataByName("history_status", hitr->status, d); + packet->setArrayDataByName("history_time_stamp", hitr->timestamp, d); + packet->setArrayDataByName("history_reason", hitr->reason.c_str(), d); + packet->setArrayDataByName("history_add_flag", hitr->pos_flag, d); + d++; + } + + EQ2Packet* pack = packet->serialize(); + //DumpPacket(pack); + client->QueuePacket(pack); + } + safe_delete(packet); +} + +void ClientPacketFunctions::SendHouseVisitWindow(Client* client, vector houses) { + PacketStruct* packet = configReader.getStruct("WS_DisplayVisitScreen", client->GetVersion()); + if (packet) { + vector::iterator itr; + packet->setArrayLengthByName("num_houses", houses.size()); + int16 i = 0; + for (itr = houses.begin(); itr != houses.end(); itr++) { + PlayerHouse* ph = *itr; + if (ph) { + HouseZone* hz = world.GetHouseZone(ph->house_id); + if (hz) { + packet->setArrayDataByName("house_id", ph->unique_id, i); + packet->setArrayDataByName("house_owner", ph->player_name.c_str(), i); + packet->setArrayDataByName("house_location", hz->name.c_str(), i); + packet->setArrayDataByName("house_zone", client->GetCurrentZone()->GetZoneName(), i); + + if ( string(client->GetPlayer()->GetName()).compare(ph->player_name) == 0 ) + packet->setArrayDataByName("access_level", 4, i); + else + packet->setArrayDataByName("access_level", 1, i); + packet->setArrayDataByName("visit_flag", 0, i); // 0 = allowed to visit, 1 = owner hasn't paid upkeep + i++; + } + } + } + client->QueuePacket(packet->serialize()); + } + safe_delete(packet); +} + +/* + + + + + + + + + + + + + + +*/ + + +void ClientPacketFunctions::SendLocalizedTextMessage(Client* client) +{ + /*** + -- OP_ReloadLocalizedTxtMsg -- +5/26/2020 19:08:41 +69.174.200.100 -> 192.168.1.1 +0000: 01 FF 63 01 62 00 00 00 1C 00 49 72 6F 6E 74 6F ..c.b.....Ironto +0010: 65 73 20 45 61 73 74 20 4C 61 72 67 65 20 49 6E es East Large In +0020: 6E 20 52 6F 6F 6D 07 01 00 00 00 1C 00 49 72 6F n Room.......Iro +0030: 6E 74 6F 65 73 20 45 61 73 74 20 4C 61 72 67 65 ntoes East Large +0040: 20 49 6E 6E 20 52 6F 6F 6D 07 02 00 00 00 1C 00 Inn Room....... +0050: 49 72 6F 6E 74 6F 65 73 20 45 61 73 74 20 4C 61 Irontoes East La +0060: 72 67 65 20 49 6E 6E 20 52 6F 6F 6D 07 03 00 00 rge Inn Room.... +0070: 00 1C 00 49 72 6F 6E 74 6F 65 73 20 45 61 73 74 ...Irontoes East +0080: 20 4C 61 72 67 65 20 49 6E 6E 20 52 6F 6F 6D 07 Large Inn Room. +0090: 04 00 00 00 1C 00 49 72 6F 6E 74 6F 65 73 20 45 ......Irontoes E +00A0: 61 73 74 20 4C 61 72 67 65 20 49 6E 6E 20 52 6F ast Large Inn Ro +00B0: 6F 6D 07 05 00 00 00 1C 00 49 72 6F 6E 74 6F 65 om.......Irontoe +00C0: 73 20 45 61 73 74 20 4C 61 72 67 65 20 49 6E 6E s East Large Inn +00D0: 20 52 6F 6F 6D 07 06 00 00 00 1C 00 49 72 6F 6E Room.......Iron +00E0: 74 6F 65 73 20 45 61 73 74 20 4C 61 72 67 65 20 toes East Large +00F0: 49 6E 6E 20 52 6F 6F 6D 07 07 00 00 00 19 00 51 Inn Room.......Q +0100: 65 79 6E 6F 73 20 47 75 69 6C 64 20 48 61 6C 6C eynos Guild Hall +0110: 2C 20 54 69 65 72 20 31 07 08 00 00 00 16 00 4C , Tier 1.......L +0120: 69 6F 6E 27 73 20 4D 61 6E 65 20 53 75 69 74 65 ion's Mane Suite +0130: 20 52 6F 6F 6D 07 09 00 00 00 16 00 4C 69 6F 6E Room.......Lion +0140: 27 73 20 4D 61 6E 65 20 53 75 69 74 65 20 52 6F 's Mane Suite Ro +0150: 6F 6D 07 0A 00 00 00 16 00 4C 69 6F 6E 27 73 20 om.......Lion's +0160: 4D 61 6E 65 20 53 75 69 74 65 20 52 6F 6F 6D 07 Mane Suite Room. +0170: 0B 00 00 00 16 00 4C 69 6F 6E 27 73 20 4D 61 6E ......Lion's Man +0180: 65 20 53 75 69 74 65 20 52 6F 6F 6D 07 0C 00 00 e Suite Room.... +0190: 00 0E 00 32 20 4C 75 63 69 65 20 53 74 72 65 65 ...2 Lucie Stree +01A0: 74 07 0D 00 00 00 0F 00 31 37 20 54 72 61 6E 71 t.......17 Tranq +01B0: 75 69 6C 20 57 61 79 07 0E 00 00 00 0E 00 38 20 uil Way.......8 +01C0: 4C 75 63 69 65 20 53 74 72 65 65 74 07 0F 00 00 Lucie Street.... +01D0: 00 0F 00 31 32 20 4C 75 63 69 65 20 53 74 72 65 ...12 Lucie Stre +01E0: 65 74 07 10 00 00 00 0F 00 31 38 20 4C 75 63 69 et.......18 Luci +01F0: 65 20 53 74 72 65 65 74 07 11 00 00 00 0F 00 32 e Street.......2 +0200: 30 20 4C 75 63 69 65 20 53 74 72 65 65 74 07 12 0 Lucie Street.. +0210: 00 00 00 0E 00 33 20 54 72 61 6E 71 75 69 6C 20 .....3 Tranquil +0220: 57 61 79 07 13 00 00 00 0E 00 37 20 54 72 61 6E Way.......7 Tran +0230: 71 75 69 6C 20 57 61 79 07 14 00 00 00 0F 00 31 quil Way.......1 +0240: 33 20 54 72 61 6E 71 75 69 6C 20 57 61 79 07 15 3 Tranquil Way.. +0250: 00 00 00 0F 00 31 35 20 54 72 61 6E 71 75 69 6C .....15 Tranquil +0260: 20 57 61 79 07 16 00 00 00 19 00 51 65 79 6E 6F Way.......Qeyno +0270: 73 20 47 75 69 6C 64 20 48 61 6C 6C 2C 20 54 69 s Guild Hall, Ti +0280: 65 72 20 32 07 17 00 00 00 0F 00 38 20 45 72 6F er 2.......8 Ero +0290: 6C 6C 69 73 69 20 4C 61 6E 65 07 18 00 00 00 0F llisi Lane...... +02A0: 00 35 20 45 72 6F 6C 6C 69 73 69 20 4C 61 6E 65 .5 Erollisi Lane +02B0: 07 19 00 00 00 0E 00 35 20 4B 61 72 61 6E 61 20 .......5 Karana +02C0: 43 6F 75 72 74 07 1A 00 00 00 0D 00 32 20 42 61 Court.......2 Ba +02D0: 79 6C 65 20 43 6F 75 72 74 07 1B 00 00 00 0D 00 yle Court....... +02E0: 34 20 42 61 79 6C 65 20 43 6F 75 72 74 07 1C 00 4 Bayle Court... +02F0: 00 00 16 00 4C 69 6F 6E 27 73 20 4D 61 6E 65 20 ....Lion's Mane +0300: 53 75 69 74 65 20 52 6F 6F 6D 07 1D 00 00 00 16 Suite Room...... +0310: 00 4C 69 6F 6E 27 73 20 4D 61 6E 65 20 53 75 69 .Lion's Mane Sui +0320: 74 65 20 52 6F 6F 6D 07 1E 00 00 00 16 00 4C 69 te Room.......Li +0330: 6F 6E 27 73 20 4D 61 6E 65 20 53 75 69 74 65 20 on's Mane Suite +0340: 52 6F 6F 6D 07 1F 00 00 00 16 00 4C 69 6F 6E 27 Room.......Lion' +0350: 73 20 4D 61 6E 65 20 53 75 69 74 65 20 52 6F 6F s Mane Suite Roo +0360: 6D 07 20 00 00 00 0E 00 35 20 4C 75 63 69 65 20 m. .....5 Lucie +0370: 53 74 72 65 65 74 07 21 00 00 00 0F 00 32 30 20 Street.!.....20 +0380: 4B 61 72 61 6E 61 20 43 6F 75 72 74 07 22 00 00 Karana Court.".. +0390: 00 0E 00 39 20 4C 75 63 69 65 20 53 74 72 65 65 ...9 Lucie Stree +03A0: 74 07 23 00 00 00 0F 00 31 35 20 4C 75 63 69 65 t.#.....15 Lucie +03B0: 20 53 74 72 65 65 74 07 24 00 00 00 0F 00 31 37 Street.$.....17 +03C0: 20 4C 75 63 69 65 20 53 74 72 65 65 74 07 25 00 Lucie Street.%. +03D0: 00 00 0F 00 32 31 20 4C 75 63 69 65 20 53 74 72 ....21 Lucie Str +03E0: 65 65 74 07 26 00 00 00 0E 00 36 20 4B 61 72 61 eet.&.....6 Kara +03F0: 6E 61 20 43 6F 75 72 74 07 27 00 00 00 0F 00 31 na Court.'.....1 +0400: 32 20 4B 61 72 61 6E 61 20 43 6F 75 72 74 07 28 2 Karana Court.( +0410: 00 00 00 0F 00 31 34 20 4B 61 72 61 6E 61 20 43 .....14 Karana C +0420: 6F 75 72 74 07 29 00 00 00 0F 00 31 38 20 4B 61 ourt.).....18 Ka +0430: 72 61 6E 61 20 43 6F 75 72 74 07 2A 00 00 00 1E rana Court.*.... +0440: 00 43 6F 6E 63 6F 72 64 69 75 6D 20 54 6F 77 65 .Concordium Towe +0450: 72 20 4D 61 67 69 63 61 6C 20 4D 61 6E 6F 72 07 r Magical Manor. +0460: 2B 00 00 00 15 00 41 72 63 61 6E 65 20 41 63 61 +.....Arcane Aca +0470: 64 65 6D 79 20 50 6F 72 74 61 6C 07 2C 00 00 00 demy Portal.,... +0480: 13 00 43 6F 75 72 74 20 6F 66 20 74 68 65 20 4D ..Court of the M +0490: 61 73 74 65 72 07 2D 00 00 00 13 00 43 69 74 79 aster.-.....City +04A0: 20 6F 66 20 4D 69 73 74 20 45 73 74 61 74 65 07 of Mist Estate. +04B0: 2E 00 00 00 10 00 44 61 72 6B 6C 69 67 68 74 20 ......Darklight +04C0: 50 61 6C 61 63 65 07 2F 00 00 00 11 00 44 65 65 Palace./.....Dee +04D0: 70 77 61 74 65 72 20 52 65 74 72 65 61 74 07 30 pwater Retreat.0 +04E0: 00 00 00 24 00 44 68 61 6C 67 61 72 20 50 72 65 ...$.Dhalgar Pre +04F0: 63 69 70 69 63 65 20 6F 66 20 74 68 65 20 44 65 cipice of the De +0500: 65 70 20 50 6F 72 74 61 6C 07 31 00 00 00 12 00 ep Portal.1..... +0510: 44 69 6D 65 6E 73 69 6F 6E 61 6C 20 50 6F 63 6B Dimensional Pock +0520: 65 74 07 32 00 00 00 0B 00 44 6F 6A 6F 20 50 6F et.2.....Dojo Po +0530: 72 74 61 6C 07 33 00 00 00 21 00 45 6C 61 62 6F rtal.3...!.Elabo +0540: 72 61 74 65 20 45 73 74 61 74 65 20 6F 66 20 55 rate Estate of U +0550: 6E 72 65 73 74 20 50 6F 72 74 61 6C 07 34 00 00 nrest Portal.4.. +0560: 00 11 00 45 74 68 65 72 6E 65 72 65 20 45 6E 63 ...Ethernere Enc +0570: 6C 61 76 65 07 35 00 00 00 10 00 45 76 65 72 66 lave.5.....Everf +0580: 72 6F 73 74 20 50 6F 72 74 61 6C 07 36 00 00 00 rost Portal.6... +0590: 16 00 46 65 61 72 66 75 6C 20 52 65 74 72 65 61 ..Fearful Retrea +05A0: 74 20 50 6F 72 74 61 6C 07 37 00 00 00 0F 00 46 t Portal.7.....F +05B0: 65 6C 77 69 74 68 65 20 50 6F 72 74 61 6C 07 38 elwithe Portal.8 +05C0: 00 00 00 10 00 46 72 65 65 62 6C 6F 6F 64 20 50 .....Freeblood P +05D0: 6F 72 74 61 6C 07 39 00 00 00 0C 00 46 72 69 67 ortal.9.....Frig +05E0: 68 74 20 4D 61 6E 6F 72 07 3A 00 00 00 11 00 47 ht Manor.:.....G +05F0: 61 6C 6C 65 6F 6E 20 6F 66 20 44 72 65 61 6D 73 alleon of Dreams +0600: 07 3B 00 00 00 14 00 48 61 6C 6C 20 6F 66 20 74 .;.....Hall of t +0610: 68 65 20 43 68 61 6D 70 69 6F 6E 07 3C 00 00 00 he Champion.<... +0620: 10 00 48 75 61 20 4D 65 69 6E 20 52 65 74 72 65 ..Hua Mein Retre +0630: 61 74 07 3D 00 00 00 1C 00 49 73 6C 65 20 6F 66 at.=.....Isle of +0640: 20 52 65 66 75 67 65 20 50 72 65 73 74 69 67 65 Refuge Prestige +0650: 20 48 6F 6D 65 07 3E 00 00 00 0F 00 4B 65 72 61 Home.>.....Kera +0660: 66 79 72 6D 27 73 20 4C 61 69 72 07 3F 00 00 00 fyrm's Lair.?... +0670: 0E 00 4B 72 6F 6D 7A 65 6B 20 50 6F 72 74 61 6C ..Kromzek Portal +0680: 07 40 00 00 00 10 00 4C 61 76 61 73 74 6F 72 6D .@.....Lavastorm +0690: 20 50 6F 72 74 61 6C 07 41 00 00 00 0E 00 4C 69 Portal.A.....Li +06A0: 62 72 61 72 79 20 50 6F 72 74 61 6C 07 42 00 00 brary Portal.B.. +06B0: 00 0B 00 4D 61 72 61 20 45 73 74 61 74 65 07 43 ...Mara Estate.C +06C0: 00 00 00 21 00 4D 61 6A 27 44 75 6C 20 41 73 74 ...!.Maj'Dul Ast +06D0: 72 6F 6E 6F 6D 65 72 27 73 20 54 6F 77 65 72 20 ronomer's Tower +06E0: 50 6F 72 74 61 6C 07 44 00 00 00 14 00 4D 61 6A Portal.D.....Maj +06F0: 27 44 75 6C 20 53 75 69 74 65 20 50 6F 72 74 61 'Dul Suite Porta +0700: 6C 07 45 00 00 00 17 00 4D 69 73 74 6D 6F 6F 72 l.E.....Mistmoor +0710: 65 20 43 72 61 67 73 20 45 73 74 61 74 65 73 07 e Crags Estates. +0720: 46 00 00 00 0D 00 4F 61 6B 6D 79 73 74 20 47 6C F.....Oakmyst Gl +0730: 61 64 65 07 47 00 00 00 12 00 4F 70 65 72 61 20 ade.G.....Opera +0740: 48 6F 75 73 65 20 50 6F 72 74 61 6C 07 48 00 00 House Portal.H.. +0750: 00 16 00 50 65 72 73 6F 6E 61 6C 20 47 72 6F 74 ...Personal Grot +0760: 74 6F 20 50 6F 72 74 61 6C 07 49 00 00 00 17 00 to Portal.I..... +0770: 52 75 6D 20 52 75 6E 6E 65 72 73 20 43 6F 76 65 Rum Runners Cove +0780: 20 50 6F 72 74 61 6C 07 4A 00 00 00 12 00 50 6C Portal.J.....Pl +0790: 61 6E 65 74 61 72 69 75 6D 20 50 6F 72 74 61 6C anetarium Portal +07A0: 07 4B 00 00 00 14 00 52 65 73 65 61 72 63 68 65 .K.....Researche +07B0: 72 27 73 20 53 61 6E 63 74 75 6D 07 4C 00 00 00 r's Sanctum.L... +07C0: 1E 00 52 65 73 69 64 65 6E 63 65 20 6F 66 20 74 ..Residence of t +07D0: 68 65 20 42 6C 61 64 65 73 20 50 6F 72 74 61 6C he Blades Portal +07E0: 07 4D 00 00 00 16 00 53 61 6E 63 74 75 73 20 53 .M.....Sanctus S +07F0: 65 72 75 20 50 72 6F 6D 65 6E 61 64 65 07 4E 00 eru Promenade.N. +0800: 00 00 22 00 53 61 6E 74 61 20 47 6C 75 67 27 73 ..".Santa Glug's +0810: 20 43 68 65 65 72 66 75 6C 20 48 6F 6C 69 64 61 Cheerful Holida +0820: 79 20 48 6F 6D 65 07 4F 00 00 00 17 00 53 65 63 y Home.O.....Sec +0830: 6C 75 64 65 64 20 53 61 6E 63 74 75 6D 20 50 6F luded Sanctum Po +0840: 72 74 61 6C 07 50 00 00 00 18 00 53 6B 79 62 6C rtal.P.....Skybl +0850: 61 64 65 20 53 6B 69 66 66 20 4C 61 75 6E 63 68 ade Skiff Launch +0860: 70 61 64 07 51 00 00 00 0E 00 53 6E 6F 77 79 20 pad.Q.....Snowy +0870: 44 77 65 6C 6C 69 6E 67 07 52 00 00 00 1D 00 53 Dwelling.R.....S +0880: 70 72 6F 63 6B 65 74 27 73 20 49 6E 74 65 72 6C procket's Interl +0890: 6F 63 6B 69 6E 67 20 50 6C 61 6E 65 07 53 00 00 ocking Plane.S.. +08A0: 00 17 00 53 74 6F 72 6D 20 54 6F 77 65 72 20 49 ...Storm Tower I +08B0: 73 6C 65 20 50 6F 72 74 61 6C 07 54 00 00 00 21 sle Portal.T...! +08C0: 00 52 65 6C 69 63 20 54 69 6E 6B 65 72 20 50 72 .Relic Tinker Pr +08D0: 65 73 74 69 67 65 20 48 6F 6D 65 20 50 6F 72 74 estige Home Port +08E0: 61 6C 07 55 00 00 00 10 00 54 65 6E 65 62 72 6F al.U.....Tenebro +08F0: 75 73 20 50 6F 72 74 61 6C 07 56 00 00 00 10 00 us Portal.V..... +0900: 54 68 65 20 42 61 75 62 62 6C 65 73 68 69 72 65 The Baubbleshire +0910: 07 57 00 00 00 0F 00 54 69 6E 6B 65 72 65 72 27 .W.....Tinkerer' +0920: 73 20 49 73 6C 65 07 58 00 00 00 12 00 54 6F 77 s Isle.X.....Tow +0930: 65 72 20 6F 66 20 4B 6E 6F 77 6C 65 64 67 65 07 er of Knowledge. +0940: 59 00 00 00 15 00 55 6E 63 61 6E 6E 79 20 45 73 Y.....Uncanny Es +0950: 74 61 74 65 20 50 6F 72 74 61 6C 07 5A 00 00 00 tate Portal.Z... +0960: 1E 00 56 61 63 61 6E 74 20 45 73 74 61 74 65 20 ..Vacant Estate +0970: 6F 66 20 55 6E 72 65 73 74 20 50 6F 72 74 61 6C of Unrest Portal +0980: 07 5B 00 00 00 18 00 56 61 6C 65 20 6F 66 20 48 .[.....Vale of H +0990: 61 6C 66 70 69 6E 74 20 44 65 6C 69 67 68 74 07 alfpint Delight. +09A0: 5C 00 00 00 26 00 4C 69 6F 6E 27 73 20 4D 61 6E \...&.Lion's Man +09B0: 65 20 56 65 73 74 69 67 65 20 52 6F 6F 6D 20 2D e Vestige Room - +09C0: 20 4E 65 74 74 6C 65 76 69 6C 6C 65 07 5D 00 00 Nettleville.].. +09D0: 00 2C 00 4C 69 6F 6E 27 73 20 4D 61 6E 65 20 56 .,.Lion's Mane V +09E0: 65 73 74 69 67 65 20 52 6F 6F 6D 20 2D 20 53 74 estige Room - St +09F0: 61 72 63 72 65 73 74 20 43 6F 6D 6D 75 6E 65 07 arcrest Commune. +0A00: 5E 00 00 00 29 00 4C 69 6F 6E 27 73 20 4D 61 6E ^...).Lion's Man +0A10: 65 20 56 65 73 74 69 67 65 20 52 6F 6F 6D 20 2D e Vestige Room - +0A20: 20 47 72 61 79 73 74 6F 6E 65 20 59 61 72 64 07 Graystone Yard. +0A30: 5F 00 00 00 2C 00 4C 69 6F 6E 27 73 20 4D 61 6E _...,.Lion's Man +0A40: 65 20 56 65 73 74 69 67 65 20 52 6F 6F 6D 20 2D e Vestige Room - +0A50: 20 43 61 73 74 6C 65 76 69 65 77 20 48 61 6D 6C Castleview Haml +0A60: 65 74 07 60 00 00 00 2A 00 4C 69 6F 6E 27 73 20 et.`...*.Lion's +0A70: 4D 61 6E 65 20 56 65 73 74 69 67 65 20 52 6F 6F Mane Vestige Roo +0A80: 6D 20 2D 20 54 68 65 20 57 69 6C 6C 6F 77 20 57 m - The Willow W +0A90: 6F 6F 64 07 61 00 00 00 2B 00 4C 69 6F 6E 27 73 ood.a...+.Lion's +0AA0: 20 4D 61 6E 65 20 56 65 73 74 69 67 65 20 52 6F Mane Vestige Ro +0AB0: 6F 6D 20 2D 20 54 68 65 20 42 61 75 62 62 6C 65 om - The Baubble +0AC0: 73 68 69 72 65 07 62 00 00 00 FF FF FF FF shire.b....... +*/ +} \ No newline at end of file diff --git a/source/WorldServer/Items/Items.cpp b/source/WorldServer/Items/Items.cpp new file mode 100644 index 0000000..a7b87fe --- /dev/null +++ b/source/WorldServer/Items/Items.cpp @@ -0,0 +1,4648 @@ +/* + 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 . +*/ +#include "Items.h" +#include "../Spells.h" +#include "../Quests.h" +#include "../Player.h" +#include "../classes.h" +#include "math.h" +#include "../World.h" +#include "../LuaInterface.h" +#include "../../common/Log.h" +#include "../Entity.h" +#include "../Recipes/Recipe.h" +#include +#include +#include +#include "../Rules/Rules.h" + +extern World world; +extern MasterSpellList master_spell_list; +extern MasterQuestList master_quest_list; +extern MasterRecipeList master_recipe_list; +extern ConfigReader configReader; +extern LuaInterface* lua_interface; +extern RuleManager rule_manager; +extern Classes classes; + +MasterItemList::MasterItemList(){ + AddMappedItemStat(ITEM_STAT_ADORNING, std::string("adorning")); + AddMappedItemStat(ITEM_STAT_AGGRESSION, std::string("aggression")); + AddMappedItemStat(ITEM_STAT_ARTIFICING, std::string("artificing")); + AddMappedItemStat(ITEM_STAT_ARTISTRY, std::string("artistry")); + AddMappedItemStat(ITEM_STAT_CHEMISTRY, std::string("chemistry")); + AddMappedItemStat(ITEM_STAT_CRUSHING, std::string("crushing")); + AddMappedItemStat(ITEM_STAT_DEFENSE, std::string("defense")); + AddMappedItemStat(ITEM_STAT_DEFLECTION, std::string("deflection")); + AddMappedItemStat(ITEM_STAT_DISRUPTION, std::string("disruption")); + AddMappedItemStat(ITEM_STAT_FISHING, std::string("fishing")); + AddMappedItemStat(ITEM_STAT_FLETCHING, std::string("fletching")); + AddMappedItemStat(ITEM_STAT_FOCUS, std::string("focus")); + AddMappedItemStat(ITEM_STAT_FORESTING, std::string("foresting")); + AddMappedItemStat(ITEM_STAT_GATHERING, std::string("gathering")); + AddMappedItemStat(ITEM_STAT_METAL_SHAPING, std::string("metal shaping")); + AddMappedItemStat(ITEM_STAT_METALWORKING, std::string("metalworking")); + AddMappedItemStat(ITEM_STAT_MINING, std::string("mining")); + AddMappedItemStat(ITEM_STAT_MINISTRATION, std::string("ministration")); + AddMappedItemStat(ITEM_STAT_ORDINATION, std::string("ordination")); + AddMappedItemStat(ITEM_STAT_ADORNING, std::string("adorning")); + AddMappedItemStat(ITEM_STAT_PARRY, std::string("parry")); + AddMappedItemStat(ITEM_STAT_PIERCING, std::string("piercing")); + AddMappedItemStat(ITEM_STAT_RANGED, std::string("ranged")); + AddMappedItemStat(ITEM_STAT_SAFE_FALL, std::string("safe fall")); + AddMappedItemStat(ITEM_STAT_SCRIBING, std::string("scribing")); + AddMappedItemStat(ITEM_STAT_SCULPTING, std::string("sculpting")); + AddMappedItemStat(ITEM_STAT_SLASHING, std::string("slashing")); + AddMappedItemStat(ITEM_STAT_SUBJUGATION, std::string("subjugation")); + AddMappedItemStat(ITEM_STAT_SWIMMING, std::string("swimming")); + AddMappedItemStat(ITEM_STAT_TAILORING, std::string("tailoring")); + AddMappedItemStat(ITEM_STAT_TINKERING, std::string("tinkering")); + AddMappedItemStat(ITEM_STAT_TRANSMUTING, std::string("transmuting")); + AddMappedItemStat(ITEM_STAT_TRAPPING, std::string("trapping")); + AddMappedItemStat(ITEM_STAT_WEAPON_SKILLS, std::string("weapon skills")); + AddMappedItemStat(ITEM_STAT_POWER_COST_REDUCTION, std::string("power cost reduction")); + AddMappedItemStat(ITEM_STAT_SPELL_AVOIDANCE, std::string("spell avoidance")); +} + +void MasterItemList::AddMappedItemStat(int32 id, std::string lower_case_name) +{ + mappedItemStatsStrings[lower_case_name] = id; + mappedItemStatTypeIDs[id] = lower_case_name; +} + +MasterItemList::~MasterItemList(){ + RemoveAll(); + + map>::iterator itr; + for (itr = broker_item_map.begin(); itr != broker_item_map.end(); itr++) + { + VersionRange* range = itr->first; + delete range; + } + + broker_item_map.clear(); +} + + +void MasterItemList::AddBrokerItemMapRange(int32 min_version, int32 max_version, + int64 client_bitmask, int64 server_bitmask) +{ + map>::iterator itr = FindBrokerItemMapVersionRange(min_version, max_version); + if (itr != broker_item_map.end()) { + itr->second.insert(make_pair(client_bitmask, server_bitmask)); + return; + } + + VersionRange* range = new VersionRange(min_version, max_version); + broker_item_map[range][client_bitmask] = server_bitmask; +} + +map>::iterator MasterItemList::FindBrokerItemMapVersionRange(int32 min_version, int32 max_version) +{ + map>::iterator itr; + for (itr = broker_item_map.begin(); itr != broker_item_map.end(); itr++) + { + VersionRange* range = itr->first; + // if min and max version are both in range + if (range->GetMinVersion() <= min_version && max_version <= range->GetMaxVersion()) + return itr; + // if the min version is in range, but max range is 0 + else if (range->GetMinVersion() <= min_version && range->GetMaxVersion() == 0) + return itr; + // if min version is 0 and max_version has a cap + else if (range->GetMinVersion() == 0 && max_version <= range->GetMaxVersion()) + return itr; + } + + return broker_item_map.end(); +} + +map>::iterator MasterItemList::FindBrokerItemMapByVersion(int32 version) +{ + map>::iterator enditr = broker_item_map.end(); + map>::iterator itr; + for (itr = broker_item_map.begin(); itr != broker_item_map.end(); itr++) + { + VersionRange* range = itr->first; + // if min and max version are both in range + if(range->GetMinVersion() == 0 && range->GetMaxVersion() == 0) + enditr = itr; + else if (version >= range->GetMinVersion() && version <= range->GetMaxVersion()) + return itr; + } + + return enditr; +} + +vector* MasterItemList::GetItems(string name, int64 itype, int64 ltype, int64 btype, int64 minprice, int64 maxprice, int8 minskill, int8 maxskill, string seller, string adornment, int8 mintier, int8 maxtier, int16 minlevel, int16 maxlevel, sint8 itemclass){ + vector* ret = new vector; + map::iterator iter; + Item* item = 0; + const char* chkname = 0; + //const char* chkseller = 0; + //const char* chkadornment = 0; + if(name.length() > 0) + chkname = name.c_str(); + //if(seller.length() > 0) + // chkseller = seller.c_str(); + //if(adornment.length() > 0) + // chkadornment = adornment.c_str(); + LogWrite(ITEM__WARNING, 0, "Item", "Get Items: %s (itype: %llu, ltype: %llu, btype: %llu, minskill: %u, maxskill: %u, mintier: %u, maxtier: %u, minlevel: %u, maxlevel: %u itemclass %i)", name.c_str(), itype, ltype, btype, minskill, maxskill, mintier, maxtier, minlevel, maxlevel, itemclass); + bool should_add = true; + for(iter = items.begin();iter != items.end(); iter++){ + item = iter->second; + if(item){ + if(itype != ITEM_BROKER_TYPE_ANY && itype != ITEM_BROKER_TYPE_ANY64BIT){ + should_add = false; + switch(itype){ + case ITEM_BROKER_TYPE_ADORNMENT:{ + if(item->IsAdornment()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_AMMO:{ + if(item->IsAmmo()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_ATTUNEABLE:{ + if(item->CheckFlag(ATTUNEABLE)) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_BAG:{ + if(item->IsBag()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_BAUBLE:{ + if(item->IsBauble()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_BOOK:{ + if(item->IsBook()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_CHAINARMOR:{ + if(item->IsChainArmor()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_CLOAK:{ + if(item->IsCloak()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_CLOTHARMOR:{ + if(item->IsClothArmor()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_COLLECTABLE:{ + if(item->IsCollectable()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_CRUSHWEAPON:{ + if(item->IsCrushWeapon() && (item->weapon_info->wield_type == ITEM_WIELD_TYPE_DUAL || item->weapon_info->wield_type == ITEM_WIELD_TYPE_SINGLE)) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_DRINK:{ + if(item->IsFoodDrink()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_FOOD:{ + if(item->IsFoodFood()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_HOUSEITEM:{ + if(item->IsHouseItem()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_JEWELRY:{ + if(item->IsJewelry()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_LEATHERARMOR:{ + if(item->IsLeatherArmor()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_LORE:{ + if(item->CheckFlag(LORE)) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_MISC:{ + if(item->IsMisc()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_PIERCEWEAPON:{ + if(item->IsPierceWeapon() && (item->weapon_info->wield_type == ITEM_WIELD_TYPE_DUAL || item->weapon_info->wield_type == ITEM_WIELD_TYPE_SINGLE)) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_PLATEARMOR:{ + if(item->IsPlateArmor()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_POISON:{ + if(item->IsPoison()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_POTION:{ + if(item->IsPotion()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_RECIPEBOOK:{ + if(item->IsRecipeBook()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_SALESDISPLAY:{ + if(item->IsSalesDisplay()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_SHIELD:{ + if(item->IsShield()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_SLASHWEAPON:{ + if(item->IsSlashWeapon() && (item->weapon_info->wield_type == ITEM_WIELD_TYPE_DUAL || item->weapon_info->wield_type == ITEM_WIELD_TYPE_SINGLE)) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_SPELLSCROLL:{ + if(item->IsSpellScroll()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_TINKERED:{ + if(item->tinkered == 1) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_TRADESKILL:{ + if(item->crafted == 1) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_2H_CRUSH:{ + should_add = item->IsWeapon() && item->weapon_info->wield_type == ITEM_WIELD_TYPE_TWO_HAND && item->generic_info.skill_req1 == SKILL_ID_STAFF; + break; + } + case ITEM_BROKER_TYPE_2H_PIERCE:{ + should_add = item->IsWeapon() && item->weapon_info->wield_type == ITEM_WIELD_TYPE_TWO_HAND && item->generic_info.skill_req1 == SKILL_ID_GREATSPEAR; + break; + } + case ITEM_BROKER_TYPE_2H_SLASH:{ + should_add = item->IsWeapon() && item->weapon_info->wield_type == ITEM_WIELD_TYPE_TWO_HAND && item->generic_info.skill_req1 == SKILL_ID_GREATSWORD; + break; + } + } + if(!should_add) + continue; + } + if(ltype != ITEM_BROKER_SLOT_ANY){ + should_add = false; + switch(ltype){ + case ITEM_BROKER_SLOT_AMMO:{ + should_add = item->HasSlot(EQ2_AMMO_SLOT); + break; + } + case ITEM_BROKER_SLOT_CHARM:{ + should_add = item->HasSlot(EQ2_CHARM_SLOT_1, EQ2_CHARM_SLOT_2); + break; + } + case ITEM_BROKER_SLOT_CHEST:{ + should_add = item->HasSlot(EQ2_CHEST_SLOT); + break; + } + case ITEM_BROKER_SLOT_CLOAK:{ + should_add = item->HasSlot(EQ2_CLOAK_SLOT); + break; + } + case ITEM_BROKER_SLOT_DRINK:{ + should_add = item->HasSlot(EQ2_DRINK_SLOT); + break; + } + case ITEM_BROKER_SLOT_EARS:{ + should_add = item->HasSlot(EQ2_EARS_SLOT_1, EQ2_EARS_SLOT_2); + break; + } + case ITEM_BROKER_SLOT_FEET:{ + should_add = item->HasSlot(EQ2_FEET_SLOT); + break; + } + case ITEM_BROKER_SLOT_FOOD:{ + should_add = item->HasSlot(EQ2_FOOD_SLOT); + break; + } + case ITEM_BROKER_SLOT_FOREARMS:{ + should_add = item->HasSlot(EQ2_FOREARMS_SLOT); + break; + } + case ITEM_BROKER_SLOT_HANDS:{ + should_add = item->HasSlot(EQ2_HANDS_SLOT); + break; + } + case ITEM_BROKER_SLOT_HEAD:{ + should_add = item->HasSlot(EQ2_HEAD_SLOT); + break; + } + case ITEM_BROKER_SLOT_LEGS:{ + should_add = item->HasSlot(EQ2_LEGS_SLOT); + break; + } + case ITEM_BROKER_SLOT_NECK:{ + should_add = item->HasSlot(EQ2_NECK_SLOT); + break; + } + case ITEM_BROKER_SLOT_PRIMARY:{ + should_add = item->HasSlot(EQ2_PRIMARY_SLOT); + break; + } + case ITEM_BROKER_SLOT_PRIMARY_2H:{ + should_add = item->HasSlot(EQ2_PRIMARY_SLOT) && item->IsWeapon() && item->weapon_info->wield_type == ITEM_WIELD_TYPE_TWO_HAND; + break; + } + case ITEM_BROKER_SLOT_RANGE_WEAPON:{ + should_add = item->HasSlot(EQ2_RANGE_SLOT); + break; + } + case ITEM_BROKER_SLOT_RING:{ + should_add = item->HasSlot(EQ2_LRING_SLOT, EQ2_RRING_SLOT); + break; + } + case ITEM_BROKER_SLOT_SECONDARY:{ + should_add = item->HasSlot(EQ2_SECONDARY_SLOT); + break; + } + case ITEM_BROKER_SLOT_SHOULDERS:{ + should_add = item->HasSlot(EQ2_SHOULDERS_SLOT); + break; + } + case ITEM_BROKER_SLOT_WAIST:{ + should_add = item->HasSlot(EQ2_WAIST_SLOT); + break; + } + case ITEM_BROKER_SLOT_WRIST:{ + should_add = item->HasSlot(EQ2_LWRIST_SLOT, EQ2_RWRIST_SLOT); + break; + } + } + if(!should_add) + continue; + } + + if(btype != 0xFFFFFFFF){ + vector::iterator itr; + bool stat_found = false; + should_add = false; + switch(btype){ + case ITEM_BROKER_STAT_TYPE_NONE:{ + if (item->item_stats.size() == 0) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_DEF:{ + stat_found = item->HasStat(ITEM_STAT_DEFENSE, GetItemStatNameByID(ITEM_STAT_DEFENSE)); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_STR:{ + stat_found = item->HasStat(ITEM_STAT_STR); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_STA:{ + stat_found = item->HasStat(ITEM_STAT_STA); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_AGI:{ + stat_found = item->HasStat(ITEM_STAT_AGI); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_WIS:{ + stat_found = item->HasStat(ITEM_STAT_WIS); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_INT:{ + stat_found = item->HasStat(ITEM_STAT_INT); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_HEALTH:{ + stat_found = item->HasStat(ITEM_STAT_HEALTH); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_POWER:{ + stat_found = item->HasStat(ITEM_STAT_POWER); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_HEAT:{ + stat_found = item->HasStat(ITEM_STAT_VS_HEAT); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_COLD:{ + stat_found = item->HasStat(ITEM_STAT_VS_COLD); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_MAGIC:{ + stat_found = item->HasStat(ITEM_STAT_VS_MAGIC); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_MENTAL:{ + stat_found = item->HasStat(ITEM_STAT_VS_MENTAL); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_DIVINE:{ + stat_found = item->HasStat(ITEM_STAT_VS_DIVINE); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_POISON:{ + stat_found = item->HasStat(ITEM_STAT_VS_POISON); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_DISEASE:{ + stat_found = item->HasStat(ITEM_STAT_VS_DISEASE); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_CRUSH:{ + stat_found = item->HasStat(ITEM_STAT_DMG_CRUSH); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_SLASH:{ + stat_found = item->HasStat(ITEM_STAT_DMG_SLASH); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_PIERCE:{ + stat_found = item->HasStat(ITEM_STAT_DMG_PIERCE); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_CRITICAL: { + stat_found = item->HasStat(ITEM_STAT_CRITICALMITIGATION); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_DBL_ATTACK:{ + stat_found = item->HasStat(ITEM_STAT_MULTIATTACKCHANCE); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_ABILITY_MOD:{ + stat_found = item->HasStat(ITEM_STAT_ABILITY_MODIFIER); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_POTENCY:{ + stat_found = item->HasStat(ITEM_STAT_POTENCY); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_AEAUTOATTACK:{ + stat_found = item->HasStat(ITEM_STAT_AEAUTOATTACKCHANCE); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_ATTACKSPEED:{ + stat_found = item->HasStat(ITEM_STAT_ATTACKSPEED); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_BLOCKCHANCE:{ + stat_found = item->HasStat(ITEM_STAT_EXTRASHIELDBLOCKCHANCE); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_CASTINGSPEED:{ + stat_found = item->HasStat(ITEM_STAT_ABILITYCASTINGSPEED); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_CRITBONUS:{ + stat_found = item->HasStat(ITEM_STAT_CRITBONUS); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_CRITCHANCE:{ + stat_found = item->HasStat(ITEM_STAT_MELEECRITCHANCE); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_DPS:{ + stat_found = item->HasStat(ITEM_STAT_DPS); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_FLURRYCHANCE:{ + stat_found = item->HasStat(ITEM_STAT_FLURRY); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_HATEGAIN:{ + stat_found = item->HasStat(ITEM_STAT_HATEGAINMOD); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_MITIGATION:{ + stat_found = item->HasStat(ITEM_STAT_ARMORMITIGATIONINCREASE); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_MULTI_ATTACK:{ + stat_found = item->HasStat(ITEM_STAT_MULTIATTACKCHANCE); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_RECOVERY:{ + stat_found = item->HasStat(ITEM_STAT_ABILITYRECOVERYSPEED); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_REUSE_SPEED:{ + stat_found = item->HasStat(ITEM_STAT_ABILITYREUSESPEED); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_SPELL_WPNDMG:{ + stat_found = item->HasStat(ITEM_STAT_SPELLWEAPONDAMAGEBONUS); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_STRIKETHROUGH:{ + stat_found = item->HasStat(ITEM_STAT_STRIKETHROUGH); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_TOUGHNESS:{ + stat_found = item->HasStat(ITEM_STAT_PVPTOUGHNESS); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_WEAPONDMG:{ + stat_found = item->HasStat(ITEM_STAT_WEAPONDAMAGEBONUS); + if (stat_found) + should_add = true; + break; + } + default: { + LogWrite(ITEM__DEBUG, 0, "Item", "Unknown item broker stat type %u", btype); + LogWrite(ITEM__DEBUG, 0, "Item", "If you have a client before the new expansion this may be the reason. Please be patient while we update items to support the new client.", btype); + break; + } + } + if (!should_add) + continue; + } + + if(itemclass > 0){ + int64 tmpVal = ((int64)2) << (itemclass-1); + should_add = (item->generic_info.adventure_classes & tmpVal); + if(!should_add && !(item->generic_info.tradeskill_classes & tmpVal)) + continue; + } + if(chkname && item->lowername.find(chkname) >= 0xFFFFFFFF) + continue; + if(item->generic_info.adventure_default_level == 0 && item->generic_info.tradeskill_default_level == 0 && minlevel > 0 && maxlevel > 0){ + if(item->details.recommended_level < minlevel) + continue; + if(item->details.recommended_level > maxlevel) + continue; + } + else{ + if(minlevel > 0 && ((item->generic_info.adventure_default_level == 0 && item->generic_info.tradeskill_default_level == 0) || (item->generic_info.adventure_default_level > 0 && item->generic_info.adventure_default_level < minlevel) || (item->generic_info.tradeskill_default_level > 0 && item->generic_info.tradeskill_default_level < minlevel))) + continue; + if(maxlevel > 0 && ((item->generic_info.adventure_default_level > 0 && item->generic_info.adventure_default_level > maxlevel) || (item->generic_info.tradeskill_default_level > 0 && item->generic_info.tradeskill_default_level > maxlevel))) + continue; + } + // mintier of 1 is 'ANY' + if(mintier > 1 && item->details.tier < mintier) + continue; + if(maxtier > 0 && item->details.tier > maxtier) + continue; + + /* these skill values are not fields provided in the UI beyond CLASSIC + ** They are also not in line with skill_min, they provide a scale of 0-6, obselete is 0, 6 is red (cannot be used) + if(minskill > 0 && item->generic_info.skill_min < minskill) + continue; + if(maxskill > 0 && item->generic_info.skill_min > maxskill) + continue; + */ + ret->push_back(item); + } + } + return ret; +} + +vector* MasterItemList::GetItems(map criteria, Client* client_to_map){ + string name, seller, adornment; + int64 itype = ITEM_BROKER_TYPE_ANY64BIT; + int64 ltype = ITEM_BROKER_TYPE_ANY64BIT; + int64 btype = ITEM_BROKER_TYPE_ANY64BIT; + int64 minprice = 0; + int64 maxprice = 0; + int8 minskill = 0; + int8 maxskill = 0; + int8 mintier = 0; + int8 maxtier = 0; + int16 minlevel = 0; + int16 maxlevel = 0; + sint8 itemclass = 0; + int32 itemID = 0; + if (criteria.count("ITEM") > 0) + { + if (IsNumber(criteria["ITEM"].c_str())) + { + itemID = atoul(criteria["ITEM"].c_str()); + Item* itm = GetItem(itemID); + vector* ret = new vector; + if (itm) + ret->push_back(itm); + return ret; + } + else + name = criteria["ITEM"]; + } + if(criteria.count("MINSKILL") > 0) + minskill = (int8)ParseIntValue(criteria["MINSKILL"]); + if(criteria.count("MAXSKILL") > 0) + maxskill = (int8)ParseIntValue(criteria["MAXSKILL"]); + if(criteria.count("MINTIER") > 0) + mintier = (int8)ParseIntValue(criteria["MINTIER"]); + if(criteria.count("MAXTIER") > 0) + maxtier = (int8)ParseIntValue(criteria["MAXTIER"]); + if(criteria.count("MINLEVEL") > 0) + minlevel = (int16)ParseIntValue(criteria["MINLEVEL"]); + if(criteria.count("MAXLEVEL") > 0) + maxlevel = (int16)ParseIntValue(criteria["MAXLEVEL"]); + if(criteria.count("ITYPE") > 0) + itype = ParseLongLongValue(criteria["ITYPE"]); + if(criteria.count("LTYPE") > 0) + ltype = ParseLongLongValue(criteria["LTYPE"]); + if(criteria.count("BTYPE") > 0) + btype = ParseLongLongValue(criteria["BTYPE"]); + if(criteria.count("SKILLNAME") > 0) + itemclass = world.GetClassID(criteria["SKILLNAME"].c_str()); + + if(client_to_map) { + map>::iterator itr = FindBrokerItemMapByVersion(client_to_map->GetVersion()); + if(itr != broker_item_map.end() && itr->second.find(btype) != itr->second.end()) { + LogWrite(ITEM__DEBUG, 0, "Item", "Found broker mapping, btype %u becomes %llu", btype, itr->second[btype]); + btype = itr->second[btype]; + } + } + return GetItems(name, itype, ltype, btype, minprice, maxprice, minskill, maxskill, seller, adornment, mintier, maxtier, minlevel, maxlevel, itemclass); +} + +void MasterItemList::ResetUniqueID(int32 new_id){ + next_unique_id = new_id; +} + +int32 MasterItemList::NextUniqueID(){ + next_unique_id++; + if(next_unique_id >= 0xFFFFFFF0) + next_unique_id = 1; + return next_unique_id; +} + +bool MasterItemList::IsBag(int32 item_id){ + Item* item = GetItem(item_id); + if(item && item->details.num_slots > 0) + return true; + else + return false; +} + + +Item* MasterItemList::GetItem(int32 id){ + Item* item = 0; + if(items.count(id) > 0) + item = items[id]; + return item; +} + +Item* MasterItemList::GetItemByName(const char* name) { + Item* item = 0; + map::iterator itr; + for (itr = items.begin(); itr != items.end(); itr++) { + Item* current_item = itr->second; + if (::ToLower(string(current_item->name.c_str())) == ::ToLower(string(name))) { + item = current_item; + break; + } + } + return item; +} + +ItemStatsValues* MasterItemList::CalculateItemBonuses(int32 item_id, Entity* entity){ + return CalculateItemBonuses(items[item_id], entity); +} + +ItemStatsValues* MasterItemList::CalculateItemBonuses(Item* item, Entity* entity, ItemStatsValues* values){ + if(item){ + if(!values){ + values = new ItemStatsValues; + memset(values, 0, sizeof(ItemStatsValues)); + } + for(int32 i=0;iitem_stats.size();i++){ + ItemStat* stat = item->item_stats[i]; + int multiplier = 100; + if(stat->stat_subtype > 99) + multiplier = 1000; + + int32 id = 0; + sint32 value = stat->value; + if(stat->stat_type != 1) + id = stat->stat_type*multiplier + stat->stat_subtype; + else + { + int32 tmp_id = master_item_list.GetItemStatIDByName(::ToLower(stat->stat_name)); + if(tmp_id != 0xFFFFFFFF) + { + id = tmp_id; + value = stat->stat_subtype; + } + else + id = stat->stat_type*multiplier + stat->stat_subtype; + } + + if(entity->IsPlayer()) { + int32 effective_level = entity->GetInfoStructUInt("effective_level"); + if(effective_level && effective_level < entity->GetLevel() && item->details.recommended_level > effective_level) + { + int32 diff = item->details.recommended_level - effective_level; + float tmpValue = (float)value; + value = (sint32)(float)(tmpValue / (1.0f + ((float)diff * rule_manager.GetGlobalRule(R_Player, MentorItemDecayRate)->GetFloat()))); + } + } + + world.AddBonuses(item, values, id, value, entity); + } + return values; + } + return 0; +} + +void MasterItemList::RemoveAll(){ + map::iterator iter; + for(iter = items.begin();iter != items.end(); iter++){ + safe_delete(iter->second); + } + items.clear(); + if(lua_interface) + lua_interface->DestroyItemScripts(); +} + +void MasterItemList::AddItem(Item* item){ + map::iterator iter; + if((iter = items.find(item->details.item_id)) != items.end()) { + Item* tmpItem = items[item->details.item_id]; + items.erase(iter); + safe_delete(tmpItem); + } + items[item->details.item_id] = item; +} + +Item::Item(){ + item_script = ""; + sell_price = 0; + sell_status = 0; + max_sell_value = 0; + save_needed = true; + needs_deletion = false; + weapon_info = 0; + ranged_info = 0; + adornment_info = 0; + bag_info = 0; + food_info = 0; + bauble_info = 0; + thrown_info = 0; + skill_info = 0; + recipebook_info = 0; + itemset_info = 0; + armor_info = 0; + book_info = 0; + book_info_pages = 0; + houseitem_info = 0; + housecontainer_info = 0; + memset(&details, 0, sizeof(ItemCore)); + memset(&generic_info, 0, sizeof(Generic_Info)); + generic_info.condition = 100; + no_buy_back = false; + no_sale = false; + created = std::time(nullptr); + effect_type = NO_EFFECT_TYPE; + book_language = 0; +} + +Item::Item(Item* in_item){ + needs_deletion = false; + sell_price = in_item->sell_price; + sell_status = in_item->sell_status; + max_sell_value = in_item->max_sell_value; + save_needed = true; + SetItem(in_item); + details.unique_id = master_item_list.NextUniqueID(); + if (IsBag()) + details.bag_id = details.unique_id; + generic_info.condition = 100; + spell_id = in_item->spell_id; + spell_tier = in_item->spell_tier; + no_buy_back = in_item->no_buy_back; + no_sale = in_item->no_sale; + created = in_item->created; + grouped_char_ids.insert(in_item->grouped_char_ids.begin(), in_item->grouped_char_ids.end()); + effect_type = in_item->effect_type; + book_language = in_item->book_language; +} + +Item::~Item(){ + for(int32 i=0;iGetItemScript()) + SetItemScript(old_item->GetItemScript()); + name = old_item->name; + lowername = old_item->lowername; + description = old_item->description; + memcpy(&generic_info, &old_item->generic_info, sizeof(Generic_Info)); + weapon_info = 0; + ranged_info = 0; + adornment_info = 0; + adorn0 = 0; + adorn1 = 0; + adorn2 = 0; + bag_info = 0; + food_info = 0; + bauble_info = 0; + thrown_info = 0; + skill_info = 0; + recipebook_info = 0; + itemset_info = 0; + armor_info = 0; + book_info = 0; + book_info_pages = 0; + houseitem_info = 0; + housecontainer_info = 0; + stack_count = old_item->stack_count; + generic_info.skill_req1 = old_item->generic_info.skill_req1; + generic_info.skill_req2 = old_item->generic_info.skill_req2; + memcpy(&details, &old_item->details, sizeof(ItemCore)); + weapon_type = old_item->GetWeaponType(); + switch(old_item->generic_info.item_type){ + case ITEM_TYPE_WEAPON:{ + weapon_info = new Weapon_Info; + memcpy(weapon_info, old_item->weapon_info, sizeof(Weapon_Info)); + break; + } + case ITEM_TYPE_RANGED:{ + ranged_info = new Ranged_Info; + memcpy(ranged_info, old_item->ranged_info, sizeof(Ranged_Info)); + break; + } + case ITEM_TYPE_SHIELD: + case ITEM_TYPE_ARMOR:{ + armor_info = new Armor_Info; + memcpy(armor_info, old_item->armor_info, sizeof(Armor_Info)); + break; + } + case ITEM_TYPE_BAG:{ + bag_info = new Bag_Info; + memcpy(bag_info, old_item->bag_info, sizeof(Bag_Info)); + break; + } + case ITEM_TYPE_FOOD:{ + food_info = new Food_Info; + memcpy(food_info, old_item->food_info, sizeof(Food_Info)); + break; + } + case ITEM_TYPE_BAUBLE:{ + bauble_info = new Bauble_Info; + memcpy(bauble_info, old_item->bauble_info, sizeof(Bauble_Info)); + break; + } + case ITEM_TYPE_SKILL:{ + skill_info = new Skill_Info; + memcpy(skill_info, old_item->skill_info, sizeof(Skill_Info)); + break; + } + case ITEM_TYPE_THROWN:{ + thrown_info = new Thrown_Info; + memcpy(thrown_info, old_item->thrown_info, sizeof(Thrown_Info)); + break; + } + case ITEM_TYPE_BOOK:{ + book_info = new Book_Info; + book_info->language = old_item->book_info->language; + book_info->author.data = old_item->book_info->author.data; + book_info->author.size = old_item->book_info->author.size; + book_info->title.data = old_item->book_info->title.data; + book_info->title.size = old_item->book_info->title.size; + + break; + } + case ITEM_TYPE_HOUSE:{ + houseitem_info = new HouseItem_Info; + memcpy(houseitem_info, old_item->houseitem_info, sizeof(HouseItem_Info)); + break; + } + case ITEM_TYPE_RECIPE:{ + // Recipe Book + recipebook_info = new RecipeBook_Info; + if (old_item->recipebook_info) { + recipebook_info->recipe_id = old_item->recipebook_info->recipe_id; + recipebook_info->uses = old_item->recipebook_info->uses; + for (int32 i = 0; i < old_item->recipebook_info->recipes.size(); i++) + recipebook_info->recipes.push_back(old_item->recipebook_info->recipes.at(i)); + } + break; + } + + case ITEM_TYPE_ADORNMENT:{ + adornment_info = new Adornment_Info; + memcpy(adornment_info, old_item->adornment_info, sizeof(Adornment_Info)); + break; + } + case ITEM_TYPE_HOUSE_CONTAINER:{ + // House Containers + housecontainer_info = new HouseContainer_Info; + if (old_item->housecontainer_info) { + housecontainer_info->broker_commission = old_item->housecontainer_info->broker_commission; + housecontainer_info->fence_commission = old_item->housecontainer_info->fence_commission; + housecontainer_info->allowed_types = old_item->housecontainer_info->allowed_types; + housecontainer_info->num_slots = old_item->housecontainer_info->num_slots; + } + break; + } + } + creator = old_item->creator; + adornment = old_item->adornment; + DeleteItemSets(); + for (int32 i = 0; iitem_sets.size(); i++){ + ItemSet* set = old_item->item_sets[i]; + if (set){ + ItemSet* set2 = new ItemSet; + set2->item_id = set->item_id; + set2->item_crc = set->item_crc; + set2->item_icon = set->item_icon; + set2->item_stack_size = set->item_stack_size; + set2->item_list_color = set->item_list_color; + item_sets.push_back(set2); + } + } + item_stats.clear(); + for(int32 i=0;iitem_stats.size();i++){ + ItemStat* stat = old_item->item_stats[i]; + if(stat){ + ItemStat* stat2 = new ItemStat; + stat2->stat_name = stat->stat_name; + stat2->stat_type = stat->stat_type; + stat2->stat_subtype = stat->stat_subtype; + stat2->value = stat->value; + stat2->stat_type_combined = stat->stat_type_combined; + item_stats.push_back(stat2); + } + } + item_string_stats.clear(); + for(int32 i=0;iitem_string_stats.size();i++){ + ItemStatString* stat = old_item->item_string_stats[i]; + if(stat){ + ItemStatString* stat2 = new ItemStatString; + stat2->stat_string.data = stat->stat_string.data; + stat2->stat_string.size = stat->stat_string.size; + item_string_stats.push_back(stat2); + } + } + item_level_overrides.clear(); + for(int32 i=0;iitem_level_overrides.size();i++){ + ItemLevelOverride* item_override = old_item->item_level_overrides[i]; + if(item_override){ + ItemLevelOverride* item_override2 = new ItemLevelOverride; + memcpy(item_override2, item_override, sizeof(ItemLevelOverride)); + item_level_overrides.push_back(item_override2); + } + } + item_effects.clear(); + for(int32 i=0;iitem_effects.size();i++){ + ItemEffect* effect = old_item->item_effects[i]; + if(effect){ + ItemEffect* effect_2 = new ItemEffect; + effect_2->effect = effect->effect; + effect_2->percentage = effect->percentage; + effect_2->subbulletflag = effect->subbulletflag; + item_effects.push_back(effect_2); + } + } + book_pages.clear(); + for (int32 i = 0; i < old_item->book_pages.size(); i++) { + BookPage* bookpage = old_item->book_pages[i]; + if (bookpage) { + BookPage* bookpage_2 = new BookPage; + bookpage_2->page = bookpage->page; + bookpage_2->page_text.data = bookpage->page_text.data; + bookpage_2->page_text.size = bookpage->page_text.size; + bookpage_2->valign = bookpage->valign; + bookpage_2->halign = bookpage->halign; + + + + + book_pages.push_back(bookpage_2); + } + } + slot_data.clear(); + slot_data = old_item->slot_data; + spell_id = old_item->spell_id; + spell_tier = old_item->spell_tier; + book_language = old_item->book_language; +} + +bool Item::CheckArchetypeAdvSubclass(int8 adventure_class, map* adv_class_levels) { + if (adventure_class > FIGHTER && adventure_class < ANIMALIST) { + int8 check = adventure_class % 10; + if (check == 2 || check == 5 || check == 8) { + int64 adv_classes = 0; + int16 level = 0; + for (int i = adventure_class + 1; i < adventure_class + 3; i++) { + if (adv_class_levels) { //need to match levels + if (level == 0) { + if (adv_class_levels->count(i) > 0) + level = adv_class_levels->at(i); + else + return false; + } + else{ + if (adv_class_levels->count(i) > 0 && adv_class_levels->at(i) != level) + return false; + } + } + else { + adv_classes = ((int64)2) << (i - 1); + if (!(generic_info.adventure_classes & adv_classes)) + return false; + } + } + return true; + } + } + return false; +} + +bool Item::CheckArchetypeAdvClass(int8 adventure_class, map* adv_class_levels) { + if (adventure_class == 1 || adventure_class == 11 || adventure_class == 21 || adventure_class == 31) { + //if the class is an archetype class and the subclasses have access, then allow + if (CheckArchetypeAdvSubclass(adventure_class + 1, adv_class_levels) && CheckArchetypeAdvSubclass(adventure_class + 4, adv_class_levels) && CheckArchetypeAdvSubclass(adventure_class + 7, adv_class_levels)) { + if (adv_class_levels) { + int16 level = 0; + for (int i = adventure_class + 1; i <= adventure_class + 7; i += 3) { + if (adv_class_levels->count(i+1) == 0 || adv_class_levels->count(i + 2) == 0) + return false; + if(level == 0) + level = adv_class_levels->at(i+1); + if (adv_class_levels->at(i+1) != level) //already verified the classes, just need to verify the subclasses have the same levels + return false; + } + + } + return true; + } + } + else if (CheckArchetypeAdvSubclass(adventure_class, adv_class_levels)) {//check archetype subclass + return true; + } + return false; +} + +bool Item::CheckClass(int8 adventure_class, int8 tradeskill_class) { + int64 adv_classes = ((int64)2) << (adventure_class - 1); + int64 ts_classes = ((int64)2) << (tradeskill_class - 1); + if( ((generic_info.adventure_classes & adv_classes) || generic_info.adventure_classes == 0) && ((generic_info.tradeskill_classes & ts_classes) || generic_info.tradeskill_classes == 0) ) + return true; + //check arechtype classes as last resort + return CheckArchetypeAdvClass(adventure_class); +} + +bool Item::CheckLevel(int8 adventure_class, int8 tradeskill_class, int16 level) { + if ((level >= generic_info.adventure_default_level && adventure_class < 255) && (level >= generic_info.tradeskill_default_level && tradeskill_class < 255)) + return true; + return false; +} + +void Item::AddStat(ItemStat* in_stat){ + item_stats.push_back(in_stat); +} + +bool Item::HasStat(uint32 statID, std::string statNameLower) +{ + vector::iterator itr; + for (itr = item_stats.begin(); itr != item_stats.end(); itr++) { + if (statID > 99 && statID < 200 && + (*itr)->stat_type == 1 && ::ToLower((*itr)->stat_name) == statNameLower) { + return true; + break; + } + else if((*itr)->stat_type_combined == statID && (statNameLower.length() < 1 || + (::ToLower((*itr)->stat_name) == statNameLower))) { + return true; + break; + } + } + + return false; +} + +void Item::DeleteItemSets() +{ + for (int32 i = 0; i < item_sets.size(); i++){ + ItemSet* set = item_sets[i]; + safe_delete(set); + } + + item_sets.clear(); +} + +void Item::AddSet(ItemSet* in_set){ + item_sets.push_back(in_set); +} +void Item::AddStatString(ItemStatString* in_stat){ + item_string_stats.push_back(in_stat); +} + +bool Item::IsNormal(){ + return generic_info.item_type == ITEM_TYPE_NORMAL; +} + +bool Item::IsWeapon(){ + return generic_info.item_type == ITEM_TYPE_WEAPON; +} + +bool Item::IsDualWieldAble(Client* client, Item* item, int8 slot) { + + if (!item || !client || slot < 0) { + LogWrite(ITEM__DEBUG, 0, "Items", "Error in IsDualWieldAble. No Item, Client, or slot Passed"); + return 0; + } + + Player* player = client->GetPlayer(); + int8 base_class = classes.GetBaseClass(player->GetAdventureClass()); + + //map out classes that can dw vs those that cant (did it this way so its easier to expand should we need to add classes later + int8 can_dw; + switch ((int)base_class) { + case 1: + can_dw = 1; + break; + case 5: + can_dw = 1; + break; + case 31: + can_dw = 1; + break; + case 35: + can_dw = 1; + break; + case 41: + can_dw = 1; + break; + + default : + can_dw = 0; + } + + //if mage, item is dw, and they are trying to put offhand. Not sure this will ever happen but figured I should cover it. + if (base_class == 21 && item->weapon_info->wield_type == ITEM_WIELD_TYPE_DUAL && slot == 1) { + return 0; + } + + //if the item is main hand (single) and they are trying to put in in offhand. + //exceptions are classes 1, 5, 31, 35, 42 (fighter/brawler/rogue/bard/beastlord) + if (item->weapon_info->wield_type == ITEM_WIELD_TYPE_SINGLE && slot == 1 && can_dw != 1) { + return 0; + } +//assume its safe if the above 2 if's arent hit. +return 1; +} + +bool Item::IsArmor(){ + return generic_info.item_type == ITEM_TYPE_ARMOR || generic_info.item_type == ITEM_TYPE_SHIELD; +} + +bool Item::IsRanged(){ + return generic_info.item_type == ITEM_TYPE_RANGED; +} + +bool Item::IsBag(){ + return generic_info.item_type == ITEM_TYPE_BAG; +} + +bool Item::IsFood(){ + return generic_info.item_type == ITEM_TYPE_FOOD; +} + +bool Item::IsBauble(){ + return generic_info.item_type == ITEM_TYPE_BAUBLE; +} + +bool Item::IsSkill(){ + return generic_info.item_type == ITEM_TYPE_SKILL; +} + +bool Item::IsHouseItem(){ + return generic_info.item_type == ITEM_TYPE_HOUSE; +} + +bool Item::IsHouseContainer(){ + return generic_info.item_type == ITEM_TYPE_HOUSE_CONTAINER; +} + +bool Item::IsShield(){ + return generic_info.item_type == ITEM_TYPE_SHIELD; +} + +bool Item::IsAdornment(){ + return generic_info.item_type == ITEM_TYPE_ADORNMENT && !CheckFlag2(ORNATE); +} + +bool Item::IsAmmo(){ + return HasSlot(EQ2_AMMO_SLOT); +} + +bool Item::HasAdorn0(){ + if (adorn0 > 0) + return true; + + return false; +} + +bool Item::HasAdorn1(){ + if (adorn1 > 0) + return true; + + return false; +} + +bool Item::HasAdorn2(){ + if (adorn2 > 0) + return true; + + return false; +} + + + + +bool Item::IsBook(){ + return generic_info.item_type == ITEM_TYPE_BOOK; +} + +bool Item::IsChainArmor(){ + return generic_info.item_type == ITEM_TYPE_ARMOR && (generic_info.skill_req1 == 2246237129UL || generic_info.skill_req2 == 2246237129UL); +} + +bool Item::IsClothArmor(){ + return generic_info.item_type == ITEM_TYPE_ARMOR && (generic_info.skill_req1 == 3539032716UL || generic_info.skill_req2 == 3539032716UL); +} + +bool Item::IsCollectable(){ + return generic_info.collectable == 1; +} + +bool Item::HasSlot(int8 slot, int8 slot2){ + for(int32 i=0;itype == 1; +} + +bool Item::IsFoodDrink(){ + return generic_info.item_type == ITEM_TYPE_FOOD && food_info && food_info->type == 0; +} + +bool Item::IsJewelry(){ + if(generic_info.item_type != ITEM_TYPE_ARMOR || (generic_info.skill_req1 != 2072844078 && generic_info.skill_req2 != 2072844078)) + return false; + for(int32 i=0;itinkered. +/* +bool Item::IsTinkered(){ + LogWrite(MISC__TODO, 1, "TODO", "Item Is Tinkered\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__); + return false; +} +*/ + +bool Item::IsThrown(){ + return generic_info.item_type == ITEM_TYPE_THROWN; +} + +bool Item::IsHarvest() { + return generic_info.harvest == 1; +} + +bool Item::IsBodyDrop() { + return generic_info.body_drop == 1; +} +//item->crafted +/*bool Item::IsTradeskill(){ + LogWrite(MISC__TODO, 1, "TODO", "Item Is Crafted\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__); + return false; +}*/ + +void Item::SetItemType(int8 in_type){ + generic_info.item_type = in_type; + if(IsArmor() && !armor_info){ + armor_info = new Armor_Info; + memset(armor_info, 0, sizeof(Armor_Info)); + } + else if (IsWeapon() && !weapon_info){ + weapon_info = new Weapon_Info; + memset(weapon_info, 0, sizeof(Weapon_Info)); + } + else if (IsAdornment() && !adornment_info){ + adornment_info = new Adornment_Info; + memset(adornment_info, 0, sizeof(Adornment_Info)); + } + else if(IsRanged() && !ranged_info){ + ranged_info = new Ranged_Info; + memset(ranged_info, 0, sizeof(Ranged_Info)); + } + else if(IsBag() && !bag_info){ + bag_info = new Bag_Info; + memset(bag_info, 0, sizeof(Bag_Info)); + } + else if(IsFood() && !food_info){ + food_info = new Food_Info; + memset(food_info, 0, sizeof(Food_Info)); + } + else if(IsBauble() && !bauble_info){ + bauble_info = new Bauble_Info; + memset(bauble_info, 0, sizeof(Bauble_Info)); + } + else if(IsThrown() && !thrown_info){ + thrown_info = new Thrown_Info; + memset(thrown_info, 0, sizeof(Thrown_Info)); + } + else if(IsSkill() && !skill_info){ + skill_info = new Skill_Info; + memset(skill_info, 0, sizeof(Skill_Info)); + } + else if(IsRecipeBook() && !recipebook_info){ + recipebook_info = new RecipeBook_Info; + recipebook_info->recipe_id = 0; + recipebook_info->uses = 0; + } + else if(IsBook() && !book_info){ + book_info = new Book_Info; + book_info->language = 0; + book_info->author.size = 0; + book_info->title.size = 0; + } + else if(IsHouseItem() && !houseitem_info){ + houseitem_info = new HouseItem_Info; + memset(houseitem_info, 0, sizeof(HouseItem_Info)); + } + else if(IsHouseContainer() && !housecontainer_info){ + housecontainer_info = new HouseContainer_Info; + housecontainer_info->allowed_types = 0; + housecontainer_info->broker_commission = 0; + housecontainer_info->fence_commission = 0; + housecontainer_info->num_slots = 0; + } +} +bool Item::CheckFlag2(int32 flag){ + int32 value = 0; + int32 flag_val = generic_info.item_flags2; + while (flag_val > 0){ + if (flag_val >= FLAGS2_32768) + value = FLAGS2_32768; + else if (flag_val >= FREE_REFORGE) + value = FREE_REFORGE; + else if (flag_val >= BUILDING_BLOCK) + value = BUILDING_BLOCK; + else if (flag_val >= FLAGS2_4096) + value = FLAGS2_4096; + else if (flag_val >= HOUSE_LORE) + value = HOUSE_LORE; + else if (flag_val >= NO_EXPERIMENT) + value = NO_EXPERIMENT; + else if (flag_val >= INDESTRUCTABLE) + value = INDESTRUCTABLE; + else if (flag_val >= NO_SALVAGE) + value = NO_SALVAGE; + else if (flag_val >= REFINED) + value = REFINED; + else if (flag_val >= ETHERAL) + value = ETHERAL; + else if (flag_val >= NO_REPAIR) + value = NO_REPAIR; + else if (flag_val >= REFORGED) + value = REFORGED; + else if (flag_val >= UNLOCKED) + value = UNLOCKED; + else if (flag_val >= APPEARANCE_ONLY) + value = APPEARANCE_ONLY; + else if (flag_val >= HEIRLOOM) + value = HEIRLOOM; + else if (flag_val >= ORNATE) + value = ORNATE; + if (value == flag) + return true; + else + flag_val -= value; + } + + return false; +} + +bool Item::CheckFlag(int32 flag){ + int32 value = 0; + int32 flag_val = generic_info.item_flags; + while(flag_val>0){ + if (flag_val >= CURSED) //change this + value = CURSED; + else if (flag_val >= NO_TRANSMUTE) //change this + value = NO_TRANSMUTE; + else if (flag_val >= LORE_EQUIP) //change this + value = LORE_EQUIP; + else if (flag_val >= STACK_LORE) //change this + value = STACK_LORE; + else if(flag_val >= EVIL_ONLY) + value = EVIL_ONLY; + else if(flag_val >= GOOD_ONLY) + value = GOOD_ONLY; + else if(flag_val >= CRAFTED) + value = CRAFTED; + else if(flag_val >= NO_DESTROY) + value = NO_DESTROY; + else if(flag_val >= NO_ZONE) + value = NO_ZONE; + else if(flag_val >= NO_VALUE) + value = NO_VALUE; + else if(flag_val >= NO_TRADE) + value = NO_TRADE; + else if(flag_val >= TEMPORARY) + value = TEMPORARY; + else if(flag_val >= LORE) + value = LORE; + else if(flag_val >= ARTIFACT) + value = ARTIFACT; + else if(flag_val >= ATTUNEABLE) + value = ATTUNEABLE; + else if(flag_val >= ATTUNED) + value = ATTUNED; + if(value == flag) + return true; + else + flag_val -= value; + } + return false; +} + +void Item::SetSlots(int32 slots){ + if(slots & PRIMARY_SLOT) + AddSlot(EQ2_PRIMARY_SLOT); + if(slots & SECONDARY_SLOT) + AddSlot(EQ2_SECONDARY_SLOT); + if(slots & HEAD_SLOT) + AddSlot(EQ2_HEAD_SLOT); + if(slots & CHEST_SLOT) + AddSlot(EQ2_CHEST_SLOT); + if(slots & SHOULDERS_SLOT) + AddSlot(EQ2_SHOULDERS_SLOT); + if(slots & FOREARMS_SLOT) + AddSlot(EQ2_FOREARMS_SLOT); + if(slots & HANDS_SLOT) + AddSlot(EQ2_HANDS_SLOT); + if(slots & LEGS_SLOT) + AddSlot(EQ2_LEGS_SLOT); + if(slots & FEET_SLOT) + AddSlot(EQ2_FEET_SLOT); + if(slots & LRING_SLOT) + AddSlot(EQ2_LRING_SLOT); + if(slots & RRING_SLOT) + AddSlot(EQ2_RRING_SLOT); + if(slots & EARS_SLOT_1) + AddSlot(EQ2_EARS_SLOT_1); + if(slots & EARS_SLOT_2) + AddSlot(EQ2_EARS_SLOT_2); + if(slots & NECK_SLOT) + AddSlot(EQ2_NECK_SLOT); + if(slots & LWRIST_SLOT) + AddSlot(EQ2_LWRIST_SLOT); + if(slots & RWRIST_SLOT) + AddSlot(EQ2_RWRIST_SLOT); + if(slots & RANGE_SLOT) + AddSlot(EQ2_RANGE_SLOT); + if(slots & AMMO_SLOT) + AddSlot(EQ2_AMMO_SLOT); + if(slots & WAIST_SLOT) + AddSlot(EQ2_WAIST_SLOT); + if(slots & CLOAK_SLOT) + AddSlot(EQ2_CLOAK_SLOT); + if(slots & CHARM_SLOT_1) + AddSlot(EQ2_CHARM_SLOT_1); + if(slots & CHARM_SLOT_2) + AddSlot(EQ2_CHARM_SLOT_2); + if(slots & FOOD_SLOT) + AddSlot(EQ2_FOOD_SLOT); + if(slots & DRINK_SLOT) + AddSlot(EQ2_DRINK_SLOT); + if(slots & TEXTURES_SLOT) + AddSlot(EQ2_TEXTURES_SLOT); +} + +void Item::AddStat(int8 type, int16 subtype, float value, int8 level, char* name){ + char item_stat_combined_string[8] = {0}; + if(name && strlen(name) > 0 && type != 1){ + ItemStatString* stat = new ItemStatString; + stat->stat_string.data = string(name); + stat->stat_string.size = stat->stat_string.data.length(); + AddStatString(stat); + } + else{ + ItemStat* stat = new ItemStat; + if(name && strlen(name) > 0) + stat->stat_name = string(name); + stat->stat_type = type; + stat->stat_subtype = subtype; + stat->value = value; + stat->level = level; + snprintf(item_stat_combined_string, 7, "%u%02u", type, subtype); + stat->stat_type_combined = atoi(item_stat_combined_string); + AddStat(stat); + } +} +void Item::AddSet(int32 item_id, int32 item_crc, int16 item_icon, int32 item_stack_size, int32 item_list_color, std::string name, int8 language){ + ItemSet* set = new ItemSet; + set->item_id = item_id; + set->item_icon = item_icon; + set->item_crc = item_crc; + set->item_stack_size = item_stack_size; + set->item_list_color = item_list_color; + set->name = string(name); + set->language = language; + + AddSet(set); +} + +int16 Item::GetOverrideLevel(int8 adventure_class, int8 tradeskill_class){ + int16 ret = 0; + int8 tmp_class = 0; + bool found_class = false; + for(int32 i=0;iadventure_class; + if(tmp_class == PRIEST && (adventure_class >= CLERIC && adventure_class <= DEFILER)) + found_class = true; + else if(tmp_class == MAGE && (adventure_class >= SORCERER && adventure_class <= NECROMANCER)) + found_class = true; + else if(tmp_class == SCOUT && (adventure_class >= ROGUE && adventure_class <= ASSASSIN)) + found_class = true; + else if(tmp_class == adventure_class || tmp_class == COMMONER || (tmp_class == FIGHTER && (adventure_class >= WARRIOR && adventure_class <= PALADIN))) + found_class = true; + } + else if(tradeskill_class != 255){ + tmp_class = item_level_overrides[i]->tradeskill_class; + if(tmp_class == CRAFTSMAN && (tradeskill_class >= PROVISIONER && adventure_class <= CARPENTER)) + found_class = true; + else if(tmp_class == OUTFITTER && (tradeskill_class >= ARMORER && tradeskill_class <= TAILOR)) + found_class = true; + else if(tmp_class == SCHOLAR && (tradeskill_class >= JEWELER && tradeskill_class <= ALCHEMIST)) + found_class = true; + else if(tmp_class == tradeskill_class || tmp_class == ARTISAN) + found_class = true; + } + if(found_class){ + ret = item_level_overrides[i]->level; + break; + } + } + return ret; +} + +void Item::serialize(PacketStruct* packet, bool show_name, Player* player, int16 packet_type, int8 subtype, bool loot_item, bool inspect){ + int64 classes = 0; + Client *client; + int8 tmp_subtype = 0; + if (!packet || !player) + return; + client = ((Player*)player)->GetClient(); + if (!client) + return; + if(creator.length() > 0){ + packet->setSubstructSubstructDataByName("header", "info_header", "creator_flag", 1); + packet->setSubstructSubstructDataByName("header", "info_header", "creator", creator.c_str()); + } + if(show_name) + packet->setSubstructSubstructDataByName("header", "info_header", "show_name", show_name); + + if(packet_type == 0) + packet->setSubstructSubstructDataByName("header", "info_header", "packettype", GetItemPacketType(packet->GetVersion())); + else + packet->setSubstructSubstructDataByName("header", "info_header", "packettype", packet_type); + packet->setSubstructSubstructDataByName("header", "info_header", "packetsubtype", subtype); // should be substype + + /* +0 red +1 orange +2 yellow +3 white +4 blue +5 green +6 grey +7 purple*/ + int32 color = 3; + + if(player) + { + int32 effective_level = player->GetInfoStructUInt("effective_level"); + if(effective_level && effective_level < player->GetLevel() && details.recommended_level > effective_level) + color = 7; + } + + packet->setSubstructDataByName("header_info", "footer_type", color); + packet->setSubstructDataByName("header_info", "item_id", details.item_id); + + if (!loot_item) + packet->setSubstructDataByName("header_info", "broker_item_id", details.item_id); + else + packet->setSubstructDataByName("header_info", "broker_item_id", 0xFFFFFFFFFFFFFFFF); + + if(details.unique_id == 0) + packet->setSubstructDataByName("header_info", "unique_id", details.item_id); + else + packet->setSubstructDataByName("header_info", "unique_id", details.unique_id); + packet->setSubstructDataByName("header_info", "icon", GetIcon(packet->GetVersion())); + + if(rule_manager.GetGlobalRule(R_World, DisplayItemTiers)->GetBool()) { + packet->setSubstructDataByName("header_info", "tier", details.tier); + } + packet->setSubstructDataByName("header_info", "flags", generic_info.item_flags); + packet->setSubstructDataByName("header_info", "flags2", generic_info.item_flags2); + if(item_stats.size() > 0){ + //packet->setSubstructArrayLengthByName("header_info", "stat_count", item_stats.size()); + int8 dropstat = 0; + int8 bluemod = 0; + for (int32 i = 0; i < item_stats.size(); i++){ + ItemStat* stat = item_stats[i]; + + if(!stat) + { + LogWrite(ITEM__ERROR, 0, "Item", "%s: %s (itemid: %u) Error Serializing Item: Invalid item in item_stats position %u", client->GetPlayer()->GetName(), this->name.c_str(), this->details.item_id, i); + continue; + } + + if (stat->stat_type == 9){ + bluemod += 1; + } + + tmp_subtype = world.TranslateSlotSubTypeToClient(client, stat->stat_type, stat->stat_subtype); + + if (tmp_subtype == 255 ){ + dropstat += 1; + } + + } + packet->setSubstructArrayLengthByName("header_info", "stat_count", item_stats.size() - dropstat); + dropstat = 0; + for (int32 i = 0; i < item_stats.size(); i++) { + ItemStat* stat = item_stats[i]; + tmp_subtype = world.TranslateSlotSubTypeToClient(client, stat->stat_type, stat->stat_subtype); + int16 stat_type = stat->stat_type; + + float statValue = stat->value; + if(player) + { + int32 effective_level = player->GetInfoStructUInt("effective_level"); + if(effective_level && effective_level < player->GetLevel() && details.recommended_level > effective_level) + { + int32 diff = details.recommended_level - effective_level; + float tmpValue = (float)statValue; + statValue = (sint32)(float)(tmpValue / (1.0f + ((float)diff * rule_manager.GetGlobalRule(R_Player, MentorItemDecayRate)->GetFloat()))); + } + } + + bool valueSet = false; + if (tmp_subtype == 255 ){ + + dropstat += 1; + //packet->setSubstructArrayLengthByName("header_info", "stat_count", item_stats.size()-dropstat); + } + else { + packet->setArrayDataByName("stat_type", stat_type, i-dropstat); + + if(client->GetVersion() <= 561 && stat_type == 5) { + valueSet = true; + // DoF client has to be goofy about this junk, stat_subtype is the stat value, value is always "9" and we set the stat_name to the appropriate stat (but power=mana) + packet->setArrayDataByName("stat_subtype", (sint16)statValue , i - dropstat); + packet->setArrayDataByName("value", (sint16)9 , i - dropstat); + switch(tmp_subtype) { + case 0: { + packet->setArrayDataByName("stat_name", "health", i - dropstat); + break; + } + case 1: { + packet->setArrayDataByName("stat_name", "mana", i - dropstat); + break; + } + case 2: { + packet->setArrayDataByName("stat_name", "concentration", i - dropstat); + break; + } + } + } + else { + packet->setArrayDataByName("stat_subtype", tmp_subtype, i-dropstat); + } + } + if (stat->stat_name.length() > 0) + packet->setArrayDataByName("stat_name", stat->stat_name.c_str(), i-dropstat); + /* SF client */ + + if(!valueSet) { + if ((client->GetVersion() >= 63119) || client->GetVersion() == 61331) { + if (stat->stat_type == 6){ + packet->setArrayDataByName("value", statValue , i - dropstat);//63119 or when diety started (this is actually the modified stat + packet->setArrayDataByName("value2", stat->value, i - dropstat);//63119 temp will be replace by modified value (this is the unmodified stat + } + else { + packet->setArrayDataByName("value", (sint16)statValue , i - dropstat, 0U, true); + packet->setArrayDataByName("value2", stat->value, i - dropstat);//63119 temp will be replace by modified value + } + } + else if (client->GetVersion() >= 1028) { + if (stat->stat_type == 6){ + packet->setArrayDataByName("value", statValue , i - dropstat);//63119 or when diety started (this is actually the infused modified stat + packet->setArrayDataByName("value2", stat->value, i - dropstat);//63119 temp will be replace by modified value (this is the unmodified stat + } + else { + packet->setArrayDataByName("value", (sint16)statValue , i - dropstat, 0U, true); + packet->setArrayDataByName("value2", stat->value, i - dropstat);//63119 temp will be replace by modified value + } + + } + else{ + packet->setArrayDataByName("value", (sint16)statValue , i - dropstat); + packet->setArrayDataByName("value2", stat->value, i - dropstat);//63119 temp will be replace by modified value + } + } + } + } + if (item_string_stats.size() > 0 && !loot_item){ + if ((client->GetVersion() >= 63119) || client->GetVersion() == 61331) { + packet->setSubstructArrayLengthByName("header_info", "mod_count", item_string_stats.size()); + for (int32 i = 0; i < item_string_stats.size(); i++){ + ItemStatString* stat = item_string_stats[i]; + packet->setArrayDataByName("mod_string", &(stat->stat_string), i); + packet->setArrayDataByName("mod_need", 0, i); + } + } + + else if (client->GetVersion() >= 1096) { + packet->setSubstructArrayLengthByName("header_info", "stat_string_count", item_string_stats.size()); + for (int32 i = 0; i < item_string_stats.size(); i++){ + ItemStatString* stat = item_string_stats[i]; + packet->setArrayDataByName("stat_string", &(stat->stat_string), i); + + } + } + } + if (item_sets.size() > 0){ + packet->setArrayLengthByName("num_pieces", item_sets.size()); + for (int32 i = 0; i < item_sets.size(); i++){ + ItemSet* set = item_sets[i]; + packet->setArrayDataByName("item_id", set->item_id, i); + packet->setArrayDataByName("item_crc", set->item_crc, i); + packet->setArrayDataByName("item_icon", set->item_icon, i); + packet->setArrayDataByName("item_unknown1", set->item_stack_size, i); + + Item* item2 = master_item_list.GetItem(set->item_id); + if (item2) + packet->setArrayDataByName("item_name", item2->name.c_str(), i); + + packet->setArrayDataByName("item_unknown2", set->item_list_color, i); + + } + + + } + + + + + + if(!loot_item && item_effects.size() > 0){ + packet->setSubstructArrayLengthByName("footer", "num_effects", item_effects.size()); + for(int32 i=0;isetArrayDataByName("subbulletflag", effect->subbulletflag, i); + packet->setArrayDataByName("effect", &(effect->effect), i); + packet->setArrayDataByName("percentage", effect->percentage, i); + } + } + + if (packet->GetVersion() < 1096) { + packet->setSubstructDataByName("header_info", "adornment_id", 0xFFFFFFFF); // Send no ID for now + packet->setSubstructDataByName("header_info", "unknown3", 0xFFFFFFFF); + } + packet->setSubstructDataByName("header_info", "unknown21", 0x00000000); + packet->setSubstructDataByName("header_info", "condition", generic_info.condition); + packet->setSubstructDataByName("header_info", "weight", generic_info.weight); + if (packet->GetVersion() <= 373) { //orig client only has one skill + if (generic_info.skill_req1 == 0 || generic_info.skill_req1 == 0xFFFFFFFF) { + if (generic_info.skill_req2 != 0 && generic_info.skill_req2 != 0xFFFFFFFF) { + packet->setSubstructDataByName("header_info", "skill_req1", generic_info.skill_req2); + } + else { + packet->setSubstructDataByName("header_info", "skill_req1", 0xFFFFFFFF); + } + } + else { + packet->setSubstructDataByName("header_info", "skill_req1", generic_info.skill_req1); + } + } + else { + if (generic_info.skill_req1 == 0) + packet->setSubstructDataByName("header_info", "skill_req1", 0xFFFFFFFF); + else + packet->setSubstructDataByName("header_info", "skill_req1", generic_info.skill_req1); + if (generic_info.skill_req2 == 0) + packet->setSubstructDataByName("header_info", "skill_req2", 0xFFFFFFFF); + else + packet->setSubstructDataByName("header_info", "skill_req2", generic_info.skill_req2); + } + if(generic_info.skill_min != 0) + packet->setSubstructDataByName("header_info", "skill_min", generic_info.skill_min); + if (client->GetVersion() <= 373) { + string flags; + if (CheckFlag(NO_TRADE)) + flags += "NO-TRADE "; + if (CheckFlag(NO_VALUE)) + flags += "NO-VALUE "; + if(flags.length() > 0) + packet->setSubstructDataByName("header_info", "flag_names", flags.c_str()); + } + if (generic_info.adventure_classes > 0 || generic_info.tradeskill_classes > 0 || item_level_overrides.size() > 0) { + //int64 classes = 0; + int16 tmp_level = 0; + map adv_class_levels; + map tradeskill_class_levels; + map::iterator itr; + int64 tmpVal = 0; + int8 temp = ASSASSIN; + // AoD + clients with beastlords + if (packet->GetVersion() >= 1142) + temp += 2; + + // Chaneler class, get a more accurate version + if (packet->GetVersion() >= 60000) + temp += 2; + + for (int32 i = 0; i <= temp; i++) { + tmpVal = (int64)pow(2.0, (double)i); + if ((generic_info.adventure_classes & tmpVal)) { + //classes += 2 << (i - 1); + classes += tmpVal; + tmp_level = GetOverrideLevel(i, 255); + if (tmp_level == 0) + adv_class_levels[i] = generic_info.adventure_default_level; + else + adv_class_levels[i] = tmp_level; + } + if (tmpVal == 0) { + if (packet->GetVersion() >= 60000) + classes = 576379072454289112; + else if (packet->GetVersion() >= 57048) + classes = 6281081087704; + else if (packet->GetVersion() >= 1142) + classes = 144095080877876952; + else + classes = 36024082983773912; + } + } + for (int i = ALCHEMIST + 1 - ARTISAN; i >= 0; i--) { + //tmpVal = 2 << i; + tmpVal = (int64)pow(2.0, (double)i); + if ((generic_info.tradeskill_classes & tmpVal)) { + classes += pow(2, (i + ARTISAN)); + //classes += 2 << (i+ARTISAN-1); + tmp_level = GetOverrideLevel(i, 255); + if (tmp_level == 0) + tradeskill_class_levels[i] = generic_info.tradeskill_default_level; + else + tradeskill_class_levels[i] = tmp_level; + } + } + if (client->GetVersion() <= 561) { //simplify display (if possible) + map new_adv_class_levels; + for (int i = 1; i <= 31; i += 10) { + bool add_archetype = CheckArchetypeAdvClass(i, &adv_class_levels); + if (add_archetype) { + new_adv_class_levels[i] = 0; + } + else { + for (int x = 1; x <= 7; x += 3) { + if (CheckArchetypeAdvSubclass(i+x, &adv_class_levels)) { + new_adv_class_levels[i+x] = 0; + } + } + } + } + if (new_adv_class_levels.size() > 0) { + int8 i = 0; + for (itr = new_adv_class_levels.begin(); itr != new_adv_class_levels.end(); itr++) { + i = itr->first; + if ((i % 10) == 1) { + int16 level = 0; + for (int x = i; x < i+10; x++) { + if (adv_class_levels.count(x) > 0) { + if(level == 0) + level = adv_class_levels.at(x); + adv_class_levels.erase(x); + } + } + adv_class_levels[i] = level; + } + else { + int16 level = 0; + for (int x = i+1; x < i + 3; x++) { + if (adv_class_levels.count(x) > 0) { + if (level == 0) + level = adv_class_levels.at(x); + adv_class_levels.erase(x); + } + } + adv_class_levels[i] = level; + } + } + } + } + packet->setSubstructArrayLengthByName("header_info", "class_count", adv_class_levels.size() + tradeskill_class_levels.size()); + int i = 0; + for (itr = adv_class_levels.begin(); itr != adv_class_levels.end(); itr++, i++) { + packet->setArrayDataByName("adventure_class", itr->first, i); + packet->setArrayDataByName("tradeskill_class", 255, i); + packet->setArrayDataByName("level", itr->second * 10, i); + } + for (itr = tradeskill_class_levels.begin(); itr != tradeskill_class_levels.end(); itr++, i++) { + packet->setArrayDataByName("adventure_class", 255, i); + packet->setArrayDataByName("tradeskill_class", itr->first, i); + packet->setArrayDataByName("level", itr->second * 10, i); + } + packet->setSubstructDataByName("footer", "required_classes", classes); + } + else { + if (packet->GetVersion() >= 60000) + classes = 576379072454289112; + else if (packet->GetVersion() >= 57048) + classes = 6281081087704; + else if (packet->GetVersion() >= 1142) + classes = 144095080877876952; + else + classes = 36024082983773912; + packet->setSubstructDataByName("footer", "required_classes", classes); + } + + // Is this a copy and paste error??? + + + packet->setSubstructDataByName("footer", "required_classes2", classes); + + { + if (packet->GetVersion() >= 60000) + classes = 576379072454289112; + else if (packet->GetVersion() >= 57048) + classes = 6281081087704; + else if (packet->GetVersion() >= 1142) + classes = 144095080877876952; + else + classes = 36024082983773912; + + } + if (client->GetVersion() <= 373 && generic_info.adventure_default_level > 0) { + packet->setSubstructDataByName("header_info", "skill_min", (generic_info.adventure_default_level-1)*5+1); + packet->setSubstructDataByName("header_info", "skill_recommended", details.recommended_level * 5); + } + packet->setSubstructDataByName("footer", "recommended_level", details.recommended_level); + if(generic_info.adventure_default_level > 0){ + packet->setSubstructDataByName("footer", "required_level", generic_info.adventure_default_level); + packet->setSubstructDataByName("footer", "footer_unknown2", 0);// remove defualt + } + else{ + packet->setSubstructDataByName("footer", "required_level", generic_info.tradeskill_default_level * 10); + packet->setSubstructDataByName("footer", "footer_unknown2", 0);//remove default + } + if(slot_data.size() > 0){ + packet->setSubstructArrayLengthByName("header_info", "slot_count", slot_data.size()); + for(int32 i=0;iGetVersion() <= 373) { + if (slot > EQ2_EARS_SLOT_1 && slot <= EQ2_WAIST_SLOT) //they added a second ear slot later, adjust for only 1 original slot + slot -= 1; + else if (slot == EQ2_FOOD_SLOT) + slot = EQ2_ORIG_FOOD_SLOT; + else if(slot == EQ2_DRINK_SLOT) + slot = EQ2_ORIG_DRINK_SLOT; + } + else if (client->GetVersion() <= 561) { + if (slot > EQ2_EARS_SLOT_1 && slot <= EQ2_WAIST_SLOT) //they added a second ear slot later, adjust for only 1 original slot + slot -= 1; + else if (slot == EQ2_FOOD_SLOT) + slot = EQ2_DOF_FOOD_SLOT; + else if (slot == EQ2_DRINK_SLOT) + slot = EQ2_DOF_DRINK_SLOT; + } + packet->setArrayDataByName("slot", slot, i); + } + } + if(!loot_item && !inspect){ + if (adornment_info) + LogWrite(ITEM__DEBUG, 0, "Items", "\ttype: %i, Duration: %i, item_types_: %i, slot_type: %i", generic_info.item_type, adornment_info->duration, adornment_info->item_types, adornment_info->slot_type); + + int8 tmpType = generic_info.item_type; + if (client->GetVersion() <= 373 && generic_info.item_type > ITEM_TYPE_RECIPE) + tmpType = 0; + else if(client->GetVersion() <= 561 && (generic_info.item_type > ITEM_TYPE_HOUSE || generic_info.item_type == ITEM_TYPE_BAUBLE)) + tmpType = 0; + + packet->setSubstructDataByName("header", "item_type", tmpType); + switch(generic_info.item_type){ + case ITEM_TYPE_WEAPON:{ + if(weapon_info){ + if (client->GetVersion() < 373) { + packet->setSubstructDataByName("details", "wield_type", weapon_info->wield_type); + packet->setSubstructDataByName("details", "damage_low1", weapon_info->damage_low1); + packet->setSubstructDataByName("details", "damage_high1", weapon_info->damage_high1); + packet->setSubstructDataByName("details", "damage_low2", weapon_info->damage_low2); + packet->setSubstructDataByName("details", "damage_high2", weapon_info->damage_high2); + packet->setSubstructDataByName("details", "damage_type", weapon_type); + packet->setSubstructDataByName("details", "delay", weapon_info->delay); + } + else { + packet->setDataByName("wield_type", weapon_info->wield_type); + packet->setDataByName("damage_low1", weapon_info->damage_low1); + packet->setDataByName("damage_high1", weapon_info->damage_high1); + packet->setDataByName("damage_low2", weapon_info->damage_low2); + packet->setDataByName("damage_high2", weapon_info->damage_high2); + packet->setDataByName("damage_low3", weapon_info->damage_low3); + packet->setDataByName("damage_high3", weapon_info->damage_high3); + packet->setDataByName("damage_type", weapon_type); + packet->setDataByName("delay", weapon_info->delay); + packet->setDataByName("rating", weapon_info->rating); + } + } + break; + } + case ITEM_TYPE_RANGED:{ + if(ranged_info){ + if (client->GetVersion() < 373) { + packet->setSubstructDataByName("details", "damage_low1", ranged_info->weapon_info.damage_low1); + packet->setSubstructDataByName("details", "damage_high1", ranged_info->weapon_info.damage_high1); + packet->setSubstructDataByName("details", "damage_low2", ranged_info->weapon_info.damage_low2); + packet->setSubstructDataByName("details", "damage_high2", ranged_info->weapon_info.damage_high2); + packet->setSubstructDataByName("details", "delay", ranged_info->weapon_info.delay); + packet->setSubstructDataByName("details", "range_low", ranged_info->range_low); + packet->setSubstructDataByName("details", "range_high", ranged_info->range_high); + } + else { + packet->setDataByName("damage_low1", ranged_info->weapon_info.damage_low1); + packet->setDataByName("damage_high1", ranged_info->weapon_info.damage_high1); + packet->setDataByName("damage_low2", ranged_info->weapon_info.damage_low2); + packet->setDataByName("damage_high2", ranged_info->weapon_info.damage_high2); + packet->setDataByName("damage_low3", ranged_info->weapon_info.damage_low3); + packet->setDataByName("damage_high3", ranged_info->weapon_info.damage_high3); + packet->setDataByName("delay", ranged_info->weapon_info.delay); + packet->setDataByName("range_low", ranged_info->range_low); + packet->setDataByName("range_high", ranged_info->range_high); + packet->setDataByName("rating", ranged_info->weapon_info.rating); + } + } + break; + } + case ITEM_TYPE_SHIELD: + case ITEM_TYPE_ARMOR:{ + if(armor_info){ + if (client->GetVersion() < 373) { + packet->setSubstructDataByName("details", "mitigation_low", armor_info->mitigation_low); + packet->setSubstructDataByName("details", "mitigation_high", armor_info->mitigation_high); + } + else { + packet->setDataByName("mitigation_low", armor_info->mitigation_low); + packet->setDataByName("mitigation_high", armor_info->mitigation_high); + } + } + break; + } + case ITEM_TYPE_BAG:{ + if(bag_info){ + + int8 max_slots = player->GetMaxBagSlots(client->GetVersion()); + if (bag_info->num_slots > max_slots) + bag_info->num_slots = max_slots; + + int16 free_slots = bag_info->num_slots; + if (player) { + Item* bag = player->GetPlayerItemList()->GetItemFromUniqueID(details.unique_id, true); + if (bag && bag->IsBag()) { + vector* bag_items = player->GetPlayerItemList()->GetItemsInBag(bag); + if (bag_items->size() > bag->bag_info->num_slots) { + free_slots = 0; + packet->setArrayLengthByName("num_names", bag->bag_info->num_slots); + } + else { + free_slots = bag->bag_info->num_slots - bag_items->size(); + packet->setArrayLengthByName("num_names", bag_items->size()); + } + vector::iterator itr; + int16 i = 0; + Item* tmp_bag_item = 0; + for (itr = bag_items->begin(); itr != bag_items->end(); itr++) { + tmp_bag_item = *itr; + if (tmp_bag_item && tmp_bag_item->details.slot_id < bag->bag_info->num_slots) { + packet->setArrayDataByName("item_name", tmp_bag_item->name.c_str(), i); + i++; + } + } + safe_delete(bag_items); + } + } + packet->setDataByName("num_slots", bag_info->num_slots); + packet->setDataByName("num_empty", free_slots); + packet->setDataByName("weight_reduction", bag_info->weight_reduction); + packet->setDataByName("item_score", 2); + //packet->setDataByName("unknown5", 0x1e50a86f); + //packet->setDataByName("unknown6", 0x2c17f61d); + //1 armorer + //2 weaponsmith + //4 tailor + //16 jeweler + //32 sage + //64 alchemist + //120 all scholars + //250 all craftsman + //int8 blah[] = {0x00,0x00,0x01,0x01,0xb6,0x01,0x01}; + //int8 blah[] = {0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + int8 blah[] = { 0xd8,0x66,0x9b,0x6d,0xb6,0xfb,0x7f }; + for (int8 i = 0; i < sizeof(blah); i++) + packet->setSubstructDataByName("footer", "footer_unknown_0", blah[i], 0, i); + } + break; + } + case ITEM_TYPE_FOOD:{ + if(food_info && client->GetVersion() >=374){ + packet->setDataByName("food_type", food_info->type); + packet->setDataByName("level", food_info->level); + packet->setDataByName("duration", food_info->duration); + } + break; + } + case ITEM_TYPE_SKILL:{ + //Spell Books + if(skill_info->spell_id > 0){ + Spell* spell = master_spell_list.GetSpell(skill_info->spell_id, skill_info->spell_tier); + if(spell){ + if(player && client->GetVersion() >= 374) { + packet->setSubstructDataByName("header_info", "footer_type", 0); + + spell->SetPacketInformation(packet, client); + if (player->HasSpell(skill_info->spell_id, skill_info->spell_tier, true)) { + packet->setDataByName("scribed", 1); + } + + if (packet->GetVersion() >= 927){ + if (player->HasSpell(skill_info->spell_id, skill_info->spell_tier, true)) { + packet->setAddToPacketByName("scribed_better_version", 1);// need to confirm + } + } + else + packet->setAddToPacketByName("scribed_better_version", 0); //if not scribed + + // either don't require previous tier or check that we have the lower tier spells potentially + int32 tier_up = player->GetTierUp(skill_info->spell_tier); + if (!rule_manager.GetGlobalRule(R_Spells, RequirePreviousTierScribe)->GetInt8() || player->HasSpell(skill_info->spell_id, tier_up, false, true)) + packet->setDataByName("require_previous", 1, 0); + // membership required + //packet->setDataByName("unknown_1188_2_MJ", 1, 1); + + } + else { + spell->SetPacketInformation(packet, client); + } + //packet->setDataByName("unknown26", 0); + } + } + break; + } + case ITEM_TYPE_BAUBLE:{ + if(bauble_info && client->GetVersion() >= 546){ + packet->setDataByName("cast", bauble_info->cast); + packet->setDataByName("recovery", bauble_info->recovery); + packet->setDataByName("duration", bauble_info->duration); + packet->setDataByName("recast", bauble_info->recast); + packet->setDataByName("display_slot_optional", bauble_info->display_slot_optional); + packet->setDataByName("display_cast_time", bauble_info->display_cast_time); + packet->setDataByName("display_bauble_type", bauble_info->display_bauble_type); + packet->setDataByName("effect_radius", bauble_info->effect_radius); + packet->setDataByName("max_aoe_targets", bauble_info->max_aoe_targets); + packet->setDataByName("display_until_cancelled", bauble_info->display_until_cancelled); + //packet->setDataByName("item_score", 1); + } + break; + } + case ITEM_TYPE_THROWN:{ + if(thrown_info && client->GetVersion() >= 374){ + packet->setDataByName("range", thrown_info->range); + packet->setDataByName("damage_modifier", thrown_info->damage_modifier); + packet->setDataByName("hit_bonus", thrown_info->hit_bonus); + packet->setDataByName("damage_type", thrown_info->damage_type); + } + break; + } + case ITEM_TYPE_HOUSE:{ + if(houseitem_info && client->GetVersion() >= 374){ + packet->setDataByName("status_rent_reduction", houseitem_info->status_rent_reduction); + packet->setDataByName("coin_rent_reduction", houseitem_info->coin_rent_reduction); + packet->setDataByName("house_only", houseitem_info->house_only); + } + break; + } + case ITEM_TYPE_BOOK:{ + if(book_info && client->GetVersion() >= 374){ + packet->setDataByName("language", book_info->language); + packet->setMediumStringByName("author", book_info->author.data.c_str()); + packet->setMediumStringByName("title", book_info->title.data.c_str()); + } + if (packet->GetVersion() <= 1096) packet->setDataByName("item_type", 13); + + break; + } + case ITEM_TYPE_RECIPE:{ + // Recipe Books + if(recipebook_info){ + packet->setArrayLengthByName("num_recipes", recipebook_info->recipes.size()); + for (int32 i = 0; i < recipebook_info->recipes.size(); i++) { + Recipe* recipe = master_recipe_list.GetRecipeByCRC(recipebook_info->recipes.at(i)); + if (recipe) { + packet->setArrayDataByName("recipe_name", recipe->GetName(), i); + packet->setArrayDataByName("recipe_id", recipe->GetID(), i); + packet->setArrayDataByName("recipe_icon", recipe->GetIcon(), i); + } + } + packet->setDataByName("uses", recipebook_info->uses); + if(player->GetRecipeBookList()->HasRecipeBook(recipebook_info->recipe_id)) + packet->setDataByName("scribed", 1); + else + packet->setDataByName("scribed", 0); + } + break; + } + case ITEM_TYPE_ADORNMENT:{ + //Adornements + if (client->GetVersion() >= 374) { + packet->setDataByName("item_types", adornment_info->item_types); + packet->setDataByName("duration", adornment_info->duration); // need to calcualte for remaining duration + packet->setDataByName("slot_type", adornment_info->slot_type); + packet->setDataByName("footer_set_name", "test footer set name"); + packet->setArrayLengthByName("footer_set_bonus_list_count", 1);// list of the bonus items + packet->setArrayDataByName("footer_set_bonus_items_needed", 2, 0); //this is nember of items needed for granteing that stat //name,value,array + packet->setSubArrayLengthByName("footer_set_bonus_stats_count", 2, 0);//name,value,array,subarray + packet->setSubArrayDataByName("set_stat_type", 5, 0, 0); + packet->setSubArrayDataByName("set_stat_subtype", 1, 0, 0); + packet->setSubArrayDataByName("set_value", 25000, 0, 0); + } + + } + case ITEM_TYPE_HOUSE_CONTAINER:{ + //House Containers + if(housecontainer_info && client->GetVersion() >= 374){ + packet->setDataByName("allowed_types", housecontainer_info->allowed_types); + packet->setDataByName("num_slots", housecontainer_info->num_slots); + packet->setDataByName("broker_commission", housecontainer_info->broker_commission); + packet->setDataByName("fence_commission", housecontainer_info->fence_commission); + } + } + } + } + + LogWrite(MISC__TODO, 1, "TODO", "Item Set information\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__); + if (IsBauble()) { + packet->setSubstructDataByName("footer", "stack_size", stack_count); + } + else { + packet->setSubstructDataByName("footer", "stack_size", stack_count); + } + packet->setSubstructDataByName("footer", "collectable", generic_info.collectable); + + + + + + packet->setSubstructDataByName("footer", "status_item", sell_status); + + + LogWrite(MISC__TODO, 1, "TODO", "Set collection_needed information properly\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__); + + packet->setSubstructDataByName("footer", "collection_needed", player->GetCollectionList()->NeedsItem(this) ? 1 : 0); + + if(generic_info.offers_quest_id > 0){ + Quest* quest = master_quest_list.GetQuest(generic_info.offers_quest_id, false); + if(quest){ + packet->setSubstructDataByName("footer", "offers_quest", strlen(generic_info.offers_quest_name) ? generic_info.offers_quest_name : quest->GetName()); + packet->setSubstructDataByName("footer", "offers_quest_color", player->GetArrowColor(quest->GetQuestLevel())); + } + } + if(generic_info.part_of_quest_id > 0){ + Quest* quest = master_quest_list.GetQuest(generic_info.part_of_quest_id, false); + if(quest){ + packet->setSubstructDataByName("footer", "part_of_quest", strlen(generic_info.required_by_quest_name) ? generic_info.required_by_quest_name : quest->GetName()); + packet->setSubstructDataByName("footer", "part_of_quest_color", player->GetArrowColor(quest->GetQuestLevel())); + } + } + if(generic_info.max_charges > 0){ + packet->setSubstructDataByName("footer", "charges", 1); + packet->setSubstructDataByName("footer", "total_charges", generic_info.max_charges); + packet->setSubstructDataByName("footer", "charges_left", details.count); + packet->setSubstructDataByName("footer", "display_charges", generic_info.display_charges); + } + if ((packet->GetVersion() >= 63119) || packet->GetVersion() == 61331){ + if (sell_status > 0){ + + } + } + //packet->setSubstructDataByName("footer", "status_item", 0); + + if (IsHarvest()){ + packet->setSubstructDataByName("footer", "crafting_flag", 1); + + + + } + + // Set these to 0 for now + if(packet->GetVersion() >= 1188){ + packet->setSubstructDataByName("footer", "locked_flag", 0); + packet->setSubstructDataByName("footer", "account_retricted", 0); + } + + // Adorns, set all to FF for now + if (packet->GetVersion() >= 1096) {// changed to 1096 for dov from 1188 + packet->setSubstructDataByName("footer", "adorn_slots", 0xFF, 0, 0); + packet->setSubstructDataByName("footer", "adorn_slots", 0xFF, 0, 1); + packet->setSubstructDataByName("footer", "adorn_slots", 0xFF, 0, 2); + packet->setSubstructDataByName("footer", "adorn_slots", 0xFF, 0, 3); + packet->setSubstructDataByName("footer", "adorn_slots", 0xFF, 0, 4); + packet->setSubstructDataByName("footer", "adorn_slots", 0xFF, 0, 5); + + } + if (packet->GetVersion() >= 1289) {// at some point after this there are 10 adornment slots all FF for now but will skip this if not needed for a version + + packet->setSubstructDataByName("footer", "adorn_slots", 0xFF, 0, 6); + packet->setSubstructDataByName("footer", "adorn_slots", 0xFF, 0, 7); + packet->setSubstructDataByName("footer", "adorn_slots", 0xFF, 0, 8); + packet->setSubstructDataByName("footer", "adorn_slots", 0xFF, 0, 9); + packet->setSubstructDataByName("footer", "adorn_slots", 0xFF, 0, 10); + } + + + packet->setSubstructDataByName("footer", "name", name.c_str()); + packet->setSubstructDataByName("footer", "description", description.c_str()); + + LogWrite(ITEM__PACKET, 0, "Items", "Dump/Print Packet in func: %s, line: %i", __FUNCTION__, __LINE__); +#if EQDEBUG >= 9 + packet->PrintPacket(); +#endif + +} + +PacketStruct* Item::PrepareItem(int16 version, bool merchant_item, bool loot_item, bool inspection){ + PacketStruct* packet = 0; + + if(loot_item && version > 561) + packet = configReader.getStruct("WS_LootItemGeneric", version); + else if(!inspection && loot_item && version <= 561) { + packet = configReader.getStruct("WS_ItemGeneric", version); + packet->AddFlag("loot"); + } + else if(inspection && version <= 373) { + packet = configReader.getStruct("WS_ItemInspect", version); + } + else if(version <= 561 && (generic_info.item_type > ITEM_TYPE_HOUSE || generic_info.item_type == ITEM_TYPE_BAUBLE)) { + packet = configReader.getStruct("WS_ItemGeneric", version); + } + else{ + int8 tmpType = generic_info.item_type; + if (version <= 373 && generic_info.item_type > ITEM_TYPE_RECIPE) + tmpType = 0; + else if(version <= 561 && (generic_info.item_type > ITEM_TYPE_HOUSE || generic_info.item_type == ITEM_TYPE_BAUBLE)) + tmpType = 0; + + switch(tmpType){ + case ITEM_TYPE_WEAPON:{ + if(merchant_item) + packet = configReader.getStruct("WS_MerchantItemWeapon", version); + else + packet = configReader.getStruct("WS_ItemWeapon", version); + break; + } + case ITEM_TYPE_RANGED:{ + if(merchant_item) + packet = configReader.getStruct("WS_MerchantItemRange", version); + else + packet = configReader.getStruct("WS_ItemRange", version); + break; + } + case ITEM_TYPE_SHIELD:{ + if (merchant_item) + packet = configReader.getStruct("WS_MerchantItemShield", version); + else + packet = configReader.getStruct("WS_ItemShield", version); + break; + } + case ITEM_TYPE_ARMOR:{ + if(merchant_item) + packet = configReader.getStruct("WS_MerchantItemArmor", version); + else + packet = configReader.getStruct("WS_ItemArmor", version); + break; + } + case ITEM_TYPE_BAG:{ + if(merchant_item) + packet = configReader.getStruct("WS_MerchantItemBag", version); + else + packet = configReader.getStruct("WS_ItemBag", version); + break; + } + case ITEM_TYPE_BOOK:{ + if(merchant_item) + packet = configReader.getStruct("WS_MerchantItemBook", version); + else + packet = configReader.getStruct("WS_ItemBook", version); + break; + } + case ITEM_TYPE_SKILL:{ + if(merchant_item) + packet = configReader.getStruct("WS_MerchantItemSkill", version); + else + packet = configReader.getStruct("WS_ItemSkill", version); + break; + } + case ITEM_TYPE_RECIPE:{ + if(merchant_item) + packet = configReader.getStruct("WS_MerchantItemRecipeBook", version); + else + packet = configReader.getStruct("WS_ItemRecipeBook", version); + break; + } + case ITEM_TYPE_FOOD:{ + if(merchant_item) + packet = configReader.getStruct("WS_MerchantItemFood", version); + else + packet = configReader.getStruct("WS_ItemFood", version); + break; + } + case ITEM_TYPE_BAUBLE:{ + if(merchant_item) + packet = configReader.getStruct("WS_MerchantItemBauble", version); + else + packet = configReader.getStruct("WS_ItemBauble", version); + break; + } + case ITEM_TYPE_ITEMCRATE:{ + if (merchant_item) + packet = configReader.getStruct("WS_MerchantItemSet", version); + else + packet = configReader.getStruct("WS_ItemSet", version); + break; + } + case ITEM_TYPE_HOUSE:{ + if(merchant_item) + packet = configReader.getStruct("WS_MerchantItemHouse", version); + else + packet = configReader.getStruct("WS_ItemHouse", version); + break; + } + case ITEM_TYPE_THROWN:{ + if(merchant_item) + packet = configReader.getStruct("WS_MerchantItemThrown", version); + else + packet = configReader.getStruct("WS_ItemThrown", version); + break; + } + case ITEM_TYPE_HOUSE_CONTAINER:{ + if(merchant_item) + packet = configReader.getStruct("WS_MerchantItemHouseContainer", version); + else + packet = configReader.getStruct("WS_ItemHouseContainer", version); + break; + } + case ITEM_TYPE_ADORNMENT:{ + if(merchant_item) + packet = configReader.getStruct("WS_MerchantAdornment", version); + else + packet = configReader.getStruct("WS_ItemAdornment", version); + break; + } + default:{ + if(merchant_item) + packet = configReader.getStruct("WS_MerchantItemGeneric", version); + else + packet = configReader.getStruct("WS_ItemGeneric", version); + } + } + if (packet && loot_item) + packet->AddFlag("loot"); + } + if(!packet){ + LogWrite(ITEM__ERROR, 0, "Item", "Unhandled Item type: %i", (int)generic_info.item_type); + return 0; + } + return packet; +} + +EQ2Packet* Item::serialize(int16 version, bool show_name, Player* player, bool include_twice, int16 packet_type, int8 subtype, bool merchant_item, bool loot_item, bool inspect){ + PacketStruct* packet = PrepareItem(version, merchant_item, loot_item, inspect); + if(!packet) + return 0; + if (version <= 561) { + include_twice = false; + packet_type = 0; + } + if(include_twice && IsBag() == false && IsBauble() == false && IsFood() == false) + serialize(packet, show_name, player, packet_type, 0x80, loot_item, inspect); + else + serialize(packet, show_name, player, packet_type, 0, loot_item, inspect); + if(merchant_item) + packet->setSubstructDataByName("header_info", "unique_id", 0xFFFFFFFF); + string* generic_string_data = packet->serializeString(); + + //packet->PrintPacket(); + //LogWrite(ITEM__DEBUG, 9, "Items", "generic_string_data:"); + //DumpPacket((uchar*)generic_string_data->c_str(), generic_string_data->length()); + + int32 size = generic_string_data->length(); + if(include_twice && IsBag() == false && IsBauble() == false && IsFood() == false) + size = (size*2)-13; + uchar* out_data = new uchar[size+1]; + uchar* out_ptr = out_data; + memcpy(out_ptr, (uchar*)generic_string_data->c_str(), generic_string_data->length()); + out_ptr += generic_string_data->length(); + if(include_twice && IsBag() == false && IsBauble() == false && IsFood() == false){ + memcpy(out_ptr, (uchar*)generic_string_data->c_str() + 13, generic_string_data->length() -13); + } + int32 size2 = size; + if (version <= 373) { + uchar* out_ptr2 = out_data; + if (size2 >= 0xFF) { + size2 -= 3; + out_ptr2[0] = 0xFF; + out_ptr2 += sizeof(int8); + memcpy(out_ptr2, &size2, sizeof(int16)); + } + else { + size2 -= 1; + out_ptr2[0] = size2; + } + } + else { + size2 -= 4; + memcpy(out_data, &size2, sizeof(int32)); + } + EQ2Packet* outapp = new EQ2Packet(OP_ClientCmdMsg, out_data, size); + //DumpPacket(outapp); + safe_delete(packet); + safe_delete_array(out_data); + return outapp; +} + +void Item::SetAppearance(ItemAppearance* appearance){ + SetAppearance(appearance->type, appearance->red, appearance->green, appearance->blue, appearance->highlight_red, appearance->highlight_green, appearance->highlight_blue); +} + +void Item::SetAppearance(int16 type, int8 red, int8 green, int8 blue, int8 highlight_red, int8 highlight_green, int8 highlight_blue){ + generic_info.appearance_id = type; + generic_info.appearance_red = red; + generic_info.appearance_green = green; + generic_info.appearance_blue = blue; + generic_info.appearance_highlight_red = highlight_red; + generic_info.appearance_highlight_green = highlight_green; + generic_info.appearance_highlight_blue = highlight_blue; +} + +void Item::AddEffect(string effect, int8 percentage, int8 subbulletflag){ + ItemEffect* item_effect = new ItemEffect; + item_effect->subbulletflag = subbulletflag; + item_effect->effect.data = effect; + item_effect->effect.size = effect.length(); + item_effect->percentage = percentage; + item_effects.push_back(item_effect); +} +void Item::AddBookPage(int8 page, string page_text, int8 valign, int8 halign) { + BookPage * bookpage = new BookPage; + bookpage->page = page; + bookpage->page_text.data = page_text; + bookpage->page_text.size = page_text.length(); + bookpage->valign = valign; + bookpage->halign = halign; + book_pages.push_back(bookpage); +} +void Item::AddLevelOverride(ItemLevelOverride* level_override){ + AddLevelOverride(level_override->adventure_class, level_override->tradeskill_class, level_override->level); +} + +void Item::AddLevelOverride(int8 adventure_class, int8 tradeskill_class, int16 level){ + ItemLevelOverride* item_override = new ItemLevelOverride; + item_override->adventure_class = adventure_class; + item_override->tradeskill_class = tradeskill_class; + item_override->level = level; + item_level_overrides.push_back(item_override); +} + +void Item::AddSlot(int8 slot_id){ + slot_data.push_back(slot_id); +} + +void Item::SetWeaponType(int8 type){ + weapon_type = type; +} + +int8 Item::GetWeaponType(){ + return weapon_type; +} + +int32 Item::GetMaxSellValue(){ + return max_sell_value; +} + +void Item::SetMaxSellValue(int32 val){ + max_sell_value = val; +} + +void Item::SetItemScript(string name){ + item_script = name; +} + +const char* Item::GetItemScript(){ + if(item_script.length() > 0) + return item_script.c_str(); + return 0; +} + +int32 Item::CalculateRepairCost() { + if (generic_info.condition == 100) + return 0; + float repair_cost = (float)generic_info.adventure_default_level * (10.0 - ((float)generic_info.condition * 0.1)); + if (details.tier == ITEM_TAG_LEGENDARY) + repair_cost *= 4; + else if (details.tier == ITEM_TAG_FABLED) + repair_cost *= 8; + else if (details.tier == ITEM_TAG_MYTHICAL) + repair_cost *= 12; + return (int32)repair_cost; +} + +PlayerItemList::PlayerItemList(){ + packet_count = 0; + xor_packet = 0; + orig_packet = 0; + max_saved_index = 0; + MPlayerItems.SetName("PlayerItemList::MPlayerItems"); +} + +PlayerItemList::~PlayerItemList(){ + safe_delete_array(xor_packet); + safe_delete_array(orig_packet); + map> >::iterator bag_iter; + map::iterator itr; + for(bag_iter = items.begin(); bag_iter != items.end(); bag_iter++){ + for(itr = bag_iter->second[0].begin(); itr != bag_iter->second[0].end(); itr++){ + safe_delete(itr->second); + } + for(itr = bag_iter->second[1].begin(); itr != bag_iter->second[1].end(); itr++){ + safe_delete(itr->second); + } + bag_iter->second.clear(); + } + items.clear(); + while (!overflowItems.empty()){ + safe_delete(overflowItems.back()); + overflowItems.pop_back(); + } +} + +map* PlayerItemList::GetAllItems(){ + map* ret = new map; + MPlayerItems.readlock(__FUNCTION__, __LINE__); + ret->insert(indexed_items.begin(), indexed_items.end()); + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +Item* PlayerItemList::GetItemFromIndex(int32 index){ + Item* ret = 0; + MPlayerItems.readlock(__FUNCTION__, __LINE__); + if(indexed_items.count(index) > 0) + ret = indexed_items[index]; + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +Item* PlayerItemList::GetItem(sint32 bag_slot, int16 slot, int8 appearance_type){ + Item* ret = 0; + MPlayerItems.readlock(__FUNCTION__, __LINE__); + if(items.count(bag_slot) > 0 && items[bag_slot][appearance_type].count(slot) > 0) + ret = items[bag_slot][appearance_type][slot]; + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +int32 PlayerItemList::SetMaxItemIndex() { + int32 max_index = indexed_items.size(); + int32 new_index = 0; + map::iterator itr; + MPlayerItems.writelock(__FUNCTION__, __LINE__); + for(itr = indexed_items.begin();itr != indexed_items.end(); itr++){ + if(itr->first > max_index) //just grab the highest index val for next loop + max_index = itr->first; + } + max_saved_index = max_index; + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); + + return max_index; +} + +bool PlayerItemList::AddItem(Item* item){ //is called with a slot already set + //quick check to verify item + if(!item) + return false; + else{ + if(item->details.inv_slot_id != 0){ + Item* bag = GetItemFromUniqueID(item->details.inv_slot_id, true); + if(bag && bag->IsBag()){ + if(item->details.slot_id > bag->details.num_slots){ + LogWrite(ITEM__ERROR, 0, "Item", "Error Adding Item: Invalid slot for item unique id: %u (%s - %i), InvSlotID: %u, slotid: %u, numslots: %u", item->details.unique_id, item->name.c_str(), + item->details.item_id, item->details.inv_slot_id, item->details.slot_id, bag->details.num_slots); + lua_interface->SetLuaUserDataStale(item); + safe_delete(item); + return false; + } + } + } + } + int32 max_index = indexed_items.size(); + int32 new_index = 0; + map::iterator itr; + MPlayerItems.writelock(__FUNCTION__, __LINE__); + for(itr = indexed_items.begin();itr != indexed_items.end(); itr++){ + if(itr->first > max_index) //just grab the highest index val for next loop + max_index = itr->first; + } + + bool doNotOverrideIndex = false; + int32 i=0; + for(i=0;iname.c_str(), i); + item->details.new_item = false; + item->details.new_index = 0; + doNotOverrideIndex = true; + break; + } + } + + if(doNotOverrideIndex) { + if(i < max_saved_index) { + item->details.new_item = false; + } else { + item->details.new_item = true; + } + } + + // may break non DoF clients + if(!doNotOverrideIndex && new_index == 0 && max_index > 0) + new_index = max_index; + + indexed_items[new_index] = item; + item->details.index = new_index; + items[item->details.inv_slot_id][item->details.appearance_type][item->details.slot_id] = item; + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); + + return true; +} + +Item* PlayerItemList::GetBag(int8 inventory_slot, bool lock){ + Item* bag = 0; + if(lock) + MPlayerItems.readlock(__FUNCTION__, __LINE__); + if(items.count(0) > 0 && items[0][BASE_EQUIPMENT].count(inventory_slot) > 0 && items[0][BASE_EQUIPMENT][inventory_slot]->IsBag()) + bag = items[0][BASE_EQUIPMENT][inventory_slot]; + if(lock) + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return bag; +} + +Item* PlayerItemList::GetBankBag(int8 inventory_slot, bool lock){ + Item* bag = 0; + if(lock) + MPlayerItems.readlock(__FUNCTION__, __LINE__); + if(items.count(-3) > 0 && items[-3][BASE_EQUIPMENT].count(inventory_slot) > 0 && items[-3][BASE_EQUIPMENT][inventory_slot]->IsBag()) + bag = items[-3][BASE_EQUIPMENT][inventory_slot]; + if(lock) + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return bag; +} + +int16 PlayerItemList::GetNumberOfFreeSlots(){ + int16 count = 0; + MPlayerItems.readlock(__FUNCTION__, __LINE__); + for(int8 i=0;idetails.num_slots > 0){ + if(items.count(bag->details.bag_id) > 0){ + for(int16 x=0;xdetails.num_slots;x++){ + if(items[bag->details.bag_id][BASE_EQUIPMENT].count(x) == 0) + count++; + } + } + else + count += bag->bag_info->num_slots; //if the bag hasnt been used yet, add all the free slots + } + } + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return count; +} + +bool PlayerItemList::HasFreeBagSlot(){ + bool ret = false; + MPlayerItems.readlock(__FUNCTION__, __LINE__); + if(items.count(0) > 0){ + for(int8 i=0;i 0){ + for(int8 i=0;idetails.num_slots > 0){ + if(items.count(bag->details.bag_id) > 0){ + for(int16 x=0;xdetails.num_slots;x++){ + if(items[bag->details.bag_id][BASE_EQUIPMENT].count(x) == 0){ + ret = true; + break; + } + } + } + else{ //if the bag hasnt been used yet, then all slots are free + ret = true; + break; + } + } + } + } + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +bool PlayerItemList::GetFirstFreeBankSlot(sint32* bag_id, sint16* slot) { + bool ret = false; + MPlayerItems.readlock(__FUNCTION__, __LINE__); + if (items.count(-3) > 0) { + for (int8 i = 0; i < NUM_BANK_SLOTS; i++) { + if (items[-3][BASE_EQUIPMENT].count(i) == 0) { + *bag_id = -3; + *slot = i; + ret = true; + break; + } + } + } + else { + *bag_id = -3; + *slot = 0; + ret = true; + } + + if(!ret) { + // Inventory slots were full so check bags + Item* bag = 0; + for(int8 i = 0; !ret && i < NUM_BANK_SLOTS; i++) { + // Check to see if the item in the inventory slot is a bag and it has slots + bag = GetBankBag(i, false); + if(bag && bag->details.num_slots > 0) { + // Item was a bag so lets loop through the slots and try to find an empty one + if(items.count(bag->details.bag_id) > 0) { + for(int16 x = 0; x < bag->details.num_slots; x++) { + if(items[bag->details.bag_id][BASE_EQUIPMENT].count(x) == 0) { + // Found a free slot, get the bag id of this bag + *bag_id = bag->details.bag_id; + // Get the slot + *slot = x; + ret = true; + break; + } + } + } + else { + //if the bag hasnt been used yet, then all slots are free, so set the bag_id to this bag + // and the slot to 0 (the first slot) + *bag_id = bag->details.bag_id; + *slot = 0; + ret = true; + break; + } + } + } + } + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +bool PlayerItemList::GetFirstFreeSlot(sint32* bag_id, sint16* slot) { + // Mostly copy and paste from the above function + bool ret = false; + // Try to place the item in the normal inventory slots first + MPlayerItems.readlock(__FUNCTION__, __LINE__); + if(items.count(0) > 0){ + for(int8 i=0; i < NUM_INV_SLOTS; i++) { + if(items[0][BASE_EQUIPMENT].count(i) == 0) { + // Found an empty slot, store the slot id and set the return value + *bag_id = 0; + *slot = i; + ret = true; + break; + } + } + } + else { + // no items in the players inventory, set it to the first slot + *bag_id = 0; + *slot = 0; + ret = true; + } + + if(!ret) { + // Inventory slots were full so check bags + Item* bag = 0; + for(int8 i = 0; !ret && i < NUM_INV_SLOTS; i++) { + // Check to see if the item in the inventory slot is a bag and it has slots + bag = GetBag(i, false); + if(bag && bag->details.num_slots > 0) { + // Item was a bag so lets loop through the slots and try to find an empty one + if(items.count(bag->details.bag_id) > 0) { + for(int16 x = 0; x < bag->details.num_slots; x++) { + if(items[bag->details.bag_id][BASE_EQUIPMENT].count(x) == 0) { + // Found a free slot, get the bag id of this bag + *bag_id = bag->details.bag_id; + // Get the slot + *slot = x; + ret = true; + break; + } + } + } + else { + //if the bag hasnt been used yet, then all slots are free, so set the bag_id to this bag + // and the slot to 0 (the first slot) + *bag_id = bag->details.bag_id; + *slot = 0; + ret = true; + break; + } + } + } + } + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} +vector PlayerItemList::GetAllItemsFromID(int32 id, bool include_bank, bool lock) { + //first check for an exact count match + map> >::iterator itr; + map::iterator slot_itr; + vector ret ; + if (lock) + MPlayerItems.readlock(__FUNCTION__, __LINE__); + for (itr = items.begin(); itr != items.end(); itr++) { + if (include_bank || (!include_bank && itr->first >= 0)) { + for (int8 i = 0; i < MAX_EQUIPMENT; i++) + { + for (slot_itr = itr->second[i].begin(); slot_itr != itr->second[i].end(); slot_itr++) { + if (slot_itr->second && slot_itr->second->details.item_id == id) { + if (lock) + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + ret.push_back(slot_itr->second); + } + } + + } + + } + } + return ret; +} +Item* PlayerItemList::CanStack(Item* item, bool include_bank){ + if(!item || item->stack_count < 2) + return 0; + + Item* ret = 0; + map> >::iterator itr; + map::iterator slot_itr; + MPlayerItems.readlock(__FUNCTION__, __LINE__); + for(itr = items.begin(); itr != items.end(); itr++){ + if(include_bank || (!include_bank && itr->first >= 0)){ + for(slot_itr=itr->second[0].begin();slot_itr!=itr->second[0].end(); slot_itr++){ + if(slot_itr->second && slot_itr->second->details.item_id == item->details.item_id && (((slot_itr->second->details.count ? slot_itr->second->details.count : 1) + (item->details.count > 0 ? item->details.count : 1)) <= slot_itr->second->stack_count)){ + ret = slot_itr->second; + break; + } + } + for(slot_itr=itr->second[1].begin();slot_itr!=itr->second[1].end(); slot_itr++){ + if(slot_itr->second && slot_itr->second->details.item_id == item->details.item_id && (((slot_itr->second->details.count ? slot_itr->second->details.count : 1) + (item->details.count > 0 ? item->details.count : 1)) <= slot_itr->second->stack_count)){ + ret = slot_itr->second; + break; + } + } + } + if(ret) + break; + } + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +void PlayerItemList::Stack(Item* orig_item, Item* item){ + if(!orig_item || !item) + return; + orig_item->details.count += item->details.count; + orig_item->save_needed = true; +} + +bool PlayerItemList::AssignItemToFreeSlot(Item* item){ + if(item){ + Item* orig_item = CanStack(item); + if(orig_item){ + Stack(orig_item, item); + return true; + } + bool use_bag_freeslot = false; + if(item->IsBag()) + use_bag_freeslot = HasFreeBagSlot(); + MPlayerItems.writelock(__FUNCTION__, __LINE__); + if(!use_bag_freeslot){ + Item* bag = 0; + for(int8 i=0;iIsBag() && bag->details.num_slots > 0){ + for(int16 x=0;xdetails.num_slots;x++){ + if(items[bag->details.bag_id][BASE_EQUIPMENT].count(x) == 0){ + item->details.inv_slot_id = bag->details.bag_id; + item->details.slot_id = x; + item->details.new_item = true; + item->details.new_index = 0; + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); + bool ret = AddItem(item); + return ret; + } + } + } + } + } + //bags full, check inventory slots + for(int8 i=0;idetails.inv_slot_id = 0; + item->details.slot_id = i; + item->details.new_item = true; + item->details.new_index = 0; + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); + bool ret = AddItem(item); + return ret; + } + } + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); + } + return false; +} + + +void PlayerItemList::RemoveItem(Item* item, bool delete_item){ + MPlayerItems.writelock(__FUNCTION__, __LINE__); + if(items.count(item->details.inv_slot_id) > 0 && items[item->details.inv_slot_id][item->details.appearance_type].count(item->details.slot_id) > 0){ + items[item->details.inv_slot_id][item->details.appearance_type].erase(item->details.slot_id); + indexed_items[item->details.index] = 0; + } + if(item->IsBag() && item->details.inv_slot_id == 0 && item->details.slot_id < NUM_INV_SLOTS && items.count(item->details.bag_id) > 0){ + map::iterator itr; + for(itr = items[item->details.bag_id][item->details.appearance_type].begin(); itr != items[item->details.bag_id][item->details.appearance_type].end(); itr++){ + indexed_items[itr->second->details.index] = 0; + if(delete_item){ + if(itr->second == item) { + item = nullptr; + } + lua_interface->SetLuaUserDataStale(itr->second); + safe_delete(itr->second); + } + } + items.erase(item->details.bag_id); + } + if(item && delete_item){ + map::iterator itr = indexed_items.find(item->details.index); + if(itr != indexed_items.end() && item == indexed_items[item->details.index]) + indexed_items[item->details.index] = 0; + + lua_interface->SetLuaUserDataStale(item); + safe_delete(item); + } + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); +} + +void PlayerItemList::DestroyItem(int16 index){ + MPlayerItems.writelock(__FUNCTION__, __LINE__); + Item* item = indexed_items[index]; + map::iterator itr; + if(item && item->IsBag() && item->details.inv_slot_id == 0 && item->details.slot_id < NUM_INV_SLOTS && items.count((sint32)item->details.bag_id) > 0){ //inventory + map* tmp_map = &(items[(sint32)item->details.bag_id][item->details.appearance_type]); + for(itr = tmp_map->begin(); itr != tmp_map->end(); itr++){ + indexed_items[itr->second->details.index] = 0; + if(itr->second != item){ + lua_interface->SetLuaUserDataStale(itr->second); + safe_delete(itr->second); + } + } + items.erase(item->details.bag_id); + } + if(item) { + if(items.count(item->details.inv_slot_id) > 0 && items[item->details.inv_slot_id][item->details.appearance_type].count(item->details.slot_id) > 0) + items[item->details.inv_slot_id][item->details.appearance_type].erase(item->details.slot_id); + indexed_items[index] = 0; + + lua_interface->SetLuaUserDataStale(item); + safe_delete(item); + } + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); +} + +void PlayerItemList::MoveItem(Item* item, sint32 inv_slot, int16 slot, int8 appearance_type, bool erase_old){ + if(erase_old && items.count(item->details.inv_slot_id) > 0 && items[item->details.inv_slot_id][BASE_EQUIPMENT].count(item->details.slot_id)) + items[item->details.inv_slot_id][BASE_EQUIPMENT].erase(item->details.slot_id); + items[inv_slot][BASE_EQUIPMENT][slot] = item; + item->details.inv_slot_id = inv_slot; + item->details.slot_id = slot; + item->details.appearance_type = 0; + item->save_needed = true; +} + +void PlayerItemList::EraseItem(Item* item){ + if(items.count(item->details.inv_slot_id) > 0 && items[item->details.inv_slot_id][BASE_EQUIPMENT].count(item->details.slot_id)) + items[item->details.inv_slot_id][BASE_EQUIPMENT].erase(item->details.slot_id); +} + +int16 PlayerItemList::GetNumberOfItems(){ + int16 ret = 0; + MPlayerItems.readlock(__FUNCTION__, __LINE__); + if(items.size() > 0){ + map> >::iterator itr; + sint32 bag_id = 0; + for(itr = items.begin(); itr != items.end(); itr++){ + bag_id = itr->first; + if(items[bag_id].count(0)) + ret += items[bag_id][BASE_EQUIPMENT].size(); + } + } + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +int32 PlayerItemList::GetWeight(){ + int32 ret = 0; + MPlayerItems.readlock(__FUNCTION__, __LINE__); + for(int16 i = 0; i < indexed_items.size(); i++){ + Item* item = indexed_items[i]; + if (item) { + ret += item->generic_info.weight; + } + } + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + + +bool PlayerItemList::MoveItem(sint32 to_bag_id, int16 from_index, sint8 to, int8 appearance_type, int8 charges){ + MPlayerItems.writelock(__FUNCTION__, __LINE__); + Item* item_from = indexed_items[from_index]; + Item* item_to = 0; + if(item_from){ + if(to_bag_id > 0){ //bag item + Item* bag = GetItemFromUniqueID(to_bag_id, true, false); + if(bag && bag->details.num_slots > to && (!item_from || !item_from->IsBag())) + item_to = items[to_bag_id][BASE_EQUIPMENT][to]; + else{ + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); + return false; + } + } + else { + item_to = items[to_bag_id][BASE_EQUIPMENT][to]; + if(item_to && item_to->IsBag() && item_from && item_from->IsBag()) { + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); + return false; + } + } + if(charges > 0) { + if (item_to && item_from->details.item_id == item_to->details.item_id){ + if(item_to->details.count > 0 && item_to->details.count < item_to->stack_count){ + int32 total_tmp_price = 0; + if((item_to->details.count + item_from->details.count) <= item_to->stack_count){ + total_tmp_price = (item_to->GetMaxSellValue()*item_to->details.count) + (item_from->GetMaxSellValue()*item_from->details.count); + item_to->details.count += item_from->details.count; + indexed_items[from_index] = 0; + items[item_from->details.inv_slot_id][BASE_EQUIPMENT].erase(item_from->details.slot_id); + item_from->needs_deletion = true; + item_to->save_needed = true; + } + else{ + int8 diff = item_to->stack_count - item_to->details.count; + total_tmp_price = (item_to->GetMaxSellValue()*item_to->details.count) + (item_from->GetMaxSellValue()*diff); + item_to->details.count = item_to->stack_count; + item_from->details.count -= diff; + item_to->save_needed = true; + } + item_to->SetMaxSellValue(total_tmp_price/item_to->details.count); + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); + return true; + } + } + else { + if (item_from->details.count == charges) { + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); + if (item_to) + MoveItem(item_to, item_from->details.inv_slot_id, item_from->details.slot_id, BASE_EQUIPMENT, true); + + MoveItem(item_from, to_bag_id, to, BASE_EQUIPMENT, item_to ? false:true); + } + else { + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); + if (item_to) { + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); + return false; + } + item_from->details.count -= charges; + Item* new_item = new Item(master_item_list.GetItem(item_from->details.item_id)); + new_item->details.count = charges; + new_item->details.slot_id = to; + new_item->details.inv_slot_id = to_bag_id; + new_item->details.appearance_type = 0; + new_item->save_needed = true; + AddItem(new_item); + if (item_from->details.count == 0) + RemoveItem(item_from); + } + return true; + } + } + else if(item_to && item_to->IsBag() && item_to->details.num_slots > 0){ + // if item we are moving is a bag + if (item_from->IsBag() && item_from->details.num_slots > 0) { + for (int8 i = 0; i < item_from->details.num_slots; i++) { + // if there is something in the bag return, can't put bags with items into other bags + if (items[item_from->details.bag_id][BASE_EQUIPMENT].count(i) != 0) { + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); + return false; + } + } + } + if(items.count(item_to->details.bag_id) > 0){ + for(int8 i=0;idetails.num_slots;i++){ + if(items[item_to->details.bag_id][BASE_EQUIPMENT].count(i) == 0){ + MoveItem(item_from, item_to->details.bag_id, i, 0, true); + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); + return true; + } + } + } + else{ + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); + MoveItem(item_from, item_to->details.bag_id, 0, BASE_EQUIPMENT, true); + return true; + } + } + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); + + if (item_to) + MoveItem(item_to, item_from->details.inv_slot_id, item_from->details.slot_id, BASE_EQUIPMENT, true); + + MoveItem(item_from, to_bag_id, to, BASE_EQUIPMENT, item_to ? false:true); + + return true; + } + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); + return false; +} + +EQ2Packet* PlayerItemList::serialize(Player* player, int16 version){ + bool firstRun = false; + if(version <= 561 && !packet_count) { + firstRun = true; + } + EQ2Packet* app = 0; + PacketStruct* packet = configReader.getStruct("WS_UpdateInventory",version); + Item* item = 0; + MPlayerItems.readlock(__FUNCTION__, __LINE__); + if(packet && indexed_items.size() > 0){ + int8 packet_size = 0; + int16 size = indexed_items.size(); + int16 actual_size = 0; + for(int16 i = 0; i < indexed_items.size(); i++){ + item = indexed_items[i]; + if(item) + actual_size++; + } + size = actual_size; + + if (!firstRun && overflowItems.size() > 0) + size++; + + if(size > 20 && firstRun) { + size = 20; + } + PacketStruct* packet2 = configReader.getStruct("Substruct_Item", version); + packet_size = packet2->GetTotalPacketSize(); + safe_delete(packet2); + packet->setArrayLengthByName("item_count", size); + if(packet_count < size){ + if(!orig_packet){ + xor_packet = new uchar[packet_size * size]; + orig_packet = new uchar[packet_size * size]; + memset(xor_packet, 0, packet_size * size); + memset(orig_packet, 0, packet_size * size); + } + else{ + uchar* tmp = new uchar[packet_size * size]; + memset(tmp, 0, packet_size * size); + memcpy(tmp, orig_packet, packet_size * packet_count); + safe_delete_array(orig_packet); + orig_packet = tmp; + safe_delete_array(xor_packet); + xor_packet = new uchar[packet_size * size]; + } + } + + packet_count = size; + + int16 new_index = 0; + for(int16 i = 0; i < indexed_items.size(); i++){ + item = indexed_items[i]; + if(item && item->details.new_item) + new_index++; + + if(item) { + printf("%u: %s\n", i, item->name.c_str()); + } + else{ + printf("%u: empty\n", i); + } + + if(item && firstRun && i > 19) { + item->details.new_item = true; + continue; + } + + if (item && item->details.item_id > 0) + AddItemToPacket(packet, player, item, i, false, new_index); + + } + + if (!firstRun && overflowItems.size() > 0) { + // We have overflow items, lets get the first one + item = overflowItems.at(0); + // Lets make sure the item is valid + if (item && item->details.item_id > 0) { + // Set the slot to 6 as that is what overflow requires to work + item->details.slot_id = 6; + // now add it to the packet + AddItemToPacket(packet, player, item, size - 1, true); + } + } + + LogWrite(ITEM__PACKET, 0, "Items", "Dump/Print Packet in func: %s, line: %i", __FUNCTION__, __LINE__); +#if EQDEBUG >= 9 + packet->PrintPacket(); +#endif + packet->setDataByName("equip_flag",0); + app = packet->serializeCountPacket(version, 1, orig_packet, xor_packet); + safe_delete(packet); + } + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return app; +} + +int16 PlayerItemList::GetFirstNewItem() { + int16 new_item_slot = 0; + for(int16 i = 0; i < indexed_items.size(); i++){ + Item* item = indexed_items[i]; + if(item && item->details.new_item) { + return i; + } + } + return 0xFFFF; +} + +int16 PlayerItemList::GetNewItemByIndex(int16 in_index) { + int16 new_item_slot = 0; + for(int16 i = 0; i < indexed_items.size(); i++){ + Item* item = indexed_items[i]; + if(item && item->details.new_item) { + new_item_slot++; + int16 actual_index = in_index - new_item_slot; + // this isn't compiling right + //printf("In index: %u new index %u actual %u and %u, new slot num %u\n", in_index, item->details.new_index, actual_index, i, new_item_slot); + if(actual_index == i) { + return i; + } + } + } + return 0xFFFF; +} + +void PlayerItemList::AddItemToPacket(PacketStruct* packet, Player* player, Item* item, int16 i, bool overflow, int16 new_index){ + Client *client; + if (!packet || !player) + return; + client = ((Player*)player)->GetClient(); + if (!client) + return; + + int32 menu_data = 3; + if(item->slot_data.size() > 0) + menu_data -= ITEM_MENU_TYPE_GENERIC; + + if (item->details.num_slots > 0) { + int8 max_slots = player->GetMaxBagSlots(client->GetVersion()); + if (item->details.num_slots > max_slots) + item->details.num_slots = max_slots; + + menu_data += ITEM_MENU_TYPE_BAG; + + if (item->details.num_free_slots == item->details.num_slots) + menu_data += ITEM_MENU_TYPE_EMPTY_BAG; + } + if (item->details.item_id == 21355) { + //menu_data += ITEM_MENU_TYPE_GENERIC; + //menu_data += ITEM_MENU_TYPE_EQUIP; + menu_data += ITEM_MENU_TYPE_BOOK; + //menu_data += ITEM_MENU_TYPE_BAG; + //menu_data += ITEM_MENU_TYPE_HOUSE; + //menu_data += ITEM_MENU_TYPE_TEST12; + //menu_data += ITEM_MENU_TYPE_SCRIBE; + //menu_data += ITEM_MENU_TYPE_TEST13; + //menu_data += ITEM_MENU_TYPE_INVALID; + //menu_data += ITEM_MENU_TYPE_TEST14; + //menu_data += ITEM_MENU_TYPE_BROKEN; + } + if (item->details.item_id == 21356) { + //menu_data += ITEM_MENU_TYPE_TEST15; + menu_data += ITEM_MENU_TYPE_ATTUNED; + menu_data += ITEM_MENU_TYPE_ATTUNEABLE; + menu_data += ITEM_MENU_TYPE_BOOK; + menu_data += ITEM_MENU_TYPE_DISPLAY_CHARGES; + menu_data += ITEM_MENU_TYPE_TEST1; + menu_data += ITEM_MENU_TYPE_NAMEPET; + menu_data += ITEM_MENU_TYPE_MENTORED; + menu_data += ITEM_MENU_TYPE_CONSUME; + menu_data += ITEM_MENU_TYPE_USE; + } + if (item->details.item_id == 21357) { + menu_data += ITEM_MENU_TYPE_CONSUME_OFF ; + menu_data += ITEM_MENU_TYPE_TEST3 ; + menu_data += ITEM_MENU_TYPE_TEST4 ; + menu_data += ITEM_MENU_TYPE_TEST5 ; + menu_data += ITEM_MENU_TYPE_TEST6 ; + menu_data += ITEM_MENU_TYPE_TEST7 ; + menu_data += ITEM_MENU_TYPE_TEST8 ; + menu_data += ITEM_MENU_TYPE_TEST9 ; + menu_data += ITEM_MENU_TYPE_DAMAGED ; + menu_data += ITEM_MENU_TYPE_BROKEN2 ; + menu_data += ITEM_MENU_TYPE_REDEEM ; + menu_data += ITEM_MENU_TYPE_TEST10 ; + menu_data += ITEM_MENU_TYPE_UNPACK ; + } + if(item->IsSkill()){ + Spell* spell = master_spell_list.GetSpell(item->skill_info->spell_id, item->skill_info->spell_tier); + if (spell && spell->ScribeAllowed(player)) + menu_data += ITEM_MENU_TYPE_SCRIBE; + else + menu_data += ITEM_MENU_TYPE_INSUFFICIENT_KNOWLEDGE; + } + if(item->IsRecipeBook()){ + //TODO: Add check to allow scribe + menu_data += ITEM_MENU_TYPE_SCRIBE; + } + if (item->generic_info.item_type == 10){ + menu_data += ITEM_MENU_TYPE_TEST1; + menu_data += ITEM_MENU_TYPE_HOUSE; + } + if (item->generic_info.item_type == 18){ + menu_data += ITEM_MENU_TYPE_UNPACK; + packet->setSubstructArrayDataByName("items", "unknown3", ITEM_MENU_TYPE2_UNPACK, 0, i); + } + + if(item->generic_info.condition == 0) + menu_data += ITEM_MENU_TYPE_BROKEN; + if (client->GetVersion() <= 373){ + string flags; + if (item->CheckFlag(NO_TRADE)) + flags += "NO-TRADE "; + if (item->CheckFlag(NO_VALUE)) + flags += "NO-VALUE "; + if (flags.length() > 0) + packet->setSubstructArrayDataByName("items", "flag_names", flags.c_str(), 0, i); + } + + if (item->CheckFlag(ATTUNED) || item->CheckFlag(NO_TRADE)) { + if (client->GetVersion() <= 373) + menu_data += ORIG_ITEM_MENU_TYPE_ATTUNED; + else + menu_data += ITEM_MENU_TYPE_ATTUNED; + } + else if (item->CheckFlag(ATTUNEABLE)) { + if (client->GetVersion() <= 373) + menu_data += ORIG_ITEM_MENU_TYPE_ATTUNEABLE; + else + menu_data += ITEM_MENU_TYPE_ATTUNEABLE; + } + if (item->generic_info.usable == 1) + menu_data += ITEM_MENU_TYPE_USE; + if (item->details.count > 0 && item->stack_count > 1) { + if (client->GetVersion() <= 373) + menu_data += ORIG_ITEM_MENU_TYPE_STACKABLE; + else + menu_data += ITEM_MENU_TYPE_DISPLAY_CHARGES; + } + if(item->IsFood()) { + if (client->GetVersion() <= 373) { + if (item->IsFoodDrink()) + menu_data += ORIG_ITEM_MENU_TYPE_DRINK; + else if(item->IsFoodFood()) + menu_data += ORIG_ITEM_MENU_TYPE_FOOD; + } + } + if(item->details.item_locked) { + menu_data += ITEM_MENU_TYPE_BROKEN; // broken is also used to lock item during crafting + } + // Added the if (overflow) so mouseover examines work properly + if (overflow) + packet->setSubstructArrayDataByName("items", "unique_id", item->details.item_id, 0, i); + else + packet->setSubstructArrayDataByName("items", "unique_id", item->details.unique_id, 0, i); + packet->setSubstructArrayDataByName("items", "bag_id", item->details.bag_id, 0, i); + packet->setSubstructArrayDataByName("items", "inv_slot_id", item->details.inv_slot_id, 0, i); + packet->setSubstructArrayDataByName("items", "menu_type", menu_data, 0, i); + if (overflow) + packet->setSubstructArrayDataByName("items", "index", 0xFFFF, 0, i); + else { + if(packet->GetVersion() <= 561) { + /* DoF client and earlier side automatically assigns indexes + ** we have to send 0xFF or else all index is set to 255 on client + ** and then examine inventory won't work */ + LogWrite(ITEM__DEBUG, 0, "%s Offset index %u bag id %u (new index %u, set index %u)",item->name.c_str(),i, item->details.bag_id, new_index, item->details.new_index); + if(item->details.new_item) { + item->details.new_index = new_index + i; // we have to offset in this way to get consistent indexes for the client to send back + packet->setSubstructArrayDataByName("items", "index", 0xFF+item->details.new_index, 0, i); + } + else { + packet->setSubstructArrayDataByName("items", "index", 0xFF, 0, i); + } + } + else { + packet->setSubstructArrayDataByName("items", "index", i, 0, i); + } + } + item->details.index = i; + + packet->setSubstructArrayDataByName("items", "icon", item->GetIcon(client->GetVersion()), 0, i); + packet->setSubstructArrayDataByName("items", "slot_id", item->details.slot_id, 0, i); // inventory doesn't convert slots + if (client->GetVersion() <= 1208) { + packet->setSubstructArrayDataByName("items", "count", (std::min)(item->details.count, (int16)255), 0, i); + } + else + packet->setSubstructArrayDataByName("items", "count", item->details.count, 0, i); + //packet->setSubstructArrayDataByName("items", "unknown4", 5, 0, i); + // need item level + packet->setSubstructArrayDataByName("items", "item_level", item->details.recommended_level , 0, i); + + + if(rule_manager.GetGlobalRule(R_World, DisplayItemTiers)->GetBool()) { + packet->setSubstructArrayDataByName("items", "tier", item->details.tier, 0, i); + } + + packet->setSubstructArrayDataByName("items", "num_slots", item->details.num_slots, 0, i); + // need empty slots + packet->setSubstructArrayDataByName("items", "item_id", item->details.item_id, 0, i); + //need broker id + packet->setSubstructArrayDataByName("items", "name", item->name.c_str(), 0, i); + +} + +bool PlayerItemList::AddOverflowItem(Item* item) { + bool ret = false; + MPlayerItems.writelock(__FUNCTION__, __LINE__); + if (item && item->details.item_id > 0 && overflowItems.size() < 255) { + item->details.slot_id = 6; + item->details.inv_slot_id = -2; + overflowItems.push_back(item); + ret = true; + } + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); + return ret; +} + +Item* PlayerItemList::GetOverflowItem() { + if(overflowItems.empty()) { + return nullptr; + } + + return overflowItems.at(0); +} + +void PlayerItemList::RemoveOverflowItem(Item* item) { + MPlayerItems.writelock(__FUNCTION__, __LINE__); + vector::iterator itr = std::find(overflowItems.begin(), overflowItems.end(), item); + if(itr != overflowItems.end()) { + overflowItems.erase(itr); + } + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); +} + +vector* PlayerItemList::GetOverflowItemList() { + vector* ret = new vector; + MPlayerItems.readlock(__FUNCTION__, __LINE__); + vector::iterator itr= ret->begin(); + ret->insert(itr, overflowItems.begin(), overflowItems.end()); + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +bool PlayerItemList::HasItem(int32 id, bool include_bank){ + map> >::iterator itr; + map::iterator slot_itr; + MPlayerItems.readlock(__FUNCTION__, __LINE__); + for(itr = items.begin(); itr != items.end(); itr++){ + if(include_bank || (!include_bank && itr->first >= 0)){ + for(slot_itr=itr->second[BASE_EQUIPMENT].begin();slot_itr!=itr->second[BASE_EQUIPMENT].end(); slot_itr++){ + if(slot_itr->second && slot_itr->second->details.item_id == id){ + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return true; + } + } + for(slot_itr=itr->second[APPEARANCE_EQUIPMENT].begin();slot_itr!=itr->second[APPEARANCE_EQUIPMENT].end(); slot_itr++){ + if(slot_itr->second && slot_itr->second->details.item_id == id){ + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return true; + } + } + } + } + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return false; +} + +bool PlayerItemList::SharedBankAddAllowed(Item* item){ + if(!item || (item->CheckFlag(NO_TRADE) && (item->CheckFlag2(HEIRLOOM) == 0))) + return false; + + MPlayerItems.readlock(__FUNCTION__, __LINE__); + if(item->IsBag() && items.count(item->details.bag_id) > 0){ + map::iterator itr; + for(itr = items[item->details.bag_id][BASE_EQUIPMENT].begin(); itr != items[item->details.bag_id][BASE_EQUIPMENT].end(); itr++){ + if(itr->second->CheckFlag(NO_TRADE) && itr->second->CheckFlag2(HEIRLOOM) == 0){ + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return false; + } + } + } + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return true; +} + +vector* PlayerItemList::GetItemsFromBagID(sint32 bag_id){ + vector* ret = new vector; + if(items.count(bag_id) > 0){ + MPlayerItems.readlock(__FUNCTION__, __LINE__); + map::iterator itr; + map::iterator itr2; + Item* item = 0; + for(itr = items[bag_id][BASE_EQUIPMENT].begin(); itr != items[bag_id][BASE_EQUIPMENT].end(); itr++){ + item = itr->second; + if(item) + ret->push_back(item); + } + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + } + return ret; +} +int32 PlayerItemList::GetItemCountInBag(Item* bag){ + MPlayerItems.readlock(__FUNCTION__, __LINE__); + if(bag && bag->IsBag() && items.count(bag->details.bag_id) > 0){ + int32 bagitems = items.count(bag->details.bag_id); + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return bagitems; + } + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return 0; +} +vector* PlayerItemList::GetItemsInBag(Item* bag){ + vector* ret_items = new vector; + MPlayerItems.readlock(__FUNCTION__, __LINE__); + if(bag && bag->IsBag() && items.count(bag->details.bag_id) > 0){ + map::iterator itr; + for(itr = items[bag->details.bag_id][BASE_EQUIPMENT].begin(); itr != items[bag->details.bag_id][BASE_EQUIPMENT].end(); itr++){ + ret_items->push_back(itr->second); + } + } + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return ret_items; +} + +Item* PlayerItemList::GetItemFromID(int32 id, int8 count, bool include_bank, bool lock){ + //first check for an exact count match + map> >::iterator itr; + map::iterator slot_itr; + if(lock) + MPlayerItems.readlock(__FUNCTION__, __LINE__); + for(itr = items.begin(); itr != items.end(); itr++){ + if(include_bank || (!include_bank && itr->first >= 0)){ + for(int8 i=0;isecond[i].begin();slot_itr!=itr->second[i].end(); slot_itr++){ + if(slot_itr->second && slot_itr->second->details.item_id == id && (count == 0 || slot_itr->second->details.count == count)){ + if(lock) + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return slot_itr->second; + } + } + } + } + } + + //couldn't find an exact match, look for closest + Item* closest = 0; + for(itr = items.begin(); itr != items.end(); itr++){ + if(include_bank || (!include_bank && itr->first >= 0)){ + for(int8 i=0;isecond[i].begin();slot_itr!=itr->second[i].end(); slot_itr++){ + if(slot_itr->second && slot_itr->second->details.item_id == id && slot_itr->second->details.count > count && (closest == 0 || slot_itr->second->details.count < closest->details.count)) + closest = slot_itr->second; + } + } + } + } + if(lock) + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return closest; +} + +sint32 PlayerItemList::GetAllStackCountItemFromID(int32 id, int8 count, bool include_bank, bool lock){ + sint32 stack_count = 0; + //first check for an exact count match + map> >::iterator itr; + map::iterator slot_itr; + if(lock) + MPlayerItems.readlock(__FUNCTION__, __LINE__); + for(itr = items.begin(); itr != items.end(); itr++){ + if(include_bank || (!include_bank && itr->first >= 0)){ + for(int8 i=0;isecond[i].begin();slot_itr!=itr->second[i].end(); slot_itr++){ + if(slot_itr->second && slot_itr->second->details.item_id == id && (count == 0 || slot_itr->second->details.count == count)){ + stack_count += slot_itr->second->details.count; + } + } + } + } + } + + //couldn't find an exact match, look for closest + Item* closest = 0; + for(itr = items.begin(); itr != items.end(); itr++){ + if(include_bank || (!include_bank && itr->first >= 0)){ + for(int8 i=0;isecond[i].begin();slot_itr!=itr->second[i].end(); slot_itr++){ + if(slot_itr->second && slot_itr->second->details.item_id == id && slot_itr->second->details.count > count && (closest == 0 || slot_itr->second->details.count < closest->details.count)) + stack_count += slot_itr->second->details.count; + } + } + } + } + if(lock) + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return stack_count; +} + +Item* PlayerItemList::GetItemFromUniqueID(int32 id, bool include_bank, bool lock){ + map> >::iterator itr; + map::iterator slot_itr; + if(lock) + MPlayerItems.readlock(__FUNCTION__, __LINE__); + for(itr = items.begin(); itr != items.end(); itr++){ + if(include_bank || (!include_bank && itr->first >= 0)){ + for(slot_itr=itr->second[0].begin();slot_itr!=itr->second[0].end(); slot_itr++){ + if(slot_itr->second && slot_itr->second->details.unique_id == id){ + if(lock) + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return slot_itr->second; + } + } + for(slot_itr=itr->second[1].begin();slot_itr!=itr->second[1].end(); slot_itr++){ + if(slot_itr->second && slot_itr->second->details.unique_id == id){ + if(lock) + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return slot_itr->second; + } + } + } + } + if(lock) + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return 0; +} + +bool PlayerItemList::HasFreeBankSlot() { + bool ret = false; + MPlayerItems.readlock(__FUNCTION__, __LINE__); + if (items[-3][BASE_EQUIPMENT].size() < 12) //12 slots in the bank + ret = true; + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +int8 PlayerItemList::FindFreeBankSlot() { + int8 ret = 0; + MPlayerItems.readlock(__FUNCTION__, __LINE__); + for (int8 i = 0; i < 12; i++) { //12 slots in the bank + if (items[-3][BASE_EQUIPMENT].count(i) == 0) { + ret = i; + break; + } + } + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +void PlayerItemList::ResetPackets() { + MPlayerItems.writelock(__FUNCTION__, __LINE__); + safe_delete_array(orig_packet); + safe_delete_array(xor_packet); + orig_packet = 0; + xor_packet = 0; + packet_count = 0; + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); +} + + +int32 PlayerItemList::CheckSlotConflict(Item* item, bool check_lore_only, bool lock, int16* lore_stack_count) { + bool is_lore = false; + bool is_stack_lore = false; + if(!(is_lore = item->CheckFlag(LORE)) && !(is_stack_lore = item->CheckFlag(STACK_LORE)) && check_lore_only) { + return 0; + } + + if(!check_lore_only && !is_lore && !is_stack_lore && !item->CheckFlag(LORE_EQUIP)) { + return 0; + } + + + int32 conflict = 0; + + if(lock) + MPlayerItems.readlock(__FUNCTION__, __LINE__); + + map> >::iterator itr; + map::iterator slot_itr; + + for(itr = items.begin(); itr != items.end(); itr++){ + for(slot_itr=itr->second[0].begin();slot_itr!=itr->second[0].end(); slot_itr++){ + if(slot_itr->second && slot_itr->second->details.item_id == item->details.item_id){ + if(lore_stack_count) { + *lore_stack_count += slot_itr->second->details.count; + } + if(!is_stack_lore && slot_itr->second->CheckFlag(LORE)) { + conflict = LORE; + break; + } + else if(is_stack_lore && (*lore_stack_count + item->details.count) > slot_itr->second->stack_count) { + conflict = STACK_LORE; + break; + } + else if(!check_lore_only && slot_itr->second->CheckFlag(LORE_EQUIP)) { + conflict = LORE_EQUIP; + break; + } + } + } + + if(conflict > 0) + break; + + for(slot_itr=itr->second[1].begin();slot_itr!=itr->second[1].end(); slot_itr++){ + if(slot_itr->second && slot_itr->second->details.item_id == item->details.item_id){ + if(lore_stack_count) { + *lore_stack_count += slot_itr->second->details.count; + } + if(!is_stack_lore && slot_itr->second->CheckFlag(LORE)) { + conflict = LORE; + break; + } + else if(is_stack_lore && (*lore_stack_count + item->details.count) > slot_itr->second->stack_count) { + conflict = STACK_LORE; + break; + } + else if(!check_lore_only && slot_itr->second->CheckFlag(LORE_EQUIP)) { + conflict = LORE_EQUIP; + break; + } + } + } + + if(conflict > 0) + break; + } + + if(lock) + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + + return conflict; +} + + +EquipmentItemList::EquipmentItemList(){ + orig_packet = 0; + xor_packet = 0; + for(int8 i=0;iname.c_str(), slot, item->name.c_str()); + return false; + } + + SetItem(slot, item, true); + if (item->details.unique_id == 0) { + GetItem(slot)->details.unique_id = MasterItemList::NextUniqueID(); + if (item->IsBag()) + item->details.bag_id = item->details.unique_id; + } + MEquipmentItems.unlock(); + return true; + } + return false; +} + +int8 EquipmentItemList::GetNumberOfItems(){ + int8 ret = 0; + MEquipmentItems.lock(); + for(int8 i=0;igeneric_info.weight; + } + } + MEquipmentItems.unlock(); + return ret; +} + +void EquipmentItemList::SetItem(int8 slot_id, Item* item, bool locked){ + if(!locked) + MEquipmentItems.lock(); + item->details.bag_id = item->details.unique_id; + if(!item->IsBag()) { + item->details.inv_slot_id = 0; + } + else { + item->details.equip_slot_id = slot_id; + } + + if(!item->IsBag()) { + item->details.slot_id = slot_id; + item->details.index = slot_id; + } + item->details.appearance_type = GetAppearanceType(); + items[slot_id] = item; + + if(!locked) + MEquipmentItems.unlock(); +} + +vector* EquipmentItemList::GetAllEquippedItems(){ + vector* ret = new vector; + MEquipmentItems.lock(); + for(int8 i=0;ipush_back(items[i]); + } + MEquipmentItems.unlock(); + return ret; +} + +Item* EquipmentItemList::GetItem(int8 slot_id){ + return items[slot_id]; +} + +void EquipmentItemList::SendEquippedItems(Player* player){ + if(!player->GetClient()) { + return; + } + + for(int16 i=0;idetails.item_id > 0) + player->GetClient()->QueuePacket(item->serialize(player->GetClient()->GetVersion(), false, player)); + } +} + +EQ2Packet* EquipmentItemList::serialize(int16 version, Player* player){ + EQ2Packet* app = 0; + Item* item = 0; + PacketStruct* packet = configReader.getStruct("WS_UpdateInventory", version); + MEquipmentItems.lock(); + if(packet){ + int8 packet_size = 0; + PacketStruct* packet2 = configReader.getStruct("Substruct_Item", version); + packet_size = packet2->GetTotalPacketSize(); + safe_delete(packet2); + int8 num_slots = player->GetNumSlotsEquip(version); + packet->setArrayLengthByName("item_count", num_slots); + if(!orig_packet){ + xor_packet = new uchar[packet_size* num_slots]; + orig_packet = new uchar[packet_size* num_slots]; + memset(xor_packet, 0, packet_size* num_slots); + memset(orig_packet, 0, packet_size* num_slots); + } + int32 menu_data = 3; + int32 effective_level = player->GetInfoStructUInt("effective_level"); + + int32 levelsLowered = (effective_level > 0 && effective_level < player->GetLevel()) ? player->GetLevel() - effective_level : 0; + + for(int16 i=0;iConvertSlotFromClient(i, version); + + menu_data = 3; + item = items[itemIdx]; + if(item && item->details.item_id > 0){ + if(item->slot_data.size() > 0) + menu_data -= ITEM_MENU_TYPE_GENERIC; + if (item->details.num_slots > 0) { + int8 max_slots = player->GetMaxBagSlots(version); + if (item->details.num_slots > max_slots) + item->details.num_slots = max_slots; + + menu_data += ITEM_MENU_TYPE_BAG; + + if (item->details.num_free_slots == item->details.num_slots) + menu_data += ITEM_MENU_TYPE_EMPTY_BAG; + } + if(item->IsSkill()) + menu_data += ITEM_MENU_TYPE_SCRIBE; + if(item->generic_info.condition == 0) + menu_data += ITEM_MENU_TYPE_BROKEN2; + else if (item->generic_info.condition <= 20) + menu_data += ITEM_MENU_TYPE_DAMAGED; + if (item->CheckFlag(ATTUNED) || item->CheckFlag(NO_TRADE)) { + if (version <= 373) + menu_data += ORIG_ITEM_MENU_TYPE_ATTUNED; + else + menu_data += ITEM_MENU_TYPE_ATTUNED; + } + else if (item->CheckFlag(ATTUNEABLE)) { + if (version <= 373) + menu_data += ORIG_ITEM_MENU_TYPE_ATTUNEABLE; + else + menu_data += ITEM_MENU_TYPE_ATTUNEABLE; + } + if (item->generic_info.usable == 1) + menu_data += ITEM_MENU_TYPE_USE; + if (item->IsFood()) + { + if (version <= 373) { + if (item->IsFoodDrink()) + menu_data += ORIG_ITEM_MENU_TYPE_DRINK; + else + menu_data += ORIG_ITEM_MENU_TYPE_FOOD; + } + else { + menu_data += ITEM_MENU_TYPE_CONSUME; + if (player && ((item->IsFoodFood() && player->get_character_flag(CF_FOOD_AUTO_CONSUME)) || (item->IsFoodDrink() && player->get_character_flag(CF_DRINK_AUTO_CONSUME)))) + { + // needs all 3 to display 'auto consume' off option as well as set the yellowish tint in the background + menu_data += ITEM_MENU_TYPE_CONSUME_OFF; + menu_data += ORIG_ITEM_MENU_TYPE_DRINK; + menu_data += ORIG_ITEM_MENU_TYPE_FOOD; + } + } + } + packet->setSubstructArrayDataByName("items", "unique_id", item->details.unique_id, 0, i); + packet->setSubstructArrayDataByName("items", "bag_id", item->details.bag_id, 0, i); + packet->setSubstructArrayDataByName("items", "inv_slot_id", item->details.inv_slot_id, 0, i); + if (item->details.count > 0 && item->stack_count > 1) { + if (version <= 373) + menu_data += ORIG_ITEM_MENU_TYPE_STACKABLE; + else + menu_data += ITEM_MENU_TYPE_DISPLAY_CHARGES; + } + if(levelsLowered && item->details.recommended_level > effective_level) + menu_data += ITEM_MENU_TYPE_MENTORED; + packet->setSubstructArrayDataByName("items", "menu_type", menu_data, 0, i); + packet->setSubstructArrayDataByName("items", "icon", item->GetIcon(version), 0, i); + packet->setSubstructArrayDataByName("items", "slot_id", player->ConvertSlotToClient(item->details.equip_slot_id > 0 ? item->details.equip_slot_id : item->details.slot_id, version), 0, i); + packet->setSubstructArrayDataByName("items", "count", item->details.count, 0, i); + // item level needed here + + if(rule_manager.GetGlobalRule(R_World, DisplayItemTiers)->GetBool()) { + packet->setSubstructArrayDataByName("items", "tier", item->details.tier, 0, i); + } + packet->setSubstructArrayDataByName("items", "num_slots", item->details.num_slots, 0, i); + //empty slots needed here + packet->setSubstructArrayDataByName("items", "item_id", item->details.item_id, 0, i); + //broker id needed here + packet->setSubstructArrayDataByName("items", "name", item->name.c_str(), 0, i); + + //packet->setSubstructArrayDataByName("items", "unknown4", 10, 0, i); + + item->details.index = i; + } + packet->setSubstructArrayDataByName("items", "index", i, 0, i); + } + packet->setDataByName("equip_flag", GetAppearanceType() ? 2 : 1); + app = packet->serializeCountPacket(version, 1, orig_packet, xor_packet); + safe_delete(packet); + } + MEquipmentItems.unlock(); + return app; +} +ItemStatsValues* EquipmentItemList::CalculateEquipmentBonuses(Entity* entity){ + ItemStatsValues* stats = new ItemStatsValues; + memset(stats, 0, sizeof(ItemStatsValues)); + entity->GetInfoStruct()->set_mitigation_base(0); + MEquipmentItems.lock(); + for(int8 i=0;idetails.item_id > 0){ + master_item_list.CalculateItemBonuses(items[i], entity, stats); + if (items[i]->armor_info && !items[i]->IsShield()) + entity->GetInfoStruct()->add_mitigation_base(items[i]->armor_info->mitigation_high); + } + } + MEquipmentItems.unlock(); + return stats; +} +bool EquipmentItemList::HasItem(int32 id){ + MEquipmentItems.lock(); + for(int8 i=0;idetails.item_id == id){ + MEquipmentItems.unlock(); + return true; + } + } + MEquipmentItems.unlock(); + return false; +} +void EquipmentItemList::RemoveItem(int8 slot, bool delete_item){ + if(slot < NUM_SLOTS){ + MEquipmentItems.lock(); + if(items[slot] && items[slot]->details.appearance_type) + items[slot]->details.appearance_type = 0; + + if(delete_item){ + safe_delete(items[slot]); + } + items[slot] = 0; + MEquipmentItems.unlock(); + } +} + +Item* EquipmentItemList::GetItemFromUniqueID(int32 item_id){ + MEquipmentItems.lock(); + for(int8 i=0;idetails.unique_id == item_id){ + MEquipmentItems.unlock(); + return items[i]; + } + } + MEquipmentItems.unlock(); + return 0; +} + +Item* EquipmentItemList::GetItemFromItemID(int32 item_id) { + Item* item = 0; + MEquipmentItems.lock(); + for(int8 i = 0; i < NUM_SLOTS; i++) { + if(items[i] && items[i]->details.item_id == item_id) { + item = items[i]; + break; + } + } + MEquipmentItems.unlock(); + return item; +} + +bool EquipmentItemList::CanItemBeEquippedInSlot(Item* tmp, int8 slot){ + MEquipmentItems.lock(); + for(int8 i=0;tmp && islot_data.size();i++){ + if(tmp->slot_data[i] == slot){ + MEquipmentItems.unlock(); + return true; + } + } + MEquipmentItems.unlock(); + return false; +} +bool EquipmentItemList::CheckEquipSlot(Item* tmp, int8 slot){ + MEquipmentItems.lock(); + for(int8 i=0;tmp && islot_data.size();i++){ + if(tmp->slot_data[i] == slot){ + Item* tmp_item = GetItem(tmp->slot_data[i]); + if(!tmp_item || tmp_item->details.item_id == 0){ + if(slot == EQ2_SECONDARY_SLOT) + { + Item* primary = GetItem(EQ2_PRIMARY_SLOT); + if(primary && primary->weapon_info->wield_type == ITEM_WIELD_TYPE_TWO_HAND) + continue; + } + MEquipmentItems.unlock(); + return true; + } + } + } + MEquipmentItems.unlock(); + return false; +} + +int8 EquipmentItemList::GetFreeSlot(Item* tmp, int8 slot_id, int16 version){ + int8 slot = 0; + MEquipmentItems.lock(); + for(int8 i=0;tmp && islot_data.size();i++){ + slot = tmp->slot_data[i]; + if(slot_id == 255 || slot == slot_id){ + Item* tmp_item = GetItem(slot); + if(!tmp_item || tmp_item->details.item_id == 0){ + if(slot == EQ2_SECONDARY_SLOT) + { + Item* primary = GetItem(EQ2_PRIMARY_SLOT); + if(primary && primary->weapon_info->wield_type == ITEM_WIELD_TYPE_TWO_HAND) + continue; + } + MEquipmentItems.unlock(); + return slot; + } + else if ( slot == EQ2_LRING_SLOT || slot == EQ2_EARS_SLOT_1 || slot == EQ2_LWRIST_SLOT || slot == EQ2_CHARM_SLOT_1) + { + if(version <= 561 && slot == EQ2_EARS_SLOT_1) + continue; + + Item* rslot = GetItem(slot+1); + if(!rslot) + { + MEquipmentItems.unlock(); + return slot+1; + } + } + } + } + MEquipmentItems.unlock(); + return 255; +} + +int32 EquipmentItemList::CheckSlotConflict(Item* item, bool check_lore_only, int16* lore_stack_count) { + bool is_lore = false; + bool is_stack_lore = false; + if(!(is_lore = item->CheckFlag(LORE)) && !(is_stack_lore = item->CheckFlag(STACK_LORE)) && check_lore_only) { + return 0; + } + + if(!check_lore_only && !is_lore && !is_stack_lore && !item->CheckFlag(LORE_EQUIP)) { + return 0; + } + + int32 conflict = 0; + MEquipmentItems.lock(); + for(int8 i=0;idetails.item_id == item->details.item_id) { + if(lore_stack_count) + *lore_stack_count += items[i]->details.count; + if(!is_stack_lore && items[i]->CheckFlag(LORE)) { + conflict = LORE; + break; + } + else if(is_stack_lore && (*lore_stack_count + item->details.count) > items[i]->stack_count) { + conflict = STACK_LORE; + break; + } + else if(!check_lore_only && items[i]->CheckFlag(LORE_EQUIP)) { + conflict = LORE_EQUIP; + break; + } + } + } + + MEquipmentItems.unlock(); + return conflict; +} + +int8 EquipmentItemList::GetSlotByItem(Item* item) { + int8 slot = 255; + for (int8 i = 0; i < NUM_SLOTS; i++) { + if (items[i] && items[i] == item) { + slot = i; + break; + } + } + return slot; +} + +string Item::CreateItemLink(int16 client_Version, bool bUseUniqueID) { + ostringstream ss; + if(client_Version > 561) + ss << "\\aITEM " << details.item_id << ' ' << (bUseUniqueID ? details.unique_id : 0) << ':' << name << "\\/a"; + else { + if(bUseUniqueID) + ss << "\\aITEM " << details.item_id << ' ' << details.unique_id << ':' << name << "\\/a"; + else + ss << "\\aITEM " << details.item_id << ' ' << name << ':' << name << "\\/a"; + } + return ss.str(); +} + +int16 Item::GetIcon(int16 version) { + if(version <= 561 && details.classic_icon) { + return details.classic_icon; + } + + return details.icon; +} + +int32 MasterItemList::GetItemStatIDByName(std::string name) +{ + boost::to_lower(name); + map::iterator itr = mappedItemStatsStrings.find(name.c_str()); + if(itr != mappedItemStatsStrings.end()) + return itr->second; + + return 0xFFFFFFFF; +} + +std::string MasterItemList::GetItemStatNameByID(int32 id) +{ + map::iterator itr = mappedItemStatTypeIDs.find(id); + if(itr != mappedItemStatTypeIDs.end()) + return itr->second; + + return std::string(""); +} diff --git a/source/WorldServer/Items/Items.h b/source/WorldServer/Items/Items.h new file mode 100644 index 0000000..c098f50 --- /dev/null +++ b/source/WorldServer/Items/Items.h @@ -0,0 +1,1223 @@ +/* + 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 . +*/ +#ifndef __EQ2_ITEMS__ +#define __EQ2_ITEMS__ +#include +#include +#include +#include "../../common/types.h" +#include "../../common/DataBuffer.h" +#include "../Commands/Commands.h" +#include "../../common/ConfigReader.h" + +using namespace std; +class MasterItemList; +class Player; +class Entity; +extern MasterItemList master_item_list; + +#define BASE_EQUIPMENT 0 +#define APPEARANCE_EQUIPMENT 1 +#define MAX_EQUIPMENT 2 // max iterations for equipment (base is 0, appearance is 1, so this is 2) + +#define EQ2_PRIMARY_SLOT 0 +#define EQ2_SECONDARY_SLOT 1 +#define EQ2_HEAD_SLOT 2 +#define EQ2_CHEST_SLOT 3 +#define EQ2_SHOULDERS_SLOT 4 +#define EQ2_FOREARMS_SLOT 5 +#define EQ2_HANDS_SLOT 6 +#define EQ2_LEGS_SLOT 7 +#define EQ2_FEET_SLOT 8 +#define EQ2_LRING_SLOT 9 +#define EQ2_RRING_SLOT 10 +#define EQ2_EARS_SLOT_1 11 +#define EQ2_EARS_SLOT_2 12 +#define EQ2_NECK_SLOT 13 +#define EQ2_LWRIST_SLOT 14 +#define EQ2_RWRIST_SLOT 15 +#define EQ2_RANGE_SLOT 16 +#define EQ2_AMMO_SLOT 17 +#define EQ2_WAIST_SLOT 18 +#define EQ2_CLOAK_SLOT 19 +#define EQ2_CHARM_SLOT_1 20 +#define EQ2_CHARM_SLOT_2 21 +#define EQ2_FOOD_SLOT 22 +#define EQ2_DRINK_SLOT 23 +#define EQ2_TEXTURES_SLOT 24 +#define EQ2_HAIR_SLOT 25 +#define EQ2_BEARD_SLOT 26 +#define EQ2_WINGS_SLOT 27 +#define EQ2_NAKED_CHEST_SLOT 28 +#define EQ2_NAKED_LEGS_SLOT 29 +#define EQ2_BACK_SLOT 30 +#define EQ2_ORIG_FOOD_SLOT 18 +#define EQ2_ORIG_DRINK_SLOT 19 +#define EQ2_DOF_FOOD_SLOT 20 +#define EQ2_DOF_DRINK_SLOT 21 + +#define PRIMARY_SLOT 1 +#define SECONDARY_SLOT 2 +#define HEAD_SLOT 4 +#define CHEST_SLOT 8 +#define SHOULDERS_SLOT 16 +#define FOREARMS_SLOT 32 +#define HANDS_SLOT 64 +#define LEGS_SLOT 128 +#define FEET_SLOT 256 +#define LRING_SLOT 512 +#define RRING_SLOT 1024 +#define EARS_SLOT_1 2048 +#define EARS_SLOT_2 4096 +#define NECK_SLOT 8192 +#define LWRIST_SLOT 16384 +#define RWRIST_SLOT 32768 +#define RANGE_SLOT 65536 +#define AMMO_SLOT 131072 +#define WAIST_SLOT 262144 +#define CLOAK_SLOT 524288 +#define CHARM_SLOT_1 1048576 +#define CHARM_SLOT_2 2097152 +#define FOOD_SLOT 4194304 +#define DRINK_SLOT 8388608 +#define TEXTURES_SLOT 16777216 +#define HAIR_SLOT 33554432 +#define BEARD_SLOT 67108864 +#define WINGS_SLOT 134217728 +#define NAKED_CHEST_SLOT 268435456 +#define NAKED_LEGS_SLOT 536870912 +#define BACK_SLOT 1073741824 +#define ORIG_FOOD_SLOT 524288 +#define ORIG_DRINK_SLOT 1048576 +#define DOF_FOOD_SLOT 1048576 +#define DOF_DRINK_SLOT 2097152 + +#define CLASSIC_EQ_MAX_BAG_SLOTS 20 +#define DOF_EQ_MAX_BAG_SLOTS 36 +#define NUM_BANK_SLOTS 12 +#define NUM_SHARED_BANK_SLOTS 8 +#define CLASSIC_NUM_SLOTS 22 +#define NUM_SLOTS 25 +#define NUM_INV_SLOTS 6 +#define INV_SLOT1 0 +#define INV_SLOT2 50 +#define INV_SLOT3 100 +#define INV_SLOT4 150 +#define INV_SLOT5 200 +#define INV_SLOT6 250 +#define BANK_SLOT1 1000 +#define BANK_SLOT2 1100 +#define BANK_SLOT3 1200 +#define BANK_SLOT4 1300 +#define BANK_SLOT5 1400 +#define BANK_SLOT6 1500 +#define BANK_SLOT7 1600 +#define BANK_SLOT8 1700 + +// FLAGS +#define ATTUNED 1 +#define ATTUNEABLE 2 +#define ARTIFACT 4 +#define LORE 8 +#define TEMPORARY 16 +#define NO_TRADE 32 +#define NO_VALUE 64 +#define NO_ZONE 128 +#define NO_DESTROY 256 +#define CRAFTED 512 +#define GOOD_ONLY 1024 +#define EVIL_ONLY 2048 +#define STACK_LORE 4096 +#define LORE_EQUIP 8192 +#define NO_TRANSMUTE 16384 +#define CURSED 32768 + +// FLAGS2 +#define ORNATE 1 +#define HEIRLOOM 2 +#define APPEARANCE_ONLY 4 +#define UNLOCKED 8 +#define REFORGED 16 +#define NO_REPAIR 32 +#define ETHERAL 64 +#define REFINED 128 +#define NO_SALVAGE 256 +#define INDESTRUCTABLE 512 +#define NO_EXPERIMENT 1024 +#define HOUSE_LORE 2048 +#define FLAGS2_4096 4096//AoM: not used at this time +#define BUILDING_BLOCK 8192 +#define FREE_REFORGE 16384 +#define FLAGS2_32768 32768//AoM: not used at this time + + +#define ITEM_WIELD_TYPE_DUAL 1 +#define ITEM_WIELD_TYPE_SINGLE 2 +#define ITEM_WIELD_TYPE_TWO_HAND 4 + +#define ITEM_TYPE_NORMAL 0 +#define ITEM_TYPE_WEAPON 1 +#define ITEM_TYPE_RANGED 2 +#define ITEM_TYPE_ARMOR 3 +#define ITEM_TYPE_SHIELD 4 +#define ITEM_TYPE_BAG 5 +#define ITEM_TYPE_SKILL 6 +#define ITEM_TYPE_RECIPE 7 +#define ITEM_TYPE_FOOD 8 +#define ITEM_TYPE_BAUBLE 9 +#define ITEM_TYPE_HOUSE 10 +#define ITEM_TYPE_THROWN 11 +#define ITEM_TYPE_HOUSE_CONTAINER 12 +#define ITEM_TYPE_ADORNMENT 13 +#define ITEM_TYPE_GENERIC_ADORNMENT 14 +#define ITEM_TYPE_PROFILE 16 +#define ITEM_TYPE_PATTERN 17 +#define ITEM_TYPE_ARMORSET 18 +#define ITEM_TYPE_ITEMCRATE 18 +#define ITEM_TYPE_BOOK 19 +#define ITEM_TYPE_DECORATION 20 +#define ITEM_TYPE_DUNGEON_MAKER 21 +#define ITEM_TYPE_MARKETPLACE 22 + + +//DOV defines everything till 13 is the same +//#define ITEM_TYPE_BOOK 13 +//#define ITEM_TYPE_ADORNMENT 14 +//#define ITEM_TYPE_PATTERN 15 +//#define ITEM_TYPE_ARMORSET 16 + + + +#define ITEM_MENU_TYPE_GENERIC 1 //0 (NON_EQUIPABLE) +#define ITEM_MENU_TYPE_EQUIP 2 //1 (This is SLOT_FULL for classic) +#define ITEM_MENU_TYPE_BAG 4//2 +#define ITEM_MENU_TYPE_HOUSE 8 //3 Place +#define ITEM_MENU_TYPE_EMPTY_BAG 16 //4 +#define ITEM_MENU_TYPE_SCRIBE 32//5 +#define ITEM_MENU_TYPE_BANK_BAG 64//6 +#define ITEM_MENU_TYPE_INSUFFICIENT_KNOWLEDGE 128//7 +#define ITEM_MENU_TYPE_ACTIVATE 256//8 +#define ITEM_MENU_TYPE_BROKEN 512//9 +#define ITEM_MENU_TYPE_TWO_HANDED 1024//10 +#define ITEM_MENU_TYPE_ATTUNED 2048//11 +#define ITEM_MENU_TYPE_ATTUNEABLE 4096//12 +#define ITEM_MENU_TYPE_BOOK 8192//13 +#define ITEM_MENU_TYPE_DISPLAY_CHARGES 16384//14 +#define ITEM_MENU_TYPE_TEST1 32768//15 Possibly toogle decorator mode +#define ITEM_MENU_TYPE_NAMEPET 65536 //16 Right CLick Menu +#define ITEM_MENU_TYPE_MENTORED 131072 //sets a purple background on item +#define ITEM_MENU_TYPE_CONSUME 262144//18 +#define ITEM_MENU_TYPE_USE 524288//19 +#define ITEM_MENU_TYPE_CONSUME_OFF 1048576//20 +#define ITEM_MENU_TYPE_TEST3 1310720// bad number combo of 2 bits +#define ITEM_MENU_TYPE_TEST4 2097152//21 +#define ITEM_MENU_TYPE_TEST5 4194304//22 infusable +#define ITEM_MENU_TYPE_TEST6 8388608 //drink option on menu +#define ITEM_MENU_TYPE_TEST7 16777216//24 +#define ITEM_MENU_TYPE_TEST8 33554432 // bit 25 use option in bags +#define ITEM_MENU_TYPE_TEST9 67108864//26 +#define ITEM_MENU_TYPE_DAMAGED 134217728 //27 +#define ITEM_MENU_TYPE_BROKEN2 268435456 //28 +#define ITEM_MENU_TYPE_REDEEM 536870912 //29 //READ?? +#define ITEM_MENU_TYPE_TEST10 1073741824 //30 +#define ITEM_MENU_TYPE_UNPACK 2147483648//31 * on items i found this unpack is used at same time as UNPACK below +#define ORIG_ITEM_MENU_TYPE_FOOD 2048 +#define ORIG_ITEM_MENU_TYPE_DRINK 4096 +#define ORIG_ITEM_MENU_TYPE_ATTUNED 8192 +#define ORIG_ITEM_MENU_TYPE_ATTUNEABLE 16384 +#define ORIG_ITEM_MENU_TYPE_BOOK 32768 +#define ORIG_ITEM_MENU_TYPE_STACKABLE 65536 +#define ORIG_ITEM_MENU_TYPE_NAMEPET 262144 + +#define ITEM_MENU_TYPE2_TEST1 1 //0 auto consume on +#define ITEM_MENU_TYPE2_TEST2 2 //1 +#define ITEM_MENU_TYPE2_UNPACK 4//2 +#define ITEM_MENU_TYPE2_TEST4 8 //3 +#define ITEM_MENU_TYPE2_TEST5 16 //4 +#define ITEM_MENU_TYPE2_TEST6 32//5 +#define ITEM_MENU_TYPE2_TEST7 64//6 +#define ITEM_MENU_TYPE2_TEST8 128//7 +#define ITEM_MENU_TYPE2_TEST9 256//8 +#define ITEM_MENU_TYPE2_TEST10 512//9 +#define ITEM_MENU_TYPE2_TEST11 1024//10 +#define ITEM_MENU_TYPE2_TEST12 2048//11 +#define ITEM_MENU_TYPE2_TEST13 4096//12 +#define ITEM_MENU_TYPE2_TEST14 8192//13 +#define ITEM_MENU_TYPE2_TEST15 16384//14 +#define ITEM_MENU_TYPE2_TEST16 32768//15 + +#define ITEM_TAG_COMMON 2 +#define ITEM_TAG_UNCOMMON 3 //tier tags +#define ITEM_TAG_TREASURED 4 +#define ITEM_TAG_LEGENDARY 7 +#define ITEM_TAG_FABLED 9 +#define ITEM_TAG_MYTHICAL 12 + +#define ITEM_BROKER_TYPE_ANY 0xFFFFFFFF +#define ITEM_BROKER_TYPE_ANY64BIT 0xFFFFFFFFFFFFFFFF +#define ITEM_BROKER_TYPE_ADORNMENT 134217728 +#define ITEM_BROKER_TYPE_AMMO 1024 +#define ITEM_BROKER_TYPE_ATTUNEABLE 16384 +#define ITEM_BROKER_TYPE_BAG 2048 +#define ITEM_BROKER_TYPE_BAUBLE 16777216 +#define ITEM_BROKER_TYPE_BOOK 128 +#define ITEM_BROKER_TYPE_CHAINARMOR 2097152 +#define ITEM_BROKER_TYPE_CLOAK 1073741824 +#define ITEM_BROKER_TYPE_CLOTHARMOR 524288 +#define ITEM_BROKER_TYPE_COLLECTABLE 67108864 +#define ITEM_BROKER_TYPE_CRUSHWEAPON 4 +#define ITEM_BROKER_TYPE_DRINK 131072 +#define ITEM_BROKER_TYPE_FOOD 4096 +#define ITEM_BROKER_TYPE_HOUSEITEM 512 +#define ITEM_BROKER_TYPE_JEWELRY 262144 +#define ITEM_BROKER_TYPE_LEATHERARMOR 1048576 +#define ITEM_BROKER_TYPE_LORE 8192 +#define ITEM_BROKER_TYPE_MISC 1 +#define ITEM_BROKER_TYPE_PIERCEWEAPON 8 +#define ITEM_BROKER_TYPE_PLATEARMOR 4194304 +#define ITEM_BROKER_TYPE_POISON 65536 +#define ITEM_BROKER_TYPE_POTION 32768 +#define ITEM_BROKER_TYPE_RECIPEBOOK 8388608 +#define ITEM_BROKER_TYPE_SALESDISPLAY 33554432 +#define ITEM_BROKER_TYPE_SHIELD 32 +#define ITEM_BROKER_TYPE_SLASHWEAPON 2 +#define ITEM_BROKER_TYPE_SPELLSCROLL 64 +#define ITEM_BROKER_TYPE_TINKERED 268435456 +#define ITEM_BROKER_TYPE_TRADESKILL 256 + +#define ITEM_BROKER_TYPE_2H_CRUSH 17179869184 +#define ITEM_BROKER_TYPE_2H_PIERCE 34359738368 +#define ITEM_BROKER_TYPE_2H_SLASH 8589934592 + +#define ITEM_BROKER_SLOT_ANY 0xFFFFFFFF +#define ITEM_BROKER_SLOT_AMMO 65536 +#define ITEM_BROKER_SLOT_CHARM 524288 +#define ITEM_BROKER_SLOT_CHEST 32 +#define ITEM_BROKER_SLOT_CLOAK 262144 +#define ITEM_BROKER_SLOT_DRINK 2097152 +#define ITEM_BROKER_SLOT_EARS 4096 +#define ITEM_BROKER_SLOT_FEET 1024 +#define ITEM_BROKER_SLOT_FOOD 1048576 +#define ITEM_BROKER_SLOT_FOREARMS 128 +#define ITEM_BROKER_SLOT_HANDS 256 +#define ITEM_BROKER_SLOT_HEAD 16 +#define ITEM_BROKER_SLOT_LEGS 512 +#define ITEM_BROKER_SLOT_NECK 8192 +#define ITEM_BROKER_SLOT_PRIMARY 1 +#define ITEM_BROKER_SLOT_PRIMARY_2H 2 +#define ITEM_BROKER_SLOT_RANGE_WEAPON 32768 +#define ITEM_BROKER_SLOT_RING 2048 +#define ITEM_BROKER_SLOT_SECONDARY 8 +#define ITEM_BROKER_SLOT_SHOULDERS 64 +#define ITEM_BROKER_SLOT_WAIST 131072 +#define ITEM_BROKER_SLOT_WRIST 16384 + +#define ITEM_BROKER_STAT_TYPE_NONE 0 +#define ITEM_BROKER_STAT_TYPE_DEF 2 +#define ITEM_BROKER_STAT_TYPE_STR 4 +#define ITEM_BROKER_STAT_TYPE_STA 8 +#define ITEM_BROKER_STAT_TYPE_AGI 16 +#define ITEM_BROKER_STAT_TYPE_WIS 32 +#define ITEM_BROKER_STAT_TYPE_INT 64 +#define ITEM_BROKER_STAT_TYPE_HEALTH 128 +#define ITEM_BROKER_STAT_TYPE_POWER 256 +#define ITEM_BROKER_STAT_TYPE_HEAT 512 +#define ITEM_BROKER_STAT_TYPE_COLD 1024 +#define ITEM_BROKER_STAT_TYPE_MAGIC 2048 +#define ITEM_BROKER_STAT_TYPE_MENTAL 4096 +#define ITEM_BROKER_STAT_TYPE_DIVINE 8192 +#define ITEM_BROKER_STAT_TYPE_POISON 16384 +#define ITEM_BROKER_STAT_TYPE_DISEASE 32768 +#define ITEM_BROKER_STAT_TYPE_CRUSH 65536 +#define ITEM_BROKER_STAT_TYPE_SLASH 131072 +#define ITEM_BROKER_STAT_TYPE_PIERCE 262144 +#define ITEM_BROKER_STAT_TYPE_CRITICAL 524288 +#define ITEM_BROKER_STAT_TYPE_DBL_ATTACK 1048576 +#define ITEM_BROKER_STAT_TYPE_ABILITY_MOD 2097152 +#define ITEM_BROKER_STAT_TYPE_POTENCY 4194304 +#define ITEM_BROKER_STAT_TYPE_AEAUTOATTACK 8388608 +#define ITEM_BROKER_STAT_TYPE_ATTACKSPEED 16777216 +#define ITEM_BROKER_STAT_TYPE_BLOCKCHANCE 33554432 +#define ITEM_BROKER_STAT_TYPE_CASTINGSPEED 67108864 +#define ITEM_BROKER_STAT_TYPE_CRITBONUS 134217728 +#define ITEM_BROKER_STAT_TYPE_CRITCHANCE 268435456 +#define ITEM_BROKER_STAT_TYPE_DPS 536870912 +#define ITEM_BROKER_STAT_TYPE_FLURRYCHANCE 1073741824 +#define ITEM_BROKER_STAT_TYPE_HATEGAIN 2147483648 +#define ITEM_BROKER_STAT_TYPE_MITIGATION 4294967296 +#define ITEM_BROKER_STAT_TYPE_MULTI_ATTACK 8589934592 +#define ITEM_BROKER_STAT_TYPE_RECOVERY 17179869184 +#define ITEM_BROKER_STAT_TYPE_REUSE_SPEED 34359738368 +#define ITEM_BROKER_STAT_TYPE_SPELL_WPNDMG 68719476736 +#define ITEM_BROKER_STAT_TYPE_STRIKETHROUGH 137438953472 +#define ITEM_BROKER_STAT_TYPE_TOUGHNESS 274877906944 +#define ITEM_BROKER_STAT_TYPE_WEAPONDMG 549755813888 + + +#define OVERFLOW_SLOT 0xFFFFFFFE +#define SLOT_INVALID 0xFFFF + +#define ITEM_STAT_STR 0 +#define ITEM_STAT_STA 1 +#define ITEM_STAT_AGI 2 +#define ITEM_STAT_WIS 3 +#define ITEM_STAT_INT 4 + +#define ITEM_STAT_ADORNING 100 +#define ITEM_STAT_AGGRESSION 101 +#define ITEM_STAT_ARTIFICING 102 +#define ITEM_STAT_ARTISTRY 103 +#define ITEM_STAT_CHEMISTRY 104 +#define ITEM_STAT_CRUSHING 105 +#define ITEM_STAT_DEFENSE 106 +#define ITEM_STAT_DEFLECTION 107 +#define ITEM_STAT_DISRUPTION 108 +#define ITEM_STAT_FISHING 109 +#define ITEM_STAT_FLETCHING 110 +#define ITEM_STAT_FOCUS 111 +#define ITEM_STAT_FORESTING 112 +#define ITEM_STAT_GATHERING 113 +#define ITEM_STAT_METAL_SHAPING 114 +#define ITEM_STAT_METALWORKING 115 +#define ITEM_STAT_MINING 116 +#define ITEM_STAT_MINISTRATION 117 +#define ITEM_STAT_ORDINATION 118 +#define ITEM_STAT_PARRY 119 +#define ITEM_STAT_PIERCING 120 +#define ITEM_STAT_RANGED 121 +#define ITEM_STAT_SAFE_FALL 122 +#define ITEM_STAT_SCRIBING 123 +#define ITEM_STAT_SCULPTING 124 +#define ITEM_STAT_SLASHING 125 +#define ITEM_STAT_SUBJUGATION 126 +#define ITEM_STAT_SWIMMING 127 +#define ITEM_STAT_TAILORING 128 +#define ITEM_STAT_TINKERING 129 +#define ITEM_STAT_TRANSMUTING 130 +#define ITEM_STAT_TRAPPING 131 +#define ITEM_STAT_WEAPON_SKILLS 132 +#define ITEM_STAT_POWER_COST_REDUCTION 133 +#define ITEM_STAT_SPELL_AVOIDANCE 134 + +#define ITEM_STAT_VS_PHYSICAL 200 +#define ITEM_STAT_VS_HEAT 201 //elemental +#define ITEM_STAT_VS_POISON 202 //noxious +#define ITEM_STAT_VS_MAGIC 203 //arcane +#define ITEM_STAT_VS_DROWNING 210 +#define ITEM_STAT_VS_FALLING 211 +#define ITEM_STAT_VS_PAIN 212 +#define ITEM_STAT_VS_MELEE 213 + +#define ITEM_STAT_VS_SLASH 204 +#define ITEM_STAT_VS_CRUSH 205 +#define ITEM_STAT_VS_PIERCE 206 +//#define ITEM_STAT_VS_HEAT 203 //just so no build error +#define ITEM_STAT_VS_COLD 207 +//#define ITEM_STAT_VS_MAGIC 205 //just so no build error +#define ITEM_STAT_VS_MENTAL 208 +#define ITEM_STAT_VS_DIVINE 209 +#define ITEM_STAT_VS_DISEASE 214 +//#define ITEM_STAT_VS_POISON 209 //just so no build error +//#define ITEM_STAT_VS_DROWNING 210 //just so no build error +//#define ITEM_STAT_VS_FALLING 211 //just so no build error +//#define ITEM_STAT_VS_PAIN 212 //just so no build error +//#define ITEM_STAT_VS_MELEE 213 //just so no build error + +#define ITEM_STAT_DMG_SLASH 300 +#define ITEM_STAT_DMG_CRUSH 301 +#define ITEM_STAT_DMG_PIERCE 302 +#define ITEM_STAT_DMG_HEAT 303 +#define ITEM_STAT_DMG_COLD 304 +#define ITEM_STAT_DMG_MAGIC 305 +#define ITEM_STAT_DMG_MENTAL 306 +#define ITEM_STAT_DMG_DIVINE 307 +#define ITEM_STAT_DMG_DISEASE 308 +#define ITEM_STAT_DMG_POISON 309 +#define ITEM_STAT_DMG_DROWNING 310 +#define ITEM_STAT_DMG_FALLING 311 +#define ITEM_STAT_DMG_PAIN 312 +#define ITEM_STAT_DMG_MELEE 313 + +#define ITEM_STAT_DEFLECTIONCHANCE 400 //just so no build error + +#define ITEM_STAT_HEALTH 500 +#define ITEM_STAT_POWER 501 +#define ITEM_STAT_CONCENTRATION 502 +#define ITEM_STAT_SAVAGERY 503 + +//this is the master stat list you should be using and names match what is in census. it is based off of DoV. the comment is what is displayed on items when examining +//the itemstats table will maintain the custom lists per expansion +// emu # is digits after the 6 + +#define ITEM_STAT_HPREGEN 600 //Health Regeneration +#define ITEM_STAT_MANAREGEN 601 //Power Regeneration +#define ITEM_STAT_HPREGENPPT 602 //Out-of-Combat Health Regeneration %%? +#define ITEM_STAT_MPREGENPPT 603 //Out-of-Combat Power Regeneration %%? +#define ITEM_STAT_COMBATHPREGENPPT 604 //In-Combat Health Regeneration %%? +#define ITEM_STAT_COMBATMPREGENPPT 605 //In-Combat Power Regeneration %%? +#define ITEM_STAT_MAXHP 606 //Max Health +#define ITEM_STAT_MAXHPPERC 607 +#define ITEM_STAT_MAXHPPERCFINAL 608 //% Max Mealth +#define ITEM_STAT_SPEED 609 //Out of Combat Run Speed +#define ITEM_STAT_SLOW 610 //Slow +#define ITEM_STAT_MOUNTSPEED 611 //Ground Mount Speed +#define ITEM_STAT_MOUNTAIRSPEED 612 //Mount Air Speed +#define ITEM_STAT_LEAPSPEED 613 +#define ITEM_STAT_LEAPTIME 614 +#define ITEM_STAT_GLIDEEFFICIENCY 615 +#define ITEM_STAT_OFFENSIVESPEED 616 //In Combat Run Speed +#define ITEM_STAT_ATTACKSPEED 617 //% Attack Speed +#define ITEM_STAT_SPELLWEAPONATTACKSPEED 618 +#define ITEM_STAT_MAXMANA 619 //Max Power +#define ITEM_STAT_MAXMANAPERC 620 //% Max Power +#define ITEM_STAT_MAXATTPERC 621 //All Attributes //is this a percent or is it a stat change +#define ITEM_STAT_BLURVISION 622 //Blurs Vision +#define ITEM_STAT_MAGICLEVELIMMUNITY 623 //Magic Level Immunity +#define ITEM_STAT_HATEGAINMOD 624 //% Hate Gain +#define ITEM_STAT_COMBATEXPMOD 625 //Combat XP Gain +#define ITEM_STAT_TRADESKILLEXPMOD 626 //Tradeskill XP Gain +#define ITEM_STAT_ACHIEVEMENTEXPMOD 627 //AA XP Gain +#define ITEM_STAT_SIZEMOD 628 //Size +#define ITEM_STAT_DPS 629 //%Damage Per Second +#define ITEM_STAT_SPELLWEAPONDPS 630 //%Damage Per Second +#define ITEM_STAT_STEALTH 631 //Stealth +#define ITEM_STAT_INVIS 632 //Invisibility +#define ITEM_STAT_SEESTEALTH 633 //See Stealth +#define ITEM_STAT_SEEINVIS 634 //See Invisible +#define ITEM_STAT_EFFECTIVELEVELMOD 635 //Effective Level +#define ITEM_STAT_RIPOSTECHANCE 636 //%Extra Riposte Chance +#define ITEM_STAT_PARRYCHANCE 637 //%Extra Parry Chance +#define ITEM_STAT_DODGECHANCE 638 //%Extra Dodge Chance +#define ITEM_STAT_AEAUTOATTACKCHANCE 639 //% AE Autoattck Chance +#define ITEM_STAT_SPELLWEAPONAEAUTOATTACKCHANCE 640 // +#define ITEM_STAT_MULTIATTACKCHANCE 641 //% Multi Attack Chance // inconsistant with db +#define ITEM_STAT_PVPDOUBLEATTACKCHANCE 642 +#define ITEM_STAT_SPELLWEAPONDOUBLEATTACKCHANCE 643 // missing in db +#define ITEM_STAT_PVPSPELLWEAPONDOUBLEATTACKCHANCE 644 +#define ITEM_STAT_SPELLMULTIATTACKCHANCE 645 //% Spell Multi Atttack Chance +#define ITEM_STAT_PVPSPELLDOUBLEATTACKCHANCE 646 +#define ITEM_STAT_FLURRY 647 //%Flurry +#define ITEM_STAT_SPELLWEAPONFLURRY 648 +#define ITEM_STAT_MELEEDAMAGEMULTIPLIER 649 //Melee Damage Multiplier +#define ITEM_STAT_EXTRAHARVESTCHANCE 650 //Extra Harvest Chance +#define ITEM_STAT_EXTRASHIELDBLOCKCHANCE 651 //Block Chance +#define ITEM_STAT_ITEMHPREGENPPT 652 //In-Combat Health Regeneration +#define ITEM_STAT_ITEMPPREGENPPT 653 //In-Combat Power Regeneration +#define ITEM_STAT_MELEECRITCHANCE 654 //% Crit Chance +#define ITEM_STAT_CRITAVOIDANCE 655 //% Crit Avoidance +#define ITEM_STAT_BENEFICIALCRITCHANCE 656 //% Beneficial Crit Chance +#define ITEM_STAT_CRITBONUS 657 //% Crit Bonus +#define ITEM_STAT_PVPCRITBONUS 658 +#define ITEM_STAT_POTENCY 659 //% Potency +#define ITEM_STAT_PVPPOTENCY 660 +#define ITEM_STAT_UNCONSCIOUSHPMOD 661 //Unconcious Health +#define ITEM_STAT_ABILITYREUSESPEED 662 //% Ability Reuse Speed +#define ITEM_STAT_ABILITYRECOVERYSPEED 663 //% Ability Recovery Speed +#define ITEM_STAT_ABILITYCASTINGSPEED 664 //% Ability Casting Speed +#define ITEM_STAT_SPELLREUSESPEED 665 //% Spell Reuse Speed +#define ITEM_STAT_MELEEWEAPONRANGE 666 //% Melee Weapon Range Increase +#define ITEM_STAT_RANGEDWEAPONRANGE 667 //% Ranged Weapon Range Increase +#define ITEM_STAT_FALLINGDAMAGEREDUCTION 668 //Fallling Damage Reduction +#define ITEM_STAT_RIPOSTEDAMAGE 669 //% Riposte Damage +#define ITEM_STAT_MINIMUMDEFLECTIONCHANCE 670 //% Minimum Block Chance +#define ITEM_STAT_MOVEMENTWEAVE 671 //Movement Weave +#define ITEM_STAT_COMBATHPREGEN 672 //Combat HP Regen +#define ITEM_STAT_COMBATMANAREGEN 673 //Combat Mana Regen +#define ITEM_STAT_CONTESTSPEEDBOOST 674 //Contest Only Speed +#define ITEM_STAT_TRACKINGAVOIDANCE 675 //Tracking avoidance +#define ITEM_STAT_STEALTHINVISSPEEDMOD 676 //Movement Bonus whie Stealthed or Invisible +#define ITEM_STAT_LOOT_COIN 677 //Loot Coin +#define ITEM_STAT_ARMORMITIGATIONINCREASE 678 //% Mitigation Increase +#define ITEM_STAT_AMMOCONSERVATION 679 // Ammo Conservation +#define ITEM_STAT_STRIKETHROUGH 680 //Strikethrough +#define ITEM_STAT_STATUSBONUS 681 //Status Bonus +#define ITEM_STAT_ACCURACY 682 //% Accuracy +#define ITEM_STAT_COUNTERSTRIKE 683 //CounterStrike +#define ITEM_STAT_SHIELDBASH 684 //Shield Bash +#define ITEM_STAT_WEAPONDAMAGEBONUS 685 //Weapon Damage Bonus +#define ITEM_STAT_WEAPONDAMAGEBONUSMELEEONLY 686 //additional chance to Riposte +#define ITEM_STAT_ADDITIONALRIPOSTECHANCE 687 //additional chance to Riposte +#define ITEM_STAT_CRITICALMITIGATION 688 //Critical Mitigation +#define ITEM_STAT_PVPTOUGHNESS 689 //Toughness +#define ITEM_STAT_PVPLETHALITY 690 // +#define ITEM_STAT_STAMINABONUS 691 //Stamina Bonus +#define ITEM_STAT_WISDOMMITBONUS 692 //Wisdom Mitigation Bonus +#define ITEM_STAT_HEALRECEIVE 693 //Applied Heals +#define ITEM_STAT_HEALRECEIVEPERC 694 //% Applied Heals +#define ITEM_STAT_PVPCRITICALMITIGATION 695 //PvP Critical Mitigation +#define ITEM_STAT_BASEAVOIDANCEBONUS 696 +#define ITEM_STAT_INCOMBATSAVAGERYREGEN 697 +#define ITEM_STAT_OUTOFCOMBATSAVAGERYREGEN 698 +#define ITEM_STAT_SAVAGERYREGEN 699 +#define ITEM_STAT_SAVAGERYGAINMOD 6100 +#define ITEM_STAT_MAXSAVAGERYLEVEL 6101 +#define ITEM_STAT_SPELLWEAPONDAMAGEBONUS 6102 +#define ITEM_STAT_INCOMBATDISSONANCEREGEN 6103 +#define ITEM_STAT_OUTOFCOMBATDISSONANCEREGEN 6104 +#define ITEM_STAT_DISSONANCEREGEN 6105 +#define ITEM_STAT_DISSONANCEGAINMOD 6106 +#define ITEM_STAT_AEAUTOATTACKAVOID 6107 +#define ITEM_STAT_AGNOSTICDAMAGEBONUS 6108 +#define ITEM_STAT_AGNOSTICHEALBONUS 6109 +#define ITEM_STAT_TITHEGAIN 6110 +#define ITEM_STAT_FERVER 6111 +#define ITEM_STAT_RESOLVE 6112 +#define ITEM_STAT_COMBATMITIGATION 6113 +#define ITEM_STAT_ABILITYMITIGATION 6114 +#define ITEM_STAT_MULTIATTACKAVOIDANCE 6115 +#define ITEM_STAT_DOUBLECASTAVOIDANCE 6116 +#define ITEM_STAT_ABILITYDOUBLECASTAVOIDANCE 6117 +#define ITEM_STAT_DAMAGEPERSECONDMITIGATION 6118 +#define ITEM_STAT_FERVERMITIGATION 6119 +#define ITEM_STAT_FLURRYAVOIDANCE 6120 +#define ITEM_STAT_WEAPONDAMAGEBONUSMITIGATION 6121 +#define ITEM_STAT_ABILITYDOUBLECASTCHANCE 6122 +#define ITEM_STAT_ABILITYMODIFIERMITIGATATION 6123 +#define ITEM_STAT_STATUSEARNED 6124 + + + + +#define ITEM_STAT_SPELL_DAMAGE 700 +#define ITEM_STAT_HEAL_AMOUNT 701 +#define ITEM_STAT_SPELL_AND_HEAL 702 +#define ITEM_STAT_COMBAT_ART_DAMAGE 703 +#define ITEM_STAT_SPELL_AND_COMBAT_ART_DAMAGE 704 +#define ITEM_STAT_TAUNT_AMOUNT 705 +#define ITEM_STAT_TAUNT_AND_COMBAT_ART_DAMAGE 706 +#define ITEM_STAT_ABILITY_MODIFIER 707 + +// Other stats not listed above (not sent from the server), never send these to the client +// using type 8 as it is not used by the client as far as we know +#define ITEM_STAT_DURABILITY_MOD 800 +#define ITEM_STAT_DURABILITY_ADD 801 +#define ITEM_STAT_PROGRESS_ADD 802 +#define ITEM_STAT_PROGRESS_MOD 803 +#define ITEM_STAT_SUCCESS_MOD 804 +#define ITEM_STAT_CRIT_SUCCESS_MOD 805 +#define ITEM_STAT_EX_DURABILITY_MOD 806 +#define ITEM_STAT_EX_DURABILITY_ADD 807 +#define ITEM_STAT_EX_PROGRESS_MOD 808 +#define ITEM_STAT_EX_PROGRESS_ADD 809 +#define ITEM_STAT_EX_SUCCESS_MOD 810 +#define ITEM_STAT_EX_CRIT_SUCCESS_MOD 811 +#define ITEM_STAT_EX_CRIT_FAILURE_MOD 812 +#define ITEM_STAT_RARE_HARVEST_CHANCE 813 +#define ITEM_STAT_MAX_CRAFTING 814 +#define ITEM_STAT_COMPONENT_REFUND 815 +#define ITEM_STAT_BOUNTIFUL_HARVEST 816 + +#define ITEM_STAT_UNCONTESTED_PARRY 850 +#define ITEM_STAT_UNCONTESTED_BLOCK 851 +#define ITEM_STAT_UNCONTESTED_DODGE 852 +#define ITEM_STAT_UNCONTESTED_RIPOSTE 853 + +#define DISPLAY_FLAG_RED_TEXT 1 // old clients +#define DISPLAY_FLAG_NO_GUILD_STATUS 8 +#define DISPLAY_FLAG_NO_BUYBACK 16 +#define DISPLAY_FLAG_NOT_FOR_SALE 64 +#define DISPLAY_FLAG_NO_BUY 128 // disables buying on merchant 'buy' list + +enum ItemEffectType { + NO_EFFECT_TYPE=0, + EFFECT_CURE_TYPE_TRAUMA=1, + EFFECT_CURE_TYPE_ARCANE=2, + EFFECT_CURE_TYPE_NOXIOUS=3, + EFFECT_CURE_TYPE_ELEMENTAL=4, + EFFECT_CURE_TYPE_CURSE=5, + EFFECT_CURE_TYPE_MAGIC=6, + EFFECT_CURE_TYPE_ALL=7 +}; +#pragma pack(1) +struct ItemStatsValues{ + sint16 str; + sint16 sta; + sint16 agi; + sint16 wis; + sint16 int_; + sint16 vs_slash; + sint16 vs_crush; + sint16 vs_pierce; + sint16 vs_heat; + sint16 vs_cold; + sint16 vs_magic; + sint16 vs_mental; + sint16 vs_divine; + sint16 vs_disease; + sint16 vs_poison; + sint16 health; + sint16 power; + sint8 concentration; + sint16 ability_modifier; + sint16 criticalmitigation; + sint16 extrashieldblockchance; + sint16 beneficialcritchance; + sint16 critbonus; + sint16 potency; + sint16 hategainmod; + sint16 abilityreusespeed; + sint16 abilitycastingspeed; + sint16 abilityrecoveryspeed; + sint16 spellreusespeed; + sint16 spellmultiattackchance; + sint16 dps; + sint16 attackspeed; + sint16 multiattackchance; + sint16 flurry; + sint16 aeautoattackchance; + sint16 strikethrough; + sint16 accuracy; + sint16 offensivespeed; + float uncontested_parry; + float uncontested_block; + float uncontested_dodge; + float uncontested_riposte; + + +}; +struct ItemCore{ + int32 item_id; + sint32 soe_id; + int32 bag_id; + sint32 inv_slot_id; + sint16 slot_id; + sint16 equip_slot_id; // used for when a bag is equipped + sint16 appearance_type; // 0 for combat armor, 1 for appearance armor + int8 index; + int16 icon; + int16 classic_icon; + int16 count; + int8 tier; + int8 num_slots; + int32 unique_id; + int8 num_free_slots; + int16 recommended_level; + bool item_locked; + bool new_item; + int16 new_index; +}; +#pragma pack() +struct ItemStat{ + string stat_name; + int8 stat_type; + sint16 stat_subtype; + int16 stat_type_combined; + float value; + int8 level; +}; +struct ItemSet{ + int32 item_id; + int32 item_crc; + int16 item_icon; + int16 item_stack_size; + int32 item_list_color; + std::string name; + int8 language; +}; +struct Classifications{ + int32 classification_id; //classifications MJ + string classification_name; +}; +struct ItemLevelOverride{ + int8 adventure_class; + int8 tradeskill_class; + int16 level; +}; +struct ItemClass{ + int8 adventure_class; + int8 tradeskill_class; + int16 level; +}; +struct ItemAppearance{ + int16 type; + int8 red; + int8 green; + int8 blue; + int8 highlight_red; + int8 highlight_green; + int8 highlight_blue; +}; + +enum AddItemType { + NOT_SET = 0, + BUY_FROM_BROKER = 1, + GM_COMMAND = 2 +}; + +struct QuestRewardData { + int32 quest_id; + bool is_temporary; + std::string description; + bool is_collection; + bool has_displayed; + int64 tmp_coin; + int32 tmp_status; + bool db_saved; + int32 db_index; +}; + +class PlayerItemList; +class Item{ +public: + #pragma pack(1) + struct ItemStatString{ + EQ2_8BitString stat_string; + }; + struct Generic_Info{ + int8 show_name; + int8 creator_flag; + int16 item_flags; + int16 item_flags2; + int8 condition; + int32 weight; // num/10 + int32 skill_req1; + int32 skill_req2; + int16 skill_min; + int8 item_type; //0=normal, 1=weapon, 2=range, 3=armor, 4=shield, 5=bag, 6=scroll, 7=recipe, 8=food, 9=bauble, 10=house item, 11=thrown, 12=house container, 13=adormnet, 14=??, 16=profile, 17=patter set, 18=item set, 19=book, 20=decoration, 21=dungeon maker, 22=marketplace + int16 appearance_id; + int8 appearance_red; + int8 appearance_green; + int8 appearance_blue; + int8 appearance_highlight_red; + int8 appearance_highlight_green; + int8 appearance_highlight_blue; + int8 collectable; + int32 offers_quest_id; + int32 part_of_quest_id; + int16 max_charges; + int8 display_charges; + int64 adventure_classes; + int64 tradeskill_classes; + int16 adventure_default_level; + int16 tradeskill_default_level; + int8 usable; + int8 harvest; + int8 body_drop; + int8 pvp_description; + int8 merc_only; + int8 mount_only; + int32 set_id; + int8 collectable_unk; + char offers_quest_name[255]; + char required_by_quest_name[255]; + int8 transmuted_material; + }; + struct Armor_Info { + int16 mitigation_low; + int16 mitigation_high; + }; + struct Adornment_Info { + float duration; + int16 item_types; + int16 slot_type; + }; + struct Weapon_Info { + int16 wield_type; + int16 damage_low1; + int16 damage_high1; + int16 damage_low2; + int16 damage_high2; + int16 damage_low3; + int16 damage_high3; + int16 delay; + float rating; + }; + struct Shield_Info { + Armor_Info armor_info; + }; + struct Ranged_Info { + Weapon_Info weapon_info; + int16 range_low; + int16 range_high; + }; + struct Bag_Info { + int8 num_slots; + int16 weight_reduction; + }; + struct Food_Info{ + int8 type; //0=water, 1=food + int8 level; + float duration; + int8 satiation; + }; + struct Bauble_Info{ + int16 cast; + int16 recovery; + int32 duration; + float recast; + int8 display_slot_optional; + int8 display_cast_time; + int8 display_bauble_type; + float effect_radius; + int32 max_aoe_targets; + int8 display_until_cancelled; + }; + struct Book_Info{ + int8 language; + EQ2_16BitString author; + EQ2_16BitString title; + }; + struct Book_Info_Pages { + int8 page; + EQ2_16BitString page_text; + int8 page_text_valign; + int8 page_text_halign; + }; + struct Skill_Info{ + int32 spell_id; + int32 spell_tier; + }; + struct HouseItem_Info{ + int32 status_rent_reduction; + float coin_rent_reduction; + int8 house_only; + }; + struct HouseContainer_Info{ + int64 allowed_types; + int8 num_slots; + int8 broker_commission; + int8 fence_commission; + }; + struct RecipeBook_Info{ + vector recipes; + int32 recipe_id; + int8 uses; + }; + struct ItemSet_Info{ + int32 item_id; + int32 item_crc; + int16 item_icon; + int32 item_stack_size; + int32 item_list_color; + int32 soe_item_id_unsigned; + int32 soe_item_crc_unsigned; + }; + struct Thrown_Info{ + sint32 range; + sint32 damage_modifier; + float hit_bonus; + int32 damage_type; + }; + struct ItemEffect{ + EQ2_16BitString effect; + int8 percentage; + int8 subbulletflag; + }; + struct BookPage { + int8 page; + EQ2_16BitString page_text; + int8 valign; + int8 halign; + }; + #pragma pack() + Item(); + Item(Item* in_item); + ~Item(); + string lowername; + string name; + string description; + int16 stack_count; + int32 sell_price; + int32 sell_status; + int32 max_sell_value; + bool save_needed; + int8 weapon_type; + string adornment; + string creator; + int32 adorn0; + int32 adorn1; + int32 adorn2; + vectorclassifications; //classifications MJ + vector item_stats; + vector item_sets; + vector item_string_stats; + vector item_level_overrides; + vector item_effects; + vector book_pages; + Generic_Info generic_info; + Weapon_Info* weapon_info; + Ranged_Info* ranged_info; + Armor_Info* armor_info; + Adornment_Info* adornment_info; + Bag_Info* bag_info; + Food_Info* food_info; + Bauble_Info* bauble_info; + Book_Info* book_info; + Book_Info_Pages* book_info_pages; + HouseItem_Info* houseitem_info; + HouseContainer_Info* housecontainer_info; + Skill_Info* skill_info; + RecipeBook_Info* recipebook_info; + ItemSet_Info* itemset_info; + Thrown_Info* thrown_info; + vector slot_data; + ItemCore details; + int32 spell_id; + int8 spell_tier; + string item_script; + bool no_buy_back; + bool no_sale; + bool needs_deletion; + std::time_t created; + std::map grouped_char_ids; + ItemEffectType effect_type; + bool crafted; + bool tinkered; + int8 book_language; + + void AddEffect(string effect, int8 percentage, int8 subbulletflag); + void AddBookPage(int8 page, string page_text,int8 valign, int8 halign); + int32 GetMaxSellValue(); + void SetMaxSellValue(int32 val); + void SetItem(Item* old_item); + int16 GetOverrideLevel(int8 adventure_class, int8 tradeskill_class); + void AddLevelOverride(int8 adventure_class, int8 tradeskill_class, int16 level); + void AddLevelOverride(ItemLevelOverride* class_); + bool CheckClassLevel(int8 adventure_class, int8 tradeskill_class, int16 level); + bool CheckClass(int8 adventure_class, int8 tradeskill_class); + bool CheckArchetypeAdvClass(int8 adventure_class, map* adv_class_levels = 0); + bool CheckArchetypeAdvSubclass(int8 adventure_class, map* adv_class_levels = 0); + bool CheckLevel(int8 adventure_class, int8 tradeskill_class, int16 level); + void SetAppearance(int16 type, int8 red, int8 green, int8 blue, int8 highlight_red, int8 highlight_green, int8 highlight_blue); + void SetAppearance(ItemAppearance* appearance); + void AddStat(ItemStat* in_stat); + bool HasStat(uint32 statID, std::string statName = std::string("")); + void DeleteItemSets(); + void AddSet(ItemSet* in_set); + void AddStatString(ItemStatString* in_stat); + void AddStat(int8 type, int16 subtype, float value, int8 level, char* name = 0); + void AddSet(int32 item_id, int32 item_crc, int16 item_icon, int32 item_stack_size, int32 item_list_color, std::string name, int8 language); + void SetWeaponType(int8 type); + int8 GetWeaponType(); + bool HasSlot(int8 slot, int8 slot2 = 255); + bool HasAdorn0(); + bool HasAdorn1(); + bool HasAdorn2(); + bool IsNormal(); + bool IsWeapon(); + bool IsArmor(); + bool IsDualWieldAble(Client* client, Item* item, int8 slot = -1); + bool IsRanged(); + bool IsBag(); + bool IsFood(); + bool IsBauble(); + bool IsSkill(); + bool IsHouseItem(); + bool IsHouseContainer(); + bool IsShield(); + bool IsAdornment(); + bool IsAmmo(); + bool IsBook(); + bool IsChainArmor(); + bool IsClothArmor(); + bool IsCollectable(); + bool IsCloak(); + bool IsCrushWeapon(); + bool IsFoodFood(); + bool IsFoodDrink(); + bool IsJewelry(); + bool IsLeatherArmor(); + bool IsMisc(); + bool IsPierceWeapon(); + bool IsPlateArmor(); + bool IsPoison(); + bool IsPotion(); + bool IsRecipeBook(); + bool IsSalesDisplay(); + bool IsSlashWeapon(); + bool IsSpellScroll(); + bool IsTinkered(); + bool IsTradeskill(); + bool IsThrown(); + bool IsHarvest(); + bool IsBodyDrop(); + void SetItemScript(string name); + const char* GetItemScript(); + int32 CalculateRepairCost(); + string CreateItemLink(int16 client_Version, bool bUseUniqueID=false); + + void SetItemType(int8 in_type); + void serialize(PacketStruct* packet, bool show_name = false, Player* player = 0, int16 packet_type = 0, int8 subtype = 0, bool loot_item = false, bool inspect = false); + EQ2Packet* serialize(int16 version, bool show_name = false, Player* player = 0, bool include_twice = true, int16 packet_type = 0, int8 subtype = 0, bool merchant_item = false, bool loot_item = false, bool inspect = false); + PacketStruct* PrepareItem(int16 version, bool merchant_item = false, bool loot_item = false, bool inspection = false); + bool CheckFlag(int32 flag); + bool CheckFlag2(int32 flag); + void AddSlot(int8 slot_id); + void SetSlots(int32 slots); + int16 GetIcon(int16 version); +}; +class MasterItemList{ +public: + MasterItemList(); + ~MasterItemList(); + map items; + + Item* GetItem(int32 id); + Item* GetItemByName(const char *name); + Item* GetAllItemsByClassification(const char* name); + ItemStatsValues* CalculateItemBonuses(int32 item_id, Entity* entity = 0); + ItemStatsValues* CalculateItemBonuses(Item* desc, Entity* entity = 0, ItemStatsValues* values = 0); + vector* GetItems(string name, int64 itype, int64 ltype, int64 btype, int64 minprice, int64 maxprice, int8 minskill, int8 maxskill, string seller, string adornment, int8 mintier, int8 maxtier, int16 minlevel, int16 maxlevel, sint8 itemclass); + vector* GetItems(map criteria, Client* client_to_map); + void AddItem(Item* item); + bool IsBag(int32 item_id); + void RemoveAll(); + static int32 NextUniqueID(); + static void ResetUniqueID(int32 new_id); + static int32 next_unique_id; + int32 GetItemStatIDByName(std::string name); + std::string GetItemStatNameByID(int32 id); + void AddMappedItemStat(int32 id, std::string lower_case_name); + + + void AddBrokerItemMapRange(int32 min_version, int32 max_version, int64 client_bitmask, int64 server_bitmask); + map>::iterator FindBrokerItemMapVersionRange(int32 min_version, int32 max_version); + map>::iterator FindBrokerItemMapByVersion(int32 version); + + map mappedItemStatsStrings; + map mappedItemStatTypeIDs; + std::map> broker_item_map; +}; +class PlayerItemList { +public: + PlayerItemList(); + ~PlayerItemList(); +// int16 number; + int32 max_saved_index; + map indexed_items; + map> > items; +// map< int8, Item* > inv_items; +// map< int8, Item* > bank_items; + int32 SetMaxItemIndex(); + bool SharedBankAddAllowed(Item* item); + vector* GetItemsFromBagID(sint32 bag_id); + vector* GetItemsInBag(Item* bag); + Item* GetBag(int8 inventory_slot, bool lock = true); + bool HasItem(int32 id, bool include_bank = false); + Item* GetItemFromIndex(int32 index); + void MoveItem(Item* item, sint32 inv_slot, int16 slot, int8 appearance_type, bool erase_old); // erase old was true + bool MoveItem(sint32 to_bag_id, int16 from_index, sint8 to, int8 appearance_type, int8 charges); + void EraseItem(Item* item); + Item* GetItemFromUniqueID(int32 item_id, bool include_bank = false, bool lock = true); + Item* GetItemFromID(int32 item_id, int8 count = 0, bool include_bank = false, bool lock = true); + sint32 GetAllStackCountItemFromID(int32 item_id, int8 count = 0, bool include_bank = false, bool lock = true); + bool AssignItemToFreeSlot(Item* item); + int16 GetNumberOfFreeSlots(); + int16 GetNumberOfItems(); + int32 GetWeight(); + bool HasFreeSlot(); + bool HasFreeBagSlot(); + void DestroyItem(int16 index); + Item* CanStack(Item* item, bool include_bank = false); + vector GetAllItemsFromID(int32 item, bool include_bank = false, bool lock = false); + void RemoveItem(Item* item, bool delete_item = false); + bool AddItem(Item* item); + + Item* GetItem(sint32 bag_slot, int16 slot, int8 appearance_type = 0); + + EQ2Packet* serialize(Player* player, int16 version); + uchar* xor_packet; + uchar* orig_packet; + map* GetAllItems(); + bool HasFreeBankSlot(); + int8 FindFreeBankSlot(); + + ///Get the first free slot and store them in the provided variables + ///Will contain the bag id of the first free spot + ///Will contain the slot id of the first free slot + ///True if a free slot was found + bool GetFirstFreeSlot(sint32* bag_id, sint16* slot); + + /// Get the first free slot in the bank and store it in the provided variables + /// Will contain the bag id of the first free bank slot + /// Will contain the slot id of the first free bank slot + /// True if a free bank slot was found + bool GetFirstFreeBankSlot(sint32* bag_id, sint16* slot); + + /// + Item* GetBankBag(int8 inventory_slot, bool lock = true); + + /// + bool AddOverflowItem(Item* item); + + Item* GetOverflowItem(); + + void RemoveOverflowItem(Item* item); + + vector* GetOverflowItemList(); + + void ResetPackets(); + + int32 CheckSlotConflict(Item* tmp, bool check_lore_only = false, bool lock_mutex = true, int16* lore_stack_count = 0); + + int32 GetItemCountInBag(Item* bag); + + int16 GetFirstNewItem(); + int16 GetNewItemByIndex(int16 in_index); + + Mutex MPlayerItems; +private: + void AddItemToPacket(PacketStruct* packet, Player* player, Item* item, int16 i, bool overflow = false, int16 new_index = 0); + void Stack(Item* orig_item, Item* item); + int16 packet_count; + vector overflowItems; +}; + +class EquipmentItemList{ +public: + EquipmentItemList(); + EquipmentItemList(const EquipmentItemList& list); + ~EquipmentItemList(); + Item* items[NUM_SLOTS]; + Mutex MEquipmentItems; + + vector* GetAllEquippedItems(); + + void ResetPackets(); + + bool HasItem(int32 id); + int8 GetNumberOfItems(); + int32 GetWeight(); + Item* GetItemFromUniqueID(int32 item_id); + Item* GetItemFromItemID(int32 item_id); + void SetItem(int8 slot_id, Item* item, bool locked = false); + void RemoveItem(int8 slot, bool delete_item = false); + Item* GetItem(int8 slot_id); + bool AddItem(int8 slot, Item* item); + bool CheckEquipSlot(Item* tmp, int8 slot); + bool CanItemBeEquippedInSlot(Item* tmp, int8 slot); + int8 GetFreeSlot(Item* tmp, int8 slot_id = 255, int16 version = 0); + int32 CheckSlotConflict(Item* tmp, bool check_lore_only = false, int16* lore_stack_count = 0); + + int8 GetSlotByItem(Item* item); + ItemStatsValues* CalculateEquipmentBonuses(Entity* entity = 0); + EQ2Packet* serialize(int16 version, Player* player); + void SendEquippedItems(Player* player); + uchar* xor_packet; + uchar* orig_packet; + + void SetAppearanceType(int8 type) { AppearanceType = type; } + int8 GetAppearanceType() { return AppearanceType; } +private: + int8 AppearanceType; // 0 for normal equip, 1 for appearance +}; + +#endif + diff --git a/source/WorldServer/Items/ItemsDB.cpp b/source/WorldServer/Items/ItemsDB.cpp new file mode 100644 index 0000000..62888b8 --- /dev/null +++ b/source/WorldServer/Items/ItemsDB.cpp @@ -0,0 +1,1419 @@ +/* + 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 . +*/ +#ifdef WIN32 + #include + #include +#endif +#include +#include +#include "../../common/Log.h" +#include "../WorldDatabase.h" +#include "Items.h" +#include "../World.h" +#include "../Rules/Rules.h" +#include "../LuaInterface.h" + +extern World world; +extern RuleManager rule_manager; +extern LuaInterface* lua_interface; + +// handle new database class til all functions are converted +void WorldDatabase::LoadDataFromRow(DatabaseResult* result, Item* item) +{ +// this is too much on top of already having the top level load item debug msg +// LogWrite(ITEM__DEBUG, 5, "Items", "\tSetting details for item ID: %i", result->GetInt32Str("id")); + + item->details.item_id = result->GetInt32Str("id"); + int8 size = strlen(result->GetStringStr("name")); + + if(size > 63) + size = 63; + + item->name = string(result->GetStringStr("name")); + item->lowername = ToLower(item->name); + item->details.icon = result->GetInt16Str("icon"); + item->details.classic_icon = result->GetInt16Str("classic_icon"); + item->details.count = result->GetInt16Str("count"); + item->details.tier = result->GetInt8Str("tier"); + item->generic_info.weight = result->GetInt32Str("weight"); + + if( strlen(result->GetStringStr("description")) > 0 ) + item->description = string(result->GetStringStr("description")); + + item->generic_info.show_name = result->GetInt8Str("show_name"); + + if( result->GetInt8Str("attuneable") == 1 ) + item->generic_info.item_flags += ATTUNEABLE; + + if( result->GetInt8Str("artifact") == 1 ) + item->generic_info.item_flags += ARTIFACT; + + if( result->GetInt8Str("lore") == 1 ) + item->generic_info.item_flags += LORE; + + if( result->GetInt8Str("temporary") == 1 ) + item->generic_info.item_flags += TEMPORARY; + + if( result->GetInt8Str("notrade") == 1 ) + item->generic_info.item_flags += NO_TRADE; + + if( result->GetInt8Str("novalue") == 1 ) + item->generic_info.item_flags += NO_VALUE; + + if( result->GetInt8Str("nozone") == 1 ) + item->generic_info.item_flags += NO_ZONE; + + if( result->GetInt8Str("nodestroy") == 1 ) + item->generic_info.item_flags += NO_DESTROY; + + if( result->GetInt8Str("crafted") == 1 ) + item->generic_info.item_flags += CRAFTED; + + if( result->GetInt8Str("good_only") == 1 ) + item->generic_info.item_flags += GOOD_ONLY; + + if( result->GetInt8Str("evil_only") == 1 ) + item->generic_info.item_flags += EVIL_ONLY; + + if( result->GetInt8Str("stacklore") == 1 ) + item->generic_info.item_flags += STACK_LORE; + + // add more Flags/Flags2 here + + if (result->GetInt8Str("lore_equip") == 1) + item->generic_info.item_flags += LORE_EQUIP; + + if (result->GetInt8Str("no_transmute") == 1) + item->generic_info.item_flags += NO_TRANSMUTE; + + if (result->GetInt8Str("CURSED_flags_32768") == 1) + item->generic_info.item_flags += CURSED; + + if (result->GetInt8Str("ornate") == 1) + item->generic_info.item_flags2 += ORNATE; + + if (result->GetInt8Str("heirloom") == 1) + item->generic_info.item_flags2 += HEIRLOOM; + + if (result->GetInt8Str("appearance_only") == 1) + item->generic_info.item_flags2 += APPEARANCE_ONLY; + + if (result->GetInt8Str("unlocked") == 1) + item->generic_info.item_flags2 += UNLOCKED; + + if (result->GetInt8Str("reforged") == 1) + item->generic_info.item_flags2 += REFORGED; + + if (result->GetInt8Str("norepair") == 1) + item->generic_info.item_flags2 += NO_REPAIR; + + if (result->GetInt8Str("etheral") == 1) + item->generic_info.item_flags2 += ETHERAL; + + if (result->GetInt8Str("refined") == 1) + item->generic_info.item_flags2 += REFINED; + + if (result->GetInt8Str("no_salvage") == 1) + item->generic_info.item_flags2 += NO_SALVAGE; + + if (result->GetInt8Str("indestructable") == 1) + item->generic_info.item_flags2 += INDESTRUCTABLE; + + if (result->GetInt8Str("no_experiment") == 1) + item->generic_info.item_flags2 += NO_EXPERIMENT; + + if (result->GetInt8Str("house_lore") == 1) + item->generic_info.item_flags2 += HOUSE_LORE; + + if (result->GetInt8Str("building_block") == 1) + item->generic_info.item_flags2 += BUILDING_BLOCK; + + if (result->GetInt8Str("free_reforge") == 1) + item->generic_info.item_flags2 += FREE_REFORGE; + + + if( result->GetInt32Str("skill_id_req") == 0 ) + item->generic_info.skill_req1 = 0xFFFFFFFF; + else + item->generic_info.skill_req1 = result->GetInt32Str("skill_id_req"); + + if( result->GetInt32Str("skill_id_req2") == 0 ) + item->generic_info.skill_req2 = 0xFFFFFFFF; + else + item->generic_info.skill_req2 = result->GetInt32Str("skill_id_req2"); + + item->generic_info.skill_min = result->GetInt16Str("skill_min"); + + if( result->GetInt32Str("slots") > 0) + item->SetSlots(result->GetInt32Str("slots")); + + item->sell_price = result->GetInt32Str("sell_price"); + item->sell_status = result->GetInt32Str("sell_status_amount"); + item->stack_count = result->GetInt16Str("stack_count"); + item->generic_info.collectable = result->GetInt8Str("collectable"); + item->generic_info.offers_quest_id = result->GetInt32Str("offers_quest_id"); + item->generic_info.part_of_quest_id = result->GetInt32Str("part_of_quest_id"); + item->details.recommended_level = result->GetInt16Str("recommended_level"); + item->details.item_locked = false; + item->generic_info.adventure_default_level = result->GetInt16Str("adventure_default_level"); + item->generic_info.max_charges = result->GetInt16Str("max_charges"); + item->generic_info.display_charges = result->GetInt8Str("display_charges"); + item->generic_info.tradeskill_default_level = result->GetInt16Str("tradeskill_default_level"); + + item->generic_info.adventure_classes = result->GetInt64Str("adventure_classes"); + item->generic_info.tradeskill_classes = result->GetInt64Str("tradeskill_classes"); + + if( !result->IsNullStr("lua_script") && strlen(result->GetStringStr("lua_script")) > 0 ) + { + item->SetItemScript(string(result->GetStringStr("lua_script"))); + LogWrite(ITEM__DEBUG, 5, "LUA", "--Loading LUA Item Script: '%s'", item->item_script.c_str()); + } + + item->effect_type = (ItemEffectType)result->GetInt32Str("effect_type"); + + if(item->generic_info.max_charges > 0) + item->details.count = item->generic_info.max_charges; + + if(item->details.count == 0) + item->details.count = 1; + + item->generic_info.usable = result->GetInt8Str("usable"); + item->details.soe_id = result->GetSInt32Str("soe_item_id"); + + item->generic_info.harvest = result->GetInt8Str("harvest"); + item->generic_info.body_drop = result->GetInt8Str("body_drop"); + + item->no_buy_back = (result->GetInt8Str("no_buy_back") == 1); + + item->generic_info.pvp_description = result->GetInt8Str("bPvpDesc"); + + item->generic_info.merc_only = (result->GetInt8Str("merc_only") == 1); + item->generic_info.mount_only = (result->GetInt8Str("mount_only") == 1); + + item->generic_info.set_id = result->GetInt32Str("set_id"); + + item->generic_info.collectable_unk = result->GetInt8Str("collectable_unk"); + + const char* offerQuestName = result->GetFieldValueStr("offers_quest_name"); + if(offerQuestName) + strncpy(item->generic_info.offers_quest_name,offerQuestName,255); + else + strcpy(item->generic_info.offers_quest_name,""); + + const char* requiredQuestName = result->GetFieldValueStr("required_by_quest_name"); + if(requiredQuestName) + strncpy(item->generic_info.required_by_quest_name,requiredQuestName,255); + else + strcpy(item->generic_info.required_by_quest_name,""); + + item->generic_info.transmuted_material = result->GetInt8Str("transmuted_material"); + + item->crafted = result->GetInt8Str("crafted"); + item->tinkered = result->GetInt8Str("tinkered"); + item->book_language = result->GetInt8Str("book_language"); + +} + +int32 WorldDatabase::LoadSkillItems(int32 item_id) +{ + Query query; + MYSQL_ROW row; + + std::string select_query_addition = std::string(" where item_id = ") + std::to_string(item_id); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, spell_id, spell_tier FROM item_details_skill%s", (item_id == 0) ? "" : select_query_addition.c_str()); + int32 total = 0; + int32 id = 0; + + if(result) + { + while(result && (row = mysql_fetch_row(result))) + { + id = atoul(row[0]); + Item* item = master_item_list.GetItem(id); + + if(!row[1] || !row[2]) + continue; + + if(item) + { + LogWrite(ITEM__DEBUG, 5, "Items", "\tLoading Skill for item_id %u", id); + LogWrite(ITEM__DEBUG, 5, "Items", "\ttype: %i, spell: %i, tier: %i", ITEM_TYPE_SKILL, atoi(row[1]), atoi(row[2])); + item->SetItemType(ITEM_TYPE_SKILL); + item->skill_info->spell_id = atoul(row[1]); + item->skill_info->spell_tier = atoi(row[2]); + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error loading `item_details_skill`, ID: %i", id); + } + } + return total; +} + +int32 WorldDatabase::LoadShields(int32 item_id) +{ + Query query; + MYSQL_ROW row; + + std::string select_query_addition = std::string(" where item_id = ") + std::to_string(item_id); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, mitigation_low, mitigation_high FROM item_details_shield%s", (item_id == 0) ? "" : select_query_addition.c_str()); + int32 total = 0; + int32 id = 0; + + if(result) + { + while(result && (row = mysql_fetch_row(result))) + { + id = strtoul(row[0], NULL, 0); + Item* item = master_item_list.GetItem(id); + + if(item) + { + LogWrite(ITEM__DEBUG, 5, "Items", "\tItem Shield for item_id: %u", id); + LogWrite(ITEM__DEBUG, 5, "Items", "\ttype: %i, mit_low: %i, mit_high: %i", ITEM_TYPE_SHIELD, atoi(row[1]), atoi(row[2])); + item->SetItemType(ITEM_TYPE_SHIELD); + item->armor_info->mitigation_low = atoi(row[1]); + item->armor_info->mitigation_high = atoi(row[2]); + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error loading `item_details_shield`, ID: %i", id); + } + } + return total; +} +int32 WorldDatabase::LoadAdornments(int32 item_id) +{ + Query query; + MYSQL_ROW row; + + std::string select_query_addition = std::string(" where item_id = ") + std::to_string(item_id); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, duration, item_types,slot_type FROM item_details_adornments%s", (item_id == 0) ? "" : select_query_addition.c_str()); + int32 total = 0; + int32 id = 0; + + if (result) + { + while (result && (row = mysql_fetch_row(result))) + { + id = strtoul(row[0], NULL, 0); + Item* item = master_item_list.GetItem(id); + + if (item) + { + //LogWrite(ITEM__DEBUG, 0, "Items", "\tItem Adornment for item_id: %u", id); + //LogWrite(ITEM__DEBUG, 0, "Items", "\ttype: %i, Duration: %i, item_types_: %i, slot_type: %i", ITEM_TYPE_ADORNMENT, atoi(row[1]), atoi(row[2]), atoi(row[3])); + item->SetItemType(ITEM_TYPE_ADORNMENT); + item->adornment_info->duration = atof(row[1]); + item->adornment_info->item_types = atoi(row[2]); + item->adornment_info->slot_type = atoi(row[3]); + //LogWrite(ITEM__DEBUG, 0, "Items", "\ttype: %i, Duration: %i, item_types_: %i, slot_type: %i",item->generic_info.item_type, item->adornment_info->duration, item->adornment_info->item_types, item->adornment_info->slot_type); + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error loading `item_details_shield`, ID: %i", id); + } + } + return total; +} +int32 WorldDatabase::LoadClassifications() +{ + int32 total = 0; + int32 id = 0; + return total; +} + +int32 WorldDatabase::LoadBaubles(int32 item_id) +{ + Query query; + MYSQL_ROW row; + + std::string select_query_addition = std::string(" where item_id = ") + std::to_string(item_id); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, cast, recovery, duration, recast, display_slot_optional, display_cast_time, display_bauble_type, effect_radius, max_aoe_targets, display_until_cancelled FROM item_details_bauble%s", (item_id == 0) ? "" : select_query_addition.c_str()); + int32 total = 0; + int32 id = 0; + + if(result) + { + while(result && (row = mysql_fetch_row(result))) + { + id = strtoul(row[0], NULL, 0); + Item* item = master_item_list.GetItem(id); + + if(item) + { + LogWrite(ITEM__DEBUG, 5, "Items", "\tItem Bauble for item_id %u", id); + LogWrite(ITEM__DEBUG, 5, "Items", "\ttype: %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i", ITEM_TYPE_BAUBLE, atoi(row[1]), atoi(row[2]), atoi(row[3]), atoi(row[4]), atoi(row[5]), atoi(row[6]), atof(row[7]), atoi(row[8]), atoi(row[9]), atoi(row[10])); + item->SetItemType(ITEM_TYPE_BAUBLE); + item->bauble_info->cast = atoi(row[1]); + item->bauble_info->recovery = atoi(row[2]); + item->bauble_info->duration = atoi(row[3]); + item->bauble_info->recast = atoi(row[4]); + item->bauble_info->display_slot_optional = atoi(row[5]); + item->bauble_info->display_cast_time = atoi(row[6]); + item->bauble_info->display_bauble_type = atoi(row[7]); + item->bauble_info->effect_radius = atof(row[8]); + item->bauble_info->max_aoe_targets = atoi(row[9]); + item->bauble_info->display_until_cancelled = atoi(row[10]); + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error loading `item_details_bauble`, ID: %i", id); + } + } + return total; +} + +int32 WorldDatabase::LoadBooks(int32 item_id) +{ + DatabaseResult result; + int32 total = 0; + int32 id = 0; + + std::string select_query_addition = std::string(" where item_id = ") + std::to_string(item_id); + if( database_new.Select(&result, "SELECT item_id, language, author, title FROM item_details_book%s", (item_id == 0) ? "" : select_query_addition.c_str()) ) + { + while( result.Next() ) + { + id = result.GetInt32Str("item_id"); + Item* item = master_item_list.GetItem(id); + + if(item) + { + LogWrite(ITEM__DEBUG, 5, "Items", "\tItem Book for item_id %u", id); + LogWrite(ITEM__DEBUG, 5, "Items", "\ttype: %i, %i, %s, %s", + ITEM_TYPE_BOOK, + result.GetInt8Str("language"), + result.GetStringStr("author"), + result.GetStringStr("title")); + + item->SetItemType(ITEM_TYPE_BOOK); + item->book_info->language = result.GetInt8Str("language"); + item->book_info->author.data = result.GetStringStr("author"); + item->book_info->author.size = item->book_info->author.data.length(); + item->book_info->title.data = result.GetStringStr("title"); + item->book_info->title.size = item->book_info->title.data.length(); + + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error loading `item_details_book`, ID: %i", id); + } + } + + return total; +} +int32 WorldDatabase::LoadItemsets(int32 item_id) +{ + DatabaseResult result; + int32 total = 0; + int32 id = 0; + + std::string select_query_addition = std::string(" and crate.item_id = ") + std::to_string(item_id); + //if (database_new.Select(&result, "SELECT id, itemset_item_id, item_id, item_icon,item_stack_size,item_list_color,language_type FROM item_details_itemset")) + if (database_new.Select(&result, "select crate.item_id, crateitem.reward_item_id, crateitem.icon, crateitem.stack_size, crateitem.name_color, crateitem.name, crateitem.language_type from item_details_reward_crate crate, item_details_reward_crate_item crateitem where crateitem.crate_item_id = crate.item_id%s", (item_id == 0) ? "" : select_query_addition.c_str())) + { + while (result.Next()) + { + id = result.GetInt32(0); + Item* item = master_item_list.GetItem(id); + + if (item) + { + item->SetItemType(ITEM_TYPE_ITEMCRATE); + //int32 item_id = result.GetInt32Str("item_id"); + const char* setName = result.GetString(5); + item->AddSet(result.GetInt32(1),0, result.GetInt16(2), result.GetInt16(3), result.GetInt32(4), setName ? string(setName) : string(""), result.GetInt8(6)); + + + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Item Set Crate Items", "Error loading `item_details_Items`, ID: %i", id); + } + } + + return total; +} +int32 WorldDatabase::LoadHouseItem(int32 item_id) +{ + Query query; + MYSQL_ROW row; + + std::string select_query_addition = std::string(" where item_id = ") + std::to_string(item_id); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, rent_reduction, status_rent_reduction, coin_rent_reduction, house_only FROM item_details_house%s", (item_id == 0) ? "" : select_query_addition.c_str()); + int32 total = 0; + int32 id = 0; + + if(result) + { + while(result && (row = mysql_fetch_row(result))) + { + id = strtoul(row[0], NULL, 0); + Item* item = master_item_list.GetItem(id); + + if(item) + { + LogWrite(ITEM__DEBUG, 5, "Items", "\tItem HouseItem for item_id %u", id); + LogWrite(ITEM__DEBUG, 5, "Items", "\ttype: %i, %i, %u, %.2f, %u", ITEM_TYPE_HOUSE, atoul(row[1]), atoi(row[2]), atof(row[3]), atoul(row[4])); + item->SetItemType(ITEM_TYPE_HOUSE); + item->houseitem_info->status_rent_reduction = atoi(row[2]); + item->houseitem_info->coin_rent_reduction = atof(row[3]); + item->houseitem_info->house_only = atoi(row[4]); + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error loading `item_details_house`, ID: %i", id); + } + } + return total; +} + +int32 WorldDatabase::LoadRecipeBookItems(int32 item_id) +{ + Query query; + MYSQL_ROW row; + + //std::string select_query_addition = std::string(" where item_id = ") + std::to_string(item_id); + //MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, name FROM item_details_recipe_items%s", (item_id == 0) ? "" : select_query_addition.c_str()); + std::string select_query_addition = std::string(" and r.item_id = ") + std::to_string(item_id); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT r.item_id, ri.recipe_id ,ri.`name`,ri.soe_recipe_crc FROM item_details_recipe r LEFT JOIN item_details_recipe_items ri ON ri.recipe_id = r.recipe_id where ri.recipe_id is not null%s", (item_id == 0) ? "" : select_query_addition.c_str()); + + int32 total = 0; + int32 id = 0; + uint32 soe_id = 0; + if (result) + { + while(result && (row = mysql_fetch_row(result))) + { + id = strtoul(row[0], NULL, 0); + Item* item = master_item_list.GetItem(id); + soe_id = strtoul(row[3], NULL, 0); + if(item) + { + LogWrite(ITEM__DEBUG, 5, "Items", "\tRecipe Book for item_id %u", id); + LogWrite(ITEM__DEBUG, 5, "Items", "\tType: %i, '%s'", ITEM_TYPE_RECIPE, row[2]); + item->SetItemType(ITEM_TYPE_RECIPE); + item->recipebook_info->recipe_id = (atoi(row[1])); + item->recipebook_info->recipes.push_back(soe_id); + //item->recipebook_info->recipe_id(row[1]); + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error loading `item_details_recipe_items`, ID: %u", id); + } + } + return total; +} + +int32 WorldDatabase::LoadHouseContainers(int32 item_id){ + DatabaseResult result; + int32 total = 0; + int32 id = 0; + + + std::string select_query_addition = std::string(" where item_id = ") + std::to_string(item_id); + if( database_new.Select(&result, "SELECT item_id, num_slots, allowed_types, broker_commission, fence_commission FROM item_details_house_container%s", (item_id == 0) ? "" : select_query_addition.c_str()) ) + { + while (result.Next() ) + { + id = result.GetInt32Str("item_id"); + Item* item = master_item_list.GetItem(id); + + if (item) + { + LogWrite(ITEM__DEBUG, 5, "Items", "\tHouse Container for item_id %u", id); + LogWrite(ITEM__DEBUG, 5, "Items", "\tType: %i, '%i', '%u', '%i', '%i'", ITEM_TYPE_RECIPE, result.GetInt8Str("num_slots"), result.GetInt64Str("allowed_types"), result.GetInt8Str("broker_commission"), result.GetInt8Str("fence_commission")); + + item->SetItemType(ITEM_TYPE_HOUSE_CONTAINER); + item->housecontainer_info->num_slots = result.GetInt8Str("num_slots"); + item->housecontainer_info->allowed_types = result.GetInt64Str("allowed_types"); + item->housecontainer_info->broker_commission = result.GetInt8Str("broker_commission"); + item->housecontainer_info->fence_commission = result.GetInt8Str("fence_commission"); + + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error loading `item_details_house_container`, ID: %u", id); + } + } + return total; +} + +int32 WorldDatabase::LoadArmor(int32 item_id) +{ + Query query; + MYSQL_ROW row; + + std::string select_query_addition = std::string(" where item_id = ") + std::to_string(item_id); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, mitigation_low, mitigation_high FROM item_details_armor%s", (item_id == 0) ? "" : select_query_addition.c_str()); + int32 total = 0; + int32 id = 0; + + if(result) + { + while(result && (row = mysql_fetch_row(result))) + { + id = strtoul(row[0], NULL, 0); + Item* item = master_item_list.GetItem(id); + if(item) + { + LogWrite(ITEM__DEBUG, 5, "Items", "\tItem Armor for item_id %u", id); + LogWrite(ITEM__DEBUG, 5, "Items", "\ttype: %i, mit_low: %i, mit_high: %i", ITEM_TYPE_ARMOR, atoi(row[1]), atoi(row[2])); + item->SetItemType(ITEM_TYPE_ARMOR); + item->armor_info->mitigation_low = atoi(row[1]); + item->armor_info->mitigation_high = atoi(row[2]); + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error loading `item_details_armor`, ID: %i", id); + } + } + return total; +} + +int32 WorldDatabase::LoadBags(int32 item_id) +{ + Query query; + MYSQL_ROW row; + + std::string select_query_addition = std::string(" where item_id = ") + std::to_string(item_id); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, num_slots, weight_reduction FROM item_details_bag%s", (item_id == 0) ? "" : select_query_addition.c_str()); + int32 total = 0; + int32 id = 0; + if(result) + { + while(result && (row = mysql_fetch_row(result))) + { + id = strtoul(row[0], NULL, 0); + Item* item = master_item_list.GetItem(id); + + if(item) + { + LogWrite(ITEM__DEBUG, 5, "Items", "\tItem Bag for item_id %u", id); + LogWrite(ITEM__DEBUG, 5, "Items", "\ttype: %i, slots: %i, wt_red: %i", id, ITEM_TYPE_BAG, atoi(row[1]), atoi(row[2])); + item->SetItemType(ITEM_TYPE_BAG); + item->details.num_slots = atoi(row[1]); + item->details.num_free_slots = item->details.num_slots; + item->bag_info->num_slots = item->details.num_slots; + item->bag_info->weight_reduction = atoi(row[2]); + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error loading `item_details_bag`, ID: %i", id); + } + } + return total; +} + +int32 WorldDatabase::LoadFoods(int32 item_id) +{ + Query query; + MYSQL_ROW row; + + std::string select_query_addition = std::string(" where item_id = ") + std::to_string(item_id); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, type, level, duration, satiation FROM item_details_food%s", (item_id == 0) ? "" : select_query_addition.c_str()); + int32 total = 0; + int32 id = 0; + + if(result) + { + while(result && (row = mysql_fetch_row(result))) + { + id = strtoul(row[0], NULL, 0); + Item* item = master_item_list.GetItem(id); + + if(item) + { + LogWrite(ITEM__DEBUG, 5, "Items", "\tItem Food for item_id %u", id); + LogWrite(ITEM__DEBUG, 5, "Items", "\ttype: %i, lvl: %i, dur: %i, sat: %.2f, tier: %i", ITEM_TYPE_FOOD, atoi(row[1]), atoi(row[2]), atof(row[3]), atoi(row[4])); + item->SetItemType(ITEM_TYPE_FOOD); + item->food_info->type = atoi(row[1]); + item->food_info->level = atoi(row[2]); + item->food_info->duration = atof(row[3]); + item->food_info->satiation = atoi(row[4]); + item->details.tier = atoi(row[4]); + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error loading `item_details_food`, ID: %i", id); + } + } + return total; +} + +int32 WorldDatabase::LoadRangeWeapons(int32 item_id) +{ + Query query; + MYSQL_ROW row; + + std::string select_query_addition = std::string(" where item_id = ") + std::to_string(item_id); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, dmg_low, dmg_high, dmg_mastery_low, dmg_mastery_high, dmg_base_low, dmg_base_high, delay, damage_rating, range_low, range_high, damage_type FROM item_details_range%s", (item_id == 0) ? "" : select_query_addition.c_str()); + int32 total = 0; + int32 id = 0; + + if(result) + { + while(result && (row = mysql_fetch_row(result))) + { + id = strtoul(row[0], NULL, 0); + Item* item = master_item_list.GetItem(id); + + if(item) + { + LogWrite(ITEM__DEBUG, 5, "Items", "\tItem Ranged for item_id %u", id); + LogWrite(ITEM__DEBUG, 5, "Items", "\ttype: %i, %i, %i, %i, %i, %i, %i, %i, %.2f, %i, %i, %i", ITEM_TYPE_RANGED, atoi(row[1]), atoi(row[2]), atoi(row[3]), atoi(row[4]), atoi(row[5]), atoi(row[6]), atoi(row[7]), atof(row[8]), atoi(row[9]), atoi(row[10]), atoi(row[11])); + item->SetItemType(ITEM_TYPE_RANGED); + item->ranged_info->weapon_info.damage_low1 = atoi(row[1]); + item->ranged_info->weapon_info.damage_high1 = atoi(row[2]); + item->ranged_info->weapon_info.damage_low2 = atoi(row[3]); + item->ranged_info->weapon_info.damage_high2 = atoi(row[4]); + item->ranged_info->weapon_info.damage_low3 = atoi(row[5]); + item->ranged_info->weapon_info.damage_high3 = atoi(row[6]); + item->ranged_info->weapon_info.delay = atoi(row[7]); + item->ranged_info->weapon_info.rating = atof(row[8]); + item->ranged_info->range_low = atoi(row[9]); + item->ranged_info->range_high = atoi(row[10]); + item->SetWeaponType(atoi(row[11])); + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error loading `item_details_range`, ID: %i", id); + } + } + return total; +} + +int32 WorldDatabase::LoadThrownWeapons(int32 item_id) +{ + Query query; + MYSQL_ROW row; + + std::string select_query_addition = std::string(" where item_id = ") + std::to_string(item_id); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, range_bonus, damage_bonus, hit_bonus, damage_type FROM item_details_thrown%s", (item_id == 0) ? "" : select_query_addition.c_str()); + int32 total = 0; + int32 id = 0; + + if(result) + { + while(result && (row = mysql_fetch_row(result))) + { + id = strtoul(row[0], NULL, 0); + Item* item = master_item_list.GetItem(id); + + if(item) + { + LogWrite(ITEM__DEBUG, 5, "Items", "\tItem Thrown for item_id %u", id); + LogWrite(ITEM__DEBUG, 5, "Items", "\ttype: %i, %i, %u, %.2f, %u", ITEM_TYPE_THROWN, atoul(row[1]), atoi(row[2]), atof(row[3]), atoul(row[4])); + item->SetItemType(ITEM_TYPE_THROWN); + item->thrown_info->range = atoul(row[1]); + item->thrown_info->damage_modifier = atoul(row[2]); + item->thrown_info->hit_bonus = atof(row[3]); + item->thrown_info->damage_type = atoul(row[4]); + item->SetWeaponType(item->thrown_info->damage_type); + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error loading `item_details_thrown`, ID: %i", id); + } + } + return total; +} + +int32 WorldDatabase::LoadWeapons(int32 item_id) +{ + Query query; + MYSQL_ROW row; + + std::string select_query_addition = std::string(" where item_id = ") + std::to_string(item_id); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, wield_style, dmg_low, dmg_high, dmg_mastery_low, dmg_mastery_high, dmg_base_low, dmg_base_high, delay, damage_rating, damage_type FROM item_details_weapon%s", (item_id == 0) ? "" : select_query_addition.c_str()); + int32 total = 0; + int32 id = 0; + + if(result) + { + while(result && (row = mysql_fetch_row(result))) + { + id = strtoul(row[0], NULL, 0); + Item* item = master_item_list.GetItem(id); + + if(item) + { + LogWrite(ITEM__DEBUG, 5, "Items", "\tItem Weapon for item_id %u", id); + LogWrite(ITEM__DEBUG, 5, "Items", "\ttype: %i, %i, %i, %i, %i, %i, %i, %i, %i, %.2f, %i", ITEM_TYPE_WEAPON, atoi(row[1]), atoi(row[2]), atoi(row[3]), atoi(row[4]), atoi(row[5]), atoi(row[6]), atoi(row[7]), atoi(row[8]), atof(row[9]), atoi(row[10])); + item->SetItemType(ITEM_TYPE_WEAPON); + item->weapon_info->wield_type = atoi(row[1]); + item->weapon_info->damage_low1 = atoi(row[2]); + item->weapon_info->damage_high1 = atoi(row[3]); + item->weapon_info->damage_low2 = atoi(row[4]); + item->weapon_info->damage_high2 = atoi(row[5]); + item->weapon_info->damage_low3 = atoi(row[6]); + item->weapon_info->damage_high3 = atoi(row[7]); + item->weapon_info->delay = atoi(row[8]); + item->weapon_info->rating = atof(row[9]); + item->SetWeaponType(atoi(row[10])); + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error loading `item_weapons`, ID: %i", id); + } + } + return total; +} + +int32 WorldDatabase::LoadItemAppearances(int32 item_id) +{ + Query query; + MYSQL_ROW row; + + std::string select_query_addition = std::string("where item_id = ") + std::to_string(item_id) + " "; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, equip_type, red, green, blue, highlight_red, highlight_green, highlight_blue FROM item_appearances %sORDER BY item_id asc", (item_id == 0) ? "" : select_query_addition.c_str()); + int32 id = 0; + Item* item = 0; + int32 total = 0; + + if(result && mysql_num_rows(result) >0) + { + while(result && (row = mysql_fetch_row(result))) + { + if(id != strtoul(row[0], NULL, 0)) + { + id = strtoul(row[0], NULL, 0); + item = master_item_list.GetItem(id); + + if(item) + { + LogWrite(ITEM__DEBUG, 5, "Items", "\tItem Appearance for item_id %u", id); + LogWrite(ITEM__DEBUG, 5, "Items", "\tequip_type: %i, R: %i, G: %i, B: %i, HR: %i, HG: %i, HB: %i", atoi(row[1]), atoi(row[2]), atoi(row[3]), atoi(row[4]), atoi(row[5]), atoi(row[6]), atoi(row[7])); + item->SetAppearance(atoi(row[1]), atoi(row[2]), atoi(row[3]), atoi(row[4]), atoi(row[5]), atoi(row[6]), atoi(row[7])); + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error Loading item_appearances, ID: %i", id); + } + } + } + return total; +} + +int32 WorldDatabase::LoadItemEffects(int32 item_id) +{ + Query query; + MYSQL_ROW row; + + std::string select_query_addition = std::string("where item_id = ") + std::to_string(item_id) + " "; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, effect, percentage, bullet FROM item_effects %sORDER BY item_id, id", (item_id == 0) ? "" : select_query_addition.c_str()); + int32 id = 0; + Item* item = 0; + int32 total = 0; + + if(result && mysql_num_rows(result) >0) + { + while(result && (row = mysql_fetch_row(result))) + { + if(id != atoul(row[0])) + { + id = atoul(row[0]); + item = master_item_list.GetItem(id); + } + + if(item && row[1]) + { + LogWrite(ITEM__DEBUG, 5, "Items", "\tItem Effects for item_id %u", id); + LogWrite(ITEM__DEBUG, 5, "Items", "\tEffect: '%s', Percent: %i, Sub: %i", row[1], atoi(row[2]), atoi(row[3])); + item->AddEffect(row[1], atoi(row[2]), atoi(row[3])); + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error Loading item_effects, ID: %i", id); + } + } + return total; +} +int32 WorldDatabase::LoadBookPages(int32 item_id) +{ + Query query; + MYSQL_ROW row; + + std::string select_query_addition = std::string("where item_id = ") + std::to_string(item_id) + " "; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, page, page_text, page_text_valign, page_text_halign FROM item_details_book_pages %sORDER BY item_id, id", (item_id == 0) ? "" : select_query_addition.c_str()); + int32 id = 0; + Item* item = 0; + int32 total = 0; + + if (result && mysql_num_rows(result) > 0) + { + while (result && (row = mysql_fetch_row(result))) + { + if (id != atoul(row[0])) + { + id = atoul(row[0]); + item = master_item_list.GetItem(id); + } + + if (item && row[1]) + { + LogWrite(ITEM__DEBUG, 5, "Items", "\tBook Pages for item_id %u", id); + //LogWrite(ITEM__DEBUG, 5, "Items", "\tPages: '%s', Percent: %i, Sub: %i", row[1], atoi(row[2]), atoi(row[3])); + item->AddBookPage(atoi(row[1]), row[2], atoi(row[3]), atoi(row[4])); + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error Loading item_details_book_pages, ID: %i", id); + } + } + return total; +} +int32 WorldDatabase::LoadItemLevelOverride(int32 item_id) +{ + Query query; + MYSQL_ROW row; + + std::string select_query_addition = std::string("where item_id = ") + std::to_string(item_id) + " "; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, adventure_class_id, tradeskill_class_id, level FROM item_levels_override %sORDER BY item_id asc", (item_id == 0) ? "" : select_query_addition.c_str()); + int32 id = 0; + Item* item = 0; + int32 total = 0; + + if(result && mysql_num_rows(result) >0) + { + while(result && (row = mysql_fetch_row(result))) + { + if(id != strtoul(row[0], NULL, 0)) + { + id = strtoul(row[0], NULL, 0); + item = master_item_list.GetItem(id); + } + + if(item) + { + LogWrite(ITEM__DEBUG, 5, "Items", "\tLevel Override for item_id %u", id); + LogWrite(ITEM__DEBUG, 5, "Items", "\tAdv: %i, TS: %i, Lvl: %i", atoi(row[1]), atoi(row[2]), atoi(row[3])); + item->AddLevelOverride(atoi(row[1]), atoi(row[2]), atoi(row[3])); + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error loading `item_levels_override`, ID: %i", id); + } + } + return total; +} + +int32 WorldDatabase::LoadItemStats(int32 item_id) +{ + Query query; + MYSQL_ROW row; + + std::string select_query_addition = std::string("where item_id = ") + std::to_string(item_id) + " "; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, type, subtype, iValue, fValue, sValue, level FROM item_mod_stats %sORDER BY stats_order asc", (item_id == 0) ? "" : select_query_addition.c_str()); + int32 id = 0; + Item* item = 0; + int32 total = 0; + + if(result && mysql_num_rows(result) >0) + { + while(result && (row = mysql_fetch_row(result))) + { + if(id != strtoul(row[0], NULL, 0)) + { + id = strtoul(row[0], NULL, 0); + item = master_item_list.GetItem(id); + } + + if(item) + { + LogWrite(ITEM__DEBUG, 5, "Items", "\tItem Stats for item_id %u", id); + + float fValue = 0.0f; + if(row[3]) + fValue = atof(row[3]); + else if(row[4]) + fValue = atof(row[4]); + + //LogWrite(ITEM__DEBUG, 5, "Items", "\ttype: %i, sub: %i, val: %.2f, name: %s", atoi(row[1]), atoi(row[2]), atof(row[3]), row[4]); + item->AddStat(atoi(row[1]), atoi(row[2]), fValue, atoul(row[6]), row[5]); + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error loading `item_stats`, ID: %i", id); + } + } + return total; +} + +int32 WorldDatabase::LoadItemModStrings(int32 item_id) +{ + DatabaseResult result; + + int32 id = 0; + Item* item = 0; + int32 total = 0; + + std::string select_query_addition = std::string(" where item_id = ") + std::to_string(item_id); + if( !database_new.Select(&result, "SELECT * FROM item_mod_strings%s", (item_id == 0) ? "" : select_query_addition.c_str()) ) { + LogWrite(ITEM__ERROR, 0, "Items", "Cannot load WorldDatabase::LoadItemModStrings in %s, line: %i", __FUNCTION__, __LINE__); + return 0; + } + else { + while( result.Next() ) + { + int32 item_id = result.GetInt32Str("item_id"); + if(id != item_id) + { + item = master_item_list.GetItem(item_id); + id = item_id; + } + + const char* modName = result.GetFieldValueStr("mod"); + if(item && modName) + { + Item::ItemStatString* stat_ = new Item::ItemStatString; + stat_->stat_string.data = string(modName); + stat_->stat_string.size = stat_->stat_string.data.length(); + item->AddStatString(stat_); + } + total++; + } + } + return total; +} + +void WorldDatabase::LoadBrokerItemStats() +{ + DatabaseResult result; + + if( !database_new.Select(&result, "SELECT * FROM broker_item_map") ) { + LogWrite(ITEM__ERROR, 0, "Items", "Cannot load WorldDatabase::LoadBrokerItemStats in %s, line: %i", __FUNCTION__, __LINE__); + } + else { + while( result.Next() ) + { + int32 version_range1 = result.GetInt32Str("version_range1"); + int32 version_range2 = result.GetInt32Str("version_range2"); + int64 client_bitmask = result.GetInt64Str("client_bitmask"); + int64 server_bitmask = result.GetInt64Str("server_bitmask"); + master_item_list.AddBrokerItemMapRange(version_range1, version_range2, client_bitmask, server_bitmask); + } + } +} +void WorldDatabase::ReloadItemList(int32 item_id) +{ + LogWrite(ITEM__DEBUG, 0, "Items", "Unloading Item List..."); + if(!item_id) { + master_item_list.RemoveAll(); + } + LoadItemList(item_id); +} + +void WorldDatabase::LoadItemList(int32 item_id) +{ + DatabaseResult result; + + int32 t_now = Timer::GetUnixTimeStamp(); + int32 total = 0; + int32 normal_items = 0; + string item_type; + std::string select_query_addition = std::string(" where id = ") + std::to_string(item_id); + if( !database_new.Select(&result, "SELECT * FROM items%s", (item_id == 0) ? "" : select_query_addition.c_str()) ) + LogWrite(ITEM__ERROR, 0, "Items", "Cannot load items in %s, line: %i", __FUNCTION__, __LINE__); + else + { + while( result.Next() ) + { + item_type = result.GetStringStr("item_type"); + LogWrite(ITEM__DEBUG, 5, "Items", "\tLoading: %s (ID: %i, Type: %s)...", result.GetStringStr("name"), result.GetInt32Str("id"), item_type.c_str()); + + Item* item = new Item; + LoadDataFromRow(&result, item); + master_item_list.AddItem(item); + + if( strcmp(item_type.c_str(), "Normal") == 0 ) + { + item->SetItemType(ITEM_TYPE_NORMAL); + normal_items++; + } + total++; + } + } + + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Normal Items", normal_items); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Baubles", LoadBaubles(item_id)); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Bags", LoadBags(item_id)); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Books", LoadBooks(item_id)); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Item Sets", LoadItemsets(item_id)); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u House Items", LoadHouseItem(item_id)); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Food Items", LoadFoods(item_id)); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Weapons", LoadWeapons(item_id)); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Ranged Weapons", LoadRangeWeapons(item_id)); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Thrown Weapons", LoadThrownWeapons(item_id)); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Armor Pieces", LoadArmor(item_id)); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Shields", LoadShields(item_id)); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Skill Items", LoadSkillItems(item_id)); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Adornment Items", LoadAdornments(item_id)); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Recipe Book Items", LoadRecipeBookItems(item_id)); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u House Containers", LoadHouseContainers(item_id)); + + LogWrite(ITEM__DEBUG, 0, "Items", "Loading Item Appearances..."); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Item Appearances", LoadItemAppearances(item_id)); + + LogWrite(ITEM__DEBUG, 0, "Items", "Loading Item Stats..."); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Item Stats", LoadItemStats(item_id)); + + LogWrite(ITEM__DEBUG, 0, "Items", "Loading Item Stats Mods (Strings)..."); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Item Stats", LoadItemModStrings(item_id)); + + LogWrite(ITEM__DEBUG, 0, "Items", "Loading Item Effects..."); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Item Effects", LoadItemEffects(item_id)); + + LogWrite(ITEM__DEBUG, 0, "Items", "Loading Book Pages..."); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Book Pages", LoadBookPages(item_id)); + + LogWrite(ITEM__DEBUG, 0, "Items", "Loading Item Level Overrides..."); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Item Level Overrides", LoadItemLevelOverride(item_id)); + + if(!item_id) { + LoadBrokerItemStats(); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded Broker Item Stat Map Versioning"); + } + + LogWrite(ITEM__INFO, 0, "Items", "Loaded %u Total Item%s (took %u seconds)", total, ( total == 1 ) ? "" : "s", Timer::GetUnixTimeStamp() - t_now); +} + +int32 WorldDatabase::LoadNextUniqueItemID() +{ + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT max(id) FROM character_items"); + + if(result && (row = mysql_fetch_row(result))) + { + if(row[0]) + { + LogWrite(ITEM__DEBUG, 0, "Items", "%s: max(id): %u", __FUNCTION__, atoul(row[0])); + return strtoul(row[0], NULL, 0); + } + else + return 0; + } + else if(!result) + LogWrite(ITEM__ERROR, 0, "Items", "%s: Unable to load next unique item ID.", __FUNCTION__); + + return 0; +} + +void WorldDatabase::SaveItems(Client* client) +{ + LogWrite(ITEM__DEBUG, 3, "Items", "Save Items for Player %i", client->GetCharacterID()); + + map* items = client->GetPlayer()->GetItemList(); + map::iterator item_iter; + Item* item = 0; + + for(item_iter = items->begin(); item_iter != items->end(); item_iter++) + { + item = item_iter->second; + + if(item) { + if(item->CheckFlag(TEMPORARY)) { + item->save_needed = true; // we need to keep updating the timestamp so it doesn't expire + } + if(item->needs_deletion || (client->IsZoning() && item->CheckFlag(NO_ZONE))) { + DeleteItem(client->GetCharacterID(), item, 0); + client->GetPlayer()->item_list.DestroyItem(item->details.index); + if(!client->IsZoning()) { + client->QueuePacket(client->GetPlayer()->SendInventoryUpdate(client->GetVersion())); + } + } + else if(item->save_needed) + { + LogWrite(ITEM__DEBUG, 5, "Items", "SaveItems: Acct: %u, Char: %u, Item: %u, NOT-EQUIPPED", client->GetAccountID(), client->GetCharacterID(), item); + SaveItem(client->GetAccountID(), client->GetCharacterID(), item, "NOT-EQUIPPED"); + item->save_needed = false; + } + } + } + safe_delete(items); + + vector* equipped_list = client->GetPlayer()->GetEquippedItemList(); + + for(int32 i=0;isize();i++) + { + item = equipped_list->at(i); + + if(item) + { + if(item->CheckFlag(TEMPORARY)) { + item->save_needed = true; // we need to keep updating the timestamp so it doesn't expire + } + if(item->needs_deletion || (client->IsZoning() && item->CheckFlag(NO_ZONE))) { + DeleteItem(client->GetCharacterID(), item, 0); + client->GetPlayer()->item_list.DestroyItem(item->details.index); + if(!client->IsZoning()) { + client->QueuePacket(client->GetPlayer()->SendInventoryUpdate(client->GetVersion())); + } + } + else if(item->save_needed) { + if(item->details.appearance_type) + SaveItem(client->GetAccountID(), client->GetCharacterID(), item, "APPEARANCE"); + else + SaveItem(client->GetAccountID(), client->GetCharacterID(), item, "EQUIPPED"); + } + item->save_needed = false; + } + } + safe_delete(equipped_list); + + + vector* appearance_equipped_list = client->GetPlayer()->GetAppearanceEquippedItemList(); + + for(int32 i=0;isize();i++) + { + item = appearance_equipped_list->at(i); + + if(item) + { + if(item->CheckFlag(TEMPORARY)) { + item->save_needed = true; // we need to keep updating the timestamp so it doesn't expire + } + if(item->needs_deletion || (client->IsZoning() && item->CheckFlag(NO_ZONE))) { + DeleteItem(client->GetCharacterID(), item, 0); + client->GetPlayer()->item_list.DestroyItem(item->details.index); + if(!client->IsZoning()) { + client->QueuePacket(client->GetPlayer()->SendInventoryUpdate(client->GetVersion())); + } + } + else if(item->save_needed) { + SaveItem(client->GetAccountID(), client->GetCharacterID(), item, "APPEARANCE"); + item->save_needed = false; + } + } + } + safe_delete(appearance_equipped_list); + + vector* overflow = client->GetPlayer()->item_list.GetOverflowItemList(); + for (int32 i = 0; i < overflow->size(); i++){ + item = overflow->at(i); + if (item) { + if(item->CheckFlag(TEMPORARY)) { + item->save_needed = true; // we need to keep updating the timestamp so it doesn't expire + } + if(item->needs_deletion || (client->IsZoning() && item->CheckFlag(NO_ZONE))) { + DeleteItem(client->GetCharacterID(), item, 0); + client->GetPlayer()->item_list.DestroyItem(item->details.index); + if(!client->IsZoning()) { + client->QueuePacket(client->GetPlayer()->SendInventoryUpdate(client->GetVersion())); + } + } + else { + sint16 slot = item->details.slot_id; + item->details.slot_id = i; + SaveItem(client->GetAccountID(), client->GetCharacterID(), item, "NOT-EQUIPPED"); + item->details.slot_id = slot; + } + } + } + safe_delete(overflow); +} + +void WorldDatabase::SaveItem(int32 account_id, int32 char_id, Item* item, const char* type) +{ + LogWrite(ITEM__DEBUG, 1, "Items", "Saving ItemID: %u (Type: %s) for account: %u, player: %u", item->details.item_id, type, account_id, char_id); + + Query query; + string update_item = string("REPLACE INTO character_items (id, type, char_id, slot, equip_slot, item_id, creator,adorn0,adorn1,adorn2, condition_, attuned, bag_id, count, max_sell_value, no_sale, account_id, login_checksum) VALUES (%u, '%s', %u, %i, %i, %u, '%s', %i, %i, %i, %i, %i, %i, %i, %u, %u, %u, 0)"); + query.AddQueryAsync(char_id, this, Q_REPLACE, update_item.c_str(), item->details.unique_id, type, char_id, item->details.slot_id, item->details.equip_slot_id, item->details.item_id, + getSafeEscapeString(item->creator.c_str()).c_str(),item->adorn0,item->adorn1,item->adorn2, item->generic_info.condition, item->CheckFlag(ATTUNED) ? 1 : 0, item->details.inv_slot_id, item->details.count, item->GetMaxSellValue(), item->no_sale, account_id); + if(item->CheckFlag2(HEIRLOOM)) { + std::map::iterator itr; + for(itr = item->grouped_char_ids.begin(); itr != item->grouped_char_ids.end(); itr++) { + string addmembers_query = string("REPLACE INTO character_items_group_members (unique_id, character_id) VALUES (%u, %u)"); + query.AddQueryAsync(char_id, this, Q_REPLACE, addmembers_query.c_str(), item->details.unique_id, itr->first); + } + } +} + +void WorldDatabase::DeleteItem(int32 char_id, Item* item, const char* type) +{ + string delete_item; + + if(type) + { + LogWrite(ITEM__DEBUG, 1, "Items", "Deleting item_id %u (Type: %s) for player %u", item->details.item_id, type, char_id); + + delete_item = string("DELETE FROM character_items WHERE char_id = %u AND (id = %u OR bag_id = %u) AND type='%s'"); + Query query; + query.RunQuery2(Q_DELETE, delete_item.c_str(), char_id, item->details.unique_id, item->details.unique_id, type); + } + else + { + LogWrite(ITEM__DEBUG, 0, "Items", "Deleting item_id %u for player %u", item->details.item_id, char_id); + + delete_item = string("DELETE FROM character_items WHERE char_id = %u AND (id = %u OR bag_id = %u)"); + Query query2; + query2.RunQuery2(Q_DELETE, delete_item.c_str(), char_id, item->details.unique_id, item->details.unique_id); + } + + if(item->CheckFlag2(HEIRLOOM)) { + delete_item = string("DELETE FROM character_items_group_members WHERE unique_id = %u"); + Query query3; + query3.RunQuery2(Q_DELETE, delete_item.c_str(), item->details.unique_id); + } +} + +void WorldDatabase::LoadCharacterItemList(int32 account_id, int32 char_id, Player* player, int16 version) +{ + LogWrite(ITEM__DEBUG, 0, "Items", "Loading items for character '%s' (%u)", player->GetName(), char_id); + + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT type, id, slot, equip_slot, item_id, creator,adorn0,adorn1,adorn2, condition_, attuned, bag_id, count, max_sell_value, no_sale, UNIX_TIMESTAMP(last_saved), UNIX_TIMESTAMP(created) FROM character_items where char_id = %u or (bag_id = -4 and account_id = %u) ORDER BY bag_id, slot asc", char_id, account_id); + + if(result) + { + bool ret = true; + + while(result && (row = mysql_fetch_row(result))) + { + LogWrite(ITEM__DEBUG, 5, "Items", "Loading character item: %u, slot: %i", strtoul(row[1], NULL, 0), atoi(row[2])); + Item* master_item = master_item_list.GetItem(strtoul(row[4], NULL, 0)); + if(master_item) + { + Item* item = new Item(master_item); + int32 xxx = 0; + if(master_item->recipebook_info) + item->recipebook_info->recipe_id = master_item->recipebook_info->recipe_id; + item->details.unique_id = strtoul(row[1], NULL, 0); + item->details.slot_id = atoi(row[2]); + if(item->IsBag()) { + item->details.equip_slot_id = atoi(row[3]); + } + + if(item->details.num_slots > 0) + item->details.bag_id = item->details.unique_id; + + item->save_needed = false; + + // we need the items basics (unique id slot id bag id) to continue this temporary check + if(item->CheckFlag(TEMPORARY)) { + std::time_t last_saved = static_cast(atoul(row[15])); + double timeInSeconds = std::difftime(std::time(nullptr), last_saved); + LogWrite(ITEM__INFO, 0, "Items", "Character ID %u has a temporary item %s time in seconds %f last saved.", char_id, item->name.c_str(), timeInSeconds); + if(timeInSeconds >= rule_manager.GetGlobalRule(R_Player, TemporaryItemLogoutTime)->GetFloat()) { + DeleteItem(char_id, item, 0); + LogWrite(ITEM__INFO, 0, "Items", "\tCharacter ID %u had a temporary item %s which was removed due to time limit.", char_id, item->name.c_str()); + lua_interface->SetLuaUserDataStale(item); + safe_delete(item); + continue; + } + } + + if(row[5]) + item->creator = string(row[5]);//creator + item->adorn0 = atoi(row[6]); //adorn0 + item->adorn1 = atoi(row[7]); //adorn1 + item->adorn2 = atoi(row[8]); //adorn2 + item->generic_info.condition = atoi(row[9]); //condition + + if(row[10] && atoi(row[10])>0) //attuned + { + if(item->CheckFlag(ATTUNEABLE)) + item->generic_info.item_flags -= ATTUNEABLE; + + if(!item->CheckFlag(NO_TRADE)) + item->generic_info.item_flags += NO_TRADE; + + item->generic_info.item_flags += ATTUNED; + } + + if(item->CheckFlag2(HEIRLOOM)) { + MYSQL_ROW row2; + MYSQL_RES* result2 = query.RunQuery2(Q_SELECT, "SELECT character_id from character_items_group_members where unique_id = %u", item->details.unique_id); + + if(result2) + { + bool ret = true; + + while(result2 && (row2 = mysql_fetch_row(result2))) + { + item->grouped_char_ids.insert(std::make_pair(atoul(row2[0]),true)); + } + } + } + + item->details.inv_slot_id = atol(row[11]); //bag_id + item->details.new_item = false; + item->details.new_index = 0; + item->details.count = atoi(row[12]); //count + item->SetMaxSellValue(atoul(row[13])); //max sell value + item->no_sale = (atoul(row[14]) == 1); + item->details.appearance_type = 0; + + // position 14 is used for the last_saved timestamp (primarily for checking temporary items on login) + item->created = static_cast(atoul(row[16])); + + if(strncasecmp(row[0], "EQUIPPED", 8)==0) + ret = player->GetEquipmentList()->AddItem(item->details.slot_id, item); + else if (strncasecmp(row[0], "APPEARANCE", 10) == 0) + { + item->details.appearance_type = 1; + ret = player->GetAppearanceEquipmentList()->AddItem(item->details.slot_id, item); + } + else { + if (version < 1209 && item->details.count > 255) { + int stacks = item->details.count / 255; + int8 remainder = item->details.count % 255; + item->details.count = remainder; + + if (item->details.inv_slot_id == -2) + player->item_list.AddOverflowItem(item); + else { + if(!player->item_list.AddItem(item)) + item = nullptr; + } + + if(item) { + for (int stack = 1; stack <= stacks; stack++) { + item->details.count = 255; + item->details.inv_slot_id = -2; + player->item_list.AddOverflowItem(item); + } + } + } + else { + if (item->details.inv_slot_id == -2) + player->item_list.AddOverflowItem(item); + else + player->item_list.AddItem(item); + } + + if(item->details.equip_slot_id) { + player->GetEquipmentList()->AddItem(item->details.equip_slot_id, item); + } + } + } + else + ret = false; + } + + if(!ret) + LogWrite(ITEM__ERROR, 0, "Items", "%s: Error Loading item(s) for Char ID: %u (%s)", __FUNCTION__, char_id, player->GetName()); + } +} + diff --git a/source/WorldServer/Items/Items_CoE.h b/source/WorldServer/Items/Items_CoE.h new file mode 100644 index 0000000..5786b7f --- /dev/null +++ b/source/WorldServer/Items/Items_CoE.h @@ -0,0 +1,817 @@ +/* + 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 . +*/ +#ifndef __EQ2_ITEMS__ +#define __EQ2_ITEMS__ +#include +#include +#include "../../common/types.h" +#include "../../common/DataBuffer.h" +#include "../../common/MiscFunctions.h" +#include "../Commands/Commands.h" +#include "../../common/ConfigReader.h" + +using namespace std; +class MasterItemList; +class Player; +extern MasterItemList master_item_list; +#define EQ2_PRIMARY_SLOT 0 +#define EQ2_SECONDARY_SLOT 1 +#define EQ2_HEAD_SLOT 2 +#define EQ2_CHEST_SLOT 3 +#define EQ2_SHOULDERS_SLOT 4 +#define EQ2_FOREARMS_SLOT 5 +#define EQ2_HANDS_SLOT 6 +#define EQ2_LEGS_SLOT 7 +#define EQ2_FEET_SLOT 8 +#define EQ2_LRING_SLOT 9 +#define EQ2_RRING_SLOT 10 +#define EQ2_EARS_SLOT_1 11 +#define EQ2_EARS_SLOT_2 12 +#define EQ2_NECK_SLOT 13 +#define EQ2_LWRIST_SLOT 14 +#define EQ2_RWRIST_SLOT 15 +#define EQ2_RANGE_SLOT 16 +#define EQ2_AMMO_SLOT 17 +#define EQ2_WAIST_SLOT 18 +#define EQ2_CLOAK_SLOT 19 +#define EQ2_CHARM_SLOT_1 20 +#define EQ2_CHARM_SLOT_2 21 +#define EQ2_FOOD_SLOT 22 +#define EQ2_DRINK_SLOT 23 +#define EQ2_TEXTURES_SLOT 24 +#define EQ2_UNKNOWN_SLOT 25 + +#define PRIMARY_SLOT 1 +#define SECONDARY_SLOT 2 +#define HEAD_SLOT 4 +#define CHEST_SLOT 8 +#define SHOULDERS_SLOT 16 +#define FOREARMS_SLOT 32 +#define HANDS_SLOT 64 +#define LEGS_SLOT 128 +#define FEET_SLOT 256 +#define LRING_SLOT 512 +#define RRING_SLOT 1024 +#define EARS_SLOT_1 2048 +#define EARS_SLOT_2 4096 +#define NECK_SLOT 8192 +#define LWRIST_SLOT 16384 +#define RWRIST_SLOT 32768 +#define RANGE_SLOT 65536 +#define AMMO_SLOT 131072 +#define WAIST_SLOT 262144 +#define CLOAK_SLOT 524288 +#define CHARM_SLOT_1 1048576 +#define CHARM_SLOT_2 2097152 +#define FOOD_SLOT 4194304 +#define DRINK_SLOT 8388608 +#define TEXTURES_SLOT 16777216 + +#define NUM_BANK_SLOTS 12 +#define NUM_SHARED_BANK_SLOTS 8 +#define NUM_SLOTS 25 +#define NUM_INV_SLOTS 6 +#define INV_SLOT1 0 +#define INV_SLOT2 50 +#define INV_SLOT3 100 +#define INV_SLOT4 150 +#define INV_SLOT5 200 +#define INV_SLOT6 250 +#define BANK_SLOT1 1000 +#define BANK_SLOT2 1100 +#define BANK_SLOT3 1200 +#define BANK_SLOT4 1300 +#define BANK_SLOT5 1400 +#define BANK_SLOT6 1500 +#define BANK_SLOT7 1600 +#define BANK_SLOT8 1700 +#define ATTUNED 1 +#define ATTUNEABLE 2 +#define ARTIFACT 4 +#define LORE 8 +#define TEMPORARY 16 +#define NO_TRADE 32 +#define NO_VALUE 64 +#define NO_ZONE 128 +#define NO_DESTROY 256 +#define CRAFTED 512 +#define GOOD_ONLY 1024 +#define EVIL_ONLY 2048 + +#define ITEM_WIELD_TYPE_DUAL 1 +#define ITEM_WIELD_TYPE_SINGLE 2 +#define ITEM_WIELD_TYPE_TWO_HAND 4 + +#define ITEM_TYPE_NORMAL 0 +#define ITEM_TYPE_WEAPON 1 +#define ITEM_TYPE_RANGED 2 +#define ITEM_TYPE_ARMOR 3 +#define ITEM_TYPE_SHIELD 4 +#define ITEM_TYPE_BAG 5 +#define ITEM_TYPE_SKILL 6 +#define ITEM_TYPE_RECIPE 7 +#define ITEM_TYPE_FOOD 8 +#define ITEM_TYPE_BAUBLE 9 +#define ITEM_TYPE_HOUSE 10 +#define ITEM_TYPE_THROWN 11 +#define ITEM_TYPE_HOUSE_CONTAINER 12 +#define ITEM_TYPE_BOOK 13 +#define ITEM_TYPE_ADORNMENT 14 +#define ITEM_TYPE_PATTERN 15 +#define ITEM_TYPE_ARMORSET 16 + +#define ITEM_MENU_TYPE_GENERIC 1 +#define ITEM_MENU_TYPE_EQUIP 2 +#define ITEM_MENU_TYPE_BAG 4 +#define ITEM_MENU_TYPE_HOUSE 8 +#define ITEM_MENU_TYPE_SCRIBE 32 +#define ITEM_MENU_TYPE_INVALID 128 +#define ITEM_MENU_TYPE_BROKEN 512 +#define ITEM_MENU_TYPE_ATTUNED 2048 +#define ITEM_MENU_TYPE_ATTUNEABLE 4096 +#define ITEM_MENU_TYPE_BOOK 8192 +#define ITEM_MENU_TYPE_DISPLAY_CHARGES 16384 +#define ITEM_MENU_TYPE_NAMEPET 65536 +#define ITEM_MENU_TYPE_USE 524288 +#define ITEM_MENU_TYPE_DRINK 8388608 +#define ITEM_MENU_TYPE_REDEEM 536870912 + +#define ITEM_TAG_UNCOMMON 3 //tier tags +#define ITEM_TAG_TREASURED 4 +#define ITEM_TAG_LEGENDARY 7 +#define ITEM_TAG_FABLED 9 +#define ITEM_TAG_MYTHICAL 12 + +#define ITEM_BROKER_TYPE_ANY 0xFFFFFFFF +#define ITEM_BROKER_TYPE_ADORNMENT 134217728 +#define ITEM_BROKER_TYPE_AMMO 1024 +#define ITEM_BROKER_TYPE_ATTUNEABLE 16384 +#define ITEM_BROKER_TYPE_BAG 2048 +#define ITEM_BROKER_TYPE_BAUBLE 16777216 +#define ITEM_BROKER_TYPE_BOOK 128 +#define ITEM_BROKER_TYPE_CHAINARMOR 2097152 +#define ITEM_BROKER_TYPE_CLOAK 1073741824 +#define ITEM_BROKER_TYPE_CLOTHARMOR 524288 +#define ITEM_BROKER_TYPE_COLLECTABLE 67108864 +#define ITEM_BROKER_TYPE_CRUSHWEAPON 4 +#define ITEM_BROKER_TYPE_DRINK 131072 +#define ITEM_BROKER_TYPE_FOOD 4096 +#define ITEM_BROKER_TYPE_HOUSEITEM 512 +#define ITEM_BROKER_TYPE_JEWELRY 262144 +#define ITEM_BROKER_TYPE_LEATHERARMOR 1048576 +#define ITEM_BROKER_TYPE_LORE 8192 +#define ITEM_BROKER_TYPE_MISC 1 +#define ITEM_BROKER_TYPE_PIERCEWEAPON 8 +#define ITEM_BROKER_TYPE_PLATEARMOR 4194304 +#define ITEM_BROKER_TYPE_POISON 65536 +#define ITEM_BROKER_TYPE_POTION 32768 +#define ITEM_BROKER_TYPE_RECIPEBOOK 8388608 +#define ITEM_BROKER_TYPE_SALESDISPLAY 33554432 +#define ITEM_BROKER_TYPE_SHIELD 32 +#define ITEM_BROKER_TYPE_SLASHWEAPON 2 +#define ITEM_BROKER_TYPE_SPELLSCROLL 64 +#define ITEM_BROKER_TYPE_TINKERED 268435456 +#define ITEM_BROKER_TYPE_TRADESKILL 256 + +#define ITEM_BROKER_SLOT_ANY 0xFFFFFFFF +#define ITEM_BROKER_SLOT_AMMO 65536 +#define ITEM_BROKER_SLOT_CHARM 524288 +#define ITEM_BROKER_SLOT_CHEST 32 +#define ITEM_BROKER_SLOT_CLOAK 262144 +#define ITEM_BROKER_SLOT_DRINK 2097152 +#define ITEM_BROKER_SLOT_EARS 4096 +#define ITEM_BROKER_SLOT_FEET 1024 +#define ITEM_BROKER_SLOT_FOOD 1048576 +#define ITEM_BROKER_SLOT_FOREARMS 128 +#define ITEM_BROKER_SLOT_HANDS 256 +#define ITEM_BROKER_SLOT_HEAD 16 +#define ITEM_BROKER_SLOT_LEGS 512 +#define ITEM_BROKER_SLOT_NECK 8192 +#define ITEM_BROKER_SLOT_PRIMARY 1 +#define ITEM_BROKER_SLOT_PRIMARY_2H 2 +#define ITEM_BROKER_SLOT_RANGE_WEAPON 32768 +#define ITEM_BROKER_SLOT_RING 2048 +#define ITEM_BROKER_SLOT_SECONDARY 8 +#define ITEM_BROKER_SLOT_SHOULDERS 64 +#define ITEM_BROKER_SLOT_WAIST 131072 +#define ITEM_BROKER_SLOT_WRIST 16384 + +#define ITEM_BROKER_STAT_TYPE_NONE 0 +#define ITEM_BROKER_STAT_TYPE_DEF 2 +#define ITEM_BROKER_STAT_TYPE_STR 4 +#define ITEM_BROKER_STAT_TYPE_STA 8 +#define ITEM_BROKER_STAT_TYPE_AGI 16 +#define ITEM_BROKER_STAT_TYPE_WIS 32 +#define ITEM_BROKER_STAT_TYPE_INT 64 +#define ITEM_BROKER_STAT_TYPE_HEALTH 128 +#define ITEM_BROKER_STAT_TYPE_POWER 256 +#define ITEM_BROKER_STAT_TYPE_HEAT 512 +#define ITEM_BROKER_STAT_TYPE_COLD 1024 +#define ITEM_BROKER_STAT_TYPE_MAGIC 2048 +#define ITEM_BROKER_STAT_TYPE_MENTAL 4096 +#define ITEM_BROKER_STAT_TYPE_DIVINE 8192 +#define ITEM_BROKER_STAT_TYPE_POISON 16384 +#define ITEM_BROKER_STAT_TYPE_DISEASE 32768 +#define ITEM_BROKER_STAT_TYPE_CRUSH 65536 +#define ITEM_BROKER_STAT_TYPE_SLASH 131072 +#define ITEM_BROKER_STAT_TYPE_PIERCE 262144 +#define ITEM_BROKER_STAT_TYPE_CRITICAL 524288 +#define ITEM_BROKER_STAT_TYPE_DBL_ATTACK 1048576 +#define ITEM_BROKER_STAT_TYPE_ABILITY_MOD 2097152 +#define ITEM_BROKER_STAT_TYPE_POTENCY 4194304 + + + +#define OVERFLOW_SLOT 0xFFFFFFFE +#define SLOT_INVALID 0xFFFF + +#define ITEM_STAT_STR 0 +#define ITEM_STAT_STA 1 +#define ITEM_STAT_AGI 2 +#define ITEM_STAT_WIS 3 +#define ITEM_STAT_INT 4 + +#define ITEM_STAT_ADORNING 100 +#define ITEM_STAT_AGGRESSION 101 +#define ITEM_STAT_ARTIFICING 102 +#define ITEM_STAT_ARTISTRY 103 +#define ITEM_STAT_CHEMISTRY 104 +#define ITEM_STAT_CRUSHING 105 +#define ITEM_STAT_DEFENSE 106 +#define ITEM_STAT_DEFLECTION 107 +#define ITEM_STAT_DISRUPTION 108 +#define ITEM_STAT_FISHING 109 +#define ITEM_STAT_FLETCHING 110 +#define ITEM_STAT_FOCUS 111 +#define ITEM_STAT_FORESTING 112 +#define ITEM_STAT_GATHERING 113 +#define ITEM_STAT_METAL_SHAPING 114 +#define ITEM_STAT_METALWORKING 115 +#define ITEM_STAT_MINING 116 +#define ITEM_STAT_MINISTRATION 117 +#define ITEM_STAT_ORDINATION 118 +#define ITEM_STAT_PARRY 119 +#define ITEM_STAT_PIERCING 120 +#define ITEM_STAT_RANGED 121 +#define ITEM_STAT_SAFE_FALL 122 +#define ITEM_STAT_SCRIBING 123 +#define ITEM_STAT_SCULPTING 124 +#define ITEM_STAT_SLASHING 125 +#define ITEM_STAT_SUBJUGATION 126 +#define ITEM_STAT_SWIMMING 127 +#define ITEM_STAT_TAILORING 128 +#define ITEM_STAT_TINKERING 129 +#define ITEM_STAT_TRANSMUTING 130 +#define ITEM_STAT_TRAPPING 131 +#define ITEM_STAT_WEAPON_SKILLS 132 + +#define ITEM_STAT_VS_PHYSICAL 200 +#define ITEM_STAT_VS_ELEMENTAL 201 +#define ITEM_STAT_VS_NOXIOUS 202 +#define ITEM_STAT_VS_ARCANE 203 + +//#define ITEM_STAT_VS_SLASH 200 +//#define ITEM_STAT_VS_CRUSH 201 +//#define ITEM_STAT_VS_PIERCE 202 +//#define ITEM_STAT_VS_HEAT 203 +//#define ITEM_STAT_VS_COLD 204 +//#define ITEM_STAT_VS_MAGIC 205 +//#define ITEM_STAT_VS_MENTAL 206 +//#define ITEM_STAT_VS_DIVINE 207 +//#define ITEM_STAT_VS_DISEASE 208 +//#define ITEM_STAT_VS_POISON 209 +//#define ITEM_STAT_VS_DROWNING 210 +//#define ITEM_STAT_VS_FALLING 211 +//#define ITEM_STAT_VS_PAIN 212 +//#define ITEM_STAT_VS_MELEE 213 + + +#define ITEM_STAT_DMG_SLASH 300 +#define ITEM_STAT_DMG_CRUSH 301 +#define ITEM_STAT_DMG_PIERCE 302 +#define ITEM_STAT_DMG_HEAT 303 +#define ITEM_STAT_DMG_COLD 304 +#define ITEM_STAT_DMG_MAGIC 305 +#define ITEM_STAT_DMG_MENTAL 306 +#define ITEM_STAT_DMG_DIVINE 307 +#define ITEM_STAT_DMG_DISEASE 308 +#define ITEM_STAT_DMG_POISON 309 +#define ITEM_STAT_DMG_DROWNING 310 +#define ITEM_STAT_DMG_FALLING 311 +#define ITEM_STAT_DMG_PAIN 312 +#define ITEM_STAT_DMG_MELEE 313 + + +#define ITEM_STAT_HEALTH 500 +#define ITEM_STAT_POWER 501 +#define ITEM_STAT_CONCENTRATION 502 +#define ITEM_STAT_SAVAGERY 503 + + +#define ITEM_STAT_HPREGEN 600 +#define ITEM_STAT_MANAREGEN 601 +#define ITEM_STAT_HPREGENPPT 602 +#define ITEM_STAT_MPREGENPPT 603 +#define ITEM_STAT_COMBATHPREGENPPT 604 +#define ITEM_STAT_COMBATMPREGENPPT 605 +#define ITEM_STAT_MAXHP 606 +#define ITEM_STAT_MAXHPPERC 607 +#define ITEM_STAT_MAXHPPERCFINAL 608 +#define ITEM_STAT_SPEED 609 +#define ITEM_STAT_SLOW 610 +#define ITEM_STAT_MOUNTSPEED 611 +#define ITEM_STAT_MOUNTAIRSPEED 612 +#define ITEM_STAT_LEAPSPEED 613 +#define ITEM_STAT_LEAPTIME 614 +#define ITEM_STAT_GLIDEEFFICIENCY 615 +#define ITEM_STAT_OFFENSIVESPEED 616 +#define ITEM_STAT_ATTACKSPEED 617 +#define ITEM_STAT_SPELLWEAPONATTACKSPEED 618 +#define ITEM_STAT_MAXMANA 619 +#define ITEM_STAT_MAXMANAPERC 620 +#define ITEM_STAT_MAXATTPERC 621 +#define ITEM_STAT_BLURVISION 622 +#define ITEM_STAT_MAGICLEVELIMMUNITY 623 +#define ITEM_STAT_HATEGAINMOD 624 +#define ITEM_STAT_COMBATEXPMOD 625 +#define ITEM_STAT_TRADESKILLEXPMOD 626 +#define ITEM_STAT_ACHIEVEMENTEXPMOD 627 +#define ITEM_STAT_SIZEMOD 628 +#define ITEM_STAT_DPS 629 +#define ITEM_STAT_SPELLWEAPONDPS 630 +#define ITEM_STAT_STEALTH 631 +#define ITEM_STAT_INVIS 632 +#define ITEM_STAT_SEESTEALTH 633 +#define ITEM_STAT_SEEINVIS 634 +#define ITEM_STAT_EFFECTIVELEVELMOD 635 +#define ITEM_STAT_RIPOSTECHANCE 636 +#define ITEM_STAT_PARRYCHANCE 637 +#define ITEM_STAT_DODGECHANCE 638 +#define ITEM_STAT_AEAUTOATTACKCHANCE 639 +#define ITEM_STAT_SPELLWEAPONAEAUTOATTACKCHANCE 640 +#define ITEM_STAT_DOUBLEATTACKCHANCE 641 +#define ITEM_STAT_PVPDOUBLEATTACKCHANCE 642 +#define ITEM_STAT_SPELLWEAPONAEAUTOATTACKCHANCE 643 +#define ITEM_STAT_PVPSPELLWEAPONDOUBLEATTACKCHANCE 644 +#define ITEM_STAT_SPELLDOUBLEATTACKCHANCE 645 +#define ITEM_STAT_PVPSPELLDOUBLEATTACKCHANCE 646 +#define ITEM_STAT_FLURRY 647 +#define ITEM_STAT_SPELLWEAPONFLURRY 648 +#define ITEM_STAT_MELEEDAMAGEMULTIPLIER 649 +#define ITEM_STAT_EXTRAHARVESTCHANCE 650 +#define ITEM_STAT_EXTRASHIELDBLOCKCHANCE 651 +#define ITEM_STAT_ITEMHPREGENPPT 652 +#define ITEM_STAT_ITEMPPREGENPPT 653 +#define ITEM_STAT_MELEECRITCHANCE 654 +#define ITEM_STAT_CRITAVOIDANCE 655 +#define ITEM_STAT_BENEFICIALCRITCHANCE 656 +#define ITEM_STAT_CRITBONUS 657 +#define ITEM_STAT_PVPCRITBONUS 658 +#define ITEM_STAT_BASEMODIFIER 659 +#define ITEM_STAT_PVPBASEMODIFIER 660 +#define ITEM_STAT_UNCONSCIOUSHPMOD 661 +#define ITEM_STAT_SPELLTIMEREUSEPCT 662 +#define ITEM_STAT_SPELLTIMERECOVERYPCT 663 +#define ITEM_STAT_SPELLTIMECASTPCT 664 +#define ITEM_STAT_SPELLTIMEREUSESPELLONLY 665 +#define ITEM_STAT_MELEEWEAPONRANGE 666 +#define ITEM_STAT_RANGEDWEAPONRANGE 667 +#define ITEM_STAT_FALLINGDAMAGEREDUCTION 668 +#define ITEM_STAT_RIPOSTEDAMAGE 669 +#define ITEM_STAT_MINIMUMDEFLECTIONCHANCE 670 +#define ITEM_STAT_MOVEMENTWEAVE 671 +#define ITEM_STAT_COMBATHPREGEN 672 +#define ITEM_STAT_COMBATMANAREGEN 673 +#define ITEM_STAT_CONTESTSPEEDBOOST 674 +#define ITEM_STAT_TRACKINGAVOIDANCE 675 +#define ITEM_STAT_STEALTHINVISSPEEDMOD 676 +#define ITEM_STAT_LOOT_COIN 677 +#define ITEM_STAT_ARMORMITIGATIONINCREASE 678 +#define ITEM_STAT_AMMOCONSERVATION 679 +#define ITEM_STAT_STRIKETHROUGH 680 +#define ITEM_STAT_STATUSBONUS 681 +#define ITEM_STAT_ACCURACY 682 +#define ITEM_STAT_COUNTERSTRIKE 683 +#define ITEM_STAT_SHIELDBASH 684 +#define ITEM_STAT_WEAPONDAMAGEBONUS 685 +#define ITEM_STAT_WEAPONDAMAGEBONUSMELEEONLY 686 +#define ITEM_STAT_ADDITIONALRIPOSTECHANCE 687 +#define ITEM_STAT_CRITICALMITIGATION 688 +#define ITEM_STAT_PVPTOUGHNESS 689 +#define ITEM_STAT_PVPLETHALITY 690 +#define ITEM_STAT_STAMINABONUS 691 +#define ITEM_STAT_WISDOMMITBONUS 692 +#define ITEM_STAT_HEALRECEIVE 693 +#define ITEM_STAT_HEALRECEIVEPERC 694 +#define ITEM_STAT_PVPCRITICALMITIGATION 695 +#define ITEM_STAT_BASEAVOIDANCEBONUS 696 +#define ITEM_STAT_INCOMBATSAVAGERYREGEN 697 +#define ITEM_STAT_OUTOFCOMBATSAVAGERYREGEN 698 +#define ITEM_STAT_SAVAGERYREGEN 699 +#define ITEM_STAT_SAVAGERYGAINMOD 6100 +#define ITEM_STAT_MAXSAVAGERYLEVEL 6101 + +#define ITEM_STAT_SPELL_DAMAGE 700 +#define ITEM_STAT_HEAL_AMOUNT 701 +#define ITEM_STAT_SPELL_AND_HEAL 702 +#define ITEM_STAT_COMBAT_ART_DAMAGE 703 +#define ITEM_STAT_SPELL_AND_COMBAT_ART_DAMAGE 704 +#define ITEM_STAT_TAUNT_AMOUNT 705 +#define ITEM_STAT_TAUNT_AND_COMBAT_ART_DAMAGE 706 +#define ITEM_STAT_ABILITY_MODIFIER 707 + + + +#pragma pack(1) +struct ItemStatsValues{ + sint16 str; + sint16 sta; + sint16 agi; + sint16 wis; + sint16 int_; + sint16 vs_slash; + sint16 vs_crush; + sint16 vs_pierce; + sint16 vs_heat; + sint16 vs_cold; + sint16 vs_magic; + sint16 vs_mental; + sint16 vs_divine; + sint16 vs_disease; + sint16 vs_poison; + sint16 health; + sint16 power; + sint8 concentration; +}; +struct ItemCore{ + int32 item_id; + sint32 soe_id; + int32 bag_id; + sint32 inv_slot_id; + sint16 slot_id; + int8 index; + int16 icon; + int16 count; + int8 tier; + int8 num_slots; + int32 unique_id; + int8 num_free_slots; + int16 recommended_level; + bool item_locked; +}; +#pragma pack() +struct ItemStat{ + string stat_name; + int8 stat_type; + sint16 stat_subtype; + int16 stat_type_combined; + float value; +}; +struct ItemLevelOverride{ + int8 adventure_class; + int8 tradeskill_class; + int16 level; +}; +struct ItemClass{ + int8 adventure_class; + int8 tradeskill_class; + int16 level; +}; +struct ItemAppearance{ + int16 type; + int8 red; + int8 green; + int8 blue; + int8 highlight_red; + int8 highlight_green; + int8 highlight_blue; +}; +class PlayerItemList; +class Item{ +public: + #pragma pack(1) + struct ItemStatString{ + EQ2_8BitString stat_string; + }; + struct Generic_Info{ + int8 show_name; + int8 creator_flag; + int16 item_flags; + int8 condition; + int32 weight; // num/10 + int32 skill_req1; + int32 skill_req2; + int16 skill_min; + int8 item_type; //0=normal, 1=weapon, 2=range, 3=armor, 4=shield, 5=container, 6=spell scroll, 7=recipe book, 8=food/drink, 9=bauble, 10=house item, 11=ammo, 12=house container + int16 appearance_id; + int8 appearance_red; + int8 appearance_green; + int8 appearance_blue; + int8 appearance_highlight_red; + int8 appearance_highlight_green; + int8 appearance_highlight_blue; + int8 collectable; + int32 offers_quest_id; + int32 part_of_quest_id; + int16 max_charges; + int8 display_charges; + int64 adventure_classes; + int64 tradeskill_classes; + int16 adventure_default_level; + int16 tradeskill_default_level; + int8 usable; + }; + struct Armor_Info { + int16 mitigation_low; + int16 mitigation_high; + }; + struct Weapon_Info { + int16 wield_type; + int16 damage_low1; + int16 damage_high1; + int16 damage_low2; + int16 damage_high2; + int16 damage_low3; + int16 damage_high3; + int16 delay; + float rating; + }; + struct Shield_Info { + Armor_Info armor_info; + }; + struct Ranged_Info { + Weapon_Info weapon_info; + int16 range_low; + int16 range_high; + }; + struct Bag_Info { + int8 num_slots; + int16 weight_reduction; + }; + struct Food_Info{ + int8 type; //0=water, 1=food + int8 level; + float duration; + int8 satiation; + }; + struct Bauble_Info{ + int16 cast; + int16 recovery; + int32 duration; + float recast; + int8 display_slot_optional; + int8 display_cast_time; + int8 display_bauble_type; + float effect_radius; + int32 max_aoe_targets; + int8 display_until_cancelled; + }; + struct Book_Info{ + int8 language; + EQ2_16BitString author; + EQ2_16BitString title; + }; + struct Skill_Info{ + int32 spell_id; + int32 spell_tier; + }; + struct House_Info{ + int32 status_rent_reduction; + }; + struct HouseContainer_Info{ + int16 disallowed_types; + int16 allowed_types; + int8 num_slots; + }; + struct RecipeBook_Info{ + vector recipes; + int8 uses; + }; + struct Thrown_Info{ + sint32 range; + sint32 damage_modifier; + float hit_bonus; + int32 damage_type; + }; + struct ItemEffect{ + EQ2_16BitString effect; + int8 percentage; + int8 subbulletflag; + }; + #pragma pack() + Item(); + Item(Item* in_item); + ~Item(); + string lowername; + string name; + string description; + int8 stack_count; + int32 sell_price; + int32 max_sell_value; + bool save_needed; + int8 weapon_type; + string adornment; + string creator; + vector item_stats; + vector item_string_stats; + vector item_level_overrides; + vector item_effects; + Generic_Info generic_info; + Weapon_Info* weapon_info; + Ranged_Info* ranged_info; + Armor_Info* armor_info; + Bag_Info* bag_info; + Food_Info* food_info; + Bauble_Info* bauble_info; + Book_Info* book_info; + Skill_Info* skill_info; + RecipeBook_Info* recipebook_info; + Thrown_Info* thrown_info; + vector slot_data; + ItemCore details; + int32 spell_id; + int8 spell_tier; + string item_script; + + void AddEffect(string effect, int8 percentage, int8 subbulletflag); + int32 GetMaxSellValue(); + void SetMaxSellValue(int32 val); + void SetItem(Item* old_item); + int16 GetOverrideLevel(int8 adventure_class, int8 tradeskill_class); + void AddLevelOverride(int8 adventure_class, int8 tradeskill_class, int16 level); + void AddLevelOverride(ItemLevelOverride* class_); + bool CheckClassLevel(int8 adventure_class, int8 tradeskill_class, int16 level); + bool CheckClass(int8 adventure_class, int8 tradeskill_class); + bool CheckLevel(int8 adventure_class, int8 tradeskill_class, int16 level); + void SetAppearance(int16 type, int8 red, int8 green, int8 blue, int8 highlight_red, int8 highlight_green, int8 highlight_blue); + void SetAppearance(ItemAppearance* appearance); + void AddStat(ItemStat* in_stat); + void AddStatString(ItemStatString* in_stat); + void AddStat(int8 type, int16 subtype, float value, char* name = 0); + void SetWeaponType(int8 type); + int8 GetWeaponType(); + bool HasSlot(int8 slot, int8 slot2 = 255); + bool IsNormal(); + bool IsWeapon(); + bool IsArmor(); + bool IsRanged(); + bool IsBag(); + bool IsFood(); + bool IsBauble(); + bool IsSkill(); + bool IsHouseItem(); + bool IsHouseContainer(); + bool IsShield(); + bool IsAdornment(); + bool IsAmmo(); + bool IsBook(); + bool IsChainArmor(); + bool IsClothArmor(); + bool IsCollectable(); + bool IsCloak(); + bool IsCrushWeapon(); + bool IsFoodFood(); + bool IsFoodDrink(); + bool IsJewelry(); + bool IsLeatherArmor(); + bool IsMisc(); + bool IsPierceWeapon(); + bool IsPlateArmor(); + bool IsPoison(); + bool IsPotion(); + bool IsRecipeBook(); + bool IsSalesDisplay(); + bool IsSlashWeapon(); + bool IsSpellScroll(); + bool IsTinkered(); + bool IsTradeskill(); + bool IsThrown(); + void SetItemScript(string name); + const char* GetItemScript(); + int32 CalculateRepairCost(); + + void SetItemType(int8 in_type); + void serialize(PacketStruct* packet, bool show_name = false, Player* player = 0, int16 packet_type = 0, int8 subtype = 0, bool loot_item = false); + EQ2Packet* serialize(int16 version, bool show_name = false, Player* player = 0, bool include_twice = true, int16 packet_type = 0, int8 subtype = 0, bool merchant_item = false, bool loot_item = false); + PacketStruct* PrepareItem(int16 version, bool merchant_item = false, bool loot_item = false); + bool CheckFlag(int32 flag); + void AddSlot(int8 slot_id); + void SetSlots(int32 slots); + bool needs_deletion; +}; +class MasterItemList{ +public: + ~MasterItemList(); + map items; + + Item* GetItem(int32 id); + Item* GetItemByName(const char *name); + ItemStatsValues* CalculateItemBonuses(int32 item_id); + ItemStatsValues* CalculateItemBonuses(Item* desc, ItemStatsValues* values = 0); + vector* GetItems(string name, int32 itype, int32 ltype, int32 btype, int64 minprice, int64 maxprice, int8 minskill, int8 maxskill, string seller, string adornment, int8 mintier, int8 maxtier, int16 minlevel, int16 maxlevel, sint8 itemclass); + vector* GetItems(map criteria); + void AddItem(Item* item); + bool IsBag(int32 item_id); + void RemoveAll(); + static int32 NextUniqueID(); + static void ResetUniqueID(int32 new_id); + static int32 next_unique_id; +}; +class PlayerItemList { +public: + PlayerItemList(); + ~PlayerItemList(); +// int16 number; + map indexed_items; + map > items; +// map< int8, Item* > inv_items; +// map< int8, Item* > bank_items; + bool SharedBankAddAllowed(Item* item); + vector* GetItemsFromBagID(sint32 bag_id); + vector* GetItemsInBag(Item* bag); + Item* GetBag(int8 inventory_slot, bool lock = true); + bool HasItem(int32 id, bool include_bank = false); + Item* GetItemFromIndex(int32 index); + void MoveItem(Item* item, sint32 inv_slot, int16 slot, bool erase_old = true); + bool MoveItem(sint32 to_bag_id, int16 from_index, sint8 to, int8 charges); + Item* GetItemFromUniqueID(int32 item_id, bool include_bank = false, bool lock = true); + Item* GetItemFromID(int32 item_id, int8 count = 0, bool include_bank = false, bool lock = true); + bool AssignItemToFreeSlot(Item* item); + int16 GetNumberOfFreeSlots(); + int16 GetNumberOfItems(); + bool HasFreeSlot(); + bool HasFreeBagSlot(); + void DestroyItem(int16 index); + Item* CanStack(Item* item, bool include_bank = false); + + void RemoveItem(Item* item, bool delete_item = false); + void AddItem(Item* item); + + Item* GetItem(sint32 bag_slot, int16 slot); + + EQ2Packet* serialize(Player* player, int16 version); + uchar* xor_packet; + uchar* orig_packet; + map* GetAllItems(); + bool HasFreeBankSlot(); + int8 FindFreeBankSlot(); + +private: + void Stack(Item* orig_item, Item* item); + Mutex MPlayerItems; + int16 packet_count; +}; +class OverFlowItemList : public PlayerItemList { +public: + bool OverFlowSlotFull(); + int8 GetNextOverFlowSlot(); + bool AddItem(Item* item); + Item* GetOverFlowItem(); +}; +class EquipmentItemList{ +public: + EquipmentItemList(); + EquipmentItemList(const EquipmentItemList& list); + ~EquipmentItemList(); + Item* items[NUM_SLOTS]; + + vector* GetAllEquippedItems(); + + bool HasItem(int32 id); + int8 GetNumberOfItems(); + Item* GetItemFromUniqueID(int32 item_id); + Item* GetItemFromItemID(int32 item_id); + void SetItem(int8 slot_id, Item* item); + void RemoveItem(int8 slot, bool delete_item = false); + Item* GetItem(int8 slot_id); + bool AddItem(int8 slot, Item* item); + bool CheckEquipSlot(Item* tmp, int8 slot); + bool CanItemBeEquippedInSlot(Item* tmp, int8 slot); + int8 GetFreeSlot(Item* tmp, int8 slot_id = 255); + ItemStatsValues* CalculateEquipmentBonuses(); + EQ2Packet* serialize(int16 version); + uchar* xor_packet; + uchar* orig_packet; +private: + Mutex MEquipmentItems; +}; + +#endif + diff --git a/source/WorldServer/Items/Items_DoV.h b/source/WorldServer/Items/Items_DoV.h new file mode 100644 index 0000000..9bfc87e --- /dev/null +++ b/source/WorldServer/Items/Items_DoV.h @@ -0,0 +1,916 @@ +/* + 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 . +*/ +#ifndef __EQ2_ITEMS__ +#define __EQ2_ITEMS__ +#include +#include +#include "../../common/types.h" +#include "../../common/DataBuffer.h" +#include "../../common/MiscFunctions.h" +#include "../Commands/Commands.h" +#include "../../common/ConfigReader.h" + +using namespace std; +class MasterItemList; +class Player; +class Entity; +extern MasterItemList master_item_list; +#define EQ2_PRIMARY_SLOT 0 +#define EQ2_SECONDARY_SLOT 1 +#define EQ2_HEAD_SLOT 2 +#define EQ2_CHEST_SLOT 3 +#define EQ2_SHOULDERS_SLOT 4 +#define EQ2_FOREARMS_SLOT 5 +#define EQ2_HANDS_SLOT 6 +#define EQ2_LEGS_SLOT 7 +#define EQ2_FEET_SLOT 8 +#define EQ2_LRING_SLOT 9 +#define EQ2_RRING_SLOT 10 +#define EQ2_EARS_SLOT_1 11 +#define EQ2_EARS_SLOT_2 12 +#define EQ2_NECK_SLOT 13 +#define EQ2_LWRIST_SLOT 14 +#define EQ2_RWRIST_SLOT 15 +#define EQ2_RANGE_SLOT 16 +#define EQ2_AMMO_SLOT 17 +#define EQ2_WAIST_SLOT 18 +#define EQ2_CLOAK_SLOT 19 +#define EQ2_CHARM_SLOT_1 20 +#define EQ2_CHARM_SLOT_2 21 +#define EQ2_FOOD_SLOT 22 +#define EQ2_DRINK_SLOT 23 +#define EQ2_TEXTURES_SLOT 24 +#define EQ2_HAIR_SLOT 25 +#define EQ2_BEARD_SLOT 26 +#define EQ2_WINGS_SLOT 27 +#define EQ2_NAKED_CHEST_SLOT 28 +#define EQ2_NAKED_LEGS_SLOT 29 +#define EQ2_BACK_SLOT 30 + +#define PRIMARY_SLOT 1 +#define SECONDARY_SLOT 2 +#define HEAD_SLOT 4 +#define CHEST_SLOT 8 +#define SHOULDERS_SLOT 16 +#define FOREARMS_SLOT 32 +#define HANDS_SLOT 64 +#define LEGS_SLOT 128 +#define FEET_SLOT 256 +#define LRING_SLOT 512 +#define RRING_SLOT 1024 +#define EARS_SLOT_1 2048 +#define EARS_SLOT_2 4096 +#define NECK_SLOT 8192 +#define LWRIST_SLOT 16384 +#define RWRIST_SLOT 32768 +#define RANGE_SLOT 65536 +#define AMMO_SLOT 131072 +#define WAIST_SLOT 262144 +#define CLOAK_SLOT 524288 +#define CHARM_SLOT_1 1048576 +#define CHARM_SLOT_2 2097152 +#define FOOD_SLOT 4194304 +#define DRINK_SLOT 8388608 +#define TEXTURES_SLOT 16777216 +#define HAIR_SLOT 33554432 +#define BEARD_SLOT 67108864 +#define WINGS_SLOT 134217728 +#define NAKED_CHEST_SLOT 268435456 +#define NAKED_LEGS_SLOT 536870912 +#define BACK_SLOT 1073741824 + +#define NUM_BANK_SLOTS 12 +#define NUM_SHARED_BANK_SLOTS 8 +#define NUM_SLOTS 25 +#define NUM_INV_SLOTS 6 +#define INV_SLOT1 0 +#define INV_SLOT2 50 +#define INV_SLOT3 100 +#define INV_SLOT4 150 +#define INV_SLOT5 200 +#define INV_SLOT6 250 +#define BANK_SLOT1 1000 +#define BANK_SLOT2 1100 +#define BANK_SLOT3 1200 +#define BANK_SLOT4 1300 +#define BANK_SLOT5 1400 +#define BANK_SLOT6 1500 +#define BANK_SLOT7 1600 +#define BANK_SLOT8 1700 +#define ATTUNED 1 +#define ATTUNEABLE 2 +#define ARTIFACT 4 +#define LORE 8 +#define TEMPORARY 16 +#define NO_TRADE 32 +#define NO_VALUE 64 +#define NO_ZONE 128 +#define NO_DESTROY 256 +#define CRAFTED 512 +#define GOOD_ONLY 1024 +#define EVIL_ONLY 2048 + +#define ITEM_WIELD_TYPE_DUAL 1 +#define ITEM_WIELD_TYPE_SINGLE 2 +#define ITEM_WIELD_TYPE_TWO_HAND 4 + +#define ITEM_TYPE_NORMAL 0 +#define ITEM_TYPE_WEAPON 1 +#define ITEM_TYPE_RANGED 2 +#define ITEM_TYPE_ARMOR 3 +#define ITEM_TYPE_SHIELD 4 +#define ITEM_TYPE_BAG 5 +#define ITEM_TYPE_SKILL 6 +#define ITEM_TYPE_RECIPE 7 +#define ITEM_TYPE_FOOD 8 +#define ITEM_TYPE_BAUBLE 9 +#define ITEM_TYPE_HOUSE 10 +#define ITEM_TYPE_THROWN 11 +#define ITEM_TYPE_HOUSE_CONTAINER 12 +#define ITEM_TYPE_BOOK 13 +#define ITEM_TYPE_ADORNMENT 14 +#define ITEM_TYPE_PATTERN 15 +#define ITEM_TYPE_ARMORSET 16 + +#define ITEM_MENU_TYPE_GENERIC 1 +#define ITEM_MENU_TYPE_EQUIP 2 +#define ITEM_MENU_TYPE_BAG 4 +#define ITEM_MENU_TYPE_HOUSE 8 +#define ITEM_MENU_TYPE_SCRIBE 32 +#define ITEM_MENU_TYPE_INVALID 128 +#define ITEM_MENU_TYPE_BROKEN 512 +#define ITEM_MENU_TYPE_ATTUNED 2048 +#define ITEM_MENU_TYPE_ATTUNEABLE 4096 +#define ITEM_MENU_TYPE_BOOK 8192 +#define ITEM_MENU_TYPE_DISPLAY_CHARGES 16384 +#define ITEM_MENU_TYPE_NAMEPET 65536 +#define ITEM_MENU_TYPE_CONSUME 262144 +#define ITEM_MENU_TYPE_USE 524288 +#define ITEM_MENU_TYPE_DRINK 8388608 +#define ITEM_MENU_TYPE_REDEEM 536870912 + +#define ITEM_TAG_UNCOMMON 3 //tier tags +#define ITEM_TAG_TREASURED 4 +#define ITEM_TAG_LEGENDARY 7 +#define ITEM_TAG_FABLED 9 +#define ITEM_TAG_MYTHICAL 12 + +#define ITEM_BROKER_TYPE_ANY 0xFFFFFFFF +#define ITEM_BROKER_TYPE_ADORNMENT 134217728 +#define ITEM_BROKER_TYPE_AMMO 1024 +#define ITEM_BROKER_TYPE_ATTUNEABLE 16384 +#define ITEM_BROKER_TYPE_BAG 2048 +#define ITEM_BROKER_TYPE_BAUBLE 16777216 +#define ITEM_BROKER_TYPE_BOOK 128 +#define ITEM_BROKER_TYPE_CHAINARMOR 2097152 +#define ITEM_BROKER_TYPE_CLOAK 1073741824 +#define ITEM_BROKER_TYPE_CLOTHARMOR 524288 +#define ITEM_BROKER_TYPE_COLLECTABLE 67108864 +#define ITEM_BROKER_TYPE_CRUSHWEAPON 4 +#define ITEM_BROKER_TYPE_DRINK 131072 +#define ITEM_BROKER_TYPE_FOOD 4096 +#define ITEM_BROKER_TYPE_HOUSEITEM 512 +#define ITEM_BROKER_TYPE_JEWELRY 262144 +#define ITEM_BROKER_TYPE_LEATHERARMOR 1048576 +#define ITEM_BROKER_TYPE_LORE 8192 +#define ITEM_BROKER_TYPE_MISC 1 +#define ITEM_BROKER_TYPE_PIERCEWEAPON 8 +#define ITEM_BROKER_TYPE_PLATEARMOR 4194304 +#define ITEM_BROKER_TYPE_POISON 65536 +#define ITEM_BROKER_TYPE_POTION 32768 +#define ITEM_BROKER_TYPE_RECIPEBOOK 8388608 +#define ITEM_BROKER_TYPE_SALESDISPLAY 33554432 +#define ITEM_BROKER_TYPE_SHIELD 32 +#define ITEM_BROKER_TYPE_SLASHWEAPON 2 +#define ITEM_BROKER_TYPE_SPELLSCROLL 64 +#define ITEM_BROKER_TYPE_TINKERED 268435456 +#define ITEM_BROKER_TYPE_TRADESKILL 256 + +#define ITEM_BROKER_SLOT_ANY 0xFFFFFFFF +#define ITEM_BROKER_SLOT_AMMO 65536 +#define ITEM_BROKER_SLOT_CHARM 524288 +#define ITEM_BROKER_SLOT_CHEST 32 +#define ITEM_BROKER_SLOT_CLOAK 262144 +#define ITEM_BROKER_SLOT_DRINK 2097152 +#define ITEM_BROKER_SLOT_EARS 4096 +#define ITEM_BROKER_SLOT_FEET 1024 +#define ITEM_BROKER_SLOT_FOOD 1048576 +#define ITEM_BROKER_SLOT_FOREARMS 128 +#define ITEM_BROKER_SLOT_HANDS 256 +#define ITEM_BROKER_SLOT_HEAD 16 +#define ITEM_BROKER_SLOT_LEGS 512 +#define ITEM_BROKER_SLOT_NECK 8192 +#define ITEM_BROKER_SLOT_PRIMARY 1 +#define ITEM_BROKER_SLOT_PRIMARY_2H 2 +#define ITEM_BROKER_SLOT_RANGE_WEAPON 32768 +#define ITEM_BROKER_SLOT_RING 2048 +#define ITEM_BROKER_SLOT_SECONDARY 8 +#define ITEM_BROKER_SLOT_SHOULDERS 64 +#define ITEM_BROKER_SLOT_WAIST 131072 +#define ITEM_BROKER_SLOT_WRIST 16384 + +#define ITEM_BROKER_STAT_TYPE_NONE 0 +#define ITEM_BROKER_STAT_TYPE_DEF 2 +#define ITEM_BROKER_STAT_TYPE_STR 4 +#define ITEM_BROKER_STAT_TYPE_STA 8 +#define ITEM_BROKER_STAT_TYPE_AGI 16 +#define ITEM_BROKER_STAT_TYPE_WIS 32 +#define ITEM_BROKER_STAT_TYPE_INT 64 +#define ITEM_BROKER_STAT_TYPE_HEALTH 128 +#define ITEM_BROKER_STAT_TYPE_POWER 256 +#define ITEM_BROKER_STAT_TYPE_HEAT 512 +#define ITEM_BROKER_STAT_TYPE_COLD 1024 +#define ITEM_BROKER_STAT_TYPE_MAGIC 2048 +#define ITEM_BROKER_STAT_TYPE_MENTAL 4096 +#define ITEM_BROKER_STAT_TYPE_DIVINE 8192 +#define ITEM_BROKER_STAT_TYPE_POISON 16384 +#define ITEM_BROKER_STAT_TYPE_DISEASE 32768 +#define ITEM_BROKER_STAT_TYPE_CRUSH 65536 +#define ITEM_BROKER_STAT_TYPE_SLASH 131072 +#define ITEM_BROKER_STAT_TYPE_PIERCE 262144 +#define ITEM_BROKER_STAT_TYPE_CRITICAL 524288 +#define ITEM_BROKER_STAT_TYPE_DBL_ATTACK 1048576 +#define ITEM_BROKER_STAT_TYPE_ABILITY_MOD 2097152 +#define ITEM_BROKER_STAT_TYPE_POTENCY 4194304 + + + +#define OVERFLOW_SLOT 0xFFFFFFFE +#define SLOT_INVALID 0xFFFF + +#define ITEM_STAT_STR 0 +#define ITEM_STAT_STA 1 +#define ITEM_STAT_AGI 2 +#define ITEM_STAT_WIS 3 +#define ITEM_STAT_INT 4 + +#define ITEM_STAT_ADORNING 100 +#define ITEM_STAT_AGGRESSION 101 +#define ITEM_STAT_ARTIFICING 102 +#define ITEM_STAT_ARTISTRY 103 +#define ITEM_STAT_CHEMISTRY 104 +#define ITEM_STAT_CRUSHING 105 +#define ITEM_STAT_DEFENSE 106 +#define ITEM_STAT_DEFLECTION 107 +#define ITEM_STAT_DISRUPTION 108 +#define ITEM_STAT_FISHING 109 +#define ITEM_STAT_FLETCHING 110 +#define ITEM_STAT_FOCUS 111 +#define ITEM_STAT_FORESTING 112 +#define ITEM_STAT_GATHERING 113 +#define ITEM_STAT_METAL_SHAPING 114 +#define ITEM_STAT_METALWORKING 115 +#define ITEM_STAT_MINING 116 +#define ITEM_STAT_MINISTRATION 117 +#define ITEM_STAT_ORDINATION 118 +#define ITEM_STAT_PARRY 119 +#define ITEM_STAT_PIERCING 120 +#define ITEM_STAT_RANGED 121 +#define ITEM_STAT_SAFE_FALL 122 +#define ITEM_STAT_SCRIBING 123 +#define ITEM_STAT_SCULPTING 124 +#define ITEM_STAT_SLASHING 125 +#define ITEM_STAT_SUBJUGATION 126 +#define ITEM_STAT_SWIMMING 127 +#define ITEM_STAT_TAILORING 128 +#define ITEM_STAT_TINKERING 129 +#define ITEM_STAT_TRANSMUTING 130 +#define ITEM_STAT_TRAPPING 131 +#define ITEM_STAT_WEAPON_SKILLS 132 + +#define ITEM_STAT_VS_PHYSICAL 200 +#define ITEM_STAT_VS_HEAT 201 //elemental +#define ITEM_STAT_VS_POISON 202 //noxious +#define ITEM_STAT_VS_MAGIC 203 //arcane +#define ITEM_STAT_VS_DROWNING 210 +#define ITEM_STAT_VS_FALLING 211 +#define ITEM_STAT_VS_PAIN 212 +#define ITEM_STAT_VS_MELEE 213 + +#define ITEM_STAT_VS_SLASH 204 +#define ITEM_STAT_VS_CRUSH 205 +#define ITEM_STAT_VS_PIERCE 206 +//#define ITEM_STAT_VS_HEAT 203 //just so no build error +#define ITEM_STAT_VS_COLD 207 +//#define ITEM_STAT_VS_MAGIC 205 //just so no build error +#define ITEM_STAT_VS_MENTAL 208 +#define ITEM_STAT_VS_DIVINE 209 +#define ITEM_STAT_VS_DISEASE 214 +//#define ITEM_STAT_VS_POISON 209 //just so no build error +//#define ITEM_STAT_VS_DROWNING 210 //just so no build error +//#define ITEM_STAT_VS_FALLING 211 //just so no build error +//#define ITEM_STAT_VS_PAIN 212 //just so no build error +//#define ITEM_STAT_VS_MELEE 213 //just so no build error + +#define ITEM_STAT_DMG_SLASH 300 +#define ITEM_STAT_DMG_CRUSH 301 +#define ITEM_STAT_DMG_PIERCE 302 +#define ITEM_STAT_DMG_HEAT 303 +#define ITEM_STAT_DMG_COLD 304 +#define ITEM_STAT_DMG_MAGIC 305 +#define ITEM_STAT_DMG_MENTAL 306 +#define ITEM_STAT_DMG_DIVINE 307 +#define ITEM_STAT_DMG_DISEASE 308 +#define ITEM_STAT_DMG_POISON 309 +#define ITEM_STAT_DMG_DROWNING 310 +#define ITEM_STAT_DMG_FALLING 311 +#define ITEM_STAT_DMG_PAIN 312 +#define ITEM_STAT_DMG_MELEE 313 + +#define ITEM_STAT_HEALTH 500 +#define ITEM_STAT_POWER 501 +#define ITEM_STAT_CONCENTRATION 502 + +#define ITEM_STAT_HPREGEN 600 +#define ITEM_STAT_MANAREGEN 601 +#define ITEM_STAT_HPREGENPPT 602 +#define ITEM_STAT_MPREGENPPT 603 +#define ITEM_STAT_COMBATHPREGENPPT 604 +#define ITEM_STAT_COMBATMPREGENPPT 605 +#define ITEM_STAT_MAXHP 606 +#define ITEM_STAT_MAXHPPERC 607 +#define ITEM_STAT_SPEED 608 +#define ITEM_STAT_SLOW 609 +#define ITEM_STAT_MOUNTSPEED 610 +#define ITEM_STAT_MOUNTAIRSPEED 611 +#define ITEM_STAT_OFFENSIVESPEED 612 +#define ITEM_STAT_ATTACKSPEED 613 +#define ITEM_STAT_MAXMANA 614 +#define ITEM_STAT_MAXMANAPERC 615 +#define ITEM_STAT_MAXATTPERC 616 +#define ITEM_STAT_BLURVISION 617 +#define ITEM_STAT_MAGICLEVELIMMUNITY 618 +#define ITEM_STAT_HATEGAINMOD 619 +#define ITEM_STAT_COMBATEXPMOD 620 +#define ITEM_STAT_TRADESKILLEXPMOD 621 +#define ITEM_STAT_ACHIEVEMENTEXPMOD 622 +#define ITEM_STAT_SIZEMOD 623 +#define ITEM_STAT_DPS 624 +#define ITEM_STAT_STEALTH 625 +#define ITEM_STAT_INVIS 626 +#define ITEM_STAT_SEESTEALTH 627 +#define ITEM_STAT_SEEINVIS 628 +#define ITEM_STAT_EFFECTIVELEVELMOD 629 +#define ITEM_STAT_RIPOSTECHANCE 630 +#define ITEM_STAT_PARRYCHANCE 631 +#define ITEM_STAT_DODGECHANCE 632 +#define ITEM_STAT_AEAUTOATTACKCHANCE 633 +#define ITEM_STAT_MULTIATTACKCHANCE 634 +#define ITEM_STAT_SPELLMULTIATTACKCHANCE 635 +#define ITEM_STAT_FLURRY 636 +#define ITEM_STAT_MELEEDAMAGEMULTIPLIER 637 +#define ITEM_STAT_EXTRAHARVESTCHANCE 638 +#define ITEM_STAT_EXTRASHIELDBLOCKCHANCE 639 +#define ITEM_STAT_ITEMHPREGENPPT 640 +#define ITEM_STAT_ITEMPPREGENPPT 641 +#define ITEM_STAT_MELEECRITCHANCE 642 +#define ITEM_STAT_CRITAVOIDANCE 643 +#define ITEM_STAT_BENEFICIALCRITCHANCE 644 +#define ITEM_STAT_CRITBONUS 645 +#define ITEM_STAT_POTENCY 646 +#define ITEM_STAT_UNCONSCIOUSHPMOD 647 +#define ITEM_STAT_ABILITYREUSESPEED 648 +#define ITEM_STAT_ABILITYRECOVERYSPEED 649 +#define ITEM_STAT_ABILITYCASTINGSPEED 650 +#define ITEM_STAT_SPELLREUSESPEED 651 +#define ITEM_STAT_MELEEWEAPONRANGE 652 +#define ITEM_STAT_RANGEDWEAPONRANGE 653 +#define ITEM_STAT_FALLINGDAMAGEREDUCTION 654 +#define ITEM_STAT_RIPOSTEDAMAGE 655 +#define ITEM_STAT_MINIMUMDEFLECTIONCHANCE 656 +#define ITEM_STAT_MOVEMENTWEAVE 657 +#define ITEM_STAT_COMBATHPREGEN 658 +#define ITEM_STAT_COMBATMANAREGEN 659 +#define ITEM_STAT_CONTESTSPEEDBOOST 660 +#define ITEM_STAT_TRACKINGAVOIDANCE 661 +#define ITEM_STAT_STEALTHINVISSPEEDMOD 662 +#define ITEM_STAT_LOOT_COIN 663 +#define ITEM_STAT_ARMORMITIGATIONINCREASE 664 +#define ITEM_STAT_AMMOCONSERVATION 665 +#define ITEM_STAT_STRIKETHROUGH 666 +#define ITEM_STAT_STATUSBONUS 667 +#define ITEM_STAT_ACCURACY 668 +#define ITEM_STAT_COUNTERSTRIKE 669 +#define ITEM_STAT_SHIELDBASH 670 +#define ITEM_STAT_WEAPONDAMAGEBONUS 671 +#define ITEM_STAT_ADDITIONALRIPOSTECHANCE 672 +#define ITEM_STAT_CRITICALMITIGATION 673 +#define ITEM_STAT_PVPTOUGHNESS 674 +#define ITEM_STAT_STAMINABONUS 675 +#define ITEM_STAT_WISDOMITBONUS 676 +#define ITEM_STAT_HEALRECEIVE 677 +#define ITEM_STAT_HEALRECEIVEPERC 678 +#define ITEM_STAT_PVPCRITICALMITIGATION 679 +#define ITEM_STAT_BASEAVOIDANCEBONUS 680 + + +//#define ITEM_STAT_HPREGEN 600 +//#define ITEM_STAT_MANAREGEN 601 +//#define ITEM_STAT_HPREGENPPT 602 +//#define ITEM_STAT_MPREGENPPT 603 +//#define ITEM_STAT_COMBATHPREGENPPT 604 +//#define ITEM_STAT_COMBATMPREGENPPT 605 +//#define ITEM_STAT_MAXHP 606 +//#define ITEM_STAT_MAXHPPERC 607 +//#define ITEM_STAT_SPEED 608 +//#define ITEM_STAT_SLOW 609 +//#define ITEM_STAT_MOUNTSPEED 610 +//#define ITEM_STAT_OFFENSIVESPEED 611 +//#define ITEM_STAT_ATTACKSPEED 612 +//#define ITEM_STAT_MAXMANA 613 +//#define ITEM_STAT_MAXMANAPERC 614 +//#define ITEM_STAT_MAXATTPERC 615 +//#define ITEM_STAT_BLURVISION 616 +//#define ITEM_STAT_MAGICLEVELIMMUNITY 617 +//#define ITEM_STAT_HATEGAINMOD 618 +//#define ITEM_STAT_COMBATEXPMOD 619 +//#define ITEM_STAT_TRADESKILLEXPMOD 620 +//#define ITEM_STAT_ACHIEVEMENTEXPMOD 621 +//#define ITEM_STAT_SIZEMOD 622 +//#define ITEM_STAT_UNKNOWN 623 +//#define ITEM_STAT_STEALTH 624 +//#define ITEM_STAT_INVIS 625 +//#define ITEM_STAT_SEESTEALTH 626 +//#define ITEM_STAT_SEEINVIS 627 +//#define ITEM_STAT_EFFECTIVELEVELMOD 628 +//#define ITEM_STAT_RIPOSTECHANCE 629 +//#define ITEM_STAT_PARRYCHANCE 630 +//#define ITEM_STAT_DODGECHANCE 631 +//#define ITEM_STAT_AEAUTOATTACKCHANCE 632 +//#define ITEM_STAT_DOUBLEATTACKCHANCE 633 +//#define ITEM_STAT_RANGEDDOUBLEATTACKCHANCE 634 +//#define ITEM_STAT_SPELLDOUBLEATTACKCHANCE 635 +//#define ITEM_STAT_FLURRY 636 +//#define ITEM_STAT_EXTRAHARVESTCHANCE 637 +//#define ITEM_STAT_EXTRASHIELDBLOCKCHANCE 638 +#define ITEM_STAT_DEFLECTIONCHANCE 400 //just so no build error +//#define ITEM_STAT_ITEMHPREGENPPT 640 +//#define ITEM_STAT_ITEMPPREGENPPT 641 +//#define ITEM_STAT_MELEECRITCHANCE 642 +//#define ITEM_STAT_RANGEDCRITCHANCE 643 +//#define ITEM_STAT_DMGSPELLCRITCHANCE 644 +//#define ITEM_STAT_HEALSPELLCRITCHANCE 645 +//#define ITEM_STAT_MELEECRITBONUS 646 +//#define ITEM_STAT_RANGEDCRITBONUS 647 +//#define ITEM_STAT_DMGSPELLCRITBONUS 648 +//#define ITEM_STAT_HEALSPELLCRITBONUS 649 +//#define ITEM_STAT_UNCONSCIOUSHPMOD 650 +//#define ITEM_STAT_SPELLTIMEREUSEPCT 651 +//#define ITEM_STAT_SPELLTIMERECOVERYPCT 652 +//#define ITEM_STAT_SPELLTIMECASTPCT 653 +//#define ITEM_STAT_MELEEWEAPONRANGE 654 +//#define ITEM_STAT_RANGEDWEAPONRANGE 655 +//#define ITEM_STAT_FALLINGDAMAGEREDUCTION 656 +//#define ITEM_STAT_SHIELDEFFECTIVENESS 657 +//#define ITEM_STAT_RIPOSTEDAMAGE 658 +//#define ITEM_STAT_MINIMUMDEFLECTIONCHANCE 659 +//#define ITEM_STAT_MOVEMENTWEAVE 660 +//#define ITEM_STAT_COMBATHPREGEN 661 +//#define ITEM_STAT_COMBATMANAREGEN 662 +//#define ITEM_STAT_CONTESTSPEEDBOOST 663 +//#define ITEM_STAT_TRACKINGAVOIDANCE 664 +//#define ITEM_STAT_STEALTHINVISSPEEDMOD 665 +//#define ITEM_STAT_LOOT_COIN 666 +//#define ITEM_STAT_ARMORMITIGATIONINCREASE 667 +//#define ITEM_STAT_AMMOCONSERVATION 668 +//#define ITEM_STAT_STRIKETHROUGH 669 +//#define ITEM_STAT_STATUSBONUS 670 +//#define ITEM_STAT_ACCURACY 671 +//#define ITEM_STAT_COUNTERSTRIKE 672 +//#define ITEM_STAT_SHIELDBASH 673 +//#define ITEM_STAT_WEAPONDAMAGEBONUS 674 +//#define ITEM_STAT_ADDITIONALRIPOSTECHANCE 675 +//#define ITEM_STAT_CRITICALMITIGATION 676 +//#define ITEM_STAT_COMBATARTDAMAGE 677 +//#define ITEM_STAT_SPELLDAMAGE 678 +//#define ITEM_STAT_HEALAMOUNT 679 +//#define ITEM_STAT_TAUNTAMOUNT 680 + +#define ITEM_STAT_SPELL_DAMAGE 700 +#define ITEM_STAT_HEAL_AMOUNT 701 +#define ITEM_STAT_SPELL_AND_HEAL 702 +#define ITEM_STAT_COMBAT_ART_DAMAGE 703 +#define ITEM_STAT_SPELL_AND_COMBAT_ART_DAMAGE 704 +#define ITEM_STAT_TAUNT_AMOUNT 705 +#define ITEM_STAT_TAUNT_AND_COMBAT_ART_DAMAGE 706 +#define ITEM_STAT_ABILITY_MODIFIER 707 + + + +#pragma pack(1) +struct ItemStatsValues{ + sint16 str; + sint16 sta; + sint16 agi; + sint16 wis; + sint16 int_; + sint16 vs_slash; + sint16 vs_crush; + sint16 vs_pierce; + sint16 vs_heat; + sint16 vs_cold; + sint16 vs_magic; + sint16 vs_mental; + sint16 vs_divine; + sint16 vs_disease; + sint16 vs_poison; + sint16 health; + sint16 power; + sint8 concentration; + sint16 ability_modifier; + sint16 criticalmitigation; + sint16 extrashieldblockchance; + sint16 beneficialcritchance; + sint16 critbonus; + sint16 potency; + sint16 hategainmod; + sint16 abilityreusespeed; + sint16 abilitycastingspeed; + sint16 abilityrecoveryspeed; + sint16 spellreusespeed; + sint16 spellmultiattackchance; + sint16 dps; + sint16 attackspeed; + sint16 multiattackchance; + sint16 aeautoattackchance; + sint16 strikethrough; + sint16 accuracy; + sint16 offensivespeed; +}; +struct ItemCore{ + int32 item_id; + sint32 soe_id; + int32 bag_id; + sint32 inv_slot_id; + sint16 slot_id; + int8 index; + int16 icon; + int16 count; + int8 tier; + int8 num_slots; + int32 unique_id; + int8 num_free_slots; + int16 recommended_level; + bool item_locked; +}; +#pragma pack() +struct ItemStat{ + string stat_name; + int8 stat_type; + sint16 stat_subtype; + int16 stat_type_combined; + float value; +}; +struct ItemLevelOverride{ + int8 adventure_class; + int8 tradeskill_class; + int16 level; +}; +struct ItemClass{ + int8 adventure_class; + int8 tradeskill_class; + int16 level; +}; +struct ItemAppearance{ + int16 type; + int8 red; + int8 green; + int8 blue; + int8 highlight_red; + int8 highlight_green; + int8 highlight_blue; +}; +class PlayerItemList; +class Item{ +public: + #pragma pack(1) + struct ItemStatString{ + EQ2_8BitString stat_string; + }; + struct Generic_Info{ + int8 show_name; + int8 creator_flag; + int16 item_flags; + int8 condition; + int32 weight; // num/10 + int32 skill_req1; + int32 skill_req2; + int16 skill_min; + int8 item_type; //0=normal, 1=weapon, 2=range, 3=armor, 4=shield, 5=bag, 6=scroll, 7=recipe, 8=food, 9=bauble, 10=house item, 11=thrown, 12=house container, 13=adormnet, 14=??, 16=profile, 17=patter set, 18=item set, 19=book, 20=decoration, 21=dungeon maker, 22=marketplace + int16 appearance_id; + int8 appearance_red; + int8 appearance_green; + int8 appearance_blue; + int8 appearance_highlight_red; + int8 appearance_highlight_green; + int8 appearance_highlight_blue; + int8 collectable; + int32 offers_quest_id; + int32 part_of_quest_id; + int16 max_charges; + int8 display_charges; + int64 adventure_classes; + int64 tradeskill_classes; + int16 adventure_default_level; + int16 tradeskill_default_level; + int8 usable; + }; + struct Armor_Info { + int16 mitigation_low; + int16 mitigation_high; + }; + struct Weapon_Info { + int16 wield_type; + int16 damage_low1; + int16 damage_high1; + int16 damage_low2; + int16 damage_high2; + int16 damage_low3; + int16 damage_high3; + int16 delay; + float rating; + }; + struct Shield_Info { + Armor_Info armor_info; + }; + struct Ranged_Info { + Weapon_Info weapon_info; + int16 range_low; + int16 range_high; + }; + struct Bag_Info { + int8 num_slots; + int16 weight_reduction; + }; + struct Food_Info{ + int8 type; //0=water, 1=food + int8 level; + float duration; + int8 satiation; + }; + struct Bauble_Info{ + int16 cast; + int16 recovery; + int32 duration; + float recast; + int8 display_slot_optional; + int8 display_cast_time; + int8 display_bauble_type; + float effect_radius; + int32 max_aoe_targets; + int8 display_until_cancelled; + }; + struct Book_Info{ + int8 language; + EQ2_16BitString author; + EQ2_16BitString title; + }; + struct Skill_Info{ + int32 spell_id; + int32 spell_tier; + }; + struct House_Info{ + int32 status_rent_reduction; + }; + struct HouseContainer_Info{ + int16 disallowed_types; + int16 allowed_types; + int8 num_slots; + }; + struct RecipeBook_Info{ + vector recipes; + int8 uses; + }; + struct Thrown_Info{ + sint32 range; + sint32 damage_modifier; + float hit_bonus; + int32 damage_type; + }; + struct ItemEffect{ + EQ2_16BitString effect; + int8 percentage; + int8 subbulletflag; + }; + #pragma pack() + Item(); + Item(Item* in_item); + ~Item(); + string lowername; + string name; + string description; + int8 stack_count; + int32 sell_price; + int32 max_sell_value; + bool save_needed; + int8 weapon_type; + string adornment; + string creator; + vector item_stats; + vector item_string_stats; + vector item_level_overrides; + vector item_effects; + Generic_Info generic_info; + Weapon_Info* weapon_info; + Ranged_Info* ranged_info; + Armor_Info* armor_info; + Bag_Info* bag_info; + Food_Info* food_info; + Bauble_Info* bauble_info; + Book_Info* book_info; + Skill_Info* skill_info; + RecipeBook_Info* recipebook_info; + Thrown_Info* thrown_info; + vector slot_data; + ItemCore details; + int32 spell_id; + int8 spell_tier; + string item_script; + + void AddEffect(string effect, int8 percentage, int8 subbulletflag); + int32 GetMaxSellValue(); + void SetMaxSellValue(int32 val); + void SetItem(Item* old_item); + int16 GetOverrideLevel(int8 adventure_class, int8 tradeskill_class); + void AddLevelOverride(int8 adventure_class, int8 tradeskill_class, int16 level); + void AddLevelOverride(ItemLevelOverride* class_); + bool CheckClassLevel(int8 adventure_class, int8 tradeskill_class, int16 level); + bool CheckClass(int8 adventure_class, int8 tradeskill_class); + bool CheckLevel(int8 adventure_class, int8 tradeskill_class, int16 level); + void SetAppearance(int16 type, int8 red, int8 green, int8 blue, int8 highlight_red, int8 highlight_green, int8 highlight_blue); + void SetAppearance(ItemAppearance* appearance); + void AddStat(ItemStat* in_stat); + void AddStatString(ItemStatString* in_stat); + void AddStat(int8 type, int16 subtype, float value, char* name = 0); + void SetWeaponType(int8 type); + int8 GetWeaponType(); + bool HasSlot(int8 slot, int8 slot2 = 255); + bool IsNormal(); + bool IsWeapon(); + bool IsArmor(); + bool IsRanged(); + bool IsBag(); + bool IsFood(); + bool IsBauble(); + bool IsSkill(); + bool IsHouseItem(); + bool IsHouseContainer(); + bool IsShield(); + bool IsAdornment(); + bool IsAmmo(); + bool IsBook(); + bool IsChainArmor(); + bool IsClothArmor(); + bool IsCollectable(); + bool IsCloak(); + bool IsCrushWeapon(); + bool IsFoodFood(); + bool IsFoodDrink(); + bool IsJewelry(); + bool IsLeatherArmor(); + bool IsMisc(); + bool IsPierceWeapon(); + bool IsPlateArmor(); + bool IsPoison(); + bool IsPotion(); + bool IsRecipeBook(); + bool IsSalesDisplay(); + bool IsSlashWeapon(); + bool IsSpellScroll(); + bool IsTinkered(); + bool IsTradeskill(); + bool IsThrown(); + void SetItemScript(string name); + const char* GetItemScript(); + int32 CalculateRepairCost(); + + void SetItemType(int8 in_type); + void serialize(PacketStruct* packet, bool show_name = false, Player* player = 0, int16 packet_type = 0, int8 subtype = 0, bool loot_item = false); + EQ2Packet* serialize(int16 version, bool show_name = false, Player* player = 0, bool include_twice = true, int16 packet_type = 0, int8 subtype = 0, bool merchant_item = false, bool loot_item = false); + PacketStruct* PrepareItem(int16 version, bool merchant_item = false, bool loot_item = false); + bool CheckFlag(int32 flag); + void AddSlot(int8 slot_id); + void SetSlots(int32 slots); + bool needs_deletion; +}; +class MasterItemList{ +public: + ~MasterItemList(); + map items; + + Item* GetItem(int32 id); + Item* GetItemByName(const char *name); + ItemStatsValues* CalculateItemBonuses(int32 item_id, Entity* entity = 0); + ItemStatsValues* CalculateItemBonuses(Item* desc, Entity* entity = 0, ItemStatsValues* values = 0); + vector* GetItems(string name, int32 itype, int32 ltype, int32 btype, int64 minprice, int64 maxprice, int8 minskill, int8 maxskill, string seller, string adornment, int8 mintier, int8 maxtier, int16 minlevel, int16 maxlevel, sint8 itemclass); + vector* GetItems(map criteria); + void AddItem(Item* item); + bool IsBag(int32 item_id); + void RemoveAll(); + static int32 NextUniqueID(); + static void ResetUniqueID(int32 new_id); + static int32 next_unique_id; +}; +class PlayerItemList { +public: + PlayerItemList(); + ~PlayerItemList(); +// int16 number; + map indexed_items; + map > items; +// map< int8, Item* > inv_items; +// map< int8, Item* > bank_items; + bool SharedBankAddAllowed(Item* item); + vector* GetItemsFromBagID(sint32 bag_id); + vector* GetItemsInBag(Item* bag); + Item* GetBag(int8 inventory_slot, bool lock = true); + bool HasItem(int32 id, bool include_bank = false); + Item* GetItemFromIndex(int32 index); + void MoveItem(Item* item, sint32 inv_slot, int16 slot, bool erase_old = true); + bool MoveItem(sint32 to_bag_id, int16 from_index, sint8 to, int8 charges); + Item* GetItemFromUniqueID(int32 item_id, bool include_bank = false, bool lock = true); + Item* GetItemFromID(int32 item_id, int8 count = 0, bool include_bank = false, bool lock = true); + bool AssignItemToFreeSlot(Item* item); + int16 GetNumberOfFreeSlots(); + int16 GetNumberOfItems(); + bool HasFreeSlot(); + bool HasFreeBagSlot(); + void DestroyItem(int16 index); + Item* CanStack(Item* item, bool include_bank = false); + + void RemoveItem(Item* item, bool delete_item = false); + void AddItem(Item* item); + + Item* GetItem(sint32 bag_slot, int16 slot); + + EQ2Packet* serialize(Player* player, int16 version); + uchar* xor_packet; + uchar* orig_packet; + map* GetAllItems(); + bool HasFreeBankSlot(); + int8 FindFreeBankSlot(); + + ///Get the first free slot and stor them in the provided variables + ///Will contain the bag id of the first free spot + ///Will contain the slot id of the first free slot + ///True if a free slot was found + bool GetFirstFreeSlot(sint32* bag_id, sint16* slot); +private: + void Stack(Item* orig_item, Item* item); + Mutex MPlayerItems; + int16 packet_count; +}; +class OverFlowItemList : public PlayerItemList { +public: + bool OverFlowSlotFull(); + int8 GetNextOverFlowSlot(); + bool AddItem(Item* item); + Item* GetOverFlowItem(); +}; +class EquipmentItemList{ +public: + EquipmentItemList(); + EquipmentItemList(const EquipmentItemList& list); + ~EquipmentItemList(); + Item* items[NUM_SLOTS]; + + vector* GetAllEquippedItems(); + + bool HasItem(int32 id); + int8 GetNumberOfItems(); + Item* GetItemFromUniqueID(int32 item_id); + Item* GetItemFromItemID(int32 item_id); + void SetItem(int8 slot_id, Item* item); + void RemoveItem(int8 slot, bool delete_item = false); + Item* GetItem(int8 slot_id); + bool AddItem(int8 slot, Item* item); + bool CheckEquipSlot(Item* tmp, int8 slot); + bool CanItemBeEquippedInSlot(Item* tmp, int8 slot); + int8 GetFreeSlot(Item* tmp, int8 slot_id = 255); + ItemStatsValues* CalculateEquipmentBonuses(Entity* entity = 0); + EQ2Packet* serialize(int16 version); + uchar* xor_packet; + uchar* orig_packet; +private: + Mutex MEquipmentItems; +}; + +#endif + diff --git a/source/WorldServer/Items/Items_ToV.h b/source/WorldServer/Items/Items_ToV.h new file mode 100644 index 0000000..30bfbc2 --- /dev/null +++ b/source/WorldServer/Items/Items_ToV.h @@ -0,0 +1,211 @@ +/* + 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 . +*/ + +//Item Stat defines for ToV Client + +//Stat type 6 (blue stats) +#define TOV_ITEM_STAT_HPREGEN 600 +#define TOV_ITEM_STAT_MANAREGEN 601 +#define TOV_ITEM_STAT_HPREGENPPT 602 +#define TOV_ITEM_STAT_MPREGENPPT 603 +#define TOV_ITEM_STAT_COMBATHPREGENPPT 604 +#define TOV_ITEM_STAT_COMBATMPREGENPPT 605 +#define TOV_ITEM_STAT_MAXHP 606 +#define TOV_ITEM_STAT_MAXHPPERC 607 +#define TOV_ITEM_STAT_MAXHPPERCFINAL 608 +#define TOV_ITEM_STAT_SPEED 609 +#define TOV_ITEM_STAT_SLOW 610 +#define TOV_ITEM_STAT_MOUNTSPEED 611 +#define TOV_ITEM_STAT_MOUNTAIRSPEED 612 +#define TOV_ITEM_STAT_LEAPSPEED 613 +#define TOV_ITEM_STAT_LEAPTIME 614 +#define TOV_ITEM_STAT_GLIDEEFFICIENCY 615 +#define TOV_ITEM_STAT_OFFENSIVESPEED 616 +#define TOV_ITEM_STAT_ATTACKSPEED 617 +#define TOV_ITEM_STAT_MAXMANA 618 +#define TOV_ITEM_STAT_MAXMANAPERC 619 +#define TOV_ITEM_STAT_MAXATTPERC 620 +#define TOV_ITEM_STAT_BLURVISION 621 +#define TOV_ITEM_STAT_MAGICLEVELIMMUNITY 622 +#define TOV_ITEM_STAT_HATEGAINMOD 623 +#define TOV_ITEM_STAT_COMBATEXPMOD 624 +#define TOV_ITEM_STAT_TRADESKILLEXPMOD 625 +#define TOV_ITEM_STAT_ACHIEVEMENTEXPMOD 626 +#define TOV_ITEM_STAT_SIZEMOD 627 +#define TOV_ITEM_STAT_DPS 628 +#define TOV_ITEM_STAT_STEALTH 629 +#define TOV_ITEM_STAT_INVIS 630 +#define TOV_ITEM_STAT_SEESTEALTH 631 +#define TOV_ITEM_STAT_SEEINVIS 632 +#define TOV_ITEM_STAT_EFFECTIVELEVELMOD 633 +#define TOV_ITEM_STAT_RIPOSTECHANCE 634 +#define TOV_ITEM_STAT_PARRYCHANCE 635 +#define TOV_ITEM_STAT_DODGECHANCE 636 +#define TOV_ITEM_STAT_AEAUTOATTACKCHANCE 637 +#define TOV_ITEM_STAT_MULTIATTACKCHANCE 638 //DOUBLEATTACKCHANCE +#define TOV_ITEM_STAT_SPELLMULTIATTACKCHANCE 639 +#define TOV_ITEM_STAT_FLURRY 640 +#define TOV_ITEM_STAT_MELEEDAMAGEMULTIPLIER 641 +#define TOV_ITEM_STAT_EXTRAHARVESTCHANCE 642 +#define TOV_ITEM_STAT_EXTRASHIELDBLOCKCHANCE 643 +#define TOV_ITEM_STAT_ITEMHPREGENPPT 644 +#define TOV_ITEM_STAT_ITEMPPREGENPPT 645 +#define TOV_ITEM_STAT_MELEECRITCHANCE 646 +#define TOV_ITEM_STAT_CRITAVOIDANCE 647 +#define TOV_ITEM_STAT_BENEFICIALCRITCHANCE 648 +#define TOV_ITEM_STAT_CRITBONUS 649 +#define TOV_ITEM_STAT_POTENCY 650 //BASEMODIFIER +#define TOV_ITEM_STAT_UNCONSCIOUSHPMOD 651 +#define TOV_ITEM_STAT_ABILITYREUSESPEED 652 //SPELLTIMEREUSEPCT +#define TOV_ITEM_STAT_ABILITYRECOVERYSPEED 653 //SPELLTIMERECOVERYPCT +#define TOV_ITEM_STAT_ABILITYCASTINGSPEED 654 //SPELLTIMECASTPCT +#define TOV_ITEM_STAT_SPELLREUSESPEED 655 //SPELLTIMEREUSESPELLONLY +#define TOV_ITEM_STAT_MELEEWEAPONRANGE 656 +#define TOV_ITEM_STAT_RANGEDWEAPONRANGE 657 +#define TOV_ITEM_STAT_FALLINGDAMAGEREDUCTION 658 +#define TOV_ITEM_STAT_RIPOSTEDAMAGE 659 +#define TOV_ITEM_STAT_MINIMUMDEFLECTIONCHANCE 660 +#define TOV_ITEM_STAT_MOVEMENTWEAVE 661 +#define TOV_ITEM_STAT_COMBATHPREGEN 662 +#define TOV_ITEM_STAT_COMBATMANAREGEN 663 +#define TOV_ITEM_STAT_CONTESTSPEEDBOOST 664 +#define TOV_ITEM_STAT_TRACKINGAVOIDANCE 665 +#define TOV_ITEM_STAT_STEALTHINVISSPEEDMOD 666 +#define TOV_ITEM_STAT_LOOT_COIN 667 +#define TOV_ITEM_STAT_ARMORMITIGATIONINCREASE 668 +#define TOV_ITEM_STAT_AMMOCONSERVATION 669 +#define TOV_ITEM_STAT_STRIKETHROUGH 670 +#define TOV_ITEM_STAT_STATUSBONUS 671 +#define TOV_ITEM_STAT_ACCURACY 672 +#define TOV_ITEM_STAT_COUNTERSTRIKE 673 +#define TOV_ITEM_STAT_SHIELDBASH 674 +#define TOV_ITEM_STAT_WEAPONDAMAGEBONUS 675 +#define TOV_ITEM_STAT_SPELLWEAPONDAMAGEBONUS 676 +#define TOV_ITEM_STAT_WEAPONDAMAGEBONUSMELEEONLY 677 +#define TOV_ITEM_STAT_ADDITIONALRIPOSTECHANCE 678 +#define TOV_ITEM_STAT_PVPTOUGHNESS 680 +#define TOV_ITEM_STAT_PVPLETHALITY 681 +#define TOV_ITEM_STAT_STAMINABONUS 682 +#define TOV_ITEM_STAT_WISDOMMITBONUS 683 +#define TOV_ITEM_STAT_HEALRECEIVE 684 +#define TOV_ITEM_STAT_HEALRECEIVEPERC 685 +#define TOV_ITEM_STAT_PVPCRITICALMITIGATION 686 +#define TOV_ITEM_STAT_BASEAVOIDANCEBONUS 687 +#define TOV_ITEM_STAT_INCOMBATSAVAGERYREGEN 688 +#define TOV_ITEM_STAT_OUTOFCOMBATSAVAGERYREGEN 689 +#define TOV_ITEM_STAT_SAVAGERYREGEN 690 +#define TOV_ITEM_STAT_SAVAGERYGAINMOD 691 +#define TOV_ITEM_STAT_MAXSAVAGERYLEVEL 692 +#define TOV_ITEM_STAT_INCOMBATDISSONANCEREGEN 693 +#define TOV_ITEM_STAT_OUTOFCOMBATDISSONANCEREGEN 694 +#define TOV_ITEM_STAT_DISSONANCEREGEN 695 +#define TOV_ITEM_STAT_DISSONANCEGAINMOD 696 +#define TOV_ITEM_STAT_AEAUTOATTACKAVOID 697 +//End of stat type 6 (blue stats) + + +//Item stat type 5 (health,power,savagery,dissonance,concentration) +#define TOV_ITEM_STAT_HEALTH 500 +#define TOV_ITEM_STAT_POWER 501 +#define TOV_ITEM_STAT_CONCENTRATION 502 +#define TOV_ITEM_STAT_SAVAGERY 503 +#define TOV_ITEM_STAT_DISSONANCE 504 +//End of stat type 5 + +//Item stat type 3 (damage mods) +#define TOV_ITEM_STAT_DMG_SLASH 300 +#define TOV_ITEM_STAT_DMG_CRUSH 301 +#define TOV_ITEM_STAT_DMG_PIERCE 302 +#define TOV_ITEM_STAT_DMG_HEAT 303 +#define TOV_ITEM_STAT_DMG_COLD 304 +#define TOV_ITEM_STAT_DMG_MAGIC 305 +#define TOV_ITEM_STAT_DMG_MENTAL 306 +#define TOV_ITEM_STAT_DMG_DIVINE 307 +#define TOV_ITEM_STAT_DMG_DISEASE 308 +#define TOV_ITEM_STAT_DMG_POISON 309 +#define TOV_ITEM_STAT_DMG_DROWNING 310 +#define TOV_ITEM_STAT_DMG_FALLING 311 +#define TOV_ITEM_STAT_DMG_PAIN 312 +#define TOV_ITEM_STAT_DMG_MELEE 313 +//End of item stat 3 + +#define TOV_ITEM_STAT_DEFLECTIONCHANCE 400 //just so no build error + +// Other stats not listed above (not sent from the server), never send these to the client +// using type 8 as it is not used by the client as far as we know +#define TOV_ITEM_STAT_DURABILITY_MOD 800 +#define TOV_ITEM_STAT_DURABILITY_ADD 801 +#define TOV_ITEM_STAT_PROGRESS_ADD 802 +#define TOV_ITEM_STAT_PROGRESS_MOD 803 +#define TOV_ITEM_STAT_SUCCESS_MOD 804 +#define TOV_ITEM_STAT_CRIT_SUCCESS_MOD 805 +#define TOV_ITEM_STAT_EX_DURABILITY_MOD 806 +#define TOV_ITEM_STAT_EX_DURABILITY_ADD 807 +#define TOV_ITEM_STAT_EX_PROGRESS_MOD 808 +#define TOV_ITEM_STAT_EX_PROGRESS_ADD 809 +#define TOV_ITEM_STAT_EX_SUCCESS_MOD 810 +#define TOV_ITEM_STAT_EX_CRIT_SUCCESS_MOD 811 +#define TOV_ITEM_STAT_EX_CRIT_FAILURE_MOD 812 +#define TOV_ITEM_STAT_RARE_HARVEST_CHANCE 813 +#define TOV_ITEM_STAT_MAX_CRAFTING 814 +#define TOV_ITEM_STAT_COMPONENT_REFUND 815 +#define TOV_ITEM_STAT_BOUNTIFUL_HARVEST 816 + +#define TOV_ITEM_STAT_STR 0 +#define TOV_ITEM_STAT_STA 1 +#define TOV_ITEM_STAT_AGI 2 +#define TOV_ITEM_STAT_WIS 3 +#define TOV_ITEM_STAT_INT 4 + + + +#define TOV_ITEM_STAT_ADORNING 100 +#define TOV_ITEM_STAT_AGGRESSION 101 +#define TOV_ITEM_STAT_ARTIFICING 102 +#define TOV_ITEM_STAT_ARTISTRY 103 +#define TOV_ITEM_STAT_CHEMISTRY 104 +#define TOV_ITEM_STAT_CRUSHING 105 +#define TOV_ITEM_STAT_DEFENSE 106 +#define TOV_ITEM_STAT_DEFLECTION 107 +#define TOV_ITEM_STAT_DISRUPTION 108 +#define TOV_ITEM_STAT_FISHING 109 +#define TOV_ITEM_STAT_FLETCHING 110 +#define TOV_ITEM_STAT_FOCUS 111 +#define TOV_ITEM_STAT_FORESTING 112 +#define TOV_ITEM_STAT_GATHERING 113 +#define TOV_ITEM_STAT_METAL_SHAPING 114 +#define TOV_ITEM_STAT_METALWORKING 115 +#define TOV_ITEM_STAT_MINING 116 +#define TOV_ITEM_STAT_MINISTRATION 117 +#define TOV_ITEM_STAT_ORDINATION 118 +#define TOV_ITEM_STAT_PARRY 119 +#define TOV_ITEM_STAT_PIERCING 120 +#define TOV_ITEM_STAT_RANGED 121 +#define TOV_ITEM_STAT_SAFE_FALL 122 +#define TOV_ITEM_STAT_SCRIBING 123 +#define TOV_ITEM_STAT_SCULPTING 124 +#define TOV_ITEM_STAT_SLASHING 125 +#define TOV_ITEM_STAT_SUBJUGATION 126 +#define TOV_ITEM_STAT_SWIMMING 127 +#define TOV_ITEM_STAT_TAILORING 128 +#define TOV_ITEM_STAT_TINKERING 129 +#define TOV_ITEM_STAT_TRANSMUTING 130 +#define TOV_ITEM_STAT_TRAPPING 131 +#define TOV_ITEM_STAT_WEAPON_SKILLS 132 \ No newline at end of file diff --git a/source/WorldServer/Items/Loot.cpp b/source/WorldServer/Items/Loot.cpp new file mode 100644 index 0000000..112b714 --- /dev/null +++ b/source/WorldServer/Items/Loot.cpp @@ -0,0 +1,134 @@ +/* + 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 . +*/ +#include "Loot.h" +#include "../client.h" +#include "../../common/ConfigReader.h" +#include "../classes.h" +#include "../../common/debug.h" +#include "../zoneserver.h" +#include "../Skills.h" +#include "../classes.h" +#include "../World.h" +#include "../LuaInterface.h" +#include "../../common/Log.h" +#include "../Entity.h" +#include "../Rules/Rules.h" + +extern Classes classes; +extern ConfigReader configReader; +extern MasterSkillList master_skill_list; +extern RuleManager rule_manager; + +// If we want to transfer functions to this file then we should just do it, but for now we don't need all this commented code here + +NPC* Entity::DropChest() { + // Check to see if treasure chests are disabled in the rules + if (rule_manager.GetGlobalRule(R_World, TreasureChestDisabled)->GetBool()) + return 0; + + if(GetChestDropTime()) { + return 0; // this is a chest! It doesn't drop itself! + } + + NPC* chest = 0; + + chest = new NPC(); + chest->SetAttackable(0); + chest->SetShowLevel(0); + chest->SetShowName(1); + chest->SetTargetable(1); + chest->SetLevel(GetLevel()); + chest->SetChestDropTime(); + chest->SetTotalHP(100); + chest->SetHP(100); + chest->SetAlive(false); + // Set the brain to a blank brain so it does nothing + chest->SetBrain(new BlankBrain(chest)); + // Set the x, y, z, heading, location (grid id) to that of the dead spawn + chest->SetZone(GetZone()); + // heading needs to be GetHeading() - 180 so the chest faces the proper way + chest->SetHeading(GetHeading() - 180); + // Set the primary command to loot and the secondary to disarm + chest->AddPrimaryEntityCommand("loot", rule_manager.GetGlobalRule(R_Loot, LootRadius)->GetFloat(), "loot", "", 0, 0); + chest->AddSecondaryEntityCommand("Disarm", rule_manager.GetGlobalRule(R_Loot, LootRadius)->GetFloat(), "Disarm", "", 0, 0); + // 32 = loot icon for the mouse + chest->SetIcon(32); + // 1 = show the right click menu + chest->SetShowCommandIcon(1); + chest->SetLootMethod(this->GetLootMethod(), this->GetLootRarity(), this->GetLootGroupID()); + chest->SetLootName(this->GetName()); + int8 highest_tier = 0; + vector::iterator itr; + for (itr = ((Spawn*)this)->GetLootItems()->begin(); itr != ((Spawn*)this)->GetLootItems()->end(); ) { + if ((*itr)->details.tier >= ITEM_TAG_COMMON && !(*itr)->IsBodyDrop()) { + if ((*itr)->details.tier > highest_tier) + highest_tier = (*itr)->details.tier; + + // Add the item to the chest + chest->AddLootItem((*itr)->details.item_id, (*itr)->details.count); + // Remove the item from the corpse + itr = ((Spawn*)this)->GetLootItems()->erase(itr); + } + else + itr++; + } + + /*4034 = small chest | 5864 = treasure chest | 5865 = ornate treasure chest | 4015 = exquisite chest*/ + if (highest_tier >= ITEM_TAG_FABLED) { + chest->SetModelType(4015); + chest->SetName("Exquisite Chest"); + } + else if (highest_tier >= ITEM_TAG_LEGENDARY) { + chest->SetModelType(5865); + chest->SetName("Ornate Chest"); + } + else if (highest_tier >= ITEM_TAG_TREASURED) { + chest->SetModelType(5864); + chest->SetName("Treasure Chest"); + } + else if (highest_tier >= ITEM_TAG_COMMON) { + chest->SetModelType(4034); + chest->SetName("Small Chest"); + } + else { + safe_delete(chest); + chest = nullptr; + } + + if (chest) { + chest->SetID(Spawn::NextID()); + chest->SetShowHandIcon(1); + chest->SetLocation(GetLocation()); + chest->SetX(GetX()); + chest->SetZ(GetZ()); + ((Entity*)chest)->GetInfoStruct()->set_flying_type(false); + chest->is_flying_creature = false; + if(GetMap()) { + auto loc = glm::vec3(GetX(), GetZ(), GetY()); + float new_z = FindBestZ(loc, nullptr); + chest->appearance.pos.Y = new_z; // don't use SetY here can cause a loop + } + else { + chest->appearance.pos.Y = GetY(); + } + } + + return chest; +} \ No newline at end of file diff --git a/source/WorldServer/Items/Loot.h b/source/WorldServer/Items/Loot.h new file mode 100644 index 0000000..de4f567 --- /dev/null +++ b/source/WorldServer/Items/Loot.h @@ -0,0 +1,23 @@ +/* + 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 . +*/ +#ifndef __EQ2_LOOT__ +#define __EQ2_LOOT__ + +#endif diff --git a/source/WorldServer/Items/LootDB.cpp b/source/WorldServer/Items/LootDB.cpp new file mode 100644 index 0000000..374159a --- /dev/null +++ b/source/WorldServer/Items/LootDB.cpp @@ -0,0 +1,210 @@ +/* + 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 . +*/ + +#include "../../common/Log.h" +#include "../WorldDatabase.h" +#include "../World.h" + +extern World world; + +void WorldDatabase::LoadLoot(ZoneServer* zone) +{ + // First, clear previous loot tables... + zone->ClearLootTables(); + + DatabaseResult result; + int32 count = 0; + + if (database_new.Select(&result, "SELECT id, name, mincoin, maxcoin, maxlootitems, lootdrop_probability, coin_probability FROM loottable")) { + LogWrite(LOOT__DEBUG, 0, "Loot", "--Loading LootTables..."); + LootTable* table = 0; + // Load loottable from DB + while(result.Next()) { + int32 id = result.GetInt32Str("id"); + + table = new LootTable; + table->name = result.GetStringStr("name"); + table->mincoin = result.GetInt32Str("mincoin"); + table->maxcoin = result.GetInt32Str("maxcoin"); + table->maxlootitems = result.GetInt16Str("maxlootitems"); + table->lootdrop_probability = result.GetFloatStr("lootdrop_probability"); + table->coin_probability = result.GetFloatStr("coin_probability"); + zone->AddLootTable(id, table); + + LogWrite(LOOT__DEBUG, 5, "Loot", "---Loading LootTable '%s' (id: %u)", table->name.c_str(), id); + LogWrite(LOOT__DEBUG, 5, "Loot", "---min_coin: %u, max_coin: %u, max_items: %i, prob: %.2f, coin_prob: %.2f", table->mincoin, table->maxcoin, table->maxlootitems, table->lootdrop_probability, table->coin_probability); + count++; + } + LogWrite(LOOT__DEBUG, 0, "Loot", "--Loaded %u loot table%s.", count, count == 1 ? "" : "s"); + } + + // Now, load Loot Drops for configured loot tables + if (database_new.Select(&result, "SELECT loot_table_id, item_id, item_charges, equip_item, probability, no_drop_quest_completed FROM lootdrop")) { + count = 0; + LogWrite(LOOT__DEBUG, 0, "Loot", "--Loading LootDrops..."); + LootDrop* drop = 0; + + while(result.Next()) { + int32 id = result.GetInt32Str("loot_table_id"); + drop = new LootDrop; + drop->item_id = result.GetInt32Str("item_id"); + drop->item_charges = result.GetInt16Str("item_charges"); + drop->equip_item = (result.GetInt8Str("equip_item") == 1); + drop->probability = result.GetFloatStr("probability"); + drop->no_drop_quest_completed_id = result.GetInt32Str("no_drop_quest_completed"); + zone->AddLootDrop(id, drop); + + LogWrite(LOOT__DEBUG, 5, "Loot", "---Loading LootDrop item_id %u (tableID: %u", drop->item_id, id); + LogWrite(LOOT__DEBUG, 5, "Loot", "---charges: %i, equip_item: %i, prob: %.2f", drop->item_charges, drop->equip_item, drop->probability); + count++; + } + LogWrite(LOOT__DEBUG, 0, "Loot", "--Loaded %u loot drop%s.", count, count == 1 ? "" : "s"); + } + + // Finally, load loot tables into spawns that are set to use these loot tables + if (database_new.Select(&result, "SELECT spawn_id, loottable_id FROM spawn_loot")) { + count = 0; + LogWrite(LOOT__DEBUG, 0, "Loot", "--Assigning loot table(s) to spawn(s)..."); + + while(result.Next()) { + int32 spawn_id = result.GetInt32Str("spawn_id"); + int32 table_id = result.GetInt32Str("loottable_id"); + zone->AddSpawnLootList(spawn_id, table_id); + LogWrite(LOOT__DEBUG, 5, "Loot", "---Adding loot table %u to spawn %u", table_id, spawn_id); + count++; + } + LogWrite(LOOT__DEBUG, 0, "Loot", "--Loaded %u spawn loot list%s.", count, count == 1 ? "" : "s"); + } + + // Load global loot lists + LoadGlobalLoot(zone); +} + +void WorldDatabase::LoadGlobalLoot(ZoneServer* zone) { + LogWrite(LOOT__INFO, 0, "Loot", "-Loading Global loot data..."); + DatabaseResult result; + int32 count = 0; + if (database_new.Select(&result, "SELECT type, loot_table, value1, value2, value3, value4 FROM loot_global")) { + while(result.Next()) { + const char* type = result.GetStringStr("type"); + int32 table_id = result.GetInt32Str("loot_table"); + if (strcmp(type, "Level") == 0) { + GlobalLoot* loot = new GlobalLoot(); + loot->minLevel = result.GetInt8Str("value1"); + loot->maxLevel = result.GetInt8Str("value2"); + loot->table_id = table_id; + loot->loot_tier = result.GetInt32Str("value4"); + + if (loot->minLevel > loot->maxLevel) + loot->maxLevel = loot->minLevel; + + zone->AddLevelLootList(loot); + LogWrite(LOOT__DEBUG, 5, "Loot", "---Loading Level %i loot table (id: %u)", loot->minLevel, table_id); + LogWrite(LOOT__DEBUG, 5, "Loot", "---minlevel: %i, maxlevel: %i", loot->minLevel, loot->maxLevel); + } + else if (strcmp(type, "Racial") == 0) { + GlobalLoot* loot = new GlobalLoot(); + int16 race_id = result.GetInt16Str("value1"); + loot->minLevel = result.GetInt8Str("value2"); + loot->maxLevel = result.GetInt8Str("value3"); + loot->table_id = table_id; + loot->loot_tier = result.GetInt32Str("value4"); + + if (loot->minLevel > loot->maxLevel) + loot->maxLevel = loot->minLevel; + + zone->AddRacialLootList(race_id, loot); + LogWrite(LOOT__DEBUG, 5, "Loot", "---Loading Racial %i loot table (id: %u)", race_id, table_id); + LogWrite(LOOT__DEBUG, 5, "Loot", "---minlevel: %i, maxlevel: %i", loot->minLevel, loot->maxLevel); + } + else if (strcmp(type, "Zone") == 0) { + GlobalLoot* loot = new GlobalLoot(); + int32 zoneID = result.GetInt32Str("value1"); + loot->minLevel = result.GetInt8Str("value2"); + loot->maxLevel = result.GetInt8Str("value3"); + loot->table_id = table_id; + loot->loot_tier = result.GetInt32Str("value4"); + + if (loot->minLevel > loot->maxLevel) + loot->maxLevel = loot->minLevel; + + zone->AddZoneLootList(zoneID, loot); + LogWrite(LOOT__DEBUG, 5, "Loot", "---Loading Zone %i loot table (id: %u)", zoneID, table_id); + LogWrite(LOOT__DEBUG, 5, "Loot", "---minlevel: %i, maxlevel: %i", loot->minLevel, loot->maxLevel); + } + count++; + } + + LogWrite(LOOT__DEBUG, 0, "Loot", "--Loaded %u Global loot list%s.", count, count == 1 ? "" : "s"); + } +} + +bool WorldDatabase::LoadSpawnLoot(ZoneServer* zone, Spawn* spawn) +{ + if (!spawn->GetDatabaseID()) + return false; + + DatabaseResult result; + int32 count = 0; + + zone->ClearSpawnLootList(spawn->GetDatabaseID()); + // Finally, load loot tables into spawns that are set to use these loot tables + if (database_new.Select(&result, "SELECT spawn_id, loottable_id FROM spawn_loot where spawn_id=%u",spawn->GetDatabaseID())) { + count = 0; + LogWrite(LOOT__DEBUG, 0, "Loot", "--Assigning loot table(s) to spawn(s)..."); + + while (result.Next()) { + int32 spawn_id = result.GetInt32Str("spawn_id"); + int32 table_id = result.GetInt32Str("loottable_id"); + zone->AddSpawnLootList(spawn_id, table_id); + LogWrite(LOOT__DEBUG, 5, "Loot", "---Adding loot table %u to spawn %u", table_id, spawn_id); + count++; + } + LogWrite(LOOT__DEBUG, 0, "Loot", "--Loaded %u spawn loot list%s.", count, count == 1 ? "" : "s"); + return true; + } + return false; +} + +void WorldDatabase::AddLootTableToSpawn(Spawn* spawn, int32 loottable_id) { + Query query; + query.RunQuery2(Q_INSERT, "insert into spawn_loot set spawn_id=%u,loottable_id=%u", spawn->GetDatabaseID(), loottable_id); +} + +bool WorldDatabase::RemoveSpawnLootTable(Spawn* spawn, int32 loottable_id) { + Query query; + if (loottable_id) + { + string delete_char = string("delete from spawn_loot where spawn_id=%i and loottable_id=%i"); + query.RunQuery2(Q_DELETE, delete_char.c_str(), spawn->GetDatabaseID(), loottable_id); + } + else + { + string delete_char = string("delete from spawn_loot where spawn_id=%i"); + query.RunQuery2(Q_DELETE, delete_char.c_str(), spawn->GetDatabaseID()); + } + if (!query.GetAffectedRows()) + { + //No error just in case ppl try doing stupid stuff + return false; + } + + return true; +} \ No newline at end of file diff --git a/source/WorldServer/Languages.cpp b/source/WorldServer/Languages.cpp new file mode 100644 index 0000000..f2ed895 --- /dev/null +++ b/source/WorldServer/Languages.cpp @@ -0,0 +1,153 @@ +/* + 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 . +*/ + +#include +#include +#include "Languages.h" + +Language::Language(){ + id = 0; + memset(name, 0, sizeof(name)); + save_needed = false; +} + +Language::Language(Language* language){ + id = language->id; + strncpy(name, language->GetName(), sizeof(name)); + save_needed = language->save_needed; +} + +MasterLanguagesList::MasterLanguagesList(){ +} + +MasterLanguagesList::~MasterLanguagesList(){ + Clear(); +} + + +// don't bother calling this beyond its deconstructor its not thread-safe +void MasterLanguagesList::Clear(){ + list::iterator itr; + Language* language = 0; + for(itr = languages_list.begin(); itr != languages_list.end(); itr++){ + language = *itr; + safe_delete(language); + } + languages_list.clear(); +} + +int32 MasterLanguagesList::Size(){ + return languages_list.size(); +} + +void MasterLanguagesList::AddLanguage(Language* language){ + assert(language); + languages_list.push_back(language); +} + +Language* MasterLanguagesList::GetLanguage(int32 id){ + list::iterator itr; + Language* language = 0; + Language* ret = 0; + for(itr = languages_list.begin(); itr != languages_list.end(); itr++){ + language = *itr; + if(language->GetID() == id){ + ret = language; + break; + } + } + return ret; +} + +Language* MasterLanguagesList::GetLanguageByName(const char* name){ + list::iterator itr; + Language* language = 0; + Language* ret = 0; + for(itr = languages_list.begin(); itr != languages_list.end(); itr++){ + language = *itr; + if(!language) + continue; + if(!strncmp(language->GetName(), name, strlen(name))){ + ret = language; + break; + } + } + return ret; +} + +list* MasterLanguagesList::GetAllLanguages(){ + return &languages_list; +} + +PlayerLanguagesList::PlayerLanguagesList(){ +} + +PlayerLanguagesList::~PlayerLanguagesList(){ +} + +void PlayerLanguagesList::Clear() { + list::iterator itr; + Language* language = 0; + Language* ret = 0; + for(itr = player_languages_list.begin(); itr != player_languages_list.end(); itr++){ + language = *itr; + safe_delete(language); + } + + player_languages_list.clear(); +} + +void PlayerLanguagesList::Add(Language* language){ + player_languages_list.push_back(language); +} + +Language* PlayerLanguagesList::GetLanguage(int32 id){ + list::iterator itr; + Language* language = 0; + Language* ret = 0; + for(itr = player_languages_list.begin(); itr != player_languages_list.end(); itr++){ + language = *itr; + if(language->GetID() == id){ + ret = language; + break; + } + } + return ret; +} + +Language* PlayerLanguagesList::GetLanguageByName(const char* name){ + list::iterator itr; + Language* language = 0; + Language* ret = 0; + for(itr = player_languages_list.begin(); itr != player_languages_list.end(); itr++){ + language = *itr; + if(!language) + continue; + if(!strncmp(language->GetName(), name, strlen(name))){ + ret = language; + break; + } + } + return ret; +} + +list* PlayerLanguagesList::GetAllLanguages(){ + return &player_languages_list; +} diff --git a/source/WorldServer/Languages.h b/source/WorldServer/Languages.h new file mode 100644 index 0000000..4d8a1e2 --- /dev/null +++ b/source/WorldServer/Languages.h @@ -0,0 +1,76 @@ +/* + 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 . +*/ + +#ifndef LANGUAGES_H_ +#define LANGUAGES_H_ + +#include +#include +#include "../common/types.h" + +using namespace std; + +class Language { +public: + Language(); + Language(Language* language); + void SetID(int32 id) {this->id = id;} + void SetName(const char *name) {strncpy(this->name, name, sizeof(this->name));} + void SetSaveNeeded(bool save_needed) {this->save_needed = save_needed;} + + int32 GetID() {return id;} + const char* GetName() {return name;} + bool GetSaveNeeded() {return save_needed;} + +private: + int32 id; + char name[50]; + bool save_needed; +}; + +class MasterLanguagesList { +public: + MasterLanguagesList(); + ~MasterLanguagesList(); + void Clear(); + int32 Size(); + void AddLanguage(Language* language); + Language* GetLanguage(int32 id); + Language* GetLanguageByName(const char* name); + list* GetAllLanguages(); + +private: + list languages_list; +}; + +class PlayerLanguagesList { +public: + PlayerLanguagesList(); + ~PlayerLanguagesList(); + void Clear(); + void Add(Language* language); + Language* GetLanguage(int32 id); + Language* GetLanguageByName(const char* name); + list* GetAllLanguages(); + +private: + list player_languages_list; +}; +#endif diff --git a/source/WorldServer/LoginServer.cpp b/source/WorldServer/LoginServer.cpp new file mode 100644 index 0000000..3f1426c --- /dev/null +++ b/source/WorldServer/LoginServer.cpp @@ -0,0 +1,667 @@ +/* + 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 . +*/ +#include "../common/debug.h" +#include "../common/Log.h" +#include +using namespace std; +#include +#include +#include +using namespace std; +#include +#include "../common/version.h" +#include "../common/GlobalHeaders.h" +#include "../common/sha512.h" + +#ifdef WIN32 +#include +#include +#include +#include + +#define strncasecmp _strnicmp +#define strcasecmp _stricmp +#else // Pyro: fix for linux +#include +#ifdef FREEBSD //Timothy Whitman - January 7, 2003 +#include +#endif +#include +#include +#include +#include +#include + +#include "../common/unix.h" + +#define SOCKET_ERROR -1 +#define INVALID_SOCKET -1 +extern int errno; +#endif + +#include "../common/servertalk.h" +#include "LoginServer.h" +#include "../common/packet_dump.h" +#include "net.h" +#include "zoneserver.h" +#include "WorldDatabase.h" +#include "Variables.h" +#include "World.h" +#include "../common/ConfigReader.h" +#include "Rules/Rules.h" + +extern sint32 numzones; +extern sint32 numclients; +extern NetConnection net; +extern LoginServer loginserver; +extern WorldDatabase database; +extern ZoneAuth zone_auth; +extern Variables variables; +extern ZoneList zone_list; +extern ClientList client_list; +extern volatile bool RunLoops; +volatile bool LoginLoopRunning = false; +extern ConfigReader configReader; +extern RuleManager rule_manager; + +bool AttemptingConnect = false; + +LoginServer::LoginServer(const char* iAddress, int16 iPort) { + LoginServerIP = ResolveIP(iAddress); + LoginServerPort = iPort; + statusupdate_timer = new Timer(LoginServer_StatusUpdateInterval); + tcpc = new TCPConnection(false); + pTryReconnect = true; + minLockedStatus = 100; + maxPlayers = -1; + minGameFullStatus = 100; + last_checked_time = 0; + zone_updates = 0; + loginEquip_updates = 0; +} + +LoginServer::~LoginServer() { + delete statusupdate_timer; + delete tcpc; +} + +void LoginServer::SendImmediateEquipmentUpdatesForChar(int32 char_id) { + LogWrite(WORLD__DEBUG, 5, "World", "Sending login equipment updates for char_id: %u", char_id); + + int16 count = 0; + if(!loginEquip_updates) + loginEquip_updates = database.GetEquipmentUpdates(char_id); + if(loginEquip_updates && loginEquip_updates->size() > 0) + { + map send_map; + int32 size = 0; + MutexMap::iterator itr = loginEquip_updates->begin(); + while(itr.Next()) + { + send_map[itr->first] = itr->second; + size += sizeof(EquipmentUpdate_Struct); + loginEquip_updates->erase(itr->first); + count++; + } + ServerPacket* outpack = new ServerPacket(ServerOP_LoginEquipment, size + sizeof(EquipmentUpdateList_Struct)+5); + EquipmentUpdateList_Struct* updates = (EquipmentUpdateList_Struct*)outpack->pBuffer; + updates->total_updates = count; + int32 pos = sizeof(EquipmentUpdateList_Struct); + map::iterator send_itr; + for(send_itr = send_map.begin(); send_itr != send_map.end(); send_itr++) + { + EquipmentUpdate_Struct* update = (EquipmentUpdate_Struct*)(outpack->pBuffer + pos); + update->id = send_itr->first; + update->world_char_id = send_itr->second.world_char_id; + update->equip_type = send_itr->second.equip_type; + update->red = send_itr->second.red; + update->green = send_itr->second.green; + update->blue = send_itr->second.blue; + update->highlight_red = send_itr->second.red; + update->highlight_green = send_itr->second.green; + update->highlight_blue = send_itr->second.blue; + update->slot = send_itr->second.slot; + pos += sizeof(EquipmentUpdate_Struct); + } + SendPacket(outpack); + outpack->Deflate(); + safe_delete(outpack); + } + if(loginEquip_updates && count) + loginEquip_updates->clear(); + if(loginEquip_updates && loginEquip_updates->size() == 0) + { + database.UpdateLoginEquipment(); + safe_delete(loginEquip_updates); + } + +} + +bool LoginServer::Process() { + if(last_checked_time > Timer::GetCurrentTime2()) + return true; + last_checked_time = Timer::GetCurrentTime2() + 50; + bool ret = true; + if (statusupdate_timer->Check()) { + this->SendStatus(); + } + + /************ Get all packets from packet manager out queue and process them ************/ + ServerPacket *pack = 0; + while((pack = tcpc->PopPacket())) + { + switch(pack->opcode) + { + case ServerOP_LSFatalError: + { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): ServerOP_LSFatalError", pack->opcode, pack->opcode); + + LogWrite(WORLD__ERROR, 0, "World", "Login Server returned a fatal error: %s\n", pack->pBuffer); + tcpc->Disconnect(); + ret = false; + net.ReadLoginINI(); + break; + } + case ServerOP_CharTimeStamp: + { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): ServerOP_CharTimeStamp", pack->opcode, pack->opcode); + + if(pack->size != sizeof(CharacterTimeStamp_Struct)) + break; + + CharacterTimeStamp_Struct* cts = (CharacterTimeStamp_Struct*) pack->pBuffer; + + // determine if the character exists and retrieve its latest timestamp from the world server + bool char_exist = false; + //int32 character_timestamp = database.GetCharacterTimeStamp(cts->char_id,cts->account_id,&char_exist); + + if(!char_exist) + { + //Character doesn't exist, get rid of it + SendDeleteCharacter ( cts ); + break; + } + + break; + } + + // Push Character Select "item appearances" to login_equipment table + case ServerOP_LoginEquipment:{ + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): ServerOP_LoginEquipment", pack->opcode, pack->opcode); + + LogWrite(MISC__TODO, 0, "TODO", "Implement map > method to update Login.\n%s, %s, %i", __FILE__, __FUNCTION__, __LINE__); + + if( pack->size == sizeof(EquipmentUpdateRequest_Struct) ) + { + int16 max = ((EquipmentUpdateRequest_Struct*)pack->pBuffer)->max_per_batch; + int16 count = 0; + if(!loginEquip_updates) + loginEquip_updates = database.GetEquipmentUpdates(); + if(loginEquip_updates && loginEquip_updates->size() > 0) + { + map send_map; + int32 size = 0; + MutexMap::iterator itr = loginEquip_updates->begin(); + while(itr.Next() && count < max) + { + send_map[itr->first] = itr->second; + size += sizeof(EquipmentUpdate_Struct); + loginEquip_updates->erase(itr->first); + count++; + } + ServerPacket* outpack = new ServerPacket(ServerOP_LoginEquipment, size + sizeof(EquipmentUpdateList_Struct)+5); + EquipmentUpdateList_Struct* updates = (EquipmentUpdateList_Struct*)outpack->pBuffer; + updates->total_updates = count; + int32 pos = sizeof(EquipmentUpdateList_Struct); + map::iterator send_itr; + for(send_itr = send_map.begin(); send_itr != send_map.end(); send_itr++) + { + EquipmentUpdate_Struct* update = (EquipmentUpdate_Struct*)(outpack->pBuffer + pos); + update->id = send_itr->first; + update->world_char_id = send_itr->second.world_char_id; + update->equip_type = send_itr->second.equip_type; + update->red = send_itr->second.red; + update->green = send_itr->second.green; + update->blue = send_itr->second.blue; + update->highlight_red = send_itr->second.red; + update->highlight_green = send_itr->second.green; + update->highlight_blue = send_itr->second.blue; + update->slot = send_itr->second.slot; + pos += sizeof(EquipmentUpdate_Struct); + } + SendPacket(outpack); + outpack->Deflate(); + safe_delete(outpack); + } + if(loginEquip_updates && count < max) + loginEquip_updates->clear(); + if(loginEquip_updates && loginEquip_updates->size() == 0) + { + database.UpdateLoginEquipment(); + safe_delete(loginEquip_updates); + } + } + break; + } + + case ServerOP_ZoneUpdates:{ + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): ServerOP_ZoneUpdates", pack->opcode, pack->opcode); + + if(pack->size == sizeof(ZoneUpdateRequest_Struct)){ + int16 max = ((ZoneUpdateRequest_Struct*)pack->pBuffer)->max_per_batch; + int16 count = 0; + if(!zone_updates) + zone_updates = database.GetZoneUpdates(); + if(zone_updates && zone_updates->size() > 0){ + map send_map; + int32 size = 0; + MutexMap::iterator itr = zone_updates->begin(); + while(itr.Next() && count < max){ + send_map[itr->first] = itr->second; + size += sizeof(ZoneUpdate_Struct) + itr->second.name.length() + itr->second.description.length(); + zone_updates->erase(itr->first); + count++; + } + ServerPacket* outpack = new ServerPacket(ServerOP_ZoneUpdates, size + sizeof(ZoneUpdateList_Struct)+5); + ZoneUpdateList_Struct* updates = (ZoneUpdateList_Struct*)outpack->pBuffer; + updates->total_updates = count; + int32 pos = sizeof(ZoneUpdateList_Struct); + map::iterator send_itr; + for(send_itr = send_map.begin(); send_itr != send_map.end(); send_itr++){ + ZoneUpdate_Struct* update = (ZoneUpdate_Struct*)(outpack->pBuffer + pos); + update->zone_id = send_itr->first; + update->zone_name_length = send_itr->second.name.length(); + update->zone_desc_length = send_itr->second.description.length(); + strcpy(update->data, send_itr->second.name.c_str()); + strcpy(update->data + send_itr->second.name.length(), send_itr->second.description.c_str()); + pos += sizeof(ZoneUpdate_Struct) + send_itr->second.name.length() + send_itr->second.description.length(); + } + SendPacket(outpack); + outpack->Deflate(); + safe_delete(outpack); + } + if(zone_updates && count < max) + zone_updates->clear(); + if(zone_updates && zone_updates->size() == 0){ + database.UpdateLoginZones(); + safe_delete(zone_updates); + } + } + break; + } + case ServerOP_CharacterCreate:{ + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): ServerOP_CharacterCreate", pack->opcode, pack->opcode); + + int16 version = 1; + if(pack->pBuffer[0] > 0) + memcpy(&version, pack->pBuffer, sizeof(int16)); + //DumpPacket(pack->pBuffer,pack->size); + PacketStruct* packet = configReader.getStruct("CreateCharacter", version); + int8 resp = 0; + int32 acct_id = 0; + int32 char_id = 0; + if(packet && packet->LoadPacketData(pack->pBuffer+sizeof(int16),pack->size - sizeof(int16), version <= 561 ? false : true)){ + EQ2_16BitString name = packet->getType_EQ2_16BitString_ByName("name"); + resp = database.CheckNameFilter(name.data.c_str()); + acct_id = packet->getType_int32_ByName("account_id"); + LogWrite(WORLD__DEBUG, 0, "World", "Response: %i", (int)resp); + + sint16 lowestStatus = database.GetLowestCharacterAdminStatus(acct_id); + if(lowestStatus == -2) + resp = UNKNOWNERROR_REPLY2; + else if(resp == CREATESUCCESS_REPLY) + char_id = database.SaveCharacter(packet, acct_id); + } + else{ + LogWrite(WORLD__ERROR, 0, "World", "Invalid creation request!"); + resp = UNKNOWNERROR_REPLY; + } + // send name filter response data back to the login server + SendFilterNameResponse ( resp , acct_id , char_id ); + safe_delete(packet); + break; + } + case ServerOP_BasicCharUpdate: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): ServerOP_BasicCharUpdate", pack->opcode, pack->opcode); + + if(pack->size != sizeof(CharDataUpdate_Struct)) + break; + + CharDataUpdate_Struct* cdu = (CharDataUpdate_Struct*) pack->pBuffer; + + switch(cdu->update_field) + { + case DELETE_UPDATE_FLAG: + { + LogWrite(WORLD__DEBUG, 0, "World", "Delete character request: %i %i",cdu->account_id,cdu->char_id ); + database.DeleteCharacter(cdu->account_id,cdu->char_id); + break; + } + } + break; + } + case ServerOP_UsertoWorldReq:{ + LogWrite(OPCODE__DEBUG, 0, "Opcode", "Opcode 0x%X (%i): ServerOP_UsertoWorldReq", pack->opcode, pack->opcode); + + UsertoWorldRequest_Struct* utwr = (UsertoWorldRequest_Struct*) pack->pBuffer; + /*int32 id = database.GetAccountIDFromLSID(utwr->lsaccountid); + sint16 status = database.CheckStatus(id); + */ + + int32 access_key = 0; + + // if it is a accepted login, we add the zone auth request + access_key = DetermineCharacterLoginRequest ( utwr ); + + if ( access_key != 0 ) + { + zone_auth.PurgeInactiveAuth(); + char* characterName = database.GetCharacterName( utwr->char_id ); + if(characterName != 0){ + ZoneAuthRequest* zar = new ZoneAuthRequest(utwr->lsaccountid,characterName,access_key); + zar->setFirstLogin ( true ); + zone_auth.AddAuth(zar); + safe_delete_array(characterName); + } + } + break; + } + case ServerOP_ResetDatabase:{ + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): ServerOP_ResetDatabase", pack->opcode, pack->opcode); + + database.ResetDatabase(); + break; + } + + default: + { + LogWrite(WORLD__ERROR, 0, "World", "Unhandled opcode: %i", pack->opcode); + DumpPacket(pack); + } + } + safe_delete(pack); + + // break out if ret is now false + if (!ret) + break; + } + return ret; +} + +// this should always be called in a new thread +#ifdef WIN32 +void AutoInitLoginServer(void *tmp) { +#else +void *AutoInitLoginServer(void *tmp) { +#endif + if (loginserver.GetState() == TCPS_Ready) { + InitLoginServer(); + } +#ifndef WIN32 + return 0; +#endif +} + +bool InitLoginServer() { + if (loginserver.GetState() != TCPS_Ready) { + LogWrite(WORLD__ERROR, 0, "World", "InitLoginServer() while already attempting connect."); + return false; + } + if (!net.LoginServerInfo) { + LogWrite(WORLD__ERROR, 0, "World", "Login server info not loaded."); + return false; + } + + AttemptingConnect = true; + int16 port; + char* address = net.GetLoginInfo(&port); + LogWrite(WORLD__INFO, 0, "World", "InitLoginServer() attempt connect to %s on port %u.", address, port); + loginserver.Connect(address, port); + return true; +} + +void LoginServer::InitLoginServerVariables() +{ + minLockedStatus = rule_manager.GetGlobalRule(R_World, ServerLockedOverrideStatus)->GetSInt16(); + maxPlayers = rule_manager.GetGlobalRule(R_World, MaxPlayers)->GetSInt16(); + minGameFullStatus = rule_manager.GetGlobalRule(R_World, MaxPlayersOverrideStatus)->GetSInt16(); +} + + + +bool LoginServer::Connect(const char* iAddress, int16 iPort) { + if(!pTryReconnect) + return false; + + char errbuf[TCPConnection_ErrorBufferSize]; + memset(errbuf, 0, TCPConnection_ErrorBufferSize); + if (iAddress == 0) { + LogWrite(WORLD__ERROR, 0, "World", "LoginServer::Connect: address == 0"); + return false; + } + else { + if ((LoginServerIP = ResolveIP(iAddress, errbuf)) == 0) { + LogWrite(WORLD__ERROR, 0, "World", "LoginServer::Connect: Resolving IP address: '%s'", errbuf); + return false; + } + } + if (iPort != 0) + LoginServerPort = iPort; + + if (LoginServerIP == 0 || LoginServerPort == 0) { + LogWrite(WORLD__ERROR, 0, "World", "LoginServer::Connect: Connect info incomplete, cannot connect"); + return false; + } + + if (tcpc->Connect(LoginServerIP, LoginServerPort, errbuf)) { + LogWrite(WORLD__INFO, 0, "World", "Connected to LoginServer: %s: %i", iAddress, LoginServerPort); + SendInfo(); + SendStatus(); + return true; + } + else { + LogWrite(WORLD__ERROR, 0, "World", "LoginServer::Connect: '%s'", errbuf); + return false; + } +} +void LoginServer::GetLatestTables(){ + ServerPacket* pack = new ServerPacket(ServerOP_GetLatestTables, sizeof(GetLatestTables_Struct)); + GetLatestTables_Struct* data = (GetLatestTables_Struct*)pack->pBuffer; + data->table_version = CURRENT_DATABASE_MAJORVERSION*100 + CURRENT_DATABASE_MINORVERSION; + data->data_version = CURRENT_DATABASE_MAJORVERSION*100 + CURRENT_DATABASE_MINORVERSION; + SendPacket(pack); + delete pack; +} +void LoginServer::SendInfo() { + ServerPacket* pack = new ServerPacket; + pack->opcode = ServerOP_LSInfo; + pack->size = sizeof(ServerLSInfo_Struct); + pack->pBuffer = new uchar[pack->size]; + memset(pack->pBuffer, 0, pack->size); + ServerLSInfo_Struct* lsi = (ServerLSInfo_Struct*) pack->pBuffer; + strcpy(lsi->protocolversion, EQEMU_PROTOCOL_VERSION); + strcpy(lsi->serverversion, CURRENT_VERSION); + strcpy(lsi->name, net.GetWorldName()); + strcpy(lsi->account, net.GetWorldAccount()); + lsi->dbversion = CURRENT_DATABASE_MAJORVERSION*100 + CURRENT_DATABASE_MINORVERSION; +#ifdef _DEBUG + lsi->servertype = 4; +#endif + string passwdSha512 = sha512(net.GetWorldPassword()); + memcpy(lsi->password, (char*)passwdSha512.c_str(), passwdSha512.length()); + strcpy(lsi->address, net.GetWorldAddress()); + SendPacket(pack); + delete pack; +} + +void LoginServer::SendStatus() { + statusupdate_timer->Start(); + ServerPacket* pack = new ServerPacket; + pack->opcode = ServerOP_LSStatus; + pack->size = sizeof(ServerLSStatus_Struct); + pack->pBuffer = new uchar[pack->size]; + memset(pack->pBuffer, 0, pack->size); + ServerLSStatus_Struct* lss = (ServerLSStatus_Struct*) pack->pBuffer; + + if (net.world_locked) + lss->status = -2; + else if(loginserver.maxPlayers > -1 && numclients >= loginserver.maxPlayers) + lss->status = -3; + else + lss->status = 1; + + lss->num_zones = numzones; + lss->num_players = numclients; + lss->world_max_level = rule_manager.GetGlobalRule(R_Player, MaxLevel)->GetInt8(); + SendPacket(pack); + delete pack; +} + +void LoginServer::SendDeleteCharacter ( CharacterTimeStamp_Struct* cts ) { + ServerPacket* outpack = new ServerPacket(ServerOP_BasicCharUpdate, sizeof(CharDataUpdate_Struct)); + CharDataUpdate_Struct* cdu = (CharDataUpdate_Struct*)outpack->pBuffer; + cdu->account_id = cts->account_id; + cdu->char_id = cts->char_id; + cdu->update_field = DELETE_UPDATE_FLAG; + cdu->update_data = 1; + SendPacket(outpack); + safe_delete(outpack); +} + +void LoginServer::SendFilterNameResponse ( int8 resp, int32 acct_id , int32 char_id ) { + ServerPacket* outpack = new ServerPacket(ServerOP_CharacterCreate, sizeof(WorldCharNameFilterResponse_Struct)); + WorldCharNameFilterResponse_Struct* wcfr = (WorldCharNameFilterResponse_Struct*)outpack->pBuffer; + wcfr->response = resp; + wcfr->account_id = acct_id; + wcfr->char_id = char_id; + SendPacket(outpack); + safe_delete(outpack); +} + +int32 LoginServer::DetermineCharacterLoginRequest ( UsertoWorldRequest_Struct* utwr ) { + LogWrite(LOGIN__TRACE, 9, "Login", "Enter: %s", __FUNCTION__); + ServerPacket* outpack = new ServerPacket; + outpack->opcode = ServerOP_UsertoWorldResp; + outpack->size = sizeof(UsertoWorldResponse_Struct); + outpack->pBuffer = new uchar[outpack->size]; + memset(outpack->pBuffer, 0, outpack->size); + UsertoWorldResponse_Struct* utwrs = (UsertoWorldResponse_Struct*) outpack->pBuffer; + utwrs->lsaccountid = utwr->lsaccountid; + utwrs->char_id = utwr->char_id; + utwrs->ToID = utwr->FromID; + int32 timestamp = Timer::GetUnixTimeStamp(); + utwrs->access_key = timestamp; + + // set default response to 0 + utwrs->response = 0; + + sint16 lowestStatus = database.GetLowestCharacterAdminStatus( utwr->lsaccountid ); + + sint16 status = 0; + + if(lowestStatus == -2) + status = -1; + else + status = database.GetCharacterAdminStatus ( utwr->lsaccountid , utwr->char_id ); + + if(status < 100 && zone_list.ClientConnected(utwr->lsaccountid)) + status = -9; + if(status < 0){ + LogWrite(WORLD__ERROR, 0, "World", "Login Rejected based on PLAY_ERROR (UserStatus) (MinStatus: %i), UserStatus: %i, CharID: %i",loginserver.minLockedStatus,status,utwr->char_id ); + switch(status){ + case -10: + utwrs->response = PLAY_ERROR_CHAR_NOT_LOADED; + break; + case -9: + utwrs->response = 0;//PLAY_ERROR_ACCOUNT_IN_USE; + break; + case -8: + utwrs->response = PLAY_ERROR_LOADING_ERROR; + break; + case -1: + utwrs->response = PLAY_ERROR_ACCOUNT_BANNED; + break; + default: + utwrs->response = PLAY_ERROR_PROBLEM; + } + } + else if(net.world_locked == true){ + LogWrite(WORLD__INFO, 0, "World", "Login Lock Check (MinStatus: %i):, UserStatus: %i, CharID: %i",loginserver.minLockedStatus,status,utwr->char_id ); + + // has high enough status, allow it + if(status >= loginserver.minLockedStatus) + utwrs->response = 1; + } + else if(loginserver.maxPlayers > -1 && ((sint16)client_list.Count()) >= loginserver.maxPlayers) + { + LogWrite(WORLD__INFO, 0, "World", "Login GameFull Check (MinStatus: %i):, UserStatus: %i, CharID: %i",loginserver.minGameFullStatus,status,utwr->char_id ); + + // has high enough status, allow it + if(status >= loginserver.minGameFullStatus) + { + utwrs->response = 1; + } + else + utwrs->response = -3; // server full response is -3 + } + else + utwrs->response = 1; + + /*sint32 x = database.CommandRequirement("$MAXCLIENTS"); + if( (sint32)numplayers >= x && x != -1 && x != 255 && status < 80) + utwrs->response = -3; + + if(status == -1) + utwrs->response = -1; + if(status == -2) + utwrs->response = -2; + */ + //printf("Response is %i for %i\n",utwrs->response,id);struct sockaddr_in sa; + int32 ipv4addr = 0; + int result = 0; + #ifdef WIN32 + struct sockaddr_in myaddr; + ZeroMemory(&myaddr, sizeof(myaddr)); + result = InetPton(AF_INET, utwr->ip_address, &(myaddr.sin_addr)); + if(result) + ipv4addr = ntohl(myaddr.sin_addr.s_addr); + + #else + result = inet_pton(AF_INET, utwr->ip_address, &ipv4addr); + if(result) + ipv4addr = ntohl(ipv4addr); + #endif + if (((result > 0 && IsPrivateAddress(ipv4addr)) || (strcmp(net.GetWorldAddress(), utwr->ip_address) == 0)) && (strlen(net.GetInternalWorldAddress()) > 0)) + strcpy(utwrs->ip_address, net.GetInternalWorldAddress()); + else + strcpy(utwrs->ip_address, net.GetWorldAddress()); + + LogWrite(CCLIENT__INFO, 0, "World", "New client login attempt from %s, providing %s as the world server address.",utwr->ip_address, utwrs->ip_address ); + + utwrs->port = net.GetWorldPort(); + utwrs->worldid = utwr->worldid; + SendPacket(outpack); + delete outpack; + + LogWrite(LOGIN__TRACE, 9, "Login", "Exit: %s with timestamp=%u", __FUNCTION__, timestamp); + // depending on the response determined above, this could return 0 (for failure) + return timestamp; +} + diff --git a/source/WorldServer/LoginServer.h b/source/WorldServer/LoginServer.h new file mode 100644 index 0000000..d0b4839 --- /dev/null +++ b/source/WorldServer/LoginServer.h @@ -0,0 +1,88 @@ +/* + 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 . +*/ +#ifndef LOGINSERVER_H +#define LOGINSERVER_H + +#include "../common/servertalk.h" +#include "../common/linked_list.h" +#include "../common/timer.h" +#include "../common/queue.h" +#include "../common/Mutex.h" +#include "../common/TCPConnection.h" +#include +#include "MutexMap.h" + +#ifdef WIN32 + void AutoInitLoginServer(void *tmp); +#else + void *AutoInitLoginServer(void *tmp); +#endif +bool InitLoginServer(); + +class LoginServer{ +public: + LoginServer(const char* iAddress = 0, int16 iPort = 5999); + ~LoginServer(); + + bool Process(); + bool Connect(const char* iAddress = 0, int16 iPort = 0); + + bool ConnectToUpdateServer(const char* iAddress = 0, int16 iPort = 0); + + void SendInfo(); + void SendStatus(); + void GetLatestTables(); + + void SendPacket(ServerPacket* pack) { tcpc->SendPacket(pack); } + int8 GetState() { return tcpc->GetState(); } + bool Connected() { return tcpc->Connected(); } + + void SendFilterNameResponse ( int8 resp , int32 acct_id , int32 char_id ); + + void SendDeleteCharacter ( CharacterTimeStamp_Struct* cts ); + + int32 DetermineCharacterLoginRequest ( UsertoWorldRequest_Struct* utwr ); + + void InitLoginServerVariables(); + + sint16 minLockedStatus; + sint16 maxPlayers; + sint16 minGameFullStatus; + + void SendImmediateEquipmentUpdatesForChar(int32 char_id); + + bool CanReconnect() { return pTryReconnect; } + +private: + bool try_auto_update; + bool pTryReconnect; + TCPConnection* tcpc; + int32 LoginServerIP; + int32 UpdateServerIP; + int16 LoginServerPort; + + uchar* data_waiting; + MutexMap* zone_updates; + MutexMap* loginEquip_updates; + int32 last_checked_time; + + Timer* statusupdate_timer; +}; +#endif diff --git a/source/WorldServer/LuaFunctions.cpp b/source/WorldServer/LuaFunctions.cpp new file mode 100644 index 0000000..acbb07b --- /dev/null +++ b/source/WorldServer/LuaFunctions.cpp @@ -0,0 +1,14167 @@ +/* + 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 . +*/ +#include "LuaFunctions.h" +#include "Spawn.h" +#include "WorldDatabase.h" +#include "LuaInterface.h" +#include "../common/ConfigReader.h" +#include "client.h" +#include "World.h" +#include "Commands/Commands.h" +#include "races.h" +#include "classes.h" +#include "Variables.h" +#include "SpellProcess.h" +#include "Rules/Rules.h" +#include "../common/Log.h" +#include +#include "HeroicOp/HeroicOp.h" +#include "RaceTypes/RaceTypes.h" +#include "ClientPacketFunctions.h" +#include "Transmute.h" +#include "Titles.h" +#include +#include +#include + +extern MasterFactionList master_faction_list; +extern WorldDatabase database; +extern LuaInterface* lua_interface; +extern ConfigReader configReader; +extern MasterQuestList master_quest_list; +extern MasterItemList master_item_list; +extern MasterSpellList master_spell_list; +extern World world; +extern Commands commands; +extern ZoneList zone_list; +extern Races races; +extern Classes classes; +extern Variables variables; +extern MasterSkillList master_skill_list; +extern MasterHeroicOPList master_ho_list; +extern MasterRaceTypeList race_types_list; +extern MasterLanguagesList master_languages_list; +extern MasterTitlesList master_titles_list; +extern RuleManager rule_manager; + +vector ParseString(string strVal, char delim) { + stringstream ss(strVal); + vector ret; + while (ss.good()) + { + string substr; + getline(ss, substr, delim); + ret.push_back(substr); + } + return ret; +} + +vector ParseStringToInt32(string strVal, char delim) { + stringstream ss(strVal); + vector ret; + while (ss.good()) + { + string substr; + getline(ss, substr, delim); + stringstream valss(substr); + unsigned int val = 0; + valss >> val; + ret.push_back(val); + } + return ret; +} + +map ParseStringMap(string strVal, char delim) { + vector pairs = ParseString(strVal, delim); + vector::iterator itr; + map ret; + for (itr = pairs.begin(); itr != pairs.end(); itr++) { + vector keyvaluepair = ParseString(*itr, ':'); + if (keyvaluepair.size() == 2) { + stringstream valss(keyvaluepair[1]); + int32 val = 0; + valss >> val; + ret[keyvaluepair[0]] = val; + } + } + + return ret; +} + +map ParseIntMap(string strVal, char delim) { + vector pairs = ParseString(strVal, delim); + vector::iterator itr; + map ret; + for (itr = pairs.begin(); itr != pairs.end(); itr++) { + vector keyvaluepair = ParseString(*itr, ':'); + int32 key = 0; + if (keyvaluepair.size() > 0) { + stringstream keyss(keyvaluepair[0]); + keyss >> key; + } + if (keyvaluepair.size() == 1) { + ret[key] = 1; + } + else if (keyvaluepair.size() == 2) { + stringstream valss(keyvaluepair[1]); + unsigned short val = 0; + valss >> val; + ret[key] = val; + } + } + return ret; +} + +map ParseSInt32Map(string strVal, char delim) { + vector pairs = ParseString(strVal, delim); + vector::iterator itr; + map ret; + for (itr = pairs.begin(); itr != pairs.end(); itr++) { + vector keyvaluepair = ParseString(*itr, ':'); + int32 key = 0; + if (keyvaluepair.size() > 0) { + stringstream keyss(keyvaluepair[0]); + keyss >> key; + } + if (keyvaluepair.size() == 1) { + ret[key] = 1; + } + else if (keyvaluepair.size() == 2) { + stringstream valss(keyvaluepair[1]); + signed int val = 0; + valss >> val; + ret[key] = val; + } + } + return ret; +} + +int EQ2Emu_lua_PlayFlavor(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + string mp3_string = lua_interface->GetStringValue(state, 2); + string text_string = lua_interface->GetStringValue(state, 3); + string emote_string = lua_interface->GetStringValue(state, 4); + int32 key1 = lua_interface->GetInt32Value(state, 5); + int32 key2 = lua_interface->GetInt32Value(state, 6); + Spawn* player = lua_interface->GetSpawn(state, 7); + int8 language = lua_interface->GetInt8Value(state, 8); + lua_interface->ResetFunctionStack(state); + if (spawn) { + const char* mp3 = 0; + const char* text = 0; + const char* emote = 0; + if (mp3_string.length() > 0) + mp3 = mp3_string.c_str(); + if (text_string.length() > 0) + text = text_string.c_str(); + if (emote_string.length() > 0) + emote = emote_string.c_str(); + Client* client = 0; + if (player && player->IsPlayer()) + client = ((Player*)player)->GetClient(); + if (client) { + if (((Player*)player)->WasSentSpawn(spawn->GetID())) + spawn->GetZone()->PlayFlavor(client, spawn, mp3, text, emote, key1, key2, language); + } + else + spawn->GetZone()->PlayFlavor(spawn, mp3, text, emote, key1, key2, language); + } + return 0; +} + +int EQ2Emu_lua_PlayFlavorID(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + + int8 type = lua_interface->GetInt8Value(state, 2); + int32 id = lua_interface->GetInt32Value(state, 3); + int16 index = lua_interface->GetInt16Value(state, 4); + Spawn* player = lua_interface->GetSpawn(state, 5); + int8 language = lua_interface->GetInt8Value(state, 6); + lua_interface->ResetFunctionStack(state); + if (spawn) { + Client* client = 0; + if (player && player->IsPlayer()) + client = ((Player*)player)->GetClient(); + if (client) { + VoiceOverStruct non_garble, garble; + bool garble_success = false; + bool success = world.FindVoiceOver(type, id, index, &non_garble, &garble_success, &garble); + client->SendPlayFlavor(spawn, language, &non_garble, &garble, success, garble_success); + } + else + spawn->GetZone()->PlayFlavorID(spawn, type, id, index, language); + } + return 0; +} + +int EQ2Emu_lua_PlaySound(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + string sound_string = lua_interface->GetStringValue(state, 2); + float x = lua_interface->GetFloatValue(state, 3); + float y = lua_interface->GetFloatValue(state, 4); + float z = lua_interface->GetFloatValue(state, 5); + Spawn* player = lua_interface->GetSpawn(state, 6); + lua_interface->ResetFunctionStack(state); + if (spawn && sound_string.length() > 0) { + Client* client = 0; + if (player && player->IsPlayer()) + client = ((Player*)player)->GetClient(); + if (client) + spawn->GetZone()->PlaySoundFile(client, sound_string.c_str(), x, y, z); + else + spawn->GetZone()->PlaySoundFile(0, sound_string.c_str(), x, y, z); + + } + return 0; +} +int EQ2Emu_lua_SetRequiredQuest(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 quest_id = lua_interface->GetInt32Value(state, 2); + int16 quest_step = lua_interface->GetInt16Value(state, 3); + bool private_spawn = (lua_interface->GetInt8Value(state, 4) == 1); + bool continued_access = (lua_interface->GetInt8Value(state, 5) == 1); + int16 flag_override = lua_interface->GetInt16Value(state, 6); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA SetRequiredQuest command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (quest_id > 0) { + //Add this quest to the list of required quests for this spawn + spawn->SetQuestsRequired(quest_id, quest_step); + //If private spawn value set + if (private_spawn) { + //Set the spawn to be private when not granted access through this quest + spawn->AddAllowAccessSpawn(spawn); + spawn->SetPrivateQuestSpawn(true); + } + //This value allows access after a quest step, or the whole quest has been completed + if (continued_access) + spawn->SetQuestsRequiredContinuedAccess(true); + //This value will override vis_flags in the vis packet + if (flag_override > 0) + spawn->SetQuestsRequiredOverride(flag_override); + } + return 0; +} + +int EQ2Emu_lua_SpawnSetByDistance(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + float max_distance = lua_interface->GetFloatValue(state, 2); + string variable = lua_interface->GetStringValue(state, 3); + string value = lua_interface->GetStringValue(state, 4); + lua_interface->ResetFunctionStack(state); + if (max_distance > 0 && spawn && value.length() > 0 && variable.length() > 0 && spawn->GetZone()) + spawn->GetZone()->SpawnSetByDistance(spawn, max_distance, variable, value); + return 0; +} +int EQ2Emu_lua_PerformCameraShake(lua_State* state) { + + if (!lua_interface) + return 0; + Client* client = 0; + Spawn* player = lua_interface->GetSpawn(state); + float intensity = lua_interface->GetFloatValue(state, 2); + int8 direction = lua_interface->GetInt8Value(state, 3); + lua_interface->ResetFunctionStack(state); + if (!player) { + lua_interface->LogError("LUA PerformCameraShake command error: spawn is not valid"); + return 0; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("LUA PerformCameraShake command error: spawn is not a player"); + return 0; + } + + if (player->GetZone()) + client = ((Player*)player)->GetClient(); + + if (!client) { + lua_interface->LogError("LUA PerformCameraShake command error: could not find client"); + return 0; + } + PacketStruct* packet = configReader.getStruct("WS_PerformCameraShakeMsg", client->GetVersion()); + if (packet) { + /* Client Intensity Logic (does not restrict service side, but expect .01 - 1.0 range) + v1 = *(float *)(a1 + 4); + if ( v1 > 0.0 ) + v2 = fminf(v1, 1.0); + else + v2 = 0.1; + */ + packet->setDataByName("intensity", intensity); + if ( client->GetVersion() > 561 ) + packet->setDataByName("direction", direction); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + return 0; +} + +int EQ2Emu_lua_KillSpawn(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* dead = lua_interface->GetSpawn(state); + Spawn* killer = lua_interface->GetSpawn(state, 2); + bool send_packet = (lua_interface->GetInt8Value(state, 3) == 1); + lua_interface->ResetFunctionStack(state); + if (dead && dead->Alive() && dead->GetZone()) + dead->GetZone()->KillSpawn(false, dead, killer, send_packet); + return 0; +} + +int EQ2Emu_lua_KillSpawnByDistance(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + float max_distance = lua_interface->GetFloatValue(state, 2); + bool include_players = lua_interface->GetInt8Value(state, 3); + bool send_packet = (lua_interface->GetInt8Value(state, 4) == 1); + lua_interface->ResetFunctionStack(state); + if (max_distance > 0 && spawn && spawn->GetZone()) + spawn->GetZone()->KillSpawnByDistance(spawn, max_distance, include_players, send_packet); + return 0; +} + +int EQ2Emu_lua_Despawn(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 delay = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn && spawn->GetZone()) + spawn->GetZone()->Despawn(spawn, delay); + return 0; +} + +int EQ2Emu_lua_ChangeHandIcon(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int8 displayHandIcon = lua_interface->GetInt8Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn) { + spawn->info_changed = true; + spawn->SetShowHandIcon(displayHandIcon); + } + return 0; +} + +//this function is used to force an update packet to be sent. +//Useful if certain calculated things change after the player is sent the spawn packet, like quest flags or player has access to an object now +int EQ2Emu_lua_SetVisualFlag(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + spawn->vis_changed = true; + spawn->GetZone()->AddChangedSpawn(spawn); + } + return 0; +} + +//this function is used to force an update packet to be sent. +//Useful if certain calculated things change after the player is sent the spawn packet, like quest flags or player has access to an object now +int EQ2Emu_lua_SetInfoFlag(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + spawn->info_changed = true; + spawn->GetZone()->AddChangedSpawn(spawn); + } + return 0; +} + +int EQ2Emu_lua_SendStateCommand(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 new_state = lua_interface->GetInt32Value(state, 2); + Spawn* player = lua_interface->GetSpawn(state, 3); + + lua_interface->ResetFunctionStack(state); + + if (spawn) { + if(player) + { + if(player->IsPlayer()) + { + Client* client = ((Player*)player)->GetClient(); + if(client) + { + ClientPacketFunctions::SendStateCommand(client, client->GetPlayer()->GetIDWithPlayerSpawn(spawn), new_state); + lua_interface->SetBooleanValue(state, true); + return 1; + } + else + LogWrite(LUA__ERROR, 0, "LUA", "Spawn %s Error in SendStateCommand,attempted to pass player value in argument 3, but argument does not have active client.", spawn->GetName()); + } + else + LogWrite(LUA__ERROR, 0, "LUA", "Spawn %s Error in SendStateCommand,attempted to pass player value in argument 3, but argument is NOT a player.", spawn->GetName()); + } + else + { + spawn->GetZone()->QueueStateCommandToClients(spawn->GetID(), new_state); + lua_interface->SetBooleanValue(state, true); + return 1; + } + } + + lua_interface->SetBooleanValue(state, false); + return 1; +} + +int EQ2Emu_lua_SpawnSet(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + string variable = lua_interface->GetStringValue(state, 2); + string value = lua_interface->GetStringValue(state, 3); + bool no_update = lua_interface->GetBooleanValue(state, 4); // send update is true by default in SetSpawnCommand, so allow user to specify 'true' to disable send update. + bool temporary_flag = true; + + int8 num_args = (int8)lua_interface->GetNumberOfArgs(state); + int8 index = 0; + + if(num_args >= 5) + { + temporary_flag = lua_interface->GetBooleanValue(state, 5); // this used to be false, but no one bothered to set it temporary, we don't need to update the DB + + index = lua_interface->GetInt8Value(state, 6); + } + + lua_interface->ResetFunctionStack(state); + + int32 type = commands.GetSpawnSetType(variable); + if (type != 0xFFFFFFFF && value.length() > 0 && spawn) + commands.SetSpawnCommand(0, spawn, type, value.c_str(), !no_update, temporary_flag, nullptr, index); + return 0; +} + +int EQ2Emu_lua_GetSpawn(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 spawn_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn && spawn_id > 0) { + Spawn* closest_spawn = spawn->GetZone()->GetClosestSpawn(spawn, spawn_id); + if (closest_spawn) { + lua_interface->SetSpawnValue(state, closest_spawn); + return 1; + } + } + return 0; +} + +int EQ2Emu_lua_GetSpawnFromList(lua_State* state) { + if (!lua_interface) + return 0; + + vector spawns; + int32 position = 0; + + if(lua_istable(state, 1)) { + size_t len = lua_rawlen(state, 1); + for(int i=1;i <= len; i++) + { + // get the entry to stack + lua_rawgeti(state, 1, i); + int Top = lua_gettop(state); + + if(lua_islightuserdata(state,Top)) { + LUAUserData* data = (LUAUserData*)lua_touserdata(state, Top); + if(data->IsSpawn()) { + spawns.push_back(data->spawn); + } + } + // remove entry from stack + lua_pop(state,1); + } + } + + position = lua_interface->GetInt32Value(state, 2); + + lua_interface->ResetFunctionStack(state); + + Spawn* spawn = 0; + if(position < spawns.size()) { + spawn = spawns.at(position); + } + + if(spawn) { + lua_interface->SetSpawnValue(state, spawn); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetSpawnListSize(lua_State* state) { + if (!lua_interface) + return 0; + + vector spawns; + + if(lua_istable(state, 1)) { + size_t len = lua_rawlen(state, 1); + for(int i=1;i <= len; i++) + { + // get the entry to stack + lua_rawgeti(state, 1, i); + int Top = lua_gettop(state); + + if(lua_islightuserdata(state,Top)) { + LUAUserData* data = (LUAUserData*)lua_touserdata(state, Top); + if(data->IsSpawn()) { + spawns.push_back(data->spawn); + } + } + // remove entry from stack + lua_pop(state,1); + } + } + + lua_interface->ResetFunctionStack(state); + + lua_interface->SetInt32Value(state, spawns.size()); + return 1; +} + +int EQ2Emu_lua_GetSpawnListBySpawnID(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 spawn_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn) { + vector spawns = spawn->GetZone()->GetSpawnsByID(spawn_id); + if (spawns.size() > 0) { + lua_createtable(state, spawns.size(), 0); + int newTable = lua_gettop(state); + for (int32 i = 0; i < spawns.size(); i++) { + lua_interface->SetSpawnValue(state, spawns.at(i)); + lua_rawseti(state, newTable, i + 1); + } + return 1; + } + } + return 0; +} + +int EQ2Emu_lua_GetSpawnListByRailID(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + sint64 rail_id = lua_interface->GetSInt64Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn) { + vector spawns = spawn->GetZone()->GetSpawnsByRailID(rail_id); + if (spawns.size() > 0) { + lua_createtable(state, spawns.size(), 0); + int newTable = lua_gettop(state); + for (int32 i = 0; i < spawns.size(); i++) { + lua_interface->SetSpawnValue(state, spawns.at(i)); + lua_rawseti(state, newTable, i + 1); + } + return 1; + } + } + return 0; +} + +int EQ2Emu_lua_GetPassengerSpawnList(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + vector spawns = spawn->GetPassengersOnRail(); + if (spawns.size() > 0) { + lua_createtable(state, spawns.size(), 0); + int newTable = lua_gettop(state); + for (int32 i = 0; i < spawns.size(); i++) { + lua_interface->SetSpawnValue(state, spawns.at(i)); + lua_rawseti(state, newTable, i + 1); + } + return 1; + } + } + return 0; +} + +int EQ2Emu_lua_GetVariableValue(lua_State* state) { + if (!lua_interface) + return 0; + string variable_name = lua_interface->GetStringValue(state); + lua_interface->ResetFunctionStack(state); + Variable* var = variables.FindVariable(variable_name); + if (var) { + lua_interface->SetStringValue(state, var->GetValue()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetCoinMessage(lua_State* state) { + if (!lua_interface) + return 0; + int32 total_coins = lua_interface->GetInt32Value(state); + lua_interface->ResetFunctionStack(state); + if (total_coins == 0) { + lua_interface->SetStringValue(state, "0 copper"); + return 1; + } + char tmp[64] = { 0 }; + string message = ""; + int32 val = 0; + if (total_coins >= 1000000) { + val = total_coins / 1000000; + total_coins -= 1000000 * val; + sprintf(tmp, " %u Platinum", val); + message.append(tmp); + memset(tmp, 0, 64); + } + if (total_coins >= 10000) { + val = total_coins / 10000; + total_coins -= 10000 * val; + sprintf(tmp, " %u Gold", val); + message.append(tmp); + memset(tmp, 0, 64); + } + if (total_coins >= 100) { + val = total_coins / 100; + total_coins -= 100 * val; + sprintf(tmp, " %u Silver", val); + message.append(tmp); + memset(tmp, 0, 64); + } + if (total_coins > 0) { + sprintf(tmp, " %u Copper", (int32)total_coins); + message.append(tmp); + } + lua_interface->SetStringValue(state, message.c_str()); + return 1; +} + +int EQ2Emu_lua_GetSpawnByGroupID(lua_State* state) { + ZoneServer* zone = lua_interface->GetZone(state); + int32 group_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (zone) { + Spawn* spawn = zone->GetSpawnGroup(group_id); + if (spawn) { + lua_interface->SetSpawnValue(state, spawn); + return 1; + } + } + return 0; +} + +int EQ2Emu_lua_GetSpawnByLocationID(lua_State* state) { + ZoneServer* zone = lua_interface->GetZone(state); + int32 location_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (zone) { + Spawn* spawn = zone->GetSpawnByLocationID(location_id); + if (spawn) { + lua_interface->SetSpawnValue(state, spawn); + return 1; + } + } + return 0; +} + +int EQ2Emu_lua_GetSpawnID(lua_State* state) { + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + lua_interface->SetInt32Value(state, spawn->GetDatabaseID()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetSpawnGroupID(lua_State* state) { + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + lua_interface->SetInt32Value(state, spawn->GetSpawnGroupID()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_SetSpawnGroupID(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 new_group_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn) { + spawn->SetSpawnGroupID(new_group_id); + lua_interface->SetBooleanValue(state, true); + return 1; + } + + lua_interface->SetBooleanValue(state, false); + return 1; +} + +int EQ2Emu_lua_AddSpawnToGroup(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 new_group_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn) { + if(spawn->GetSpawnGroupID() == new_group_id) { + lua_interface->SetBooleanValue(state, false); + return 1; + } + spawn->GetZone()->AddSpawnToGroup(spawn, new_group_id); + lua_interface->SetBooleanValue(state, true); + return 1; + } + + lua_interface->SetBooleanValue(state, false); + return 1; +} + +int EQ2Emu_lua_GetSpawnLocationID(lua_State* state) { + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + lua_interface->SetInt32Value(state, spawn->GetSpawnLocationID()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetSpawnLocationPlacementID(lua_State* state) { + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + lua_interface->SetInt32Value(state, spawn->GetSpawnLocationPlacementID()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetFactionAmount(lua_State* state) { + Player* player = (Player*)lua_interface->GetSpawn(state); + int32 faction_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer() && faction_id > 0) { + lua_interface->SetSInt32Value(state, player->GetFactions()->GetFactionValue(faction_id)); + return 1; + } + return 0; +} + +int EQ2Emu_lua_SetFactionID(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 value = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn) { + spawn->SetFactionID(value); + } + return 0; +} + +int EQ2Emu_lua_GetFactionID(lua_State* state) { + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + lua_interface->SetInt32Value(state, spawn->GetFactionID()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetGender(lua_State* state) { + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + lua_interface->SetInt32Value(state, spawn->GetGender()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetTarget(lua_State* state) { + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn && spawn->IsEntity() && ((Entity*)spawn)->GetTarget()) { + lua_interface->SetSpawnValue(state, ((Entity*)spawn)->GetTarget()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_PlayVoice(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + string mp3_string = lua_interface->GetStringValue(state, 2); + int32 key1 = lua_interface->GetInt32Value(state, 3); + int32 key2 = lua_interface->GetInt32Value(state, 4); + Spawn* player = lua_interface->GetSpawn(state, 5); + lua_interface->ResetFunctionStack(state); + if (spawn && mp3_string.length() > 0) { + Client* client = 0; + if (player && player->IsPlayer()) + client = ((Player*)player)->GetClient(); + if (client) { + if (((Player*)player)->WasSentSpawn(spawn->GetID())) + spawn->GetZone()->PlayVoice(client, spawn, mp3_string.c_str(), key1, key2); + } + else + spawn->GetZone()->PlayVoice(spawn, mp3_string.c_str(), key1, key2); + } + return 0; +} + +int EQ2Emu_lua_GetCurrentZoneSafeLocation(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + lua_interface->SetFloatValue(state, spawn->GetZone()->GetSafeX()); + lua_interface->SetFloatValue(state, spawn->GetZone()->GetSafeY()); + lua_interface->SetFloatValue(state, spawn->GetZone()->GetSafeZ()); + return 3; + } + return 0; +} + +int EQ2Emu_lua_HasLootItem(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + int32 item_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + lua_interface->SetBooleanValue(state, spawn->HasLootItemID(item_id)); + return 1; + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_AddLootItem(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn && spawn->IsEntity()) { + int32 item_id = lua_interface->GetInt32Value(state, 2); + int16 charges = lua_interface->GetInt16Value(state, 3); + if (charges == 0) + charges = 1; + ((Entity*)spawn)->AddLootItem(item_id, charges); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_RemoveLootItem(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn && spawn->IsEntity()) { + int32 item_id = lua_interface->GetInt32Value(state, 2); + Item* item = spawn->LootItem(item_id); + lua_interface->SetLuaUserDataStale(item); + safe_delete(item); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_AddLootCoin(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + int32 val = lua_interface->GetInt32Value(state, 2); + spawn->AddLootCoins(val); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_GiveLoot(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* entity = lua_interface->GetSpawn(state); + Spawn* player = lua_interface->GetSpawn(state, 2); + if (entity && player && player->IsPlayer()) { + int32 coins = lua_interface->GetInt32Value(state, 3); + vector* items = 0; + int i = 0; + int32 item_id = 0; + while ((item_id = lua_interface->GetInt32Value(state, 4 + i))) { + if (items == 0) + items = new vector; + if (master_item_list.GetItem(item_id)) + items->push_back(master_item_list.GetItem(item_id)); + i++; + } + Client* client = 0; + client = ((Player*)player)->GetClient(); + if (client) + ((Player*)player)->AddPendingLootItems(entity->GetID(), items); + if(coins > 0) + entity->AddLootCoins(coins); + safe_delete(items); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_HasPendingLootItem(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* entity = lua_interface->GetSpawn(state); + Spawn* player = lua_interface->GetSpawn(state, 2); + int32 item_id = lua_interface->GetInt32Value(state, 3); + lua_interface->ResetFunctionStack(state); + if (entity && entity->IsEntity() && player && player->IsPlayer() && item_id > 0) { + lua_interface->SetBooleanValue(state, ((Player*)player)->HasPendingLootItem(entity->GetID(), item_id)); + return 1; + } + return 0; +} + +int EQ2Emu_lua_HasPendingLoot(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* entity = lua_interface->GetSpawn(state); + Spawn* player = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + if (entity && player && player->IsPlayer()) { + lua_interface->SetBooleanValue(state, ((Player*)player)->HasPendingLootItems(entity->GetID())); + return 1; + } + return 0; +} + +int EQ2Emu_lua_CreateConversation(lua_State* state) { + if (!lua_interface) + return 0; + + vector* conversation = lua_interface->GetConversation(state); + lua_interface->SetLuaUserDataStale(conversation); + safe_delete(conversation); + lua_interface->ResetFunctionStack(state); + + conversation = new vector(); + lua_interface->SetConversationValue(state, conversation); + return 1; +} + +int EQ2Emu_lua_AddConversationOption(lua_State* state) { + if (!lua_interface) + return 0; + vector* conversation = lua_interface->GetConversation(state); + if (conversation) { + ConversationOption conv_option; + conv_option.option = lua_interface->GetStringValue(state, 2); + conv_option.function = lua_interface->GetStringValue(state, 3); + if (conv_option.option.length() > 0) + conversation->push_back(conv_option); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_CloseConversation(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* npc = lua_interface->GetSpawn(state); + Spawn* player = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + if (npc && player && player->IsPlayer() && player->GetZone()) { + Client* client = ((Player*)player)->GetClient(); + if (client) { + int32 conversation_id = client->GetConversationID(npc, 0); + client->CloseDialog(conversation_id); + } + } + return 0; +} + +int EQ2Emu_lua_CloseItemConversation(lua_State* state) { + if (!lua_interface) + return 0; + Item* item = lua_interface->GetItem(state); + Spawn* player = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + if (item && player && player->IsPlayer() && player->GetZone()) { + Client* client = ((Player*)player)->GetClient(); + if (client) { + int32 conversation_id = client->GetConversationID(0, item); + client->CloseDialog(conversation_id); + } + } + return 0; +} + +int EQ2Emu_lua_StartDialogConversation(lua_State* state) { + if (!lua_interface) + return 0; + vector* conversation = lua_interface->GetConversation(state); + Spawn* spawn = 0; + Item* item = 0; + int8 type = lua_interface->GetInt8Value(state, 2); + if (type == 1 || type == 3) + spawn = lua_interface->GetSpawn(state, 3); + else if (type == 2 || type == 4) + item = lua_interface->GetItem(state, 3); + Spawn* player = lua_interface->GetSpawn(state, 4); + string text = lua_interface->GetStringValue(state, 5); + string mp3 = lua_interface->GetStringValue(state, 6); + int32 key1 = lua_interface->GetInt32Value(state, 7); + int32 key2 = lua_interface->GetInt32Value(state, 8); + int8 language = lua_interface->GetInt8Value(state, 9); + + int numargs = lua_interface->GetNumberOfArgs(state); + int8 can_close = 1; + if(numargs > 9) + can_close = lua_interface->GetInt32Value(state, 10); + + lua_interface->ResetFunctionStack(state); + if (conversation && text.length() > 0 && (spawn || item) && player && player->IsPlayer()) { + Client* client = ((Player*)player)->GetClient(); + if (client) { + if (spawn) { + // Need to do this so the function works the same as it did before + if (type == 1) + type++; + + if (mp3.length() > 0) + client->DisplayConversation((Entity*)spawn, type, conversation, const_cast(text.c_str()), mp3.c_str(), key1, key2, language, can_close); + else + client->DisplayConversation((Entity*)spawn, type, conversation, const_cast(text.c_str()), nullptr, 0, 0, language, can_close); + } + else { + if (mp3.length() > 0) + client->DisplayConversation(item, conversation, const_cast(text.c_str()), type, mp3.c_str(), key1, key2, language, can_close); + else + client->DisplayConversation(item, conversation, const_cast(text.c_str()), type, nullptr, 0, 0, language, can_close); + } + } + } + lua_interface->SetLuaUserDataStale(conversation); + safe_delete(conversation); + return 0; +} + +int EQ2Emu_lua_StartConversation(lua_State* state) { + if (!lua_interface) + return 0; + vector* conversation = lua_interface->GetConversation(state); + Spawn* source = lua_interface->GetSpawn(state, 2); + Spawn* player = lua_interface->GetSpawn(state, 3); + string text = lua_interface->GetStringValue(state, 4); + string mp3 = lua_interface->GetStringValue(state, 5); + int32 key1 = lua_interface->GetInt32Value(state, 6); + int32 key2 = lua_interface->GetInt32Value(state, 7); + int8 language = lua_interface->GetInt32Value(state, 8); + + int numargs = lua_interface->GetNumberOfArgs(state); + int8 can_close = 1; + if(numargs > 8) + can_close = lua_interface->GetInt32Value(state, 9); + + lua_interface->ResetFunctionStack(state); + if (conversation && conversation->size() > 0 && text.length() > 0 && source && player && player->IsPlayer()) { + Client* client = ((Player*)player)->GetClient(); + if (mp3.length() > 0) + client->DisplayConversation(source, 1, conversation, text.c_str(), mp3.c_str(), key1, key2, language, can_close); + else + client->DisplayConversation(source, 1, conversation, text.c_str(), nullptr, 0, 0, language, can_close); + lua_interface->SetLuaUserDataStale(conversation); + safe_delete(conversation); + } + else + LogWrite(LUA__ERROR, 0, "LUA", "Spawn %s Error in StartConversation, potentially AddConversationOption not yet called or the StartConversation arguments are incorrect, text: %s, conversationSize: %i.", source ? source->GetName() : "UNKNOWN", text.size() ? text.c_str() : "", conversation ? conversation->size() : -1); + return 0; +} + +int EQ2Emu_lua_SetPlayerProximityFunction(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + float distance = lua_interface->GetFloatValue(state, 2); + string in_range_function = lua_interface->GetStringValue(state, 3); + string leaving_range_function = lua_interface->GetStringValue(state, 4); + if (spawn && !spawn->IsPlayer() && distance > 0 && in_range_function.length() > 0) + spawn->GetZone()->AddPlayerProximity(spawn, distance, in_range_function, leaving_range_function); + return 0; +} + +int EQ2Emu_lua_SetLocationProximityFunction(lua_State* state) { + ZoneServer* zone = lua_interface->GetZone(state); + float x = lua_interface->GetFloatValue(state, 2); + float y = lua_interface->GetFloatValue(state, 3); + float z = lua_interface->GetFloatValue(state, 4); + float max_variation = lua_interface->GetFloatValue(state, 5); + string in_range_function = lua_interface->GetStringValue(state, 6); + string leaving_range_function = lua_interface->GetStringValue(state, 7); + lua_interface->ResetFunctionStack(state); + if (zone && in_range_function.length() > 0) + zone->AddLocationProximity(x, y, z, max_variation, in_range_function, leaving_range_function); + return 0; +} + +int EQ2Emu_lua_SetLootCoin(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn && spawn->IsEntity()) { + int32 val = lua_interface->GetInt32Value(state, 2); + ((Entity*)spawn)->SetLootCoins(val); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_GetLootCoin(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn && spawn->IsEntity()) { + lua_interface->SetInt32Value(state, ((Entity*)spawn)->GetLootCoins()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_MovementLoopAdd(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + float x = lua_interface->GetFloatValue(state, 2); + float y = lua_interface->GetFloatValue(state, 3); + float z = lua_interface->GetFloatValue(state, 4); + float speed = lua_interface->GetFloatValue(state, 5); + int32 delay = lua_interface->GetInt32Value(state, 6); //this should be given as seconds, as it is converted to ms later + string function = lua_interface->GetStringValue(state, 7); + + int8 num_args = (int8)lua_interface->GetNumberOfArgs(state); + float heading = lua_interface->GetFloatValue(state, 8); + if (spawn) { + spawn->AddMovementLocation(x, y, z, speed, delay, function.c_str(), heading, (num_args > 7) ? true : false ); + spawn->GetZone()->AddMovementNPC(spawn); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_IsPlayer(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + lua_interface->SetBooleanValue(state, spawn->IsPlayer()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetCharacterID(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn && spawn->IsPlayer()) { + lua_interface->SetInt32Value(state, ((Player*)spawn)->GetCharacterID()); + return 1; + } + + lua_interface->SetInt32Value(state, 0); + return 1; +} + +int EQ2Emu_lua_FaceTarget(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + Spawn* target = lua_interface->GetSpawn(state, 2); + + int8 num_args = (int8)lua_interface->GetNumberOfArgs(state); + bool reset_action_state = true; + if(num_args > 2) + reset_action_state = lua_interface->GetBooleanValue(state, 3); + + if (spawn && target) { + if (spawn->IsEntity()) + // ((Entity*)spawn)->FaceTarget(target); + static_cast(spawn)->FaceTarget(target, reset_action_state); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_MoveToLocation(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + float x = lua_interface->GetFloatValue(state, 2); + float y = lua_interface->GetFloatValue(state, 3); + float z = lua_interface->GetFloatValue(state, 4); + float speed = lua_interface->GetFloatValue(state, 5); + string lua_function = lua_interface->GetStringValue(state, 6); + bool more_points = lua_interface->GetBooleanValue(state, 7); + + if (spawn) { + if (speed == 0) + speed = spawn->GetSpeed(); + + spawn->AddRunningLocation(x, y, z, speed, 0.0f, true, !more_points, lua_function); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_ClearRunningLocations(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + spawn->ClearRunningLocations(); + } + return 0; +} + +int EQ2Emu_lua_Say(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + string message = lua_interface->GetStringValue(state, 2); + Spawn* player = lua_interface->GetSpawn(state, 3); + float dist = lua_interface->GetFloatValue(state, 4); + int32 language = lua_interface->GetInt32Value(state, 5); + if (spawn && message.length() > 0) { + Client* client = 0; + if (player && player->IsPlayer()) + client = ((Player*)player)->GetClient(); + if (client) + spawn->GetZone()->HandleChatMessage(client, spawn, 0, CHANNEL_SAY, message.c_str(), (dist > 0.0f) ? dist : 30.0f, 0, true, language); + else + spawn->GetZone()->HandleChatMessage(spawn, 0, CHANNEL_SAY, message.c_str(), (dist > 0.0f) ? dist : 30.0f, 0, true, language); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_Shout(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + string message = lua_interface->GetStringValue(state, 2); + Spawn* player = lua_interface->GetSpawn(state, 3); + float dist = lua_interface->GetFloatValue(state, 4); + int32 language = lua_interface->GetInt32Value(state, 5); + if (spawn && message.length() > 0) { + Client* client = 0; + if (player && player->IsPlayer()) + client = ((Player*)player)->GetClient(); + if (client) + spawn->GetZone()->HandleChatMessage(client, spawn, 0, CHANNEL_SHOUT, message.c_str(), (dist > 0.0f) ? dist : 30.0f, 0, true, language); + else + spawn->GetZone()->HandleChatMessage(spawn, 0, CHANNEL_SHOUT, message.c_str(), (dist > 0.0f) ? dist : 30.0f, 0, true, language); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_SayOOC(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + string message = lua_interface->GetStringValue(state, 2); + Spawn* player = lua_interface->GetSpawn(state, 3); + float dist = lua_interface->GetFloatValue(state, 4); + int32 language = lua_interface->GetInt32Value(state, 5); + if (spawn && message.length() > 0) { + Client* client = 0; + if (player && player->IsPlayer()) + client = ((Player*)player)->GetClient(); + if (client) + spawn->GetZone()->HandleChatMessage(client, spawn, 0, CHANNEL_OUT_OF_CHARACTER, message.c_str(), (dist > 0.0f) ? dist : 30.0f, 0, true, language); + else + spawn->GetZone()->HandleChatMessage(spawn, 0, CHANNEL_OUT_OF_CHARACTER, message.c_str(), (dist > 0.0f) ? dist : 30.0f, 0, true, language); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_Emote(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + string message = lua_interface->GetStringValue(state, 2); + Spawn* spawn2 = lua_interface->GetSpawn(state, 3); + Spawn* player = lua_interface->GetSpawn(state, 4); + char* to = 0; + if (spawn2) + to = spawn2->GetName(); + if (spawn && message.length() > 0) { + Client* client = 0; + if (player && player->IsPlayer()) + client = ((Player*)player)->GetClient(); + if (client) + spawn->GetZone()->HandleChatMessage(client, spawn, to, CHANNEL_EMOTE, message.c_str(), 30); + else + spawn->GetZone()->HandleChatMessage(spawn, to, CHANNEL_EMOTE, message.c_str(), 30); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_SpellHeal(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + if(!luaspell || luaspell->resisted) { + lua_interface->ResetFunctionStack(state); + return 0; + } + Spawn* caster = luaspell->caster; + string heal_type = lua_interface->GetStringValue(state);//power, heal ect + int32 min_heal = lua_interface->GetInt32Value(state, 2); + int32 max_heal = lua_interface->GetInt32Value(state, 3); + Spawn* target = lua_interface->GetSpawn(state, 4); + int8 crit_mod = lua_interface->GetInt32Value(state, 5); + bool no_calcs = lua_interface->GetInt32Value(state, 6) == 1; + string custom_spell_name = lua_interface->GetStringValue(state, 7);//custom spell name + lua_interface->ResetFunctionStack(state); + + boost::to_lower(heal_type); + if (caster && caster->IsEntity()) { + bool success = false; + luaspell->resisted = false; + if (target) { + float distance = caster->GetDistance(target, true); + if (((Entity*)caster)->SpellHeal(target, distance, luaspell, heal_type, min_heal, max_heal, crit_mod, no_calcs, custom_spell_name)) + success = true; + } + if ((!success || luaspell->spell->GetSpellData()->group_spell) && luaspell->targets.size() > 0) { + Spawn* target = 0; + ZoneServer* zone = luaspell->caster->GetZone(); + luaspell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < luaspell->targets.size(); i++) { + if ((target = zone->GetSpawnByID(luaspell->targets[i]))) { + float distance = caster->GetDistance(target, true); + ((Entity*)caster)->SpellHeal(target, distance, luaspell, heal_type, min_heal, max_heal, crit_mod, no_calcs, custom_spell_name); + } + } + luaspell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + success = true; + } + if (success) { + if (caster->GetZone()) + caster->GetZone()->TriggerCharSheetTimer(); + } + } + return 0; +} + +int EQ2Emu_lua_SpellHealPct(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + if(!luaspell || luaspell->resisted) { + lua_interface->ResetFunctionStack(state); + return 0; + } + Spawn* caster = luaspell->caster; + string heal_type = lua_interface->GetStringValue(state);//power, heal ect + float percentage = lua_interface->GetFloatValue(state, 2); + bool current_value = lua_interface->GetBooleanValue(state, 3); + bool caster_value = lua_interface->GetBooleanValue(state, 4); + Spawn* target = lua_interface->GetSpawn(state, 5); + int8 crit_mod = lua_interface->GetInt32Value(state, 6); + bool no_calcs = lua_interface->GetInt32Value(state, 7) == 1; + string custom_spell_name = lua_interface->GetStringValue(state, 8);//custom spell name + lua_interface->ResetFunctionStack(state); + + boost::to_lower(heal_type); + int32 min_heal = 0, max_heal = 0; + if (caster && caster->IsEntity() && target) { + if(percentage <= 0.0f) + { + LogWrite(LUA__ERROR, 0, "LUA", "Error applying SpellHealPct on '%s'. percentage %f is less than or equal to 0.",target->GetName(),percentage); + return 0; + } + + if(heal_type == "power") + { + if(current_value) + { + if(caster_value) + min_heal = max_heal = (int32)(float)caster->GetPower() * (percentage / 100.0f); + else + min_heal = max_heal = (int32)(float)target->GetPower() * (percentage / 100.0f); + } + else + { + if(caster_value) + min_heal = max_heal = (int32)(float)caster->GetTotalPower() * (percentage / 100.0f); + else + min_heal = max_heal = (int32)(float)target->GetTotalPower() * (percentage / 100.0f); + } + + } + else + { + if(current_value) + { + if(caster_value) + min_heal = max_heal = (int32)(float)caster->GetHP() * (percentage / 100.0f); + else + min_heal = max_heal = (int32)(float)target->GetHP() * (percentage / 100.0f); + } + else + { + if(caster_value) + min_heal = max_heal = (int32)(float)caster->GetTotalHP() * (percentage / 100.0f); + else + min_heal = max_heal = (int32)(float)target->GetTotalHP() * (percentage / 100.0f); + } + } + + bool success = false; + luaspell->resisted = false; + if (target) { + float distance = caster->GetDistance(target, true); + if (((Entity*)caster)->SpellHeal(target, distance, luaspell, heal_type, min_heal, max_heal, crit_mod, no_calcs, custom_spell_name)) + success = true; + } + if ((!success || luaspell->spell->GetSpellData()->group_spell) && luaspell->targets.size() > 0) { + Spawn* target = 0; + ZoneServer* zone = luaspell->caster->GetZone(); + luaspell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < luaspell->targets.size(); i++) { + if ((target = zone->GetSpawnByID(luaspell->targets[i]))) { + float distance = caster->GetDistance(target, true); + ((Entity*)caster)->SpellHeal(target, distance, luaspell, heal_type, min_heal, max_heal, crit_mod, no_calcs, custom_spell_name); + } + } + luaspell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + success = true; + } + if (success) { + if (caster->GetZone()) + caster->GetZone()->TriggerCharSheetTimer(); + } + } + return 0; +} + +int EQ2Emu_lua_AddItem(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 item_id = lua_interface->GetInt32Value(state, 2); + int16 quantity = lua_interface->GetInt32Value(state, 3); + lua_interface->ResetFunctionStack(state); + + // default of 1 quantity to add + if (quantity == 0) + quantity = 1; + + if (spawn && spawn->IsPlayer()) { + Client* client = ((Player*)spawn)->GetClient(); + if (client && item_id > 0) { + lua_interface->SetBooleanValue(state, client->AddItem(item_id, quantity)); + return 1; + } + } + + lua_interface->SetBooleanValue(state, false); + return 1; +} + + +int EQ2Emu_lua_SummonItem(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 item_id = lua_interface->GetInt32Value(state, 2); + bool send_messages = (lua_interface->GetInt8Value(state, 3) == 1); + string location = lua_interface->GetStringValue(state, 4); + int16 item_count = lua_interface->GetInt16Value(state,5); + + //devn00b: if we dont have a count, assume 1 item. + if(!item_count) { + item_count = 1; + } + + lua_interface->ResetFunctionStack(state); + + if (spawn && spawn->IsPlayer()) { + Client* client = ((Player*)spawn)->GetClient(); + if (client && item_id > 0) { + if (strncasecmp(location.c_str(), "bank", 4) == 0) + lua_interface->SetBooleanValue(state, client->AddItemToBank(item_id, item_count)); + else + lua_interface->SetBooleanValue(state, client->AddItem(item_id, item_count)); + if (send_messages) { + Item* item = master_item_list.GetItem(item_id); + if (item) { + if(item_count > 1) { + client->Message(CHANNEL_COLOR_YELLOW, "You receive %i %s.", item_count, item->CreateItemLink(client->GetVersion()).c_str()); + string popup_text1 = "You receive "+ item_count; + string popup_text2 = " " + item->name; + string popup_text = popup_text1 + popup_text2; + client->SendPopupMessage(10, popup_text.c_str(), "ui_harvested_normal", 3, 0xFF, 0xFF, 0xFF); + // return 1; + } else { + client->Message(CHANNEL_COLOR_YELLOW, "You receive %s.", item->CreateItemLink(client->GetVersion()).c_str()); + string popup_text = "You receive " + item->name; + client->SendPopupMessage(10, popup_text.c_str(), "ui_harvested_normal", 3, 0xFF, 0xFF, 0xFF); + } + } + } + return 1; + } + } + lua_interface->SetBooleanValue(state, false); + return 1; +} + +int EQ2Emu_lua_RemoveItem(lua_State* state) { + Spawn* spawn = lua_interface->GetSpawn(state); + int32 item_id = lua_interface->GetInt32Value(state, 2); + int16 quantity = lua_interface->GetInt16Value(state, 3); + lua_interface->ResetFunctionStack(state); + + // default of 1 to remove + if (quantity == 0) + quantity = 1; + + Client* client; + Item* item; + + if (spawn && spawn->IsPlayer() && item_id > 0) { + if ((client = ((Player*)spawn)->GetClient())) { + if ((item = client->GetPlayer()->item_list.GetItemFromID(item_id))) { + if (client->RemoveItem(item, quantity)) { + lua_interface->SetBooleanValue(state, true); + return 1; + } + } + } + } + + lua_interface->SetBooleanValue(state, false); + return 1; +} + +int EQ2Emu_lua_HasItem(lua_State* state) { + Spawn* player = lua_interface->GetSpawn(state); + int32 item_id = lua_interface->GetInt32Value(state, 2); + bool include_bank = lua_interface->GetInt8Value(state, 3); + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer()) { + bool hasItem = false; + hasItem = ((Player*)player)->item_list.HasItem(item_id, include_bank); + if (!hasItem) + hasItem = ((Player*)player)->GetEquipmentList()->HasItem(item_id); + lua_interface->SetBooleanValue(state, hasItem); + return 1; + } + lua_interface->SetBooleanValue(state, false); + return 1; +} + +int EQ2Emu_lua_Spawn(lua_State* state) { + if (!lua_interface) + return 0; + ZoneServer* zone = lua_interface->GetZone(state); + int32 spawn_id = lua_interface->GetInt32Value(state, 2); + bool restricted_npc = (lua_interface->GetInt8Value(state, 3) == 1); + float x = lua_interface->GetFloatValue(state, 4); + float y = lua_interface->GetFloatValue(state, 5); + float z = lua_interface->GetFloatValue(state, 6); + float heading = lua_interface->GetFloatValue(state, 7); + if (zone && spawn_id > 0 && (x != 0 || y != 0 || z != 0)) { + Spawn* spawn = zone->GetSpawn(spawn_id); + if (!spawn) + lua_interface->LogError("%s: LUA Spawn command error: Could not find spawn with id of %u.", lua_interface->GetScriptName(state), spawn_id); + else { + spawn->SetX(x); + spawn->SetZ(z); + spawn->SetY(y,true,true); + spawn->SetLocation(zone->GetClosestLocation(spawn)); + spawn->SetHeading(heading); + if (restricted_npc) + spawn->AddAllowAccessSpawn(spawn); + + const char* spawn_script = world.GetSpawnScript(spawn_id); + bool scriptActive = false; + if (spawn_script && lua_interface->GetSpawnScript(spawn_script) != 0) { + scriptActive = true; + spawn->SetSpawnScript(string(spawn_script)); + } + + zone->CallSpawnScript(spawn, SPAWN_SCRIPT_PRESPAWN); + + zone->AddSpawn(spawn); + if (scriptActive) { + zone->CallSpawnScript(spawn, SPAWN_SCRIPT_SPAWN); + } + lua_interface->ResetFunctionStack(state); + lua_interface->SetSpawnValue(state, spawn); + return 1; + } + } + else { + string output = "Invalid paramaters to LUA Spawn command: \n"; + if (!zone) + output = output.append("\t").append("Missing zone reference. \n"); + if (spawn_id == 0) + output = output.append("\t").append("Missing spawn_id."); + lua_interface->LogError("%s: Error in EQ2Emu_lua_Spawn - %s", lua_interface->GetScriptName(state), output.c_str()); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_GetZoneName(lua_State* state) { + if (!lua_interface) + return 0; + ZoneServer* zone = lua_interface->GetZone(state); + lua_interface->ResetFunctionStack(state); + if (zone) { + lua_interface->SetStringValue(state, zone->GetZoneName()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetZoneID(lua_State* state) { + if (!lua_interface) + return 0; + ZoneServer* zone = lua_interface->GetZone(state); + lua_interface->ResetFunctionStack(state); + if (zone) { + lua_interface->SetInt32Value(state, zone->GetZoneID()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetZone(lua_State* state) { + if (!lua_interface) + return 0; + int32 zone_id = lua_interface->GetInt32Value(state); + ZoneServer* zone = 0; + if (zone_id > 0) + zone = zone_list.Get(zone_id, true, false, false); + else { + string zone_name = lua_interface->GetStringValue(state); + if (zone_name.length() > 0) { + zone = zone_list.Get(zone_name.c_str(), true, false, false); + } + else { + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) + zone = spawn->GetZone(); + } + } + lua_interface->ResetFunctionStack(state); + if (zone) { + lua_interface->SetZoneValue(state, zone); + return 1; + } + return 0; +} + +int EQ2Emu_lua_AddHate(lua_State* state) { + Spawn* entity = lua_interface->GetSpawn(state); + Spawn* npc = lua_interface->GetSpawn(state, 2); + sint32 amount = lua_interface->GetSInt32Value(state, 3); + bool send_packet = lua_interface->GetInt8Value(state, 4) == 1 ? true : false; + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + + if(luaspell && luaspell->resisted) { + lua_interface->ResetFunctionStack(state); + return 0; + } + + if (entity && entity->IsEntity() && amount != 0) { + if (luaspell && luaspell->caster) { + ZoneServer* zone = luaspell->caster->GetZone(); + luaspell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < luaspell->targets.size(); i++) { + Spawn* spawn = zone->GetSpawnByID(luaspell->targets.at(i)); + if (spawn && spawn->IsNPC() && spawn->Alive() && spawn->GetZone()) { + entity->CheckEncounterState((Entity*)spawn); + ((NPC*)spawn)->AddHate((Entity*)entity, amount); + if (send_packet) + entity->GetZone()->SendThreatPacket(entity, npc, amount, luaspell->spell->GetName()); + } + } + luaspell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + else if (npc && npc->IsNPC() && npc->GetZone()) { + entity->CheckEncounterState((Entity*)npc); + ((NPC*)npc)->AddHate((Entity*)entity, amount); + } + } + lua_interface->ResetFunctionStack(state); + return 0; +} + + +int EQ2Emu_lua_Zone(lua_State* state) { + if (!lua_interface) + return 0; + ZoneServer* zone = lua_interface->GetZone(state); + Spawn* player = lua_interface->GetSpawn(state, 2); + Client* client = 0; + if (player && player->IsPlayer()) + client = ((Player*)player)->GetClient(); + float x = lua_interface->GetFloatValue(state, 3); + float y = lua_interface->GetFloatValue(state, 4); + float z = lua_interface->GetFloatValue(state, 5); + float heading = lua_interface->GetFloatValue(state, 6); + + if (zone && client) { + LogWrite(LUA__DEBUG, 0, "LUA", "LUA Zone Request by Player: '%s' (%u)", player->GetName(), player->GetID()); + LogWrite(LUA__DEBUG, 5, "LUA", "\tTo Zone: '%s' (%u)", zone->GetZoneName(), zone->GetZoneID()); + + if (!client->CheckZoneAccess(zone->GetZoneName())) + { + LogWrite(LUA__WARNING, 0, "LUA", "CheckZoneAccess() FAILED! LUA Zone Request Denied!"); + return 0; + } + + if (x != 0 || y != 0 || z != 0) { + LogWrite(LUA__DEBUG, 5, "LUA", "\tTo Coordinates: %2f, %2f, %2f, %2f", x, y, z, heading); + player->SetX(x); + player->SetY(y); + player->SetZ(z); + player->SetHeading(heading); + client->Zone(zone->GetZoneName(), false); + } + else + client->Zone(zone->GetZoneName()); + } + else + lua_interface->LogError("%s: Error in EQ2Emu_lua_Zone: invalid zone or spawn input.", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_AddSpawnAccess(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + Spawn* spawn2 = lua_interface->GetSpawn(state, 2); + if (spawn && spawn2) + spawn->AddAllowAccessSpawn(spawn2); + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_CastSpell(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* target = lua_interface->GetSpawn(state); + int32 spell_id = lua_interface->GetInt32Value(state, 2); + int8 spell_tier = lua_interface->GetInt8Value(state, 3); + Spawn* caster = lua_interface->GetSpawn(state, 4); + int16 custom_cast_time = lua_interface->GetInt16Value(state, 5); + lua_interface->ResetFunctionStack(state); + + if (!target) { + lua_interface->LogError("%s: LUA CastSpell command error: target is not a valid spawn", lua_interface->GetScriptName(state)); + return 0; + } + + if (!target->IsEntity()) { + lua_interface->LogError("%s: LUA CastSpell command error: target (%s) is not an entity", lua_interface->GetScriptName(state), target->GetName()); + return 0; + } + + if (spell_id <= 0) { + lua_interface->LogError("%s: LUA CastSpell command error: spell id is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (caster && !caster->IsEntity()) { + lua_interface->LogError("%s: LUA CastSpell command error: caster (%s) is not an entity", lua_interface->GetScriptName(state), caster->GetName()); + return 0; + } + + if (spell_tier == 0) + spell_tier = 1; + + if (!caster) + caster = target; + + target->GetZone()->ProcessSpell(master_spell_list.GetSpell(spell_id, spell_tier), (Entity*)caster, (Entity*)target, true, false, NULL, custom_cast_time); + return 0; +} + +int EQ2Emu_lua_SpellDamage(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* target = lua_interface->GetSpawn(state); + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + if(!luaspell || luaspell->resisted) { + lua_interface->ResetFunctionStack(state); + lua_interface->SetBooleanValue(state, false); + return 1; + } + Spawn* caster = luaspell->caster; + sint32 type = lua_interface->GetSInt32Value(state, 2); + int32 min_damage = lua_interface->GetInt32Value(state, 3); + int32 max_damage = lua_interface->GetInt32Value(state, 4); + int8 crit_mod = lua_interface->GetInt32Value(state, 5); + bool no_calcs = lua_interface->GetInt32Value(state, 6) == 1; + //lua_interface->ResetFunctionStack(state); + int32 class_id = lua_interface->GetInt32Value(state, 7); + vector faction_req; + vector race_req; + int32 class_req = 0; + int32 i = 0; + int8 f = 0; + int8 r = 0; + while ((class_id = lua_interface->GetInt32Value(state, 7 + i))) { + if (class_id < 100) { + class_req += pow(2.0, double(class_id - 1)); + } + else if (class_id > 100 && class_id < 1000) { + race_req.push_back(class_id); + r++; + } + else { + faction_req.push_back(class_id); + f++; + } + i++; + } + lua_interface->ResetFunctionStack(state); + if (caster && caster->IsEntity()) { + bool race_match = false; + bool success = false; + luaspell->resisted = false; + if (luaspell->targets.size() > 0) { + ZoneServer* zone = luaspell->caster->GetZone(); + Spawn* target = 0; + luaspell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < luaspell->targets.size(); i++) { + if ((target = zone->GetSpawnByID(luaspell->targets[i]))) { + + if (race_req.size() > 0) { + for (int8 i = 0; i < race_req.size(); i++) { + if(race_req[i] == target->GetRace() || + race_req[i] == race_types_list.GetRaceType(target->GetModelType()) || + race_req[i] == race_types_list.GetRaceBaseType(target->GetModelType())) { + race_match = true; + } + } + } + else + race_match = true; // if the race_req.size = 0 then there is no race requirement and the race_match will be true + if (race_match == true) { + float distance = caster->GetDistance(target, true); + ((Entity*)caster)->SpellAttack(target, distance, luaspell, type, min_damage, max_damage, crit_mod, no_calcs); + } + } + } + success = true; + luaspell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + else if (target) { + + //check class and race/faction here + if (race_req.size() > 0) { + for (int8 i = 0; i < race_req.size(); i++) { + if(race_req[i] == target->GetRace() || + race_req[i] == race_types_list.GetRaceType(target->GetModelType()) || + race_req[i] == race_types_list.GetRaceBaseType(target->GetModelType())) { + race_match = true; + } + } + } + else + race_match = true; // if the race_req.size = 0 then there is no race requirement and the race_match will be true + if (race_match == true) { + float distance = caster->GetDistance(target, true); + if (((Entity*)caster)->SpellAttack(target, distance, luaspell, type, min_damage, max_damage, crit_mod, no_calcs)) + success = true; + } + } + lua_interface->SetBooleanValue(state, luaspell->has_damaged); + if (success) { + Spell* spell = luaspell->spell; + if (caster->IsPlayer() && spell && spell->GetSpellData()->target_type == 1 && spell->GetSpellData()->spell_book_type == 1) { //offense combat art + ((Player*)caster)->InCombat(true); + if (caster->GetZone()) + caster->GetZone()->TriggerCharSheetTimer(); + } + } + } + else { + lua_interface->SetBooleanValue(state, false); + } + return 1; +} + +int EQ2Emu_lua_SpellDamageExt(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* target = lua_interface->GetSpawn(state); + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + if(!luaspell || luaspell->resisted) { + lua_interface->ResetFunctionStack(state); + lua_interface->SetBooleanValue(state, false); + return 1; + } + Spawn* caster = luaspell->caster; + sint32 type = lua_interface->GetSInt32Value(state, 2); + int32 min_damage = lua_interface->GetInt32Value(state, 3); + int32 max_damage = lua_interface->GetInt32Value(state, 4); + int8 crit_mod = lua_interface->GetInt32Value(state, 5); + bool no_calcs = lua_interface->GetInt32Value(state, 6) == 1; + int32 override_packet_type = lua_interface->GetInt32Value(state, 7); + bool take_power = lua_interface->GetInt32Value(state, 8) == 1; + //lua_interface->ResetFunctionStack(state); + int32 class_id = lua_interface->GetInt32Value(state, 9); + vector faction_req; + vector race_req; + int32 class_req = 0; + int32 i = 0; + int8 f = 0; + int8 r = 0; + while ((class_id = lua_interface->GetInt32Value(state, 9 + i))) { + if (class_id < 100) { + class_req += pow(2.0, double(class_id - 1)); + } + else if (class_id > 100 && class_id < 1000) { + race_req.push_back(class_id); + r++; + } + else { + faction_req.push_back(class_id); + f++; + } + i++; + } + lua_interface->ResetFunctionStack(state); + if (caster && caster->IsEntity()) { + bool race_match = false; + bool success = false; + luaspell->resisted = false; + if (luaspell->targets.size() > 0) { + ZoneServer* zone = luaspell->caster->GetZone(); + Spawn* target = 0; + luaspell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < luaspell->targets.size(); i++) { + if ((target = zone->GetSpawnByID(luaspell->targets[i]))) { + + if (race_req.size() > 0) { + for (int8 i = 0; i < race_req.size(); i++) { + if(race_req[i] == target->GetRace() || + race_req[i] == race_types_list.GetRaceType(target->GetModelType()) || + race_req[i] == race_types_list.GetRaceBaseType(target->GetModelType())) { + race_match = true; + } + } + } + else + race_match = true; // if the race_req.size = 0 then there is no race requirement and the race_match will be true + if (race_match == true) { + float distance = caster->GetDistance(target, true); + ((Entity*)caster)->SpellAttack(target, distance, luaspell, type, min_damage, max_damage, crit_mod, no_calcs, override_packet_type, take_power); + } + } + } + success = true; + luaspell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + else if (target) { + + //check class and race/faction here + if (race_req.size() > 0) { + for (int8 i = 0; i < race_req.size(); i++) { + if(race_req[i] == target->GetRace() || + race_req[i] == race_types_list.GetRaceType(target->GetModelType()) || + race_req[i] == race_types_list.GetRaceBaseType(target->GetModelType())) { + race_match = true; + } + } + } + else + race_match = true; // if the race_req.size = 0 then there is no race requirement and the race_match will be true + if (race_match == true) { + float distance = caster->GetDistance(target, true); + if (((Entity*)caster)->SpellAttack(target, distance, luaspell, type, min_damage, max_damage, crit_mod, no_calcs, override_packet_type, take_power)) + success = true; + } + } + lua_interface->SetBooleanValue(state, luaspell->has_damaged); + if (success) { + Spell* spell = luaspell->spell; + if (caster->IsPlayer() && spell && spell->GetSpellData()->target_type == 1 && spell->GetSpellData()->spell_book_type == 1) { //offense combat art + ((Player*)caster)->InCombat(true); + if (caster->GetZone()) + caster->GetZone()->TriggerCharSheetTimer(); + } + } + } + else { + lua_interface->SetBooleanValue(state, false); + } + return 1; +} +int EQ2Emu_lua_ModifyPower(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + sint32 value = lua_interface->GetSInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn && value != 0) { + if (spawn->GetPower() + value > spawn->GetTotalPower()) + spawn->SetPower(spawn->GetTotalPower()); + else + spawn->SetPower(spawn->GetPower() + value); + } + return 0; +} +int EQ2Emu_lua_ModifyHP(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + sint32 value = lua_interface->GetSInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn && value != 0) { + if (spawn->GetHP() + value > spawn->GetTotalHP()) + spawn->SetHP(spawn->GetTotalHP()); + else + spawn->SetHP(spawn->GetHP() + value); + } + return 0; +} +int EQ2Emu_lua_ModifyMaxPower(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + sint32 value = lua_interface->GetSInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn && value != 0) { + spawn->SetTotalPower(value); + spawn->SetTotalPowerBaseInstance(value); + } + return 0; +} +int EQ2Emu_lua_ModifyMaxHP(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + sint32 value = lua_interface->GetSInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn && value != 0) { + spawn->SetTotalHP(value); + spawn->SetTotalHPBaseInstance(value); + } + return 0; +} +int EQ2Emu_lua_SetCurrentHP(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + sint32 value = lua_interface->GetSInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn) { + spawn->SetHP(value); + if (value > spawn->GetTotalHPBase()) + spawn->SetTotalHP(value); + } + return 0; +} +int EQ2Emu_lua_SetMaxHP(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + float value = lua_interface->GetFloatValue(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn && spawn->IsEntity() && value > 0) + ((Entity*)spawn)->AddSpellBonus(luaspell, ITEM_STAT_HEALTH, value - spawn->GetTotalHP()); + + if (spawn && spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + + return 0; +} + +int EQ2Emu_lua_SetMaxHPBase(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + sint32 value = lua_interface->GetSInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn && spawn->IsEntity() && value > 0) + ((Entity*)spawn)->SetTotalHPBase(value); + return 0; +} + +int EQ2Emu_lua_SetCurrentPower(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + sint32 value = lua_interface->GetSInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn && value > 0) { + spawn->SetPower(value); + if (value > spawn->GetTotalPowerBase()) + spawn->SetTotalPower(value); + } + return 0; +} +int EQ2Emu_lua_SetMaxPower(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + sint32 value = lua_interface->GetSInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn && spawn->IsEntity() && value > 0) + ((Entity*)spawn)->AddSpellBonus(luaspell, ITEM_STAT_POWER, value - spawn->GetTotalPower()); + return 0; +} +int EQ2Emu_lua_SetMaxPowerBase(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + sint32 value = lua_interface->GetSInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn && spawn->IsEntity() && value > 0) + ((Entity*)spawn)->SetTotalPowerBase(value); + return 0; +} +int EQ2Emu_lua_SetPosition(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + float x = lua_interface->GetFloatValue(state, 2); + float y = lua_interface->GetFloatValue(state, 3); + float z = lua_interface->GetFloatValue(state, 4); + float heading = lua_interface->GetFloatValue(state, 5); + lua_interface->ResetFunctionStack(state); + if (spawn) { + spawn->SetX(x); + spawn->SetY(y); + spawn->SetZ(z); + if (heading != 0) + spawn->SetHeading(heading); + spawn->SetSpawnOrigX(spawn->GetX()); + spawn->SetSpawnOrigY(spawn->GetY()); + spawn->SetSpawnOrigZ(spawn->GetZ()); + spawn->SetSpawnOrigHeading(spawn->GetHeading()); + if (spawn->IsPlayer()) { + Client* client = ((Player*)spawn)->GetClient(); + if (client) { + EQ2Packet* packet = client->GetPlayer()->Move(x, y, z, client->GetVersion(), (heading == 0 ? -1.0f : (heading + 180.0f))); + client->QueuePacket(packet); + } + } + + } + return 0; +} +int EQ2Emu_lua_SetHeading(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + float value = lua_interface->GetFloatValue(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn) { + spawn->SetHeading(value); + if (spawn->IsPlayer()) { + Client* client = ((Player*)spawn)->GetClient(); + if (client) { + EQ2Packet* packet = client->GetPlayer()->Move(spawn->GetX(), spawn->GetY(), spawn->GetZ(), client->GetVersion(), value + 180.0f); + client->QueuePacket(packet); + } + } + } + return 0; +} +int EQ2Emu_lua_SetModelType(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int16 value = lua_interface->GetInt16Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn) + spawn->SetModelType(value); + return 0; +} +int EQ2Emu_lua_SetAdventureClass(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int8 value = lua_interface->GetInt8Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn) { + if (spawn->IsPlayer()) + ((Player*)spawn)->SetPlayerAdventureClass(value); + else + spawn->SetAdventureClass(value); + } + return 0; +} +int EQ2Emu_lua_SetTradeskillClass(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int8 value = lua_interface->GetInt8Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn) { + spawn->SetTradeskillClass(value); + if (spawn->IsEntity()) { + ((Entity*)spawn)->GetInfoStruct()->set_tradeskill_class1(classes.GetTSBaseClass(spawn->GetTradeskillClass())); + ((Entity*)spawn)->GetInfoStruct()->set_tradeskill_class2(classes.GetSecondaryTSBaseClass(spawn->GetTradeskillClass())); + ((Entity*)spawn)->GetInfoStruct()->set_tradeskill_class3(spawn->GetTradeskillClass()); + } + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + return 0; +} +int EQ2Emu_lua_SetMount(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int16 value = lua_interface->GetInt16Value(state, 2); + if (spawn && spawn->IsEntity()) { + ((Entity*)spawn)->SetMount(value); + EQ2_Color color; + color.red = 255; + color.green = 255; + color.blue = 255; + ((Entity*)spawn)->SetMountColor(&color); + ((Entity*)spawn)->SetMountSaddleColor(&color); + } + return 0; +} +int EQ2Emu_lua_SetMountColor(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + EQ2_Color mount_color; + EQ2_Color saddle_color; + mount_color.red = lua_interface->GetInt8Value(state, 2); + mount_color.green = lua_interface->GetInt8Value(state, 3); + mount_color.blue = lua_interface->GetInt8Value(state, 4); + saddle_color.red = lua_interface->GetInt8Value(state, 5); + saddle_color.green = lua_interface->GetInt8Value(state, 6); + saddle_color.blue = lua_interface->GetInt8Value(state, 7); + if (spawn && spawn->IsEntity()) { + ((Entity*)spawn)->SetMountColor(&mount_color); + ((Entity*)spawn)->SetMountSaddleColor(&saddle_color); + } + return 0; +} +int EQ2Emu_lua_GetMount(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn && spawn->IsEntity()) { + lua_interface->SetInt32Value(state, ((Entity*)spawn)->GetMount()); + return 1; + } + return 0; +} +int EQ2Emu_lua_GetRace(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) + { + LogWrite(LUA__DEBUG, 0, "LUA", "%s - Race: %i", __FUNCTION__, spawn->GetRace()); + lua_interface->SetInt32Value(state, spawn->GetRace()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetRaceName(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + lua_interface->SetStringValue(state, races.GetRaceName(spawn->GetRace())); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetClass(lua_State* state) { + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + lua_interface->SetInt32Value(state, spawn->GetAdventureClass()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetClassName(lua_State* state) { + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + lua_interface->SetStringValue(state, classes.GetClassName(spawn->GetAdventureClass())); + return 1; + } + return 0; +} + +int EQ2Emu_lua_SetSpeed(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + float value = lua_interface->GetFloatValue(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn) { + spawn->SetSpeed(value); + + if(spawn->IsEntity()) + ((Entity*)spawn)->SetSpeed(value); + if (spawn->IsPlayer()) { + Client* client = ((Player*)spawn)->GetClient(); + if (client) { + PacketStruct* packet = configReader.getStruct("WS_SetControlGhost", client->GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", 0xFFFFFFFF); + packet->setDataByName("speed", value); + packet->setDataByName("size", 0.51); + EQ2Packet* app = packet->serialize(); + client->QueuePacket(app); + safe_delete(packet); + } + } + } + } + return 0; +} + +int EQ2Emu_lua_AddSpellBonus(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + const int16 type = lua_interface->GetInt16Value(state, 2); + const float value = lua_interface->GetFloatValue(state, 3); + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + + if(!luaspell || luaspell->resisted) { + lua_interface->ResetFunctionStack(state); + return 0; + } + + int64 class_req = 0; + int32 class_id = 0; + vector faction_req; + vector race_req; + int32 i = 0; + int8 f = 0; + int8 r = 0; + while ((class_id = lua_interface->GetInt32Value(state, 4 + i))) { + if (class_id < 100) { + class_req += pow(2.0, double(class_id - 1)); + } + else if (class_id > 100 && class_id < 1000) { + race_req.push_back(class_id); + r++; + } + else { + faction_req.push_back(class_id); + f++; + } + i++; + } + + if (value != 0 && type >= 0) { + if (luaspell && luaspell->spell && luaspell->caster) { + ZoneServer* zone = luaspell->caster->GetZone(); + luaspell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < luaspell->targets.size(); i++) { + Spawn* target = zone->GetSpawnByID(luaspell->targets[i]); + if (target) { + if (target->IsPlayer()) { + ((Player*)target)->AddSpellBonus(luaspell, type, value, class_req, race_req, faction_req); + LogWrite(LUA__DEBUG, 0, "LUA", "Applying Spell Bonus to Player '%s'. Is a Group Member.", ((Player*)target)->GetName()); + if (((Player*)target)->GetGroupMemberInfo()) + ((Player*)target)->UpdateGroupMemberInfo(); + ((Player*)target)->SetCharSheetChanged(true); + } + else if (target->IsNPC()) + ((NPC*)target)->AddSpellBonus(luaspell, type, value, class_req, race_req, faction_req); + else + lua_interface->LogError("%s: Error applying spell bonus on non entity.", lua_interface->GetScriptName(state)); + } + } + luaspell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_SPELLBONUS)) + luaspell->effect_bitmask += EFFECT_FLAG_SPELLBONUS; + } + else if (spawn && spawn->IsEntity()) { + ((Entity*)spawn)->AddSpellBonus(luaspell, type, value, class_req, race_req, faction_req); + LogWrite(LUA__DEBUG, 0, "LUA", "Applying Spell Bonus to Entity '%s'. Is a Group Member.", ((Entity*)spawn)->GetName()); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + else + lua_interface->LogError("%s: Unable to apply spell bonus in AddSpellBonus.", lua_interface->GetScriptName(state)); + } + else + lua_interface->LogError("%s: Invalid parameters for AddSpellBonus.", lua_interface->GetScriptName(state)); + return 0; +} + +int EQ2Emu_lua_AddSpawnSpellBonus(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + int16 type = lua_interface->GetInt16Value(state, 2); + sint32 value = lua_interface->GetSInt32Value(state, 3); + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA AddSpawnSpellBonus command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA AddSpawnSpellBonus command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (value == 0) { + lua_interface->LogError("%s: LUA AddSpawnSpellBonus command error: value must be set", lua_interface->GetScriptName(state)); + return 0; + } + + if (!luaspell || !luaspell->spell) { + lua_interface->LogError("%s: LUA AddSpawnSpellBonus command error: can only be used in a spell script", lua_interface->GetScriptName(state)); + return 0; + } + + if(luaspell->resisted) { + lua_interface->ResetFunctionStack(state); + return 0; + } + + int32 class_req = 0; + vector faction_req; + vector race_req; + int32 class_id = 0; + int32 i = 0; + int8 f = 0; + int8 r = 0; + while ((class_id = lua_interface->GetInt32Value(state, 4 + i))) { + if (class_id < 100) { + class_req += pow(2.0, double(class_id - 1)); + } + else if (class_id > 100 && class_id < 1000) { + race_req.push_back(class_id); + r++; + } + else { + faction_req.push_back(class_id); + f++; + } + i++; + } + + + ((Entity*)spawn)->AddSpellBonus(luaspell, type, value, class_req, race_req, faction_req); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_SPELLBONUS)) + luaspell->effect_bitmask += EFFECT_FLAG_SPELLBONUS; + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + + return 0; +} + +int EQ2Emu_lua_RemoveSpawnSpellBonus(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA AddSpawnSpellBonus command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA AddSpawnSpellBonus command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (!luaspell || !luaspell->spell) { + lua_interface->LogError("%s: LUA AddSpawnSpellBonus command error: can only be used in a spell script", lua_interface->GetScriptName(state)); + return 0; + } + + ((Entity*)spawn)->RemoveSpellBonus(luaspell); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + + return 0; +} + +int EQ2Emu_lua_RemoveSpellBonus(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + if (luaspell && luaspell->spell) { + ZoneServer* zone = nullptr; + if (luaspell->caster != nullptr) + zone = luaspell->caster->GetZone(); + if(!zone && spawn) { + zone = spawn->GetZone(); // workaround to try to establish a zone to find the targets and remove the spells + } + Spawn* target = 0; + if(zone) { + luaspell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < luaspell->targets.size(); i++) { + target = zone->GetSpawnByID(luaspell->targets[i]); + if (target && target->IsEntity()) { + ((Entity*)target)->RemoveSpellBonus(luaspell); + if (target->IsPlayer()) + ((Player*)target)->SetCharSheetChanged(true); + } + } + luaspell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + else { + LogWrite(LUA__ERROR, 0, "LUA", "Error removing spell bonus buff %s called by %s, zone is not available.", luaspell->spell ? luaspell->spell->GetName() : "NotSet", spawn->GetName()); + } + } + else if (spawn && spawn->IsEntity()) { + ((Entity*)spawn)->RemoveSpellBonus(luaspell); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + return 0; +} + +int EQ2Emu_lua_AddSkillBonus(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 skill_id = lua_interface->GetInt32Value(state, 2); + float value = lua_interface->GetFloatValue(state, 3); + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + if (value != 0) { + int32 spell_id = 0; + if (luaspell && luaspell->spell && luaspell->caster) { + if(luaspell->resisted) { + lua_interface->ResetFunctionStack(state); + return 0; + } + spell_id = luaspell->spell->GetSpellID(); + ZoneServer* zone = luaspell->caster->GetZone(); + Spawn* target = 0; + luaspell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < luaspell->targets.size(); i++) { + target = zone->GetSpawnByID(luaspell->targets[i]); + if (target && target->Alive()) { + if (target->IsPlayer()) { + ((Player*)target)->AddSkillBonus(spell_id, skill_id, value); + Client* client = ((Player*)target)->GetClient(); + if (client) { + EQ2Packet* packet = ((Player*)target)->GetSkills()->GetSkillPacket(client->GetVersion()); + if (packet) + client->QueuePacket(packet); + } + if (!(luaspell->effect_bitmask & EFFECT_FLAG_SKILLBONUS)) + luaspell->effect_bitmask += EFFECT_FLAG_SKILLBONUS; + } + else if (target->IsNPC()) { + ((NPC*)target)->AddSkillBonus(spell_id, skill_id, value); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_SKILLBONUS)) + luaspell->effect_bitmask += EFFECT_FLAG_SKILLBONUS; + } + else + LogWrite(LUA__ERROR, 0, "LUA", "Error applying bonus buff on '%s'. Not a NPC or player.", target->GetName()); + } + } + luaspell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + else if (spawn) { + if (spawn->IsPlayer()) { + ((Player*)spawn)->AddSkillBonus(spell_id, skill_id, value); + Client* client = ((Player*)spawn)->GetClient(); + if (client) { + EQ2Packet* packet = ((Player*)spawn)->GetSkills()->GetSkillPacket(client->GetVersion()); + if (packet) + client->QueuePacket(packet); + } + } + else if (spawn->IsNPC()) + ((NPC*)spawn)->AddSkillBonus(spell_id, skill_id, value); + else + LogWrite(LUA__ERROR, 0, "LUA", "Error applying skill bonus on '%s'. Not a NPC or player.", spawn->GetName()); + } + } + else + lua_interface->LogError("%s: Invalid parameters for AddSkillBonus.", lua_interface->GetScriptName(state)); + return 0; +} + +int EQ2Emu_lua_RemoveSkillBonus(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + if (spawn && spawn->IsPlayer()) { + int32 spell_id = 0; + if (luaspell && luaspell->spell && luaspell->caster) { + if(luaspell->resisted) { + lua_interface->ResetFunctionStack(state); + return 0; + } + spell_id = luaspell->spell->GetSpellID(); + ZoneServer* zone = luaspell->caster->GetZone(); + Spawn* target = 0; + luaspell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < luaspell->targets.size(); i++) { + target = zone->GetSpawnByID(luaspell->targets[i]); + if (target) { + if (target->IsPlayer()) { + ((Player*)target)->RemoveSkillBonus(spell_id); + Client* client = ((Player*)target)->GetClient(); + if (client) { + EQ2Packet* packet = ((Player*)target)->GetSkills()->GetSkillPacket(client->GetVersion()); + if (packet) + client->QueuePacket(packet); + } + } + else if (target->IsNPC()) + ((NPC*)target)->RemoveSkillBonus(spell_id); + else + LogWrite(LUA__ERROR, 0, "LUA", "Error removing skill bonus on '%s'. Not a NPC or player.", spawn->GetName()); + } + } + luaspell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + else if (spawn) { + if (spawn->IsPlayer()) { + ((Player*)spawn)->RemoveSkillBonus(spell_id); + Client* client = ((Player*)spawn)->GetClient(); + if (client) { + EQ2Packet* packet = ((Player*)spawn)->GetSkills()->GetSkillPacket(client->GetVersion()); + if (packet) + client->QueuePacket(packet); + } + } + else if (spawn->IsNPC()) + ((NPC*)spawn)->RemoveSkillBonus(spell_id); + else + LogWrite(LUA__ERROR, 0, "LUA", "Error removing skill bonus on '%s'. Not a NPC or player.", spawn->GetName()); + } + } + return 0; +} + +int EQ2Emu_lua_AddControlEffect(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int8 type = lua_interface->GetInt32Value(state, 2); + bool only_add_spawn = lua_interface->GetInt8Value(state, 3) == 1; + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + + if(luaspell && luaspell->resisted) { + lua_interface->ResetFunctionStack(state); + return 0; + } + + if (!only_add_spawn && luaspell && luaspell->spell && luaspell->caster && type != 0) { + ZoneServer* zone = luaspell->caster->GetZone(); + Spawn* target = 0; + luaspell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < luaspell->targets.size(); i++) { + target = zone->GetSpawnByID(luaspell->targets[i]); + if (target && target->IsEntity()) { + if (type == CONTROL_EFFECT_TYPE_MEZ) { + ((Entity*)target)->AddMezSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_MEZ)) + luaspell->effect_bitmask += EFFECT_FLAG_MEZ; + if (target->IsNPC()) + ((NPC*)target)->Brain()->AddHate(luaspell->caster, 5); + } + else if (type == CONTROL_EFFECT_TYPE_STIFLE) { + ((Entity*)target)->AddStifleSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_STIFLE)) + luaspell->effect_bitmask += EFFECT_FLAG_STIFLE; + if (target->IsNPC()) + ((NPC*)target)->Brain()->AddHate(luaspell->caster, 5); + } + else if (type == CONTROL_EFFECT_TYPE_DAZE) { + ((Entity*)target)->AddDazeSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_DAZE)) + luaspell->effect_bitmask += EFFECT_FLAG_DAZE; + if (target->IsNPC()) + ((NPC*)target)->Brain()->AddHate(luaspell->caster, 5); + } + else if (type == CONTROL_EFFECT_TYPE_STUN) { + if (!(luaspell->effect_bitmask & EFFECT_FLAG_STUN)) + luaspell->effect_bitmask += EFFECT_FLAG_STUN; + ((Entity*)target)->AddStunSpell(luaspell); + if (target->IsNPC()) + ((NPC*)target)->Brain()->AddHate(luaspell->caster, 5); + } + else if (type == CONTROL_EFFECT_TYPE_ROOT) { + if (!(luaspell->effect_bitmask & EFFECT_FLAG_ROOT)) + luaspell->effect_bitmask += EFFECT_FLAG_ROOT; + ((Entity*)target)->AddRootSpell(luaspell); + if (target->IsNPC()) + ((NPC*)target)->Brain()->AddHate(luaspell->caster, 5); + } + else if (type == CONTROL_EFFECT_TYPE_FEAR) { + if (!(luaspell->effect_bitmask & EFFECT_FLAG_FEAR)) + luaspell->effect_bitmask += EFFECT_FLAG_FEAR; + ((Entity*)target)->AddFearSpell(luaspell); + if (target->IsNPC()) + ((NPC*)target)->Brain()->AddHate(luaspell->caster, 5); + } + else if (type == CONTROL_EFFECT_TYPE_WALKUNDERWATER) { + ((Entity*)target)->AddWaterwalkSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_WATERWALK)) + luaspell->effect_bitmask += EFFECT_FLAG_WATERWALK; + } + else if (type == CONTROL_EFFECT_TYPE_JUMPUNDERWATER) { + ((Entity*)target)->AddWaterjumpSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_WATERJUMP)) + luaspell->effect_bitmask += EFFECT_FLAG_WATERJUMP; + } + else if (type == CONTROL_EFFECT_TYPE_SNARE) { + ((Entity*)target)->AddSnareSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_SNARE)) + luaspell->effect_bitmask += EFFECT_FLAG_SNARE; + if (target->IsNPC()) + ((NPC*)target)->Brain()->AddHate(luaspell->caster, 5); + } + else if (type == CONTROL_EFFECT_TYPE_FLIGHT) { + ((Entity*)target)->AddFlightSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_FLIGHT)) + luaspell->effect_bitmask += EFFECT_FLAG_FLIGHT; + } + else if (type == CONTROL_EFFECT_TYPE_GLIDE) { + ((Entity*)target)->AddGlideSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_GLIDE)) + luaspell->effect_bitmask += EFFECT_FLAG_GLIDE; + } + else if (type == CONTROL_EFFECT_TYPE_SAFEFALL) { + ((Entity*)target)->AddSafefallSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_SAFEFALL)) + luaspell->effect_bitmask += EFFECT_FLAG_SAFEFALL; + } + else + lua_interface->LogError("%s: Unhandled control effect type of %u.", lua_interface->GetScriptName(state), type); + } + else + lua_interface->LogError("%s: Error applying control effect on non entity '%s'.", lua_interface->GetScriptName(state), (target != nullptr) ? target->GetName() : "NO_TARGET"); + } + luaspell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + else if (only_add_spawn && spawn && spawn->IsEntity()) { + if (type == CONTROL_EFFECT_TYPE_MEZ) { + ((Entity*)spawn)->AddMezSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_MEZ)) + luaspell->effect_bitmask += EFFECT_FLAG_MEZ; + } + else if (type == CONTROL_EFFECT_TYPE_STIFLE) { + ((Entity*)spawn)->AddStifleSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_STIFLE)) + luaspell->effect_bitmask += EFFECT_FLAG_STIFLE; + } + else if (type == CONTROL_EFFECT_TYPE_DAZE) { + ((Entity*)spawn)->AddDazeSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_DAZE)) + luaspell->effect_bitmask += EFFECT_FLAG_DAZE; + } + else if (type == CONTROL_EFFECT_TYPE_STUN) { + ((Entity*)spawn)->AddStunSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_STUN)) + luaspell->effect_bitmask += EFFECT_FLAG_STUN; + } + else if (type == CONTROL_EFFECT_TYPE_ROOT) { + ((Entity*)spawn)->AddRootSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_ROOT)) + luaspell->effect_bitmask += EFFECT_FLAG_ROOT; + } + else if (type == CONTROL_EFFECT_TYPE_FEAR) { + ((Entity*)spawn)->AddFearSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_FEAR)) + luaspell->effect_bitmask += EFFECT_FLAG_FEAR; + } + else if (type == CONTROL_EFFECT_TYPE_WALKUNDERWATER) { + ((Entity*)spawn)->AddWaterwalkSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_WATERWALK)) + luaspell->effect_bitmask += EFFECT_FLAG_WATERWALK; + } + else if (type == CONTROL_EFFECT_TYPE_JUMPUNDERWATER) { + ((Entity*)spawn)->AddWaterjumpSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_WATERJUMP)) + luaspell->effect_bitmask += EFFECT_FLAG_WATERJUMP; + } + else if (type == CONTROL_EFFECT_TYPE_SNARE) { + ((Entity*)spawn)->AddSnareSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_SNARE)) + luaspell->effect_bitmask += EFFECT_FLAG_SNARE; + } + else if (type == CONTROL_EFFECT_TYPE_FLIGHT) { + ((Entity*)spawn)->AddFlightSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_FLIGHT)) + luaspell->effect_bitmask += EFFECT_FLAG_FLIGHT; + } + else if (type == CONTROL_EFFECT_TYPE_GLIDE) { + ((Entity*)spawn)->AddGlideSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_GLIDE)) + luaspell->effect_bitmask += EFFECT_FLAG_GLIDE; + } + else if (type == CONTROL_EFFECT_TYPE_SAFEFALL) { + ((Entity*)spawn)->AddSafefallSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_SAFEFALL)) + luaspell->effect_bitmask += EFFECT_FLAG_SAFEFALL; + } + else + lua_interface->LogError("%s: Unhandled control effect type of %u.", lua_interface->GetScriptName(state), type); + } + else + lua_interface->LogError("%s: Error applying control effect on non entity '%s'.", lua_interface->GetScriptName(state), (spawn != nullptr) ? spawn->GetName() : "NO_SPAWN"); + return 0; +} + +int EQ2Emu_lua_RemoveControlEffect(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int8 type = lua_interface->GetInt8Value(state, 2); + bool only_remove_spawn = lua_interface->GetInt8Value(state, 3) == 1; + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + if (spawn && spawn->IsEntity()) { + if (!only_remove_spawn && luaspell && luaspell->spell && luaspell->caster) { + ZoneServer* zone = luaspell->caster->GetZone(); + Spawn* target = 0; + luaspell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < luaspell->targets.size(); i++) { + target = zone->GetSpawnByID(luaspell->targets[i]); + if (target) { + if (type == CONTROL_EFFECT_TYPE_MEZ) + ((Entity*)target)->RemoveMezSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_STIFLE) + ((Entity*)target)->RemoveStifleSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_DAZE) + ((Entity*)target)->RemoveDazeSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_STUN) + ((Entity*)target)->RemoveStunSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_ROOT) + ((Entity*)target)->RemoveRootSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_FEAR) + ((Entity*)target)->RemoveFearSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_WALKUNDERWATER) + ((Entity*)target)->RemoveWaterwalkSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_JUMPUNDERWATER) + ((Entity*)target)->RemoveWaterjumpSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_SNARE) + ((Entity*)target)->RemoveSnareSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_FLIGHT) + ((Entity*)target)->RemoveFlightSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_GLIDE) + ((Entity*)target)->RemoveGlideSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_SAFEFALL) + ((Entity*)target)->RemoveGlideSpell(luaspell); + else + lua_interface->LogError("%s: Unhandled control effect type of %u.", lua_interface->GetScriptName(state), type); + } + } + luaspell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + else if (only_remove_spawn) { + if (type == CONTROL_EFFECT_TYPE_MEZ) + ((Entity*)spawn)->RemoveMezSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_STIFLE) + ((Entity*)spawn)->RemoveStifleSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_DAZE) + ((Entity*)spawn)->RemoveDazeSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_STUN) + ((Entity*)spawn)->RemoveStunSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_ROOT) + ((Entity*)spawn)->RemoveRootSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_FEAR) + ((Entity*)spawn)->RemoveFearSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_WALKUNDERWATER) + ((Entity*)spawn)->RemoveWaterwalkSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_JUMPUNDERWATER) + ((Entity*)spawn)->RemoveWaterjumpSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_SNARE) + ((Entity*)spawn)->RemoveSnareSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_FLIGHT) + ((Entity*)spawn)->RemoveFlightSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_GLIDE) + ((Entity*)spawn)->RemoveGlideSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_SAFEFALL) + ((Entity*)spawn)->RemoveSafefallSpell(luaspell); + else + lua_interface->LogError("%s: Unhandled control effect type of %u.", lua_interface->GetScriptName(state), type); + } + } + return 0; +} + +int EQ2Emu_lua_HasControlEffect(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int8 type = lua_interface->GetInt8Value(state, 2); + + bool hasEffect = false; + + if (!spawn) + lua_interface->LogError("%s: LUA HasControlEffect error: Could not find spawn.", lua_interface->GetScriptName(state)); + else if (!spawn->IsEntity()) + lua_interface->LogError("%s: LUA HasControlEffect error: spawn %s is not an entity!.", lua_interface->GetScriptName(state), spawn->GetName()); + else if (type < CONTROL_MAX_EFFECTS) + hasEffect = ((Entity*)spawn)->HasControlEffect(type); + else + lua_interface->LogError("%s: LUA HasControlEffect unhandled control effect type of %u.", lua_interface->GetScriptName(state), type); + + lua_interface->SetBooleanValue(state, hasEffect); + + return 1; +} + +int EQ2Emu_lua_GetBaseAggroRadius(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + + float distance = 0.0f; + + if (!spawn) + lua_interface->LogError("%s: LUA GetBaseAggroRadius error: Could not find spawn.", lua_interface->GetScriptName(state)); + else if (!spawn->IsNPC()) + lua_interface->LogError("%s: LUA GetBaseAggroRadius error: spawn %s is not an NPC!.", lua_interface->GetScriptName(state), spawn->GetName()); + else + distance = ((NPC*)spawn)->GetBaseAggroRadius(); + + lua_interface->SetFloatValue(state, distance); + + return 1; +} + +int EQ2Emu_lua_GetAggroRadius(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + + float distance = 0.0f; + + if (!spawn) + lua_interface->LogError("%s: LUA GetAggroRadius error: Could not find spawn.", lua_interface->GetScriptName(state)); + else if (!spawn->IsNPC()) + lua_interface->LogError("%s: LUA GetAggroRadius error: spawn %s is not an NPC!.", lua_interface->GetScriptName(state), spawn->GetName()); + else + distance = ((NPC*)spawn)->GetAggroRadius(); + + lua_interface->SetFloatValue(state, distance); + + return 1; +} + +int EQ2Emu_lua_SetAggroRadius(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + float distance = lua_interface->GetFloatValue(state, 2); + bool override = lua_interface->GetBooleanValue(state, 3); + + bool result = false; + + lua_interface->ResetFunctionStack(state); + + if (!spawn) + lua_interface->LogError("%s: LUA SetAggroRadius error: Could not find spawn.", lua_interface->GetScriptName(state)); + else if (!spawn->IsNPC()) + lua_interface->LogError("%s: LUA SetAggroRadius error: spawn %s is not an NPC!.", lua_interface->GetScriptName(state), spawn->GetName()); + else + { + ((NPC*)spawn)->SetAggroRadius(distance, override); + result = true; + } + + lua_interface->SetBooleanValue(state, result); + + return 1; +} + +int EQ2Emu_lua_SetIntBase(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int16 value = lua_interface->GetInt16Value(state, 2); + if (spawn && spawn->IsEntity()) { + ((Entity*)spawn)->GetInfoStruct()->set_intel_base(value); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + return 0; +} + +int EQ2Emu_lua_SetAgiBase(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int16 value = lua_interface->GetInt16Value(state, 2); + if (spawn && spawn->IsEntity()) { + ((Entity*)spawn)->GetInfoStruct()->set_agi_base(value); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + return 0; +} + +int EQ2Emu_lua_SetWisBase(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int16 value = lua_interface->GetInt16Value(state, 2); + if (spawn && spawn->IsEntity()) { + ((Entity*)spawn)->GetInfoStruct()->set_wis_base(value); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + return 0; +} + +int EQ2Emu_lua_SetStaBase(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int16 value = lua_interface->GetInt16Value(state, 2); + if (spawn && spawn->IsEntity()) { + ((Entity*)spawn)->GetInfoStruct()->set_sta_base(value); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + return 0; +} + +int EQ2Emu_lua_SetStrBase(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int16 value = lua_interface->GetInt16Value(state, 2); + if (spawn && spawn->IsEntity()) { + ((Entity*)spawn)->GetInfoStruct()->set_str_base(value); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + return 0; +} + +int EQ2Emu_lua_SetDeity(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int8 value = lua_interface->GetInt8Value(state, 2); + if (spawn && spawn->IsEntity()) { + ((Entity*)spawn)->SetDeity(value); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_GetDeity(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn && spawn->IsEntity()) { + int8 deity = ((Entity*)spawn)->GetDeity(); + lua_interface->SetInt32Value(state, deity); + return 1; + } + return 0; +} + + +int EQ2Emu_lua_SetInt(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + sint32 value = lua_interface->GetSInt32Value(state, 2); + if (spawn && spawn->IsEntity()) { + ((Entity*)spawn)->AddSpellBonus(luaspell, ITEM_STAT_INT, value); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + return 0; +} + +int EQ2Emu_lua_SetWis(lua_State* state) { + if (!lua_interface) + return 0; + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + Spawn* spawn = lua_interface->GetSpawn(state); + float value = lua_interface->GetFloatValue(state, 2); + if (spawn && spawn->IsEntity()) { + ((Entity*)spawn)->AddSpellBonus(luaspell, ITEM_STAT_WIS, value); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + return 0; +} +int EQ2Emu_lua_SetSta(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + float value = lua_interface->GetFloatValue(state, 2); + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + if (spawn && spawn->IsEntity()) { + ((Entity*)spawn)->AddSpellBonus(luaspell, ITEM_STAT_STA, value); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + return 0; +} +int EQ2Emu_lua_SetStr(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + float value = lua_interface->GetFloatValue(state, 2); + if (spawn && spawn->IsEntity()) { + ((Entity*)spawn)->AddSpellBonus(luaspell, ITEM_STAT_STR, value); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + return 0; +} +int EQ2Emu_lua_SetAgi(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + float value = lua_interface->GetFloatValue(state, 2); + if (spawn && spawn->IsEntity()) { + ((Entity*)spawn)->AddSpellBonus(luaspell, ITEM_STAT_AGI, value); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + return 0; +} + +int EQ2Emu_lua_GetCurrentHP(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + lua_interface->SetInt32Value(state, spawn->GetHP()); + return 1; + } + return 0; +} +int EQ2Emu_lua_GetMaxHP(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + lua_interface->SetInt32Value(state, spawn->GetTotalHP()); + return 1; + } + return 0; +} +int EQ2Emu_lua_GetMaxHPBase(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + lua_interface->SetInt32Value(state, spawn->GetTotalHPBase()); + return 1; + } + return 0; +} +int EQ2Emu_lua_GetName(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + lua_interface->SetStringValue(state, spawn->GetName()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetLevel(lua_State* state) { + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + lua_interface->SetInt32Value(state, spawn->GetLevel()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetDifficulty(lua_State* state) { + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + lua_interface->SetInt32Value(state, spawn->GetDifficulty()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetCurrentPower(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + lua_interface->SetInt32Value(state, spawn->GetPower()); + return 1; + } + return 0; +} +int EQ2Emu_lua_GetMaxPower(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + lua_interface->SetInt32Value(state, spawn->GetTotalPower()); + return 1; + } + return 0; +} +int EQ2Emu_lua_GetMaxPowerBase(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + lua_interface->SetInt32Value(state, spawn->GetTotalPowerBase()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetDistance(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + Spawn* spawn2 = lua_interface->GetSpawn(state, 2); + bool include_radius = lua_interface->GetInt8Value(state, 3) == 1; + if (spawn && spawn2) { + float distance = spawn->GetDistance(spawn2, false, include_radius); + + lua_interface->SetFloatValue(state, distance); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetX(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + lua_interface->SetFloatValue(state, spawn->GetX()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetY(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + lua_interface->SetFloatValue(state, spawn->GetY()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetZ(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + lua_interface->SetFloatValue(state, spawn->GetZ()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetHeading(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + lua_interface->SetFloatValue(state, spawn->GetHeading()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetModelType(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + lua_interface->SetInt32Value(state, spawn->GetModelType()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetSpeed(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + lua_interface->SetFloatValue(state, spawn->GetSpeed()); + return 1; + } + return 0; +} +int EQ2Emu_lua_HasMoved(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn && spawn->IsEntity()) { + lua_interface->SetBooleanValue(state, ((Entity*)spawn)->HasMoved(false)); + return 1; + } + return 0; +} +int EQ2Emu_lua_GetInt(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn && spawn->IsEntity()) { + lua_interface->SetInt32Value(state, ((Entity*)spawn)->GetInt()); + return 1; + } + return 0; +} +int EQ2Emu_lua_GetWis(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn && spawn->IsEntity()) { + lua_interface->SetInt32Value(state, ((Entity*)spawn)->GetWis()); + return 1; + } + return 0; +} +int EQ2Emu_lua_GetSta(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn && spawn->IsEntity()) { + lua_interface->SetInt32Value(state, ((Entity*)spawn)->GetSta()); + return 1; + } + return 0; +} +int EQ2Emu_lua_GetStr(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn && spawn->IsEntity()) { + lua_interface->SetInt32Value(state, ((Entity*)spawn)->GetStr()); + return 1; + } + return 0; +} +int EQ2Emu_lua_GetAgi(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn && spawn->IsEntity()) { + lua_interface->SetInt32Value(state, ((Entity*)spawn)->GetAgi()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetIntBase(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn && spawn->IsEntity()) { + lua_interface->SetInt32Value(state, ((Entity*)spawn)->GetIntBase()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetWisBase(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn && spawn->IsEntity()) { + lua_interface->SetInt32Value(state, ((Entity*)spawn)->GetWisBase()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetStaBase(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn && spawn->IsEntity()) { + lua_interface->SetInt32Value(state, ((Entity*)spawn)->GetStaBase()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetStrBase(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn && spawn->IsEntity()) { + lua_interface->SetInt32Value(state, ((Entity*)spawn)->GetStrBase()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetAgiBase(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn && spawn->IsEntity()) { + lua_interface->SetInt32Value(state, ((Entity*)spawn)->GetAgiBase()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_SetStepComplete(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + if (!player || !player->IsPlayer()) { + lua_interface->LogError("%s: LUA SetStepComplete command error: player is not valid", lua_interface->GetScriptName(state)); + return 0; + } + int32 quest_id = lua_interface->GetInt32Value(state, 2); + if (quest_id <= 0) { + lua_interface->LogError("%s: LUA SetStepComplete command error: quest_id is not valid", lua_interface->GetScriptName(state)); + return 0; + } else if ((((Player*)player)->player_quests.count(quest_id) <= 0)) { + lua_interface->LogError("%s: LUA SetStepComplete command error: player does not have quest", lua_interface->GetScriptName(state)); + return 0; + } + int32 step = lua_interface->GetInt32Value(state, 3); + if (step > 0) { + Client* client = ((Player*)player)->GetClient(); + if (client) + client->AddPendingQuestUpdate(quest_id, step); + } else { + lua_interface->LogError("%s: LUA SetStepComplete command error: step is not valid", lua_interface->GetScriptName(state)); + } + return 0; +} + +int EQ2Emu_lua_AddStepProgress(lua_State* state) { + Spawn* player = lua_interface->GetSpawn(state); + int32 quest_id = lua_interface->GetInt32Value(state, 2); + int32 step = lua_interface->GetInt32Value(state, 3); + int32 progress = lua_interface->GetInt32Value(state, 4); + if (player && player->IsPlayer() && quest_id > 0 && step > 0 && progress > 0 && (((Player*)player)->player_quests.count(quest_id) > 0)) { + Client* client = ((Player*)player)->GetClient(); + if (client) + client->AddPendingQuestUpdate(quest_id, step, progress); + } + return 0; +} + +int EQ2Emu_lua_GetTaskGroupStep(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + int32 quest_id = lua_interface->GetInt32Value(state, 2); + if (player && player->IsPlayer() && quest_id > 0) { + lua_interface->SetInt32Value(state, ((Player*)player)->GetTaskGroupStep(quest_id)); + return 1; + } + return 0; +} + +int EQ2Emu_lua_QuestStepIsComplete(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + int32 quest_id = lua_interface->GetInt32Value(state, 2); + int32 step_id = lua_interface->GetInt32Value(state, 3); + if (player && player->IsPlayer() && quest_id > 0) { + lua_interface->SetBooleanValue(state, ((Player*)player)->GetQuestStepComplete(quest_id, step_id)); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetQuestStep(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + int32 quest_id = lua_interface->GetInt32Value(state, 2); + if (player && player->IsPlayer() && quest_id > 0) { + lua_interface->SetInt32Value(state, ((Player*)player)->GetQuestStep(quest_id)); + return 1; + } + return 0; +} + +int EQ2Emu_lua_RegisterQuest(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + string name = lua_interface->GetStringValue(state, 2); + string type = lua_interface->GetStringValue(state, 3); + string zone = lua_interface->GetStringValue(state, 4); + int16 level = lua_interface->GetInt16Value(state, 5); + string description = lua_interface->GetStringValue(state, 6); + bool load = true; + if (!quest) { + lua_interface->LogError("%s: Quest not given in RegisterQuest!", lua_interface->GetScriptName(state)); + load = false; + } + if (load && name.length() == 0) { + lua_interface->LogError("%s: Name not given in RegisterQuest!", lua_interface->GetScriptName(state)); + load = false; + } + if (load && type.length() == 0) { + lua_interface->LogError("%s: Type not given in RegisterQuest for '%s'!", lua_interface->GetScriptName(state), name.c_str()); + load = false; + } + if (load && zone.length() == 0) { + lua_interface->LogError("%s: Zone not given in RegisterQuest for '%s'!", lua_interface->GetScriptName(state), name.c_str()); + load = false; + } + if (load && description.length() == 0) { + lua_interface->LogError("%s: Description not given in RegisterQuest for '%s'!", lua_interface->GetScriptName(state), name.c_str()); + load = false; + } + if (load && level == 0) { + lua_interface->LogError("%s: Level not given in RegisterQuest for '%s'!", lua_interface->GetScriptName(state), name.c_str()); + load = false; + } + if (load) + quest->RegisterQuest(name, type, zone, level, description); + return 0; +} + +int EQ2Emu_lua_SetQuestPrereqLevel(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + if (quest) { + int8 level = lua_interface->GetInt16Value(state, 2); + quest->SetPrereqLevel(level); + } + return 0; +} + +int EQ2Emu_lua_AddQuestPrereqQuest(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + if (quest) { + int32 quest_id = lua_interface->GetInt32Value(state, 2); + quest->AddPrereqQuest(quest_id); + } + return 0; +} + +int EQ2Emu_lua_AddQuestPrereqItem(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + if (quest) { + int32 item_id = lua_interface->GetInt32Value(state, 2); + int8 quantity = lua_interface->GetInt32Value(state, 3); + if (quantity == 0) + quantity = 1; + Item* master_item = master_item_list.GetItem(item_id); + if (master_item) { + Item* item = new Item(master_item); + item->details.count = quantity; + quest->AddPrereqItem(item); + } + } + return 0; +} +int EQ2Emu_lua_HasQuest(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + if(!player || !player->IsPlayer()) { + lua_interface->LogError("%s: LUA HasQuest command error: player is not valid", lua_interface->GetScriptName(state)); + return 0; + } + int32 quest_id = lua_interface->GetInt32Value(state, 2); + if (quest_id > 0) { + lua_interface->SetBooleanValue(state, (((Player*)player)->HasActiveQuest(quest_id) == TRUE)); + return 1; + } else { + lua_interface->LogError("%s: LUA HasQuest command error: quest_id is not valid", lua_interface->GetScriptName(state)); + } + return 0; +} + +int EQ2Emu_lua_QuestReturnNPC(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 spawn_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (quest && spawn_id > 0) + quest->SetQuestReturnNPC(spawn_id); + return 0; +} + +int EQ2Emu_lua_AddTimer(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + int32 time = lua_interface->GetInt32Value(state, 2); + string function = lua_interface->GetStringValue(state, 3); + int32 max_count = lua_interface->GetInt32Value(state, 4); + Spawn* player = lua_interface->GetSpawn(state, 5); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA AddTimer command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (time <= 0) { + lua_interface->LogError("%s: LUA AddTimer command error: time is not set", lua_interface->GetScriptName(state)); + return 0; + } + + if (function.length() == 0) { + lua_interface->LogError("%s: LUA AddTimer command error: function is not set", lua_interface->GetScriptName(state)); + return 0; + } + + SpawnScriptTimer* timer = new SpawnScriptTimer; + if ( time < 10) + time = 10; + + timer->timer = Timer::GetCurrentTime2() + time; + timer->function = function; + timer->spawn = spawn->GetID(); + timer->player = player ? player->GetID() : 0; + if (max_count == 0) + max_count = 1; + timer->max_count = max_count; + timer->current_count = 0; + spawn->GetZone()->AddSpawnScriptTimer(timer); + + return 0; +} + +int EQ2Emu_lua_StopTimer(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + string function = lua_interface->GetStringValue(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA StopTimer command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if(!spawn->GetZone()) { + lua_interface->LogError("%s: LUA StopTimer command error: spawn has no zone to check spawn timers", lua_interface->GetScriptName(state)); + return 0; + } + spawn->GetZone()->StopSpawnScriptTimer(spawn, function); + + return 0; +} + +int EQ2Emu_lua_GetQuest(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + int32 quest_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer() && quest_id > 0) { + lua_interface->SetQuestValue(state, ((Player*)player)->player_quests[quest_id]); + return 1; + } + return 0; +} + +int EQ2Emu_lua_QuestIsComplete(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + int32 quest_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer() && quest_id > 0 && (((Player*)player)->player_quests.count(quest_id) > 0)) { + Quest* quest = ((Player*)player)->player_quests[quest_id]; + if (quest) + lua_interface->SetBooleanValue(state, quest->GetCompleted()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_HasCompletedQuest(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + int32 quest_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer() && quest_id > 0) { + lua_interface->SetBooleanValue(state, (((Player*)player)->HasQuestBeenCompleted(quest_id) != 0)); + return 1; + } + return 0; +} + +int EQ2Emu_lua_ProvidesQuest(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* npc = lua_interface->GetSpawn(state); + int32 quest_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (npc && !npc->IsPlayer() && quest_id > 0) + npc->AddProvidedQuest(quest_id); + return 0; +} + +int EQ2Emu_lua_OfferQuest(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* npc = lua_interface->GetSpawn(state); + Spawn* player = lua_interface->GetSpawn(state, 2); + int32 quest_id = lua_interface->GetInt32Value(state, 3); + bool forced = lua_interface->GetBooleanValue(state, 4); + lua_interface->ResetFunctionStack(state); + + /* NPC is allowed to be null */ + if (player && player->IsPlayer() && quest_id > 0) { + Quest* master_quest = master_quest_list.GetQuest(quest_id, false); + if (master_quest) { + Client* client = ((Player*)player)->GetClient(); + if (!client) { + lua_interface->LogError("%s: LUA OfferQuest command error: client is not set", lua_interface->GetScriptName(state)); + } + Quest* quest = new Quest(master_quest); + if (!quest) { + lua_interface->LogError("%s: LUA OfferQuest command error: new Quest() failed.", lua_interface->GetScriptName(state)); + } + if (client && quest) { + if (npc) + quest->SetQuestGiver(npc->GetDatabaseID()); + else + quest->SetQuestGiver(0); + client->AddPendingQuest(quest, forced); + } + } + else { + lua_interface->LogError("%s: LUA OfferQuest command error: failed to get quest %d", lua_interface->GetScriptName(state), quest_id); + } + } + else { + lua_interface->LogError("%s: LUA OfferQuest command error: player is not set or bad quest id %p %d", lua_interface->GetScriptName(state), player, quest_id); + } + return 0; +} + +int EQ2Emu_lua_AddQuestPrereqClass(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int8 class_id = lua_interface->GetInt8Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (quest) { + quest->AddPrereqClass(class_id); + } + return 0; +} + +int EQ2Emu_lua_AddQuestPrereqRace(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int8 race = lua_interface->GetInt8Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (quest) { + quest->AddPrereqRace(race); + } + return 0; +} + +int EQ2Emu_lua_AddQuestPrereqModelType(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int16 model_type = lua_interface->GetInt16Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (quest) { + quest->AddPrereqModelType(model_type); + } + return 0; +} + +int EQ2Emu_lua_AddQuestPrereqTradeskillLevel(lua_State* state) { + if (!lua_interface) + return 0; + + Quest* quest = lua_interface->GetQuest(state); + int8 level = lua_interface->GetInt8Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!quest) { + lua_interface->LogError("%s: LUA AddQuestPrereqTradeskillLevel command error: quest is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + quest->SetPrereqTSLevel(level); + return 0; +} + +int EQ2Emu_lua_AddQuestPrereqTradeskillClass(lua_State* state) { + if (!lua_interface) + return 0; + + Quest* quest = lua_interface->GetQuest(state); + int8 class_id = lua_interface->GetInt8Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (!quest) { + lua_interface->LogError("%s: LUA AddQuestPrereqTradeskillClass command error: quest is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + quest->AddPrereqTradeskillClass(class_id); + return 0; +} + +int EQ2Emu_lua_AddQuestPrereqFaction(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 faction_id = lua_interface->GetInt32Value(state, 2); + sint32 min = lua_interface->GetSInt32Value(state, 3); + sint32 max = lua_interface->GetSInt32Value(state, 4); + lua_interface->ResetFunctionStack(state); + if (quest) { + quest->AddPrereqFaction(faction_id, min, max); + } + return 0; +} + +int EQ2Emu_lua_AddQuestSelectableRewardItem(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 item_id = lua_interface->GetInt32Value(state, 2); + int8 quantity = lua_interface->GetInt8Value(state, 3); + lua_interface->ResetFunctionStack(state); + if (quest) { + if (quantity == 0) + quantity = 1; + Item* master_item = master_item_list.GetItem(item_id); + if (master_item) { + Item* item = new Item(master_item); + item->details.count = quantity; + quest->AddSelectableRewardItem(item); + } + } + return 0; +} + +int EQ2Emu_lua_HasQuestRewardItem(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + if (quest) { + int32 item_id = lua_interface->GetInt32Value(state, 2); + vector* items = quest->GetRewardItems(); + if (items) { + vector::iterator itr; + for (itr = items->begin(); itr != items->end(); itr++) { + if (*itr && (*itr)->details.item_id == item_id) { + lua_interface->SetBooleanValue(state, true); + return 1; + } + } + } + } + lua_interface->SetBooleanValue(state, false); + return 1; +} + +int EQ2Emu_lua_AddQuestRewardItem(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 item_id = lua_interface->GetInt32Value(state, 2); + int8 quantity = lua_interface->GetInt32Value(state, 3); + lua_interface->ResetFunctionStack(state); + if (quest) { + if (quantity == 0) + quantity = 1; + Item* master_item = master_item_list.GetItem(item_id); + if (master_item) { + Item* item = new Item(master_item); + item->details.count = quantity; + quest->AddRewardItem(item); + } + } + return 0; +} + +int EQ2Emu_lua_AddQuestRewardCoin(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 copper = lua_interface->GetInt32Value(state, 2); + int32 silver = lua_interface->GetInt32Value(state, 3); + int32 gold = lua_interface->GetInt32Value(state, 4); + int32 plat = lua_interface->GetInt32Value(state, 5); + lua_interface->ResetFunctionStack(state); + if (quest) { + quest->AddRewardCoins(copper, silver, gold, plat); + } + return 0; +} + +int EQ2Emu_lua_AddQuestRewardFaction(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 faction_id = lua_interface->GetInt32Value(state, 2); + sint32 amount = lua_interface->GetSInt32Value(state, 3); + lua_interface->ResetFunctionStack(state); + if (quest && faction_id > 0 && amount != 0) + quest->AddRewardFaction(faction_id, amount); + return 0; +} + +int EQ2Emu_lua_SetQuestRewardStatus(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 status = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (quest) { + quest->SetRewardStatus(status); + } + return 0; +} + +int EQ2Emu_lua_SetStatusTmpReward(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 status = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (quest) { + quest->SetStatusTmpReward(status); + } + return 0; +} + + +int EQ2Emu_lua_SetCoinTmpReward(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int64 coins = lua_interface->GetInt64Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (quest) { + quest->SetCoinTmpReward(coins); + } + return 0; +} + + +int EQ2Emu_lua_SetQuestRewardComment(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + string comment = lua_interface->GetStringValue(state, 2); + lua_interface->ResetFunctionStack(state); + if (quest) { + quest->SetRewardComment(comment); + } + return 0; +} + +int EQ2Emu_lua_SetQuestRewardExp(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 exp = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (quest) { + quest->SetRewardXP(exp); + } + return 0; +} + +int EQ2Emu_lua_AddQuestStep(lua_State* state) { + Quest* quest = lua_interface->GetQuest(state); + int32 step = lua_interface->GetInt32Value(state, 2); + string description = lua_interface->GetStringValue(state, 3); + int32 quantity = lua_interface->GetInt32Value(state, 4); + float percentage = lua_interface->GetFloatValue(state, 5); + string str_taskgroup = lua_interface->GetStringValue(state, 6); + int16 icon = lua_interface->GetInt16Value(state, 7); + int32 usableitemid = lua_interface->GetInt32Value(state, 8); + if (quest) { + const char* taskgroup = 0; + if (str_taskgroup.length() > 0) + taskgroup = str_taskgroup.c_str(); + + + int32 id = 0; + vector* ids = 0; + int i = 0; + while ((id = lua_interface->GetInt32Value(state, 9 + i))) { + if (ids == 0) + ids = new vector; + ids->push_back(id); + i++; + } + + QuestStep* quest_step = quest->AddQuestStep(step, QUEST_STEP_TYPE_NORMAL, description, ids, quantity, taskgroup, 0, 0, percentage, usableitemid); + if (quest_step && icon && quantity > 0) + quest_step->SetIcon(icon); + if (quest->GetPlayer() && ((Player*)quest->GetPlayer())->GetClient()) { + Client* client = ((Player*)quest->GetPlayer())->GetClient(); + quest->GetPlayer()->GetZone()->SendQuestUpdates(client); + } + } + lua_interface->ResetFunctionStack(state); + return 0; +} +int EQ2Emu_lua_AddQuestStepKillLogic(lua_State* state, int8 type) +{ + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 step = lua_interface->GetInt32Value(state, 2); + string description = lua_interface->GetStringValue(state, 3); + int32 quantity = lua_interface->GetInt32Value(state, 4); + float percentage = lua_interface->GetFloatValue(state, 5); + string str_taskgroup = lua_interface->GetStringValue(state, 6); + int16 icon = lua_interface->GetInt16Value(state, 7); + if (quest) { + const char* taskgroup = 0; + if (str_taskgroup.length() > 0) + taskgroup = str_taskgroup.c_str(); + + int32 id = 0; + vector* ids = 0; + int i = 0; + while ((id = lua_interface->GetInt32Value(state, 8 + i))) { + if (ids == 0) + ids = new vector; + ids->push_back(id); + i++; + } + QuestStep* quest_step = quest->AddQuestStep(step, type, description, ids, quantity, taskgroup, 0, 0, percentage, 0); + if (quest_step && icon > 0 && quantity > 0) + quest_step->SetIcon(icon); + if (quest->GetPlayer() && ((Player*)quest->GetPlayer())->GetClient()) { + Client* client = ((Player*)quest->GetPlayer())->GetClient(); + quest->GetPlayer()->GetZone()->SendQuestUpdates(client); + } + safe_delete(ids); + } + lua_interface->ResetFunctionStack(state); + return 0; +} +int EQ2Emu_lua_AddQuestStepKill(lua_State* state) { + return EQ2Emu_lua_AddQuestStepKillLogic(state, QUEST_STEP_TYPE_KILL); +} + +int EQ2Emu_lua_AddQuestStepKillByRace(lua_State* state) { + return EQ2Emu_lua_AddQuestStepKillLogic(state, QUEST_STEP_TYPE_KILL_RACE_REQ); +} + +int EQ2Emu_lua_AddQuestStepChat(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 step = lua_interface->GetInt32Value(state, 2); + string description = lua_interface->GetStringValue(state, 3); + int32 quantity = lua_interface->GetInt32Value(state, 4); + string str_taskgroup = lua_interface->GetStringValue(state, 5); + int16 icon = lua_interface->GetInt16Value(state, 6); + if (quest) { + const char* taskgroup = 0; + if (str_taskgroup.length() > 0) + taskgroup = str_taskgroup.c_str(); + int32 npc_id = 0; + vector* ids = 0; + int i = 0; + while ((npc_id = lua_interface->GetInt32Value(state, 7 + i))) { + if (ids == 0) + ids = new vector; + ids->push_back(npc_id); + i++; + } + QuestStep* quest_step = quest->AddQuestStep(step, QUEST_STEP_TYPE_CHAT, description, ids, quantity, taskgroup); + if (quest_step && icon > 0) + quest_step->SetIcon(icon); + if (quest->GetPlayer() && ((Player*)quest->GetPlayer())->GetClient()) { + Client* client = ((Player*)quest->GetPlayer())->GetClient(); + if(client) + quest->GetPlayer()->GetZone()->SendQuestUpdates(client); + } + safe_delete(ids); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_AddQuestStepObtainItem(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 step = lua_interface->GetInt32Value(state, 2); + string description = lua_interface->GetStringValue(state, 3); + int32 quantity = lua_interface->GetInt32Value(state, 4); + float percentage = lua_interface->GetFloatValue(state, 5); + string str_taskgroup = lua_interface->GetStringValue(state, 6); + int16 icon = lua_interface->GetInt16Value(state, 7); + if (quest) { + const char* taskgroup = 0; + if (str_taskgroup.length() > 0) + taskgroup = str_taskgroup.c_str(); + int32 item_id = 0; + vector* ids = 0; + int i = 0; + while ((item_id = lua_interface->GetInt32Value(state, 8 + i))) { + if (ids == 0) + ids = new vector; + ids->push_back(item_id); + i++; + } + QuestStep* quest_step = quest->AddQuestStep(step, QUEST_STEP_TYPE_OBTAIN_ITEM, description, ids, quantity, taskgroup, 0, 0, percentage, 0); + if (quest_step && icon > 0 && quantity > 0) + quest_step->SetIcon(icon); + if (quest->GetPlayer() && ((Player*)quest->GetPlayer())->GetClient()) { + Client* client = ((Player*)quest->GetPlayer())->GetClient(); + quest->GetPlayer()->GetZone()->SendQuestUpdates(client); + } + safe_delete(ids); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_AddQuestStepZoneLoc(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 step = lua_interface->GetInt32Value(state, 2); + string description = lua_interface->GetStringValue(state, 3); + float max_variation = lua_interface->GetFloatValue(state, 4); + string str_taskgroup = lua_interface->GetStringValue(state, 5); + int16 icon = lua_interface->GetInt16Value(state, 6); + if (quest) { + const char* taskgroup = 0; + if (str_taskgroup.length() > 0) + taskgroup = str_taskgroup.c_str(); + vector* locations = 0; + int8 i = 7; + int8 num_args = (int8)lua_interface->GetNumberOfArgs(state); + while (true) { + Location loc; + loc.x = lua_interface->GetFloatValue(state, i); + loc.y = lua_interface->GetFloatValue(state, i + 1); + loc.z = lua_interface->GetFloatValue(state, i + 2); + loc.zone_id = lua_interface->GetInt32Value(state, i + 3); + + if (loc.x == 0 && loc.y == 0 && loc.z == 0) + break; + if (locations == 0) + locations = new vector; + locations->push_back(loc); + i += 4; + } + QuestStep* quest_step = quest->AddQuestStep(step, QUEST_STEP_TYPE_LOCATION, description, 0, 1, taskgroup, locations, max_variation); + safe_delete(locations); // gets duplicated into new table in QuestStep constructor + if (quest_step && icon > 0) + quest_step->SetIcon(icon); + if (quest->GetPlayer() && ((Player*)quest->GetPlayer())->GetClient()) { + Client* client = ((Player*)quest->GetPlayer())->GetClient(); + quest->GetPlayer()->GetZone()->SendQuestUpdates(client); + } + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_AddQuestStepLocation(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 step = lua_interface->GetInt32Value(state, 2); + string description = lua_interface->GetStringValue(state, 3); + float max_variation = lua_interface->GetFloatValue(state, 4); + string str_taskgroup = lua_interface->GetStringValue(state, 5); + int16 icon = lua_interface->GetInt16Value(state, 6); + if (quest) { + const char* taskgroup = 0; + if (str_taskgroup.length() > 0) + taskgroup = str_taskgroup.c_str(); + vector* locations = 0; + int8 i = 7; + int8 num_args = (int8)lua_interface->GetNumberOfArgs(state); + while (true) { + Location loc; + loc.x = lua_interface->GetFloatValue(state, i); + loc.y = lua_interface->GetFloatValue(state, i + 1); + loc.z = lua_interface->GetFloatValue(state, i + 2); + loc.zone_id = 0; + + if (loc.x == 0 && loc.y == 0 && loc.z == 0) + break; + if (locations == 0) + locations = new vector; + locations->push_back(loc); + i += 3; + } + QuestStep* quest_step = quest->AddQuestStep(step, QUEST_STEP_TYPE_LOCATION, description, 0, 1, taskgroup, locations, max_variation); + safe_delete(locations); // gets duplicated into new table in QuestStep constructor + if (quest_step && icon > 0) + quest_step->SetIcon(icon); + if (quest->GetPlayer() && ((Player*)quest->GetPlayer())->GetClient()) { + Client* client = ((Player*)quest->GetPlayer())->GetClient(); + quest->GetPlayer()->GetZone()->SendQuestUpdates(client); + } + } + lua_interface->ResetFunctionStack(state); + return 0; +} +int EQ2Emu_lua_AddQuestUsableItem(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 step = lua_interface->GetInt32Value(state, 2); + string description = lua_interface->GetStringValue(state, 3); + float max_variation = lua_interface->GetFloatValue(state, 4); + string str_taskgroup = lua_interface->GetStringValue(state, 5); + int16 icon = lua_interface->GetInt16Value(state, 6); + if (quest) { + const char* taskgroup = 0; + if (str_taskgroup.length() > 0) + taskgroup = str_taskgroup.c_str(); + vector* locations = 0; + int i = 7; + while (true) { + Location loc; + loc.x = lua_interface->GetFloatValue(state, i); + loc.y = lua_interface->GetFloatValue(state, i + 1); + loc.z = lua_interface->GetFloatValue(state, i + 2); + if (loc.x == 0 && loc.y == 0 && loc.z == 0) + break; + if (locations == 0) + locations = new vector; + locations->push_back(loc); + i += 3; + } + QuestStep* quest_step = quest->AddQuestStep(step, QUEST_STEP_TYPE_LOCATION, description, 0, 1, taskgroup, locations, max_variation); + safe_delete(locations); // gets duplicated into new table in QuestStep constructor + if (quest_step && icon > 0) + quest_step->SetIcon(icon); + if (quest->GetPlayer() && ((Player*)quest->GetPlayer())->GetClient()) { + Client* client = ((Player*)quest->GetPlayer())->GetClient(); + quest->GetPlayer()->GetZone()->SendQuestUpdates(client); + } + } + lua_interface->ResetFunctionStack(state); + return 0; +} +int EQ2Emu_lua_AddQuestStepSpell(lua_State* state) { + Quest* quest = lua_interface->GetQuest(state); + int32 step = lua_interface->GetInt32Value(state, 2); + string description = lua_interface->GetStringValue(state, 3); + int32 quantity = lua_interface->GetInt32Value(state, 4); + float percentage = lua_interface->GetFloatValue(state, 5); + string str_taskgroup = lua_interface->GetStringValue(state, 6); + int16 icon = lua_interface->GetInt16Value(state, 7); + if (quest) { + const char* taskgroup = 0; + if (str_taskgroup.length() > 0) + taskgroup = str_taskgroup.c_str(); + int32 spell_id = 0; + vector* ids = 0; + int i = 0; + while ((spell_id = lua_interface->GetInt32Value(state, 8 + i))) { + if (ids == 0) + ids = new vector; + ids->push_back(spell_id); + i++; + } + QuestStep* quest_step = quest->AddQuestStep(step, QUEST_STEP_TYPE_SPELL, description, ids, quantity, taskgroup, 0, 0, percentage, 0); + if (quest_step && icon > 0 && quantity > 0) + quest_step->SetIcon(icon); + if (quest->GetPlayer() && ((Player*)quest->GetPlayer())->GetClient()) { + Client* client = ((Player*)quest->GetPlayer())->GetClient(); + quest->GetPlayer()->GetZone()->SendQuestUpdates(client); + } + safe_delete(ids); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_AddQuestStepCraft(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 step = lua_interface->GetInt32Value(state, 2); + string description = lua_interface->GetStringValue(state, 3); + int32 quantity = lua_interface->GetInt32Value(state, 4); + float percentage = lua_interface->GetFloatValue(state, 5); + string str_taskgroup = lua_interface->GetStringValue(state, 6); + int16 icon = lua_interface->GetInt16Value(state, 7); + if (quest) { + const char* taskgroup = 0; + if (str_taskgroup.length() > 0) + taskgroup = str_taskgroup.c_str(); + int32 item_id = 0; + vector* ids = 0; + int i = 0; + while ((item_id = lua_interface->GetInt32Value(state, 8 + i))) { + if (ids == 0) + ids = new vector; + ids->push_back(item_id); + i++; + } + QuestStep* quest_step = quest->AddQuestStep(step, QUEST_STEP_TYPE_CRAFT, description, ids, quantity, taskgroup, 0, 0, percentage, 0); + if (quest_step && icon > 0 && quantity > 0) + quest_step->SetIcon(icon); + if (quest->GetPlayer() && ((Player*)quest->GetPlayer())->GetClient()) { + Client* client = ((Player*)quest->GetPlayer())->GetClient(); + quest->GetPlayer()->GetZone()->SendQuestUpdates(client); + } + safe_delete(ids); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_AddQuestStepHarvest(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 step = lua_interface->GetInt32Value(state, 2); + string description = lua_interface->GetStringValue(state, 3); + int32 quantity = lua_interface->GetInt32Value(state, 4); + float percentage = lua_interface->GetFloatValue(state, 5); + string str_taskgroup = lua_interface->GetStringValue(state, 6); + int16 icon = lua_interface->GetInt16Value(state, 7); + if (quest) { + const char* taskgroup = 0; + if (str_taskgroup.length() > 0) + taskgroup = str_taskgroup.c_str(); + int32 item_id = 0; + vector* ids = 0; + int i = 0; + while ((item_id = lua_interface->GetInt32Value(state, 8 + i))) { + if (ids == 0) + ids = new vector; + ids->push_back(item_id); + i++; + } + QuestStep* quest_step = quest->AddQuestStep(step, QUEST_STEP_TYPE_HARVEST, description, ids, quantity, taskgroup, 0, 0, percentage, 0); + if (quest_step && icon > 0 && quantity > 0) + quest_step->SetIcon(icon); + if (quest->GetPlayer() && ((Player*)quest->GetPlayer())->GetClient()) { + Client* client = ((Player*)quest->GetPlayer())->GetClient(); + quest->GetPlayer()->GetZone()->SendQuestUpdates(client); + } + safe_delete(ids); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_SetQuestCompleteAction(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + string action = lua_interface->GetStringValue(state, 2); + lua_interface->ResetFunctionStack(state); + if (quest) { + if (action.length() > 0) + quest->SetCompleteAction(action); + } + return 0; +} + +int EQ2Emu_lua_AddQuestStepCompleteAction(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 step = lua_interface->GetInt32Value(state, 2); + string action = lua_interface->GetStringValue(state, 3); + lua_interface->ResetFunctionStack(state); + if (quest) { + if (step > 0 && action.length() > 0) + quest->AddCompleteAction(step, action); + } + return 0; +} + +int EQ2Emu_lua_AddQuestStepProgressAction(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 step = lua_interface->GetInt32Value(state, 2); + string action = lua_interface->GetStringValue(state, 3); + lua_interface->ResetFunctionStack(state); + if (quest) { + if (step > 0 && action.length() > 0) + quest->AddProgressAction(step, action); + } + return 0; +} + +int EQ2Emu_lua_UpdateQuestDescription(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + string description = lua_interface->GetStringValue(state, 2); + lua_interface->ResetFunctionStack(state); + if (quest && description.length() > 0) + quest->SetDescription(description); + return 0; +} + +int EQ2Emu_lua_SetCompletedDescription(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + string description = lua_interface->GetStringValue(state, 2); + lua_interface->ResetFunctionStack(state); + if (quest && description.length() > 0) + quest->SetCompletedDescription(description); + return 0; +} + +int EQ2Emu_lua_UpdateQuestTaskGroupDescription(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 step = lua_interface->GetInt32Value(state, 2); + string description = lua_interface->GetStringValue(state, 3); + bool display_bullets = (lua_interface->GetInt8Value(state, 4) == 1); + lua_interface->ResetFunctionStack(state); + if (quest && step > 0 && description.length() > 0) { + quest->SetTaskGroupDescription(step, description, display_bullets); + } + return 0; +} + +int EQ2Emu_lua_UpdateQuestStepDescription(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 step = lua_interface->GetInt32Value(state, 2); + string description = lua_interface->GetStringValue(state, 3); + lua_interface->ResetFunctionStack(state); + if (quest && step > 0 && description.length() > 0) { + quest->SetStepDescription(step, description); + } + return 0; +} + +int EQ2Emu_lua_UpdateQuestZone(lua_State* state) { + Quest* quest = lua_interface->GetQuest(state); + string zone = lua_interface->GetStringValue(state, 2); + lua_interface->ResetFunctionStack(state); + if (quest && zone.length() > 0) + quest->SetZone(zone); + return 0; +} + +int EQ2Emu_lua_GiveQuestReward(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + Spawn* spawn = lua_interface->GetSpawn(state, 2); + + lua_interface->ResetFunctionStack(state); + if (quest && spawn) { + if (spawn->IsPlayer()) { + Client* client = ((Player*)spawn)->GetClient(); + if (client) { + client->AddPendingQuestReward(quest); + } + } + } + return 0; +} + +int EQ2Emu_lua_Harvest(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + Spawn* node = lua_interface->GetSpawn(state, 2); + if (player && node && player->IsPlayer() && node->IsGroundSpawn()) { + Client* client = ((Player*)player)->GetClient(); + if (client) { + ((GroundSpawn*)node)->ProcessHarvest(client); + if (((GroundSpawn*)node)->GetNumberHarvests() == 0) { + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + player->GetZone()->RemoveSpawn(node, true, true, true, true, (spell != nullptr) ? false : true); + } + } + } + else if (player && player->IsPlayer()) { + Client* client = ((Player*)player)->GetClient(); + if (client) + client->Message(CHANNEL_COLOR_RED, "Invalid target for this spell."); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_Bind(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 zone_id = lua_interface->GetInt32Value(state, 2); + float x = lua_interface->GetFloatValue(state, 3); + float y = lua_interface->GetFloatValue(state, 4); + float z = lua_interface->GetFloatValue(state, 5); + float h = lua_interface->GetFloatValue(state, 6); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA Bind command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsPlayer()) { + lua_interface->LogError("%s: LUA Bind command error: spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + if (zone_id == 0) { + Client* client = ((Player*)spawn)->GetClient(); + if (!client) { + lua_interface->LogError("%s: LUA Bind command error: unable to get client from spawn", lua_interface->GetScriptName(state)); + return 0; + } + + if (!client->Bind()) + client->SimpleMessage(CHANNEL_COLOR_RED, "Unable to set bind point."); + } + else { + Player* player = (Player*)spawn; + player->GetPlayerInfo()->SetBindZone(zone_id); + player->GetPlayerInfo()->SetBindX(x); + player->GetPlayerInfo()->SetBindY(y); + player->GetPlayerInfo()->SetBindZ(z); + player->GetPlayerInfo()->SetBindHeading(h); + } + + return 0; +} + +int EQ2Emu_lua_Gate(lua_State* state) { + if (!lua_interface) + return 0; + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + if (spawn->IsPlayer()) { + Client* client = ((Player*)spawn)->GetClient(); + if (client) { + if (!client->Gate((spell != nullptr) ? true : false)) + client->SimpleMessage(CHANNEL_COLOR_RED, "Unable to gate."); + } + } + } + return 0; +} + +int EQ2Emu_lua_IsBindAllowed(lua_State* state) { + if (!lua_interface) + return 0; + bool ret = false; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + if (spawn->IsPlayer()) { + Client* client = ((Player*)spawn)->GetClient(); + if (client) + ret = client->BindAllowed(); + } + } + lua_interface->SetBooleanValue(state, ret); + return 1; +} + +int EQ2Emu_lua_IsGateAllowed(lua_State* state) { + if (!lua_interface) + return 0; + bool ret = false; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + if (spawn->IsPlayer()) { + Client* client = ((Player*)spawn)->GetClient(); + ZoneServer* zone = lua_interface->GetZone(state); + if (client && zone){ + ret = zone->GetCanGate(); + } + } + } + lua_interface->SetBooleanValue(state, ret); + return 1; +} + +int EQ2Emu_lua_IsAlive(lua_State* state) { + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + lua_interface->SetBooleanValue(state, spawn->Alive()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_IsInCombat(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn && spawn->IsEntity()) { + lua_interface->SetBooleanValue(state, ((Entity*)spawn)->EngagedInCombat()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_SendMessage(lua_State* state) { + Spawn* spawn = lua_interface->GetSpawn(state); + string message = lua_interface->GetStringValue(state, 2); + string color_str = lua_interface->GetStringValue(state, 3); + lua_interface->ResetFunctionStack(state); + int8 color = CHANNEL_NARRATIVE; + if (spawn && spawn->IsPlayer() && message.length() > 0) { + Client* client = ((Player*)spawn)->GetClient(); + if (client) { + if (color_str.length() > 0) { + // leave for backwards compat, but all future should just use the number + if (strncasecmp(color_str.c_str(), "red", 3) == 0) + color = CHANNEL_COLOR_RED; + else if (strncasecmp(color_str.c_str(), "yellow", 6) == 0) + color = CHANNEL_COLOR_YELLOW; + else + { + // use a number to specify the channel as per Commands/Commands.h defines + color = (int8)atoul(color_str.c_str()); + } + } + client->SimpleMessage(color, message.c_str()); + } + } + return 0; +} + +int EQ2Emu_lua_SendPopUpMessage(lua_State* state) { + Spawn* spawn = lua_interface->GetSpawn(state); + string message = lua_interface->GetStringValue(state, 2); + int8 red = lua_interface->GetInt8Value(state, 3); + int8 green = lua_interface->GetInt8Value(state, 4); + int8 blue = lua_interface->GetInt8Value(state, 5); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA SendPopUpMessage command error: Spawn is not valid.", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsPlayer()) { + lua_interface->LogError("%s: LUA SendPopUpMessage command error: Spawn is not a player.", lua_interface->GetScriptName(state)); + return 0; + } + + int32 words = ::CountWordsInString(message.c_str()); + if (words < 5) + words = 5; + Client* client = ((Player*)spawn)->GetClient(); + if (client) + client->SendPopupMessage(10, message.c_str(), "ui_harvested_normal", words, red, green, blue); + return 0; +} + +int EQ2Emu_lua_SetServerControlFlag(lua_State* state) { + Spawn* spawn = lua_interface->GetSpawn(state); + int8 param = lua_interface->GetInt8Value(state, 2); + int8 param_value = lua_interface->GetInt8Value(state, 3); + int8 value = lua_interface->GetInt8Value(state, 4); + lua_interface->ResetFunctionStack(state); + if (spawn && spawn->IsPlayer() && (param >= 1 && param <= 5)) { + Client* client = ((Player*)spawn)->GetClient(); + if (client) { + PacketStruct* packet = configReader.getStruct("WS_ServerControlFlags", client->GetVersion()); + switch (param) { + case 1: { + packet->setDataByName("parameter1", param_value); + break; + } + case 2: { + packet->setDataByName("parameter2", param_value); + break; + } + case 3: { + packet->setDataByName("parameter3", param_value); + break; + } + case 4: { + packet->setDataByName("parameter4", param_value); + break; + } + case 5: { + packet->setDataByName("parameter5", param_value); + break; + } + } + packet->setDataByName("value", value); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + return 0; +} + +int EQ2Emu_lua_ToggleTracking(lua_State* state) { + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn && spawn->IsPlayer()) { + if (((Player*)spawn)->GetIsTracking()) + spawn->GetZone()->AddPlayerTracking((Player*)spawn); + else + spawn->GetZone()->RemovePlayerTracking((Player*)spawn, TRACKING_STOP); + } + return 0; +} + +int EQ2Emu_lua_AddPrimaryEntityCommand(lua_State* state) { + Spawn* player = lua_interface->GetSpawn(state); + Spawn* spawn = lua_interface->GetSpawn(state, 2); + string name = lua_interface->GetStringValue(state, 3); + float distance = lua_interface->GetFloatValue(state, 4); + string command = lua_interface->GetStringValue(state, 5); + string error_text = lua_interface->GetStringValue(state, 6); + int16 cast_time = lua_interface->GetInt16Value(state, 7); + int32 spell_visual = lua_interface->GetInt32Value(state, 8); + bool denyListDefault = (lua_interface->GetInt8Value(state, 9) == 1); + lua_interface->ResetFunctionStack(state); + if (spawn) { + if (distance == 0) + distance = 10.0f; + if (command.length() == 0) + command = name; + if (command.length() < 1 && name.length() < 1) + { + // have to run this first to send a 'blank' default command, then remove all commands from the list + spawn->GetZone()->SendUpdateDefaultCommand(spawn, command.c_str(), distance); + spawn->RemovePrimaryCommands(); + } + else + { + spawn->AddPrimaryEntityCommand(name.c_str(), distance, command.c_str(), error_text.c_str(), cast_time, spell_visual, denyListDefault, (player && player->IsPlayer()) ? (Player*)player : NULL); + } + } + return 0; +} + +int EQ2Emu_lua_HasSpell(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + int32 spellid = lua_interface->GetInt32Value(state, 2); + int16 tier = lua_interface->GetInt16Value(state, 3); + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer()) { + lua_interface->SetBooleanValue(state, ((Player*)player)->HasSpell(spellid, tier, true)); + return 1; + } + return 0; +} + +int EQ2Emu_lua_AddSpellBookEntry(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + int32 spellid = lua_interface->GetInt32Value(state, 2); + int16 tier = lua_interface->GetInt16Value(state, 3); + int8 num_args = (int8)lua_interface->GetNumberOfArgs(state); + bool add_silently = lua_interface->GetBooleanValue(state, 4); + bool add_to_hotbar = true; + if (num_args > 4) { + add_to_hotbar = lua_interface->GetBooleanValue(state, 5); + } + lua_interface->ResetFunctionStack(state); + Spell* spell = master_spell_list.GetSpell(spellid, tier); + if (player && spell && player->IsPlayer()) { + Client* client = player->GetClient(); + if (client) { + if (!client->GetPlayer()->HasSpell(spellid, tier - 1, true)) + { + Spell* spell = master_spell_list.GetSpell(spellid, tier); + client->GetPlayer()->AddSpellBookEntry(spellid, 1, client->GetPlayer()->GetFreeSpellBookSlot(spell->GetSpellData()->spell_book_type), spell->GetSpellData()->spell_book_type, spell->GetSpellData()->linked_timer, true); + client->GetPlayer()->UnlockSpell(spell); + client->SendSpellUpdate(spell, add_silently, add_to_hotbar); + } + else + { + Spell* spell = master_spell_list.GetSpell(spellid, tier); + int8 old_slot = client->GetPlayer()->GetSpellSlot(spell->GetSpellID()); + client->GetPlayer()->RemoveSpellBookEntry(spell->GetSpellID()); + client->GetPlayer()->AddSpellBookEntry(spell->GetSpellID(), spell->GetSpellTier(), old_slot, spell->GetSpellData()->spell_book_type, spell->GetSpellData()->linked_timer, true); + client->GetPlayer()->UnlockSpell(spell); + client->SendSpellUpdate(spell, add_silently, add_to_hotbar); + } + + + + //if (client ) { + // ((Player*)player)->AddSpellBookEntry(spell->GetSpellID(), spell->GetSpellTier(), ((Player*)player)->GetFreeSpellBookSlot(spell->GetSpellData()->spell_book_type), spell->GetSpellData()->spell_book_type, spell->GetSpellData()->linked_timer, true); + EQ2Packet* outapp = ((Player*)player)->GetSpellBookUpdatePacket(client->GetVersion()); + if (outapp) + client->QueuePacket(outapp); + } + } + return 0; +} + +int EQ2Emu_lua_DeleteSpellBook(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + int8 type_selection = lua_interface->GetInt8Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer()) { + Client* client = player->GetClient(); + if (client) { + ((Player*)player)->DeleteSpellBook(type_selection); + EQ2Packet* outapp = ((Player*)player)->GetSpellBookUpdatePacket(client->GetVersion()); + if (outapp) + client->QueuePacket(outapp); + } + } + return 0; +} + +int EQ2Emu_lua_SendNewAdventureSpells(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer()) { + Client* client = player->GetClient(); + if (client) { + client->SendNewAdventureSpells(); + } + } + return 0; +} + + +int EQ2Emu_lua_SendNewTradeskillSpells(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer()) { + Client* client = player->GetClient(); + if (client) { + client->SendNewTradeskillSpells(); + } + } + return 0; +} + +int EQ2Emu_lua_RemoveSpellBookEntry(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + int32 spellid = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer()) { + SpellBookEntry* sbe = ((Player*)player)->GetSpellBookSpell(spellid); + Client* client = player->GetClient(); + if (sbe && client) { + ((Player*)player)->RemoveSpellBookEntry(spellid); + EQ2Packet* outapp = ((Player*)player)->GetSpellBookUpdatePacket(client->GetVersion()); + if (outapp) + client->QueuePacket(outapp); + } + } + return 0; +} + + +int EQ2Emu_lua_HasFreeSlot(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer()) { + lua_interface->SetBooleanValue(state, ((Player*)player)->item_list.HasFreeSlot()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_Attack(lua_State* state) { + if (lua_interface) { + Spawn* npc = lua_interface->GetSpawn(state); + Spawn* player = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + if (npc && player && npc->IsNPC() && player->IsEntity()) + ((NPC*)npc)->AddHate((Entity*)player, 100); + } + return 0; +} + +int EQ2Emu_lua_ApplySpellVisual(lua_State* state) { + if (lua_interface) { + Spawn* target = lua_interface->GetSpawn(state); + int32 spell_visual = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (target && target->GetZone()) + target->GetZone()->SendCastSpellPacket(spell_visual, target); + } + return 0; +} + +int EQ2Emu_lua_HasCollectionsToHandIn(lua_State* state) { + Spawn* player; + + if (lua_interface) { + player = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer()) { + lua_interface->SetBooleanValue(state, ((Player*)player)->GetCollectionList()->HasCollectionsToHandIn()); + return 1; + } + } + + return 0; +} + +int EQ2Emu_lua_HandInCollections(lua_State* state) { + Spawn* player; + Client* client; + + if (lua_interface) { + player = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (player && ((Player*)player)->IsPlayer() && ((Player*)player)->GetCollectionList()->HasCollectionsToHandIn()) + if ((client = ((Player*)player)->GetClient())) + client->HandInCollections(); + } + + return 0; +} + +int EQ2Emu_lua_UseWidget(lua_State* state) { + Spawn* widget; + + if (lua_interface) { + widget = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (widget && widget->IsWidget()) + ((Widget*)widget)->HandleUse(nullptr, ""); + } + + return 0; +} + +int EQ2Emu_lua_SetSpellList(lua_State* state) { + Spawn* spawn = 0; + int32 primary_list = 0; + int32 secondary_list = 0; + + if (lua_interface) { + spawn = lua_interface->GetSpawn(state); + primary_list = lua_interface->GetInt32Value(state, 2); + secondary_list = lua_interface->GetInt32Value(state, 3); + lua_interface->ResetFunctionStack(state); + + if (!spawn->IsNPC()) { + lua_interface->LogError("%s: LUA SetSpellList command error: Spawn was not a valid NPC", lua_interface->GetScriptName(state)); + return 0; + } + + NPC* npc = (NPC*)spawn; + npc->SetPrimarySpellList(primary_list); + npc->SetSecondarySpellList(secondary_list); + npc->SetSpells(world.GetNPCSpells(npc->GetPrimarySpellList(), npc->GetSecondarySpellList())); + } + return 0; +} + +int EQ2Emu_lua_GetPet(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + if (spawn->IsEntity() && ((Entity*)spawn)->GetPet()) { + lua_interface->SetSpawnValue(state, ((Entity*)spawn)->GetPet()); + return 1; + } + } + return 0; +} + +int EQ2Emu_lua_GetCharmedPet(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + if (spawn->IsEntity() && ((Entity*)spawn)->GetCharmedPet()) { + lua_interface->SetSpawnValue(state, ((Entity*)spawn)->GetCharmedPet()); + return 1; + } + } + return 0; +} + +int EQ2Emu_lua_GetDeityPet(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + if (spawn->IsEntity() && ((Entity*)spawn)->GetDeityPet()) { + lua_interface->SetSpawnValue(state, ((Entity*)spawn)->GetDeityPet()); + return 1; + } + } + return 0; +} + +int EQ2Emu_lua_GetCosmeticPet(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + if (spawn->IsEntity() && ((Entity*)spawn)->GetCosmeticPet()) { + lua_interface->SetSpawnValue(state, ((Entity*)spawn)->GetCosmeticPet()); + return 1; + } + } + return 0; +} +int EQ2Emu_lua_Charm(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* owner = lua_interface->GetSpawn(state); + Spawn* pet = lua_interface->GetSpawn(state, 2); + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + if (!luaspell) { + lua_interface->LogError("%s: LUA Charm command error: Spell is not valid, charm can only be used in spell scripts.", lua_interface->GetScriptName(state)); + return 0; + } + + if(luaspell->resisted) { + return 0; + } + if (owner && pet && owner->IsEntity() && pet->IsNPC()) { + ((Entity*)owner)->SetCharmedPet((Entity*)pet); + pet->SetPet(true); + ((NPC*)pet)->SetPetType(PET_TYPE_CHARMED); + ((NPC*)pet)->SetOwner((Entity*)owner); + // If owner is player and player does not have a summoned pet set the players charsheet + if (owner->IsPlayer() && !((Entity*)owner)->GetPet()) { + Player* player = (Player*)owner; + player->GetInfoStruct()->set_pet_id(player->GetIDWithPlayerSpawn(pet)); + player->GetInfoStruct()->set_pet_name(std::string(pet->GetName())); + player->GetInfoStruct()->set_pet_movement(2); + player->GetInfoStruct()->set_pet_behavior(3); + player->GetInfoStruct()->set_pet_health_pct(1.0f); + player->GetInfoStruct()->set_pet_power_pct(1.0f); + // Make sure the values get sent to the client + player->SetCharSheetChanged(true); + } + // Clear the spawns script so the charmed mob doesn't try to do anything like random walks + pet->SetSpawnScript(""); + // Set faction to the same as the owner + pet->SetFactionID(owner->GetFactionID()); + + ((NPC*)pet)->SetPetSpellID(luaspell->spell->GetSpellData()->id); + ((NPC*)pet)->SetPetSpellTier(luaspell->spell->GetSpellData()->tier); + + // Clear hate list + ((NPC*)pet)->Brain()->ClearHate(); + + // Set the brain to a pet brain + ((NPC*)pet)->SetBrain(new CombatPetBrain((NPC*)pet)); + } + return 0; +} + +int EQ2Emu_lua_GetGroup(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA GetGroup command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + vector groupMembers; + if (!spawn->IsPlayer() && spawn->HasSpawnGroup()) { + groupMembers = *spawn->GetSpawnGroup(); + } + else if (spawn->IsPlayer() && ((Player*)spawn)->GetGroupMemberInfo()) { + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + + deque::iterator itr; + PlayerGroup* group = world.GetGroupManager()->GetGroup(((Player*)spawn)->GetGroupMemberInfo()->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + GroupMemberInfo* info = 0; + for (itr = members->begin(); itr != members->end(); itr++) { + info = *itr; + if (info->client) + groupMembers.push_back(info->client->GetPlayer()); + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } + else + return 0; + + lua_createtable(state, groupMembers.size(), 0); + int newTable = lua_gettop(state); + for (int32 i = 0; i < groupMembers.size(); i++) { + lua_interface->SetSpawnValue(state, groupMembers.at(i)); + lua_rawseti(state, newTable, i + 1); + } + return 1; + +} + +int EQ2Emu_lua_CreateOptionWindow(lua_State* state) { + if (!lua_interface) + return 0; + + lua_interface->ResetFunctionStack(state); + vector* option_window = new vector(); + lua_interface->SetOptionWindowValue(state, option_window); + return 1; +} + +int EQ2Emu_lua_AddOptionWindowOption(lua_State* state) { + if (!lua_interface) + return 0; + vector* option_window = lua_interface->GetOptionWindow(state); + if (option_window) { + OptionWindowOption option_window_option; + option_window_option.optionName = lua_interface->GetStringValue(state, 2); + option_window_option.optionDescription = lua_interface->GetStringValue(state, 3); + option_window_option.optionIconSheet = lua_interface->GetInt32Value(state, 4); + option_window_option.optionIconID = lua_interface->GetInt16Value(state, 5); + option_window_option.optionCommand = lua_interface->GetStringValue(state, 6); + option_window_option.optionConfirmTitle = lua_interface->GetStringValue(state, 7); + if (option_window_option.optionName.length() > 0 && option_window_option.optionDescription.length() > 0) + option_window->push_back(option_window_option); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_SendOptionWindow(lua_State* state) { + if (!lua_interface) + return 0; + + vector* option_window = lua_interface->GetOptionWindow(state); + Spawn* player = lua_interface->GetSpawn(state, 2); + string window_title = lua_interface->GetStringValue(state, 3); + string cancel_command = lua_interface->GetStringValue(state, 4); + + lua_interface->ResetFunctionStack(state); + + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA SendOptionWindow command error: spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + Client* client = ((Player*)player)->GetClient(); + + if (option_window && window_title.length() > 0 && client) { + PacketStruct* packet = configReader.getStruct("WS_SelectTradeskill", client->GetVersion()); + if (!packet) + return 0; + + packet->setDataByName("title_text", window_title.c_str()); + if (cancel_command.length() > 0) + packet->setDataByName("command_text_cancel", cancel_command.c_str()); + + packet->setArrayLengthByName("num_selections", option_window->size()); + vector::iterator itr; + int8 i = 0; + for (itr = option_window->begin(); itr != option_window->end(); itr++) { + OptionWindowOption opt = *itr; + packet->setArrayDataByName("tradeskill_name", opt.optionName.c_str(), i); + packet->setArrayDataByName("tradeskill_description", opt.optionDescription.c_str(), i); + packet->setArrayDataByName("icon_sheet", opt.optionIconSheet, i); + packet->setArrayDataByName("icon_id", opt.optionIconID, i); + if (opt.optionCommand.length() > 0) + packet->setArrayDataByName("command_text", opt.optionCommand.c_str(), i); + if (opt.optionConfirmTitle.length() > 0) + packet->setArrayDataByName("confirm_window_title", opt.optionConfirmTitle.c_str(), i); + + i++; + } + client->QueuePacket(packet->serialize()); + lua_interface->SetLuaUserDataStale(option_window); + safe_delete(option_window); + safe_delete(packet); + } + return 0; +} + +int EQ2Emu_lua_GetTradeskillClass(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (spawn) { + lua_interface->SetInt32Value(state, spawn->GetTradeskillClass()); + return 1; + } + else + lua_interface->LogError("%s: LUA GetTradeskillClass command error: Spawn was not valid", lua_interface->GetScriptName(state)); + + return 0; +} + +int EQ2Emu_lua_GetTradeskillLevel(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + lua_interface->SetInt32Value(state, spawn->GetTSLevel()); + return 1; + } + else + lua_interface->LogError("%s: LUA GetTradeskillLevel command error: Spawns was not valid", lua_interface->GetScriptName(state)); + return 0; +} + +int EQ2Emu_lua_GetTradeskillClassName(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + int8 class_id = spawn->GetTradeskillClass(); + // Need to add 42 for the offset in the array + class_id += 44; + lua_interface->SetStringValue(state, classes.GetClassNameCase(class_id).c_str()); + return 1; + } + else + lua_interface->LogError("%s: LUA GetTradeskillClassName command error: Spawn was not valid", lua_interface->GetScriptName(state)); + return 0; +} + +int EQ2Emu_lua_SetTradeskillLevel(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + int16 level = lua_interface->GetInt8Value(state, 2); + + lua_interface->ResetFunctionStack(state); + if (spawn) { + if (spawn->IsPlayer() && ((Player*)spawn)->GetClient()) + ((Player*)spawn)->GetClient()->ChangeTSLevel(spawn->GetTSLevel(), level); + else + spawn->SetTSLevel(level); + } + else + lua_interface->LogError("%s: LUA SetTradeskillLevel command error: Spawn was not valid", lua_interface->GetScriptName(state)); + return 0; +} + +int EQ2Emu_lua_SetAttackable(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int8 attackable = lua_interface->GetInt8Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn) { + spawn->SetAttackable(attackable); + spawn->vis_changed = true; //some clients store this in vis instead of info, need to make sense both are updated + } + return 0; +} + +int EQ2Emu_lua_SummonPet(lua_State* state) { + // Check to see if we have a valid lua_interface + if (!lua_interface) + return 0; + + // Get the spawn that is getting the pet + Spawn* spawn = lua_interface->GetSpawn(state); + // Get the DB ID of the pet + int32 pet_id = lua_interface->GetInt32Value(state, 2); + // The max level the pet can gain + int8 max_level = lua_interface->GetInt8Value(state, 3); + // Get the spell that this command was called from + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + // Check to make sure the spawn pointer is valid + if (!spawn) { + lua_interface->LogError("%s: LUA SummonPet command error: Spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + // Check to make sure the spawn is an entity + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA SummonPet command error: Spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + // Check to make sure the spawn doesn't already have a pet of this type + if (((Entity*)spawn)->GetPet()) { + if (spawn->IsPlayer()) { + Client* client = ((Player*)spawn)->GetClient(); + if (client) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You already have a pet."); + } + + lua_interface->LogError("%s: LUA SummonPet command error: spawn already has a pet of this type", lua_interface->GetScriptName(state)); + return 0; + } + + // Check to see if the DB ID for the pet is set + if (pet_id == 0) { + lua_interface->LogError("%s: LUA SummonPet command error: pet_id can not be set to 0", lua_interface->GetScriptName(state)); + return 0; + } + + // Check to see if the pointer to the spell is valid + if (!luaspell) { + lua_interface->LogError("%s: LUA SummonPet command error: valid spell not found, SummonPet can only be used in spell scripts", lua_interface->GetScriptName(state)); + return 0; + } + + if(luaspell->resisted) { + return 0; + } + + // Get a pointer to a spawn with the given DB ID and check if the pointer is valid + Spawn* pet = spawn->GetZone()->GetSpawn(pet_id); + if (!pet) { + lua_interface->LogError("%s: LUA SummonPet command error: Could not find spawn with id of %u.", lua_interface->GetScriptName(state), pet_id); + return 0; + } + + // Check to make sure the pet is an npc + if (!pet->IsNPC()) { + lua_interface->LogError("%s: LUA SummonPet command error: id (%u) did not point to a npc", lua_interface->GetScriptName(state), pet_id); + return 0; + } + + // Spawn the pet at the same location as the owner + pet->SetX(spawn->GetX()); + pet->SetY(spawn->GetY()); + pet->SetZ(spawn->GetZ()); + pet->SetLocation(spawn->GetLocation()); + pet->SetHeading(spawn->GetHeading()); + + std::string petName = std::string(""); + if(spawn->IsEntity()) { + petName = ((Entity*)spawn)->GetInfoStruct()->get_pet_name(); + } + + if(petName.size() < 1) { + int16 rand_index = MakeRandomInt(0, spawn->GetZone()->pet_names.size() - 1); + petName = spawn->GetZone()->pet_names.at(rand_index); + LogWrite(PET__DEBUG, 0, "Pets", "Randomize Pet Name: '%s' (rand: %i)", petName.c_str(), rand_index); + } + + // If player set various values for the char sheet (pet window) + if (spawn->IsPlayer()) { + Player* player = (Player*)spawn; + + player->GetInfoStruct()->set_pet_id(player->GetIDWithPlayerSpawn(pet)); + player->GetInfoStruct()->set_pet_name(petName); + player->GetInfoStruct()->set_pet_movement(2); + player->GetInfoStruct()->set_pet_behavior(3); + player->GetInfoStruct()->set_pet_health_pct(1.0f); + player->GetInfoStruct()->set_pet_power_pct(1.0f); + // Make sure the values get sent to the client + player->SetCharSheetChanged(true); + } + + // Set the pets name + pet->SetName(petName.c_str()); + // Set the level of the pet to the owners level or max level(if set) if owners level is greater + if (max_level > 0) + pet->SetLevel(spawn->GetLevel() >= max_level ? max_level : spawn->GetLevel()); + else + pet->SetLevel(spawn->GetLevel()); + // Set the max level this pet can reach + ((NPC*)pet)->SetMaxPetLevel(max_level); + + ((NPC*)pet)->UpdateWeapons(); + + // Set the faction of the pet to the same faction as the owner + pet->SetFactionID(spawn->GetFactionID()); + // Set the spawn as a pet + pet->SetPet(true); + // Give a pointer of the owner to the pet + ((NPC*)pet)->SetOwner((Entity*)spawn); + // Give a pointer of the pet to the owner + ((Entity*)spawn)->SetCombatPet((Entity*)pet); + // Set the pet type + ((NPC*)pet)->SetPetType(PET_TYPE_COMBAT); + // Set the spell id used to create this pet + ((NPC*)pet)->SetPetSpellID(luaspell->spell->GetSpellData()->id); + // Set the spell tier used to create this pet + ((NPC*)pet)->SetPetSpellTier(luaspell->spell->GetSpellData()->tier); + // Set the pets spawn type to 6 + pet->SetSpawnType(6); + // Set the pets brain + ((NPC*)pet)->SetBrain(new CombatPetBrain((NPC*)pet)); + // Check to see if the pet has a subtitle + if (strlen(pet->GetSubTitle()) > 0) { + // Add the players name to the front of the sub title + string pet_subtitle; + pet_subtitle.append(spawn->GetName()).append("'s ").append(pet->GetSubTitle()); + LogWrite(PET__DEBUG, 0, "Pets", "Pet Subtitle: '%s'", pet_subtitle.c_str()); + // Set the pets subtitle to the new one + pet->SetSubTitle(pet_subtitle.c_str()); + } + // Add the "Pet Options" entity command to the pet + pet->AddSecondaryEntityCommand("Pet Options", 10.0f, "petoptions", "", 0, 0); + + const char* spawn_script = world.GetSpawnScript(pet_id); + bool runScript = false; + if(spawn_script && lua_interface->GetSpawnScript(spawn_script) != 0){ + runScript = true; + pet->SetSpawnScript(string(spawn_script)); + spawn->GetZone()->CallSpawnScript(pet, SPAWN_SCRIPT_PRESPAWN); + } + + spawn->GetZone()->AddSpawn(pet); + + if(runScript){ + spawn->GetZone()->CallSpawnScript(pet, SPAWN_SCRIPT_SPAWN); + } + + // Set the pet as the return value for this function + lua_interface->SetSpawnValue(state, pet); + return 1; +} + +int EQ2Emu_lua_SummonDeityPet(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 pet_id = lua_interface->GetInt32Value(state, 2); + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA SummonDeityPet command error: Spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA SummonDeityPet command error: Spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (((Entity*)spawn)->GetDeityPet()) { + if (spawn->IsPlayer()) { + Client* client = ((Player*)spawn)->GetClient(); + if (client) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You already have a deity pet."); + } + + lua_interface->LogError("%s: LUA SummonDeityPet command error: spawn already has a pet of this type", lua_interface->GetScriptName(state)); + return 0; + } + + if (pet_id == 0) { + lua_interface->LogError("%s: LUA SummonDeityPet command error: pet_id can not be set to 0", lua_interface->GetScriptName(state)); + return 0; + } + + if (!luaspell) { + lua_interface->LogError("%s: LUA SummonDeityPet command error: valid spell not found, SummonDeityPet can only be used in spell scripts", lua_interface->GetScriptName(state)); + return 0; + } + + if(luaspell->resisted) { + return 0; + } + + Spawn* pet = spawn->GetZone()->GetSpawn(pet_id); + if (!pet) { + lua_interface->LogError("%s: LUA SummonDeityPet command error: Could not find spawn with id of %u.", lua_interface->GetScriptName(state), pet_id); + return 0; + } + + if (!pet->IsNPC()) { + lua_interface->LogError("%s: LUA SummonDeityPet command error: spawn with id of %u is not a npc", lua_interface->GetScriptName(state), pet_id); + return 0; + } + + pet->SetX(spawn->GetX()); + pet->SetY(spawn->GetY()); + pet->SetZ(spawn->GetZ()); + pet->SetLocation(spawn->GetLocation()); + pet->SetHeading(spawn->GetHeading()); + spawn->GetZone()->AddSpawn(pet); + + string random_pet_name; + int16 rand_index = MakeRandomInt(0, spawn->GetZone()->pet_names.size() - 1); + random_pet_name = spawn->GetZone()->pet_names.at(rand_index); + LogWrite(PET__DEBUG, 0, "Pets", "Randomize Pet Name: '%s' (rand: %i)", random_pet_name.c_str(), rand_index); + + pet->SetName(random_pet_name.c_str()); + pet->SetLevel(spawn->GetLevel()); + pet->SetFactionID(spawn->GetFactionID()); + pet->SetPet(true); + ((NPC*)pet)->SetPetType(PET_TYPE_DEITY); + ((NPC*)pet)->SetOwner((Entity*)spawn); + ((Entity*)spawn)->SetDeityPet((Entity*)pet); + pet->SetSpawnType(6); + ((NPC*)pet)->SetBrain(new NonCombatPetBrain((NPC*)pet)); + ((NPC*)pet)->SetPetSpellID(luaspell->spell->GetSpellData()->id); + ((NPC*)pet)->SetPetSpellTier(luaspell->spell->GetSpellData()->tier); + + if (strlen(pet->GetSubTitle()) > 0) { + string pet_subtitle; + pet_subtitle.append(spawn->GetName()).append("'s ").append(pet->GetSubTitle()); + LogWrite(PET__DEBUG, 0, "Pets", "Pet Subtitle: '%s'", pet_subtitle.c_str()); + pet->SetSubTitle(pet_subtitle.c_str()); + } + + // deity and cosmetic pets are not attackable + pet->SetAttackable(false); + pet->AddSecondaryEntityCommand("Pet Options", 10.0f, "petoptions", "", 0, 0); + lua_interface->SetSpawnValue(state, pet); + return 1; +} + +int EQ2Emu_lua_SummonCosmeticPet(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 pet_id = lua_interface->GetInt32Value(state, 2); + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA SummonCosmeticPet command error: Spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA SummonCosmeticPet command error: Spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (((Entity*)spawn)->GetCosmeticPet()) { + if (spawn->IsPlayer()) { + Client* client = ((Player*)spawn)->GetClient(); + if (client) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You already have a cosmetic pet."); + } + + lua_interface->LogError("%s: LUA SummonCosmeticPet command error: spawn already has a pet of this type", lua_interface->GetScriptName(state)); + return 0; + } + + if (pet_id == 0) { + lua_interface->LogError("%s: LUA SummonCosmeticPet command error: pet_id can not be set to 0", lua_interface->GetScriptName(state)); + return 0; + } + + if (!luaspell) { + lua_interface->LogError("%s: LUA SummonCosmeticPet command error: valid spell not found, SummonCosmeticPet can only be used in spell scripts", lua_interface->GetScriptName(state)); + return 0; + } + + if(luaspell->resisted) { + return 0; + } + + Spawn* pet = spawn->GetZone()->GetSpawn(pet_id); + if (!pet) { + lua_interface->LogError("%s: LUA SummonCosmeticPet command error: Could not find spawn with id of %u.", lua_interface->GetScriptName(state), pet_id); + return 0; + } + + if (!pet->IsNPC()) { + lua_interface->LogError("%s: LUA SummonCosmeticPet command error: spawn with id of %u is not a npc", lua_interface->GetScriptName(state), pet_id); + return 0; + } + + pet->SetX(spawn->GetX()); + pet->SetY(spawn->GetY()); + pet->SetZ(spawn->GetZ()); + pet->SetLocation(spawn->GetLocation()); + pet->SetHeading(spawn->GetHeading()); + spawn->GetZone()->AddSpawn(pet); + + string random_pet_name; + int16 rand_index = MakeRandomInt(0, spawn->GetZone()->pet_names.size() - 1); + random_pet_name = spawn->GetZone()->pet_names.at(rand_index); + LogWrite(PET__DEBUG, 0, "Pets", "Randomize Pet Name: '%s' (rand: %i)", random_pet_name.c_str(), rand_index); + + pet->SetName(random_pet_name.c_str()); + pet->SetLevel(spawn->GetLevel()); + pet->SetFactionID(spawn->GetFactionID()); + pet->SetPet(true); + ((NPC*)pet)->SetPetType(PET_TYPE_COSMETIC); + ((NPC*)pet)->SetOwner((Entity*)spawn); + ((Entity*)spawn)->SetCosmeticPet((Entity*)pet); + pet->SetSpawnType(6); + ((NPC*)pet)->SetBrain(new NonCombatPetBrain((NPC*)pet)); + ((NPC*)pet)->SetPetSpellID(luaspell->spell->GetSpellData()->id); + ((NPC*)pet)->SetPetSpellTier(luaspell->spell->GetSpellData()->tier); + + if (strlen(pet->GetSubTitle()) > 0) { + string pet_subtitle; + pet_subtitle.append(spawn->GetName()).append("'s ").append(pet->GetSubTitle()); + LogWrite(PET__DEBUG, 0, "Pets", "Pet Subtitle: '%s'", pet_subtitle.c_str()); + pet->SetSubTitle(pet_subtitle.c_str()); + } + + pet->SetAttackable(false); + pet->AddSecondaryEntityCommand("Pet Options", 10.0f, "petoptions", "", 0, 0); + lua_interface->SetSpawnValue(state, pet); + return 1; +} + +int EQ2Emu_lua_DismissPet(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA DismissPet command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsPet()) { + lua_interface->LogError("%s: LUA DismissPet command error: spawn is not a pet", lua_interface->GetScriptName(state)); + return 0; + } + + if (!((NPC*)spawn)->IsDismissing() && ((NPC*)spawn)->GetOwner()) + ((NPC*)spawn)->GetOwner()->DismissPet((NPC*)spawn, false, true); + + return 0; +} + +int EQ2Emu_lua_SetQuestFeatherColor(lua_State* state) { + if (!lua_interface) + return 0; + + Quest* quest = lua_interface->GetQuest(state); + + if (!quest) { + lua_interface->LogError("%s: LUA SetQuestFeatherColor command error: valid quest not found, SetQuestFeatherColor can only be called from a quest script", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + + int8 feather_color = lua_interface->GetInt8Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (feather_color > 0) + quest->SetFeatherColor(feather_color); + + return 0; +} + +int EQ2Emu_lua_RemoveSpawnAccess(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + Spawn* spawn2 = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA RemoveSpawnAccess command error: first spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn2) { + lua_interface->LogError("%s: LUA RemoveSpawnAccess command error: second spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + spawn->RemoveSpawnAccess(spawn2); + return 0; +} + +int EQ2Emu_lua_SpawnByLocationID(lua_State* state) { + if (!lua_interface) + return 0; + + ZoneServer* zone = lua_interface->GetZone(state); + int32 location_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!zone) { + lua_interface->LogError("%s: LUA SpawnByLocationID command error: zone is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (location_id == 0) { + lua_interface->LogError("%s: LUA SpawnByLocationID command error: location id can not be 0", lua_interface->GetScriptName(state)); + return 0; + } + + SpawnLocation* location = zone->GetSpawnLocation(location_id); + if (!location) { + lua_interface->LogError("%s: LUA SpawnByLocationID command error: no location found for the given ID (%u)", lua_interface->GetScriptName(state), location_id); + return 0; + } + + Spawn* spawn = 0; + if (location->entities[0]) { + if (location->entities[0]->spawn_type == SPAWN_ENTRY_TYPE_NPC) + spawn = zone->AddNPCSpawn(location, location->entities[0]); + else if (location->entities[0]->spawn_type == SPAWN_ENTRY_TYPE_GROUNDSPAWN) + spawn = zone->AddGroundSpawn(location, location->entities[0]); + else if (location->entities[0]->spawn_type == SPAWN_ENTRY_TYPE_OBJECT) + spawn = zone->AddObjectSpawn(location, location->entities[0]); + else if (location->entities[0]->spawn_type == SPAWN_ENTRY_TYPE_WIDGET) + spawn = zone->AddWidgetSpawn(location, location->entities[0]); + else if (location->entities[0]->spawn_type == SPAWN_ENTRY_TYPE_SIGN) + spawn = zone->AddSignSpawn(location, location->entities[0]); + + if(spawn && spawn->IsOmittedByDBFlag()) + { + LogWrite(SPAWN__WARNING, 0, "Spawn", "Spawn (%u) was skipped due to a missing expansion / holiday flag being met.", location->entities[0]->spawn_id); + safe_delete(spawn); + spawn = 0; + return 0; + } + + if (spawn) { + const char* script = 0; + for (int x = 0; x < 3; x++) { + switch (x) { + case 0: + script = world.GetSpawnEntryScript(location->entities[0]->spawn_entry_id); + break; + case 1: + script = world.GetSpawnLocationScript(location->entities[0]->spawn_location_id); + break; + case 2: + script = world.GetSpawnScript(location->entities[0]->spawn_id); + break; + } + if (script && lua_interface->GetSpawnScript(script) != 0) { + spawn->SetSpawnScript(string(script)); + break; + } + } + + zone->CallSpawnScript(spawn, SPAWN_SCRIPT_SPAWN); + lua_interface->SetSpawnValue(state, spawn); + return 1; + } + else { + LogWrite(ZONE__ERROR, 0, "Zone", "Error adding spawn by location id to zone %s with location id %u.", zone->GetZoneName(), location_id); + safe_delete(spawn); + } + } + + return 0; +} + +int EQ2Emu_lua_CastEntityCommand(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* caster = lua_interface->GetSpawn(state); + Spawn* target = lua_interface->GetSpawn(state, 2); + int32 id = lua_interface->GetInt32Value(state, 3); + string command = lua_interface->GetStringValue(state, 4); + lua_interface->ResetFunctionStack(state); + + if (!caster) { + lua_interface->LogError("%s: LUA CastEntityCommand command error: caster is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!target) { + lua_interface->LogError("%s: LUA CastEntityCommand command error: target is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!caster->IsPlayer()) { + lua_interface->LogError("%s: LUA CastEntityCommand command error: caster is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + EntityCommand* entity_command = caster->GetZone()->GetEntityCommand(id, command); + if (!entity_command) { + lua_interface->LogError("%s: LUA CastEntityCommand command error: unable to get a valid EntityCommand with the given ID (%u) and name (%s)", lua_interface->GetScriptName(state), id, command.c_str()); + return 0; + } + + Client* client = ((Player*)caster)->GetClient(); + if (!client) { + lua_interface->LogError("%s: LUA CastEntityCommand command error: unable to get a valid client for the given caster", lua_interface->GetScriptName(state)); + return 0; + } + + client->GetCurrentZone()->ProcessEntityCommand(entity_command, (Player*)caster, target); + + return 0; +} + +int EQ2Emu_lua_SetLuaBrain(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA SetLuaBrain command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsNPC()) { + lua_interface->LogError("%s: LUA SetLuaBrain command error: spawn is not a npc", lua_interface->GetScriptName(state)); + return 0; + } + + ((NPC*)spawn)->SetBrain(new LuaBrain((NPC*)spawn)); + + return 0; +} + +int EQ2Emu_lua_SetBrainTick(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + int16 tick = lua_interface->GetInt16Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA SetBrainTick command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsNPC()) { + lua_interface->LogError("%s: LUA SetBrainTick command error: spawn is not a valid npc", lua_interface->GetScriptName(state)); + return 0; + } + + if (tick < 20) { + lua_interface->LogError("%s: LUA SetBrainTick command error: tick can not be set below 20 milliseconds", lua_interface->GetScriptName(state)); + return 0; + } + + ((NPC*)spawn)->Brain()->SetTick(tick); + + return 0; +} + +int EQ2Emu_lua_SetFollowTarget(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + Spawn* target = lua_interface->GetSpawn(state, 2); + int32 follow_distance = lua_interface->GetInt32Value(state, 3); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA SetFollowTarget command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + // Target can be null, setting follow target to 0 clears it and will cancel follow, so no need to check it + + spawn->SetFollowTarget(target, follow_distance); + return 0; +} + +int EQ2Emu_lua_GetFollowTarget(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA GetFollowTarget command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + Spawn* target = spawn->GetFollowTarget(); + if (target) { + lua_interface->SetSpawnValue(state, target); + return 1; + } + + return 0; +} + +int EQ2Emu_lua_ToggleFollow(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA ToggleFollow command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (spawn->following) + spawn->following = false; + else + spawn->following = true; + + return 0; +} + +int EQ2Emu_lua_IsFollowing(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA IsFollowing command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetBooleanValue(state, spawn->following); + return 1; +} + +int EQ2Emu_lua_SetTempVariable(lua_State* state) { + // As this is unique among the rest of our lua functions as the 3rd param can be of multiple types + // I will attempt to explain how this function works for future refrence + + // Fist lets make sure lua_interface is valid, if not return out + if (!lua_interface) + return 0; + + // Next we grab the first 2 params same as we usually would + Spawn* spawn = lua_interface->GetSpawn(state); + string var = lua_interface->GetStringValue(state, 2); + + // DataType will let us know the value type so we can handle it correctly, we set these ourself so the values I used are made up + // 1 = Spawn + // 2 = Zone + // 3 = Item + // 4 = Quest + // 5 = String + // 6 = nil (null) + int8 dataType = 0; + + // Define pointers for each potential type + Spawn* spawnVal = 0; + ZoneServer* zone = 0; + Item* item = 0; + Quest* quest = 0; + string val; + + // Finally we get to grabbing the third param, we will first check to see if it is light user data + // which is custom data types, in this case it can be Spawn, Zone, Item, or Quest. Conversation and + // options window are also light user data be we do not handle those. + // We check with lua_islightuserdata(lua_State*, index) + if (lua_islightuserdata(state, 3)) { + // It is light user data so we will grab the param with lua_touserdata(lua_State*, index) + // and convert it to LUAUserData* + LUAUserData* data = (LUAUserData*)lua_touserdata(state, 3); + // Check to make sure the data we got is valid, if not give an error + if (!data || !data->IsCorrectlyInitialized()) { + lua_interface->LogError("%s: LUA SetTempVariable command error while processing %s", lua_interface->GetScriptName(state), lua_tostring(state, -1)); + } + // Check if data is a Spawn, if so set our Spawn pointer and the dataType variable + else if (data->IsSpawn()) { + spawnVal = data->spawn; + dataType = 1; + } + // Check if data is a Zone, if so set our Zone pointer and the dataType variable + else if (data->IsZone()) { + zone = data->zone; + dataType = 2; + } + // Check if data is a Item, if so set our Item pointer and the dataType variable + else if (data->IsItem()) { + item = data->item; + dataType = 3; + } + // Check if data is a Ques, if so set our Quest pointer and the dataType variable + else if (data->IsQuest()) { + quest = data->quest; + dataType = 4; + } + } + // Wasn't light user data, check if it is nil(null) + else if (lua_isnil(state, 3)) { + // It is nil (null) set the dataType variable, no need to set a pointer in this case + dataType = 6; + } + // Wasn't light user data or nil (null), must be a string + else { + // Set the string and dataType variable + val = lua_interface->GetStringValue(state, 3); + dataType = 5; + } + + lua_interface->ResetFunctionStack(state); + + // We now have all the params, lets check to make sure they are valid + if (!spawn) { + lua_interface->LogError("%s: LUA SetTempVariable command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (var.length() == 0) { + lua_interface->LogError("%s: LUA SetTempVariable command error: var must be set", lua_interface->GetScriptName(state)); + return 0; + } + + if (dataType == 0) { + lua_interface->LogError("%s: LUA SetTempVariable command error: unknown data type", lua_interface->GetScriptName(state)); + return 0; + } + + // All params are valid, lets set the spawns temp variable, this is where dataType variable comes in. + // AddTempVariable has overloads for all the types of data we support, we need to make sure the third + // param gets sent to the correct list so we check the value of dataType to know where it should go. + switch (dataType) { + case 1: + // 1 = Spawn + spawn->AddTempVariable(var, spawnVal); + break; + case 2: + // 2 = Zone + spawn->AddTempVariable(var, zone); + break; + case 3: + // 3 = Item + spawn->AddTempVariable(var, item); + break; + case 4: + // 4 = Quest + spawn->AddTempVariable(var, quest); + break; + case 5: + // 5 = String + spawn->AddTempVariable(var, val); + break; + case 6: + // 6 = nil (null) so the variable is no longer set, lets remove it from the spawn + spawn->DeleteTempVariable(var); + break; + } + + // And we are done so return out + return 0; +} + +int EQ2Emu_lua_GetTempVariable(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + string var = lua_interface->GetStringValue(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA GetTempVariable command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (var.length() == 0) { + lua_interface->LogError("%s: LUA GetTempVariable command error: var must be set", lua_interface->GetScriptName(state)); + return 0; + } + + // This will tell us the type of data this variable contains, uses the same values as the previous function + int8 type = spawn->GetTempVariableType(var); + + Spawn* spawn2 = 0; + ZoneServer* zone = 0; + Item* item = 0; + Quest* quest = 0; + + // Set the lua function return value based on the type of data the variable contains + switch (type) { + case 1: + spawn2 = spawn->GetTempVariableSpawn(var); + if (!spawn2) + return 0; + lua_interface->SetSpawnValue(state, spawn2); + break; + case 2: + zone = spawn->GetTempVariableZone(var); + if (!zone) + return 0; + lua_interface->SetZoneValue(state, zone); + break; + case 3: + item = spawn->GetTempVariableItem(var); + if (!item) + return 0; + lua_interface->SetItemValue(state, item); + break; + case 4: + quest = spawn->GetTempVariableQuest(var); + if (!quest) + return 0; + lua_interface->SetQuestValue(state, quest); + break; + case 5: + lua_interface->SetStringValue(state, spawn->GetTempVariable(var).c_str()); + break; + default: + // Not a valid type then the variable was not set so return out + return 0; + } + + // Return value was set so return out + return 1; +} + +int EQ2Emu_lua_GiveQuestItem(lua_State* state) +{ + if (!lua_interface) + return 0; + + Quest* quest = lua_interface->GetQuest(state); + Spawn* spawn = lua_interface->GetSpawn(state, 2); + string description = lua_interface->GetStringValue(state, 3); + int32 item_id = lua_interface->GetInt32Value(state, 4); + + if (!quest) { + lua_interface->LogError("%s: LUA GiveQuestItem command error: quest is not valid", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + if (!spawn) { + lua_interface->LogError("%s: LUA GiveQuestItem command error: spawn is not valid", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + if (!spawn->IsPlayer()) { + lua_interface->LogError("%s: LUA GiveQuestItem command error: spawn must be a player", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + if (item_id == 0) { + lua_interface->LogError("%s: LUA GiveQuestItem command error: item_id is not valid", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + Client* client = ((Player*)spawn)->GetClient(); + if (!client) { + lua_interface->LogError("%s: LUA GiveQuestItem command error: unable to get a valid client from the given player spawn", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + Item* item = master_item_list.GetItem(item_id); + if (!item) { + lua_interface->LogError("%s: LUA GiveQuestItem command error: unable to get an item from the given id (%u)", lua_interface->GetScriptName(state), item_id); + lua_interface->ResetFunctionStack(state); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + Item* firstItem = new Item(item); + quest->AddTmpRewardItem(firstItem); + + int8 num_args = (int8)lua_interface->GetNumberOfArgs(state); + bool itemsAddedSuccessfully = true; + if(num_args > 4) + { + for(int8 n=5;nGetInt32Value(state, n); + Item* tmpItem = master_item_list.GetItem(new_item); + if(tmpItem) + { + Item* newTmpItem = new Item(tmpItem); + quest->AddTmpRewardItem(newTmpItem); + } + else + itemsAddedSuccessfully = false; + } + } + client->AddPendingQuestReward(quest, true, true, description); // queue for display + + lua_interface->ResetFunctionStack(state); + + lua_interface->SetBooleanValue(state, itemsAddedSuccessfully); + return 1; +} + +int EQ2Emu_lua_SetQuestRepeatable(lua_State* state) { + if (!lua_interface) + return 0; + + Quest* quest = lua_interface->GetQuest(state); + lua_interface->ResetFunctionStack(state); + if (!quest) { + lua_interface->LogError("%s: LUA SetQuestRepeatable command error: quest is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + quest->SetRepeatable(true); + return 0; +} + +int EQ2Emu_lua_GetArchetypeName(lua_State* state) { + + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA GetArchetypeName command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + int8 base_class = classes.GetBaseClass(spawn->GetAdventureClass()); + string ret = classes.GetClassNameCase(base_class); + if (ret.length() > 0) { + lua_interface->SetStringValue(state, ret.c_str()); + return 1; + } + + return 0; +} + +int EQ2Emu_lua_SendWaypoints(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer()) { + Client* client = player->GetClient(); + if (client) + client->SendWaypoints(); + } + return 0; +} + +int EQ2Emu_lua_AddWaypoint(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + string name = lua_interface->GetStringValue(state, 2); + int32 type = lua_interface->GetInt32Value(state, 3); + lua_interface->ResetFunctionStack(state); + if (type == 0) + type = 2; + if (name.length() > 0) { + if (player && player->IsPlayer()) { + Client* client = player->GetClient(); + if (client) + client->AddWaypoint(name, type); + } + } + return 0; +} + +int EQ2Emu_lua_RemoveWaypoint(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + string name = lua_interface->GetStringValue(state, 2); + lua_interface->ResetFunctionStack(state); + if (name.length() > 0) { + if (player && player->IsPlayer()) { + Client* client = player->GetClient(); + if (client) + client->RemoveWaypoint(name); + } + } + return 0; +} + +int EQ2Emu_lua_AddWard(lua_State* state) { + if (!lua_interface) + return 0; + + int32 damage = lua_interface->GetInt32Value(state); + bool keepWard = (lua_interface->GetInt8Value(state, 2) == 1); + int8 wardType = lua_interface->GetInt8Value(state, 3); + int8 damageTypes = lua_interface->GetInt8Value(state, 4); + int32 damageAbsorptionPercent = lua_interface->GetInt32Value(state, 5); + int32 damageAbsorptionMaxHealthPercent = lua_interface->GetInt32Value(state, 6); + int32 redirectDamagePercent = lua_interface->GetInt32Value(state, 7); + int32 maxHitCount = lua_interface->GetInt32Value(state, 8); + + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if(!spell || spell->resisted) { + return 0; + } + + bool ward_was_added = false; + + ZoneServer* zone = spell->caster->GetZone(); + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < spell->targets.size(); i++) { + Spawn* target = zone->GetSpawnByID(spell->targets.at(i)); + if (!target) + continue; + if (target->IsEntity()) { + // If the ward is already active remove it + if (((Entity*)target)->GetWard(spell->spell->GetSpellID())) + ((Entity*)target)->RemoveWard(spell->spell->GetSpellID()); + + // Create new ward info + WardInfo* ward = new WardInfo; + ward->Spell = spell; + ward->BaseDamage = damage; + ward->DamageLeft = damage; + ward->AbsorbAllDamage = (damage == 0) ? true : false; + + ward->keepWard = keepWard; + ward->WardType = wardType; + if (damageAbsorptionPercent > 100) + damageAbsorptionPercent = 100; + + ward->DamageAbsorptionPercentage = damageAbsorptionPercent; + + if (damageAbsorptionMaxHealthPercent > 100) + damageAbsorptionMaxHealthPercent = 100; + + ward->DamageAbsorptionMaxHealthPercent = damageAbsorptionMaxHealthPercent; + + ward->RedirectDamagePercent = redirectDamagePercent; + + ward->LastRedirectDamage = 0; + ward->LastAbsorbedDamage = 0; + ward->HitCount = 0; + + spell->num_triggers = maxHitCount; + spell->had_triggers = true; + spell->cancel_after_all_triggers = false; + ward->MaxHitCount = maxHitCount; + ward->RoundTriggered = false; + + if (wardType == WARD_TYPE_MAGICAL) + ward->DamageType = damageTypes; + + // Add the ward to the entity + ((Entity*)target)->AddWard(spell->spell->GetSpellID(), ward); + ward_was_added = true; + } + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + + if (ward_was_added && spell->caster->IsPlayer()) { + spell->had_dmg_remaining = true; + ClientPacketFunctions::SendMaintainedExamineUpdate(((Player*)spell->caster)->GetClient(), spell->slot_pos, damage, 1); + } + + return 0; +} + +int EQ2Emu_lua_AddToWard(lua_State* state) { + if (!lua_interface) + return 0; + + int32 amount = lua_interface->GetInt32Value(state); + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + WardInfo* ward = 0; + + if(!spell || spell->resisted) { + return 0; + } + ZoneServer* zone = spell->caster->GetZone(); + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + if (spell->targets.size() > 0 && zone->GetSpawnByID(spell->targets.at(0))->IsEntity()) { + Entity* target = (Entity*)zone->GetSpawnByID(spell->targets.at(0)); + ward = target->GetWard(spell->spell->GetSpellID()); + if (target && ward) { + ward->DamageLeft += amount; + if (ward->DamageLeft > ward->BaseDamage) + ward->DamageLeft = ward->BaseDamage; + + for (int32 i = 0; i < spell->targets.size(); i++) { + if (Spawn* spawn = zone->GetSpawnByID(spell->targets.at(i))) + zone->SendHealPacket(ward->Spell->caster, spawn, HEAL_PACKET_TYPE_REGEN_ABSORB, amount, ward->Spell->spell->GetName()); + } + } + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + + if (ward && spell->caster->IsPlayer()) + ClientPacketFunctions::SendMaintainedExamineUpdate(((Player*)spell->caster)->GetClient(), spell->slot_pos, ward->DamageLeft, 1); + + return 0; +} + +int EQ2Emu_lua_GetWardAmountLeft(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + if (!spell) { + lua_interface->LogError("%s: LUA GetWardAmountLeft command error: this command can only be used in a spell script", lua_interface->GetScriptName(state)); + return 0; + } + + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + if (spell->caster && spell->caster->GetZone() && spell->targets.size() > 0 && spell->caster->GetZone()->GetSpawnByID(spell->targets.at(0))->IsEntity()) { + Entity* target = (Entity*)spell->caster->GetZone()->GetSpawnByID(spell->targets.at(0)); + WardInfo* ward = target->GetWard(spell->spell->GetSpellID()); + if (ward) { + lua_interface->SetInt32Value(state, ward->DamageLeft); + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + return 1; + } + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + return 0; +} + +int EQ2Emu_lua_GetWardValue(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + if (!spell) { + lua_interface->LogError("%s: LUA GetWardValue command error: this command can only be used in a spell script", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + + string type = lua_interface->GetStringValue(state, 2); + + lua_interface->ResetFunctionStack(state); + + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + if (spell->caster && spell->caster->GetZone() && spell->targets.size() > 0 && spell->caster->GetZone()->GetSpawnByID(spell->targets.at(0))->IsEntity()) { + + Entity* target = (Entity*)spell->caster->GetZone()->GetSpawnByID(spell->targets.at(0)); + WardInfo* ward = target->GetWard(spell->spell->GetSpellID()); + if (ward) { + if (boost::iequals(type, "damageleft")) + lua_interface->SetInt32Value(state, ward->DamageLeft); + else if (boost::iequals(type, "basedamage")) + lua_interface->SetInt32Value(state, ward->BaseDamage); + else if (boost::iequals(type, "keepward")) + lua_interface->SetBooleanValue(state, ward->keepWard); + else if (boost::iequals(type, "wardtype")) + lua_interface->SetInt32Value(state, ward->WardType); + else if (boost::iequals(type, "dmgabsorptionpct")) + lua_interface->SetInt32Value(state, ward->DamageAbsorptionPercentage); + else if (boost::iequals(type, "dmgabsorptionmaxhealthpct")) + lua_interface->SetInt32Value(state, ward->DamageAbsorptionMaxHealthPercent); + else if (boost::iequals(type, "redirectdamagepercent")) + lua_interface->SetInt32Value(state, ward->RedirectDamagePercent); + else if (boost::iequals(type, "lastredirectdamage")) + lua_interface->SetInt32Value(state, ward->LastRedirectDamage); + else if (boost::iequals(type, "lastabsorbeddamage")) + lua_interface->SetInt32Value(state, ward->LastAbsorbedDamage); + else if (boost::iequals(type, "hitcount")) + lua_interface->SetInt32Value(state, ward->HitCount); + else if (boost::iequals(type, "maxhitcount")) + lua_interface->SetInt32Value(state, ward->MaxHitCount); + else + lua_interface->LogError("%s: LUA GetWardValue command argument type '%s' did not match any options", lua_interface->GetScriptName(state), type); + + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + return 1; + } + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + return 0; +} + +int EQ2Emu_lua_RemoveWard(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if(!spell) { + return 0; + } + + ZoneServer* zone = spell->caster->GetZone(); + Spawn* target = 0; + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < spell->targets.size(); i++) { + target = zone->GetSpawnByID(spell->targets.at(i)); + if (target && target->IsEntity()) { + ((Entity*)target)->RemoveWard(spell->spell->GetSpellID()); + } + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + + return 0; +} + +int EQ2Emu_lua_Interrupt(lua_State* state) +{ + + if (!lua_interface) + return 0; + + Spawn* caster = lua_interface->GetSpawn(state); // Second param in lua_interface->get functions defaults to 1 + Spawn* target = lua_interface->GetSpawn(state, 2); + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if (!caster) + { + lua_interface->LogError("%s: LUA Interrupt command error: caster is not a valid spawn", lua_interface->GetScriptName(state)); + return 0; + } + + if (!target) + { + lua_interface->LogError("%s: LUA Interrupt command error: target is not a valid spawn", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spell) { + lua_interface->LogError("%s: LUA Interrupt command error: spell is not a valid spawn", lua_interface->GetScriptName(state)); + return 0; + } + + if (!target->IsEntity() && !spell) + { + lua_interface->LogError("%s: LUA Interrupt command error: Target is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (!target && spell) { + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int8 i = 0; i < spell->targets.size(); i++) { + target = caster->GetZone()->GetSpawnByID(spell->targets.at(i)); + if (!target || !target->IsEntity()) + continue; + + ((Entity*)target)->GetZone()->GetSpellProcess()->Interrupted((Entity*)target, caster, SPELL_ERROR_INTERRUPTED); + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + else + caster->GetZone()->GetSpellProcess()->Interrupted((Entity*)target, caster, SPELL_ERROR_INTERRUPTED); + + return 0; +} + +int EQ2Emu_lua_Stealth(lua_State* state) { + if (!lua_interface) + return 0; + + int8 type = lua_interface->GetInt8Value(state); + Spawn* spawn = lua_interface->GetSpawn(state, 2); + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if (!spell) { + lua_interface->LogError("%s: LUA Stealth command error: must be used from spell script", lua_interface->GetScriptName(state)); + return 0; + } + + ZoneServer* zone = spell->caster->GetZone(); + + if (spawn) { + if (spawn->IsEntity()) { + if (type == 1) { + ((Entity*)spawn)->AddStealthSpell(spell); + if (!(spell->effect_bitmask & EFFECT_FLAG_STEALTH)) + spell->effect_bitmask += EFFECT_FLAG_STEALTH; + } + else if (type == 2) { + ((Entity*)spawn)->AddInvisSpell(spell); + if (!(spell->effect_bitmask & EFFECT_FLAG_INVIS)) + spell->effect_bitmask += EFFECT_FLAG_INVIS; + } + return 0; + } + else { + lua_interface->LogError("%s: LUA Stealth command error: target override is not Entity", lua_interface->GetScriptName(state)); + return 0; + } + } + else { + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < spell->targets.size(); i++) { + spawn = zone->GetSpawnByID(spell->targets.at(i)); + if (!spawn || !spawn->IsEntity()) + continue; + + if (type == 1) { + ((Entity*)spawn)->AddStealthSpell(spell); + if (!(spell->effect_bitmask & EFFECT_FLAG_STEALTH)) + spell->effect_bitmask += EFFECT_FLAG_STEALTH; + } + else if (type == 2) { + ((Entity*)spawn)->AddInvisSpell(spell); + if (!(spell->effect_bitmask & EFFECT_FLAG_INVIS)) + spell->effect_bitmask += EFFECT_FLAG_INVIS; + } + else { + lua_interface->LogError("%s: LUA Stealth command error: invalid stealth type given", lua_interface->GetScriptName(state)); + break; + } + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + return 0; +} + +int EQ2Emu_lua_IsStealthed(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA IsStealthed command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (spawn->IsEntity()) { + lua_interface->SetBooleanValue(state, ((Entity*)spawn)->IsStealthed()); + return 1; + } + else + lua_interface->LogError("%s: LUA IsStealthed command error: spawn is not entity", lua_interface->GetScriptName(state)); + return 0; +} + +int EQ2Emu_lua_IsInvis(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA IsInvis command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (spawn->IsEntity()) { + lua_interface->SetBooleanValue(state, ((Entity*)spawn)->IsInvis()); + return 1; + } + else + lua_interface->LogError("%s: LUA IsInvis command error: spawn is not entity", lua_interface->GetScriptName(state)); + return 0; +} + +int EQ2Emu_lua_HasItemEquipped(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + int32 item_id = lua_interface->GetInt32Value(state, 2); + + lua_interface->ResetFunctionStack(state); + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA HasItemEquipped command error: spawn is not player", lua_interface->GetScriptName(state)); + return 0; + } + lua_interface->SetBooleanValue(state, ((Player*)player)->GetEquipmentList()->HasItem(item_id)); + return 1; +} + +int EQ2Emu_lua_GetEquippedItemBySlot(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int8 slot = lua_interface->GetInt8Value(state, 2); + + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA GetEquippedItemBySlot command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA GetEquippedItemBySlot command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + Item* item = ((Entity*)spawn)->GetEquipmentList()->GetItem(slot); + if (!item) { + lua_interface->LogError("%s: LUA GetEquippedItemBySlot command error: item was not found in slot", lua_interface->GetScriptName(state)); + return 0; + } + lua_interface->SetItemValue(state, item); + return 1; +} + +int EQ2Emu_lua_GetEquippedItemByID(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + int32 id = lua_interface->GetInt32Value(state, 2); + + lua_interface->ResetFunctionStack(state); + if (!player) { + lua_interface->LogError("%s: LUA GetEquippedItemByID command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA GetEquippedItemByID command error: spawn is not player", lua_interface->GetScriptName(state)); + return 0; + } + Item* item = ((Player*)player)->GetEquipmentList()->GetItemFromItemID(id); + if (!item) { + lua_interface->LogError("%s: LUA GetEquippedItemByID command error: equipped item with used id not found", lua_interface->GetScriptName(state)); + return 0; + } + lua_interface->SetItemValue(state, item); + return 1; +} + +int EQ2Emu_lua_SetEquippedItemByID(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int8 slot = lua_interface->GetInt8Value(state, 2); + int32 item_id = lua_interface->GetInt32Value(state, 3); + + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA SetEquippedItemByID command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA SetEquippedItemByID command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + Item* item = master_item_list.GetItem(item_id); + if (!item) { + lua_interface->LogError("%s: LUA SetEquippedItemByID command error: equipped item with used id %u not found", item_id, lua_interface->GetScriptName(state)); + return 0; + } + + Item* copy = new Item(item); + bool result = ((Entity*)spawn)->GetEquipmentList()->AddItem(slot, copy); + + if(result) + { + ((Entity*)spawn)->SetEquipment(copy, slot); + spawn->vis_changed = true; + + if(spawn->IsPlayer()) + ((Player*)spawn)->GetClient()->SendEquipOrInvUpdateBySlot(slot); + } + else + { + safe_delete(copy); + } + + + lua_interface->SetBooleanValue(state, result); + return 1; +} + +int EQ2Emu_lua_SetEquippedItem(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int8 slot = lua_interface->GetInt8Value(state, 2); + Item* item = lua_interface->GetItem(state, 3); + + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA SetEquippedItem command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA SetEquippedItem command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + if (!item) { + lua_interface->LogError("%s: LUA SetEquippedItem command error: passed item not found", lua_interface->GetScriptName(state)); + return 0; + } + + bool result = ((Entity*)spawn)->GetEquipmentList()->AddItem(slot, item); + if(result) + { + ((Entity*)spawn)->SetEquipment(item, slot); + spawn->vis_changed = true; + + if(spawn->IsPlayer()) + ((Player*)spawn)->GetClient()->SendEquipOrInvUpdateBySlot(slot); + } + lua_interface->SetBooleanValue(state, result); + return 1; +} + +int EQ2Emu_lua_UnequipSlot(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int8 slot = lua_interface->GetInt8Value(state, 2); + bool no_delete_item = (lua_interface->GetBooleanValue(state, 3) == false); // if not set then we default to deleting it, otherwise if set to true we don't delete + + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA UnequipSlot command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA UnequipSlot command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + if(slot >= NUM_SLOTS) { + lua_interface->LogError("%s: LUA UnequipSlot command error: wrong slot id given %u, max %u", lua_interface->GetScriptName(state), slot, NUM_SLOTS); + return 0; + } + + if(spawn->IsPlayer() && ((Player*)spawn)->GetClient()) { + Item* item = ((Player*)spawn)->GetEquipmentList()->GetItem(slot); + if(item) { + item->save_needed = true; + if(no_delete_item) { + database.DeleteItem(((Player*)spawn)->GetClient()->GetCharacterID(), item, 0); + ((Player*)spawn)->GetEquipmentList()->RemoveItem(slot, no_delete_item); + } + else{ + Client* client = ((Player*)spawn)->GetClient(); + client->UnequipItem(item->details.index); + } + } + } + else + ((Entity*)spawn)->GetEquipmentList()->RemoveItem(slot, no_delete_item); + + ((Entity*)spawn)->SetEquipment(nullptr, slot); + spawn->vis_changed = true; + + if(spawn->IsPlayer()) + ((Player*)spawn)->GetClient()->SendEquipOrInvUpdateBySlot(slot); + + lua_interface->SetBooleanValue(state, true); + return 1; +} + +int EQ2Emu_lua_SetEquipment(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int8 slot = lua_interface->GetInt8Value(state, 2); + int16 type = lua_interface->GetInt16Value(state, 3); + int8 r = lua_interface->GetInt8Value(state, 4); + int8 g = lua_interface->GetInt8Value(state, 5); + int8 b = lua_interface->GetInt8Value(state, 6); + int8 h_r = lua_interface->GetInt8Value(state, 7); + int8 h_g = lua_interface->GetInt8Value(state, 8); + int8 h_b = lua_interface->GetInt8Value(state, 9); + + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA SetEquipment command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA SetEquipment command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + ((Entity*)spawn)->SetEquipment(slot, type, r, g, b, h_r, h_g, h_b); + spawn->vis_changed = true; + lua_interface->SetBooleanValue(state, true); + return 1; +} + +int EQ2Emu_lua_GetItemByID(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + int32 id = lua_interface->GetInt32Value(state, 2); + int8 count = lua_interface->GetInt8Value(state, 3); + bool include_bank = lua_interface->GetInt8Value(state, 4); + + lua_interface->ResetFunctionStack(state); + if (!player) { + lua_interface->LogError("%s: LUA GetItemByID command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA GetItemByID command error: spawn is not player", lua_interface->GetScriptName(state)); + return 0; + } + if (!count) + count = 1; + Item* item = ((Player*)player)->GetPlayerItemList()->GetItemFromID(id, count, include_bank); + if (!item) { + lua_interface->LogError("%s: LUA GetItemByID command error: item with used id not found", lua_interface->GetScriptName(state)); + return 0; + } + lua_interface->SetItemValue(state, item); + return 1; +} + +int EQ2Emu_lua_PlayAnimation(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 anim = lua_interface->GetInt32Value(state, 2); + Spawn* spawn2 = lua_interface->GetSpawn(state, 3); + int8 type = lua_interface->GetInt8Value(state, 4); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA PlayAnimation command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (spawn2) { + if (spawn2->IsPlayer()) { + if (type != 1 && type != 2) + spawn->GetZone()->PlayAnimation(spawn, anim, spawn2); + else + spawn->GetZone()->PlayAnimation(spawn, anim, spawn2, type); + return 0; + } + else { + lua_interface->LogError("%s: LUA PlayAnimation command error: second spawn not a player", lua_interface->GetScriptName(state)); + return 0; + } + } + else + spawn->GetZone()->PlayAnimation(spawn, anim); + return 0; +} + +int EQ2Emu_lua_IsPet(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA IsPet command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + lua_interface->SetBooleanValue(state, spawn->IsPet()); + return 1; +} + +int EQ2Emu_lua_GetOwner(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA GetOwner command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsNPC()) { + lua_interface->LogError("%s: LUA GetOwner command error: spawn is not a NPC", lua_interface->GetScriptName(state)); + return 0; + } + if (((NPC*)spawn)->GetOwner()) { + lua_interface->SetSpawnValue(state, ((NPC*)spawn)->GetOwner()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_SetTarget(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + Spawn* target = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA SetTarget command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn) { + lua_interface->LogError("%s: LUA SetTarget command error: target is not valid", lua_interface->GetScriptName(state)); + return 0; + } + spawn->SetTarget(target); + return 0; +} + +int EQ2Emu_lua_SetInCombat(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + bool val = lua_interface->GetBooleanValue(state, 2); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA SetInCombat command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA SetInCombat command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + ((Entity*)spawn)->InCombat(val); + + if (val) { + spawn->ClearRunningLocations(); + spawn->CalculateRunningLocation(true); + } + return 0; +} + +int EQ2Emu_lua_CompareSpawns(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn1 = lua_interface->GetSpawn(state); + Spawn* spawn2 = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + if (!spawn1) { + lua_interface->LogError("%s: LUA CompareSpawns command error: first spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn2) { + lua_interface->LogError("%s: LUA CompareSpawns command error: second spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + lua_interface->SetBooleanValue(state, (spawn1 == spawn2)); + return 1; +} + +int EQ2Emu_lua_ClearRunback(lua_State* state){ + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA ClearRunback command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsNPC()) { + lua_interface->LogError("%s: LUA ClearRunback command error: spawn is not an NPC", lua_interface->GetScriptName(state)); + return 0; + } + ((NPC*)spawn)->ClearRunback(); + return 0; +} + +int EQ2Emu_lua_Runback(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA Runback command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsNPC()) { + lua_interface->LogError("%s: LUA Runback command error: spawn is not an NPC", lua_interface->GetScriptName(state)); + return 0; + } + ((NPC*)spawn)->Runback(); + return 0; +} + +int EQ2Emu_lua_GetRunbackDistance(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA GetRunbackDistance command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsNPC()) { + lua_interface->LogError("%s: LUA GetRunbackDistance command error: spawn is not an NPC", lua_interface->GetScriptName(state)); + return 0; + } + lua_interface->SetFloatValue(state, ((NPC*)spawn)->GetRunbackDistance()); + return 1; +} + +int EQ2Emu_lua_IsCasting(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA IsCasting command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA IsCasting command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + lua_interface->SetBooleanValue(state, ((Entity*)spawn)->IsCasting()); + return 1; +} + +int EQ2Emu_lua_IsMezzed(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA IsMezzed command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA IsMezzed command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + lua_interface->SetBooleanValue(state, ((Entity*)spawn)->IsMezzed()); + return 1; +} + +int EQ2Emu_lua_IsStunned(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA IsStunned command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA IsStunned command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + lua_interface->SetBooleanValue(state, ((Entity*)spawn)->IsStunned()); + return 1; +} + +int EQ2Emu_lua_IsMezzedOrStunned(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA IsMezzedOrStunned command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA IsMezzedOrStunned command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + lua_interface->SetBooleanValue(state, ((Entity*)spawn)->IsMezzedOrStunned()); + return 1; +} + +int EQ2Emu_lua_ProcessSpell(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + Spawn* target = lua_interface->GetSpawn(state, 2); + float distance = lua_interface->GetFloatValue(state, 3); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA ProcessSpell command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!target) { + lua_interface->LogError("%s: LUA ProcessSpell command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsNPC()) { + lua_interface->LogError("%s: LUA ProcessSpell command error: spawn is not an NPC", lua_interface->GetScriptName(state)); + return 0; + } + if (!target->IsEntity()) { + lua_interface->LogError("%s: LUA ProcessSpell command error: target is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + lua_interface->SetBooleanValue(state, ((NPC*)spawn)->Brain()->ProcessSpell(((Entity*)target), distance)); + return 1; +} + +int EQ2Emu_lua_ProcessMelee(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + Spawn* target = lua_interface->GetSpawn(state, 2); + float distance = lua_interface->GetFloatValue(state, 3); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA ProcessMelee command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!target) { + lua_interface->LogError("%s: LUA ProcessMelee command error: target is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsNPC()) { + lua_interface->LogError("%s: LUA ProcessMelee command error: spawn is not an NPC", lua_interface->GetScriptName(state)); + return 0; + } + if (!target->IsEntity()) { + lua_interface->LogError("%s: LUA ProcessMelee command error: target is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + ((NPC*)spawn)->Brain()->ProcessMelee(((Entity*)target), distance); + return 0; +} + +int EQ2Emu_lua_HasRecovered(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA HasRecovered command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsNPC()) { + lua_interface->LogError("%s: LUA HasRecovered command error: spawn is not an NPC", lua_interface->GetScriptName(state)); + return 0; + } + lua_interface->SetBooleanValue(state, ((NPC*)spawn)->Brain()->HasRecovered()); + return 1; +} + +int EQ2Emu_lua_GetEncounterSize(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA GetEncounterSize command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsNPC()) { + lua_interface->LogError("%s: LUA GetEncounterSize command error: spawn is not an NPC", lua_interface->GetScriptName(state)); + return 0; + } + lua_interface->SetInt32Value(state, ((NPC*)spawn)->Brain()->GetEncounterSize()); + return 1; +} + +int EQ2Emu_lua_GetMostHated(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA GetMostHated command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsNPC()) { + lua_interface->LogError("%s: LUA GetMostHated command error: spawn is not an NPC", lua_interface->GetScriptName(state)); + return 0; + } + + Entity* hated = ((NPC*)spawn)->Brain()->GetMostHated(); + if (hated) { + lua_interface->SetSpawnValue(state, hated); + return 1; + } + + return 0; +} + +int EQ2Emu_lua_ClearHate(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + Spawn* hated = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA ClearHate command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsNPC()) { + lua_interface->LogError("%s: LUA ClearHate command error: spawn is not NPC", lua_interface->GetScriptName(state)); + return 0; + } + if (!hated) { + ((NPC*)spawn)->Brain()->ClearHate(); + return 0; + } + else + { + if (!hated->IsEntity()) { + lua_interface->LogError("%s: LUA ClearHate command error: second param is not entity", lua_interface->GetScriptName(state)); + return 0; + } + ((NPC*)spawn)->Brain()->ClearHate(((Entity*)hated)); + return 0; + } + return 0; +} + +int EQ2Emu_lua_ClearEncounter(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA ClearEncounter command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsNPC()) { + lua_interface->LogError("%s: LUA ClearEncounter command error: spawn is not an NPC", lua_interface->GetScriptName(state)); + return 0; + } + ((NPC*)spawn)->Brain()->ClearEncounter(); + return 0; +} + +int EQ2Emu_lua_GetEncounter(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA GetEncounter command error: Spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsNPC()) { + lua_interface->LogError("%s: LUA GetEncounter command error: spawn is not a NPC", lua_interface->GetScriptName(state)); + return 0; + } + + // Temp list to store hate list + vector* encounterList = ((NPC*)spawn)->Brain()->GetEncounter(); + + if (encounterList->size() == 0) { + safe_delete(encounterList); + return 0; + } + + lua_createtable(state, encounterList->size(), 0); + int newTable = lua_gettop(state); + for (int32 i = 0; i < encounterList->size(); i++) { + Spawn* temp = spawn->GetZone()->GetSpawnByID(encounterList->at(i)); + if (temp) + lua_interface->SetSpawnValue(state, temp); + lua_rawseti(state, newTable, i + 1); + } + + safe_delete(encounterList); + return 1; +} + +int EQ2Emu_lua_GetHateList(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA GetHateList command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsNPC()) { + lua_interface->LogError("%s: LUA GetHateList command error: spawn is not a NPC", lua_interface->GetScriptName(state)); + return 0; + } + + // Temp list to store hate list + vector* hateList = ((NPC*)spawn)->Brain()->GetHateList(); + + if (hateList->size() == 0) { + safe_delete(hateList); + return 0; + } + + lua_createtable(state, hateList->size(), 0); + int newTable = lua_gettop(state); + for (int32 i = 0; i < hateList->size(); i++) { + lua_interface->SetSpawnValue(state, hateList->at(i)); + lua_rawseti(state, newTable, i + 1); + } + + safe_delete(hateList); + return 1; +} + +int EQ2Emu_lua_HasGroup(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA HasGroup command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (spawn->IsPlayer()) { + if (((Player*)spawn)->GetGroupMemberInfo() && world.GetGroupManager()->GetGroupSize(((Player*)spawn)->GetGroupMemberInfo()->group_id) > 1) + lua_interface->SetBooleanValue(state, true); + else + lua_interface->SetBooleanValue(state, false); + + return 1; + } + else { + lua_interface->SetBooleanValue(state, spawn->HasSpawnGroup()); + return 1; + } +} + +int EQ2Emu_lua_SetCompleteFlag(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + lua_interface->ResetFunctionStack(state); + + if (!quest) { + lua_interface->LogError("%s: LUA SetCompleteFlag command error: quest is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + quest->SetCompletedFlag(true); + return 0; +} + +int EQ2Emu_lua_HasSpellEffect(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + int32 spellID = lua_interface->GetInt32Value(state, 2); + int8 tier = lua_interface->GetInt8Value(state, 3); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA HasSpellEffect command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA HasSpellEffect command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (spellID == 0) { + lua_interface->LogError("%s: LUA HasSpellEffect command error: spell id is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + SpellEffects* effect = ((Entity*)spawn)->GetSpellEffect(spellID); + if (effect) { + if (tier > 0) { + // If a tier was passed chec to see if it is the same as the effect + if (tier == effect->tier) + lua_interface->SetBooleanValue(state, true); + else + lua_interface->SetBooleanValue(state, false); + + return 1; + } + else { + // Have an effect but no tier was passed so return true + lua_interface->SetBooleanValue(state, true); + } + + return 1; + } + + // no effect so return false + lua_interface->SetBooleanValue(state, false); + return 1; +} + +int EQ2Emu_lua_AddSpawnIDAccess(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 id = lua_interface->GetInt32Value(state, 2); + ZoneServer* zone = lua_interface->GetZone(state, 3); + lua_interface->ResetFunctionStack(state); + Spawn* spawn2 = 0; + vector list; + + if (!spawn) { + lua_interface->LogError("%s: LUA AddSpawnIDAccess command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + //If zone not provided, use spawn's zone + if (!zone) + zone = spawn->GetZone(); + + list = zone->GetSpawnsByID(id); + if (list.size() == 0) { + lua_interface->LogError("%s: LUA AddSpawnIDAccess command error: GetSpawnsByID returned no spawns", lua_interface->GetScriptName(state)); + return 0; + } + + vector::iterator itr = list.begin(); + for (int8 i = 0; i < list.size(); i++) { + spawn2 = itr[i]; + if (spawn2) + spawn2->AddAllowAccessSpawn(spawn); + } + + return 0; +} + +int EQ2Emu_lua_RemoveSpawnIDAccess(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 id = lua_interface->GetInt32Value(state, 2); + ZoneServer* zone = lua_interface->GetZone(state, 3); + lua_interface->ResetFunctionStack(state); + Spawn* spawn2 = 0; + + if (!spawn) { + lua_interface->LogError("%s: LUA RemoveSpawnIDAccess command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + //If zone not provided, use spawn's zone + if (!zone) + zone = spawn->GetZone(); + + vector list = zone->GetSpawnsByID(id); + vector::iterator itr = list.begin(); + if (list.size() == 0) { + lua_interface->LogError("%s: LUA RemoveSpawnIDAccess command error: GetSpawnsByID returned no spawns", lua_interface->GetScriptName(state)); + return 0; + } + for (int8 i = 0; i < list.size(); i++) { + spawn2 = itr[i]; + if (spawn2) + spawn2->RemoveSpawnAccess(spawn); + } + + return 0; +} + +int EQ2Emu_lua_SetQuestYellow(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + lua_interface->ResetFunctionStack(state); + if (!quest) { + lua_interface->LogError("%s: LUA SetQuestYellow command error: quest is not valid", lua_interface->GetScriptName(state)); + return 0; + } + quest->SetYellowName(true); + return 0; +} + +int EQ2Emu_lua_CanReceiveQuest(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 quest_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA CanReceieveQuest command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsPlayer()) { + lua_interface->LogError("%s: LUA CanReceieveQuest command error: spawn is not player", lua_interface->GetScriptName(state)); + return 0; + } + lua_interface->SetBooleanValue(state, ((Player*)spawn)->CanReceiveQuest(quest_id)); + return 1; +} + +int EQ2Emu_lua_SetSuccessTimer(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA SetSuccessTimer command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsPlayer()) { + lua_interface->LogError("%s: LUA SetSuccessTimer command error: spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + ZoneServer* zone = spawn->GetZone(); + if (!zone) { + lua_interface->LogError("%s: LUA SetSuccessTimer command error: unable to get a valid zone for the given spawn", lua_interface->GetScriptName(state)); + return 0; + } + + Instance_Type iType = zone->GetInstanceType(); + if (iType == SOLO_LOCKOUT_INSTANCE || + iType == GROUP_LOCKOUT_INSTANCE || + iType == RAID_LOCKOUT_INSTANCE || + iType == SOLO_PERSIST_INSTANCE || + iType == GROUP_PERSIST_INSTANCE || + iType == RAID_PERSIST_INSTANCE) { + InstanceData* data = ((Player*)spawn)->GetCharacterInstances()->FindInstanceByZoneID(spawn->GetZone()->GetZoneID()); + if (data) { + // Check to see if the timer has already been set, if it has return out. + if (Timer::GetUnixTimeStamp() <= data->last_success_timestamp + data->success_lockout_time) + return 0; + + + database.UpdateCharacterInstance(((Player*)spawn)->GetCharacterID(), string(spawn->GetZone()->GetZoneName()), spawn->GetZone()->GetInstanceID(), 1, Timer::GetUnixTimeStamp()); + data->last_success_timestamp = Timer::GetUnixTimeStamp(); + + if(spawn->IsPlayer()) { + Client* client = ((Player*)spawn)->GetClient(); + if (client) { + string time_msg = ""; + int32 time = data->success_lockout_time; + int16 hour; + int8 min; + int8 sec; + hour = time / 3600; + time = time % 3600; + min = time / 60; + time = time % 60; + sec = time; + + if (hour > 0) { + char temp[10]; + sprintf(temp, " %i", hour); + time_msg.append(temp); + time_msg.append(" hour"); + time_msg.append((hour > 1) ? "s" : ""); + } + if (min > 0) { + char temp[5]; + sprintf(temp, " %i", min); + time_msg.append(temp); + time_msg.append(" minute"); + time_msg.append((min > 1) ? "s" : ""); + } + // Only add seconds if minutes and hours are 0 + if (hour == 0 && min == 0 && sec > 0) { + char temp[5]; + sprintf(temp, " %i", sec); + time_msg.append(temp); + time_msg.append(" second"); + time_msg.append((sec > 1) ? "s" : ""); + } + + client->Message(CHANNEL_COLOR_YELLOW, "The success zone reuse timer for %s has been set. You can return in%s.", data->zone_name.c_str(), time_msg.c_str()); + } + else { + lua_interface->LogError("LUA SetSuccessTimer command error: instance id %u client missing for player %s", spawn->GetZone()->GetInstanceID(), spawn->GetName()); + } + } + } + else + lua_interface->LogError("LUA SetSuccessTimer command error: unable to get instance data for instance %u for player %s", spawn->GetZone()->GetInstanceID(), spawn->GetName()); + } + else + lua_interface->LogError("%s: LUA SetSuccessTimer command error: current zone for given spawn is not a lockout or persistent instance", lua_interface->GetScriptName(state)); + + + return 0; +} + +int EQ2Emu_lua_SetFailureTimer(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA SetFailureTimer command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsPlayer()) { + lua_interface->LogError("%s: LUA SetFailureTimer command error: spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + ZoneServer* zone = spawn->GetZone(); + if (!zone) { + lua_interface->LogError("%s: LUA SetFailureTimer command error: unable to get a valid zone for the given spawn", lua_interface->GetScriptName(state)); + return 0; + } + + Instance_Type iType = zone->GetInstanceType(); + if (iType == SOLO_LOCKOUT_INSTANCE || + iType == GROUP_LOCKOUT_INSTANCE || + iType == RAID_LOCKOUT_INSTANCE || + iType == SOLO_PERSIST_INSTANCE || + iType == GROUP_PERSIST_INSTANCE || + iType == RAID_PERSIST_INSTANCE) { + InstanceData* data = ((Player*)spawn)->GetCharacterInstances()->FindInstanceByZoneID(spawn->GetZone()->GetZoneID()); + if (data) { + // Check to see if the timer has already been set, if it has return out. + if (Timer::GetUnixTimeStamp() <= data->last_failure_timestamp + data->failure_lockout_time) + return 0; + + database.UpdateCharacterInstance(((Player*)spawn)->GetCharacterID(), string(spawn->GetZone()->GetZoneName()), spawn->GetZone()->GetInstanceID(), 2, Timer::GetUnixTimeStamp()); + data->last_failure_timestamp = Timer::GetUnixTimeStamp(); + + if(spawn->IsPlayer()) { + Client* client = ((Player*)spawn)->GetClient(); + if (client) { + string time_msg = ""; + int32 time = data->failure_lockout_time; + int16 hour; + int8 min; + int8 sec; + hour = time / 3600; + time = time % 3600; + min = time / 60; + time = time % 60; + sec = time; + + if (hour > 0) { + char temp[10]; + sprintf(temp, " %i", hour); + time_msg.append(temp); + time_msg.append(" hour"); + time_msg.append((hour > 1) ? "s" : ""); + } + if (min > 0) { + char temp[5]; + sprintf(temp, " %i", min); + time_msg.append(temp); + time_msg.append(" minute"); + time_msg.append((min > 1) ? "s" : ""); + } + // Only add seconds if minutes and hours are 0 + if (hour == 0 && min == 0 && sec > 0) { + char temp[5]; + sprintf(temp, " %i", sec); + time_msg.append(temp); + time_msg.append(" second"); + time_msg.append((sec > 1) ? "s" : ""); + } + + client->Message(CHANNEL_COLOR_YELLOW, "The failure zone reuse timer for %s has been set. You can return in%s", data->zone_name.c_str(), time_msg.c_str()); + } + } + else { + lua_interface->LogError("LUA SetFailureTimer command error: instance id %u client missing for player %s", spawn->GetZone()->GetInstanceID(), spawn->GetName()); + } + } + else + lua_interface->LogError("LUA SetFailureTimer command error: unable to get instance data for instance %u for player %s", spawn->GetZone()->GetInstanceID(), spawn->GetName()); + } + else + lua_interface->LogError("%s: LUA SetFailureTimer command error: current zone for given spawn is not a lockout or persistent instance", lua_interface->GetScriptName(state)); + + return 0; +} + +int EQ2Emu_lua_IsGroundSpawn(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA IsGroundSpawn command error: not a valid spawn", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetBooleanValue(state, spawn->IsGroundSpawn()); + return 1; +} + +int EQ2Emu_lua_CanHarvest(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + Spawn* ground = lua_interface->GetSpawn(state, 2); + + if (!player) { + lua_interface->LogError("%s: LUA CanHarvest command error: not a valid spawn", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA CanHarvest command error: spawn is not a player", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + + if (!ground) { + lua_interface->LogError("%s: LUA CanHarvest command error: not a valid spawn", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + + if (!ground->IsGroundSpawn()) { + lua_interface->LogError("%s: LUA CanHarvest command error: spawn is not a ground spawn", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + + vector* groundspawn_entries = player->GetZone()->GetGroundSpawnEntries(((GroundSpawn*)ground)->GetGroundSpawnEntryID()); + + if (!groundspawn_entries) { + lua_interface->LogError("LUA CanHarvest command error: No groundspawn entries assigned to groundspawn id: %u", ((GroundSpawn*)ground)->GetGroundSpawnEntryID()); + lua_interface->ResetFunctionStack(state); + return 0; + } + + Skill* skill = 0; + string collection_skill = string(((GroundSpawn*)ground)->GetCollectionSkill()); + if (collection_skill == "Collecting") + skill = ((Player*)player)->GetSkillByName("Gathering"); + else + skill = ((Player*)player)->GetSkillByName(collection_skill.c_str()); + + if (!skill) { + lua_interface->LogError("LUA CanHarvest command error: Player '%s' lacks the skill: '%s'", player->GetName(), collection_skill.c_str()); + lua_interface->ResetFunctionStack(state); + return 0; + } + + + + vector::iterator itr; + GroundSpawnEntry* entry = 0; + bool can_harvest = false; + sint32 min_skill = -1; + int16 totalSkill = skill->current_val; + int32 skillID = master_item_list.GetItemStatIDByName(collection_skill); + if(skillID != 0xFFFFFFFF) + { + ((Entity*)player)->MStats.lock(); + totalSkill += ((Entity*)player)->stats[skillID]; + ((Entity*)player)->MStats.unlock(); + } + + // first, iterate through groundspawn_entries, discard tables player cannot use + for (itr = groundspawn_entries->begin(); itr != groundspawn_entries->end(); itr++) + { + entry = *itr; + + if (min_skill == -1 || entry->min_skill_level < min_skill) + min_skill = entry->min_skill_level; + // if player lacks skill, skip table + if (entry->min_skill_level > totalSkill) + continue; + // if bonus, but player lacks level, skip table + if (entry->bonus_table && (player->GetLevel() < entry->min_adventure_level)) + continue; + + can_harvest = true; + break; + } + + lua_interface->SetBooleanValue(state, can_harvest); + + // If false, send the message to the client + if (!can_harvest) { + Client* client = ((Player*)player)->GetClient(); + if (client) { + string msg = "You do not have enough skill to "; + if (collection_skill == "Gathering" || collection_skill == "Collecting") + msg.append("gather"); + else if (collection_skill == "Mining") + msg.append("mine"); + else if (collection_skill == "Trapping") + msg.append("trap"); + else if (collection_skill == "Foresting") + msg.append("forest"); + else if (collection_skill == "Fishing") + msg.append("catch"); + + msg.append(" the %s. It requires %i %s skill, and your skill is %i."); + client->Message(CHANNEL_HARVESTING_WARNINGS, msg.c_str(), ground->GetName(), min_skill, skill->name.data.c_str(), totalSkill); + // You do not have enough skill to catch the band of fish. It requires 20 Fishing skill, and your skill is 12. + } + } + lua_interface->ResetFunctionStack(state); + return 1; +} + +int EQ2Emu_lua_HasRecipeBook(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + int32 recipe_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!player) { + lua_interface->LogError("%s: LUA HasRecipeBook command error, Spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA HasRecipeBook command error, Spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + bool ret = ((Player*)player)->HasRecipeBook(recipe_id); + lua_interface->SetBooleanValue(state, ret); + return 1; +} + +int EQ2Emu_lua_SummonDumbFirePet(lua_State* state) { + // Check to see if we have a valid lua_interface + if (!lua_interface) + return 0; + + // Get the spawn that is getting the pet + Spawn* spawn = lua_interface->GetSpawn(state); + Spawn* target = lua_interface->GetSpawn(state, 2); + // Get the DB ID of the pet + int32 pet_id = lua_interface->GetInt32Value(state, 3); + + float x = lua_interface->GetFloatValue(state, 4); + float y = lua_interface->GetFloatValue(state, 5); + float z = lua_interface->GetFloatValue(state, 6); + // Get the spell that this command was called from + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + // Check to make sure the spawn pointer is valid + if (!spawn) { + lua_interface->LogError("%s: LUA SummonDumbFirePet command error: Spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + // Check to make sure the spawn is an entity + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA SummonDumbFirePet command error: Spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (!target) { + lua_interface->LogError("%s: LUA SummonDumbFirePet command error: target is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!target->IsEntity()) { + lua_interface->LogError("%s: LUA SummonDumbFirePet command error: target is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + // Check to see if the DB ID for the pet is set + if (pet_id == 0) { + lua_interface->LogError("%s: LUA SummonDumbFirePet command error: pet_id can not be set to 0", lua_interface->GetScriptName(state)); + return 0; + } + + // Check to see if the pointer to the spell is valid + if (!luaspell) { + lua_interface->LogError("%s: LUA SummonDumbFirePet command error: valid spell not found, SummonPet can only be used in spell scripts", lua_interface->GetScriptName(state)); + return 0; + } + + if(luaspell->resisted) { + return 0; + } + + // Get a pointer to a spawn with the given DB ID and check if the pointer is valid + Spawn* pet = spawn->GetZone()->GetSpawn(pet_id); + if (!pet) { + lua_interface->LogError("LUA SummonDumbFirePet command error: Could not find spawn with id of %u.", pet_id); + return 0; + } + + // Check to make sure the pet is an npc + if (!pet->IsNPC()) { + lua_interface->LogError("LUA SummonDumbFirePet command error: id (%u) did not point to a npc", pet_id); + return 0; + } + + if (x == 0) + x = spawn->GetX(); + + if (y == 0) + y = spawn->GetY(); + + if (z == 0) + z = spawn->GetZ(); + + // Spawn the pet at the same location as the owner + pet->SetX(x); + pet->SetY(y); + pet->SetZ(z); + pet->SetLocation(spawn->GetLocation()); + pet->SetHeading(spawn->GetHeading()); + spawn->GetZone()->AddSpawn(pet); + + const char* spawn_script = world.GetSpawnScript(pet_id); + if(spawn_script && lua_interface->GetSpawnScript(spawn_script) != 0){ + spawn->SetSpawnScript(string(spawn_script)); + spawn->GetZone()->CallSpawnScript(spawn, SPAWN_SCRIPT_SPAWN); + } + + std::string petName = std::string(""); + if(spawn->IsEntity()) { + petName = ((Entity*)spawn)->GetInfoStruct()->get_pet_name(); + } + + if(petName.size() < 1) { + int16 rand_index = MakeRandomInt(0, spawn->GetZone()->pet_names.size() - 1); + petName = spawn->GetZone()->pet_names.at(rand_index); + LogWrite(PET__DEBUG, 0, "Pets", "Randomize Pet Name: '%s' (rand: %i)", petName.c_str(), rand_index); + } + + // Set the pets name + pet->SetName(petName.c_str()); + + // Set the level of the pet to the owners level + pet->SetLevel(spawn->GetLevel()); + // Set the faction of the pet to the same faction as the owner + pet->SetFactionID(spawn->GetFactionID()); + // Set the spawn as a pet + pet->SetPet(true); + // Give a pointer of the owner to the pet + ((NPC*)pet)->SetOwner((Entity*)spawn); + + // Set the pet type + ((NPC*)pet)->SetPetType(PET_TYPE_DUMBFIRE); + // Set the spell id used to create this pet + ((NPC*)pet)->SetPetSpellID(luaspell->spell->GetSpellData()->id); + // Set the spell tier used to create this pet + ((NPC*)pet)->SetPetSpellTier(luaspell->spell->GetSpellData()->tier); + // Set the pets spawn type to 6 + pet->SetSpawnType(6); + // Set the pets brain + ((NPC*)pet)->SetBrain(new DumbFirePetBrain((NPC*)pet, (Entity*)target, luaspell->spell->GetSpellDuration() * 100)); + // Check to see if the pet has a subtitle + if (strlen(pet->GetSubTitle()) > 0) { + // Add the players name to the front of the sub title + string pet_subtitle; + pet_subtitle.append(spawn->GetName()).append("'s ").append(pet->GetSubTitle()); + LogWrite(PET__DEBUG, 0, "Pets", "Pet Subtitle: '%s'", pet_subtitle.c_str()); + // Set the pets subtitle to the new one + pet->SetSubTitle(pet_subtitle.c_str()); + } + + // Set the pet as the return value for this function + lua_interface->SetSpawnValue(state, pet); + return 1; +} + +int EQ2Emu_lua_SpawnMove(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + Spawn* player = lua_interface->GetSpawn(state, 2); + float max_distance = lua_interface->GetFloatValue(state, 3); + string type = lua_interface->GetStringValue(state, 4); + lua_interface->ResetFunctionStack(state); + + if (!spawn || (spawn && spawn->IsPlayer())) { + lua_interface->LogError("%s: LUA SpawnMove command error: first param spawn is not valid or is player", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player || !player->IsPlayer()) { + lua_interface->LogError("%s: LUA SpawnMove command error: second param is not player", lua_interface->GetScriptName(state)); + return 0; + } + + Client* client = ((Player*)player)->GetClient(); + + if (!client) { + lua_interface->LogError("%s: LUA SpawnMove command error: could not find client", lua_interface->GetScriptName(state)); + return 0; + } + + //Set max_distance to default if not set or not proper value + if (max_distance <= 0) + max_distance = 500; + PacketStruct* packet = configReader.getStruct("WS_MoveObjectMode", client->GetVersion()); + if (packet) { + float unknown2_3 = 0; + int8 placement_mode = 0; + if (type == "wall") { + placement_mode = 2; + unknown2_3 = 150; + } + else if (type == "ceiling") + placement_mode = 1; + packet->setDataByName("placement_mode", placement_mode); + packet->setDataByName("spawn_id", ((Player*)player)->GetIDWithPlayerSpawn(spawn)); + packet->setDataByName("model_type", spawn->GetModelType()); + packet->setDataByName("unknown", 1); //size + packet->setDataByName("unknown2", 1); //size 2 + packet->setDataByName("unknown2", .5, 1); //size 3 + packet->setDataByName("unknown2", 3, 2); + packet->setDataByName("unknown2", unknown2_3, 3); + packet->setDataByName("max_distance", max_distance); + packet->setDataByName("CoEunknown", 0xFFFFFFFF); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + return 0; +} + +int EQ2Emu_lua_GetItemType(lua_State* state) { + if (!lua_interface) + return 0; + Item* item = lua_interface->GetItem(state); + lua_interface->ResetFunctionStack(state); + + if (!item) { + lua_interface->LogError("%s: LUA GetItemType command error: item pointer is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetInt32Value(state, item->generic_info.item_type); + return 1; +} + +int EQ2Emu_lua_GetItemEffectType(lua_State* state) { + if (!lua_interface) + return 0; + Item* item = lua_interface->GetItem(state); + lua_interface->ResetFunctionStack(state); + + if (!item) { + lua_interface->LogError("%s: LUA GetItemEffectType command error: item pointer is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetInt32Value(state, item->effect_type); + return 1; +} + +int EQ2Emu_lua_AddTransportSpawn(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA AddTransportSpawn command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (spawn->GetZone()) + spawn->GetZone()->AddTransportSpawn(spawn); + + return 0; +} + +int EQ2Emu_lua_IsTransportSpawn(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA AddTransportSpawn command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetBooleanValue(state, spawn->IsTransportSpawn()); + return 1; +} + +int EQ2Emu_lua_GetSkillValue(lua_State* state) { + if (!lua_interface) + return 0; + + Skill* skill = lua_interface->GetSkill(state); + lua_interface->ResetFunctionStack(state); + if (!skill) { + lua_interface->LogError("%s: LUA GetSkillValue command error: skill is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetInt32Value(state, skill->current_val); + return 1; +} + +int EQ2Emu_lua_GetSkillMaxValue(lua_State* state) { + if (!lua_interface) + return 0; + + Skill* skill = lua_interface->GetSkill(state); + lua_interface->ResetFunctionStack(state); + if (!skill) { + lua_interface->LogError("%s: LUA GetSkillMaxValue command error: skill is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetInt32Value(state, skill->max_val); + return 1; +} + +int EQ2Emu_lua_GetSkillName(lua_State* state) { + if (!lua_interface) + return 0; + + Skill* skill = lua_interface->GetSkill(state); + lua_interface->ResetFunctionStack(state); + if (!skill) { + lua_interface->LogError("%s: LUA GetSkillName command error: skill is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetStringValue(state, skill->name.data.c_str()); + return 1; +} + +int EQ2Emu_lua_SetSkillMaxValue(lua_State* state) { + if (!lua_interface) + return 0; + + Skill* skill = lua_interface->GetSkill(state); + int16 value = lua_interface->GetInt16Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (!skill) { + lua_interface->LogError("%s: LUA SetSkillMaxValue command error: skill is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + skill->max_val = value; + if (skill->max_val < skill->current_val) + skill->current_val = skill->max_val; + + return 0; +} + +int EQ2Emu_lua_SetSkillValue(lua_State* state) { + if (!lua_interface) + return 0; + + Skill* skill = lua_interface->GetSkill(state); + int16 value = lua_interface->GetInt16Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (!skill) { + lua_interface->LogError("%s: LUA SetSkillValue command error: skill is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (value > skill->max_val) + skill->current_val = skill->max_val; + else + skill->current_val = value; + + return 0; +} + +int EQ2Emu_lua_HasSkill(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + int32 skill_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (skill_id > 0 && player && player->IsPlayer()) { + lua_interface->SetBooleanValue(state, ((Player*)player)->skill_list.HasSkill(skill_id)); + return 1; + } + return 0; +} + +int EQ2Emu_lua_AddSkill(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player_spawn = lua_interface->GetSpawn(state); + int32 skill_id = lua_interface->GetInt32Value(state, 2); + int16 current_val = lua_interface->GetInt16Value(state, 3); + int16 max_val = lua_interface->GetInt16Value(state, 4); + bool more_to_add = lua_interface->GetBooleanValue(state, 5); + lua_interface->ResetFunctionStack(state); + if (skill_id > 0 && current_val > 0 && max_val > 0) { + if (player_spawn && player_spawn->IsPlayer()) { + Player* player = (Player*)player_spawn; + bool added = false; + if (!player->skill_list.HasSkill(skill_id)) { + player->AddSkill(skill_id, current_val, max_val, true); + added = true; + } + if (!more_to_add) { //need to send update regardless, even if THIS skill wasn't added, otherwise if you have a list and the last item wasn't added but the previous ones were, it wouldn't send the update + Client* client = player->GetClient(); + if (client) { + EQ2Packet* packet = player->GetSkills()->GetSkillPacket(client->GetVersion()); + if (packet) + client->QueuePacket(packet); + } + } + if (added) { + lua_interface->SetBooleanValue(state, true); + return 1; + } + } + else { + lua_interface->LogError("%s: LUA AddSkill command error: Given spawn is not a player", lua_interface->GetScriptName(state)); + } + } + else { + lua_interface->LogError("%s: LUA AddSkill command error: Required parameters not set", lua_interface->GetScriptName(state)); + } + lua_interface->SetBooleanValue(state, false); + return 1; +} + +int EQ2Emu_lua_RemoveSkill(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player_spawn = lua_interface->GetSpawn(state); + int32 skill_id = lua_interface->GetInt32Value(state, 2); + bool more_to_remove = lua_interface->GetBooleanValue(state, 3); + lua_interface->ResetFunctionStack(state); + if (skill_id > 0) { + if (player_spawn && player_spawn->IsPlayer()) { + Player* player = (Player*)player_spawn; + if (player->skill_list.HasSkill(skill_id)) { + player->RemovePlayerSkill(skill_id, true); + if (!more_to_remove) { + Client* client = player->GetClient(); + if (client) { + EQ2Packet* packet = player->GetSkills()->GetSkillPacket(client->GetVersion()); + if (packet) + client->QueuePacket(packet); + } + } + } + } + else { + lua_interface->LogError("%s: LUA RemoveSkill command error: Given spawn is not a player", lua_interface->GetScriptName(state)); + } + } + else { + lua_interface->LogError("%s: LUA RemoveSkill command error: skill_id not set", lua_interface->GetScriptName(state)); + } + return 0; +} + +int EQ2Emu_lua_IncreaseSkillCapsByType(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player_spawn = lua_interface->GetSpawn(state); + int8 skill_type = lua_interface->GetInt8Value(state, 2); + int16 amount = lua_interface->GetInt8Value(state, 3); + bool more_to_increase = lua_interface->GetBooleanValue(state, 4); + lua_interface->ResetFunctionStack(state); + if (amount > 0 && skill_type < 100) { + if (player_spawn && player_spawn->IsPlayer()) { + Player* player = (Player*)player_spawn; + player->skill_list.IncreaseSkillCapsByType(skill_type, amount); + if (!more_to_increase) { + Client* client = player->GetClient(); + if (client) { + EQ2Packet* packet = player->GetSkills()->GetSkillPacket(client->GetVersion()); + if (packet) + client->QueuePacket(packet); + } + } + } + else { + lua_interface->LogError("%s: LUA IncreaseSkillCapsByType command error: Given spawn is not a player", lua_interface->GetScriptName(state)); + } + } + else { + lua_interface->LogError("%s: LUA IncreaseSkillCapsByType command error: Invalid parameters", lua_interface->GetScriptName(state)); + } + return 0; +} + +int EQ2Emu_lua_GetSkill(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + string name = lua_interface->GetStringValue(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA GetSkill command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA GetSkill command error: spawn is not a valid entity", lua_interface->GetScriptName(state)); + return 0; + } + + Skill* skill = ((Entity*)spawn)->GetSkillByName(name.c_str()); + if (skill) { + lua_interface->SetSkillValue(state, skill); + return 1; + } + + return 0; +} + +int EQ2Emu_lua_AddProc(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + int8 type = lua_interface->GetInt8Value(state, 2); + float chance = lua_interface->GetFloatValue(state, 3); + Item* item = lua_interface->GetItem(state, 4); + bool use_all_spelltargets = (lua_interface->GetInt8Value(state, 5) == 1); + LuaSpell* spell = 0; + + if (!item) + spell = lua_interface->GetCurrentSpell(state); + + if (!spawn && (!spell || !use_all_spelltargets)) { + lua_interface->LogError("%s: LUA AddProc command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if ((!spell || use_all_spelltargets) && spawn && !spawn->IsEntity()) { + lua_interface->LogError("%s: LUA AddProc command error: spawn is not a valid entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (!item && !spell) { + lua_interface->LogError("%s: LUA AddProc command error: can only use with an item provided or inside a spell script", lua_interface->GetScriptName(state)); + return 0; + } + + if(spell && spell->resisted) { + return 0; + } + + if (spell && spell->caster && spell->caster->GetZone() && use_all_spelltargets) { + Spawn* target; + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int8 i = 0; i < spell->targets.size(); i++) { + target = spell->caster->GetZone()->GetSpawnByID(spell->targets.at(i)); + if (!target || !target->IsEntity()) + continue; + + ((Entity*)target)->AddProc(type, chance, item, spell); + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + else + ((Entity*)spawn)->AddProc(type, chance, item, spell); + + return 0; +} + +int EQ2Emu_lua_AddProcExt(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + int8 type = lua_interface->GetInt8Value(state, 2); + int8 damage_type = lua_interface->GetInt8Value(state, 3); + float chance = lua_interface->GetFloatValue(state, 4); + int8 hp_ratio = lua_interface->GetInt8Value(state, 5); + bool below_health = lua_interface->GetBooleanValue(state, 6); + bool target_health = lua_interface->GetBooleanValue(state, 7); + Item* item = lua_interface->GetItem(state, 8); + bool use_all_spelltargets = (lua_interface->GetInt8Value(state, 9) == 1); + bool extended_version = true; + LuaSpell* spell = 0; + + if (!item) + spell = lua_interface->GetCurrentSpell(state); + + if (!spawn && (!spell || !use_all_spelltargets)) { + lua_interface->LogError("%s: LUA AddProc command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if ((!spell || use_all_spelltargets) && spawn && !spawn->IsEntity()) { + lua_interface->LogError("%s: LUA AddProc command error: spawn is not a valid entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (!item && !spell) { + lua_interface->LogError("%s: LUA AddProc command error: can only use with an item provided or inside a spell script", lua_interface->GetScriptName(state)); + return 0; + } + + if(spell && spell->resisted) { + return 0; + } + + if (spell && spell->caster && spell->caster->GetZone() && use_all_spelltargets) { + Spawn* target; + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int8 i = 0; i < spell->targets.size(); i++) { + target = spell->caster->GetZone()->GetSpawnByID(spell->targets.at(i)); + if (!target || !target->IsEntity()) + continue; + + ((Entity*)target)->AddProc(type, chance, item, spell, damage_type, hp_ratio, below_health, target_health, extended_version); + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + else + ((Entity*)spawn)->AddProc(type, chance, item, spell); + + return 0; +} + +int EQ2Emu_lua_RemoveProc(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + Item* item = lua_interface->GetItem(state, 2); + LuaSpell* spell = 0; + + if (!spawn) { + lua_interface->LogError("%s: LUA RemoveProc command error: spawn is not valid", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA RemoveProc command error: spawn is not a valid entity", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + + if (!item) + spell = lua_interface->GetCurrentSpell(state); + + lua_interface->ResetFunctionStack(state); + + if (!item && !spell) { + lua_interface->LogError("%s: LUA RemoveProc command error: can only use with an item provided or inside a spell script", lua_interface->GetScriptName(state)); + return 0; + } + + if (spell && spell->caster && spell->caster->GetZone()) { + Spawn* target; + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int8 i = 0; i < spell->targets.size(); i++) { + target = spell->caster->GetZone()->GetSpawnByID(spell->targets.at(i)); + if (!target || !target->IsEntity()) + continue; + + ((Entity*)target)->RemoveProc(item, spell); + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + spell->caster->RemoveProc(item, spell); + } + else + ((Entity*)spawn)->RemoveProc(item, spell); + + return 0; +} + +int EQ2Emu_lua_Knockback(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* target_spawn = lua_interface->GetSpawn(state); + Spawn* spawn = lua_interface->GetSpawn(state, 2); + int32 duration = lua_interface->GetInt32Value(state, 3); + float vertical = lua_interface->GetFloatValue(state, 4); + float horizontal = lua_interface->GetFloatValue(state, 5); + bool use_heading = lua_interface->GetInt8Value(state, 6) == 1 ? true : false; + lua_interface->ResetFunctionStack(state); + + if (!target_spawn) { + lua_interface->LogError("%s: LUA Knockback command error: target_spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn) { + lua_interface->LogError("%s: LUA Knockback command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if ((vertical != 0 || horizontal != 0)) { + if(spawn->IsPlayer()) { + Client* client = ((Player*)spawn)->GetClient(); + PacketStruct* packet = configReader.getStruct("WS_PlayerKnockback", client->GetVersion()); + if (packet) { + packet->setDataByName("target_x", target_spawn->GetX()); + packet->setDataByName("target_y", target_spawn->GetY()); + packet->setDataByName("target_z", target_spawn->GetZ()); + packet->setDataByName("vertical_movement", vertical); + packet->setDataByName("horizontal_movement", horizontal); + if (use_heading) + packet->setDataByName("use_player_heading", 1); + + client->QueuePacket(packet->serialize()); + } + safe_delete(packet); + } + else { + spawn->SetKnockback(target_spawn, duration, vertical, horizontal); + } + } + + return 0; +} + +int EQ2Emu_lua_IsEpic(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA IsEpic command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetBooleanValue(state, (spawn->GetHeroic() >= 2)); + return 1; +} + +int EQ2Emu_lua_ProcDamage(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* caster = lua_interface->GetSpawn(state); + Spawn* target = lua_interface->GetSpawn(state, 2); + string name = lua_interface->GetStringValue(state, 3); + int8 dmg_type = lua_interface->GetInt8Value(state, 4); + int32 low_damage = lua_interface->GetInt32Value(state, 5); + int32 high_damage = lua_interface->GetInt32Value(state, 6); + string success_msg = lua_interface->GetStringValue(state, 7); + string effect_msg = lua_interface->GetStringValue(state, 8); + lua_interface->ResetFunctionStack(state); + + if (!caster) { + lua_interface->LogError("%s: LUA ProcDamage command error: caster is not a valid spawn", lua_interface->GetScriptName(state)); + return 0; + } + + if (!caster->IsEntity()) { + lua_interface->LogError("%s: LUA ProcDamage command error: caster is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (!target) { + lua_interface->LogError("%s: LUA ProcDamage command error: target is not a valid spawn", lua_interface->GetScriptName(state)); + return 0; + } + + if (!target->IsEntity()) { + lua_interface->LogError("%s: LUA ProcDamage command error: target is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (name.length() == 0) { + lua_interface->LogError("%s: LUA ProcDamage command error: name is empty", lua_interface->GetScriptName(state)); + return 0; + } + + ((Entity*)caster)->ProcAttack(target, dmg_type, low_damage, high_damage, name, success_msg, effect_msg); + return 0; +} + +int EQ2Emu_lua_GetSkillIDByName(lua_State* state) { + if (!lua_interface) + return 0; + + string name = lua_interface->GetStringValue(state); + lua_interface->ResetFunctionStack(state); + + if (name.length() == 0) { + lua_interface->LogError("%s: LUA GetSkillIDByName command error: name param was not set", lua_interface->GetScriptName(state)); + return 0; + } + + Skill* skill = master_skill_list.GetSkillByName(name.c_str()); + if (!skill) { + lua_interface->LogError("LUA GetSkillIDByName command error: skill with name of %s not found", name.c_str()); + return 0; + } + + lua_interface->SetInt32Value(state, skill->skill_id); + return 1; +} + +int EQ2Emu_lua_IsHeroic(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA IsHeroic command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetBooleanValue(state, spawn->GetHeroic() == 1); + return 1; +} + +int EQ2Emu_lua_LastSpellAttackHit(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if (!luaspell) { + lua_interface->LogError("%s: LUA LastSpellAttackHit command error: this must be called from a spellscript", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetBooleanValue(state, luaspell->last_spellattack_hit); + return 1; +} +int EQ2Emu_lua_IsBehind(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + Spawn* target = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA IsBehind command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA IsBehind command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (!target) { + lua_interface->LogError("%s: LUA IsBehind command error: target is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetBooleanValue(state, ((Entity*)spawn)->BehindTarget(target)); + return 1; +} + +int EQ2Emu_lua_IsFlanking(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + Spawn* target = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA IsFlanking command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA IsFlanking command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (!target) { + lua_interface->LogError("%s: LUA IsFlanking command error: target is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetBooleanValue(state, ((Entity*)spawn)->FlankingTarget(target)); + return 1; +} + +int EQ2Emu_lua_InFront(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + Spawn* target = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA InFrontSpawn command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA InFrontSpawn command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (!target) { + lua_interface->LogError("%s: LUA InFrontSpawn command error: target is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetBooleanValue(state, ((Entity*)spawn)->InFrontSpawn(target, spawn->GetX(), spawn->GetZ())); + return 1; +} + +int EQ2Emu_lua_GetItemCount(lua_State* state) { + if (!lua_interface) + return 0; + + Item* item = lua_interface->GetItem(state); + lua_interface->ResetFunctionStack(state); + + if (!item) { + lua_interface->LogError("%s: LUA GetItemCount command error: item not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetInt32Value(state, item->details.count); + return 1; +} + +int EQ2Emu_lua_SetItemCount(lua_State* state) { + if (!lua_interface) + return 0; + + Item* item = lua_interface->GetItem(state); + Spawn* owner = lua_interface->GetSpawn(state, 2); + int16 new_count = lua_interface->GetInt32Value(state, 3); + lua_interface->ResetFunctionStack(state); + + if (!item) { + lua_interface->LogError("%s: LUA SetItemCount command error: item not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!owner) { + lua_interface->LogError("%s: LUA SetItemCount command error: spawn not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!owner->IsPlayer()) { + lua_interface->LogError("%s: LUA SetItemCount command error: spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + if (item->stack_count < new_count) { + lua_interface->LogError("%s: LUA SetItemCount command error: new item count cannot be more than max stack count", lua_interface->GetScriptName(state)); + return 0; + } + + if (new_count > 0) { + item->details.count = new_count; + item->save_needed = true; + } + else if (((Player*)owner)->GetEquipmentList()->GetItem(item->details.slot_id) == item) + ((Player*)owner)->GetEquipmentList()->RemoveItem(item->details.slot_id, true); + else if (((Player*)owner)->GetPlayerItemList()->GetItemFromUniqueID(item->details.unique_id) == item) + ((Player*)owner)->GetPlayerItemList()->RemoveItem(item, true); + else + { + lua_interface->LogError("%s: LUA SetItemCount command error: could not remove item from player", lua_interface->GetScriptName(state)); + return 0; + } + + Client* client = ((Player*)owner)->GetClient(); + + if (!client) + return 0; + + ((Player*)owner)->SendInventoryUpdate(client->GetVersion()); + + EQ2Packet* app = ((Player*)owner)->GetEquipmentList()->serialize(client->GetVersion(), client->GetPlayer()); + if (app) + client->QueuePacket(app); + + return 0; +} + +int EQ2Emu_lua_AddSpellTimer(lua_State* state) { + if (!lua_interface) + return 0; + + int32 time = lua_interface->GetInt32Value(state); + string function = lua_interface->GetStringValue(state, 2); + Spawn* caster = lua_interface->GetSpawn(state, 3); + Spawn* target = lua_interface->GetSpawn(state, 4); + + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if (time == 0) { + lua_interface->LogError("%s: LUA AddSpellTimer command error: time must be set", lua_interface->GetScriptName(state)); + return 0; + } + + if (function.length() == 0) { + lua_interface->LogError("%s: LUA AddSpellTimer command error: function name must be set", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spell || (!spell->caster || !spell->caster->GetZone())) { + lua_interface->LogError("%s: LUA AddSpellTimer command error: spell not found, AddSpellTimer must be used in a spell script", lua_interface->GetScriptName(state)); + return 0; + } + + SpellScriptTimer* timer = new SpellScriptTimer; + + /* //Google tells me memsetting a string is bad, manually setting just in case - Foof + #ifdef WIN32 + ZeroMemory(timer, sizeof(SpellScriptTimer)); + #else + bzero(timer, sizeof(SpellScriptTimer)); + #endif*/ + + timer->caster = 0; + timer->deleteWhenDone = false; + timer->target = 0; + + timer->time = Timer::GetCurrentTime2() + time; + timer->customFunction = function; + timer->spell = spell; + if (caster) + timer->caster = caster->GetID(); + if (target) + timer->target = target->GetID(); + + spell->caster->GetZone()->GetSpellProcess()->AddSpellScriptTimer(timer); + return 0; +} + +int EQ2Emu_lua_Resurrect(lua_State* state) { + if (!lua_interface) + return 0; + + float hp_perc = lua_interface->GetFloatValue(state); + float power_perc = lua_interface->GetFloatValue(state, 2); + bool send_window = lua_interface->GetInt32Value(state, 3) == 1; + Spawn* target = lua_interface->GetSpawn(state, 4); + string heal_name = lua_interface->GetStringValue(state, 5); + int8 crit_mod = lua_interface->GetInt32Value(state, 6); + bool no_calcs = lua_interface->GetInt32Value(state, 7) == 1; + + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + if (!spell) { + lua_interface->LogError("%s: LUA command error: this function must be used in a spellscript", lua_interface->GetScriptName(state)); + return 0; + } + + Entity* caster = spell->caster; + if (!caster) { + lua_interface->LogError("%s: LUA command error: could not find caster", lua_interface->GetScriptName(state)); + return 0; + } + + Client* client = 0; + PendingResurrection* rez = 0; + ZoneServer* zone = spell->caster->GetZone(); + if (!target) { + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + if (spell->targets.size() > 0) { + vector spell_targets = spell->targets; + for (int8 i = 0; i < spell_targets.size(); i++) { + target = zone->GetSpawnByID(spell_targets.at(i)); + if (!target) + continue; + if (!target->IsPlayer()) + continue; + + client = ((Player*)target)->GetClient(); + + if (!client) + continue; + + rez = client->GetCurrentRez(); + if (rez->active) + continue; + + client->GetResurrectMutex()->writelock(__FUNCTION__, __LINE__); + rez->active = true; + rez->caster = caster; + rez->expire_timer = new Timer; + int32 duration = spell->spell->GetSpellDuration(); + rez->expire_timer->Start(duration * 100); + rez->hp_perc = hp_perc; + rez->mp_perc = power_perc; + rez->range = spell->spell->GetSpellData()->range; + rez->spell_name = spell->spell->GetName(); + if (heal_name.length() > 0) + rez->heal_name = heal_name; + else + rez->heal_name = rez->spell_name; + rez->no_calcs = no_calcs; + rez->crit_mod = crit_mod; + rez->spell_visual = spell->spell->GetSpellData()->spell_visual; + if (send_window) + client->SendResurrectionWindow(); + else { + target->GetZone()->ResurrectSpawn(target, client); + rez->should_delete = true; + } + client->GetResurrectMutex()->releasewritelock(__FUNCTION__, __LINE__); + } + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + else { + if(!target->IsPlayer()) + return 0; + + client = ((Player*)target)->GetClient(); + + if (!client) + return 0; + + rez = client->GetCurrentRez(); + if (rez->active) + return 0; + + client->GetResurrectMutex()->writelock(__FUNCTION__, __LINE__); + rez->active = true; + rez->caster = caster; + rez->expire_timer = new Timer; + int32 duration = spell->spell->GetSpellDuration(); + rez->expire_timer->Start(duration * 100); + rez->hp_perc = hp_perc; + rez->mp_perc = power_perc; + rez->range = spell->spell->GetSpellData()->range; + rez->spell_name = spell->spell->GetName(); + if (heal_name.length() > 0) + rez->heal_name = heal_name; + else + rez->heal_name = rez->spell_name; + rez->no_calcs = no_calcs; + rez->crit_mod = crit_mod; + rez->spell_visual = spell->spell->GetSpellData()->spell_visual; + if (send_window) + client->SendResurrectionWindow(); + else { + target->GetZone()->ResurrectSpawn(target, client); + rez->should_delete = true; + } + client->GetResurrectMutex()->releasewritelock(__FUNCTION__, __LINE__); + } + return 0; +} + +int EQ2Emu_lua_SetVision(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + int32 vision = lua_interface->GetInt32Value(state, 2); + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA SetVision command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA SetVision command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (spell && spell->targets.size() > 0) { + ZoneServer* zone = spell->caster->GetZone(); + for (int8 i = 0; i < spell->targets.size(); i++) { + Spawn* target = zone->GetSpawnByID(spell->targets.at(i)); + if (target && target->IsEntity()) { + ((Entity*)target)->GetInfoStruct()->set_vision(vision); + if (target->IsPlayer()) + ((Player*)target)->SetCharSheetChanged(true); + } + } + } + else { + ((Entity*)spawn)->GetInfoStruct()->set_vision(vision); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + return 0; +} + +int EQ2Emu_lua_BlurVision(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + float intensity = lua_interface->GetFloatValue(state, 2); + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA BlurVision command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA BlurVision command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (spell && spell->targets.size() > 0) { + ZoneServer* zone = spell->caster->GetZone(); + for (int8 i = 0; i < spell->targets.size(); i++) { + Spawn* target = zone->GetSpawnByID(spell->targets.at(i)); + if (target && target->IsEntity()) { + ((Entity*)target)->GetInfoStruct()->set_drunk(intensity); + if (target->IsPlayer()) + ((Player*)target)->SetCharSheetChanged(true); + } + } + } + else { + ((Entity*)spawn)->GetInfoStruct()->set_drunk(intensity); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + return 0; +} + +int EQ2Emu_lua_BreatheUnderwater(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + bool breatheUnderwater = lua_interface->GetBooleanValue(state, 2); + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA BreathUnderwater command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA BreathUnderwater command error: spawn is not en entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (spell && spell->targets.size() > 0) { + ZoneServer* zone = spell->caster->GetZone(); + for (int8 i = 0; i < spell->targets.size(); i++) { + Spawn* target = zone->GetSpawnByID(spell->targets.at(i)); + if (target && target->IsEntity()) { + ((Entity*)target)->GetInfoStruct()->set_breathe_underwater(breatheUnderwater); + if (target->IsPlayer()) + ((Player*)target)->SetCharSheetChanged(true); + } + } + } + else { + ((Entity*)spawn)->GetInfoStruct()->set_breathe_underwater(breatheUnderwater); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + return 0; +} + +int EQ2Emu_lua_GetItemSkillReq(lua_State* state) { + if (!lua_interface) + return 0; + + Item* item = lua_interface->GetItem(state); + int8 type = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (!item) { + lua_interface->LogError("%s: LUA GetItemSkillReq command error: item not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (type == 1) + lua_interface->SetInt32Value(state, item->generic_info.skill_req1); + else if (type == 2) + lua_interface->SetInt32Value(state, item->generic_info.skill_req2); + + return 1; +} + +int EQ2Emu_lua_SetSpeedMultiplier(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* target = lua_interface->GetSpawn(state); + float val = lua_interface->GetFloatValue(state, 2); + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + // Added from Gangrenous post + if (spell && spell->resisted) + return 0; + + // if its a percentage of 100 its a slow, we want to go at a fraction of the speed + if (val > 1.0f) + val = 1.0f - (val / 100.0f); + + if (spell && spell->spell && spell->targets.size() > 0) { + ZoneServer* zone = spell->caster->GetZone(); + for (int32 i = 0; i != spell->targets.size(); i++) { + Spawn* spawn = zone->GetSpawnByID(spell->targets.at(i)); + if (spawn && spawn->IsEntity()) { + ((Entity*)spawn)->SetSpeedMultiplier(val); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + } + } + else { + if (target && target->IsEntity()) { + ((Entity*)target)->SetSpeedMultiplier(val); + if (target->IsPlayer()) + ((Player*)target)->SetCharSheetChanged(true); + } + } + + return 0; +} + +int EQ2Emu_lua_SetIllusion(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + int16 model = lua_interface->GetInt16Value(state, 2); + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if (spell && spell->spell && spell->targets.size() > 0) { + ZoneServer* zone = spell->caster->GetZone(); + for (int32 i = 0; i < spell->targets.size(); i++) { + Spawn* target = zone->GetSpawnByID(spell->targets.at(i)); + if (target) + target->SetIllusionModel(model); + } + } + else { + if (!spawn) { + lua_interface->LogError("%s: LUA SetIllusion command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + spawn->SetIllusionModel(model); + } + + return 0; +} + +int EQ2Emu_lua_ResetIllusion(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if (spell && spell->spell && spell->targets.size() > 0) { + ZoneServer* zone = spell->caster->GetZone(); + for (int32 i = 0; i < spell->targets.size(); i++) { + Spawn* target = zone->GetSpawnByID(spell->targets.at(i)); + if (target) + target->SetIllusionModel(0); + } + } + else { + if (!spawn) { + lua_interface->LogError("%s: LUA ResetIllusion command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + spawn->SetIllusionModel(0); + } + + return 0; +} + +int EQ2Emu_lua_AddThreatTransfer(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* caster = lua_interface->GetSpawn(state); + Spawn* target = lua_interface->GetSpawn(state, 2); + float chance = lua_interface->GetFloatValue(state, 3); + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if (!caster) { + lua_interface->LogError("%s: LUA AddThreatTransfer command error: caster is not a valid spawn", lua_interface->GetScriptName(state)); + return 0; + } + + if (!caster->IsEntity()) { + lua_interface->LogError("%s: LUA AddThreatTransfer command error: caster is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (!target) { + lua_interface->LogError("%s: LUA AddThreatTransfer command error: target is not a valid spawn", lua_interface->GetScriptName(state)); + return 0; + } + + if (!target->IsEntity()) { + lua_interface->LogError("%s: LUA AddThreatTransfer command error: target is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (chance <= 0) { + lua_interface->LogError("%s: LUA AddThreatTransfer command error: chance must be greater then 0%", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spell) { + lua_interface->LogError("%s: LUA AddThreatTransfer command error: can only be used in a spell script", lua_interface->GetScriptName(state)); + return 0; + } + + if(spell->resisted) { + return 0; + } + + if (((Entity*)caster)->GetThreatTransfer()) { + return 0; + } + + ThreatTransfer* transfer = new ThreatTransfer; + transfer->Target = target->GetID(); + transfer->Amount = chance; + transfer->Spell = spell; + + ((Entity*)caster)->SetThreatTransfer(transfer); + return 0; +} + +int EQ2Emu_lua_RemoveThreatTransfer(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA RemoveThreatTransfer command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spell) { + lua_interface->LogError("%s: LUA RemoveThreatTransfer command error: can only be used in a spell script", lua_interface->GetScriptName(state)); + return 0; + } + + if (((Entity*)spawn)->GetThreatTransfer() && ((Entity*)spawn)->GetThreatTransfer()->Spell == spell) { + ThreatTransfer* transfer = ((Entity*)spawn)->GetThreatTransfer(); + if(transfer && transfer->Spell != spell) { + lua_interface->LogError("%s: LUA RemoveThreatTransfer called, but there was a different spell set for the threat transfer.", lua_interface->GetScriptName(state)); + return 0; + } + ((Entity*)spawn)->SetThreatTransfer(nullptr); + } + + return 0; +} + +int EQ2Emu_lua_CureByType(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + int8 cure_count = lua_interface->GetInt8Value(state); + int8 cure_type = lua_interface->GetInt8Value(state, 2); + string cure_name = lua_interface->GetStringValue(state, 3); + int8 cure_level = lua_interface->GetInt8Value(state, 4); + Spawn* target = lua_interface->GetSpawn(state, 5); + Spawn* caster = lua_interface->GetSpawn(state, 6); // optional + lua_interface->ResetFunctionStack(state); + + if(spell && spell->resisted) { + return 0; + } + + if (target) { + if (!target->IsEntity()) { + lua_interface->LogError("%s: LUA CureByType command error: spawn override must be entity if used", lua_interface->GetScriptName(state)); + return 0; + } + + if (((Entity*)target)->GetDetTypeCount(cure_type) > 0) { + std::string alternate_name = "item"; + if(spell) + alternate_name = std::string(spell->spell->GetName()); + + if(!caster && spell) + caster = (Spawn*)spell->caster; + + ((Entity*)target)->CureDetrimentByType(cure_count, cure_type, cure_name.length() > 0 ? cure_name : alternate_name, (Entity*)caster, cure_level); + } + } + else { + ZoneServer* zone = spell->caster->GetZone(); + vector targets = spell->targets; + vector targets_to_cure; + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int8 i = 0; i < targets.size(); i++) { + target = zone->GetSpawnByID(targets.at(i)); + + if (!target || !target->IsEntity()) + continue; + + if (((Entity*)target)->GetDetTypeCount(cure_type) > 0) { + targets_to_cure.push_back((Entity*)target); + } + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + + vector::iterator itr; + for(itr = targets_to_cure.begin(); itr != targets_to_cure.end(); itr++) { + ((Entity*)*itr)->CureDetrimentByType(cure_count, cure_type, cure_name.length() > 0 ? cure_name : (string)spell->spell->GetName(), spell->caster, cure_level); + } + } + return 0; +} + +int EQ2Emu_lua_CureByControlEffect(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + if (!spell) { + lua_interface->LogError("%s: LUA CureByControlEffect command error: can only be used in a spell script", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + + if(spell->resisted) { + lua_interface->ResetFunctionStack(state); + return 0; + } + + int8 cure_count = lua_interface->GetInt8Value(state); + int8 cure_type = lua_interface->GetInt8Value(state, 2); + string cure_name = lua_interface->GetStringValue(state, 3); + int8 cure_level = lua_interface->GetInt8Value(state, 4); + Spawn* target = lua_interface->GetSpawn(state, 5); + lua_interface->ResetFunctionStack(state); + + if (target) { + if (!target->IsEntity()) { + lua_interface->LogError("%s: LUA CureByControlEffect command error: spawn override must be entity if used", lua_interface->GetScriptName(state)); + return 0; + } + + if (((Entity*)target)->GetDetCount() > 0) + ((Entity*)target)->CureDetrimentByControlEffect(cure_count, cure_type, cure_name.length() > 0 ? cure_name : (string)spell->spell->GetName(), spell->caster, cure_level); + } + else { + ZoneServer* zone = spell->caster->GetZone(); + vector targets = spell->targets; + + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int8 i = 0; i < targets.size(); i++) { + target = zone->GetSpawnByID(targets.at(i)); + + if (!target || !target->IsEntity()) + continue; + + if (((Entity*)target)->GetDetCount() > 0) + ((Entity*)target)->CureDetrimentByControlEffect(cure_count, cure_type, cure_name.length() > 0 ? cure_name : (string)spell->spell->GetName(), spell->caster, cure_level); + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + return 0; +} + +int EQ2Emu_lua_CancelSpell(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if (!spell) { + lua_interface->LogError("%s: LUA CancelSpell command error: can only be use in a spell script", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spell->caster) { + lua_interface->LogError("%s: LUA CancelSpell command error: unable to get the caster of the spell", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spell->caster->GetZone()) { + lua_interface->LogError("%s: LUA CancelSpell command error: unable to get the zone of the caster", lua_interface->GetScriptName(state)); + return 0; + } + + spell->caster->GetZone()->GetSpellProcess()->AddSpellCancel(spell); + + return 0; +} + +int EQ2Emu_lua_RemoveStealth(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if (!spell) { + lua_interface->LogError("%s: LUA RemoveStealth command error: must be used from spell script", lua_interface->GetScriptName(state)); + return 0; + } + + if (spawn && spawn->IsEntity()) + ((Entity*)spawn)->RemoveStealthSpell(spell); + else { + ZoneServer* zone = spell->caster->GetZone(); + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < spell->targets.size(); i++) { + spawn = zone->GetSpawnByID(spell->targets.at(i)); + if (!spawn || !spawn->IsEntity()) + continue; + + ((Entity*)spawn)->RemoveStealthSpell(spell); + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + return 0; +} + +int EQ2Emu_lua_RemoveInvis(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if (!spell) { + lua_interface->LogError("%s: LUA RemoveInvis command error: must be used from spell script", lua_interface->GetScriptName(state)); + return 0; + } + + if (spawn && spawn->IsEntity()) + ((Entity*)spawn)->RemoveInvisSpell(spell); + else { + ZoneServer* zone = spell->caster->GetZone(); + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < spell->targets.size(); i++) { + spawn = zone->GetSpawnByID(spell->targets.at(i)); + if (!spawn || !spawn->IsEntity()) + continue; + + ((Entity*)spawn)->RemoveInvisSpell(spell); + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + return 0; +} + +int EQ2Emu_lua_StartHeroicOpportunity(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* caster = lua_interface->GetSpawn(state); + int8 class_id = lua_interface->GetInt8Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!caster) { + lua_interface->LogError("%s: LUA StartHeroicOpportunity command error: caster is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!caster->IsPlayer()) { + lua_interface->LogError("%s: LUA StartHeroicOpportunity command error: caster must be a player", lua_interface->GetScriptName(state)); + return 0; + } + + Spawn* target = caster->GetTarget(); + if (!target) { + lua_interface->LogError("%s: LUA StartHeroicOpportunity command error: target is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + Client* client = ((Player*)caster)->GetClient(); + if (!client) { + lua_interface->LogError("%s: LUA StartHeroicOpportunity command error: unable to get a client for the given caster", lua_interface->GetScriptName(state)); + return 0; + } + + HeroicOP* ho = master_ho_list.GetHeroicOP(class_id); + if (ho) { + ho->SetTarget(target->GetID()); + LogWrite(SPELL__ERROR, 0, "HO", "caster: %u", caster->GetID()); + LogWrite(SPELL__ERROR, 0, "HO", "target: %u", target->GetID()); + if (((Entity*)caster)->GetGroupMemberInfo()) { + if (caster->GetZone()->GetSpellProcess()->AddHO(client, ho)) { + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + + deque::iterator itr; + PlayerGroup* group = world.GetGroupManager()->GetGroup(((Entity*)caster)->GetGroupMemberInfo()->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + for (itr = members->begin(); itr != members->end(); itr++) { + if ((*itr)->client) + ClientPacketFunctions::SendHeroicOPUpdate((*itr)->client, ho); + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } + else + safe_delete(ho); + } + else { + if (caster->GetZone()->GetSpellProcess()->AddHO(client, ho)) { + ClientPacketFunctions::SendHeroicOPUpdate(client, ho); + } + else + safe_delete(ho); + } + } + + return 0; +} + +int EQ2Emu_lua_SetSpellTriggerCount(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + + if (!spell) { + lua_interface->LogError("%s: LUA SetSpellTriggerCount command error: you must use this function in a spellscript!", lua_interface->GetScriptName(state)); + return 0; + } + + int16 triggerCount = lua_interface->GetInt16Value(state); + bool cancel_after_triggers = (lua_interface->GetInt8Value(state, 2) == 1); + + if (!triggerCount) { + lua_interface->LogError("%s: LUA SetSpellTriggerCount command error: used trigger value equals zero!", lua_interface->GetScriptName(state)); + return 0; + } + + spell->num_triggers = triggerCount; + spell->had_triggers = true; + spell->cancel_after_all_triggers = cancel_after_triggers; + + return 0; +} + +int EQ2Emu_lua_GetSpellTriggerCount(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if (!spell) { + lua_interface->LogError("%s: LUA GetSpellTriggerCount command error: you must use this function in a spellscript!", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetInt32Value(state, spell->num_triggers); + + return 1; +} + +int EQ2Emu_lua_RemoveTriggerFromSpell(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + + if (!spell) { + lua_interface->LogError("%s: LUA RemoveTriggerFromSpell command error: you must use this function in a spellscript!", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + + if (!spell->caster || !spell->caster->GetZone()) { + lua_interface->LogError("%s: LUA RemoveTriggerFromSpell command error: caster / caster zone must be set!", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + + int16 remove_count = lua_interface->GetInt16Value(state); + + lua_interface->ResetFunctionStack(state); + + if (!remove_count) + remove_count = 1; + + if (remove_count >= spell->num_triggers) { + spell->num_triggers = 0; + if (spell->cancel_after_all_triggers) + spell->caster->GetZone()->GetSpellProcess()->AddSpellCancel(spell); + } + else { + spell->num_triggers -= remove_count; + Client* client = spell->caster->IsPlayer() ? ((Player*)spell->caster)->GetClient() : nullptr; + ClientPacketFunctions::SendMaintainedExamineUpdate(client, spell->slot_pos, spell->num_triggers, 0); + } + return 0; +} + +int EQ2Emu_lua_CopySpawnAppearance(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + Spawn* copy_spawn = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA CopySpawnAppearance command error: the first spawn used was not valid!", lua_interface->GetScriptName(state)); + return 0; + } + + if (!copy_spawn) { + lua_interface->LogError("%s: LUA CopySpawnAppearance command error: the second spawn used was not valid!", lua_interface->GetScriptName(state)); + return 0; + } + + spawn->CopySpawnAppearance(copy_spawn); + return 0; +} + +int EQ2Emu_lua_HasSpellImmunity(lua_State* state) { + Spawn* spawn = lua_interface->GetSpawn(state); + int8 type = lua_interface->GetInt8Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA HasSpellImmunity command error: spawn does not exist.", lua_interface->GetScriptName(state)); + return 0; + } + else if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA HasSpellImmunity command error: spawn %s is not an entity.", lua_interface->GetScriptName(state), spawn->GetName()); + return 0; + } + + lua_interface->SetBooleanValue(state, ((Entity*)spawn)->IsImmune(type)); + return 1; +} + +int EQ2Emu_lua_AddImmunitySpell(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + int8 type = lua_interface->GetInt8Value(state); + Spawn* spawn = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!spell) { + lua_interface->LogError("%s: LUA AddImmunitySpell command error: This must be used in a spellscript", lua_interface->GetScriptName(state)); + return 0; + } + + if(spell->resisted) { + return 0; + } + + if (spawn) { + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA AddImmunitySpell command error: The spawn provided is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + Entity* entity = ((Entity*)spawn); + entity->AddImmunity(spell, type); + } + else if(spell->caster && spell->caster->GetZone()) { + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int8 i = 0; i < spell->targets.size(); i++) { + spawn = spell->caster->GetZone()->GetSpawnByID(spell->targets.at(i)); + if (!spawn || !spawn->IsEntity()) + continue; + Entity* entity = ((Entity*)spawn); + entity->AddImmunity(spell, type); + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + + return 0; +} + +int EQ2Emu_lua_RemoveImmunitySpell(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + int8 type = lua_interface->GetInt8Value(state); + Spawn* spawn = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!spell) { + lua_interface->LogError("%s: LUA RemoveImmunitySpell command error: This must be used in a spellscript", lua_interface->GetScriptName(state)); + return 0; + } + + if (spawn) { + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA RemoveImmunitySpell command error: The spawn provided is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + Entity* entity = ((Entity*)spawn); + entity->RemoveImmunity(spell, type); + } + else if(spell->caster && spell->caster->GetZone()) { + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int8 i = 0; i < spell->targets.size(); i++) { + spawn = spell->caster->GetZone()->GetSpawnByID(spell->targets.at(i)); + if (!spawn || !spawn->IsEntity()) + continue; + Entity* entity = ((Entity*)spawn); + entity->RemoveImmunity(spell, type); + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + + return 0; +} + +int EQ2Emu_lua_SetSpellSnareValue(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + if (!spell) { + lua_interface->LogError("%s: LUA SetSpellSnareValue command error: This can only be used in a spell script!", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + + if(spell->resisted) { + lua_interface->ResetFunctionStack(state); + return 0; + } + + float snare = lua_interface->GetFloatValue(state); + Spawn* spawn = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + + // convert the val to the speed multipler value (100 - val) + float val = 100.0 - snare; + val /= 100.0; + + if (spawn) { + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA SetSpellSnareValue command error: spawn must be an entity.", lua_interface->GetScriptName(state)); + return 0; + } + + ((Entity*)spawn)->SetSnareValue(spell, val); + } + else if(spell->caster && spell->caster->GetZone()) { + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int8 i = 0; i < spell->targets.size(); i++) { + spawn = spell->caster->GetZone()->GetSpawnByID(spell->targets.at(i)); + if (!spawn || !spawn->IsEntity()) + continue; + + ((Entity*)spawn)->SetSnareValue(spell, val); + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + + return 0; +} + +int EQ2Emu_lua_CheckRaceType(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + int16 race_id = lua_interface->GetInt16Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA CheckRaceType command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (race_id == 0) { + lua_interface->LogError("%s: LUA CheckRaceType command error: race id must be set", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetBooleanValue(state, (race_id == race_types_list.GetRaceType(spawn->GetModelType()) || race_id == race_types_list.GetRaceBaseType(spawn->GetModelType()))); + return 1; +} + +int EQ2Emu_lua_GetRaceType(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA GetRaceType command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetInt32Value(state, race_types_list.GetRaceType(spawn->GetModelType())); + return 1; +} + +int EQ2Emu_lua_GetRaceBaseType(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA GetRaceBaseType command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetInt32Value(state, race_types_list.GetRaceBaseType(spawn->GetModelType())); + return 1; +} + +int EQ2Emu_lua_GetSpellName(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if (!spell) { + lua_interface->LogError("%s: LUA GetSpellName command error: this function must be used from a spell script!", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetStringValue(state, spell->spell->GetName()); + return 1; +} + +int EQ2Emu_lua_GetQuestFlags(lua_State* state) { + if (!lua_interface) + return 0; + + Quest* quest = lua_interface->GetQuest(state); + lua_interface->ResetFunctionStack(state); + if (!quest) { + lua_interface->LogError("%s: LUA GetQuestFlags command error: quest is not valid.", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetInt32Value(state, quest->GetQuestFlags()); + return 1; +} + +int EQ2Emu_lua_SetQuestFlags(lua_State* state) { + if (!lua_interface) + return 0; + + Quest* quest = lua_interface->GetQuest(state); + int32 flags = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!quest) { + lua_interface->LogError("%s: LUA SetQuestFlags command error: quest is not valid.", lua_interface->GetScriptName(state)); + return 0; + } + + quest->SetQuestFlags(flags); + return 0; +} + +int EQ2Emu_lua_SetQuestTimer(lua_State* state) { + if (!lua_interface) + return 0; + + Quest* quest = lua_interface->GetQuest(state); + Spawn* player = lua_interface->GetSpawn(state, 2); + int32 step = lua_interface->GetInt32Value(state, 3); + int32 duration = lua_interface->GetInt32Value(state, 4); + string action = lua_interface->GetStringValue(state, 5); + lua_interface->ResetFunctionStack(state); + + if (!quest) { + lua_interface->LogError("%s: LUA SetQuestTimer command error: quest is not valid.", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player) { + lua_interface->LogError("%s: LUA SetQuestTimer command error: player is not a valid spawn.", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA SetQuestTimer command error: player is not a valid player.", lua_interface->GetScriptName(state)); + return 0; + } + + if (step == 0) { + lua_interface->LogError("%s: LUA SetQuestTimer command error: step must be set.", lua_interface->GetScriptName(state)); + return 0; + } + + if (duration == 0) { + lua_interface->LogError("%s: LUA SetQuestTimer command error: duration must be set.", lua_interface->GetScriptName(state)); + return 0; + } + + if (action.length() == 0) { + lua_interface->LogError("%s: LUA SetQuestTimer command error: failed action must be set.", lua_interface->GetScriptName(state)); + return 0; + } + + Client* client = ((Player*)player)->GetClient(); + if (!client) { + lua_interface->LogError("%s: LUA SetQuestTimer command error: failed to get a valid client pointer for the given player", lua_interface->GetScriptName(state)); + return 0; + } + + quest->SetTimerStep(step); + quest->AddFailedAction(step, action); + quest->SetStepTimer(duration); + client->AddQuestTimer(quest->GetQuestID()); + + return 0; +} + +int EQ2Emu_lua_SetQuestTimerComplete(lua_State* state) { + if (!lua_interface) + return 0; + + Quest* quest = lua_interface->GetQuest(state); + Spawn* player = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!quest) { + lua_interface->LogError("%s: LUA SetQuestTimerComplete command error: quest is not valid.", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player) { + lua_interface->LogError("%s: LUA SetQuestTimerComplete command error: player is not a valid spawn.", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA SetQuestTimerComplete command error: player is not a valid player.", lua_interface->GetScriptName(state)); + return 0; + } + + Client* client = ((Player*)player)->GetClient(); + if (!client) { + lua_interface->LogError("%s: LUA SetQuestTimerComplete command error: failed to get a valid client pointer for the given player", lua_interface->GetScriptName(state)); + return 0; + } + + quest->SetTimerStep(0); + quest->SetStepTimer(0); + client->RemoveQuestTimer(quest->GetQuestID()); + + return 0; +} + +int EQ2Emu_lua_RemoveQuestStep(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + Quest* quest = lua_interface->GetQuest(state, 2); + int32 step = lua_interface->GetInt32Value(state, 3); + lua_interface->ResetFunctionStack(state); + + if (!player) { + lua_interface->LogError("%s: LUA RemoveQuestStep command error: player is not a valid spawn.", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA RemoveQuestStep command error: player is not a valid player.", lua_interface->GetScriptName(state)); + return 0; + } + + if (!quest) { + lua_interface->LogError("%s: LUA RemoveQuestStep command error: quest is not valid.", lua_interface->GetScriptName(state)); + return 0; + } + + if (step == 0) { + lua_interface->LogError("%s: LUA RemoveQuestStep command error: step must be set.", lua_interface->GetScriptName(state)); + return 0; + } + + Client* client = ((Player*)player)->GetClient(); + if (!client) { + lua_interface->LogError("%s: LUA RemoveQuestStep command error: unable to get a valid client pointer from the given player.", lua_interface->GetScriptName(state)); + return 0; + } + + if (quest->RemoveQuestStep(step, client)) { + client->QueuePacket(quest->QuestJournalReply(client->GetVersion(), client->GetNameCRC(), (Player*)player, 0, 0, 0, true)); + client->GetCurrentZone()->SendQuestUpdates(client); + } + else + lua_interface->LogError("LUA RemoveQuestStep command error: unable to remove the step (%u) from the quest (%s).", step, quest->GetName()); + + return 0; +} + +int EQ2Emu_lua_ResetQuestStep(lua_State* state) { + if (!lua_interface) + return 0; + + Quest* quest = lua_interface->GetQuest(state, 1); + int32 step = lua_interface->GetInt32Value(state, 2); + string desc = lua_interface->GetStringValue(state, 3); + string task_group = lua_interface->GetStringValue(state, 4); + lua_interface->ResetFunctionStack(state); + + if (!quest) { + lua_interface->LogError("%s: LUA ResetQuestStep command error: quest is not valid.", lua_interface->GetScriptName(state)); + return 0; + } + + if (step == 0) { + lua_interface->LogError("%s: LUA ResetQuestStep command error: step must be set.", lua_interface->GetScriptName(state)); + return 0; + } + + QuestStep* quest_step = quest->GetQuestStep(step); + if (!quest_step) { + lua_interface->LogError("%s: LUA ResetQuestStep command error: unable to get a valid quest step.", lua_interface->GetScriptName(state)); + return 0; + } + + quest_step->SetStepProgress(0); + quest_step->SetTaskGroup(task_group); + quest_step->SetDescription(desc); + + return 0; +} + +int EQ2Emu_lua_AddQuestStepFailureAction(lua_State* state) { + if (!lua_interface) + return 0; + + Quest* quest = lua_interface->GetQuest(state); + int32 step = lua_interface->GetInt32Value(state, 2); + string action = lua_interface->GetStringValue(state, 3); + lua_interface->ResetFunctionStack(state); + + if (!quest) { + lua_interface->LogError("%s: LUA AddQuestStepFailureAction command error: quest is not valid.", lua_interface->GetScriptName(state)); + return 0; + } + + if (step == 0) { + lua_interface->LogError("%s: LUA AddQuestStepFailureAction command error: step must be set.", lua_interface->GetScriptName(state)); + return 0; + } + + if (action.length() == 0) { + lua_interface->LogError("%s: LUA AddQuestStepFailureAction command error: action must be set.", lua_interface->GetScriptName(state)); + return 0; + } + + quest->AddFailedAction(step, action); + return 0; +} + +int EQ2Emu_lua_SetStepFailed(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + int32 quest_id = lua_interface->GetInt32Value(state, 2); + int32 step = lua_interface->GetInt32Value(state, 3); + lua_interface->ResetFunctionStack(state); + + if (!player) { + lua_interface->LogError("%s: LUA SetStepFailed command error: player is not a valid spawn.", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA SetStepFailed command error: player is not a valid player.", lua_interface->GetScriptName(state)); + return 0; + } + + if (quest_id == 0) { + lua_interface->LogError("%s: LUA SetStepFailed command error: quest_id must be set.", lua_interface->GetScriptName(state)); + return 0; + } + + if (step == 0) { + lua_interface->LogError("%s: LUA SetStepFailed command error: step must be set.", lua_interface->GetScriptName(state)); + return 0; + } + + Quest* quest = ((Player*)player)->GetQuest(quest_id); + if (!quest) { + lua_interface->LogError("LUA SetStepFailed command error: unable to get a valid quest from the given id (%u).", quest_id); + return 0; + } + + quest->StepFailed(step); + return 0; +} + +int EQ2Emu_lua_GetQuestCompleteCount(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + int32 quest_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!player) { + lua_interface->LogError("%s: LUA GetQuestCompleteCount command error: player is not a valid spawn", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA GetQuestCompleteCount command error: player is not a valid player", lua_interface->GetScriptName(state)); + return 0; + } + + if (quest_id == 0) { + lua_interface->LogError("%s: LUA GetQuestCompleteCount command error: quest id is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetInt32Value(state, ((Player*)player)->GetQuestCompletedCount(quest_id)); + return 1; +} + +int EQ2Emu_lua_SetServerVariable(lua_State* state) { + if (!lua_interface) + return 0; + + string name = lua_interface->GetStringValue(state); + string value = lua_interface->GetStringValue(state, 2); + string comment = lua_interface->GetStringValue(state, 3); + lua_interface->ResetFunctionStack(state); + + if (name.length() == 0) { + lua_interface->LogError("%s: LUA SetServerVariable command error: name is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (value.length() == 0) { + lua_interface->LogError("%s: LUA SetServerVariable command error: value is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + string varname = string("lua_").append(name); + Variable* var = variables.FindVariable(varname); + if (var) + var->SetValue(value.c_str()); + else { + var = new Variable(varname.c_str(), value.c_str(), comment.c_str()); + variables.AddVariable(var); + } + + database.SaveVariable(var->GetName(), var->GetValue(), var->GetComment()); + return 0; +} + +int EQ2Emu_lua_GetServerVariable(lua_State* state) { + if (!lua_interface) + return 0; + + string name = lua_interface->GetStringValue(state); + lua_interface->ResetFunctionStack(state); + if (name.length() == 0) { + lua_interface->LogError("%s: LUA GetServerVariable command error: name is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + string varname = string("lua_").append(name); + Variable* var = variables.FindVariable(varname); + if (var) + lua_interface->SetStringValue(state, var->GetValue()); + else + lua_interface->SetStringValue(state, "NULL"); + + return 1; +} + +int EQ2Emu_lua_HasLanguage(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + int32 language_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!player) { + lua_interface->LogError("%s: LUA HasLanguage command error: player is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA HasLanguage command error: player is not a valid player", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetBooleanValue(state, ((Player*)player)->HasLanguage(language_id)); + return 1; +} + +int EQ2Emu_lua_AddLanguage(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + int32 language_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!player) { + lua_interface->LogError("%s: LUA AddLanguage command error: player is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA AddLanguage command error: player is not a valid player", lua_interface->GetScriptName(state)); + return 0; + } + + Language* language = master_languages_list.GetLanguage(language_id); + if (language) + { + ((Player*)player)->AddLanguage(language->GetID(), language->GetName(), true); + ((Player*)player)->GetClient()->SendLanguagesUpdate(language->GetID(), 0); + } + + return 0; +} + +int EQ2Emu_lua_IsNight(lua_State* state) { + if (!lua_interface) + return 0; + + ZoneServer* zone = lua_interface->GetZone(state); + lua_interface->ResetFunctionStack(state); + if (!zone) { + lua_interface->LogError("%s: LUA IsNight command error: zone is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetBooleanValue(state, zone->IsDusk()); + return 1; +} + +int EQ2Emu_lua_AddMultiFloorLift(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA AddMultiFloorLift command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsWidget()) { + lua_interface->LogError("%s: LUA AddMultiFloorLift command error: spawn is not a widget", lua_interface->GetScriptName(state)); + return 0; + } + + ((Widget*)spawn)->SetMultiFloorLift(true); + + if (spawn->GetZone()) + spawn->GetZone()->AddTransportSpawn(spawn); + + return 0; +} + +int EQ2Emu_lua_StartAutoMount(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + int32 path = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!player) { + lua_interface->LogError("%s: LUA StartAutoMount command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA StartAutoMount command error: spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + if (path == 0) { + lua_interface->LogError("%s: LUA StartAutoMount command error: path must be greater then zero", lua_interface->GetScriptName(state)); + return 0; + } + + Client* client = ((Player*)player)->GetClient(); + if (!client) { + lua_interface->LogError("%s: LUA StartAutoMount command error: unable to get a client from the given player", lua_interface->GetScriptName(state)); + return 0; + } + + client->SendFlightAutoMount(path); + + return 0; +} + +int EQ2Emu_lua_EndAutoMount(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + if (!player) { + lua_interface->LogError("%s: LUA EndAutoMount command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA EndAutoMount command error: spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + Client* client = ((Player*)player)->GetClient(); + if (!client) { + lua_interface->LogError("%s: LUA EndAutoMount command error: unable to get a client from the given player", lua_interface->GetScriptName(state)); + return 0; + } + client->EndAutoMount(); + + return 0; +} + +int EQ2Emu_lua_IsOnAutoMount(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!player) { + lua_interface->LogError("%s: LUA IsOnAutoMount command error: spawn in not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA IsOnAutoMount command error: spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + Client* client = ((Player*)player)->GetClient(); + if (!client) { + lua_interface->LogError("%s: LUA IsOnAutoMount command error: unable to get a client from the given player", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetBooleanValue(state, client->GetOnAutoMount()); + return 1; +} + +int EQ2Emu_lua_SetPlayerHistory(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + int32 event_id = lua_interface->GetInt32Value(state, 2); + int32 value = lua_interface->GetInt32Value(state, 3); + int32 value2 = lua_interface->GetInt32Value(state, 4); + lua_interface->ResetFunctionStack(state); + + if (!player) { + lua_interface->LogError("%s: LUA SetPlayerHistory command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA SetPlayerHistory command error: spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + ((Player*)player)->UpdateLUAHistory(event_id, value, value2); + return 0; +} + +int EQ2Emu_lua_GetPlayerHistory(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + int32 event_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!player) { + lua_interface->LogError("%s: LUA GetPlayerHistory command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA GetPlayerHistory command error: spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + LUAHistory* hd = ((Player*)player)->GetLUAHistory(event_id); + if (!hd) + return 0; + + lua_interface->SetInt32Value(state, hd->Value); + lua_interface->SetInt32Value(state, hd->Value2); + return 2; +} + +int EQ2Emu_lua_SetGridID(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + int32 grid = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA SetGridID command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (grid == 0) { + lua_interface->LogError("%s: LUA SetGridID command error: grid is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + spawn->SetLocation(grid); + return 0; +} + +int EQ2Emu_lua_SetRequiredHistory(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + int32 event_id = lua_interface->GetInt32Value(state, 2); + int32 value1 = lua_interface->GetInt32Value(state, 3); + int32 value2 = lua_interface->GetInt32Value(state, 4); + bool private_spawn = (lua_interface->GetInt8Value(state, 5) == 1); + int16 flag_override = lua_interface->GetInt16Value(state, 6); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA SetRequiredHistory command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + //Add this quest to the list of required quests for this spawn + spawn->SetRequiredHistory(event_id, value1, value2); + //If private spawn value set + if (private_spawn) { + //Set the spawn to be private when not granted access via history + spawn->AddAllowAccessSpawn(spawn); + spawn->SetPrivateQuestSpawn(true); + } + //This value will override vis_flags in the vis packet + if (flag_override > 0) + spawn->SetQuestsRequiredOverride(flag_override); + return 0; +} + +int EQ2Emu_lua_GetQuestStepProgress(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + int32 quest_id = lua_interface->GetInt32Value(state, 2); + int32 step_id = lua_interface->GetInt32Value(state, 3); + lua_interface->ResetFunctionStack(state); + + if (!player) { + lua_interface->LogError("%s: LUA GetQuestStepProgress command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA GetQuestStepProgress command error: spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetInt32Value(state, ((Player*)player)->GetStepProgress(quest_id, step_id)); + + return 1; +} + +int EQ2Emu_lua_SetPlayerLevel(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + int8 level = lua_interface->GetInt8Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!player) { + lua_interface->LogError("%s: LUA SetPlayerLevel command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA SetPlayerLevel command error: spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + if (level == 0) { + lua_interface->LogError("%s: LUA SetPlayerLevel command error: new level can't be 0", lua_interface->GetScriptName(state)); + return 0; + } + + Client* client = ((Player*)player)->GetClient(); + if (!client) { + lua_interface->LogError("%s: LUA SetPlayerLevel command error: unable to get a client from the given spawn", lua_interface->GetScriptName(state)); + return 0; + } + + client->ChangeLevel(client->GetPlayer()->GetLevel(), level); + return 0; +} + +int EQ2Emu_lua_AddCoin(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + int32 amount = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!player) { + lua_interface->LogError("%s: LUA AddCoin command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA AddCoin command error: spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + if (amount == 0) { + lua_interface->LogError("%s: LUA AddCoin command error: amount must be greater then 0", lua_interface->GetScriptName(state)); + return 0; + } + + ((Player*)player)->AddCoins(amount); + return 0; +} + +int EQ2Emu_lua_RemoveCoin(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + int32 amount = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!player) { + lua_interface->LogError("%s: LUA RemoveCoin command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA RemoveCoin command error: spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + if (amount == 0) { + lua_interface->LogError("%s: LUA RemoveCoin command error: amount must be greater then 0", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetBooleanValue(state, ((Player*)player)->RemoveCoins(amount)); + return 1; +} + +int EQ2Emu_lua_GetPlayersInZone(lua_State* state) { + if (!lua_interface) + return 0; + + ZoneServer* zone = lua_interface->GetZone(state); + lua_interface->ResetFunctionStack(state); + if (!zone) { + lua_interface->LogError("%s: LUA GetPlayersInZone command error: zone is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + vector players = zone->GetPlayers(); + if (players.size() == 0) + return 0; + + lua_createtable(state, players.size(), 0); + int newTable = lua_gettop(state); + for (int32 i = 0; i < players.size(); i++) { + lua_interface->SetSpawnValue(state, players.at(i)); + lua_rawseti(state, newTable, i + 1); + } + + return 1; +} + +int EQ2Emu_lua_SpawnGroupByID(lua_State* state) { + if (!lua_interface) + return 0; + + ZoneServer* zone = lua_interface->GetZone(state, 1); + if (!zone) { + lua_interface->LogError("%s: LUA GetPlayersInZone command error: zone is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + int32 group_id = lua_interface->GetInt32Value(state, 2); + int16 custom_level = lua_interface->GetInt32Value(state, 3); + + lua_interface->ResetFunctionStack(state); + + //Map of + map* locs = zone->GetSpawnLocationsByGroup(group_id); + map::iterator itr; + + vector group; + + Spawn* leader = 0; + for (itr = locs->begin(); itr != locs->end(); itr++) { + SpawnLocation* location = zone->GetSpawnLocation(itr->second); + if (!location) { + lua_interface->LogError("LUA SpawnByLocationID command error: no location found for the given ID (%u)", itr->second); + return 0; + } + + Spawn* spawn = 0; + if (location->entities[0]) { + if (location->entities[0]->spawn_type == SPAWN_ENTRY_TYPE_NPC) + spawn = zone->AddNPCSpawn(location, location->entities[0]); + else if (location->entities[0]->spawn_type == SPAWN_ENTRY_TYPE_GROUNDSPAWN) + spawn = zone->AddGroundSpawn(location, location->entities[0]); + else if (location->entities[0]->spawn_type == SPAWN_ENTRY_TYPE_OBJECT) + spawn = zone->AddObjectSpawn(location, location->entities[0]); + else if (location->entities[0]->spawn_type == SPAWN_ENTRY_TYPE_WIDGET) + spawn = zone->AddWidgetSpawn(location, location->entities[0]); + else if (location->entities[0]->spawn_type == SPAWN_ENTRY_TYPE_SIGN) + spawn = zone->AddSignSpawn(location, location->entities[0]); + + if(spawn && spawn->IsOmittedByDBFlag()) + { + LogWrite(SPAWN__WARNING, 0, "Spawn", "Spawn (%u) was skipped due to a missing expansion / holiday flag being met (LUA SpawnGroupByID).", location->entities[0]->spawn_id); + safe_delete(spawn); + continue; + } + if (spawn) { + if(!leader) + leader = spawn; + if(leader) + leader->AddSpawnToGroup(spawn); + + spawn->SetSpawnGroupID(group_id); + + if(custom_level > 0 && custom_level != 0xFFFF) { + spawn->SetLevel(custom_level); + } + + const char* script = 0; + for (int x = 0; x < 3; x++) { + switch (x) { + case 0: + script = world.GetSpawnEntryScript(location->entities[0]->spawn_entry_id); + break; + case 1: + script = world.GetSpawnLocationScript(location->entities[0]->spawn_location_id); + break; + case 2: + script = world.GetSpawnScript(location->entities[0]->spawn_id); + break; + } + if (script && lua_interface->GetSpawnScript(script) != 0) { + spawn->SetSpawnScript(string(script)); + break; + } + } + zone->CallSpawnScript(spawn, SPAWN_SCRIPT_SPAWN); + lua_interface->SetSpawnValue(state, spawn); + group.push_back(spawn); + } + else { + LogWrite(ZONE__ERROR, 0, "Zone", "Error adding spawn by group id to zone %s with location id %u.", zone->GetZoneName(), group_id); + safe_delete(spawn); + } + } + } + + if (!group.empty()) { + lua_createtable(state, group.size(), 0); + int newTable = lua_gettop(state); + for (int32 i = 0; i < group.size(); i++) { + lua_interface->SetSpawnValue(state, group[i]); + lua_rawseti(state, newTable, i + 1); + } + } + else + lua_pushnil(state); + + return 1; +} + +int EQ2Emu_lua_SetSpawnAnimation(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state, 1); + int32 anim_id = lua_interface->GetInt32Value(state, 2); + int16 leeway = lua_interface->GetInt16Value(state, 3); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA SetSpawnAnimation command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (anim_id == 0) { + lua_interface->LogError("%s: LUA SetSpawnAnimation command error: anim_id is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (leeway == 0) + leeway = 5000; + + spawn->SetSpawnAnim(anim_id); + spawn->SetSpawnAnimLeeway(leeway); + + return 0; +} + +int EQ2Emu_lua_GetClientVersion(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!player || !player->IsPlayer()) { + return 0; + } + + Client* client = ((Player*)player)->GetClient(); + if (!client) { + return 0; + } + + + lua_interface->SetInt32Value(state, client->GetVersion()); + return 1; +} + +int EQ2Emu_lua_GetItemID(lua_State* state) { + if (!lua_interface) + return 0; + + Item* item = lua_interface->GetItem(state); + lua_interface->ResetFunctionStack(state); + if (!item) { + lua_interface->LogError("%s: LUA GetItemID command error: item is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetInt32Value(state, item->details.item_id); + return 1; +} + +int EQ2Emu_lua_IsEntity(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA IsEntity command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetBooleanValue(state, spawn->IsEntity()); + return 1; +} + +int EQ2Emu_lua_GetOrigX(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA GetOrigX command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetFloatValue(state, spawn->GetSpawnOrigX()); + return 1; +} + +int EQ2Emu_lua_GetOrigY(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA GetOrigY command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetFloatValue(state, spawn->GetSpawnOrigY()); + return 1; +} + +int EQ2Emu_lua_GetOrigZ(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA GetOrigZ command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetFloatValue(state, spawn->GetSpawnOrigZ()); + return 1; +} + +int EQ2Emu_lua_GetPCTOfHP(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + float pct = lua_interface->GetFloatValue(state, 2); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA GetPCTOfHP command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (pct == 0) { + lua_interface->LogError("%s: LUA GetPCTOfHP command error: pct is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + int32 amount = std::round(spawn->GetTotalHP() * (pct / 100)); + lua_interface->SetInt32Value(state, amount); + return 1; +} + +int EQ2Emu_lua_GetPCTOfPower(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + float pct = lua_interface->GetFloatValue(state, 2); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA GetPCTOfPower command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (pct == 0) { + lua_interface->LogError("%s: LUA GetPCTOfPower command error: pct is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + int32 amount = std::round(spawn->GetTotalPower() * (pct / 100)); + lua_interface->SetInt32Value(state, amount); + return 1; +} + +int EQ2Emu_lua_GetBoundZoneID(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA GetBoundZoneID command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsPlayer()) { + lua_interface->LogError("%s: LUA GetBoundZoneID command error: spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetInt32Value(state, ((Player*)spawn)->GetPlayerInfo()->GetBindZoneID()); + return 1; +} + +int EQ2Emu_lua_Evac(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* target = lua_interface->GetSpawn(state); + + if (target) { + float x = target->GetZone()->GetSafeX(); + float y = target->GetZone()->GetSafeY(); + float z = target->GetZone()->GetSafeZ(); + float h = target->GetZone()->GetSafeHeading(); + + target->SetX(x); + target->SetY(y); + target->SetZ(z); + target->SetHeading(h); + + target->SetSpawnOrigX(x); + target->SetSpawnOrigY(y); + target->SetSpawnOrigZ(z); + target->SetSpawnOrigHeading(h); + + if (target->IsPlayer()) { + Client* client = ((Player*)target)->GetClient(); + if (client) { + client->GetCurrentZone()->ClearHate(client->GetPlayer()); + + int numargs = lua_interface->GetNumberOfArgs(state); + if(numargs == 4) { + x = lua_interface->GetFloatValue(state,1); + y = lua_interface->GetFloatValue(state,2); + z = lua_interface->GetFloatValue(state,3); + h = lua_interface->GetFloatValue(state,4); + } + + client->SetReloadingZone(true); + target->SetX(x); + target->SetY(y); + target->SetZ(z); + target->SetHeading(h); + + target->SetSpawnOrigX(x); + target->SetSpawnOrigY(y); + target->SetSpawnOrigZ(z); + target->SetSpawnOrigHeading(h); + + target->SetAppearancePosition(x,y,z); + + client->SetZoningCoords(x,y,z,h); + + PacketStruct* packet = configReader.getStruct("WS_TeleportWithinZone", client->GetVersion()); + if (packet) + { + packet->setDataByName("x", x); + packet->setDataByName("y", y); + packet->setDataByName("z", z); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + + client->GetCurrentZone()->RemoveSpawn(target, false, false, true, true); + + client->GetPlayer()->SetSpawnSentState(target, SpawnState::SPAWN_STATE_SENT); + } + } + lua_interface->ResetFunctionStack(state); + } + else { + + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + + if(!spell || !spell->caster || !spell->caster->GetZone()) { + lua_interface->ResetFunctionStack(state); + return 0; + } + + ZoneServer* zone = spell->caster->GetZone(); + + float x = spell->caster->GetZone()->GetSafeX(); + float y = spell->caster->GetZone()->GetSafeY(); + float z = spell->caster->GetZone()->GetSafeZ(); + float h = spell->caster->GetZone()->GetSafeHeading(); + + int numargs = lua_interface->GetNumberOfArgs(state); + + if(numargs == 4) { + x = lua_interface->GetFloatValue(state,1); + y = lua_interface->GetFloatValue(state,2); + z = lua_interface->GetFloatValue(state,3); + h = lua_interface->GetFloatValue(state,4); + } + + lua_interface->ResetFunctionStack(state); + + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < spell->targets.size(); i++) { + Spawn* target2 = zone->GetSpawnByID(spell->targets.at(i)); + if (!target2) + continue; + + if (target2->IsPlayer()) { + Client* client = ((Player*)target2)->GetClient(); + if (client) { + client->GetCurrentZone()->ClearHate(client->GetPlayer()); + + client->SetReloadingZone(true); + target2->SetX(x); + target2->SetY(y); + target2->SetZ(z); + target2->SetHeading(h); + + target2->SetSpawnOrigX(x); + target2->SetSpawnOrigY(y); + target2->SetSpawnOrigZ(z); + target2->SetSpawnOrigHeading(h); + + target2->SetAppearancePosition(x,y,z); + + client->SetZoningCoords(x,y,z,h); + + PacketStruct* packet = configReader.getStruct("WS_TeleportWithinZone", client->GetVersion()); + if (packet) + { + packet->setDataByName("x", x); + packet->setDataByName("y", y); + packet->setDataByName("z", z); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + + client->GetCurrentZone()->RemoveSpawn(target2, false, false, true, true); + + client->GetPlayer()->SetSpawnSentState(target2, SpawnState::SPAWN_STATE_SENT); + } + } + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + + return 0; +} + +int EQ2Emu_lua_GetSpellTier(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + if (!luaspell || !luaspell->spell) { + lua_interface->LogError("%s: LUA GetSpellTier command error: must be used in a spell script", lua_interface->GetScriptName(state)); + return 0; + } + + int8 tier = luaspell->spell->GetSpellTier(); + lua_interface->SetInt32Value(state, tier); + return 1; +} + +int EQ2Emu_lua_GetSpellID(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + if (!luaspell || !luaspell->spell) { + lua_interface->LogError("%s: LUA GetSpellID command error: must be used in a spell script", lua_interface->GetScriptName(state)); + return 0; + } + + int32 spell_id = luaspell->spell->GetSpellID(); + lua_interface->SetInt32Value(state, spell_id); + return 1; +} + +int EQ2Emu_lua_StartTransmute(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state, 1); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: Lua StartTransmute command error: no spawn", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsPlayer()) { + lua_interface->LogError("%s: Lua StartTransmute command error: spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + ZoneServer* zone = spawn->GetZone(); + if (!zone) { + return 0; + } + + Client* client = ((Player*)spawn)->GetClient(); + if (!client) { + return 0; + } + + Transmute::CreateItemRequest(client, static_cast(spawn)); + return 0; +} + +int EQ2Emu_lua_CompleteTransmute(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state, 1); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: Lua CompleteTransmute command error: no spawn", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsPlayer()) { + lua_interface->LogError("%s: Lua CompleteTransmute command error: spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + ZoneServer* zone = spawn->GetZone(); + if (!zone) { + return 0; + } + + Client* client = ((Player*)spawn)->GetClient(); + if (!client) { + return 0; + } + + Transmute::CompleteTransmutation(client, static_cast(spawn)); + return 0; +} + +int EQ2Emu_lua_ProcHate(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* caster = lua_interface->GetSpawn(state); + Spawn* target = lua_interface->GetSpawn(state, 2); + int32 threat_amt = lua_interface->GetInt32Value(state, 3); + string spell_name = lua_interface->GetStringValue(state, 4); + lua_interface->ResetFunctionStack(state); + if (!caster) { + lua_interface->LogError("%s: LUA ProcHate command error: caster is not a valid spawn", lua_interface->GetScriptName(state)); + return 0; + } + + if (!caster->IsEntity()) { + lua_interface->LogError("%s: LUA ProcHate command error: caster is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (!target) { + lua_interface->LogError("%s: LUA ProcHate command error: target is not a valid spawn", lua_interface->GetScriptName(state)); + return 0; + } + + if (!target->IsEntity()) { + lua_interface->LogError("%s: LUA ProcHate command error: target is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + static_cast(target)->AddHate(static_cast(caster), threat_amt); + caster->GetZone()->SendThreatPacket(static_cast(caster), target, threat_amt, spell_name.c_str()); + return 0; +} + +int EQ2Emu_lua_GiveExp(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + int32 amount = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (player && player->IsPlayer() && amount > 0) { + ((Player*)player)->AddXP(amount); + } + return 0; +} + +int EQ2Emu_lua_DisplayText(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + int8 type = lua_interface->GetInt8Value(state, 2); + string text = lua_interface->GetStringValue(state, 3); + lua_interface->ResetFunctionStack(state); + Client* client = 0; + if (player && player->IsPlayer()) + client = ((Player*)player)->GetClient(); + if (!client || text.length() == 0) { + lua_interface->LogError("%s: LUA DisplayText required parameters not given", lua_interface->GetScriptName(state)); + return 0; + } + client->SimpleMessage(type, text.c_str()); + return 0; +} + +int EQ2Emu_lua_ShowLootWindow(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + Spawn* spawn = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + Client* client = 0; + if (player && player->IsPlayer()) + client = ((Player*)player)->GetClient(); + if (!client || !spawn) { + lua_interface->LogError("%s: LUA ShowLootWindow required parameters not given", lua_interface->GetScriptName(state)); + return 0; + } + vector* items = ((Player*)player)->GetPendingLootItems(spawn->GetID()); + if (!items) { + lua_interface->LogError("%s: LUA ShowLootWindow has no items", lua_interface->GetScriptName(state)); + return 0; + } + client->SendLootResponsePacket(spawn->GetLootCoins(), items, spawn, true); + return 0; +} + +int EQ2Emu_lua_GetRandomSpawnByID(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawnref = lua_interface->GetSpawn(state); + int32 spawn_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn_id > 0 && spawnref) { + vector spawns = spawnref->GetZone()->GetSpawnsByID(spawn_id); + if (spawns.size() == 0) { + lua_interface->LogError("%s: LUA EQ2Emu_lua_GetRandomSpawnByID command error: GetSpawnsByID returned no spawns", lua_interface->GetScriptName(state)); + return 0; + } + Spawn* spawn = 0; + int16 index = MakeRandomInt(0, spawns.size()); + if (index >= spawns.size() || index < 0) + index = 0; + spawn = spawns[index]; + lua_interface->SetSpawnValue(state, spawn); + return 1; + } + else { + lua_interface->LogError("%s: LUA GetRandomSpawnByID required parameters not given", lua_interface->GetScriptName(state)); + } + + return 0; +} + +int EQ2Emu_lua_AddPrimaryEntityCommandAllSpawns(lua_State* state) { + Spawn* player = lua_interface->GetSpawn(state); + int32 spawn_id = lua_interface->GetInt32Value(state, 2); + string name = lua_interface->GetStringValue(state, 3); + float distance = lua_interface->GetFloatValue(state, 4); + string command = lua_interface->GetStringValue(state, 5); + string error_text = lua_interface->GetStringValue(state, 6); + int16 cast_time = lua_interface->GetInt16Value(state, 7); + int32 spell_visual = lua_interface->GetInt32Value(state, 8); + lua_interface->ResetFunctionStack(state); + if (spawn_id && player && player->IsPlayer() && name.length() > 0) { + if (distance == 0) + distance = 10.0f; + if (command.length() == 0) + command = name; + vector spawns = player->GetZone()->GetSpawnsByID(spawn_id); + if (spawns.size() == 0) { + lua_interface->LogError("%s: LUA AddPrimaryEntityCommandAllSpawns command error: GetSpawnsByID returned no spawns", lua_interface->GetScriptName(state)); + return 0; + } + Spawn* spawn = 0; + for (vector::iterator itr = spawns.begin(); itr != spawns.end(); itr++) { + spawn = *itr; + if (spawn) { + spawn->AddPrimaryEntityCommand(name.c_str(), distance, command.c_str(), error_text.c_str(), cast_time, spell_visual); + player->GetZone()->SendUpdateDefaultCommand(spawn, command.c_str(), distance); + } + } + + } + return 0; +} + +int EQ2Emu_lua_InstructionWindowGoal(lua_State* state) { + if (!lua_interface) + return 0; + Client* client = 0; + Spawn* player = lua_interface->GetSpawn(state); + int8 goal_num = lua_interface->GetInt8Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer() && player->GetZone()) + client = ((Player*)player)->GetClient(); + else{ + lua_interface->LogError("LUA InstructionWindowGoal command error: player is not valid"); + return 0; + } + if (client) { + PacketStruct* packet = configReader.getStruct("WS_InstructionWindow", client->GetVersion()); + if (packet) { + packet->setDataByName("goal_num", goal_num); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + return 0; +} + +int EQ2Emu_lua_InstructionWindowClose(lua_State* state) { + if (!lua_interface) + return 0; + Client* client = 0; + Spawn* player = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer() && player->GetZone()) + client = ((Player*)player)->GetClient(); + else { + lua_interface->LogError("LUA InstructionWindowClose command error: player is not valid"); + return 0; + } + if (client && client->GetVersion() >= 374) { + client->QueuePacket(new EQ2Packet(OP_EqInstructionWindowCloseCmd, 0, 0)); + } + return 0; +} + +int EQ2Emu_lua_InstructionWindow(lua_State* state) { + if (!lua_interface) + return 0; + Client* client = 0; + Spawn* player = lua_interface->GetSpawn(state); + float duration = lua_interface->GetFloatValue(state, 2); + string text = lua_interface->GetStringValue(state, 3); + string voice = lua_interface->GetStringValue(state, 4); + int32 voice_key1 = lua_interface->GetInt32Value(state, 5); + int32 voice_key2 = lua_interface->GetInt32Value(state, 6); + string signal = lua_interface->GetStringValue(state, 7); + string goal1 = lua_interface->GetStringValue(state, 8); + string task1 = lua_interface->GetStringValue(state, 9); + string goal2 = lua_interface->GetStringValue(state, 10); + string task2 = lua_interface->GetStringValue(state, 11); + string goal3 = lua_interface->GetStringValue(state, 12); + string task3 = lua_interface->GetStringValue(state, 13); + string goal4 = lua_interface->GetStringValue(state, 14); + string task4 = lua_interface->GetStringValue(state, 15); + lua_interface->ResetFunctionStack(state); + + if (!player) { + lua_interface->LogError("LUA InstructionWindow command error: spawn is not valid"); + return 0; + } + if (!player->IsPlayer()) { + lua_interface->LogError("LUA InstructionWindow command error: spawn is not a player"); + return 0; + } + else + client = ((Player*)player)->GetClient(); + + if (!client) { + lua_interface->LogError("LUA InstructionWindow command error: could not find client"); + return 0; + } + if (text.length() == 0) { + lua_interface->LogError("LUA InstructionWindow required parameters not given"); + return 0; + } + if (duration >= 0 && duration < 2) + duration = 2; + PacketStruct* packet = configReader.getStruct("WS_InstructionWindow", client->GetVersion()); + if (packet) { + packet->setDataByName("open_seconds_max", duration); + packet->setDataByName("text", text.c_str()); + packet->setDataByName("voice", voice.c_str()); + int8 num_goals = 1; + if (task2.length() > 0) + num_goals++; + if (task3.length() > 0) + num_goals++; + if (task4.length() > 0) + num_goals++; + packet->setArrayLengthByName("num_goals", num_goals); + for (int8 i = 0; i < num_goals; i++) { + packet->setSubArrayLengthByName("num_tasks", 1, i); + } + if (goal1.length() > 0) + packet->setArrayDataByName("goal_text", goal1.c_str()); + if (goal2.length() > 0) + packet->setArrayDataByName("goal_text", goal2.c_str(), 1); + if (goal3.length() > 0) + packet->setArrayDataByName("goal_text", goal3.c_str(), 2); + if (goal4.length() > 0) + packet->setArrayDataByName("goal_text", goal4.c_str(), 3); + packet->setSubArrayDataByName("task_text", task1.c_str()); + if (task2.length() > 0) + packet->setSubArrayDataByName("task_text", task2.c_str(), 1); + if (task3.length() > 0) + packet->setSubArrayDataByName("task_text", task3.c_str(), 2); + if (task4.length() > 0) + packet->setSubArrayDataByName("task_text", task4.c_str(), 3); + packet->setDataByName("complete_sound", "click"); + packet->setDataByName("signal", signal.c_str()); + packet->setDataByName("voice_key1", voice_key1); + packet->setDataByName("voice_key2", voice_key2); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + return 0; +} + +int EQ2Emu_lua_ShowWindow(lua_State* state) { + if (!lua_interface) + return 0; + Client* client = 0; + Spawn* player = lua_interface->GetSpawn(state); + string window = lua_interface->GetStringValue(state, 2); + int8 show = lua_interface->GetInt8Value(state, 3); + lua_interface->ResetFunctionStack(state); + if (!player) { + lua_interface->LogError("LUA ShowWindow command error: spawn is not valid"); + return 0; + } + if (!player->IsPlayer()) { + lua_interface->LogError("LUA ShowWindow command error: spawn is not a player"); + return 0; + } + else + client = ((Player*)player)->GetClient(); + + if (!client) { + lua_interface->LogError("LUA ShowWindow command error: could not find client"); + return 0; + } + if (window.length() == 0) { + lua_interface->LogError("LUA ShowWindow required parameters not given"); + return 0; + } + PacketStruct* packet = configReader.getStruct("WS_ShowWindow", client->GetVersion()); + if (packet) { + packet->setDataByName("window", window.c_str()); + packet->setDataByName("show", show); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + return 0; +} + +int EQ2Emu_lua_EnableGameEvent(lua_State* state) { + //See GameEvents.txt for options that can be used for this function + if (!lua_interface) + return 0; + Client* client = 0; + Spawn* player = lua_interface->GetSpawn(state); + string event_name = lua_interface->GetStringValue(state, 2); + int8 enabled = lua_interface->GetInt8Value(state, 3); + lua_interface->ResetFunctionStack(state); + if (!player || !player->IsPlayer()) { + lua_interface->LogError("LUA EnableGameEvent error: player is not valid"); + return 0; + } + + client = ((Player*)player)->GetClient(); + + if (!client) { + lua_interface->LogError("LUA EnableGameEvent error: could not find client"); + return 0; + } + PacketStruct* packet = configReader.getStruct("WS_EnableGameEvent", client->GetVersion()); + if (packet) { + packet->setDataByName("event_name", event_name.c_str()); + packet->setDataByName("enabled", enabled); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + return 0; +} + +int EQ2Emu_lua_GetTutorialStep(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer()) { + lua_interface->SetInt32Value(state, ((Player*)player)->GetTutorialStep()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_SetTutorialStep(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + int8 step = lua_interface->GetInt8Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer() && step > 0) { + ((Player*)player)->SetTutorialStep(step); + } + return 0; +} + +int EQ2Emu_lua_FlashWindow(lua_State* state) { + if (!lua_interface) + return 0; + Client* client = 0; + Spawn* player = lua_interface->GetSpawn(state); + string window = lua_interface->GetStringValue(state, 2); + float flash_seconds = lua_interface->GetFloatValue(state, 3); + lua_interface->ResetFunctionStack(state); + if (!player) { + lua_interface->LogError("LUA FlashWindow command error: spawn is not valid"); + return 0; + } + if (!player->IsPlayer()) { + lua_interface->LogError("LUA FlashWindow command error: spawn is not a player"); + return 0; + } + else + client = ((Player*)player)->GetClient(); + + if (!client) { + lua_interface->LogError("LUA FlashWindow command error: could not find client"); + return 0; + } + if (window.length() == 0) { + lua_interface->LogError("LUA FlashWindow required parameters not given"); + return 0; + } + PacketStruct* packet = configReader.getStruct("WS_FlashWindow", client->GetVersion()); + if (packet) { + packet->setDataByName("window", window.c_str()); + packet->setDataByName("flash_seconds", flash_seconds); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + return 0; +} + +int EQ2Emu_lua_CheckLOS(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + Spawn* target = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn && target) + return spawn->CheckLoS(target); + + return 0; +} +int EQ2Emu_lua_CheckLOSByCoordinates(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + float x = lua_interface->GetFloatValue(state, 2); + float y = lua_interface->GetFloatValue(state, 3); + float z = lua_interface->GetFloatValue(state, 4); + lua_interface->ResetFunctionStack(state); + if (spawn) + return spawn->CheckLoS(glm::vec3(spawn->GetX(), spawn->GetZ(), spawn->GetY() + 1.0f), glm::vec3(x, z, y+1.0f)); + + return 0; +} + +int EQ2Emu_lua_SetZoneExpansionFlag(lua_State* state) { + if (!lua_interface) + return 0; + ZoneServer* zone = lua_interface->GetZone(state); + int32 xpackFlag = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (zone) + zone->SetExpansionFlag(xpackFlag); + return 0; +} + +int EQ2Emu_lua_GetZoneExpansionFlag(lua_State* state) { + if (!lua_interface) + return 0; + ZoneServer* zone = lua_interface->GetZone(state); + lua_interface->ResetFunctionStack(state); + if (zone) { + lua_interface->SetInt32Value(state, zone->GetExpansionFlag()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_SetZoneHolidayFlag(lua_State* state) { + if (!lua_interface) + return 0; + ZoneServer* zone = lua_interface->GetZone(state); + int32 holidayFlag = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (zone) + zone->SetHolidayFlag(holidayFlag); + return 0; +} + +int EQ2Emu_lua_GetZoneHolidayFlag(lua_State* state) { + if (!lua_interface) + return 0; + ZoneServer* zone = lua_interface->GetZone(state); + lua_interface->ResetFunctionStack(state); + if (zone) { + lua_interface->SetInt32Value(state, zone->GetHolidayFlag()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_SetCanBind(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + bool canbind = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if(!spawn) { + lua_interface->LogError("%s: LUA SetCanBind command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + ZoneServer* zone = spawn->GetZone(); + if (zone) + zone->SetCanBind(canbind); + return 0; +} + +int EQ2Emu_lua_GetCanBind(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if(!spawn) { + lua_interface->LogError("%s: LUA GetCanBind command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + ZoneServer* zone = spawn->GetZone(); + if (zone) { + lua_interface->SetInt32Value(state, zone->GetCanBind()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_SetCanGate(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + bool cangate = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if(!spawn) { + lua_interface->LogError("%s: LUA SetCanGate command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + ZoneServer* zone = spawn->GetZone(); + if (zone) + zone->SetCanGate(cangate); + return 0; +} + +int EQ2Emu_lua_GetCanGate(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if(!spawn) { + lua_interface->LogError("%s: LUA GetCanGate command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + ZoneServer* zone = spawn->GetZone(); + if (zone) { + lua_interface->SetInt32Value(state, zone->GetCanGate()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_SetCanEvac(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + bool canevac = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if(!spawn) { + lua_interface->LogError("%s: LUA SetCanEvac command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + ZoneServer* zone = spawn->GetZone(); + if (zone) + zone->SetCanEvac(canevac); + return 0; +} + +int EQ2Emu_lua_GetCanEvac(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if(!spawn) { + lua_interface->LogError("%s: LUA GetCanEvac command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + ZoneServer* zone = spawn->GetZone(); + if (zone) { + lua_interface->SetInt32Value(state, zone->GetCanEvac()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_AddSpawnProximity(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 spawn_value = lua_interface->GetInt32Value(state, 2); + int8 spawn_type = lua_interface->GetInt8Value(state, 3); + float distance = lua_interface->GetFloatValue(state, 4); + string in_range_function = lua_interface->GetStringValue(state, 5); + string leaving_range_function = lua_interface->GetStringValue(state, 6); + lua_interface->ResetFunctionStack(state); + if (spawn && distance > 0 && in_range_function.length() > 0) + spawn->AddLUASpawnProximity(spawn_value, (Spawn::SpawnProximityType)spawn_type, distance, in_range_function, leaving_range_function); + return 0; +} + +int EQ2Emu_lua_CanSeeInvis(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + Spawn* target = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn && target) + { + if (spawn->IsPlayer() && target->IsEntity()) + { + lua_interface->SetBooleanValue(state, ((Player*)spawn)->CanSeeInvis((Entity*)target)); + return 1; + } + else if (spawn->IsEntity() && target->IsEntity()) + { + lua_interface->SetBooleanValue(state, ((Entity*)spawn)->CanSeeInvis((Entity*)target)); + return 1; + } + } + + return 0; +} + +int EQ2Emu_lua_SetSeeInvis(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + bool val = (lua_interface->GetInt8Value(state, 2) == 1); + lua_interface->ResetFunctionStack(state); + if (spawn && spawn->IsEntity()) + { + ((Entity*)spawn)->SetSeeInvisSpell(val); + if (spawn->IsPlayer()) + { + Client* client = ((Player*)spawn)->GetClient(); + if (client) + ((Player*)spawn)->GetZone()->SendAllSpawnsForSeeInvisChange(client); + } + } + + return 0; +} + +int EQ2Emu_lua_SetSeeHide(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + bool val = (lua_interface->GetInt8Value(state, 2) == 1); + lua_interface->ResetFunctionStack(state); + if (spawn && spawn->IsEntity()) + { + ((Entity*)spawn)->SetSeeHideSpell(val); + if (spawn->IsPlayer()) + { + Client* client = ((Player*)spawn)->GetClient(); + if (client) + ((Player*)spawn)->GetZone()->SendAllSpawnsForVisChange(client); + } + } + + return 0; +} + + +int EQ2Emu_lua_SetAccessToEntityCommand(lua_State* state) +{ + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + Spawn* spawn = lua_interface->GetSpawn(state, 2); + string command = lua_interface->GetStringValue(state, 3); + bool val = (lua_interface->GetInt8Value(state, 4) == 1); + + lua_interface->ResetFunctionStack(state); + if (spawn && player && player->IsPlayer()) + { + EntityCommand* cmd = spawn->FindEntityCommand(string(command), true); + bool res = false; + if (cmd) + res = spawn->SetPermissionToEntityCommand(cmd, (Player*)player, val); + + lua_interface->SetBooleanValue(state, res); + return 1; + } + + return 0; +} + + +int EQ2Emu_lua_SetAccessToEntityCommandByCharID(lua_State* state) +{ + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + int32 charID = lua_interface->GetInt32Value(state, 2); + string command = lua_interface->GetStringValue(state, 3); + bool val = (lua_interface->GetInt8Value(state, 4) == 1); + + lua_interface->ResetFunctionStack(state); + if (spawn && charID) + { + EntityCommand* cmd = spawn->FindEntityCommand(string(command), true); + bool res = false; + if (cmd) + res = spawn->SetPermissionToEntityCommandByCharID(cmd, charID, val); + + lua_interface->SetBooleanValue(state, res); + return 1; + } + + return 0; +} + +int EQ2Emu_lua_RemovePrimaryEntityCommand(lua_State* state) +{ + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + string command = lua_interface->GetStringValue(state, 2); + + lua_interface->ResetFunctionStack(state); + if (spawn && command.length() > 0) + spawn->RemovePrimaryEntityCommand(command.c_str()); + + return 0; +} + + +int EQ2Emu_lua_SendUpdateDefaultCommand(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + float distance = lua_interface->GetFloatValue(state, 2); + string command = lua_interface->GetStringValue(state, 3); + Spawn* player = lua_interface->GetSpawn(state, 4); + + lua_interface->ResetFunctionStack(state); + if (spawn) { + spawn->GetZone()->SendUpdateDefaultCommand(spawn, command.c_str(), distance, player); + } + return 0; +} + +int EQ2Emu_lua_SendTransporters(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + Spawn* spawn = lua_interface->GetSpawn(state); + Spawn* player = lua_interface->GetSpawn(state, 2); + int32 transport_id = lua_interface->GetInt32Value(state, 3); + + lua_interface->ResetFunctionStack(state); + if (spawn && player && transport_id && player->IsPlayer()) { + Client* client = 0; + if (player && player->IsPlayer()) + client = ((Player*)player)->GetClient(); + + if (!client) + return 0; + + vector destinations; + player->GetZone()->GetTransporters(&destinations, client, transport_id); + + if (destinations.size()) + { + client->SetTemporaryTransportID(transport_id); + client->ProcessTeleport(spawn, &destinations, transport_id, (spell != nullptr) ? true : false); + } + else + client->Message(CHANNEL_COLOR_RED, "There are no transporters available (ID: %u)", transport_id); + } + return 0; +} + +int EQ2Emu_lua_SetTemporaryTransportID(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + int32 transport_id = lua_interface->GetInt32Value(state, 2); + + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer()) { + Client* client = 0; + if (player && player->IsPlayer()) + client = ((Player*)player)->GetClient(); + + if (!client) + return 0; + + client->SetTemporaryTransportID(transport_id); + } + return 0; +} + +int EQ2Emu_lua_GetTemporaryTransportID(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer()) { + Client* client = 0; + if (player && player->IsPlayer()) + client = ((Player*)player)->GetClient(); + + if (!client) + return 0; + + lua_interface->SetInt32Value(state, client->GetTemporaryTransportID()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_SetAlignment(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + sint32 alignment = lua_interface->GetSInt32Value(state, 2); + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA SetAlignment command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA SetAlignment command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (alignment < SCHAR_MIN || alignment > SCHAR_MAX) + { + lua_interface->LogError("%s: LUA SetAlignment command error: alignment value beyond supported min: %i and max: %i", lua_interface->GetScriptName(state), SCHAR_MIN, SCHAR_MAX); + return 0; + } + + lua_interface->ResetFunctionStack(state); + + if (spell && spell->targets.size() > 0) { + ZoneServer* zone = spell->caster->GetZone(); + for (int8 i = 0; i < spell->targets.size(); i++) { + Spawn* target = zone->GetSpawnByID(spell->targets.at(i)); + if (target && target->IsEntity()) { + ((Entity*)target)->GetInfoStruct()->set_alignment((sint8)alignment); + if (target->IsPlayer()) + ((Player*)target)->SetCharSheetChanged(true); + } + } + } + else { + ((Entity*)spawn)->GetInfoStruct()->set_alignment((sint8)alignment); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + return 0; +} + +int EQ2Emu_lua_GetAlignment(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA GetAlignment command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA GetAlignment command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetSInt32Value(state, ((Entity*)spawn)->GetAlignment()); + return 1; +} + + +int EQ2Emu_lua_GetSpell(lua_State* state) { + if (!lua_interface) + return 0; + int32 spell_id = lua_interface->GetInt32Value(state); + int8 spell_tier = lua_interface->GetInt8Value(state, 2); + string custom_lua_script = lua_interface->GetStringValue(state, 3); + if (spell_id > 0) { + + if (spell_tier == 0) + spell_tier = 1; + + Spell* spell = master_spell_list.GetSpell(spell_id, spell_tier); + + if(!spell) { + lua_interface->LogError("%s: GetSpell: Failed, spell id %u spell tier %u does not exist.", lua_interface->GetScriptName(state), spell_id, spell_tier); + lua_interface->ResetFunctionStack(state); + return 0; + } + + LuaSpell* lua_spell = 0; + if(custom_lua_script.size() > 0) + { + // attempt to load the custom script since it isn't already loaded + // we will re-obtain the lua_spell further below + if((lua_spell = lua_interface->GetSpell(custom_lua_script.c_str())) == nullptr) + { + LogWrite(LUA__WARNING, 0, "LUA", "GetSpell(%u, %u, '%s'), custom lua script not loaded, attempting to load.", spell_id, spell_tier, custom_lua_script.c_str()); + lua_interface->LoadLuaSpell(custom_lua_script); + } + } + else + custom_lua_script = spell->GetSpellData()->lua_script; + + + if (!lua_spell && lua_interface) + lua_spell = lua_interface->GetSpell(custom_lua_script.c_str()); + + lua_interface->ResetFunctionStack(state); + + if (!lua_spell) + { + LogWrite(LUA__ERROR, 0, "LUA", "GetSpell(%u, %u, '%s') spell could not be loaded.", spell_id, spell_tier, custom_lua_script.c_str()); + return 0; + } + + lua_spell->spell = new Spell(spell); + + lua_interface->AddCustomSpell(lua_spell); + + lua_interface->SetSpellValue(state, lua_spell); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetSpellData(lua_State* state) { + if (!lua_interface) + return 0; + LuaSpell* spell = lua_interface->GetSpell(state); + string field = lua_interface->GetStringValue(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!spell) { + lua_interface->LogError("%s: Spell not given in GetSpellData!", lua_interface->GetScriptName(state)); + return 0; + } + if (!spell->spell || !spell->spell->GetSpellData()) { + lua_interface->LogError("%s: Inner Spell or SpellData not given in GetSpellData!", lua_interface->GetScriptName(state)); + return 0; + } + + boost::to_lower(field); + + + return spell->spell->GetSpellData(state, field); +} + + +int EQ2Emu_lua_SetSpellData(lua_State* state) { + if (!lua_interface) + return 0; + LuaSpell* spell = lua_interface->GetSpell(state); + string field = lua_interface->GetStringValue(state, 2); + int8 fieldArg = 3; // field value after the initial set + + if (!spell) { + lua_interface->LogError("%s: Spell not given in SetSpellData!", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + if (!spell->spell || !spell->spell->GetSpellData()) { + lua_interface->LogError("%s: Inner Spell or SpellData not given in SetSpellData!", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + + boost::to_lower(field); + + bool valSet = false; + + spell->spell->SetSpellData(state, field, fieldArg); + lua_interface->ResetFunctionStack(state); + + return valSet; +} + +int EQ2Emu_lua_SetSpellDataIndex(lua_State* state) { + if (!lua_interface) + return 0; + LuaSpell* spell = lua_interface->GetSpell(state); + int8 idx = lua_interface->GetInt32Value(state, 2); + + if (!spell) { + lua_interface->LogError("%s: Spell not given in SetSpellDataIndex!", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + if (!spell->spell || !spell->spell->GetSpellData()) { + lua_interface->LogError("%s: Inner Spell or SpellData not given in SetSpellDataIndex!", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + + if (spell->spell->lua_data.size() <= idx) + { + lua_interface->LogError("%s: lua_data size %i <= %i (idx passed) SetSpellDataIndex!", lua_interface->GetScriptName(state), spell->spell->lua_data.size(), idx); + lua_interface->ResetFunctionStack(state); + return 0; + } + + bool setVal = true; + + LUAData* data = spell->spell->lua_data[idx]; + + switch (data->type) + { + case 0: + { + sint32 value = lua_interface->GetSInt32Value(state, 3); + sint32 value2 = lua_interface->GetSInt32Value(state, 4); + data->int_value = value; + data->int_value2 = value2; + break; + } + case 1: + { + float value = lua_interface->GetFloatValue(state, 3); + float value2 = lua_interface->GetFloatValue(state, 4); + data->float_value = value; + data->float_value2 = value2; + break; + } + case 2: + { + bool value = lua_interface->GetBooleanValue(state, 3); + data->bool_value = value; + break; + } + case 3: + { + string value = lua_interface->GetStringValue(state, 3); + string value2 = lua_interface->GetStringValue(state, 4); + data->string_value = value; + data->string_value2 = value2; + break; + } + default: + setVal = false; + } + + lua_interface->ResetFunctionStack(state); + + return setVal; +} + + +int EQ2Emu_lua_GetSpellDataIndex(lua_State* state) { + if (!lua_interface) + return 0; + LuaSpell* spell = lua_interface->GetSpell(state); + int8 idx = lua_interface->GetInt32Value(state, 2); + bool secondfield = lua_interface->GetBooleanValue(state, 3); + lua_interface->ResetFunctionStack(state); + + if (!spell) { + lua_interface->LogError("%s: Spell not given in GetSpellDataIndex!", lua_interface->GetScriptName(state)); + return 0; + } + if (!spell->spell || !spell->spell->GetSpellData()) { + lua_interface->LogError("%s: Inner Spell or SpellData not given in GetSpellDataIndex!", lua_interface->GetScriptName(state)); + return 0; + } + + if (spell->spell->lua_data.size() <= idx) + { + lua_interface->LogError("%s: lua_data size %i <= %i (idx passed) GetSpellDataIndex!", lua_interface->GetScriptName(state), spell->spell->lua_data.size(), idx); + return 0; + } + + bool setVal = true; + + LUAData* data = spell->spell->lua_data[idx]; + + switch (data->type) + { + case 0: + { + if(!secondfield) + lua_interface->SetSInt32Value(state, data->int_value); + else + lua_interface->SetSInt32Value(state, data->int_value2); + break; + } + case 1: + { + if (!secondfield) + lua_interface->SetFloatValue(state, data->float_value); + else + lua_interface->SetFloatValue(state, data->float_value2); + break; + } + case 2: + { + lua_interface->SetBooleanValue(state, data->bool_value); + break; + } + case 3: + { + if (!secondfield) + lua_interface->SetStringValue(state, data->string_value.c_str()); + else + lua_interface->SetStringValue(state, data->string_value2.c_str()); + break; + } + default: + setVal = false; + } + + return setVal; +} + + +int EQ2Emu_lua_SetSpellDisplayEffect(lua_State* state) { + if (!lua_interface) + return 0; + LuaSpell* spell = lua_interface->GetSpell(state); + int8 idx = lua_interface->GetInt32Value(state, 2); + string field = lua_interface->GetStringValue(state, 3); + + boost::to_lower(field); + + if (!spell) { + lua_interface->LogError("%s: Spell not given in SetSpellDisplayEffect!", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + if (!spell->spell || !spell->spell->GetSpellData()) { + lua_interface->LogError("%s: Inner Spell or SpellData not given in SetSpellDisplayEffect!", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + + if (spell->spell->effects.size() <= idx) + { + lua_interface->LogError("%s: lua_data size %i <= %i (idx passed) SetSpellDisplayEffect!", lua_interface->GetScriptName(state), spell->spell->lua_data.size(), idx); + lua_interface->ResetFunctionStack(state); + return 0; + } + + // do we need to lock? eh probably not this should only be used before use of the custom spell + SpellDisplayEffect* effect = spell->spell->effects[idx]; + + if (field == "description") + effect->description = string(lua_interface->GetStringValue(state, 4)); + else if (field == "bullet") + effect->subbullet = lua_interface->GetInt8Value(state, 4); + else if (field == "percentage") + effect->percentage = lua_interface->GetInt8Value(state, 4); + else { // no match + lua_interface->ResetFunctionStack(state); + return 0; + } + + lua_interface->ResetFunctionStack(state); + + return 1; +} + +int EQ2Emu_lua_GetSpellDisplayEffect(lua_State* state) { + if (!lua_interface) + return 0; + LuaSpell* spell = lua_interface->GetSpell(state); + int8 idx = lua_interface->GetInt32Value(state, 2); + string field = lua_interface->GetStringValue(state, 3); + + lua_interface->ResetFunctionStack(state); + + boost::to_lower(field); + + if (!spell) { + lua_interface->LogError("%s: Spell not given in GetSpellDisplayEffect!", lua_interface->GetScriptName(state)); + return 0; + } + if (!spell->spell || !spell->spell->GetSpellData()) { + lua_interface->LogError("%s: Inner Spell or SpellData not given in GetSpellDisplayEffect!", lua_interface->GetScriptName(state)); + return 0; + } + + if (spell->spell->effects.size() <= idx) + { + lua_interface->LogError("%s: lua_data size %i <= %i (idx passed) GetSpellDisplayEffect!", lua_interface->GetScriptName(state), spell->spell->lua_data.size(), idx); + return 0; + } + + // do we need to lock? eh probably not this should only be used before use of the custom spell + SpellDisplayEffect* effect = spell->spell->effects[idx]; + + if (field == "description") + lua_interface->SetStringValue(state, effect->description.c_str()); + else if (field == "bullet") + lua_interface->SetInt32Value(state, effect->subbullet); + else if (field == "percentage") + lua_interface->SetInt32Value(state, effect->percentage); + else // no match + return 0; + + + return 1; +} +int EQ2Emu_lua_CastCustomSpell(lua_State* state) { + if (!lua_interface) + return 0; + LuaSpell* spell = lua_interface->GetSpell(state); + Spawn* caster = lua_interface->GetSpawn(state, 2); + Spawn* target = lua_interface->GetSpawn(state, 3); + lua_interface->ResetFunctionStack(state); + + if (!target) { + lua_interface->LogError("%s: LUA CastCustomSpell command error: target is not a valid spawn", lua_interface->GetScriptName(state)); + return 0; + } + + if (!target->IsEntity()) { + lua_interface->LogError("%s: LUA CastCustomSpell command error: target (%s) is not an entity", lua_interface->GetScriptName(state), target->GetName()); + return 0; + } + + if (!spell) { + lua_interface->LogError("%s: LUA CastCustomSpell command error: spell is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (caster && !caster->IsEntity()) { + lua_interface->LogError("%s: LUA CastSpell command error: caster (%s) is not an entity", lua_interface->GetScriptName(state), caster->GetName()); + return 0; + } + + target->GetZone()->ProcessSpell(NULL, (Entity*)caster, (Entity*)target, true, false, spell, 0); + return 0; +} + +int EQ2Emu_lua_InWater(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + lua_interface->SetBooleanValue(state, spawn->InWater()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_InLava(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + lua_interface->SetBooleanValue(state, spawn->InLava()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_DamageSpawn(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* attacker = lua_interface->GetSpawn(state); + Spawn* victim = lua_interface->GetSpawn(state, 2); + int8 type = lua_interface->GetInt8Value(state, 3); + int8 dmg_type = lua_interface->GetInt8Value(state, 4); + int32 low_damage = lua_interface->GetInt32Value(state, 5); + int32 high_damage = lua_interface->GetInt32Value(state, 6); + string spell_name = lua_interface->GetStringValue(state, 7); + int8 crit_mod = lua_interface->GetInt8Value(state, 8); + bool is_tick = (lua_interface->GetInt8Value(state, 9) == 1); + bool no_calcs = (lua_interface->GetInt8Value(state, 10) == 1); + bool ignore_attacker = (lua_interface->GetInt8Value(state, 11) == 1); + bool take_power = (lua_interface->GetInt8Value(state, 12) == 1); + + lua_interface->ResetFunctionStack(state); + if (!attacker) { + lua_interface->LogError("%s: LUA ProcDamage command error: caster is not a valid spawn", lua_interface->GetScriptName(state)); + lua_interface->SetBooleanValue(state, false); + return 1; + + } + + if (!attacker->IsEntity()) { + lua_interface->LogError("%s: LUA ProcDamage command error: caster is not an entity", lua_interface->GetScriptName(state)); + lua_interface->SetBooleanValue(state, false); + return 1; + + } + + if (!victim) { + lua_interface->LogError("%s: LUA ProcDamage command error: target is not a valid spawn", lua_interface->GetScriptName(state)); + lua_interface->SetBooleanValue(state, false); + return 1; + + } + + if (!victim->IsEntity()) { + lua_interface->LogError("%s: LUA ProcDamage command error: target is not an entity", lua_interface->GetScriptName(state)); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + bool has_damaged = ((Entity*)attacker)->DamageSpawn((Entity*)victim, type, dmg_type, low_damage, high_damage, spell_name.c_str(), crit_mod, is_tick, no_calcs, ignore_attacker, take_power); + lua_interface->SetBooleanValue(state, has_damaged); + return 1; +} + +int EQ2Emu_lua_IsInvulnerable(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + lua_interface->SetBooleanValue(state, spawn->GetInvulnerable()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_SetInvulnerable(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + bool invul = lua_interface->GetBooleanValue(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn) { + spawn->SetInvulnerable(invul); + } + return 0; +} + +int EQ2Emu_lua_GetRuleFlagBool(lua_State* state) { + if (!lua_interface) + return 0; + string category = lua_interface->GetStringValue(state); + string name = lua_interface->GetStringValue(state, 2); + lua_interface->ResetFunctionStack(state); + Rule *ret = 0; + if ((ret = rule_manager.GetGlobalRule(category.c_str(), name.c_str()))) { + + lua_interface->SetBooleanValue(state, ret->GetBool()); + return 1; + } + + lua_interface->LogError("%s: LUA GetRuleFlagBool Unknown rule with category '%s' and type '%s'", lua_interface->GetScriptName(state), category.c_str(), name.c_str()); + return 0; +} + +int EQ2Emu_lua_GetRuleFlagInt32(lua_State* state) { + if (!lua_interface) + return 0; + string category = lua_interface->GetStringValue(state); + string name = lua_interface->GetStringValue(state, 2); + lua_interface->ResetFunctionStack(state); + Rule *ret = 0; + if ((ret = rule_manager.GetGlobalRule(category.c_str(), name.c_str()))) { + + lua_interface->SetInt32Value(state, ret->GetInt32()); + return 1; + } + + lua_interface->LogError("%s: LUA GetRuleFlagInt32 Unknown rule with category '%s' and type '%s'", lua_interface->GetScriptName(state), category.c_str(), name.c_str()); + return 0; +} + +int EQ2Emu_lua_GetRuleFlagFloat(lua_State* state) { + if (!lua_interface) + return 0; + string category = lua_interface->GetStringValue(state); + string name = lua_interface->GetStringValue(state, 2); + lua_interface->ResetFunctionStack(state); + Rule *ret = 0; + if ((ret = rule_manager.GetGlobalRule(category.c_str(), name.c_str()))) { + + lua_interface->SetFloatValue(state, ret->GetFloat()); + return 1; + } + + lua_interface->LogError("%s: LUA GetRuleFlagFloat Unknown rule with category '%s' and type '%s'", lua_interface->GetScriptName(state), category.c_str(), name.c_str()); + return 0; +} + + +int EQ2Emu_lua_GetAAInfo(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + string type = lua_interface->GetStringValue(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn) { + int res = 1; + + boost::to_lower(type); + if(type == "assigned_aa") + lua_interface->SetSInt32Value(state, spawn->GetAssignedAA()); + else if ( type == "unassigned_aa") + lua_interface->SetSInt32Value(state, spawn->GetUnassignedAA()); + else if ( type == "assigned_tradeskill_aa") + lua_interface->SetSInt32Value(state, spawn->GetTradeskillAA()); + else if ( type == "unassigned_tradeskill_aa") + lua_interface->SetSInt32Value(state, spawn->GetUnassignedTradeskillAA()); + else if ( type == "assigned_prestige_aa") + lua_interface->SetSInt32Value(state, spawn->GetPrestigeAA()); + else if ( type == "unassigned_prestige_aa") + lua_interface->SetSInt32Value(state, spawn->GetUnassignedPretigeAA()); + else if ( type == "assigned_tradeskill_prestige_aa") + lua_interface->SetSInt32Value(state, spawn->GetTradeskillPrestigeAA()); + else if ( type == "unassigned_tradeskill_prestige_aa") + lua_interface->SetSInt32Value(state, spawn->GetUnassignedTradeskillPrestigeAA()); + else + res = 0; + + return res; + } + + lua_interface->LogError("%s: LUA GetAAInfo spawn does not exist", lua_interface->GetScriptName(state)); + return 0; +} + +int EQ2Emu_lua_SetAAInfo(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + string type = lua_interface->GetStringValue(state, 2); + sint32 value = lua_interface->GetSInt32Value(state, 3); + lua_interface->ResetFunctionStack(state); + if (spawn) { + boost::to_lower(type); + if(type == "assigned_aa") + spawn->SetAssignedAA((sint16)value); + else if ( type == "unassigned_aa") + spawn->SetUnassignedAA((sint16)value); + else if ( type == "assigned_tradeskill_aa") + spawn->SetTradeskillAA((sint16)value); + else if ( type == "unassigned_tradeskill_aa") + spawn->SetUnassignedTradeskillAA((sint16)value); + else if ( type == "assigned_prestige_aa") + spawn->SetPrestigeAA((sint16)value); + else if ( type == "unassigned_prestige_aa") + spawn->SetUnassignedPrestigeAA((sint16)value); + else if ( type == "assigned_tradeskill_prestige_aa") + spawn->SetTradeskillPrestigeAA((sint16)value); + else if ( type == "unassigned_tradeskill_prestige_aa") + spawn->SetUnassignedTradeskillPrestigeAA((sint16)value); + + if(spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + return 0; +} + +int EQ2Emu_lua_AddMasterTitle(lua_State* state) { + if (!lua_interface) + return 0; + + string titleName = lua_interface->GetStringValue(state); + int8 isPrefix = lua_interface->GetInt8Value(state, 2); + + lua_interface->ResetFunctionStack(state); + sint32 index = database.AddMasterTitle(titleName.c_str(), isPrefix); + lua_interface->SetSInt32Value(state, index); + + return 1; +} + +int EQ2Emu_lua_AddCharacterTitle(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + string titleName = lua_interface->GetStringValue(state, 2); + + lua_interface->ResetFunctionStack(state); + if(!spawn->IsPlayer()) + { + lua_interface->LogError("%s: LUA AddCharacterTitle command error: player is not valid", lua_interface->GetScriptName(state)); + lua_interface->SetSInt32Value(state, -1); + return 1; + } + + Player* player = (Player*)spawn; + // check if player already has the title, don't need to add twice + Title* playerHasTitle = player->GetPlayerTitles()->GetTitleByName(titleName.c_str()); + + if ( playerHasTitle) + { + lua_interface->SetSInt32Value(state, playerHasTitle->GetID()); + return 1; + } + + Title* title = master_titles_list.GetTitleByName(titleName.c_str()); + + if(!title) + { + lua_interface->LogError("%s: LUA AddCharacterTitle command error: title is not valid with name '%s'", lua_interface->GetScriptName(state), titleName.c_str()); + lua_interface->SetSInt32Value(state, -1); + return 1; + } + + + sint32 returnIdx = database.AddCharacterTitle(title->GetID(), player->GetCharacterID(), player); + + if(returnIdx < 0) + { + lua_interface->LogError("%s: LUA AddCharacterTitle command error: got invalid index (-1) returned for database.AddCharacterTitle '%s'", lua_interface->GetScriptName(state), titleName.c_str()); + } + + lua_interface->SetSInt32Value(state, returnIdx); + + player->GetClient()->SendTitleUpdate(); + + return 1; +} + +int EQ2Emu_lua_SetCharacterTitleSuffix(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + string titleName = lua_interface->GetStringValue(state, 2); + + lua_interface->ResetFunctionStack(state); + if(!spawn->IsPlayer()) + { + lua_interface->LogError("%s: LUA SetCharacterTitleSuffix command error: player is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + Player* player = (Player*)spawn; + + Title* title = player->GetPlayerTitles()->GetTitleByName(titleName.c_str()); + + if(!title) + { + lua_interface->LogError("%s: LUA SetCharacterTitleSuffix command error: title is not valid with name '%s'", lua_interface->GetScriptName(state), titleName.c_str()); + return 0; + } + + if(title->GetPrefix()) + { + lua_interface->LogError("%s: LUA SetCharacterTitleSuffix command error: title with name '%s' is not valid as a suffix, only prefix", lua_interface->GetScriptName(state), titleName.c_str()); + return 0; + } + + database.SaveCharSuffixIndex(title->GetID(), player->GetCharacterID()); + player->GetClient()->SendTitleUpdate(); + + return 1; +} + +int EQ2Emu_lua_SetCharacterTitlePrefix(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + string titleName = lua_interface->GetStringValue(state, 2); + + lua_interface->ResetFunctionStack(state); + if(!spawn->IsPlayer()) + { + lua_interface->LogError("%s: LUA SetCharacterTitlePrefix command error: player is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + Player* player = (Player*)spawn; + + Title* title = player->GetPlayerTitles()->GetTitleByName(titleName.c_str()); + + if(!title) + { + lua_interface->LogError("%s: LUA SetCharacterTitlePrefix command error: title is not valid with name '%s'", lua_interface->GetScriptName(state), titleName.c_str()); + return 0; + } + + if(!title->GetPrefix()) + { + lua_interface->LogError("%s: LUA SetCharacterTitlePrefix command error: title with name '%s' is not valid as a prefix, only suffix", lua_interface->GetScriptName(state), titleName.c_str()); + return 0; + } + + database.SaveCharPrefixIndex(title->GetID(), player->GetCharacterID()); + player->GetClient()->SendTitleUpdate(); + + return 1; +} + +int EQ2Emu_lua_ResetCharacterTitleSuffix(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + + lua_interface->ResetFunctionStack(state); + if(!spawn->IsPlayer()) + { + lua_interface->LogError("%s: LUA ResetCharacterTitleSuffix command error: player is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + Player* player = (Player*)spawn; + + + database.SaveCharSuffixIndex(-1, player->GetCharacterID()); + player->GetClient()->SendTitleUpdate(); + + return 1; +} + +int EQ2Emu_lua_ResetCharacterTitlePrefix(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + + lua_interface->ResetFunctionStack(state); + if(!spawn->IsPlayer()) + { + lua_interface->LogError("%s: LUA ResetCharacterTitlePrefix command error: player is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + Player* player = (Player*)spawn; + + + database.SaveCharPrefixIndex(-1, player->GetCharacterID()); + player->GetClient()->SendTitleUpdate(); + + return 1; +} + +int EQ2Emu_lua_GetInfoStructString(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + string field = lua_interface->GetStringValue(state, 2); + + lua_interface->ResetFunctionStack(state); + if(!spawn || !spawn->IsEntity()) + { + lua_interface->LogError("%s: LUA GetInfoStructString command error: spawn is not valid, either does not exist or is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + Entity* ent = (Entity*)spawn; + lua_interface->SetStringValue(state, ent->GetInfoStructString(field).c_str()); + + return 1; +} + +int EQ2Emu_lua_GetInfoStructUInt(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + string field = lua_interface->GetStringValue(state, 2); + + lua_interface->ResetFunctionStack(state); + if(!spawn || !spawn->IsEntity()) + { + lua_interface->LogError("%s: LUA GetInfoStructUInt command error: spawn is not valid, either does not exist or is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + Entity* ent = (Entity*)spawn; + lua_interface->SetInt64Value(state, ent->GetInfoStructUInt(field)); + + return 1; +} + +int EQ2Emu_lua_GetInfoStructSInt(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + string field = lua_interface->GetStringValue(state, 2); + + lua_interface->ResetFunctionStack(state); + if(!spawn || !spawn->IsEntity()) + { + lua_interface->LogError("%s: LUA GetInfoStructSInt command error: spawn is not valid, either does not exist or is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + Entity* ent = (Entity*)spawn; + lua_interface->SetSInt64Value(state, ent->GetInfoStructSInt(field)); + + return 1; +} + +int EQ2Emu_lua_GetInfoStructFloat(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + string field = lua_interface->GetStringValue(state, 2); + + lua_interface->ResetFunctionStack(state); + if(!spawn || !spawn->IsEntity()) + { + lua_interface->LogError("%s: LUA GetInfoStructFloat command error: spawn is not valid, either does not exist or is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + Entity* ent = (Entity*)spawn; + lua_interface->SetFloatValue(state, ent->GetInfoStructFloat(field)); + + return 1; +} + +int EQ2Emu_lua_SetInfoStructString(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + string field = lua_interface->GetStringValue(state, 2); + string value = lua_interface->GetStringValue(state, 3); + + lua_interface->ResetFunctionStack(state); + if(!spawn || !spawn->IsEntity()) + { + lua_interface->LogError("%s: LUA SetInfoStructString command error: spawn is not valid, either does not exist or is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + Entity* ent = (Entity*)spawn; + bool set_ = ent->SetInfoStructString(field, value); + lua_interface->SetBooleanValue(state, set_); + return 1; +} + +int EQ2Emu_lua_SetInfoStructUInt(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + string field = lua_interface->GetStringValue(state, 2); + int64 value = lua_interface->GetInt64Value(state, 3); + + lua_interface->ResetFunctionStack(state); + if(!spawn || !spawn->IsEntity()) + { + lua_interface->LogError("%s: LUA SetInfoStructUInt command error: spawn is not valid, either does not exist or is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + Entity* ent = (Entity*)spawn; + bool set_ = ent->SetInfoStructUInt(field, value); + lua_interface->SetBooleanValue(state, set_); + return 1; +} + +int EQ2Emu_lua_SetInfoStructSInt(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + string field = lua_interface->GetStringValue(state, 2); + sint64 value = lua_interface->GetSInt64Value(state, 3); + + lua_interface->ResetFunctionStack(state); + if(!spawn || !spawn->IsEntity()) + { + lua_interface->LogError("%s: LUA SetInfoStructSInt command error: spawn is not valid, either does not exist or is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + Entity* ent = (Entity*)spawn; + bool set_ = ent->SetInfoStructSInt(field, value); + lua_interface->SetBooleanValue(state, set_); + return 1; +} + +int EQ2Emu_lua_SetInfoStructFloat(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + string field = lua_interface->GetStringValue(state, 2); + float value = lua_interface->GetFloatValue(state, 3); + + lua_interface->ResetFunctionStack(state); + if(!spawn || !spawn->IsEntity()) + { + lua_interface->LogError("%s: LUA SetInfoStructFloat command error: spawn is not valid, either does not exist or is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + Entity* ent = (Entity*)spawn; + bool set_ = ent->SetInfoStructFloat(field, value); + lua_interface->SetBooleanValue(state, set_); + return 1; +} + +int EQ2Emu_lua_SetCharSheetChanged(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + bool value = lua_interface->GetBooleanValue(state, 2); + lua_interface->ResetFunctionStack(state); + + if(!spawn || !spawn->IsPlayer()) + { + lua_interface->LogError("%s: LUA SetCharSheetChanged command error: spawn is not valid, either does not exist or is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + ((Player*)spawn)->SetCharSheetChanged(value); + + return 0; +} + +int EQ2Emu_lua_AddPlayerMail(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + std::string fromName = lua_interface->GetStringValue(state, 2); + std::string subjectName = lua_interface->GetStringValue(state, 3); + std::string mailBody = lua_interface->GetStringValue(state, 4); + int8 mailType = lua_interface->GetInt8Value(state, 5); + + int32 copper = lua_interface->GetInt32Value(state, 6); + int32 silver = lua_interface->GetInt32Value(state, 7); + int32 gold = lua_interface->GetInt32Value(state, 8); + int32 platinum = lua_interface->GetInt32Value(state, 9); + + int32 item_id = lua_interface->GetInt32Value(state, 10); + + int16 stack_size = lua_interface->GetInt32Value(state, 11); + + int32 expire_time = lua_interface->GetInt32Value(state, 12); + + int32 sent_time = lua_interface->GetInt32Value(state, 13); + + lua_interface->ResetFunctionStack(state); + + if(!spawn || !spawn->IsPlayer()) + { + lua_interface->LogError("%s: LUA AddPlayerMail command error: spawn is not valid, either does not exist or is not a player", lua_interface->GetScriptName(state)); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + int32 time_sent = sent_time > 0 ? sent_time : Timer::GetUnixTimeStamp(); + + ((Player*)spawn)->GetClient()->CreateAndUpdateMail(fromName, subjectName, mailBody, mailType, copper, silver, gold, platinum, item_id, stack_size, time_sent, expire_time); + lua_interface->SetBooleanValue(state, true); + return 1; +} + + +int EQ2Emu_lua_AddPlayerMailByCharID(lua_State* state) { + if (!lua_interface) + return 0; + int32 char_id = lua_interface->GetInt32Value(state); + std::string fromName = lua_interface->GetStringValue(state, 2); + std::string subjectName = lua_interface->GetStringValue(state, 3); + std::string mailBody = lua_interface->GetStringValue(state, 4); + int8 mailType = lua_interface->GetInt8Value(state, 5); + + int32 copper = lua_interface->GetInt32Value(state, 6); + int32 silver = lua_interface->GetInt32Value(state, 7); + int32 gold = lua_interface->GetInt32Value(state, 8); + int32 platinum = lua_interface->GetInt32Value(state, 9); + + int32 item_id = lua_interface->GetInt32Value(state, 10); + + int16 stack_size = lua_interface->GetInt32Value(state, 11); + + int32 expire_time = lua_interface->GetInt32Value(state, 12); + + int32 sent_time = lua_interface->GetInt32Value(state, 13); + + lua_interface->ResetFunctionStack(state); + + int32 time_sent = sent_time > 0 ? sent_time : Timer::GetUnixTimeStamp(); + + Client::CreateMail(char_id, fromName, subjectName, mailBody, mailType, copper, silver, gold, platinum, item_id, stack_size, time_sent, expire_time); + lua_interface->SetBooleanValue(state, true); + return 1; +} + +int EQ2Emu_lua_OpenDoor(lua_State* state) { + Spawn* widget; + + if (lua_interface) { + widget = lua_interface->GetSpawn(state); + bool disable_open_sound = lua_interface->GetBooleanValue(state, 2); + + lua_interface->ResetFunctionStack(state); + + if (widget && widget->IsWidget()) + { + ((Widget*)widget)->OpenDoor(); + + if(!disable_open_sound && ((Widget*)widget)->IsOpen() && ((Widget*)widget)->GetOpenSound()) + widget->GetZone()->PlaySoundFile(0, ((Widget*)widget)->GetOpenSound(), ((Widget*)widget)->GetX(), ((Widget*)widget)->GetY(), ((Widget*)widget)->GetZ()); + } + else + lua_interface->LogError("%s: LUA OpenDoor command error: spawn is not valid, either does not exist or is not a widget", lua_interface->GetScriptName(state)); + } + + return 0; +} + +int EQ2Emu_lua_CloseDoor(lua_State* state) { + Spawn* widget; + + if (lua_interface) { + widget = lua_interface->GetSpawn(state); + bool disable_close_sound = lua_interface->GetBooleanValue(state, 2); + + lua_interface->ResetFunctionStack(state); + + if (widget && widget->IsWidget()) + { + ((Widget*)widget)->CloseDoor(); + + if(!disable_close_sound && !((Widget*)widget)->IsOpen() && ((Widget*)widget)->GetCloseSound()) + widget->GetZone()->PlaySoundFile(0, ((Widget*)widget)->GetCloseSound(), ((Widget*)widget)->GetX(), ((Widget*)widget)->GetY(), ((Widget*)widget)->GetZ()); + } + else + lua_interface->LogError("%s: LUA CloseDoor command error: spawn is not valid, either does not exist or is not a widget", lua_interface->GetScriptName(state)); + } + + return 0; +} + +int EQ2Emu_lua_IsOpen(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* widget = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (widget && widget->IsWidget()) + { + lua_interface->SetBooleanValue(state, ((Widget*)widget)->IsOpen()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_MakeRandomInt(lua_State* state) { + if (!lua_interface) + return 0; + + sint32 min = lua_interface->GetSInt32Value(state); + sint32 max = lua_interface->GetSInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + sint32 result = MakeRandomInt(min, max); + lua_interface->SetSInt32Value(state, result); + return 1; +} + +int EQ2Emu_lua_MakeRandomFloat(lua_State* state) { + if (!lua_interface) + return 0; + + float min = lua_interface->GetFloatValue(state); + float max = lua_interface->GetFloatValue(state, 2); + lua_interface->ResetFunctionStack(state); + + float result = MakeRandomFloat(min, max); + lua_interface->SetFloatValue(state, result); + return 1; +} + +int EQ2Emu_lua_AddIconValue(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + int32 value = lua_interface->GetInt32Value(state, 2); + + lua_interface->ResetFunctionStack(state); + + if(!spawn) + { + lua_interface->LogError("%s: LUA AddIconValue command error: spawn is not valid, does not exist", lua_interface->GetScriptName(state)); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + spawn->AddIconValue(value); + lua_interface->SetBooleanValue(state, true); + + return 1; +} + +int EQ2Emu_lua_RemoveIconValue(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + int32 value = lua_interface->GetInt32Value(state, 2); + + lua_interface->ResetFunctionStack(state); + + if(!spawn) + { + lua_interface->LogError("%s: LUA RemoveIconValue command error: spawn is not valid, does not exist", lua_interface->GetScriptName(state)); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + spawn->RemoveIconValue(value); + lua_interface->SetBooleanValue(state, true); + + return 1; +} + +int EQ2Emu_lua_GetShardID(lua_State* state) { + Spawn* npc = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (npc && npc->IsNPC()) { + NPC* shard = (NPC*)npc; + int32 shardid = shard->GetShardID(); + lua_interface->SetInt32Value(state, shardid); + return 1; + } + lua_interface->SetInt32Value(state, 0); + return 1; +} + +int EQ2Emu_lua_GetShardCharID(lua_State* state) { + Spawn* npc = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (npc && npc->IsNPC()) { + NPC* shard = (NPC*)npc; + int32 charid = shard->GetShardCharID(); + lua_interface->SetInt32Value(state, charid); + return 1; + } + lua_interface->SetInt32Value(state, 0); + return 1; +} + +int EQ2Emu_lua_GetShardCreatedTimestamp(lua_State* state) { + Spawn* npc = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (npc && npc->IsNPC()) { + NPC* shard = (NPC*)npc; + int64 timestamp = shard->GetShardCreatedTimestamp(); + lua_interface->SetSInt64Value(state, timestamp); + return 1; + } + lua_interface->SetSInt64Value(state, 0); + return 1; +} + +int EQ2Emu_lua_DeleteDBShardID(lua_State* state) { + if (!lua_interface) + return 0; + int32 shardid = lua_interface->GetInt32Value(state); + + lua_interface->ResetFunctionStack(state); + + if(shardid < 1) + lua_interface->SetBooleanValue(state, false); + else + lua_interface->SetBooleanValue(state, database.DeleteSpiritShard(shardid)); + return 1; +} + +int EQ2Emu_lua_PauseMovement(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 delay_in_ms = lua_interface->GetInt32Value(state, 2); + if (spawn) { + spawn->PauseMovement(delay_in_ms); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_StopMovement(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + spawn->ResetMovement(); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_GetArrowColor(lua_State* state) { + Player* player = (Player*)lua_interface->GetSpawn(state); + int8 level = lua_interface->GetInt8Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (player && player->IsPlayer() && level > 0) { + lua_interface->SetInt32Value(state, player->GetArrowColor(level)); + return 1; + } + return 0; +} +int EQ2Emu_lua_GetTSArrowColor(lua_State* state) { + Player* player = (Player*)lua_interface->GetSpawn(state); + int8 level = lua_interface->GetInt8Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (player && player->IsPlayer() && level > 0) { + lua_interface->SetInt32Value(state, player->GetTSArrowColor(level)); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetSpawnByRailID(lua_State* state) { + ZoneServer* zone = lua_interface->GetZone(state); + sint64 rail_id = lua_interface->GetSInt64Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (zone) { + Spawn* spawn = zone->GetTransportByRailID(rail_id); + if (spawn) { + lua_interface->SetSpawnValue(state, spawn); + return 1; + } + } + return 0; +} + +int EQ2Emu_lua_SetRailID(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + sint64 rail_id = lua_interface->GetSInt64Value(state, 2); + + lua_interface->ResetFunctionStack(state); + + bool res = false; + if(spawn && spawn->IsTransportSpawn()) + { + //printf("Set rail id %i for %s\n",rail_id,spawn->GetName()); + spawn->SetRailID(rail_id); + res = true; + } + else if (!spawn) { + lua_interface->LogError("%s: LUA SetRailID command error: spawn is not valid, does not exist", lua_interface->GetScriptName(state)); + } + else if(!spawn->IsTransportSpawn()) { + lua_interface->LogError("%s: LUA SetRailID command error: spawn %s is not a transport spawn, call AddTransportSpawn(NPC) first", lua_interface->GetScriptName(state), spawn->GetName()); + } + lua_interface->SetBooleanValue(state, res); + return 1; +} + +int EQ2Emu_lua_IsZoneLoading(lua_State* state) { + if (!lua_interface) + return 0; + + ZoneServer* zone = lua_interface->GetZone(state); + lua_interface->ResetFunctionStack(state); + + if (zone) { + lua_interface->SetBooleanValue(state, zone->IsLoading()); + return 1; + } + return 0; +} +int EQ2Emu_lua_IsRunning(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (spawn) { + lua_interface->SetBooleanValue(state, spawn->IsRunning()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetZoneLockoutTimer(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + int32 zoneID = lua_interface->GetInt32Value(state, 2); + bool displayClient = lua_interface->GetInt32Value(state, 3); + lua_interface->ResetFunctionStack(state); + + if (!player || !player->IsPlayer() || !player->GetClient()) { + lua_interface->LogError("%s: LUA GetZoneLockoutTimer command error: player is not valid, does not exist", lua_interface->GetScriptName(state)); + } + else if(!zoneID) { + lua_interface->LogError("%s: LUA GetZoneLockoutTimer command error: zoneID is not set."); + } + else + { + lua_interface->SetStringValue(state, player->GetClient()->IdentifyInstanceLockout(zoneID, displayClient).c_str()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_SetWorldTime(lua_State* state) { + if (!lua_interface) + return 0; + + int16 newYear = lua_interface->GetInt16Value(state, 1); + sint32 newMonth = lua_interface->GetInt16Value(state, 2); + int16 newHour = lua_interface->GetInt16Value(state, 3); + int16 newMinute = lua_interface->GetInt16Value(state, 4); + + lua_interface->ResetFunctionStack(state); + + world.MWorldTime.writelock(__FUNCTION__, __LINE__); + world.GetWorldTimeStruct()->year = newYear; + world.GetWorldTimeStruct()->month = newMonth; + world.GetWorldTimeStruct()->hour = newHour; + world.GetWorldTimeStruct()->minute = newMinute; + world.MWorldTime.releasewritelock(__FUNCTION__, __LINE__); + + return 0; +} + +int EQ2Emu_lua_GetWorldTimeYear(lua_State* state) { + if (!lua_interface) + return 0; + + lua_interface->ResetFunctionStack(state); + + world.MWorldTime.readlock(__FUNCTION__, __LINE__); + lua_interface->SetInt32Value(state, world.GetWorldTimeStruct()->year); + world.MWorldTime.releasereadlock(__FUNCTION__, __LINE__); + + return 1; +} + +int EQ2Emu_lua_GetWorldTimeMonth(lua_State* state) { + if (!lua_interface) + return 0; + + lua_interface->ResetFunctionStack(state); + + world.MWorldTime.readlock(__FUNCTION__, __LINE__); + lua_interface->SetSInt32Value(state, world.GetWorldTimeStruct()->month); + world.MWorldTime.releasereadlock(__FUNCTION__, __LINE__); + + return 1; +} + +int EQ2Emu_lua_GetWorldTimeHour(lua_State* state) { + if (!lua_interface) + return 0; + + lua_interface->ResetFunctionStack(state); + + world.MWorldTime.readlock(__FUNCTION__, __LINE__); + lua_interface->SetSInt32Value(state, world.GetWorldTimeStruct()->hour); + world.MWorldTime.releasereadlock(__FUNCTION__, __LINE__); + + return 1; +} + +int EQ2Emu_lua_GetWorldTimeMinute(lua_State* state) { + if (!lua_interface) + return 0; + + lua_interface->ResetFunctionStack(state); + + world.MWorldTime.readlock(__FUNCTION__, __LINE__); + lua_interface->SetSInt32Value(state, world.GetWorldTimeStruct()->minute); + world.MWorldTime.releasereadlock(__FUNCTION__, __LINE__); + + return 1; +} + +int EQ2Emu_lua_SendTimeUpdate(lua_State* state) { + if (!lua_interface) + return 0; + + lua_interface->ResetFunctionStack(state); + + world.SendTimeUpdate(); + + return 0; +} + +int EQ2Emu_lua_ChangeFaction(lua_State* state) { + bool update_result = false; + Faction* faction = 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + int32 faction_id = lua_interface->GetInt32Value(state, 2); + sint32 increase = lua_interface->GetInt32Value(state, 3); + lua_interface->ResetFunctionStack(state); + + if(!spawn || !spawn->IsPlayer()) { + lua_interface->LogError("LUA ChangeFaction command error: player is not valid"); + return 0; + } + + Player* player = (Player*)spawn; + Client* client = player->GetClient(); + if (faction_id > 0) { + + bool hasfaction = database.VerifyFactionID(player->GetCharacterID(),faction_id); + if(hasfaction == 0) { + //they do not have the faction. Lets get the default value and feed it in. + sint32 defaultfaction = master_faction_list.GetDefaultFactionValue(faction_id); + //add the default faction for the player. + player->SetFactionValue(faction_id, defaultfaction); + } + + if(increase >= 0) { + update_result = player->GetFactions()->IncreaseFaction(faction_id,increase); + faction = master_faction_list.GetFaction(faction_id); + + if(faction && update_result) + client->Message(CHANNEL_FACTION, "Your faction standing with %s got better.", faction->name.c_str()); + else if(faction) + client->Message(CHANNEL_FACTION, "Your faction standing with %s could not possibly get any better.", faction->name.c_str()); + + lua_interface->SetBooleanValue(state, true); + return 1; + + } + + if(increase < 0){ + //change the negative to a positive, since thats how decreasefaction() likes it. + increase *= -1; + + update_result = player->GetFactions()->DecreaseFaction(faction_id,increase); + faction = master_faction_list.GetFaction(faction_id); + if(faction && update_result) + client->Message(CHANNEL_FACTION, "Your faction standing with %s got worse.", faction->name.c_str()); + else if(faction) + client->Message(CHANNEL_FACTION, "Your faction standing with %s could not possibly get any worse.", faction->name.c_str()); + + lua_interface->SetBooleanValue(state, true); + return 1; + } + } + lua_interface->SetBooleanValue(state, false); + return 1; +} + +int EQ2Emu_lua_HasCoin(lua_State* state) { + bool hascoin = 0; + Player* player = (Player*)lua_interface->GetSpawn(state); + int32 coins = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + +if (player && player->IsPlayer()) { + hascoin = player->HasCoins(coins); + if(hascoin == 0) { + lua_interface->SetBooleanValue(state, false); + return 1; + } + if(hascoin == 1) { + lua_interface->SetBooleanValue(state, true); + return 1; + } + +} + return 0; +} + +int EQ2Emu_lua_GetLootTier(lua_State* state) { + int32 loot_tier = 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (spawn) { + loot_tier = spawn->GetLootTier(); + lua_interface->SetInt32Value(state, loot_tier); + return 1; + } + return 0; +} + +int EQ2Emu_lua_SetLootTier(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 loot_tier = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (spawn) { + spawn->SetLootTier(loot_tier); + lua_interface->SetBooleanValue(state, true); + return 1; + } + + lua_interface->SetBooleanValue(state, false); + return 1; +} + +int EQ2Emu_lua_GetLootDropType(lua_State* state) { + int32 loot_drop_type = 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (spawn) { + loot_drop_type = spawn->GetLootDropType(); + lua_interface->SetInt32Value(state, loot_drop_type); + return 1; + } + return 0; +} + +int EQ2Emu_lua_SetLootDropType(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 loot_drop_type = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (spawn) { + spawn->SetLootDropType(loot_drop_type); + lua_interface->SetBooleanValue(state, true); + return 1; + } + + lua_interface->SetBooleanValue(state, false); + return 1; +} + + +int EQ2Emu_lua_DamageEquippedItems(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + int8 damage_amount = lua_interface->GetInt32Value(state, 2); + if(damage_amount > 100) { + damage_amount = 100; + } + + if (!spawn) { + lua_interface->LogError("%s: LUA DamageEquippedItems command error: spawn is not valid", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + + lua_interface->ResetFunctionStack(state); + if (spawn->IsPlayer()) { + if (((Player*)spawn)->GetClient() && ((Player*)spawn)->DamageEquippedItems(damage_amount, ((Player*)spawn)->GetClient())) + lua_interface->SetBooleanValue(state, true); + else + lua_interface->SetBooleanValue(state, false); + } + else { + lua_interface->SetBooleanValue(state, false); + } + + return 1; +} + +int EQ2Emu_lua_CreateWidgetRegion(lua_State* state) { + ZoneServer* zone = lua_interface->GetZone(state); + int32 version = lua_interface->GetInt32Value(state, 2); + + RegionMap* region_map = world.GetRegionMap(std::string(zone->GetZoneFile()), version); + if(region_map == nullptr) { + lua_interface->LogError("%s: LUA CreateWidgetRegion command error: region map is not valid for version %u", lua_interface->GetScriptName(state), version); + lua_interface->ResetFunctionStack(state); + return 0; + } + string region_name = lua_interface->GetStringValue(state, 3); + string env_name = lua_interface->GetStringValue(state, 4); + int32 grid_id = lua_interface->GetInt32Value(state, 5); + int32 widget_id = lua_interface->GetInt32Value(state, 6); + float dist = lua_interface->GetFloatValue(state, 7); + region_map->InsertRegionNode(zone, version, region_name, env_name, grid_id, widget_id, dist); + + lua_interface->ResetFunctionStack(state); + + lua_interface->SetBooleanValue(state, true); + return 1; +} + +int EQ2Emu_lua_RemoveRegion(lua_State* state) { + ZoneServer* zone = lua_interface->GetZone(state); + int32 version = lua_interface->GetInt32Value(state, 2); + + RegionMap* region_map = world.GetRegionMap(std::string(zone->GetZoneFile()), version); + if(region_map == nullptr) { + lua_interface->LogError("%s: LUA RemoveRegion command error: region map is not valid for version %u", lua_interface->GetScriptName(state), version); + lua_interface->ResetFunctionStack(state); + return 0; + } + string region_name = lua_interface->GetStringValue(state, 3); + region_map->RemoveRegionNode(region_name); + + lua_interface->ResetFunctionStack(state); + + lua_interface->SetBooleanValue(state, true); + return 1; +} + + +int EQ2Emu_lua_SetPlayerPOVGhost(lua_State* state) { + Client* client = nullptr; + Spawn* player = lua_interface->GetSpawn(state); + if (!player) { + lua_interface->LogError("LUA SetPlayerPOVGhost command error: spawn is not valid"); + lua_interface->SetBooleanValue(state, false); + lua_interface->ResetFunctionStack(state); + return 1; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("LUA SetPlayerPOVGhost command error: spawn is not a player"); + lua_interface->SetBooleanValue(state, false); + lua_interface->ResetFunctionStack(state); + return 1; + } + + client = player->GetClient(); + + if (!client) { + lua_interface->LogError("LUA SetPlayerPOVGhost command error: could not find client"); + lua_interface->SetBooleanValue(state, false); + lua_interface->ResetFunctionStack(state); + return 1; + } + + Spawn* spawn = lua_interface->GetSpawn(state, 2); + + bool success_sight = client->SetPlayerPOVGhost(spawn); + + lua_interface->ResetFunctionStack(state); + + lua_interface->SetBooleanValue(state, success_sight); + return 1; +} + + +int EQ2Emu_lua_SetCastOnAggroComplete(lua_State* state) { + if (!lua_interface) + return 0; + bool result = false; + + Spawn* spawn = lua_interface->GetSpawn(state); + bool cast_completed = (lua_interface->GetInt8Value(state, 2) == 1); + lua_interface->ResetFunctionStack(state); + + if (!spawn) + lua_interface->LogError("%s: LUA SetCastOnAggroComplete error: Could not find spawn.", lua_interface->GetScriptName(state)); + else if (!spawn->IsNPC()) + lua_interface->LogError("%s: LUA SetCastOnAggroComplete error: spawn %s is not an NPC!.", lua_interface->GetScriptName(state), spawn->GetName()); + else + { + ((NPC*)spawn)->cast_on_aggro_completed = cast_completed; + result = true; + } + + lua_interface->SetBooleanValue(state, result); + + return 1; +} + +int EQ2Emu_lua_IsCastOnAggroComplete(lua_State* state) { + if (!lua_interface) + return 0; + bool result = false; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (!spawn) + lua_interface->LogError("%s: LUA IsCastOnAggroComplete error: Could not find spawn.", lua_interface->GetScriptName(state)); + else if (!spawn->IsNPC()) + lua_interface->LogError("%s: LUA IsCastOnAggroComplete error: spawn %s is not an NPC!.", lua_interface->GetScriptName(state), spawn->GetName()); + else + { + if(((NPC*)spawn)->cast_on_aggro_completed) + result = true; + } + + lua_interface->SetBooleanValue(state, result); + + return 1; +} + +int EQ2Emu_lua_AddRecipeBookToPlayer(lua_State* state) { + Client* client = nullptr; + Spawn* player = lua_interface->GetSpawn(state); + int32 recipe_book_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (!player) { + lua_interface->LogError("LUA AddRecipeBookToPlayer command error: spawn is not valid"); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("LUA AddRecipeBookToPlayer command error: spawn is not a player"); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + client = player->GetClient(); + + if (!client) { + lua_interface->LogError("LUA AddRecipeBookToPlayer command error: could not find client"); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + bool result = client->AddRecipeBookToPlayer(recipe_book_id); + + lua_interface->SetBooleanValue(state, result); + return 1; +} + +int EQ2Emu_lua_RemoveRecipeFromPlayer(lua_State* state) { + Client* client = nullptr; + Spawn* player = lua_interface->GetSpawn(state); + int32 recipe_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (!player) { + lua_interface->LogError("LUA RemoveRecipeFromPlayer command error: spawn is not valid"); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("LUA RemoveRecipeFromPlayer command error: spawn is not a player"); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + client = player->GetClient(); + + if (!client) { + lua_interface->LogError("LUA RemoveRecipeFromPlayer command error: could not find client"); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + bool result = client->RemoveRecipeFromPlayer(recipe_id); + + lua_interface->SetBooleanValue(state, result); + return 1; +} + +int EQ2Emu_lua_ReplaceWidgetFromClient(lua_State* state) { + Client* client = nullptr; + Spawn* player = lua_interface->GetSpawn(state); + int32 widget_id = lua_interface->GetInt32Value(state, 2); + bool delete_widget = (lua_interface->GetInt8Value(state, 3) == 1); + + // rest are all optional fields + float x = lua_interface->GetFloatValue(state, 4); + float y = lua_interface->GetFloatValue(state, 5); + float z = lua_interface->GetFloatValue(state, 6); + int32 grid_id = lua_interface->GetInt32Value(state, 7); + + lua_interface->ResetFunctionStack(state); + if (!player) { + lua_interface->LogError("LUA ReplaceWidgetFromClient command error: spawn is not valid"); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("LUA ReplaceWidgetFromClient command error: spawn is not a player"); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + if(!player->GetZone()) { + lua_interface->LogError("LUA ReplaceWidgetFromClient command error: player is not in a zone"); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + client = player->GetClient(); + + if (!client) { + lua_interface->LogError("LUA ReplaceWidgetFromClient command error: could not find client"); + lua_interface->SetBooleanValue(state, false); + return 1; + } + if(!client->IsReadyForUpdates()) { + lua_interface->LogError("LUA ReplaceWidgetFromClient command failed: client has not signaled sys_client_avatar_ready"); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + client->SendReplaceWidget(widget_id, delete_widget, x, y, z, grid_id); + + lua_interface->SetBooleanValue(state, true); + return 1; +} + +int EQ2Emu_lua_RemoveWidgetFromSpawnMap(lua_State* state) { + Client* client = nullptr; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 widget_id = lua_interface->GetInt32Value(state, 2); + + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("LUA RemoveWidgetFromSpawnMap command error: spawn is not valid"); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + if(!spawn->GetZone()) { + lua_interface->LogError("LUA ReplaceWidgetFromClient command error: player is not in a zone"); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + spawn->AddIgnoredWidget(widget_id); + + lua_interface->SetBooleanValue(state, true); + return 1; +} + +int EQ2Emu_lua_RemoveWidgetFromZoneMap(lua_State* state) { + ZoneServer* zone = lua_interface->GetZone(state); + int32 widget_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if(!zone) { + lua_interface->LogError("LUA RemoveWidgetFromZoneMap command error: zone is not valid"); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + if(!zone->IsLoading()) { + lua_interface->LogError("LUA RemoveWidgetFromZoneMap command error: can only be called during zone loading, in preinit_zone_script ZoneScript function"); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + zone->AddIgnoredWidget(widget_id); + lua_interface->SetBooleanValue(state, true); + return 1; +} + +int EQ2Emu_lua_SendHearCast(lua_State* state) { + if (!lua_interface) + return 0; + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + Spawn* spawn = lua_interface->GetSpawn(state); + int32 spell_visual_id = lua_interface->GetInt32Value(state, 2); + int16 cast_time = lua_interface->GetInt16Value(state, 3); + Spawn* caster = lua_interface->GetSpawn(state, 4); + Spawn* target = lua_interface->GetSpawn(state, 5); + lua_interface->ResetFunctionStack(state); + if(spell && spawn && spawn->IsEntity()) { + ZoneServer* zone = spawn->GetZone(); + if(zone) { + zone->SendCastSpellPacket(spell, caster && caster->IsEntity() ? (Entity*)caster : (Entity*)spawn, spell_visual_id, cast_time > 0 ? cast_time : 0xFFFF); + } + } + else if (spawn) { + if (spawn->IsPlayer()) { + Client* client = ((Player*)spawn)->GetClient(); + if (client) { + client->SendHearCast(caster ? caster : client->GetPlayer(), target ? target : client->GetPlayer(), spell_visual_id, cast_time); + } + } + } + return 0; +} + +int EQ2Emu_lua_GetCharacterFlag(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + sint32 flag_id = lua_interface->GetSInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!player) { + lua_interface->LogError("%s: LUA HasRecipeBook command error, Spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA HasRecipeBook command error, Spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + bool ret = ((Player*)player)->get_character_flag(flag_id); + lua_interface->SetBooleanValue(state, ret); + return 1; +} + +int EQ2Emu_lua_ToggleCharacterFlag(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + sint32 flag_id = lua_interface->GetSInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!player) { + lua_interface->LogError("%s: LUA HasRecipeBook command error, Spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA HasRecipeBook command error, Spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + ((Player*)player)->toggle_character_flag(flag_id); + return 0; +} + +int EQ2Emu_lua_GetSpellInitialTarget(lua_State* state) { + LuaSpell* spell = lua_interface->GetSpell(state); + + if(!spell) { + spell = lua_interface->GetCurrentSpell(state); + } + + lua_interface->ResetFunctionStack(state); + if (spell) { + if(!spell->caster) { + lua_interface->LogError("%s: LUA GetSpellTarget command error, caster does not exist.", lua_interface->GetScriptName(state)); + return 0; + } + if(!spell->caster->GetZone()) { + lua_interface->LogError("%s: LUA GetSpellTarget command error, zone does not exist.", lua_interface->GetScriptName(state)); + return 0; + } + Spawn* spawn = spell->caster->GetZone()->GetSpawnByID(spell->initial_target); + if (spawn) { + lua_interface->SetSpawnValue(state, spawn); + return 1; + } + else { + lua_interface->LogError("%s: LUA GetSpellTarget command error, could not find initial target %u to map to spawn.", lua_interface->GetScriptName(state), spell->initial_target); + } + } + return 0; +} \ No newline at end of file diff --git a/source/WorldServer/LuaFunctions.h b/source/WorldServer/LuaFunctions.h new file mode 100644 index 0000000..b538954 --- /dev/null +++ b/source/WorldServer/LuaFunctions.h @@ -0,0 +1,660 @@ +/* + 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 . +*/ +#ifndef LUA_FUNCTIONS_H +#define LUA_FUNCTIONS_H + +#include "../LUA/lua.hpp" +#include +#include +#include +using namespace std; + +vector ParseString(string strVal, char delim=','); +vector ParseStringToInt32(string strVal, char delim=','); +map ParseStringMap(string strVal, char delim=','); +map ParseIntMap(string strVal, char delim = ','); +map ParseSInt32Map(string strVal, char delim = ','); + + +//Sets +int EQ2Emu_lua_SetCurrentHP(lua_State* state); +int EQ2Emu_lua_SetMaxHP(lua_State* state); +int EQ2Emu_lua_SetMaxHPBase(lua_State* state); +int EQ2Emu_lua_SetCurrentPower(lua_State* state); +int EQ2Emu_lua_SetMaxPower(lua_State* state); +int EQ2Emu_lua_SetMaxPowerBase(lua_State* state); +int EQ2Emu_lua_ModifyMaxHP(lua_State* state); +int EQ2Emu_lua_ModifyMaxPower(lua_State* state); +int EQ2Emu_lua_SetHeading(lua_State* state); +int EQ2Emu_lua_SetModelType(lua_State* state); +int EQ2Emu_lua_SetAdventureClass(lua_State* state); +int EQ2Emu_lua_SetTradeskillClass(lua_State* state); +int EQ2Emu_lua_SetMount(lua_State* state); +int EQ2Emu_lua_SetMountColor(lua_State* state); +int EQ2Emu_lua_SetSpeed(lua_State* state); +int EQ2Emu_lua_SetPosition(lua_State* state); +int EQ2Emu_lua_AddSpellBonus(lua_State* state); +int EQ2Emu_lua_RemoveSpellBonus(lua_State* state); +int EQ2Emu_lua_AddSkillBonus(lua_State* state); +int EQ2Emu_lua_RemoveSkillBonus(lua_State* state); +int EQ2Emu_lua_AddControlEffect(lua_State* state); +int EQ2Emu_lua_RemoveControlEffect(lua_State* state); +int EQ2Emu_lua_HasControlEffect(lua_State* state); + +int EQ2Emu_lua_GetBaseAggroRadius(lua_State* state); +int EQ2Emu_lua_GetAggroRadius(lua_State* state); +int EQ2Emu_lua_SetAggroRadius(lua_State* state); + +int EQ2Emu_lua_SetDeity(lua_State* state); +int EQ2Emu_lua_GetDeity(lua_State* state); + +int EQ2Emu_lua_SetInt(lua_State* state); +int EQ2Emu_lua_SetWis(lua_State* state); +int EQ2Emu_lua_SetSta(lua_State* state); +int EQ2Emu_lua_SetStr(lua_State* state); +int EQ2Emu_lua_SetAgi(lua_State* state); +int EQ2Emu_lua_SetIntBase(lua_State* state); +int EQ2Emu_lua_SetWisBase(lua_State* state); +int EQ2Emu_lua_SetStaBase(lua_State* state); +int EQ2Emu_lua_SetStrBase(lua_State* state); +int EQ2Emu_lua_SetAgiBase(lua_State* state); +int EQ2Emu_lua_SetLootCoin(lua_State* state); +int EQ2Emu_lua_HasCoin(lua_State* state); +int EQ2Emu_lua_SetQuestYellow(lua_State* state); + +//Gets +int EQ2Emu_lua_GetLevel(lua_State* state); +int EQ2Emu_lua_GetDifficulty(lua_State* state); +int EQ2Emu_lua_GetCurrentHP(lua_State* state); +int EQ2Emu_lua_GetMaxHP(lua_State* state); +int EQ2Emu_lua_GetMaxHPBase(lua_State* state); +int EQ2Emu_lua_GetCurrentPower(lua_State* state); +int EQ2Emu_lua_GetName(lua_State* state); +int EQ2Emu_lua_GetMaxPower(lua_State* state); +int EQ2Emu_lua_GetMaxPowerBase(lua_State* state); +int EQ2Emu_lua_GetDistance(lua_State* state); +int EQ2Emu_lua_GetX(lua_State* state); +int EQ2Emu_lua_GetY(lua_State* state); +int EQ2Emu_lua_GetZ(lua_State* state); +int EQ2Emu_lua_GetHeading(lua_State* state); +int EQ2Emu_lua_GetModelType(lua_State* state); +int EQ2Emu_lua_GetRace(lua_State* state); +int EQ2Emu_lua_GetRaceName(lua_State* state); +int EQ2Emu_lua_GetMount(lua_State* state); +int EQ2Emu_lua_GetClass(lua_State* state); +int EQ2Emu_lua_GetClassName(lua_State* state); +int EQ2Emu_lua_GetArchetypeName(lua_State* state); +int EQ2Emu_lua_GetSpeed(lua_State* state); +int EQ2Emu_lua_HasMoved(lua_State* state); +int EQ2Emu_lua_GetInt(lua_State* state); +int EQ2Emu_lua_GetWis(lua_State* state); +int EQ2Emu_lua_GetSta(lua_State* state); +int EQ2Emu_lua_GetStr(lua_State* state); +int EQ2Emu_lua_GetAgi(lua_State* state); +int EQ2Emu_lua_GetIntBase(lua_State* state); +int EQ2Emu_lua_GetWisBase(lua_State* state); +int EQ2Emu_lua_GetStaBase(lua_State* state); +int EQ2Emu_lua_GetStrBase(lua_State* state); +int EQ2Emu_lua_GetAgiBase(lua_State* state); +int EQ2Emu_lua_GetLootCoin(lua_State* state); +int EQ2Emu_lua_GetSpawn(lua_State* state); +int EQ2Emu_lua_GetSpawnFromList(lua_State* state); +int EQ2Emu_lua_GetSpawnListSize(lua_State* state); +int EQ2Emu_lua_CreateSpawnList(lua_State* state); +int EQ2Emu_lua_AddSpawnToSpawnList(lua_State* state); +int EQ2Emu_lua_RemoveSpawnFromSpawnList(lua_State* state); +int EQ2Emu_lua_GetSpawnListBySpawnID(lua_State* state); +int EQ2Emu_lua_GetSpawnListByRailID(lua_State* state); +int EQ2Emu_lua_GetPassengerSpawnList(lua_State* state); +int EQ2Emu_lua_GetVariableValue(lua_State* state); +int EQ2Emu_lua_GetCoinMessage(lua_State* state); +int EQ2Emu_lua_GetSpawnByGroupID(lua_State* state); +int EQ2Emu_lua_GetSpawnByLocationID(lua_State* state); +int EQ2Emu_lua_GetSpawnID(lua_State* state); +int EQ2Emu_lua_GetSpawnGroupID(lua_State* state); +int EQ2Emu_lua_SetSpawnGroupID(lua_State* state); +int EQ2Emu_lua_AddSpawnToGroup(lua_State* state); +int EQ2Emu_lua_GetSpawnLocationID(lua_State* state); +int EQ2Emu_lua_GetSpawnLocationPlacementID(lua_State* state); +int EQ2Emu_lua_GetFactionAmount(lua_State* state); +int EQ2Emu_lua_SetFactionID(lua_State* state); +int EQ2Emu_lua_GetFactionID(lua_State* state); +int EQ2Emu_lua_ChangeFaction(lua_State* state); +int EQ2Emu_lua_GetGender(lua_State* state); +int EQ2Emu_lua_GetTarget(lua_State* state); +int EQ2Emu_lua_HasFreeSlot(lua_State* state); +int EQ2Emu_lua_HasItemEquipped(lua_State* state); +int EQ2Emu_lua_GetEquippedItemBySlot(lua_State* state); +int EQ2Emu_lua_GetEquippedItemByID(lua_State* state); +int EQ2Emu_lua_SetEquippedItemByID(lua_State* state); +int EQ2Emu_lua_SetEquippedItem(lua_State* state); +int EQ2Emu_lua_UnequipSlot(lua_State* state); +int EQ2Emu_lua_SetEquipment(lua_State* state); +int EQ2Emu_lua_GetItemByID(lua_State* state); +int EQ2Emu_lua_GetItemType(lua_State* state); +int EQ2Emu_lua_GetItemEffectType(lua_State* state); +int EQ2Emu_lua_GetSpellName(lua_State* state); + +//Misc +int EQ2Emu_lua_SetAttackable(lua_State* state); +int EQ2Emu_lua_SendStateCommand(lua_State* state); +int EQ2Emu_lua_SpawnSet(lua_State* state); +int EQ2Emu_lua_KillSpawn(lua_State* state); +int EQ2Emu_lua_KillSpawnByDistance(lua_State* state); +int EQ2Emu_lua_SpawnSetByDistance(lua_State* state); +int EQ2Emu_lua_SetRequiredQuest(lua_State* state); +int EQ2Emu_lua_SetRequiredHistory(lua_State* state); +int EQ2Emu_lua_Despawn(lua_State* state); +int EQ2Emu_lua_ChangeHandIcon(lua_State* state); +int EQ2Emu_lua_SetVisualFlag(lua_State* state); +int EQ2Emu_lua_SetInfoFlag(lua_State* state); +int EQ2Emu_lua_AddHate(lua_State* state); +int EQ2Emu_lua_GetZone(lua_State* state); +int EQ2Emu_lua_GetZoneName(lua_State* state); +int EQ2Emu_lua_GetZoneID(lua_State* state); +int EQ2Emu_lua_Zone(lua_State* state); +int EQ2Emu_lua_ModifyPower(lua_State* state); +int EQ2Emu_lua_ModifyHP(lua_State* state); +int EQ2Emu_lua_ModifyTotalPower(lua_State* state); +int EQ2Emu_lua_ModifyTotalHP(lua_State* state); +int EQ2Emu_lua_SpellHeal(lua_State* state); +int EQ2Emu_lua_SpellHealPct(lua_State* state); + +int EQ2Emu_lua_AddItem(lua_State* state); +int EQ2Emu_lua_SummonItem(lua_State* state); +int EQ2Emu_lua_RemoveItem(lua_State* state); +int EQ2Emu_lua_HasItem(lua_State* state); +int EQ2Emu_lua_Spawn(lua_State* state); +int EQ2Emu_lua_AddSpawnAccess(lua_State* state); +int EQ2Emu_lua_CastSpell(lua_State* state); +int EQ2Emu_lua_SpellDamage(lua_State* state); +int EQ2Emu_lua_SpellDamageExt(lua_State* state); +int EQ2Emu_lua_FaceTarget(lua_State* state); +int EQ2Emu_lua_MoveToLocation(lua_State* state); +int EQ2Emu_lua_ClearRunningLocations(lua_State* state); +int EQ2Emu_lua_Say(lua_State* state); +int EQ2Emu_lua_Shout(lua_State* state); +int EQ2Emu_lua_SayOOC(lua_State* state); +int EQ2Emu_lua_Emote(lua_State* state); +int EQ2Emu_lua_IsPlayer(lua_State* state); +int EQ2Emu_lua_GetCharacterID(lua_State* state); +int EQ2Emu_lua_MovementLoopAdd(lua_State* state); +int EQ2Emu_lua_GetCurrentZoneSafeLocation(lua_State* state); +int EQ2Emu_lua_PlayFlavor(lua_State* state); +int EQ2Emu_lua_PlayFlavorID(lua_State* state); +int EQ2Emu_lua_PlaySound(lua_State* state); +int EQ2Emu_lua_PlayVoice(lua_State* state); +int EQ2Emu_lua_PlayAnimation(lua_State* state); +int EQ2Emu_lua_AddLootItem(lua_State* state); +int EQ2Emu_lua_HasLootItem(lua_State* state); +int EQ2Emu_lua_RemoveLootItem(lua_State* state); +int EQ2Emu_lua_AddLootCoin(lua_State* state); +int EQ2Emu_lua_GiveLoot(lua_State* state); +int EQ2Emu_lua_HasPendingLoot(lua_State* state); +int EQ2Emu_lua_HasPendingLootItem(lua_State* state); +int EQ2Emu_lua_CreateConversation(lua_State* state); +int EQ2Emu_lua_AddConversationOption(lua_State* state); +int EQ2Emu_lua_StartConversation(lua_State* state); +int EQ2Emu_lua_StartDialogConversation(lua_State* state); +//int EQ2Emu_lua_StartItemConversation(lua_State* state); +int EQ2Emu_lua_CloseConversation(lua_State* state); +int EQ2Emu_lua_CloseItemConversation(lua_State* state); +int EQ2Emu_lua_SetPlayerProximityFunction(lua_State* state); +int EQ2Emu_lua_SetLocationProximityFunction(lua_State* state); +int EQ2Emu_lua_IsBindAllowed(lua_State* state); +int EQ2Emu_lua_IsGateAllowed(lua_State* state); +int EQ2Emu_lua_Bind(lua_State* state); +int EQ2Emu_lua_Gate(lua_State* state); +int EQ2Emu_lua_IsAlive(lua_State* state); +int EQ2Emu_lua_IsInCombat(lua_State* state); +int EQ2Emu_lua_SendMessage(lua_State* state); +int EQ2Emu_lua_SendPopUpMessage(lua_State* state); +int EQ2Emu_lua_SetServerControlFlag(lua_State* state); +int EQ2Emu_lua_ToggleTracking(lua_State* state); +int EQ2Emu_lua_AddPrimaryEntityCommand(lua_State* state); +int EQ2Emu_lua_AddSpellBookEntry(lua_State* state); +int EQ2Emu_lua_DeleteSpellBook(lua_State* state); +int EQ2Emu_lua_RemoveSpellBookEntry(lua_State* state); +int EQ2Emu_lua_SendNewAdventureSpells(lua_State* state); +int EQ2Emu_lua_SendNewTradeskillSpells(lua_State* state); +int EQ2Emu_lua_HasSpell(lua_State* state); +int EQ2Emu_lua_Attack(lua_State* state); +int EQ2Emu_lua_ApplySpellVisual(lua_State* state); +int EQ2Emu_lua_Interrupt(lua_State* state); +int EQ2Emu_lua_Stealth(lua_State* state); +int EQ2Emu_lua_IsStealthed(lua_State* state); +int EQ2Emu_lua_IsInvis(lua_State* state); +int EQ2Emu_lua_AddSpawnIDAccess(lua_State* state); +int EQ2Emu_lua_RemoveSpawnIDAccess(lua_State* state); +int EQ2Emu_lua_HasRecipeBook(lua_State* state); +int EQ2Emu_lua_SpawnMove(lua_State* state); +int EQ2Emu_lua_AddTransportSpawn(lua_State* state); +int EQ2Emu_lua_IsTransportSpawn(lua_State* state); +int EQ2Emu_lua_PerformCameraShake(lua_State* state); + +//Quest Stuff +int EQ2Emu_lua_SetStepComplete(lua_State* state); +int EQ2Emu_lua_AddStepProgress(lua_State* state); +int EQ2Emu_lua_GetTaskGroupStep(lua_State* state); +int EQ2Emu_lua_QuestStepIsComplete(lua_State* state); +int EQ2Emu_lua_GetQuestStep(lua_State* state); +int EQ2Emu_lua_RegisterQuest(lua_State* state); +int EQ2Emu_lua_OfferQuest(lua_State* state); +int EQ2Emu_lua_SetQuestPrereqLevel(lua_State* state); +int EQ2Emu_lua_AddQuestPrereqQuest(lua_State* state); +int EQ2Emu_lua_AddQuestPrereqItem(lua_State* state); +int EQ2Emu_lua_AddQuestPrereqFaction(lua_State* state); +int EQ2Emu_lua_AddQuestPrereqClass(lua_State* state); +int EQ2Emu_lua_AddQuestPrereqRace(lua_State* state); +int EQ2Emu_lua_AddQuestPrereqModelType(lua_State* state); +int EQ2Emu_lua_AddQuestPrereqTradeskillLevel(lua_State* state); +int EQ2Emu_lua_AddQuestPrereqTradeskillClass(lua_State* state); +int EQ2Emu_lua_HasQuestRewardItem(lua_State* state); +int EQ2Emu_lua_AddQuestRewardItem(lua_State* state); +int EQ2Emu_lua_AddQuestSelectableRewardItem(lua_State* state); +int EQ2Emu_lua_AddQuestRewardCoin(lua_State* state); +int EQ2Emu_lua_AddQuestRewardFaction(lua_State* state); +int EQ2Emu_lua_SetQuestRewardStatus(lua_State* state); +int EQ2Emu_lua_SetStatusTmpReward(lua_State* state); +int EQ2Emu_lua_SetCoinTmpReward(lua_State* state); +int EQ2Emu_lua_SetQuestRewardComment(lua_State* state); +int EQ2Emu_lua_SetQuestRewardExp(lua_State* state); +int EQ2Emu_lua_AddQuestStep(lua_State* state); +int EQ2Emu_lua_AddQuestStepKillLogic(lua_State* state); +int EQ2Emu_lua_AddQuestStepKill(lua_State* state); +int EQ2Emu_lua_AddQuestStepKillByRace(lua_State* state); +int EQ2Emu_lua_AddQuestStepChat(lua_State* state); +int EQ2Emu_lua_AddQuestStepObtainItem(lua_State* state); +int EQ2Emu_lua_AddQuestStepZoneLoc(lua_State* state); +int EQ2Emu_lua_AddQuestStepLocation(lua_State* state); +int EQ2Emu_lua_AddQuestStepLoc(lua_State* state); +int EQ2Emu_lua_AddQuestStepSpell(lua_State* state); +int EQ2Emu_lua_AddQuestStepCraft(lua_State* state); +int EQ2Emu_lua_AddQuestStepHarvest(lua_State* state); +int EQ2Emu_lua_AddQuestStepCompleteAction(lua_State* state); +int EQ2Emu_lua_AddQuestStepProgressAction(lua_State* state); +int EQ2Emu_lua_SetQuestCompleteAction(lua_State* state); +int EQ2Emu_lua_GiveQuestReward(lua_State* state); +int EQ2Emu_lua_UpdateQuestTaskGroupDescription(lua_State* state); +int EQ2Emu_lua_UpdateQuestStepDescription(lua_State* state); +int EQ2Emu_lua_UpdateQuestDescription(lua_State* state); +int EQ2Emu_lua_UpdateQuestZone(lua_State* state); +int EQ2Emu_lua_SetCompletedDescription(lua_State* state); +int EQ2Emu_lua_ProvidesQuest(lua_State* state); +int EQ2Emu_lua_HasQuest(lua_State* state); +int EQ2Emu_lua_HasCompletedQuest(lua_State* state); +int EQ2Emu_lua_QuestIsComplete(lua_State* state); +int EQ2Emu_lua_QuestReturnNPC(lua_State* state); +int EQ2Emu_lua_GetQuest(lua_State* state); +int EQ2Emu_lua_AddTimer(lua_State* state); +int EQ2Emu_lua_StopTimer(lua_State* state); +int EQ2Emu_lua_Harvest(lua_State* state); +int EQ2Emu_lua_SetCompleteFlag(lua_State* state); +int EQ2Emu_lua_CanReceiveQuest(lua_State* state); + +int EQ2Emu_lua_HasCollectionsToHandIn(lua_State *state); +int EQ2Emu_lua_HandInCollections(lua_State *state); + +int EQ2Emu_lua_UseWidget(lua_State* state); +int EQ2Emu_lua_SummonPet(lua_State* state); +int EQ2Emu_lua_Charm(lua_State* state); + +int EQ2Emu_lua_SetSpellList(lua_State* state); +int EQ2Emu_lua_GetPet(lua_State* state); +int EQ2Emu_lua_GetGroup(lua_State* state); + +int EQ2Emu_lua_CreateOptionWindow(lua_State* state); +int EQ2Emu_lua_AddOptionWindowOption(lua_State* state); +int EQ2Emu_lua_SendOptionWindow(lua_State* state); + +int EQ2Emu_lua_GetTradeskillClass(lua_State* state); +int EQ2Emu_lua_GetTradeskillLevel(lua_State* state); +int EQ2Emu_lua_GetTradeskillClassName(lua_State* state); +int EQ2Emu_lua_SetTradeskillLevel(lua_State* state); + +int EQ2Emu_lua_SummonDeityPet(lua_State* state); +int EQ2Emu_lua_SummonCosmeticPet(lua_State* state); +int EQ2Emu_lua_DismissPet(lua_State* state); +int EQ2Emu_lua_GetCharmedPet(lua_State* state); +int EQ2Emu_lua_GetDeityPet(lua_State* state); +int EQ2Emu_lua_GetCosmeticPet(lua_State* state); + +int EQ2Emu_lua_SetQuestFeatherColor(lua_State* state); +int EQ2Emu_lua_RemoveSpawnAccess(lua_State* state); +int EQ2Emu_lua_SpawnByLocationID(lua_State* state); +int EQ2Emu_lua_SpawnGroupByID(lua_State* state); +int EQ2Emu_lua_CastEntityCommand(lua_State* state); +int EQ2Emu_lua_SetLuaBrain(lua_State* state); +int EQ2Emu_lua_SetBrainTick(lua_State* state); +int EQ2Emu_lua_SetFollowTarget(lua_State* state); +int EQ2Emu_lua_GetFollowTarget(lua_State* state); +int EQ2Emu_lua_ToggleFollow(lua_State* state); +int EQ2Emu_lua_IsFollowing(lua_State* state); +int EQ2Emu_lua_SetTempVariable(lua_State* state); +int EQ2Emu_lua_GetTempVariable(lua_State* state); +int EQ2Emu_lua_GiveQuestItem(lua_State*state); +int EQ2Emu_lua_SetQuestRepeatable(lua_State* state); + + +int EQ2Emu_lua_AddWaypoint(lua_State* state); +int EQ2Emu_lua_RemoveWaypoint(lua_State* state); +int EQ2Emu_lua_SendWaypoints(lua_State* state); + +int EQ2Emu_lua_AddWard(lua_State* state); +int EQ2Emu_lua_AddToWard(lua_State* state); +int EQ2Emu_lua_RemoveWard(lua_State* state); +int EQ2Emu_lua_GetWardAmountLeft(lua_State* state); +int EQ2Emu_lua_GetWardValue(lua_State* state); + +//Combat AI related +int EQ2Emu_lua_SetTarget(lua_State* state); +int EQ2Emu_lua_IsPet(lua_State* state); +int EQ2Emu_lua_GetOwner(lua_State* state); +int EQ2Emu_lua_SetInCombat(lua_State* state); +int EQ2Emu_lua_CompareSpawns(lua_State* state); +int EQ2Emu_lua_ClearRunback(lua_State* state); +int EQ2Emu_lua_Runback(lua_State* state); +int EQ2Emu_lua_GetRunbackDistance(lua_State* state); +int EQ2Emu_lua_IsCasting(lua_State* state); +int EQ2Emu_lua_IsMezzed(lua_State* state); +int EQ2Emu_lua_IsStunned(lua_State* state); +int EQ2Emu_lua_IsMezzedOrStunned(lua_State* state); +int EQ2Emu_lua_ClearEncounter(lua_State* state); +int EQ2Emu_lua_ClearHate(lua_State* state); +int EQ2Emu_lua_GetMostHated(lua_State* state); +int EQ2Emu_lua_GetEncounterSize(lua_State* state); +int EQ2Emu_lua_HasRecovered(lua_State* state); +int EQ2Emu_lua_ProcessMelee(lua_State* state); +int EQ2Emu_lua_ProcessSpell(lua_State* state); +int EQ2Emu_lua_GetEncounter(lua_State* state); +int EQ2Emu_lua_GetHateList(lua_State* state); +int EQ2Emu_lua_HasGroup(lua_State* state); +int EQ2Emu_lua_HasSpellEffect(lua_State* state); + +int EQ2Emu_lua_SetSuccessTimer(lua_State* state); +int EQ2Emu_lua_SetFailureTimer(lua_State* state); +int EQ2Emu_lua_IsGroundSpawn(lua_State* state); +int EQ2Emu_lua_CanHarvest(lua_State* state); + +int EQ2Emu_lua_SummonDumbFirePet(lua_State* state); + +int EQ2Emu_lua_GetSkillValue(lua_State* state); +int EQ2Emu_lua_GetSkillMaxValue(lua_State* state); +int EQ2Emu_lua_GetSkillName(lua_State* state); +int EQ2Emu_lua_SetSkillMaxValue(lua_State* state); +int EQ2Emu_lua_SetSkillValue(lua_State* state); +int EQ2Emu_lua_GetSkill(lua_State* state); +int EQ2Emu_lua_GetSkillIDByName(lua_State* state); +int EQ2Emu_lua_HasSkill(lua_State* state); +int EQ2Emu_lua_AddSkill(lua_State* state); +int EQ2Emu_lua_RemoveSkill(lua_State* state); +int EQ2Emu_lua_IncreaseSkillCapsByType(lua_State* state); + +int EQ2Emu_lua_AddProc(lua_State* state); +int EQ2Emu_lua_AddProcExt(lua_State* state); +int EQ2Emu_lua_RemoveProc(lua_State* state); +int EQ2Emu_lua_Knockback(lua_State* state); + +int EQ2Emu_lua_IsEpic(lua_State* state); +int EQ2Emu_lua_IsHeroic(lua_State* state); +int EQ2Emu_lua_ProcDamage(lua_State* state); +int EQ2Emu_lua_LastSpellAttackHit(lua_State* state); + +int EQ2Emu_lua_IsBehind(lua_State* state); +int EQ2Emu_lua_IsFlanking(lua_State* state); +int EQ2Emu_lua_InFront(lua_State* state); +int EQ2Emu_lua_AddSpellTimer(lua_State* state); +int EQ2Emu_lua_SetItemCount(lua_State* state); +int EQ2Emu_lua_GetItemCount(lua_State* state); +int EQ2Emu_lua_Resurrect(lua_State* state); +int EQ2Emu_lua_BreatheUnderwater(lua_State* state); +int EQ2Emu_lua_BlurVision(lua_State* state); +int EQ2Emu_lua_SetVision(lua_State* state); +int EQ2Emu_lua_GetItemSkillReq(lua_State* state); +int EQ2Emu_lua_SetSpeedMultiplier(lua_State* state); +int EQ2Emu_lua_SetIllusion(lua_State* state); +int EQ2Emu_lua_ResetIllusion(lua_State* state); +int EQ2Emu_lua_AddThreatTransfer(lua_State* state); +int EQ2Emu_lua_RemoveThreatTransfer(lua_State* state); +int EQ2Emu_lua_CureByType(lua_State* state); +int EQ2Emu_lua_CureByControlEffect(lua_State* state); +int EQ2Emu_lua_AddSpawnSpellBonus(lua_State* state); +int EQ2Emu_lua_RemoveSpawnSpellBonus(lua_State* state); +int EQ2Emu_lua_CancelSpell(lua_State* state); +int EQ2Emu_lua_RemoveStealth(lua_State* state); +int EQ2Emu_lua_RemoveInvis(lua_State* state); +int EQ2Emu_lua_StartHeroicOpportunity(lua_State* state); +int EQ2Emu_lua_CopySpawnAppearance(lua_State* state); +int EQ2Emu_lua_RemoveTriggerFromSpell(lua_State* state); +int EQ2Emu_lua_GetSpellTriggerCount(lua_State* state); +int EQ2Emu_lua_SetSpellTriggerCount(lua_State* state); +int EQ2Emu_lua_HasSpellImmunity(lua_State* state); +int EQ2Emu_lua_AddImmunitySpell(lua_State* state); +int EQ2Emu_lua_RemoveImmunitySpell(lua_State* state); +int EQ2Emu_lua_SetSpellSnareValue(lua_State* state); +int EQ2Emu_lua_CheckRaceType(lua_State* state); +int EQ2Emu_lua_GetRaceType(lua_State* state); +int EQ2Emu_lua_GetRaceBaseType(lua_State* state); +int EQ2Emu_lua_GetQuestFlags(lua_State* state); +int EQ2Emu_lua_SetQuestFlags(lua_State* state); +int EQ2Emu_lua_SetQuestTimer(lua_State* state); +int EQ2Emu_lua_RemoveQuestStep(lua_State* state); +int EQ2Emu_lua_ResetQuestStep(lua_State* state); +int EQ2Emu_lua_SetQuestTimerComplete(lua_State* state); +int EQ2Emu_lua_AddQuestStepFailureAction(lua_State* state); +int EQ2Emu_lua_SetStepFailed(lua_State* state); +int EQ2Emu_lua_GetQuestCompleteCount(lua_State* state); +int EQ2Emu_lua_SetServerVariable(lua_State* state); +int EQ2Emu_lua_GetServerVariable(lua_State* state); +int EQ2Emu_lua_HasLanguage(lua_State* state); +int EQ2Emu_lua_AddLanguage(lua_State* state); +int EQ2Emu_lua_IsNight(lua_State* state); +int EQ2Emu_lua_AddMultiFloorLift(lua_State* state); +int EQ2Emu_lua_StartAutoMount(lua_State* state); +int EQ2Emu_lua_EndAutoMount(lua_State* state); +int EQ2Emu_lua_IsOnAutoMount(lua_State* state); +int EQ2Emu_lua_SetPlayerHistory(lua_State* state); +int EQ2Emu_lua_GetPlayerHistory(lua_State* state); +int EQ2Emu_lua_SetGridID(lua_State* state); +int EQ2Emu_lua_GetQuestStepProgress(lua_State* state); +int EQ2Emu_lua_SetPlayerLevel(lua_State* state); +int EQ2Emu_lua_AddCoin(lua_State* state); +int EQ2Emu_lua_RemoveCoin(lua_State* state); +int EQ2Emu_lua_GetPlayersInZone(lua_State* state); +int EQ2Emu_lua_SetSpawnAnimation(lua_State* state); +int EQ2Emu_lua_GetClientVersion(lua_State* state); +int EQ2Emu_lua_GetItemID(lua_State* state); +int EQ2Emu_lua_IsEntity(lua_State* state); +int EQ2Emu_lua_GetOrigX(lua_State* state); +int EQ2Emu_lua_GetOrigY(lua_State* state); +int EQ2Emu_lua_GetOrigZ(lua_State* state); +int EQ2Emu_lua_GetPCTOfHP(lua_State* state); +int EQ2Emu_lua_GetPCTOfPower(lua_State* state); +int EQ2Emu_lua_GetBoundZoneID(lua_State* state); +int EQ2Emu_lua_Evac(lua_State* state); +int EQ2Emu_lua_GetSpellTier(lua_State* state); +int EQ2Emu_lua_GetSpellID(lua_State* state); +int EQ2Emu_lua_StartTransmute(lua_State* state); +int EQ2Emu_lua_CompleteTransmute(lua_State* state); +int EQ2Emu_lua_ProcHate(lua_State* state); + +int EQ2Emu_lua_GiveExp(lua_State* state); +int EQ2Emu_lua_DisplayText(lua_State* state); +int EQ2Emu_lua_ShowLootWindow(lua_State* state); +int EQ2Emu_lua_GetRandomSpawnByID(lua_State* state); +int EQ2Emu_lua_AddPrimaryEntityCommandAllSpawns(lua_State* state); +int EQ2Emu_lua_InstructionWindow(lua_State* state); +int EQ2Emu_lua_InstructionWindowClose(lua_State* state); +int EQ2Emu_lua_InstructionWindowGoal(lua_State* state); +int EQ2Emu_lua_ShowWindow(lua_State* state); +int EQ2Emu_lua_FlashWindow(lua_State* state); +int EQ2Emu_lua_EnableGameEvent(lua_State* state); +int EQ2Emu_lua_GetTutorialStep(lua_State* state); +int EQ2Emu_lua_SetTutorialStep(lua_State* state); + +int EQ2Emu_lua_CheckLOS(lua_State* state); +int EQ2Emu_lua_CheckLOSByCoordinates(lua_State* state); + +int EQ2Emu_lua_SetZoneExpansionFlag(lua_State* state); +int EQ2Emu_lua_GetZoneExpansionFlag(lua_State* state); +int EQ2Emu_lua_SetZoneHolidayFlag(lua_State* state); +int EQ2Emu_lua_GetZoneHolidayFlag(lua_State* state); + +int EQ2Emu_lua_SetCanBind(lua_State* state); +int EQ2Emu_lua_GetCanBind(lua_State* state); + +int EQ2Emu_lua_GetCanGate(lua_State* state); +int EQ2Emu_lua_SetCanGate(lua_State* state); + +int EQ2Emu_lua_GetCanEvac(lua_State* state); +int EQ2Emu_lua_SetCanEvac(lua_State* state); + +int EQ2Emu_lua_AddSpawnProximity(lua_State* state); + +int EQ2Emu_lua_CanSeeInvis(lua_State* state); +int EQ2Emu_lua_SetSeeInvis(lua_State* state); +int EQ2Emu_lua_SetSeeHide(lua_State* state); + +int EQ2Emu_lua_SetAccessToEntityCommand(lua_State* state); +int EQ2Emu_lua_SetAccessToEntityCommandByCharID(lua_State* state); +int EQ2Emu_lua_RemovePrimaryEntityCommand(lua_State* state); +int EQ2Emu_lua_SendUpdateDefaultCommand(lua_State* state); + + +int EQ2Emu_lua_SendTransporters(lua_State* state); +int EQ2Emu_lua_SetTemporaryTransportID(lua_State* state); +int EQ2Emu_lua_GetTemporaryTransportID(lua_State* state); + +int EQ2Emu_lua_GetAlignment(lua_State* state); +int EQ2Emu_lua_SetAlignment(lua_State* state); + +int EQ2Emu_lua_GetSpell(lua_State* state); +int EQ2Emu_lua_GetSpellData(lua_State* state); +int EQ2Emu_lua_SetSpellData(lua_State* state); +int EQ2Emu_lua_CastCustomSpell(lua_State* state); + +int EQ2Emu_lua_SetSpellDataIndex(lua_State* state); +int EQ2Emu_lua_GetSpellDataIndex(lua_State* state); + +int EQ2Emu_lua_SetSpellDisplayEffect(lua_State* state); +int EQ2Emu_lua_GetSpellDisplayEffect(lua_State* state); + +int EQ2Emu_lua_InWater(lua_State* state); +int EQ2Emu_lua_InLava(lua_State* state); + +int EQ2Emu_lua_DamageSpawn(lua_State* state); + +int EQ2Emu_lua_IsInvulnerable(lua_State* state); +int EQ2Emu_lua_SetInvulnerable(lua_State* state); + +int EQ2Emu_lua_GetRuleFlagBool(lua_State* state); +int EQ2Emu_lua_GetRuleFlagInt32(lua_State* state); +int EQ2Emu_lua_GetRuleFlagFloat(lua_State* state); + +int EQ2Emu_lua_GetAAInfo(lua_State* state); +int EQ2Emu_lua_SetAAInfo(lua_State* state); + +int EQ2Emu_lua_AddMasterTitle(lua_State* state); +int EQ2Emu_lua_AddCharacterTitle(lua_State* state); +int EQ2Emu_lua_SetCharacterTitleSuffix(lua_State* state); +int EQ2Emu_lua_SetCharacterTitlePrefix(lua_State* state); +int EQ2Emu_lua_ResetCharacterTitleSuffix(lua_State* state); +int EQ2Emu_lua_ResetCharacterTitlePrefix(lua_State* state); + +int EQ2Emu_lua_GetInfoStructString(lua_State* state); +int EQ2Emu_lua_GetInfoStructUInt(lua_State* state); +int EQ2Emu_lua_GetInfoStructSInt(lua_State* state); +int EQ2Emu_lua_GetInfoStructFloat(lua_State* state); + +int EQ2Emu_lua_SetInfoStructString(lua_State* state); +int EQ2Emu_lua_SetInfoStructUInt(lua_State* state); +int EQ2Emu_lua_SetInfoStructSInt(lua_State* state); +int EQ2Emu_lua_SetInfoStructFloat(lua_State* state); + +int EQ2Emu_lua_SetCharSheetChanged(lua_State* state); + +int EQ2Emu_lua_AddPlayerMail(lua_State* state); +int EQ2Emu_lua_AddPlayerMailByCharID(lua_State* state); + +int EQ2Emu_lua_OpenDoor(lua_State* state); +int EQ2Emu_lua_CloseDoor(lua_State* state); +int EQ2Emu_lua_IsOpen(lua_State* state); + +int EQ2Emu_lua_MakeRandomInt(lua_State* state); +int EQ2Emu_lua_MakeRandomFloat(lua_State* state); + +int EQ2Emu_lua_AddIconValue(lua_State* state); +int EQ2Emu_lua_RemoveIconValue(lua_State* state); + +int EQ2Emu_lua_GetShardID(lua_State* state); +int EQ2Emu_lua_GetShardCharID(lua_State* state); +int EQ2Emu_lua_GetShardCreatedTimestamp(lua_State* state); +int EQ2Emu_lua_DeleteDBShardID(lua_State* state); + +int EQ2Emu_lua_PauseMovement(lua_State* state); +int EQ2Emu_lua_StopMovement(lua_State* state); + +int EQ2Emu_lua_GetArrowColor(lua_State* state); +int EQ2Emu_lua_GetTSArrowColor(lua_State* state); + +int EQ2Emu_lua_GetSpawnByRailID(lua_State* state); +int EQ2Emu_lua_SetRailID(lua_State* state); +int EQ2Emu_lua_IsZoneLoading(lua_State* state); +int EQ2Emu_lua_IsRunning(lua_State* state); + +int EQ2Emu_lua_GetZoneLockoutTimer(lua_State* state); + +int EQ2Emu_lua_SetWorldTime(lua_State* state); +int EQ2Emu_lua_GetWorldTimeYear(lua_State* state); +int EQ2Emu_lua_GetWorldTimeMonth(lua_State* state); +int EQ2Emu_lua_GetWorldTimeHour(lua_State* state); +int EQ2Emu_lua_GetWorldTimeMinute(lua_State* state); +int EQ2Emu_lua_SendTimeUpdate(lua_State* state); + + +int EQ2Emu_lua_SetLootTier(lua_State* state); +int EQ2Emu_lua_GetLootTier(lua_State* state); + +int EQ2Emu_lua_SetLootDropType(lua_State* state); +int EQ2Emu_lua_GetLootDropType(lua_State* state); + +int EQ2Emu_lua_DamageEquippedItems(lua_State* state); + +int EQ2Emu_lua_CreateWidgetRegion(lua_State* state); +int EQ2Emu_lua_RemoveRegion(lua_State* state); + +int EQ2Emu_lua_SetPlayerPOVGhost(lua_State* state); +int EQ2Emu_lua_SetCastOnAggroComplete(lua_State* state); +int EQ2Emu_lua_IsCastOnAggroComplete(lua_State* state); + +int EQ2Emu_lua_AddRecipeBookToPlayer(lua_State* state); +int EQ2Emu_lua_RemoveRecipeFromPlayer(lua_State* state); + +int EQ2Emu_lua_ReplaceWidgetFromClient(lua_State* state); +int EQ2Emu_lua_RemoveWidgetFromSpawnMap(lua_State* state); +int EQ2Emu_lua_RemoveWidgetFromZoneMap(lua_State* state); + +int EQ2Emu_lua_SendHearCast(lua_State* state); + +int EQ2Emu_lua_GetCharacterFlag(lua_State* state); +int EQ2Emu_lua_ToggleCharacterFlag(lua_State* state); + +int EQ2Emu_lua_GetSpellInitialTarget(lua_State* state); +#endif \ No newline at end of file diff --git a/source/WorldServer/LuaInterface.cpp b/source/WorldServer/LuaInterface.cpp new file mode 100644 index 0000000..490665b --- /dev/null +++ b/source/WorldServer/LuaInterface.cpp @@ -0,0 +1,2774 @@ +/* + 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 . +*/ +#include "LuaInterface.h" +#include "LuaFunctions.h" +#include "WorldDatabase.h" +#include "SpellProcess.h" +#include "../common/Log.h" +#include "World.h" + +#ifndef WIN32 + #include + #include + #include + #include + #include +#else + #include +#endif + +extern WorldDatabase database; +extern ZoneList zone_list; + + +LuaInterface::LuaInterface() { + shutting_down = false; + lua_system_reloading = false; + MDebugClients.SetName("LuaInterface::MDebugClients"); + MSpells.SetName("LuaInterface::MSpells"); + MSpawnScripts.SetName("LuaInterface::MSpawnScripts"); + MZoneScripts.SetName("LuaInterface::MZoneScripts"); + MQuests.SetName("LuaInterface::MQuests"); + MLUAMain.SetName("LuaInterface::MLUAMain"); + MItemScripts.SetName("LuaInterface::MItemScripts"); + MSpellDelete.SetName("LuaInterface::MSpellDelete"); + MCustomSpell.SetName("LuaInterface::MCustomSpell"); + MRegionScripts.SetName("LuaInterface::MRegionScripts"); + user_data_timer = new Timer(20000); + user_data_timer->Start(); + spell_delete_timer = new Timer(5000); + spell_delete_timer->Start(); +} +#ifdef WIN32 +vector* LuaInterface::GetDirectoryListing(const char* directory) { + vector* ret = new vector; + WIN32_FIND_DATA fdata; + HANDLE dhandle; + char buf[MAX_PATH]; + snprintf(buf, sizeof(buf), "%s\\*", directory); + if((dhandle = FindFirstFile(buf, &fdata)) == INVALID_HANDLE_VALUE) { + safe_delete(ret); + return 0; + } + + ret->push_back(string(fdata.cFileName)); + + while(1) { + if(FindNextFile(dhandle, &fdata)) { + ret->push_back(string(fdata.cFileName)); + } + else{ + if(GetLastError() == ERROR_NO_MORE_FILES) { + break; + } else { + safe_delete(ret); + FindClose(dhandle); + return 0; + } + } + } + + if(FindClose(dhandle) == 0) { + safe_delete(ret); + return 0; + } + return ret; +} +#else +vector* LuaInterface::GetDirectoryListing(const char* directory) { + vector* ret = new vector; + DIR *dp; + struct dirent *ep; + dp = opendir (directory); + if (dp != NULL){ + while ((ep = readdir (dp))) + ret->push_back(string(ep->d_name)); + (void) closedir (dp); + } + else { + safe_delete(ret); + return 0; + } + + return ret; +} +#endif + +LuaInterface::~LuaInterface() { + shutting_down = true; + MLUAMain.lock(); + DestroySpells(); + DestroySpawnScripts(); + DestroyQuests(); + DestroyItemScripts(); + DestroyZoneScripts(); + DestroyRegionScripts(); + DeleteUserDataPtrs(true); + DeletePendingSpells(true); + safe_delete(user_data_timer); + safe_delete(spell_delete_timer); + MLUAMain.unlock(); +} + +int LuaInterface::GetNumberOfArgs(lua_State* state) { + return lua_gettop(state); +} + +void LuaInterface::Process() { + if(shutting_down) + return; + MLUAMain.lock(); + if(user_data_timer && user_data_timer->Check()) + DeleteUserDataPtrs(false); + if(spell_delete_timer && spell_delete_timer->Check()) + DeletePendingSpells(false); + MLUAMain.unlock(); +} + +void LuaInterface::DestroySpells() { + map::iterator itr; + MSpells.lock(); + for(itr = spells.begin(); itr != spells.end(); itr++){ + MSpellDelete.lock(); + RemoveCurrentSpell(itr->second->state, false); + MSpellDelete.unlock(); + lua_close(itr->second->state); + safe_delete(itr->second); + } + spells.clear(); + MSpells.unlock(); +} + +void LuaInterface::DestroyQuests(bool reload) { + map::iterator itr; + MQuests.lock(); + for(itr = quest_states.begin(); itr != quest_states.end(); itr++){ + safe_delete(quests[itr->first]); + lua_close(itr->second); + } + quests.clear(); + quest_states.clear(); + map::iterator mutex_itr; + for(mutex_itr = quests_mutex.begin(); mutex_itr != quests_mutex.end(); mutex_itr++){ + safe_delete(mutex_itr->second); + } + quests_mutex.clear(); + if(reload) + database.LoadQuests(); + MQuests.unlock(); +} + +void LuaInterface::DestroyItemScripts() { + map >::iterator itr; + map::iterator state_itr; + Mutex* mutex = 0; + MItemScripts.writelock(__FUNCTION__, __LINE__); + for(itr = item_scripts.begin(); itr != item_scripts.end(); itr++){ + mutex = GetItemScriptMutex(itr->first.c_str()); + mutex->writelock(__FUNCTION__, __LINE__); + for(state_itr = itr->second.begin(); state_itr != itr->second.end(); state_itr++) + lua_close(state_itr->first); + mutex->releasewritelock(__FUNCTION__, __LINE__); + safe_delete(mutex); + } + item_scripts.clear(); + item_inverse_scripts.clear(); + item_scripts_mutex.clear(); + MItemScripts.releasewritelock(__FUNCTION__, __LINE__); +} + +void LuaInterface::DestroySpawnScripts() { + map >::iterator itr; + map::iterator state_itr; + Mutex* mutex = 0; + MSpawnScripts.writelock(__FUNCTION__, __LINE__); + for(itr = spawn_scripts.begin(); itr != spawn_scripts.end(); itr++){ + mutex = GetSpawnScriptMutex(itr->first.c_str()); + mutex->writelock(__FUNCTION__, __LINE__); + for(state_itr = itr->second.begin(); state_itr != itr->second.end(); state_itr++) + lua_close(state_itr->first); + mutex->releasewritelock(__FUNCTION__, __LINE__); + safe_delete(mutex); + } + spawn_scripts.clear(); + spawn_inverse_scripts.clear(); + spawn_scripts_mutex.clear(); + MSpawnScripts.releasewritelock(__FUNCTION__, __LINE__); +} + +void LuaInterface::DestroyZoneScripts() { + map >::iterator itr; + map::iterator state_itr; + Mutex* mutex = 0; + MZoneScripts.writelock(__FUNCTION__, __LINE__); + for (itr = zone_scripts.begin(); itr != zone_scripts.end(); itr++){ + mutex = GetZoneScriptMutex(itr->first.c_str()); + mutex->writelock(__FUNCTION__, __LINE__); + for(state_itr = itr->second.begin(); state_itr != itr->second.end(); state_itr++) + lua_close(state_itr->first); + mutex->releasewritelock(__FUNCTION__, __LINE__); + safe_delete(mutex); + } + zone_scripts.clear(); + zone_inverse_scripts.clear(); + zone_scripts_mutex.clear(); + MZoneScripts.releasewritelock(__FUNCTION__, __LINE__); +} + +void LuaInterface::DestroyRegionScripts() { + map >::iterator itr; + map::iterator state_itr; + Mutex* mutex = 0; + MRegionScripts.writelock(__FUNCTION__, __LINE__); + for (itr = region_scripts.begin(); itr != region_scripts.end(); itr++){ + mutex = GetRegionScriptMutex(itr->first.c_str()); + mutex->writelock(__FUNCTION__, __LINE__); + for(state_itr = itr->second.begin(); state_itr != itr->second.end(); state_itr++) + lua_close(state_itr->first); + mutex->releasewritelock(__FUNCTION__, __LINE__); + safe_delete(mutex); + } + region_scripts.clear(); + region_inverse_scripts.clear(); + region_scripts_mutex.clear(); + MRegionScripts.releasewritelock(__FUNCTION__, __LINE__); +} + +void LuaInterface::ReloadSpells() { + DestroySpells(); + database.LoadSpellScriptData(); +} + +bool LuaInterface::LoadLuaSpell(const char* name) { + LuaSpell* spell = 0; + string lua_script = string(name); + if (lua_script.find(".lua") == string::npos) + lua_script.append(".lua"); + lua_State* state = LoadLuaFile(lua_script.c_str()); + if(state){ + spell = new LuaSpell; + spell->file_name = lua_script; + spell->state = state; + spell->spell = 0; + spell->caster = 0; + spell->initial_target = 0; + spell->resisted = false; + spell->has_damaged = false; + spell->is_damage_spell = false; + spell->interrupted = false; + spell->last_spellattack_hit = false; + spell->crit = false; + spell->MSpellTargets.SetName("LuaSpell.MSpellTargets"); + spell->cancel_after_all_triggers = false; + spell->num_triggers = 0; + spell->num_calls = 0; + spell->is_recast_timer = false; + spell->had_triggers = false; + spell->had_dmg_remaining = false; + spell->slot_pos = 0; + spell->damage_remaining = 0; + spell->effect_bitmask = 0; + spell->restored = false; + spell->initial_caster_char_id = 0; + spell->initial_target_char_id = 0; + + MSpells.lock(); + if (spells.count(lua_script) > 0) { + + SetLuaUserDataStale(spells[lua_script]); + MSpellDelete.lock(); + RemoveCurrentSpell(spells[lua_script]->state, false); + MSpellDelete.unlock(); + lua_close(spells[lua_script]->state); + safe_delete(spells[lua_script]); + } + spells[lua_script] = spell; + MSpells.unlock(); + + return true; + } + return false; +} + +bool LuaInterface::LoadLuaSpell(string name) { + return LoadLuaSpell(name.c_str()); +} + +bool LuaInterface::LoadItemScript(string name) { + return LoadItemScript(name.c_str()); +} + +bool LuaInterface::LoadItemScript(const char* name) { + bool ret = false; + if(name){ + lua_State* state = LoadLuaFile(name); + if(state){ + MItemScripts.writelock(__FUNCTION__, __LINE__); + item_scripts[name][state] = false; + MItemScripts.releasewritelock(__FUNCTION__, __LINE__); + ret = true; + } + } + return ret; +} + +bool LuaInterface::LoadSpawnScript(const char* name) { + bool ret = false; + if(name){ + lua_State* state = LoadLuaFile(name); + if(state){ + MSpawnScripts.writelock(__FUNCTION__, __LINE__); + spawn_scripts[name][state] = false; + MSpawnScripts.releasewritelock(__FUNCTION__, __LINE__); + ret = true; + } + } + return ret; +} + +bool LuaInterface::LoadZoneScript(const char* name) { + bool ret = false; + if (name) { + lua_State* state = LoadLuaFile(name); + if (state) { + MZoneScripts.writelock(__FUNCTION__, __LINE__); + zone_scripts[name][state] = false; + MZoneScripts.releasewritelock(__FUNCTION__, __LINE__); + ret = true; + } + } + return ret; +} + +bool LuaInterface::LoadRegionScript(const char* name) { + bool ret = false; + if (name) { + lua_State* state = LoadLuaFile(name); + if (state) { + MRegionScripts.writelock(__FUNCTION__, __LINE__); + region_scripts[name][state] = false; + MRegionScripts.releasewritelock(__FUNCTION__, __LINE__); + ret = true; + } + } + return ret; +} + +void LuaInterface::ProcessErrorMessage(const char* message) { + MDebugClients.lock(); + vector delete_clients; + map::iterator itr; + for(itr = debug_clients.begin(); itr != debug_clients.end(); itr++){ + if((Timer::GetCurrentTime2() - itr->second) > 60000) + delete_clients.push_back(itr->first); + else + itr->first->Message(CHANNEL_COLOR_RED, "LUA Error: %s", message); + } + for(int32 i=0;iGetQuestID()) == 0){ + ret = new Mutex(); + quests_mutex[quest->GetQuestID()] = ret; + ret->SetName(string("Quest::").append(quest->GetName())); + } + else + ret = quests_mutex[quest->GetQuestID()]; + MQuests.unlock(); + return ret; +} + +bool LuaInterface::CallQuestFunction(Quest* quest, const char* function, Spawn* player, int32 step_id, int32* returnValue) { + if(shutting_down) + return false; + lua_State* state = 0; + if(quest){ + LogWrite(LUA__DEBUG, 0, "LUA", "Quest: %s, function: %s", quest->GetName(), function); + Mutex* mutex = GetQuestMutex(quest); + mutex->readlock(__FUNCTION__, __LINE__); + if(quest_states.count(quest->GetQuestID()) > 0) + state = quest_states[quest->GetQuestID()]; + bool success = false; // if no state then we return false + if(state){ + int8 arg_count = 3; + lua_getglobal(state, function); + + if (!lua_isfunction(state, lua_gettop(state))){ + lua_pop(state, 1); + mutex->releasereadlock(__FUNCTION__); + return false; + } + + SetQuestValue(state, quest); + Spawn* spawn = player->GetZone()->GetSpawnByDatabaseID(quest->GetQuestGiver()); + SetSpawnValue(state, spawn); + SetSpawnValue(state, player); + if(step_id != 0xFFFFFFFF){ + SetInt32Value(state, step_id); + arg_count++; + } + + success = CallScriptInt32(state, arg_count, returnValue); + } + mutex->releasereadlock(__FUNCTION__, __LINE__); + LogWrite(LUA__DEBUG, 0, "LUA", "Done!"); + return success; + } + return false; +} + +Quest* LuaInterface::LoadQuest(int32 id, const char* name, const char* type, const char* zone, int8 level, const char* description, char* script_name) { + if(shutting_down) + return 0; + lua_State* state = LoadLuaFile(script_name); + Quest* quest = 0; + if(state){ + quest = new Quest(id); + if (name) + quest->SetName(string(name)); + if (type) + quest->SetType(string(type)); + if (zone) + quest->SetZone(string(zone)); + quest->SetLevel(level); + if (description) + quest->SetDescription(string(description)); + lua_getglobal(state, "Init"); + SetQuestValue(state, quest); + if(lua_pcall(state, 1, 0, 0) != 0){ + LogError("Error processing Quest \"%s\" (%u): %s", name ? name : "unknown", id, lua_tostring(state, -1)); + lua_pop(state, 1); + SetLuaUserDataStale(quest); + safe_delete(quest); + return 0; + } + if(!quest->GetName()){ + SetLuaUserDataStale(quest); + safe_delete(quest); + return 0; + } + quest_states[id] = state; + quests[id] = quest; + } + return quest; +} + +const char* LuaInterface::GetScriptName(lua_State* state) +{ + map::iterator itr; + MItemScripts.writelock(__FUNCTION__, __LINE__); + itr = item_inverse_scripts.find(state); + if (itr != item_inverse_scripts.end()) + { + const char* scriptName = itr->second.c_str(); + MItemScripts.releasewritelock(__FUNCTION__, __LINE__); + return scriptName; + } + MItemScripts.releasewritelock(__FUNCTION__, __LINE__); + + MSpawnScripts.writelock(__FUNCTION__, __LINE__); + itr = spawn_inverse_scripts.find(state); + if (itr != spawn_inverse_scripts.end()) + { + const char* scriptName = itr->second.c_str(); + MSpawnScripts.releasewritelock(__FUNCTION__, __LINE__); + return scriptName; + } + MSpawnScripts.releasewritelock(__FUNCTION__, __LINE__); + + MZoneScripts.writelock(__FUNCTION__, __LINE__); + itr = zone_inverse_scripts.find(state); + if (itr != zone_inverse_scripts.end()) + { + const char* scriptName = itr->second.c_str(); + MZoneScripts.releasewritelock(__FUNCTION__, __LINE__); + return scriptName; + } + MZoneScripts.releasewritelock(__FUNCTION__, __LINE__); + + MRegionScripts.writelock(__FUNCTION__, __LINE__); + itr = region_inverse_scripts.find(state); + if (itr != region_inverse_scripts.end()) + { + const char* scriptName = itr->second.c_str(); + MRegionScripts.releasewritelock(__FUNCTION__, __LINE__); + return scriptName; + } + MRegionScripts.releasewritelock(__FUNCTION__, __LINE__); + + MSpells.lock(); + LuaSpell* spell = GetCurrentSpell(state, false); + if (spell) + { + const char* fileName = (spell->file_name.length() > 0) ? spell->file_name.c_str() : ""; + MSpells.unlock(); + return fileName; + } + MSpells.unlock(); + + return ""; +} + +bool LuaInterface::LoadSpawnScript(string name) { + return LoadSpawnScript(name.c_str()); +} + +bool LuaInterface::LoadZoneScript(string name) { + return LoadZoneScript(name.c_str()); +} + +bool LuaInterface::LoadRegionScript(string name) { + return LoadRegionScript(name.c_str()); +} + +std::string LuaInterface::AddSpawnPointers(LuaSpell* spell, bool first_cast, bool precast, const char* function, SpellScriptTimer* timer, bool passLuaSpell, Spawn* altTarget) { + std::string functionCalled = string(""); + if (function) + { + functionCalled = string(function); + lua_getglobal(spell->state, function); + } + else if (precast) + { + functionCalled = "precast"; + lua_getglobal(spell->state, "precast"); + } + else if(first_cast) + { + functionCalled = "cast"; + lua_getglobal(spell->state, "cast"); + } + else + { + functionCalled = "tick"; + lua_getglobal(spell->state, "tick"); + } + + LogWrite(SPELL__DEBUG, 0, "Spell", "LuaInterface::AddSpawnPointers spell %s (%u) function %s, caster %s.", spell->spell ? spell->spell->GetName() : "UnknownUnset", spell->spell ? spell->spell->GetSpellID() : 0, functionCalled.c_str(), spell->caster ? spell->caster->GetName() : "Unknown"); + + if (!lua_isfunction(spell->state, lua_gettop(spell->state))){ + lua_pop(spell->state, 1); + return string(""); + } + else { + lua_getglobal(spell->state, functionCalled.c_str()); + } + + if(passLuaSpell) + SetSpellValue(spell->state, spell); + + Spawn* temp_spawn = 0; + if (timer && timer->caster && spell->caster && spell->caster->GetZone()) + temp_spawn = spell->caster->GetZone()->GetSpawnByID(timer->caster); + + if (temp_spawn) + SetSpawnValue(spell->state, temp_spawn); + else if (spell->caster) + SetSpawnValue(spell->state, spell->caster); + + temp_spawn = 0; + + if (timer && timer->target && spell->caster && spell->caster->GetZone()) + temp_spawn = spell->caster->GetZone()->GetSpawnByID(timer->target); + + if (temp_spawn) + SetSpawnValue(spell->state, temp_spawn); + else { + if(altTarget) + { + SetSpawnValue(spell->state, altTarget); + } + else if(spell->caster && spell->caster->GetZone() != nullptr && spell->initial_target) + { + // easier to debug target id as ptr + Spawn* new_target = spell->caster->GetZone()->GetSpawnByID(spell->initial_target); + SetSpawnValue(spell->state, new_target); + } + else if(spell->caster && spell->caster->GetTarget()) + SetSpawnValue(spell->state, spell->caster->GetTarget()); + else + SetSpawnValue(spell->state, 0); + } + + return functionCalled; +} + +LuaSpell* LuaInterface::GetCurrentSpell(lua_State* state, bool needsLock) { + LuaSpell* spell = 0; + + if(needsLock) + MSpells.lock(); + + if(current_spells.count(state) > 0) + spell = current_spells[state]; + + if(needsLock) + MSpells.unlock(); + + return spell; +} + +void LuaInterface::RemoveCurrentSpell(lua_State* state, bool needsLock) { + if(needsLock) { + MSpells.lock(); + MSpellDelete.lock(); + } + map::iterator itr = current_spells.find(state); + if(itr != current_spells.end()) + current_spells.erase(itr); + if(needsLock) { + MSpellDelete.unlock(); + MSpells.unlock(); + } +} + +bool LuaInterface::CallSpellProcess(LuaSpell* spell, int8 num_parameters, std::string customFunction) { + if(shutting_down || !spell || !spell->caster) + return false; + + MSpells.lock(); + current_spells[spell->state] = spell; + MSpells.unlock(); + + LogWrite(SPELL__DEBUG, 0, "Spell", "LuaInterface::CallSpellProcess spell %s (%u) function %s, caster %s.", spell->spell ? spell->spell->GetName() : "UnknownUnset", spell->spell ? spell->spell->GetSpellID() : 0, customFunction.c_str(), spell->caster->GetName()); + + if(lua_pcall(spell->state, num_parameters, 0, 0) != 0){ + LogError("Error running function '%s' in %s: %s", customFunction.c_str(), spell->spell->GetName(), lua_tostring(spell->state, -1)); + lua_pop(spell->state, 1); + RemoveSpell(spell, false); // may be in a lock + return false; + } + return true; +} + +void LuaInterface::RemoveSpawnScript(const char* name) { + lua_State* state = 0; + Mutex* mutex = GetSpawnScriptMutex(name); + while((state = GetSpawnScript(name, false))){ + mutex->writelock(__FUNCTION__, __LINE__); + lua_close(state); + spawn_scripts[name].erase(state); + mutex->releasewritelock(__FUNCTION__, __LINE__); + } + MSpawnScripts.writelock(__FUNCTION__, __LINE__); + spawn_scripts.erase(name); + MSpawnScripts.releasewritelock(__FUNCTION__, __LINE__); +} + +bool LuaInterface::CallItemScript(lua_State* state, int8 num_parameters, std::string* returnValue) { + if(shutting_down) + return false; + if(!state || lua_pcall(state, num_parameters, 1, 0) != 0){ + if (state){ + const char* err = lua_tostring(state, -1); + LogError("%s: %s", GetScriptName(state), err); + lua_pop(state, 1); + } + return false; + } + + std::string result = std::string(""); + + if(lua_isstring(state, -1)){ + size_t size = 0; + const char* str = lua_tolstring(state, -1, &size); + if(str) + result = string(str); + } + + if(returnValue) + *returnValue = std::string(result); + + return true; +} + +bool LuaInterface::CallItemScript(lua_State* state, int8 num_parameters, sint64* returnValue) { + if(shutting_down) + return false; + if(!state || lua_pcall(state, num_parameters, 1, 0) != 0){ + if (state){ + const char* err = lua_tostring(state, -1); + LogError("%s: %s", GetScriptName(state), err); + lua_pop(state, 1); + } + return false; + } + + sint64 result = 0; + + if (lua_isnumber(state, -1)) + { + result = (sint64)lua_tonumber(state, -1); + lua_pop(state, 1); + } + + if(returnValue) + *returnValue = result; + + return true; +} + +bool LuaInterface::CallSpawnScript(lua_State* state, int8 num_parameters) { + if(shutting_down || lua_system_reloading) + return false; + if(!state || lua_pcall(state, num_parameters, 0, 0) != 0){ + if (state){ + const char* err = lua_tostring(state, -1); + LogError("%s: %s", GetScriptName(state), err); + lua_pop(state, 1); + } + return false; + } + return true; +} + +bool LuaInterface::CallScriptInt32(lua_State* state, int8 num_parameters, int32* returnValue) { + if(shutting_down) + return false; + if (!state || lua_pcall(state, num_parameters, 1, 0) != 0) { + if (state){ + const char* err = lua_tostring(state, -1); + LogError("%s: %s", GetScriptName(state), err); + lua_pop(state, 1); + } + return false; + } + + int32 result = 0; + + if (lua_isnumber(state, -1)) + { + result = (int32)lua_tonumber(state, -1); + lua_pop(state, 1); + } + + if(returnValue) + *returnValue = result; + + return true; +} + +bool LuaInterface::CallScriptSInt32(lua_State* state, int8 num_parameters, sint32* returnValue) { + if(shutting_down) + return false; + if (!state || lua_pcall(state, num_parameters, 1, 0) != 0) { + if (state){ + const char* err = lua_tostring(state, -1); + LogError("%s: %s", GetScriptName(state), err); + lua_pop(state, 1); + } + return false; + } + + sint32 result = 0; + + if (lua_isnumber(state, -1)) + { + result = (sint32)lua_tointeger(state, -1); + lua_pop(state, 1); + } + + if(returnValue) + *returnValue = result; + + return true; +} + +bool LuaInterface::CallRegionScript(lua_State* state, int8 num_parameters, int32* returnValue) { + if(shutting_down) + return false; + if (!state || lua_pcall(state, num_parameters, 1, 0) != 0) { + if (state){ + const char* err = lua_tostring(state, -1); + LogError("%s: %s", GetScriptName(state), err); + lua_pop(state, 1); + } + return false; + } + + int32 result = 0; + + if (lua_isnumber(state, -1)) + { + result = (int32)lua_tonumber(state, -1); + lua_pop(state, 1); + } + + if(returnValue) + *returnValue = result; + + return true; +} + +lua_State* LuaInterface::LoadLuaFile(const char* name) { + if(shutting_down) + return 0; + lua_State* state = luaL_newstate(); + luaL_openlibs(state); + if(luaL_dofile(state, name) == 0){ + RegisterFunctions(state); + return state; + } + else{ + LogError("Error loading %s (file name: '%s')", lua_tostring(state, -1), name); + lua_pop(state, 1); + lua_close(state); + } + return 0; +} + +void LuaInterface::RemoveSpell(LuaSpell* spell, bool call_remove_function, bool can_delete, string reason, bool removing_all_spells) { + if(call_remove_function){ + lua_getglobal(spell->state, "remove"); + LUASpawnWrapper* spawn_wrapper = new LUASpawnWrapper(); + spawn_wrapper->spawn = spell->caster; + AddUserDataPtr(spawn_wrapper, spawn_wrapper->spawn); + lua_pushlightuserdata(spell->state, spawn_wrapper); + if(spell->caster && (spell->initial_target || spell->caster->GetTarget())){ + spawn_wrapper = new LUASpawnWrapper(); + if(!spell->initial_target) + spawn_wrapper->spawn = spell->caster->GetTarget(); + else if(spell->caster->GetZone()) { + spawn_wrapper->spawn = spell->caster->GetZone()->GetSpawnByID(spell->initial_target); + } + else { + spawn_wrapper->spawn = nullptr; // we need it set to something or else the ptr could be loose + } + AddUserDataPtr(spawn_wrapper, spawn_wrapper->spawn); + lua_pushlightuserdata(spell->state, spawn_wrapper); + } + else + lua_pushlightuserdata(spell->state, 0); + + if (spell->caster && !spell->caster->Alive()) + reason = "dead"; + + lua_pushstring(spell->state, (char*)reason.c_str()); + + MSpells.lock(); + current_spells[spell->state] = spell; + MSpells.unlock(); + lua_pcall(spell->state, 3, 0, 0); + ResetFunctionStack(spell->state); + } + + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + if(spell->caster) { + for (int8 i = 0; i < spell->targets.size(); i++) { + if(!spell->caster->GetZone()) + continue; + + Spawn* target = spell->caster->GetZone()->GetSpawnByID(spell->targets.at(i)); + if (!target || !target->IsEntity()) + continue; + + ((Entity*)target)->RemoveProc(0, spell); + ((Entity*)target)->RemoveSpellEffect(spell); + ((Entity*)target)->RemoveSpellBonus(spell); + } + } + + multimap::iterator entries; + for(entries = spell->char_id_targets.begin(); entries != spell->char_id_targets.end(); entries++) + { + Client* tmpClient = zone_list.GetClientByCharID(entries->first); + if(tmpClient && tmpClient->GetPlayer()) + { + tmpClient->GetPlayer()->RemoveProc(0, spell); + tmpClient->GetPlayer()->RemoveSpellEffect(spell); + tmpClient->GetPlayer()->RemoveSpellBonus(spell); + } + } + spell->char_id_targets.clear(); // TODO: reach out to those clients in different + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + + if(removing_all_spells) { + if(spell->caster && spell->caster->GetZone()) { + spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(spell); + spell->caster->GetZone()->GetSpellProcess()->DeleteSpell(spell); + } + } + else { + AddPendingSpellDelete(spell); + if (spell->caster) + { + if(spell->caster->GetZone()) { + spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(spell); + } + spell->caster->RemoveProc(0, spell); + spell->caster->RemoveMaintainedSpell(spell); + + int8 spell_type = spell->spell->GetSpellData()->spell_type; + if(spell->caster->IsPlayer() && !removing_all_spells) + { + Player* player = (Player*)spell->caster; + switch(spell_type) + { + case SPELL_TYPE_FOOD: + if(player->get_character_flag(CF_FOOD_AUTO_CONSUME) && player->GetClient()) + { + Item* item = nullptr; + if(player->GetActiveFoodUniqueID()) { + item = player->item_list.GetItemFromUniqueID(player->GetActiveFoodUniqueID()); + } + if(item == nullptr) { + item = player->GetEquipmentList()->GetItem(EQ2_FOOD_SLOT); + } + if(item && player->GetClient()->CheckConsumptionAllowed(EQ2_FOOD_SLOT, false)) + player->GetClient()->ConsumeFoodDrink(item, EQ2_FOOD_SLOT); + } + break; + case SPELL_TYPE_DRINK: + if(player->get_character_flag(CF_DRINK_AUTO_CONSUME) && player->GetClient()) + { + Item* item = nullptr; + if(player->GetActiveDrinkUniqueID()) { + item = player->item_list.GetItemFromUniqueID(player->GetActiveDrinkUniqueID()); + } + if(item == nullptr) { + item = player->GetEquipmentList()->GetItem(EQ2_FOOD_SLOT); + } + + item = player->GetEquipmentList()->GetItem(EQ2_DRINK_SLOT); + if(item && player->GetClient()->CheckConsumptionAllowed(EQ2_DRINK_SLOT, false)) + player->GetClient()->ConsumeFoodDrink(item, EQ2_DRINK_SLOT); + } + break; + } + } + } + } +} + +void LuaInterface::RegisterFunctions(lua_State* state) { + lua_register(state, "SetHP", EQ2Emu_lua_SetCurrentHP); + lua_register(state, "SetMaxHP", EQ2Emu_lua_SetMaxHP); + lua_register(state, "SetMaxHPBase", EQ2Emu_lua_SetMaxHPBase); + lua_register(state, "SetPower", EQ2Emu_lua_SetCurrentPower); + lua_register(state, "SetMaxPower", EQ2Emu_lua_SetMaxPower); + lua_register(state, "SetMaxPowerBase", EQ2Emu_lua_SetMaxPowerBase); + lua_register(state, "ModifyMaxHP", EQ2Emu_lua_ModifyMaxHP); + lua_register(state, "ModifyMaxPower", EQ2Emu_lua_ModifyMaxPower); + lua_register(state, "SetPosition", EQ2Emu_lua_SetPosition); + lua_register(state, "SetHeading", EQ2Emu_lua_SetHeading); + lua_register(state, "SetModelType", EQ2Emu_lua_SetModelType); + lua_register(state, "SetAdventureClass", EQ2Emu_lua_SetAdventureClass); + lua_register(state, "SetTradeskillClass", EQ2Emu_lua_SetTradeskillClass); + lua_register(state, "SetMount", EQ2Emu_lua_SetMount); + lua_register(state, "SetMountColor", EQ2Emu_lua_SetMountColor); + lua_register(state, "GetMount", EQ2Emu_lua_GetMount); + lua_register(state, "GetRace", EQ2Emu_lua_GetRace); + lua_register(state, "GetRaceName", EQ2Emu_lua_GetRaceName); + lua_register(state, "GetClass", EQ2Emu_lua_GetClass); + lua_register(state, "GetClassName", EQ2Emu_lua_GetClassName); + lua_register(state, "GetArchetypeName", EQ2Emu_lua_GetArchetypeName); + lua_register(state, "SetSpeed", EQ2Emu_lua_SetSpeed); + lua_register(state, "ModifyPower", EQ2Emu_lua_ModifyPower); + lua_register(state, "ModifyHP", EQ2Emu_lua_ModifyHP); + + lua_register(state, "GetDistance", EQ2Emu_lua_GetDistance); + lua_register(state, "GetHeading", EQ2Emu_lua_GetHeading); + lua_register(state, "GetLevel", EQ2Emu_lua_GetLevel); + lua_register(state, "GetDifficulty", EQ2Emu_lua_GetDifficulty); + lua_register(state, "GetHP", EQ2Emu_lua_GetCurrentHP); + lua_register(state, "GetMaxHP", EQ2Emu_lua_GetMaxHP); + lua_register(state, "GetMaxHPBase", EQ2Emu_lua_GetMaxHPBase); + lua_register(state, "GetMaxPower", EQ2Emu_lua_GetMaxPower); + lua_register(state, "GetMaxPowerBase", EQ2Emu_lua_GetMaxPowerBase); + lua_register(state, "GetName", EQ2Emu_lua_GetName); + lua_register(state, "GetPower", EQ2Emu_lua_GetCurrentPower); + lua_register(state, "GetX", EQ2Emu_lua_GetX); + lua_register(state, "GetY", EQ2Emu_lua_GetY); + lua_register(state, "GetZ", EQ2Emu_lua_GetZ); + lua_register(state, "GetSpawnID", EQ2Emu_lua_GetSpawnID); + lua_register(state, "GetSpawnGroupID", EQ2Emu_lua_GetSpawnGroupID); + lua_register(state, "SetSpawnGroupID", EQ2Emu_lua_SetSpawnGroupID); + lua_register(state, "AddSpawnToGroup", EQ2Emu_lua_AddSpawnToGroup); + lua_register(state, "GetSpawnLocationID", EQ2Emu_lua_GetSpawnLocationID); + lua_register(state, "GetSpawnLocationPlacementID", EQ2Emu_lua_GetSpawnLocationPlacementID); + lua_register(state, "GetSpawnListBySpawnID", EQ2Emu_lua_GetSpawnListBySpawnID); + lua_register(state, "GetSpawnListByRailID", EQ2Emu_lua_GetSpawnListByRailID); + lua_register(state, "GetPassengerSpawnList", EQ2Emu_lua_GetPassengerSpawnList); + lua_register(state, "GetSpawnFromList", EQ2Emu_lua_GetSpawnFromList); + lua_register(state, "GetSpawnListSize", EQ2Emu_lua_GetSpawnListSize); + lua_register(state, "SetFactionID", EQ2Emu_lua_SetFactionID); + lua_register(state, "GetFactionID", EQ2Emu_lua_GetFactionID); + lua_register(state, "GetFactionAmount", EQ2Emu_lua_GetFactionAmount); + lua_register(state, "ChangeFaction", EQ2Emu_lua_ChangeFaction); + lua_register(state, "GetGender", EQ2Emu_lua_GetGender); + lua_register(state, "GetTarget", EQ2Emu_lua_GetTarget); + lua_register(state, "HasFreeSlot", EQ2Emu_lua_HasFreeSlot); + lua_register(state, "HasItemEquipped", EQ2Emu_lua_HasItemEquipped); + lua_register(state, "GetEquippedItemByID", EQ2Emu_lua_GetEquippedItemByID); + lua_register(state, "SetEquippedItemByID", EQ2Emu_lua_SetEquippedItemByID); + lua_register(state, "SetEquippedItem", EQ2Emu_lua_SetEquippedItem); + lua_register(state, "UnequipSlot", EQ2Emu_lua_UnequipSlot); + lua_register(state, "SetEquipment", EQ2Emu_lua_SetEquipment); + lua_register(state, "GetEquippedItemBySlot", EQ2Emu_lua_GetEquippedItemBySlot); + lua_register(state, "GetItemByID", EQ2Emu_lua_GetItemByID); + lua_register(state, "GetItemType", EQ2Emu_lua_GetItemType); + lua_register(state, "GetItemEffectType", EQ2Emu_lua_GetItemEffectType); + lua_register(state, "GetSpellName", EQ2Emu_lua_GetSpellName); + lua_register(state, "PerformCameraShake", EQ2Emu_lua_PerformCameraShake); + lua_register(state, "GetModelType", EQ2Emu_lua_GetModelType); + lua_register(state, "GetSpeed", EQ2Emu_lua_GetSpeed); + lua_register(state, "HasMoved", EQ2Emu_lua_HasMoved); + lua_register(state, "SpellDamage", EQ2Emu_lua_SpellDamage); + lua_register(state, "SpellDamageExt", EQ2Emu_lua_SpellDamageExt); + lua_register(state, "CastSpell", EQ2Emu_lua_CastSpell); + lua_register(state, "SpellHeal", EQ2Emu_lua_SpellHeal); + lua_register(state, "SpellHealPct", EQ2Emu_lua_SpellHealPct); + lua_register(state, "AddItem", EQ2Emu_lua_AddItem); + lua_register(state, "SummonItem", EQ2Emu_lua_SummonItem); + lua_register(state, "RemoveItem", EQ2Emu_lua_RemoveItem); + lua_register(state, "HasItem", EQ2Emu_lua_HasItem); + lua_register(state, "SpawnMob", EQ2Emu_lua_Spawn); + lua_register(state, "SummonPet", EQ2Emu_lua_SummonPet); + lua_register(state, "AddSpawnAccess", EQ2Emu_lua_AddSpawnAccess); + lua_register(state, "GetZone", EQ2Emu_lua_GetZone); + lua_register(state, "GetZoneName", EQ2Emu_lua_GetZoneName); + lua_register(state, "GetZoneID", EQ2Emu_lua_GetZoneID); + lua_register(state, "Zone", EQ2Emu_lua_Zone); + lua_register(state, "AddHate", EQ2Emu_lua_AddHate); + lua_register(state, "IsAlive", EQ2Emu_lua_IsAlive); + lua_register(state, "IsInCombat", EQ2Emu_lua_IsInCombat); + lua_register(state, "Attack", EQ2Emu_lua_Attack); + lua_register(state, "ApplySpellVisual", EQ2Emu_lua_ApplySpellVisual); + + + lua_register(state, "IsPlayer", EQ2Emu_lua_IsPlayer); + lua_register(state, "GetCharacterID", EQ2Emu_lua_GetCharacterID); + lua_register(state, "FaceTarget", EQ2Emu_lua_FaceTarget); + lua_register(state, "MoveToLocation", EQ2Emu_lua_MoveToLocation); + lua_register(state, "ClearRunningLocations", EQ2Emu_lua_ClearRunningLocations); + lua_register(state, "Shout", EQ2Emu_lua_Shout); + lua_register(state, "Say", EQ2Emu_lua_Say); + lua_register(state, "SayOOC", EQ2Emu_lua_SayOOC); + lua_register(state, "Emote", EQ2Emu_lua_Emote); + lua_register(state, "MovementLoopAddLocation", EQ2Emu_lua_MovementLoopAdd); // do not remove this function, it is already heavily used by the content team + lua_register(state, "MovementLoopAdd", EQ2Emu_lua_MovementLoopAdd); +// lua_register(state, "GetCurrentZoneSafeLocation", EQ2Emu_lua_GetCurrentZoneSafeLocation); // This is already added below. + lua_register(state, "AddTimer", EQ2Emu_lua_AddTimer); + lua_register(state, "StopTimer", EQ2Emu_lua_StopTimer); + lua_register(state, "Harvest", EQ2Emu_lua_Harvest); + lua_register(state, "SetAttackable", EQ2Emu_lua_SetAttackable); + + + lua_register(state, "AddSpellBonus", EQ2Emu_lua_AddSpellBonus); + lua_register(state, "RemoveSpellBonus", EQ2Emu_lua_RemoveSpellBonus); + lua_register(state, "AddSkillBonus", EQ2Emu_lua_AddSkillBonus); + lua_register(state, "RemoveSkillBonus", EQ2Emu_lua_RemoveSkillBonus); + lua_register(state, "AddControlEffect", EQ2Emu_lua_AddControlEffect); + lua_register(state, "RemoveControlEffect", EQ2Emu_lua_RemoveControlEffect); + lua_register(state, "HasControlEffect", EQ2Emu_lua_HasControlEffect); + + lua_register(state, "GetBaseAggroRadius", EQ2Emu_lua_GetBaseAggroRadius); + lua_register(state, "GetAggroRadius", EQ2Emu_lua_GetAggroRadius); + lua_register(state, "SetAggroRadius", EQ2Emu_lua_SetAggroRadius); + + lua_register(state, "GetCurrentZoneSafeLocation", EQ2Emu_lua_GetCurrentZoneSafeLocation); + + + lua_register(state, "SetDeity", EQ2Emu_lua_SetDeity); + lua_register(state, "GetDeity", EQ2Emu_lua_GetDeity); + + + lua_register(state, "GetInt", EQ2Emu_lua_GetInt); + lua_register(state, "GetWis", EQ2Emu_lua_GetWis); + lua_register(state, "GetSta", EQ2Emu_lua_GetSta); + lua_register(state, "GetStr", EQ2Emu_lua_GetStr); + lua_register(state, "GetAgi", EQ2Emu_lua_GetAgi); + lua_register(state, "SetInt", EQ2Emu_lua_SetInt); + lua_register(state, "SetWis", EQ2Emu_lua_SetWis); + lua_register(state, "SetSta", EQ2Emu_lua_SetSta); + lua_register(state, "SetStr", EQ2Emu_lua_SetStr); + lua_register(state, "SetAgi", EQ2Emu_lua_SetAgi); + lua_register(state, "GetIntBase", EQ2Emu_lua_GetIntBase); + lua_register(state, "GetWisBase", EQ2Emu_lua_GetWisBase); + lua_register(state, "GetStaBase", EQ2Emu_lua_GetStaBase); + lua_register(state, "GetStrBase", EQ2Emu_lua_GetStrBase); + lua_register(state, "GetAgiBase", EQ2Emu_lua_GetAgiBase); + lua_register(state, "SetIntBase", EQ2Emu_lua_SetIntBase); + lua_register(state, "SetWisBase", EQ2Emu_lua_SetWisBase); + lua_register(state, "SetStaBase", EQ2Emu_lua_SetStaBase); + lua_register(state, "SetStrBase", EQ2Emu_lua_SetStrBase); + lua_register(state, "SetAgiBase", EQ2Emu_lua_SetAgiBase); + lua_register(state, "GetSpawn", EQ2Emu_lua_GetSpawn); + lua_register(state, "GetVariableValue", EQ2Emu_lua_GetVariableValue); + lua_register(state, "GetCoinMessage", EQ2Emu_lua_GetCoinMessage); + lua_register(state, "GetSpawnByGroupID", EQ2Emu_lua_GetSpawnByGroupID); + lua_register(state, "GetSpawnByLocationID", EQ2Emu_lua_GetSpawnByLocationID); + lua_register(state, "PlayFlavor", EQ2Emu_lua_PlayFlavor); + lua_register(state, "PlayFlavorID", EQ2Emu_lua_PlayFlavorID); + lua_register(state, "PlaySound", EQ2Emu_lua_PlaySound); + lua_register(state, "PlayVoice", EQ2Emu_lua_PlayVoice); + lua_register(state, "PlayAnimation", EQ2Emu_lua_PlayAnimation); + lua_register(state, "AddLootItem", EQ2Emu_lua_AddLootItem); + lua_register(state, "HasLootItem", EQ2Emu_lua_HasLootItem); + lua_register(state, "RemoveLootItem", EQ2Emu_lua_RemoveLootItem); + lua_register(state, "AddLootCoin", EQ2Emu_lua_AddLootCoin); + lua_register(state, "GiveLoot", EQ2Emu_lua_GiveLoot); + lua_register(state, "HasPendingLootItem", EQ2Emu_lua_HasPendingLootItem); + lua_register(state, "HasPendingLoot", EQ2Emu_lua_HasPendingLoot); + lua_register(state, "SetLootCoin", EQ2Emu_lua_SetLootCoin); + lua_register(state, "HasCoin", EQ2Emu_lua_HasCoin); + lua_register(state, "GetLootCoin", EQ2Emu_lua_GetLootCoin); + lua_register(state, "SetPlayerProximityFunction", EQ2Emu_lua_SetPlayerProximityFunction); + lua_register(state, "SetLocationProximityFunction", EQ2Emu_lua_SetLocationProximityFunction); + lua_register(state, "CreateConversation", EQ2Emu_lua_CreateConversation); + lua_register(state, "AddConversationOption", EQ2Emu_lua_AddConversationOption); + lua_register(state, "StartConversation", EQ2Emu_lua_StartConversation); + lua_register(state, "CloseConversation", EQ2Emu_lua_CloseConversation); + lua_register(state, "CloseItemConversation", EQ2Emu_lua_CloseItemConversation); + //lua_register(state, "StartItemConversation", EQ2Emu_lua_StartItemConversation); + lua_register(state, "StartDialogConversation", EQ2Emu_lua_StartDialogConversation); + lua_register(state, "SendStateCommand", EQ2Emu_lua_SendStateCommand); + lua_register(state, "SpawnSet", EQ2Emu_lua_SpawnSet); + lua_register(state, "SpawnSetByDistance", EQ2Emu_lua_SpawnSetByDistance); + lua_register(state, "SpawnMove", EQ2Emu_lua_SpawnMove); + lua_register(state, "KillSpawn", EQ2Emu_lua_KillSpawn); + lua_register(state, "KillSpawnByDistance", EQ2Emu_lua_KillSpawnByDistance); + lua_register(state, "Despawn", EQ2Emu_lua_Despawn); + lua_register(state, "ChangeHandIcon", EQ2Emu_lua_ChangeHandIcon); + lua_register(state, "SetVisualFlag", EQ2Emu_lua_SetVisualFlag); + lua_register(state, "SetInfoFlag", EQ2Emu_lua_SetInfoFlag); + lua_register(state, "IsBindAllowed", EQ2Emu_lua_IsBindAllowed); + lua_register(state, "IsGateAllowed", EQ2Emu_lua_IsGateAllowed); + lua_register(state, "Bind", EQ2Emu_lua_Bind); + lua_register(state, "Gate", EQ2Emu_lua_Gate); + lua_register(state, "SendMessage", EQ2Emu_lua_SendMessage); + lua_register(state, "SendPopUpMessage", EQ2Emu_lua_SendPopUpMessage); + lua_register(state, "SetServerControlFlag", EQ2Emu_lua_SetServerControlFlag); + lua_register(state, "ToggleTracking", EQ2Emu_lua_ToggleTracking); + lua_register(state, "AddPrimaryEntityCommand", EQ2Emu_lua_AddPrimaryEntityCommand); + lua_register(state, "AddSpellBookEntry", EQ2Emu_lua_AddSpellBookEntry); + lua_register(state, "DeleteSpellBook", EQ2Emu_lua_DeleteSpellBook); + lua_register(state, "RemoveSpellBookEntry", EQ2Emu_lua_RemoveSpellBookEntry); + lua_register(state, "SendNewAdventureSpells", EQ2Emu_lua_SendNewAdventureSpells); + lua_register(state, "SendNewTradeskillSpells", EQ2Emu_lua_SendNewTradeskillSpells); + lua_register(state, "HasSpell", EQ2Emu_lua_HasSpell); + lua_register(state, "Interrupt", EQ2Emu_lua_Interrupt); + lua_register(state, "Stealth", EQ2Emu_lua_Stealth); + lua_register(state, "IsInvis", EQ2Emu_lua_IsInvis); + lua_register(state, "IsStealthed", EQ2Emu_lua_IsStealthed); + lua_register(state, "AddSpawnIDAccess", EQ2Emu_lua_AddSpawnIDAccess); + lua_register(state, "RemoveSpawnIDAccess", EQ2Emu_lua_RemoveSpawnIDAccess); + lua_register(state, "HasRecipeBook", EQ2Emu_lua_HasRecipeBook); + + lua_register(state, "SetRequiredQuest", EQ2Emu_lua_SetRequiredQuest); + lua_register(state, "SetRequiredHistory", EQ2Emu_lua_SetRequiredHistory); + lua_register(state, "SetStepComplete", EQ2Emu_lua_SetStepComplete); + lua_register(state, "AddStepProgress", EQ2Emu_lua_AddStepProgress); + lua_register(state, "UpdateQuestTaskGroupDescription", EQ2Emu_lua_UpdateQuestTaskGroupDescription); + lua_register(state, "GetTaskGroupStep", EQ2Emu_lua_GetTaskGroupStep); + lua_register(state, "GetQuestStep", EQ2Emu_lua_GetQuestStep); + lua_register(state, "QuestStepIsComplete", EQ2Emu_lua_QuestStepIsComplete); + lua_register(state, "RegisterQuest", EQ2Emu_lua_RegisterQuest); + lua_register(state, "SetQuestPrereqLevel", EQ2Emu_lua_SetQuestPrereqLevel); + lua_register(state, "AddQuestPrereqQuest", EQ2Emu_lua_AddQuestPrereqQuest); + lua_register(state, "AddQuestPrereqItem", EQ2Emu_lua_AddQuestPrereqItem); + lua_register(state, "AddQuestPrereqFaction", EQ2Emu_lua_AddQuestPrereqFaction); + lua_register(state, "AddQuestPrereqRace", EQ2Emu_lua_AddQuestPrereqRace); + lua_register(state, "AddQuestPrereqModelType", EQ2Emu_lua_AddQuestPrereqModelType); + lua_register(state, "AddQuestPrereqClass", EQ2Emu_lua_AddQuestPrereqClass); + lua_register(state, "AddQuestPrereqTradeskillLevel", EQ2Emu_lua_AddQuestPrereqTradeskillLevel); + lua_register(state, "AddQuestPrereqTradeskillClass", EQ2Emu_lua_AddQuestPrereqTradeskillClass); + lua_register(state, "AddQuestSelectableRewardItem", EQ2Emu_lua_AddQuestSelectableRewardItem); + lua_register(state, "HasQuestRewardItem", EQ2Emu_lua_HasQuestRewardItem); + lua_register(state, "AddQuestRewardItem", EQ2Emu_lua_AddQuestRewardItem); + lua_register(state, "AddQuestRewardCoin", EQ2Emu_lua_AddQuestRewardCoin); + lua_register(state, "AddQuestRewardFaction", EQ2Emu_lua_AddQuestRewardFaction); + lua_register(state, "SetQuestRewardStatus", EQ2Emu_lua_SetQuestRewardStatus); + lua_register(state, "SetStatusTmpReward", EQ2Emu_lua_SetStatusTmpReward); + lua_register(state, "SetCoinTmpReward", EQ2Emu_lua_SetCoinTmpReward); + lua_register(state, "SetQuestRewardComment", EQ2Emu_lua_SetQuestRewardComment); + lua_register(state, "SetQuestRewardExp", EQ2Emu_lua_SetQuestRewardExp); + lua_register(state, "AddQuestStepKill", EQ2Emu_lua_AddQuestStepKill); + lua_register(state, "AddQuestStepKillByRace", EQ2Emu_lua_AddQuestStepKillByRace); + lua_register(state, "AddQuestStep", EQ2Emu_lua_AddQuestStep); + lua_register(state, "AddQuestStepChat", EQ2Emu_lua_AddQuestStepChat); + lua_register(state, "AddQuestStepObtainItem", EQ2Emu_lua_AddQuestStepObtainItem); + lua_register(state, "AddQuestStepZoneLoc", EQ2Emu_lua_AddQuestStepZoneLoc); + lua_register(state, "AddQuestStepLocation", EQ2Emu_lua_AddQuestStepLocation); + lua_register(state, "AddQuestStepSpell", EQ2Emu_lua_AddQuestStepSpell); + lua_register(state, "AddQuestStepCraft", EQ2Emu_lua_AddQuestStepCraft); + lua_register(state, "AddQuestStepHarvest", EQ2Emu_lua_AddQuestStepHarvest); + lua_register(state, "AddQuestStepCompleteAction", EQ2Emu_lua_AddQuestStepCompleteAction); + lua_register(state, "AddQuestStepProgressAction", EQ2Emu_lua_AddQuestStepProgressAction); + lua_register(state, "SetQuestCompleteAction", EQ2Emu_lua_SetQuestCompleteAction); + lua_register(state, "GiveQuestReward", EQ2Emu_lua_GiveQuestReward); + lua_register(state, "UpdateQuestStepDescription", EQ2Emu_lua_UpdateQuestStepDescription); + lua_register(state, "UpdateQuestDescription", EQ2Emu_lua_UpdateQuestDescription); + lua_register(state, "UpdateQuestZone", EQ2Emu_lua_UpdateQuestZone); + lua_register(state, "SetCompletedDescription", EQ2Emu_lua_SetCompletedDescription); + lua_register(state, "OfferQuest", EQ2Emu_lua_OfferQuest); + lua_register(state, "ProvidesQuest", EQ2Emu_lua_ProvidesQuest); + lua_register(state, "HasQuest", EQ2Emu_lua_HasQuest); + lua_register(state, "HasCompletedQuest", EQ2Emu_lua_HasCompletedQuest); + lua_register(state, "QuestIsComplete", EQ2Emu_lua_QuestIsComplete); + lua_register(state, "QuestReturnNPC", EQ2Emu_lua_QuestReturnNPC); + lua_register(state, "GetQuest", EQ2Emu_lua_GetQuest); + lua_register(state, "HasCollectionsToHandIn", EQ2Emu_lua_HasCollectionsToHandIn); + lua_register(state, "HandInCollections", EQ2Emu_lua_HandInCollections); + lua_register(state, "UseWidget", EQ2Emu_lua_UseWidget); + lua_register(state, "SetSpellList", EQ2Emu_lua_SetSpellList); + lua_register(state, "GetPet", EQ2Emu_lua_GetPet); + lua_register(state, "Charm", EQ2Emu_lua_Charm); + lua_register(state, "GetGroup", EQ2Emu_lua_GetGroup); + lua_register(state, "SetCompleteFlag", EQ2Emu_lua_SetCompleteFlag); + lua_register(state, "SetQuestYellow", EQ2Emu_lua_SetQuestYellow); + lua_register(state, "CanReceiveQuest", EQ2Emu_lua_CanReceiveQuest); + lua_register(state, "AddTransportSpawn", EQ2Emu_lua_AddTransportSpawn); + lua_register(state, "IsTransportSpawn", EQ2Emu_lua_IsTransportSpawn); + + // Option window + lua_register(state, "CreateOptionWindow", EQ2Emu_lua_CreateOptionWindow); + lua_register(state, "AddOptionWindowOption", EQ2Emu_lua_AddOptionWindowOption); + lua_register(state, "SendOptionWindow", EQ2Emu_lua_SendOptionWindow); + + lua_register(state, "GetTradeskillClass", EQ2Emu_lua_GetTradeskillClass); + lua_register(state, "GetTradeskillLevel", EQ2Emu_lua_GetTradeskillLevel); + lua_register(state, "GetTradeskillClassName", EQ2Emu_lua_GetTradeskillClassName); + lua_register(state, "SetTradeskillLevel", EQ2Emu_lua_SetTradeskillLevel); + + lua_register(state, "SummonDeityPet", EQ2Emu_lua_SummonDeityPet); + lua_register(state, "SummonCosmeticPet", EQ2Emu_lua_SummonCosmeticPet); + lua_register(state, "DismissPet", EQ2Emu_lua_DismissPet); + + lua_register(state, "GetCharmedPet", EQ2Emu_lua_GetCharmedPet); + lua_register(state, "GetDeityPet", EQ2Emu_lua_GetDeityPet); + lua_register(state, "GetCosmeticPet", EQ2Emu_lua_GetCosmeticPet); + + lua_register(state, "SetQuestFeatherColor", EQ2Emu_lua_SetQuestFeatherColor); + lua_register(state, "RemoveSpawnAccess", EQ2Emu_lua_RemoveSpawnAccess); + lua_register(state, "SpawnByLocationID", EQ2Emu_lua_SpawnByLocationID); + lua_register(state, "CastEntityCommand", EQ2Emu_lua_CastEntityCommand); + lua_register(state, "SetLuaBrain", EQ2Emu_lua_SetLuaBrain); + lua_register(state, "SetBrainTick", EQ2Emu_lua_SetBrainTick); + lua_register(state, "SetFollowTarget", EQ2Emu_lua_SetFollowTarget); + lua_register(state, "GetFollowTarget", EQ2Emu_lua_GetFollowTarget); + lua_register(state, "ToggleFollow", EQ2Emu_lua_ToggleFollow); + lua_register(state, "IsFollowing", EQ2Emu_lua_IsFollowing); + lua_register(state, "SetTempVariable", EQ2Emu_lua_SetTempVariable); + lua_register(state, "GetTempVariable", EQ2Emu_lua_GetTempVariable); + lua_register(state, "GiveQuestItem", EQ2Emu_lua_GiveQuestItem); + lua_register(state, "SetQuestRepeatable", EQ2Emu_lua_SetQuestRepeatable); + + lua_register(state, "AddWaypoint", EQ2Emu_lua_AddWaypoint); + lua_register(state, "RemoveWaypoint", EQ2Emu_lua_RemoveWaypoint); + lua_register(state, "SendWaypoints", EQ2Emu_lua_SendWaypoints); + + lua_register(state, "AddWard", EQ2Emu_lua_AddWard); + lua_register(state, "AddToWard", EQ2Emu_lua_AddToWard); + lua_register(state, "RemoveWard", EQ2Emu_lua_RemoveWard); + lua_register(state, "GetWardAmountLeft", EQ2Emu_lua_GetWardAmountLeft); + lua_register(state, "GetWardValue", EQ2Emu_lua_GetWardValue); + + lua_register(state, "SetTarget", EQ2Emu_lua_SetTarget); + lua_register(state, "IsPet", EQ2Emu_lua_IsPet); + lua_register(state, "GetOwner", EQ2Emu_lua_GetOwner); + lua_register(state, "SetInCombat", EQ2Emu_lua_SetInCombat); + lua_register(state, "CompareSpawns", EQ2Emu_lua_CompareSpawns); + lua_register(state, "ClearRunback", EQ2Emu_lua_ClearRunback); + lua_register(state, "Runback", EQ2Emu_lua_Runback); + lua_register(state, "GetRunbackDistance", EQ2Emu_lua_GetRunbackDistance); + lua_register(state, "IsCasting", EQ2Emu_lua_IsCasting); + lua_register(state, "IsMezzed", EQ2Emu_lua_IsMezzed); + lua_register(state, "IsStunned", EQ2Emu_lua_IsStunned); + lua_register(state, "IsMezzedOrStunned", EQ2Emu_lua_IsMezzedOrStunned); + lua_register(state, "ProcessSpell", EQ2Emu_lua_ProcessSpell); + lua_register(state, "ProcessMelee", EQ2Emu_lua_ProcessMelee); + lua_register(state, "HasRecovered", EQ2Emu_lua_HasRecovered); + lua_register(state, "GetEncounterSize", EQ2Emu_lua_GetEncounterSize); + lua_register(state, "GetMostHated", EQ2Emu_lua_GetMostHated); + lua_register(state, "ClearHate", EQ2Emu_lua_ClearHate); + lua_register(state, "ClearEncounter", EQ2Emu_lua_ClearEncounter); + lua_register(state, "GetEncounter", EQ2Emu_lua_GetEncounter); + lua_register(state, "GetHateList", EQ2Emu_lua_GetHateList); + lua_register(state, "HasGroup", EQ2Emu_lua_HasGroup); + lua_register(state, "HasSpellEffect", EQ2Emu_lua_HasSpellEffect); + + lua_register(state, "SetSuccessTimer", EQ2Emu_lua_SetSuccessTimer); + lua_register(state, "SetFailureTimer", EQ2Emu_lua_SetFailureTimer); + lua_register(state, "IsGroundSpawn", EQ2Emu_lua_IsGroundSpawn); + lua_register(state, "CanHarvest", EQ2Emu_lua_CanHarvest); + lua_register(state, "SummonDumbFirePet", EQ2Emu_lua_SummonDumbFirePet); + + lua_register(state, "GetSkillValue", EQ2Emu_lua_GetSkillValue); + lua_register(state, "GetSkillMaxValue", EQ2Emu_lua_GetSkillMaxValue); + lua_register(state, "GetSkillName", EQ2Emu_lua_GetSkillName); + lua_register(state, "SetSkillMaxValue", EQ2Emu_lua_SetSkillMaxValue); + lua_register(state, "SetSkillValue", EQ2Emu_lua_SetSkillValue); + lua_register(state, "GetSkill", EQ2Emu_lua_GetSkill); + lua_register(state, "GetSkillIDByName", EQ2Emu_lua_GetSkillIDByName); + lua_register(state, "HasSkill", EQ2Emu_lua_HasSkill); + lua_register(state, "AddSkill", EQ2Emu_lua_AddSkill); + lua_register(state, "IncreaseSkillCapsByType", EQ2Emu_lua_IncreaseSkillCapsByType); + lua_register(state, "RemoveSkill", EQ2Emu_lua_RemoveSkill); + lua_register(state, "AddProc", EQ2Emu_lua_AddProc); + lua_register(state, "AddProcExt", EQ2Emu_lua_AddProcExt); + lua_register(state, "RemoveProc", EQ2Emu_lua_RemoveProc); + lua_register(state, "Knockback", EQ2Emu_lua_Knockback); + + lua_register(state, "IsEpic", EQ2Emu_lua_IsEpic); + lua_register(state, "IsHeroic", EQ2Emu_lua_IsHeroic); + lua_register(state, "ProcDamage", EQ2Emu_lua_ProcDamage); + lua_register(state, "LastSpellAttackHit", EQ2Emu_lua_LastSpellAttackHit); + lua_register(state, "IsBehind", EQ2Emu_lua_IsBehind); + lua_register(state, "IsFlanking", EQ2Emu_lua_IsFlanking); + lua_register(state, "InFront", EQ2Emu_lua_InFront); + lua_register(state, "AddSpellTimer", EQ2Emu_lua_AddSpellTimer); + lua_register(state, "GetItemCount", EQ2Emu_lua_GetItemCount); + lua_register(state, "SetItemCount", EQ2Emu_lua_SetItemCount); + lua_register(state, "Resurrect", EQ2Emu_lua_Resurrect); + lua_register(state, "BreatheUnderwater", EQ2Emu_lua_BreatheUnderwater); + lua_register(state, "BlurVision", EQ2Emu_lua_BlurVision); + lua_register(state, "SetVision", EQ2Emu_lua_SetVision); + lua_register(state, "GetItemSkillReq", EQ2Emu_lua_GetItemSkillReq); + lua_register(state, "SetSpeedMultiplier", EQ2Emu_lua_SetSpeedMultiplier); + lua_register(state, "SetIllusion", EQ2Emu_lua_SetIllusion); + lua_register(state, "ResetIllusion", EQ2Emu_lua_ResetIllusion); + lua_register(state, "AddThreatTransfer", EQ2Emu_lua_AddThreatTransfer); + lua_register(state, "RemoveThreatTransfer", EQ2Emu_lua_RemoveThreatTransfer); + lua_register(state, "CureByType", EQ2Emu_lua_CureByType); + lua_register(state, "CureByControlEffect", EQ2Emu_lua_CureByControlEffect); + lua_register(state, "AddSpawnSpellBonus", EQ2Emu_lua_AddSpawnSpellBonus); + lua_register(state, "RemoveSpawnSpellBonus", EQ2Emu_lua_RemoveSpawnSpellBonus); + lua_register(state, "CancelSpell", EQ2Emu_lua_CancelSpell); + lua_register(state, "RemoveStealth", EQ2Emu_lua_RemoveStealth); + lua_register(state, "RemoveInvis", EQ2Emu_lua_RemoveInvis); + lua_register(state, "StartHeroicOpportunity", EQ2Emu_lua_StartHeroicOpportunity); + lua_register(state, "CopySpawnAppearance", EQ2Emu_lua_CopySpawnAppearance); + lua_register(state, "SetSpellTriggerCount", EQ2Emu_lua_SetSpellTriggerCount); + lua_register(state, "GetSpellTriggerCount", EQ2Emu_lua_GetSpellTriggerCount); + lua_register(state, "RemoveTriggerFromSpell", EQ2Emu_lua_RemoveTriggerFromSpell); + lua_register(state, "HasSpellImmunity", EQ2Emu_lua_HasSpellImmunity); + lua_register(state, "AddImmunitySpell", EQ2Emu_lua_AddImmunitySpell); + lua_register(state, "RemoveImmunitySpell", EQ2Emu_lua_RemoveImmunitySpell); + lua_register(state, "SetSpellSnareValue", EQ2Emu_lua_SetSpellSnareValue); + lua_register(state, "CheckRaceType", EQ2Emu_lua_CheckRaceType); + lua_register(state, "GetRaceType", EQ2Emu_lua_GetRaceType); + lua_register(state, "GetRaceBaseType", EQ2Emu_lua_GetRaceBaseType); + lua_register(state, "GetQuestFlags", EQ2Emu_lua_GetQuestFlags); + lua_register(state, "SetQuestFlags", EQ2Emu_lua_SetQuestFlags); + lua_register(state, "SetQuestTimer", EQ2Emu_lua_SetQuestTimer); + lua_register(state, "RemoveQuestStep", EQ2Emu_lua_RemoveQuestStep); + lua_register(state, "ResetQuestStep", EQ2Emu_lua_ResetQuestStep); + lua_register(state, "SetQuestTimerComplete", EQ2Emu_lua_SetQuestTimerComplete); + lua_register(state, "AddQuestStepFailureAction", EQ2Emu_lua_AddQuestStepFailureAction); + lua_register(state, "SetStepFailed", EQ2Emu_lua_SetStepFailed); + lua_register(state, "GetQuestCompleteCount", EQ2Emu_lua_GetQuestCompleteCount); + lua_register(state, "SetServerVariable", EQ2Emu_lua_SetServerVariable); + lua_register(state, "GetServerVariable", EQ2Emu_lua_GetServerVariable); + lua_register(state, "HasLanguage", EQ2Emu_lua_HasLanguage); + lua_register(state, "AddLanguage", EQ2Emu_lua_AddLanguage); + lua_register(state, "IsNight", EQ2Emu_lua_IsNight); + lua_register(state, "AddMultiFloorLift", EQ2Emu_lua_AddMultiFloorLift); + lua_register(state, "StartAutoMount", EQ2Emu_lua_StartAutoMount); + lua_register(state, "EndAutoMount", EQ2Emu_lua_EndAutoMount); + lua_register(state, "IsOnAutoMount", EQ2Emu_lua_IsOnAutoMount); + lua_register(state, "SetPlayerHistory", EQ2Emu_lua_SetPlayerHistory); + lua_register(state, "GetPlayerHistory", EQ2Emu_lua_GetPlayerHistory); + lua_register(state, "SetGridID", EQ2Emu_lua_SetGridID); + lua_register(state, "GetQuestStepProgress", EQ2Emu_lua_GetQuestStepProgress); + lua_register(state, "SetPlayerLevel", EQ2Emu_lua_SetPlayerLevel); + lua_register(state, "AddCoin", EQ2Emu_lua_AddCoin); + lua_register(state, "RemoveCoin", EQ2Emu_lua_RemoveCoin); + lua_register(state, "GetPlayersInZone", EQ2Emu_lua_GetPlayersInZone); + lua_register(state, "SpawnGroupByID", EQ2Emu_lua_SpawnGroupByID); + lua_register(state, "SetSpawnAnimation", EQ2Emu_lua_SetSpawnAnimation); + lua_register(state, "GetClientVersion", EQ2Emu_lua_GetClientVersion); + lua_register(state, "GetItemID", EQ2Emu_lua_GetItemID); + lua_register(state, "IsEntity", EQ2Emu_lua_IsEntity); + lua_register(state, "GetOrigX", EQ2Emu_lua_GetOrigX); + lua_register(state, "GetOrigY", EQ2Emu_lua_GetOrigY); + lua_register(state, "GetOrigZ", EQ2Emu_lua_GetOrigZ); + lua_register(state, "GetPCTOfHP", EQ2Emu_lua_GetPCTOfHP); + lua_register(state, "GetPCTOfPower", EQ2Emu_lua_GetPCTOfPower); + lua_register(state, "GetBoundZoneID", EQ2Emu_lua_GetBoundZoneID); + lua_register(state, "Evac", EQ2Emu_lua_Evac); + lua_register(state, "GetSpellTier", EQ2Emu_lua_GetSpellTier); + lua_register(state, "GetSpellID", EQ2Emu_lua_GetSpellID); + lua_register(state, "StartTransmute", EQ2Emu_lua_StartTransmute); + lua_register(state, "CompleteTransmute", EQ2Emu_lua_CompleteTransmute); + lua_register(state, "ProcHate", EQ2Emu_lua_ProcHate); + + lua_register(state, "GetRandomSpawnByID", EQ2Emu_lua_GetRandomSpawnByID); + lua_register(state, "ShowLootWindow", EQ2Emu_lua_ShowLootWindow); + lua_register(state, "AddPrimaryEntityCommandAllSpawns", EQ2Emu_lua_AddPrimaryEntityCommandAllSpawns); + lua_register(state, "InstructionWindow", EQ2Emu_lua_InstructionWindow); + lua_register(state, "InstructionWindowClose", EQ2Emu_lua_InstructionWindowClose); + lua_register(state, "InstructionWindowGoal", EQ2Emu_lua_InstructionWindowGoal); + lua_register(state, "ShowWindow", EQ2Emu_lua_ShowWindow); + lua_register(state, "FlashWindow", EQ2Emu_lua_FlashWindow); + lua_register(state, "EnableGameEvent", EQ2Emu_lua_EnableGameEvent); + lua_register(state, "GetTutorialStep", EQ2Emu_lua_GetTutorialStep); + lua_register(state, "SetTutorialStep", EQ2Emu_lua_SetTutorialStep); + lua_register(state, "DisplayText", EQ2Emu_lua_DisplayText); + lua_register(state, "GiveExp", EQ2Emu_lua_GiveExp); + + lua_register(state, "CheckLOS", EQ2Emu_lua_CheckLOS); + lua_register(state, "CheckLOSByCoordinates", EQ2Emu_lua_CheckLOSByCoordinates); + + lua_register(state, "SetZoneExpansionFlag", EQ2Emu_lua_SetZoneExpansionFlag); + lua_register(state, "GetZoneExpansionFlag", EQ2Emu_lua_GetZoneExpansionFlag); + lua_register(state, "SetZoneHolidayFlag", EQ2Emu_lua_SetZoneHolidayFlag); + lua_register(state, "GetZoneHolidayFlag", EQ2Emu_lua_GetZoneHolidayFlag); + + lua_register(state, "SetCanBind", EQ2Emu_lua_SetCanBind); + lua_register(state, "GetCanBind", EQ2Emu_lua_GetCanBind); + + lua_register(state, "GetCanGate", EQ2Emu_lua_GetCanGate); + lua_register(state, "SetCanGate", EQ2Emu_lua_SetCanGate); + + lua_register(state, "SetCanEvac", EQ2Emu_lua_SetCanEvac); + lua_register(state, "GetCanEvac", EQ2Emu_lua_GetCanEvac); + + lua_register(state, "AddSpawnProximity", EQ2Emu_lua_AddSpawnProximity); + + lua_register(state, "CanSeeInvis", EQ2Emu_lua_CanSeeInvis); + lua_register(state, "SetSeeInvis", EQ2Emu_lua_SetSeeInvis); + lua_register(state, "SetSeeHide", EQ2Emu_lua_SetSeeHide); + + lua_register(state, "SetAccessToEntityCommand", EQ2Emu_lua_SetAccessToEntityCommand); + lua_register(state, "SetAccessToEntityCommandByCharID", EQ2Emu_lua_SetAccessToEntityCommandByCharID); + lua_register(state, "RemovePrimaryEntityCommand", EQ2Emu_lua_RemovePrimaryEntityCommand); + lua_register(state, "SendUpdateDefaultCommand", EQ2Emu_lua_SendUpdateDefaultCommand); + + lua_register(state, "SendTransporters", EQ2Emu_lua_SendTransporters); + lua_register(state, "SetTemporaryTransportID", EQ2Emu_lua_SetTemporaryTransportID); + lua_register(state, "GetTemporaryTransportID", EQ2Emu_lua_GetTemporaryTransportID); + + lua_register(state, "SetAlignment", EQ2Emu_lua_SetAlignment); + lua_register(state, "GetAlignment", EQ2Emu_lua_GetAlignment); + + lua_register(state, "GetSpell", EQ2Emu_lua_GetSpell); + lua_register(state, "GetSpellData", EQ2Emu_lua_GetSpellData); + lua_register(state, "SetSpellData", EQ2Emu_lua_SetSpellData); + lua_register(state, "CastCustomSpell", EQ2Emu_lua_CastCustomSpell); + + lua_register(state, "SetSpellDataIndex", EQ2Emu_lua_SetSpellDataIndex); + lua_register(state, "GetSpellDataIndex", EQ2Emu_lua_GetSpellDataIndex); + + lua_register(state, "SetSpellDisplayEffect", EQ2Emu_lua_SetSpellDisplayEffect); + lua_register(state, "GetSpellDisplayEffect", EQ2Emu_lua_GetSpellDisplayEffect); + + lua_register(state, "InWater", EQ2Emu_lua_InWater); + lua_register(state, "InLava", EQ2Emu_lua_InLava); + + lua_register(state, "DamageSpawn", EQ2Emu_lua_DamageSpawn); + lua_register(state, "IsInvulnerable", EQ2Emu_lua_IsInvulnerable); + lua_register(state, "SetInvulnerable", EQ2Emu_lua_SetInvulnerable); + + lua_register(state, "GetRuleFlagBool", EQ2Emu_lua_GetRuleFlagBool); + lua_register(state, "GetRuleFlagInt32", EQ2Emu_lua_GetRuleFlagInt32); + lua_register(state, "GetRuleFlagFloat", EQ2Emu_lua_GetRuleFlagFloat); + + lua_register(state, "GetAAInfo", EQ2Emu_lua_GetAAInfo); + lua_register(state, "SetAAInfo", EQ2Emu_lua_SetAAInfo); + + lua_register(state, "AddMasterTitle", EQ2Emu_lua_AddMasterTitle); + lua_register(state, "AddCharacterTitle", EQ2Emu_lua_AddCharacterTitle); + lua_register(state, "SetCharacterTitleSuffix", EQ2Emu_lua_SetCharacterTitleSuffix); + lua_register(state, "SetCharacterTitlePrefix", EQ2Emu_lua_SetCharacterTitlePrefix); + lua_register(state, "ResetCharacterTitleSuffix", EQ2Emu_lua_ResetCharacterTitleSuffix); + lua_register(state, "ResetCharacterTitlePrefix", EQ2Emu_lua_ResetCharacterTitlePrefix); + + lua_register(state, "GetInfoStructString", EQ2Emu_lua_GetInfoStructString); + lua_register(state, "GetInfoStructUInt", EQ2Emu_lua_GetInfoStructUInt); + lua_register(state, "GetInfoStructSInt", EQ2Emu_lua_GetInfoStructSInt); + lua_register(state, "GetInfoStructFloat", EQ2Emu_lua_GetInfoStructFloat); + + lua_register(state, "SetInfoStructString", EQ2Emu_lua_SetInfoStructString); + lua_register(state, "SetInfoStructUInt", EQ2Emu_lua_SetInfoStructUInt); + lua_register(state, "SetInfoStructSInt", EQ2Emu_lua_SetInfoStructSInt); + lua_register(state, "SetInfoStructFloat", EQ2Emu_lua_SetInfoStructFloat); + + lua_register(state, "SetCharSheetChanged", EQ2Emu_lua_SetCharSheetChanged); + + lua_register(state, "AddPlayerMail", EQ2Emu_lua_AddPlayerMail); + lua_register(state, "AddPlayerMailByCharID", EQ2Emu_lua_AddPlayerMailByCharID); + + lua_register(state, "OpenDoor", EQ2Emu_lua_OpenDoor); + lua_register(state, "CloseDoor", EQ2Emu_lua_CloseDoor); + lua_register(state, "IsOpen", EQ2Emu_lua_IsOpen); + + lua_register(state, "MakeRandomInt", EQ2Emu_lua_MakeRandomInt); + lua_register(state, "MakeRandomFloat", EQ2Emu_lua_MakeRandomFloat); + + lua_register(state, "AddIconValue", EQ2Emu_lua_AddIconValue); + lua_register(state, "RemoveIconValue", EQ2Emu_lua_RemoveIconValue); + + lua_register(state, "GetShardID", EQ2Emu_lua_GetShardID); + lua_register(state, "GetShardCharID", EQ2Emu_lua_GetShardCharID); + lua_register(state, "GetShardCreatedTimestamp", EQ2Emu_lua_GetShardCreatedTimestamp); + lua_register(state, "DeleteDBShardID", EQ2Emu_lua_DeleteDBShardID); + + lua_register(state, "PauseMovement", EQ2Emu_lua_PauseMovement); + lua_register(state, "StopMovement", EQ2Emu_lua_StopMovement); + + lua_register(state, "GetArrowColor", EQ2Emu_lua_GetArrowColor); + lua_register(state, "GetTSArrowColor", EQ2Emu_lua_GetTSArrowColor); + + lua_register(state, "GetSpawnByRailID", EQ2Emu_lua_GetSpawnByRailID); + lua_register(state, "SetRailID", EQ2Emu_lua_SetRailID); + lua_register(state, "IsZoneLoading", EQ2Emu_lua_IsZoneLoading); + lua_register(state, "IsRunning", EQ2Emu_lua_IsRunning); + + lua_register(state, "GetZoneLockoutTimer", EQ2Emu_lua_GetZoneLockoutTimer); + + lua_register(state, "SetWorldTime", EQ2Emu_lua_SetWorldTime); + lua_register(state, "GetWorldTimeYear", EQ2Emu_lua_GetWorldTimeYear); + lua_register(state, "GetWorldTimeMonth", EQ2Emu_lua_GetWorldTimeMonth); + lua_register(state, "GetWorldTimeHour", EQ2Emu_lua_GetWorldTimeHour); + lua_register(state, "GetWorldTimeMinute", EQ2Emu_lua_GetWorldTimeMinute); + lua_register(state, "SendTimeUpdate", EQ2Emu_lua_SendTimeUpdate); + + lua_register(state, "GetLootTier", EQ2Emu_lua_GetLootTier); + lua_register(state, "SetLootTier", EQ2Emu_lua_SetLootTier); + lua_register(state, "GetLootDropType", EQ2Emu_lua_GetLootDropType); + lua_register(state, "SetLootDropType", EQ2Emu_lua_SetLootDropType); + + lua_register(state, "DamageEquippedItems", EQ2Emu_lua_DamageEquippedItems); + + lua_register(state, "CreateWidgetRegion", EQ2Emu_lua_CreateWidgetRegion); + lua_register(state, "RemoveRegion", EQ2Emu_lua_RemoveRegion); + + lua_register(state, "SetPlayerPOVGhost", EQ2Emu_lua_SetPlayerPOVGhost); + + lua_register(state, "SetCastOnAggroComplete", EQ2Emu_lua_SetCastOnAggroComplete); + lua_register(state, "IsCastOnAggroComplete", EQ2Emu_lua_IsCastOnAggroComplete); + + lua_register(state, "AddRecipeBookToPlayer", EQ2Emu_lua_AddRecipeBookToPlayer); + lua_register(state, "RemoveRecipeFromPlayer", EQ2Emu_lua_RemoveRecipeFromPlayer); + + lua_register(state, "ReplaceWidgetFromClient", EQ2Emu_lua_ReplaceWidgetFromClient); + lua_register(state, "RemoveWidgetFromSpawnMap", EQ2Emu_lua_RemoveWidgetFromSpawnMap); + lua_register(state, "RemoveWidgetFromZoneMap", EQ2Emu_lua_RemoveWidgetFromZoneMap); + + lua_register(state, "SendHearCast", EQ2Emu_lua_SendHearCast); + + lua_register(state, "GetCharacterFlag", EQ2Emu_lua_GetCharacterFlag); + lua_register(state, "ToggleCharacterFlag", EQ2Emu_lua_ToggleCharacterFlag); + + lua_register(state, "GetSpellInitialTarget", EQ2Emu_lua_GetSpellInitialTarget); +} + +void LuaInterface::LogError(const char* error, ...) { + va_list argptr; + char buffer[4096]; + + va_start(argptr, error); + vsnprintf(buffer, sizeof(buffer), error, argptr); + va_end(argptr); + SimpleLogError(buffer); +} + +void LuaInterface::SimpleLogError(const char* error) { + ProcessErrorMessage(error); + LogWrite(LUA__ERROR, 0, "LUA", "%s", error); +} + +void LuaInterface::ResetFunctionStack(lua_State* state) { + lua_settop(state, 0); +} + +void LuaInterface::AddUserDataPtr(LUAUserData* data, void* data_ptr) { + std::unique_lock lock(MLUAUserData); + if(data_ptr) { + user_data_ptr[data_ptr] = data; + } + user_data[data] = Timer::GetCurrentTime2() + 300000; //allow a function to use this pointer for 5 minutes +} + +void LuaInterface::DeletePendingSpells(bool all) { + MSpells.lock(); + MSpellDelete.lock(); + if (spells_pending_delete.size() > 0) { + int32 time = Timer::GetCurrentTime2(); + map::iterator itr; + vector tmp_deletes; + vector::iterator del_itr; + for (itr = spells_pending_delete.begin(); itr != spells_pending_delete.end(); itr++) { + if (all || time >= itr->second) + tmp_deletes.push_back(itr->first); + } + LuaSpell* spell = 0; + for (del_itr = tmp_deletes.begin(); del_itr != tmp_deletes.end(); del_itr++) { + spell = *del_itr; + + if(!all) { + if (spell->caster && spell->caster->GetZone()) { + spell->caster->GetZone()->GetSpellProcess()->DeleteActiveSpell(spell); + } + else if(spell->targets.size() > 0 && spell->caster && spell->caster->GetZone()) { + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int8 i = 0; i < spell->targets.size(); i++) { + Spawn* target = spell->caster->GetZone()->GetSpawnByID(spell->targets.at(i)); + if (!target || !target->IsEntity()) + continue; + target->GetZone()->GetSpellProcess()->DeleteActiveSpell(spell); + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + } + + spells_pending_delete.erase(spell); + + if (spell->spell->IsCopiedSpell()) + { + RemoveCustomSpell(spell->spell->GetSpellID()); + safe_delete(spell->spell); + } + + SetLuaUserDataStale(spell); + RemoveCurrentSpell(spell->state, false); + safe_delete(spell); + } + } + MSpellDelete.unlock(); + MSpells.unlock(); +} + +void LuaInterface::DeletePendingSpell(LuaSpell* spell) { + MSpellDelete.lock(); + if (spells_pending_delete.size() > 0) { + map::iterator itr = spells_pending_delete.find(spell); + if (itr != spells_pending_delete.end()) + spells_pending_delete.erase(itr); + } + MSpellDelete.unlock(); +} + +void LuaInterface::DeleteUserDataPtrs(bool all) { + std::unique_lock lock(MLUAUserData); + if(user_data.size() > 0){ + map::iterator itr; + int32 time = Timer::GetCurrentTime2(); + vector tmp_deletes; + vector::iterator del_itr; + for(itr = user_data.begin(); itr != user_data.end(); itr++){ + if(all || time >= itr->second) + tmp_deletes.push_back(itr->first); + } + LUAUserData* data = 0; + for(del_itr = tmp_deletes.begin(); del_itr != tmp_deletes.end(); del_itr++){ + data = *del_itr; + + void* target = 0; + if(data->IsConversationOption()) { + target = data->conversation_options; + } + else if(data->IsOptionWindow()) { + target = data->option_window_option; + } + else if(data->IsSpawn()) { + target = data->spawn; + } + else if(data->IsQuest()) { + target = data->quest; + } + else if(data->IsZone()) { + target = data->zone; + } + else if(data->IsItem()) { + target = data->item; + } + else if(data->IsSkill()) { + target = data->skill; + } + else if(data->IsSpell()) { + target = data->spell; + } + if(target) { + std::map::iterator itr = user_data_ptr.find(target); + if(itr != user_data_ptr.end()) { + user_data_ptr.erase(itr); + } + } + user_data.erase(data); + safe_delete(data); + } + } +} + +Spawn* LuaInterface::GetSpawn(lua_State* state, int8 arg_num) { + std::shared_lock lock(MLUAUserData); + Spawn* ret = 0; + if (lua_islightuserdata(state, arg_num)){ + LUAUserData* data = (LUAUserData*)lua_touserdata(state, arg_num); + if(!data || !data->IsCorrectlyInitialized()){ + LogError("%s: GetSpawn error while processing %s", GetScriptName(state), lua_tostring(state, -1)); + } + else if(!data->IsSpawn()){ + lua_Debug ar; + lua_getstack (state, 1, &ar); + lua_getinfo(state, "Sln", &ar); + LogError("%s: Invalid data type used for GetSpawn in %s (line %d)", GetScriptName(state), ar.source, ar.currentline); + } + else + ret = data->spawn; + } + return ret; +} + +vector* LuaInterface::GetConversation(lua_State* state, int8 arg_num) { + std::shared_lock lock(MLUAUserData); + vector* ret = 0; + if(lua_islightuserdata(state, arg_num)){ + LUAUserData* data = (LUAUserData*)lua_touserdata(state, arg_num); + if(!data || !data->IsCorrectlyInitialized()){ + LogError("%s: GetConversation error while processing %s", GetScriptName(state), lua_tostring(state, -1)); + } + else if(!data->IsConversationOption()){ + lua_Debug ar; + lua_getstack (state, 1, &ar); + lua_getinfo(state, "Sln", &ar); + LogError("%s: Invalid data type used for GetConversation in %s (line %d)", GetScriptName(state), ar.source, ar.currentline); + } + else + ret = data->conversation_options; + } + return ret; +} + +vector* LuaInterface::GetOptionWindow(lua_State* state, int8 arg_num) { + std::shared_lock lock(MLUAUserData); + vector* ret = 0; + if(lua_islightuserdata(state, arg_num)){ + LUAUserData* data = (LUAUserData*)lua_touserdata(state, arg_num); + if(!data || !data->IsCorrectlyInitialized()){ + LogError("%s: GetOptionWindow error while processing %s", GetScriptName(state), lua_tostring(state, -1)); + } + else if(!data->IsOptionWindow()){ + lua_Debug ar; + lua_getstack (state, 1, &ar); + lua_getinfo(state, "Sln", &ar); + LogError("%s: Invalid data type used for GetOptionWindow in %s (line %d)", GetScriptName(state), ar.source, ar.currentline); + } + else + ret = data->option_window_option; + } + return ret; +} + +Quest* LuaInterface::GetQuest(lua_State* state, int8 arg_num) { + std::shared_lock lock(MLUAUserData); + Quest* ret = 0; + if(lua_islightuserdata(state, arg_num)){ + LUAUserData* data = (LUAUserData*)lua_touserdata(state, arg_num); + if(!data || !data->IsCorrectlyInitialized()){ + LogError("%s: GetQuest error while processing %s", GetScriptName(state), lua_tostring(state, -1)); + } + else if(!data->IsQuest()){ + lua_Debug ar; + lua_getstack (state, 1, &ar); + lua_getinfo(state, "Sln", &ar); + LogError("%s: Invalid data type used for GetQuest in %s (line %d)", GetScriptName(state), ar.source, ar.currentline); + } + else + ret = data->quest; + } + return ret; +} + +Item* LuaInterface::GetItem(lua_State* state, int8 arg_num) { + std::shared_lock lock(MLUAUserData); + Item* ret = 0; + if(lua_islightuserdata(state, arg_num)){ + LUAUserData* data = (LUAUserData*)lua_touserdata(state, arg_num); + if(!data || !data->IsCorrectlyInitialized()){ + LogError("%s: GetItem error while processing %s", GetScriptName(state), lua_tostring(state, -1)); + } + else if(!data->IsItem()){ + lua_Debug ar; + lua_getstack (state, 1, &ar); + lua_getinfo(state, "Sln", &ar); + LogError("%s: Invalid data type used for GetItem in %s (line %d)", GetScriptName(state), ar.source, ar.currentline); + } + else + ret = data->item; + } + return ret; +} + +Skill* LuaInterface::GetSkill(lua_State* state, int8 arg_num) { + std::shared_lock lock(MLUAUserData); + Skill* ret = 0; + if (lua_islightuserdata(state, arg_num)) { + LUAUserData* data = (LUAUserData*)lua_touserdata(state, arg_num); + if (!data || !data->IsCorrectlyInitialized()) { + LogError("%s: GetSkill error while processing %s", GetScriptName(state), lua_tostring(state, -1)); + } + else if (!data->IsSkill()) { + lua_Debug ar; + lua_getstack(state, 1, &ar); + lua_getinfo(state, "Sln", &ar); + LogError("%s: Invalid data type used for GetSkill in %s (line %d)", GetScriptName(state), ar.source, ar.currentline); + } + else + ret = data->skill; + } + return ret; +} + +LuaSpell* LuaInterface::GetSpell(lua_State* state, int8 arg_num) { + std::shared_lock lock(MLUAUserData); + LuaSpell* ret = 0; + if (lua_islightuserdata(state, arg_num)) { + LUAUserData* data = (LUAUserData*)lua_touserdata(state, arg_num); + if (!data || !data->IsCorrectlyInitialized()) { + LogError("%s: GetSpell error while processing %s", GetScriptName(state), lua_tostring(state, -1)); + } + else if (!data->IsSpell()) { + lua_Debug ar; + lua_getstack(state, 1, &ar); + lua_getinfo(state, "Sln", &ar); + LogError("%s: Invalid data type used for GetSpell in %s (line %d)", GetScriptName(state), ar.source, ar.currentline); + } + else + ret = data->spell; + } + return ret; +} + +ZoneServer* LuaInterface::GetZone(lua_State* state, int8 arg_num) { + std::shared_lock lock(MLUAUserData); + ZoneServer* ret = 0; + if(lua_islightuserdata(state, arg_num)){ + LUAUserData* data = (LUAUserData*)lua_touserdata(state, arg_num); + if(!data || !data->IsCorrectlyInitialized()){ + LogError("%s: GetZone error while processing %s", GetScriptName(state), lua_tostring(state, -1)); + } + else if(!data->IsZone()){ + lua_Debug ar; + lua_getstack (state, 1, &ar); + lua_getinfo(state, "Sln", &ar); + LogError("%s: Invalid data type used for GetZone in %s (line %d)", GetScriptName(state), ar.source, ar.currentline); + } + else + ret = data->zone; + } + return ret; +} + +sint64 LuaInterface::GetSInt64Value(lua_State* state, int8 arg_num) { + sint64 val = 0; + if(lua_isnumber(state, arg_num)){ + val = (sint64)lua_tointeger(state, arg_num); + } + return val; +} + +int64 LuaInterface::GetInt64Value(lua_State* state, int8 arg_num) { + int64 val = 0; + if(lua_isnumber(state, arg_num)){ + val = (int64)lua_tonumber(state, arg_num); + } + return val; +} + +sint32 LuaInterface::GetSInt32Value(lua_State* state, int8 arg_num) { + sint32 val = 0; + if(lua_isnumber(state, arg_num)){ + val = lua_tointeger(state, arg_num); + } + return val; +} + +int32 LuaInterface::GetInt32Value(lua_State* state, int8 arg_num) { + int32 val = 0; + if(lua_isnumber(state, arg_num)){ + val = (int32)lua_tonumber(state, arg_num); + } + return val; +} + +int16 LuaInterface::GetInt16Value(lua_State* state, int8 arg_num) { + int16 val = 0; + if(lua_isnumber(state, arg_num)){ + val = lua_tointeger(state, arg_num); + } + return val; +} + +int8 LuaInterface::GetInt8Value(lua_State* state, int8 arg_num) { + int8 val = 0; + if(lua_isnumber(state, arg_num)){ + val = lua_tointeger(state, arg_num); + } + return val; +} + +float LuaInterface::GetFloatValue(lua_State* state, int8 arg_num) { + float val = 0; + if(lua_isnumber(state, arg_num)) + val = (float)lua_tonumber(state, arg_num); + return val; +} + +string LuaInterface::GetStringValue(lua_State* state, int8 arg_num) { + string val; + if(lua_isstring(state, arg_num)){ + size_t size = 0; + const char* str = lua_tolstring(state, arg_num, &size); + if(str) + val = string(str); + } + return val; +} + +bool LuaInterface::GetBooleanValue(lua_State* state, int8 arg_num) { + return lua_toboolean(state, arg_num) == 1; +} + +void LuaInterface::SetStringValue(lua_State* state, const char* value) { + lua_pushstring(state, value); +} + +void LuaInterface::SetBooleanValue(lua_State* state, bool value) { + lua_pushboolean(state, value); +} + +void LuaInterface::SetFloatValue(lua_State* state, float value) { + lua_pushnumber(state, value); +} + +void LuaInterface::SetInt32Value(lua_State* state, int32 value) { + lua_pushinteger(state, value); +} + +void LuaInterface::SetSInt32Value(lua_State* state, sint32 value) { + lua_pushinteger(state, value); +} + +void LuaInterface::SetInt64Value(lua_State* state, int64 value) { + lua_pushinteger(state, value); +} + +void LuaInterface::SetSInt64Value(lua_State* state, sint64 value) { + lua_pushinteger(state, value); +} + +void LuaInterface::SetSpawnValue(lua_State* state, Spawn* spawn) { + LUASpawnWrapper* spawn_wrapper = new LUASpawnWrapper(); + spawn_wrapper->spawn = spawn; + AddUserDataPtr(spawn_wrapper, spawn); + lua_pushlightuserdata(state, spawn_wrapper); +} + +void LuaInterface::SetConversationValue(lua_State* state, vector* conversation) { + LUAConversationOptionWrapper* conv_wrapper = new LUAConversationOptionWrapper(); + conv_wrapper->conversation_options = conversation; + AddUserDataPtr(conv_wrapper, conversation); + lua_pushlightuserdata(state, conv_wrapper); +} + +void LuaInterface::SetOptionWindowValue(lua_State* state, vector* optionWindow) { + LUAOptionWindowWrapper* option_wrapper = new LUAOptionWindowWrapper(); + option_wrapper->option_window_option = optionWindow; + AddUserDataPtr(option_wrapper, optionWindow); + lua_pushlightuserdata(state, option_wrapper); +} + +void LuaInterface::SetItemValue(lua_State* state, Item* item) { + LUAItemWrapper* item_wrapper = new LUAItemWrapper(); + item_wrapper->item = item; + AddUserDataPtr(item_wrapper, item); + lua_pushlightuserdata(state, item_wrapper); +} + +void LuaInterface::SetSkillValue(lua_State* state, Skill* skill) { + LUASkillWrapper* skill_wrapper = new LUASkillWrapper(); + skill_wrapper->skill = skill; + AddUserDataPtr(skill_wrapper, skill); + lua_pushlightuserdata(state, skill_wrapper); +} + +void LuaInterface::SetQuestValue(lua_State* state, Quest* quest) { + LUAQuestWrapper* quest_wrapper = new LUAQuestWrapper(); + quest_wrapper->quest = quest; + AddUserDataPtr(quest_wrapper, quest); + lua_pushlightuserdata(state, quest_wrapper); +} + +void LuaInterface::SetZoneValue(lua_State* state, ZoneServer* zone) { + LUAZoneWrapper* zone_wrapper = new LUAZoneWrapper(); + zone_wrapper->zone = zone; + AddUserDataPtr(zone_wrapper, zone); + lua_pushlightuserdata(state, zone_wrapper); +} + +void LuaInterface::SetSpellValue(lua_State* state, LuaSpell* spell) { + LUASpellWrapper* spell_wrapper = new LUASpellWrapper(); + spell_wrapper->spell = spell; + AddUserDataPtr(spell_wrapper, spell); + lua_pushlightuserdata(state, spell_wrapper); +} + +LuaSpell* LuaInterface::GetSpell(const char* name) { + string lua_script = string(name); + if (lua_script.find(".lua") == string::npos) + lua_script.append(".lua"); + if(spells.count(lua_script) > 0) + { + LogWrite(LUA__DEBUG, 0, "LUA", "Found LUA Spell Script: '%s'", lua_script.c_str()); + LuaSpell* spell = spells[lua_script]; + LuaSpell* new_spell = new LuaSpell; + new_spell->state = spell->state; + new_spell->file_name = string(spell->file_name); + new_spell->timer = spell->timer; + new_spell->timer.Disable(); + new_spell->resisted = false; + new_spell->is_damage_spell = false; + new_spell->has_damaged = false; + new_spell->interrupted = false; + new_spell->crit = false; + new_spell->last_spellattack_hit = false; + new_spell->MSpellTargets.SetName("LuaSpell.MSpellTargets"); + new_spell->cancel_after_all_triggers = false; + new_spell->num_triggers = 0; + new_spell->num_calls = 0; + new_spell->is_recast_timer = false; + new_spell->had_triggers = false; + new_spell->had_dmg_remaining = false; + new_spell->slot_pos = 0; + new_spell->damage_remaining = 0; + new_spell->effect_bitmask = 0; + new_spell->caster = 0; + new_spell->initial_target = 0; + new_spell->spell = 0; + new_spell->restored = false; + new_spell->initial_caster_char_id = 0; + new_spell->initial_target_char_id = 0; + return new_spell; + } + else{ + LogWrite(LUA__ERROR, 0, "LUA", "Error LUA Spell Script: '%s'", name); + return 0; + } +} + +Mutex* LuaInterface::GetItemScriptMutex(const char* name) { + Mutex* mutex = 0; + if(item_scripts_mutex.count(name) > 0) + mutex = item_scripts_mutex[name]; + if(!mutex){ + mutex = new Mutex(); + item_scripts_mutex[name] = mutex; + } + return mutex; +} + +Mutex* LuaInterface::GetSpawnScriptMutex(const char* name) { + Mutex* mutex = 0; + if(spawn_scripts_mutex.count(string(name)) > 0) + mutex = spawn_scripts_mutex[name]; + if(!mutex){ + mutex = new Mutex(); + spawn_scripts_mutex[name] = mutex; + } + return mutex; +} + +Mutex* LuaInterface::GetZoneScriptMutex(const char* name) { + Mutex* mutex = 0; + if(zone_scripts_mutex.count(name) > 0) + mutex = zone_scripts_mutex[name]; + if(!mutex){ + mutex = new Mutex(); + zone_scripts_mutex[name] = mutex; + } + return mutex; +} + +Mutex* LuaInterface::GetRegionScriptMutex(const char* name) { + Mutex* mutex = 0; + if(region_scripts_mutex.count(name) > 0) + mutex = region_scripts_mutex[name]; + if(!mutex){ + mutex = new Mutex(); + region_scripts_mutex[name] = mutex; + } + return mutex; +} + +void LuaInterface::UseItemScript(const char* name, lua_State* state, bool val) { + MItemScripts.writelock(__FUNCTION__, __LINE__); + item_scripts[name][state] = val; + item_inverse_scripts[state] = name; + MItemScripts.releasewritelock(__FUNCTION__, __LINE__); +} + +void LuaInterface::UseSpawnScript(const char* name, lua_State* state, bool val) { + MSpawnScripts.writelock(__FUNCTION__, __LINE__); + spawn_scripts[name][state] = val; + spawn_inverse_scripts[state] = name; + MSpawnScripts.releasewritelock(__FUNCTION__, __LINE__); +} + +void LuaInterface::UseZoneScript(const char* name, lua_State* state, bool val) { + + MZoneScripts.writelock(__FUNCTION__, __LINE__); + zone_scripts[name][state] = val; + zone_inverse_scripts[state] = name; + MZoneScripts.releasewritelock(__FUNCTION__, __LINE__); +} + +void LuaInterface::UseRegionScript(const char* name, lua_State* state, bool val) { + + MRegionScripts.writelock(__FUNCTION__, __LINE__); + region_scripts[name][state] = val; + region_inverse_scripts[state] = name; + MRegionScripts.releasewritelock(__FUNCTION__, __LINE__); +} + +lua_State* LuaInterface::GetItemScript(const char* name, bool create_new, bool use) { + map >::iterator itr; + map::iterator item_script_itr; + lua_State* ret = 0; + Mutex* mutex = 0; + + itr = item_scripts.find(name); + if(itr != item_scripts.end()) { + mutex = GetItemScriptMutex(name); + mutex->readlock(__FUNCTION__, __LINE__); + for(item_script_itr = itr->second.begin(); item_script_itr != itr->second.end(); item_script_itr++){ + if(!item_script_itr->second){ //not in use + ret = item_script_itr->first; + + if (use) + { + item_script_itr->second = true; + break; // don't keep iterating, we already have our result + } + } + } + mutex->releasereadlock(__FUNCTION__, __LINE__); + } + if(!ret && create_new){ + if(name && LoadItemScript(name)) + ret = GetItemScript(name, create_new, use); + else{ + LogError("Error LUA Item Script '%s'", name); + return 0; + } + } + return ret; +} + +lua_State* LuaInterface::GetSpawnScript(const char* name, bool create_new, bool use) { + if (lua_system_reloading) + return 0; + map >::iterator itr; + map::iterator spawn_script_itr; + lua_State* ret = 0; + Mutex* mutex = 0; + + itr = spawn_scripts.find(name); + if(itr != spawn_scripts.end()) { + mutex = GetSpawnScriptMutex(name); + mutex->readlock(__FUNCTION__, __LINE__); + for(spawn_script_itr = itr->second.begin(); spawn_script_itr != itr->second.end(); spawn_script_itr++){ + if(!spawn_script_itr->second){ //not in use + ret = spawn_script_itr->first; + + if (use) + { + spawn_script_itr->second = true; + break; // don't keep iterating, we already have our result + } + } + } + mutex->releasereadlock(__FUNCTION__, __LINE__); + } + if(!ret && create_new){ + if(name && LoadSpawnScript(name)) + ret = GetSpawnScript(name, create_new, use); + else{ + LogError("Error LUA Spawn Script '%s'", name); + return 0; + } + } + return ret; +} + +lua_State* LuaInterface::GetZoneScript(const char* name, bool create_new, bool use) { + map >::iterator itr; + map::iterator zone_script_itr; + lua_State* ret = 0; + Mutex* mutex = 0; + + itr = zone_scripts.find(name); + if(itr != zone_scripts.end()) { + mutex = GetZoneScriptMutex(name); + mutex->readlock(__FUNCTION__, __LINE__); + for(zone_script_itr = itr->second.begin(); zone_script_itr != itr->second.end(); zone_script_itr++){ + if(!zone_script_itr->second){ //not in use + ret = zone_script_itr->first; + + if (use) + { + zone_script_itr->second = true; + break; // don't keep iterating, we already have our result + } + } + } + mutex->releasereadlock(__FUNCTION__, __LINE__); + } + if(!ret && create_new){ + if(name && LoadZoneScript(name)) + ret = GetZoneScript(name); + else{ + LogError("Error LUA Zone Script '%s'", name); + return 0; + } + } + return ret; +} + +lua_State* LuaInterface::GetRegionScript(const char* name, bool create_new, bool use) { + map >::iterator itr; + map::iterator region_script_itr; + lua_State* ret = 0; + Mutex* mutex = 0; + + itr = region_scripts.find(name); + if(itr != region_scripts.end()) { + mutex = GetRegionScriptMutex(name); + mutex->readlock(__FUNCTION__, __LINE__); + for(region_script_itr = itr->second.begin(); region_script_itr != itr->second.end(); region_script_itr++){ + if(!region_script_itr->second){ //not in use + ret = region_script_itr->first; + + if (use) + { + region_script_itr->second = true; + break; // don't keep iterating, we already have our result + } + } + } + mutex->releasereadlock(__FUNCTION__, __LINE__); + } + if(!ret && create_new){ + if(name && LoadRegionScript(name)) + ret = GetRegionScript(name); + else{ + LogError("Error LUA Zone Script '%s'", name); + return 0; + } + } + return ret; +} + +bool LuaInterface::RunItemScript(string script_name, const char* function_name, Item* item, Spawn* spawn, Spawn* target, sint64* returnValue) { + if(!item) + return false; + lua_State* state = GetItemScript(script_name.c_str(), true, true); + if(state){ + Mutex* mutex = GetItemScriptMutex(script_name.c_str()); + if(mutex) + mutex->readlock(__FUNCTION__, __LINE__); + else{ + LogError("Error getting lock for '%s'", script_name.c_str()); + UseItemScript(script_name.c_str(), state, false); + return false; + } + lua_getglobal(state, function_name); + if (!lua_isfunction(state, lua_gettop(state))){ + lua_pop(state, 1); + mutex->releasereadlock(__FUNCTION__); + UseItemScript(script_name.c_str(), state, false); + return false; + } + SetItemValue(state, item); + int8 num_parms = 1; + if(spawn){ + SetSpawnValue(state, spawn); + num_parms++; + } + if(target){ + SetSpawnValue(state, target); + num_parms++; + } + if(!CallItemScript(state, num_parms, returnValue)){ + if(mutex) + mutex->releasereadlock(__FUNCTION__, __LINE__); + UseItemScript(script_name.c_str(), state, false); + return false; + } + if(mutex) + mutex->releasereadlock(__FUNCTION__, __LINE__); + UseItemScript(script_name.c_str(), state, false); + return true; + } + else + return false; +} + +bool LuaInterface::RunItemScriptWithReturnString(string script_name, const char* function_name, Item* item, Spawn* spawn, std::string* returnValue) { + if(!item) + return false; + lua_State* state = GetItemScript(script_name.c_str(), true, true); + if(state){ + Mutex* mutex = GetItemScriptMutex(script_name.c_str()); + if(mutex) + mutex->readlock(__FUNCTION__, __LINE__); + else{ + LogError("Error getting lock for '%s'", script_name.c_str()); + UseItemScript(script_name.c_str(), state, false); + return false; + } + lua_getglobal(state, function_name); + if (!lua_isfunction(state, lua_gettop(state))){ + lua_pop(state, 1); + mutex->releasereadlock(__FUNCTION__); + UseItemScript(script_name.c_str(), state, false); + return false; + } + SetItemValue(state, item); + int8 num_parms = 1; + if(spawn){ + SetSpawnValue(state, spawn); + num_parms++; + } + if(!CallItemScript(state, num_parms, returnValue)){ + if(mutex) + mutex->releasereadlock(__FUNCTION__, __LINE__); + UseItemScript(script_name.c_str(), state, false); + return false; + } + if(mutex) + mutex->releasereadlock(__FUNCTION__, __LINE__); + UseItemScript(script_name.c_str(), state, false); + return true; + } + else + return false; +} + + +bool LuaInterface::RunSpawnScript(string script_name, const char* function_name, Spawn* npc, Spawn* spawn, const char* message, bool is_door_open, sint32 input_value, sint32* return_value) { + if(!npc || lua_system_reloading) + return false; + + bool isUseDoorFunction = false; + if(!strcmp(function_name,"usedoor")) + isUseDoorFunction = true; + + lua_State* state = GetSpawnScript(script_name.c_str(), true, true); + if(state){ + Mutex* mutex = GetSpawnScriptMutex(script_name.c_str()); + if(mutex) + mutex->readlock(__FUNCTION__, __LINE__); + else{ + LogError("Error getting lock for '%s'", script_name.c_str()); + UseSpawnScript(script_name.c_str(), state, false); + return false; + } + lua_getglobal(state, function_name); + if (!lua_isfunction(state, lua_gettop(state))){ + lua_pop(state, 1); + mutex->releasereadlock(__FUNCTION__); + UseSpawnScript(script_name.c_str(), state, false); + return false; + } + SetSpawnValue(state, npc); + int8 num_parms = 1; + // we always send spawn, even if null (nil) when its 'usedoor' function + if(spawn || isUseDoorFunction){ + SetSpawnValue(state, spawn); + num_parms++; + } + + // usedoor function always passes just npc, spawn and is_door_open + if(isUseDoorFunction){ + SetBooleanValue(state, is_door_open); + num_parms++; + } + else { + if(message){ + SetStringValue(state, message); + num_parms++; + } + + if(!strcmp(function_name,"healthchanged")) + { + // passes as damage dealt + SetSInt32Value(state, input_value); + num_parms++; + } + } + if(!CallScriptSInt32(state, num_parms, return_value)){ + if(mutex) + mutex->releasereadlock(__FUNCTION__, __LINE__); + UseSpawnScript(script_name.c_str(), state, false); + return false; + } + if(mutex) + mutex->releasereadlock(__FUNCTION__, __LINE__); + UseSpawnScript(script_name.c_str(), state, false); + return true; + } + else + return false; +} + +bool LuaInterface::RunZoneScript(string script_name, const char* function_name, ZoneServer* zone, Spawn* spawn, int32 int32_arg1, const char* str_arg1, Spawn* spawn_arg1, int32 int32_arg2, const char* str_arg2, Spawn* spawn_arg2) { + if (!zone) + return false; + lua_State* state = GetZoneScript(script_name.c_str(), true, true); + if (state) { + Mutex* mutex = GetZoneScriptMutex(script_name.c_str()); + if (mutex) + mutex->readlock(__FUNCTION__, __LINE__); + else { + LogError("Error getting lock for '%s'", script_name.c_str()); + UseZoneScript(script_name.c_str(), state, false); + return false; + } + lua_getglobal(state, function_name); + if (!lua_isfunction(state, lua_gettop(state))) { + lua_pop(state, 1); + mutex->releasereadlock(__FUNCTION__); + UseZoneScript(script_name.c_str(), state, false); + return false; + } + SetZoneValue(state, zone); + int8 num_params = 1; + if (spawn) { + SetSpawnValue(state, spawn); + num_params++; + } + if (int32_arg1 > 0) { + SetInt32Value(state, int32_arg1); + num_params++; + } + if (str_arg1) { + SetStringValue(state, str_arg1); + num_params++; + } + if (spawn_arg1) { + SetSpawnValue(state, spawn_arg1); + num_params++; + } + if (int32_arg2 > 0) { + SetInt32Value(state, int32_arg2); + num_params++; + } + if (str_arg2) { + SetStringValue(state, str_arg2); + num_params++; + } + if (spawn_arg2) { + SetSpawnValue(state, spawn_arg2); + num_params++; + } + if (!CallScriptInt32(state, num_params)) { + if (mutex) + mutex->releasereadlock(__FUNCTION__, __LINE__); + UseZoneScript(script_name.c_str(), state, false); + return false; + } + if (mutex) + mutex->releasereadlock(__FUNCTION__, __LINE__); + UseZoneScript(script_name.c_str(), state, false); + return true; + } + else + return false; +} + +bool LuaInterface::RunZoneScriptWithReturn(string script_name, const char* function_name, ZoneServer* zone, Spawn* spawn, int32 int32_arg1, int32 int32_arg2, int32 int32_arg3, int32* returnValue) { + if (!zone) + return false; + lua_State* state = GetZoneScript(script_name.c_str(), true, true); + if (state) { + Mutex* mutex = GetZoneScriptMutex(script_name.c_str()); + if (mutex) + mutex->readlock(__FUNCTION__, __LINE__); + else { + LogError("Error getting lock for '%s'", script_name.c_str()); + UseZoneScript(script_name.c_str(), state, false); + return false; + } + lua_getglobal(state, function_name); + if (!lua_isfunction(state, lua_gettop(state))) { + lua_pop(state, 1); + mutex->releasereadlock(__FUNCTION__); + UseZoneScript(script_name.c_str(), state, false); + return false; + } + SetZoneValue(state, zone); + int8 num_params = 1; + SetSpawnValue(state, spawn); + num_params++; + SetInt32Value(state, int32_arg1); + num_params++; + SetInt32Value(state, int32_arg2); + num_params++; + SetInt32Value(state, int32_arg3); + num_params++; + if (!CallScriptInt32(state, num_params, returnValue)) { + if (mutex) + mutex->releasereadlock(__FUNCTION__, __LINE__); + UseZoneScript(script_name.c_str(), state, false); + return false; + } + if (mutex) + mutex->releasereadlock(__FUNCTION__, __LINE__); + UseZoneScript(script_name.c_str(), state, false); + return true; + } + else + return false; +} + + +bool LuaInterface::RunRegionScript(string script_name, const char* function_name, ZoneServer* zone, Spawn* spawn, sint32 int32_arg1, int32* returnValue) { + if (!zone) + return false; + lua_State* state = GetRegionScript(script_name.c_str(), true, true); + if (state) { + Mutex* mutex = GetRegionScriptMutex(script_name.c_str()); + if (mutex) + mutex->readlock(__FUNCTION__, __LINE__); + else { + LogError("Error getting lock for '%s'", script_name.c_str()); + UseRegionScript(script_name.c_str(), state, false); + return false; + } + lua_getglobal(state, function_name); + if (!lua_isfunction(state, lua_gettop(state))) { + lua_pop(state, 1); + mutex->releasereadlock(__FUNCTION__); + UseRegionScript(script_name.c_str(), state, false); + return false; + } + SetZoneValue(state, zone); + int8 num_params = 1; + if (spawn) { + SetSpawnValue(state, spawn); + num_params++; + } + if (int32_arg1 > 0) { + SetSInt32Value(state, int32_arg1); + num_params++; + } + if (!CallRegionScript(state, num_params, returnValue)) { + if (mutex) + mutex->releasereadlock(__FUNCTION__, __LINE__); + UseRegionScript(script_name.c_str(), state, false); + return false; + } + if (mutex) + mutex->releasereadlock(__FUNCTION__, __LINE__); + UseRegionScript(script_name.c_str(), state, false); + return true; + } + else + return false; +} + +void LuaInterface::AddPendingSpellDelete(LuaSpell* spell) { + MSpellDelete.lock(); + if ( spells_pending_delete.count(spell) == 0 ) + spells_pending_delete[spell] = Timer::GetCurrentTime2() + 10000; + MSpellDelete.unlock(); +} + +void LuaInterface::AddCustomSpell(LuaSpell* spell) +{ + MCustomSpell.writelock(); + custom_spells[spell->spell->GetSpellID()] = spell; + MCustomSpell.releasewritelock(); +} + +void LuaInterface::RemoveCustomSpell(int32 id) +{ + MCustomSpell.writelock(); + map::iterator itr = custom_spells.find(id); + if (itr != custom_spells.end()) + { + custom_spells.erase(itr); + custom_free_spell_ids.push_front(id); + } + MCustomSpell.releasewritelock(); +} + +// prior to calling FindCustomSpell you should call FindCustomSpellLock and after FindCustomSpellUnlock +LuaSpell* LuaInterface::FindCustomSpell(int32 id) +{ + LuaSpell* spell = 0; + map::iterator itr = custom_spells.find(id); + if (itr != custom_spells.end()) + spell = itr->second; + return spell; +} + +int32 LuaInterface::GetFreeCustomSpellID() +{ + int32 id = 0; + MCustomSpell.writelock(); + if (!custom_free_spell_ids.empty()) + { + id = custom_free_spell_ids.front(); + custom_free_spell_ids.pop_front(); + } + MCustomSpell.releasewritelock(); + return id; +} + + +void LuaInterface::SetLuaUserDataStale(void* ptr) { + std::unique_lock lock(MLUAUserData); + std::map::iterator itr = user_data_ptr.find(ptr); + if(itr != user_data_ptr.end()) { + itr->second->correctly_initialized = false; + } +} + +LUAUserData::LUAUserData(){ + correctly_initialized = false; + quest = 0; + conversation_options = 0; + spawn = 0; + zone = 0; + skill = 0; + option_window_option = 0; + item = 0; +} + +bool LUAUserData::IsCorrectlyInitialized(){ + return correctly_initialized; +} + +bool LUAUserData::IsConversationOption(){ + return false; +} + +bool LUAUserData::IsOptionWindow() { + return false; +} + +bool LUAUserData::IsSpawn(){ + return false; +} + +bool LUAUserData::IsQuest(){ + return false; +} + +bool LUAUserData::IsZone(){ + return false; +} + +bool LUAUserData::IsItem(){ + return false; +} + +bool LUAUserData::IsSkill() { + return false; +} + +bool LUAUserData::IsSpell() { + return false; +} + +LUAConversationOptionWrapper::LUAConversationOptionWrapper(){ + correctly_initialized = true; +} + +bool LUAConversationOptionWrapper::IsConversationOption(){ + return true; +} + +LUAOptionWindowWrapper::LUAOptionWindowWrapper() { + correctly_initialized = true; +} + +bool LUAOptionWindowWrapper::IsOptionWindow() { + return true; +} + +LUASpawnWrapper::LUASpawnWrapper(){ + correctly_initialized = true; +} + +bool LUASpawnWrapper::IsSpawn(){ + return true; +} + +LUAZoneWrapper::LUAZoneWrapper(){ + correctly_initialized = true; +} + +bool LUAZoneWrapper::IsZone(){ + return true; +} + +LUAQuestWrapper::LUAQuestWrapper(){ + correctly_initialized = true; +} + +bool LUAQuestWrapper::IsQuest(){ + return true; +} + +LUAItemWrapper::LUAItemWrapper(){ + correctly_initialized = true; +} + +bool LUAItemWrapper::IsItem(){ + return true; +} + +LUASkillWrapper::LUASkillWrapper() { + correctly_initialized = true; +} + +bool LUASkillWrapper::IsSkill() { + return true; +} + +LUASpellWrapper::LUASpellWrapper() { + correctly_initialized = true; +} + +bool LUASpellWrapper::IsSpell() { + return true; +} \ No newline at end of file diff --git a/source/WorldServer/LuaInterface.h b/source/WorldServer/LuaInterface.h new file mode 100644 index 0000000..f0e9c98 --- /dev/null +++ b/source/WorldServer/LuaInterface.h @@ -0,0 +1,357 @@ +/* + 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 . +*/ +#ifndef LUA_INTERFACE_H +#define LUA_INTERFACE_H + +#include +#include + +#include "Spawn.h" +#include "Spells.h" +#include "../common/Mutex.h" +#include "Quests.h" +#include "zoneserver.h" +#include "client.h" + +#include "../LUA/lua.hpp" + +using namespace std; + +struct ConversationOption{ + string option; + string function; +}; + +struct OptionWindowOption { + string optionName; + string optionDescription; + string optionCommand; + int32 optionIconSheet; + int16 optionIconID; + string optionConfirmTitle; +}; + +//Bitmask Values +#define EFFECT_FLAG_STUN 1 +#define EFFECT_FLAG_ROOT 2 +#define EFFECT_FLAG_MEZ 4 +#define EFFECT_FLAG_STIFLE 8 +#define EFFECT_FLAG_DAZE 16 +#define EFFECT_FLAG_FEAR 32 +#define EFFECT_FLAG_SPELLBONUS 64 +#define EFFECT_FLAG_SKILLBONUS 128 +#define EFFECT_FLAG_STEALTH 256 +#define EFFECT_FLAG_INVIS 512 +#define EFFECT_FLAG_SNARE 1024 +#define EFFECT_FLAG_WATERWALK 2048 +#define EFFECT_FLAG_WATERJUMP 4096 +#define EFFECT_FLAG_FLIGHT 8192 +#define EFFECT_FLAG_GLIDE 16384 +#define EFFECT_FLAG_AOE_IMMUNE 32768 +#define EFFECT_FLAG_STUN_IMMUNE 65536 +#define EFFECT_FLAG_MEZ_IMMUNE 131072 +#define EFFECT_FLAG_DAZE_IMMUNE 262144 +#define EFFECT_FLAG_ROOT_IMMUNE 524288 +#define EFFECT_FLAG_STIFLE_IMMUNE 1048576 +#define EFFECT_FLAG_FEAR_IMMUNE 2097152 +#define EFFECT_FLAG_SAFEFALL 4194304 + +struct LuaSpell{ + Entity* caster; + int32 initial_caster_char_id; + int32 initial_target; + int32 initial_target_char_id; + vector targets; + vector removed_targets; // previously cancelled, expired, used, so on + multimap char_id_targets; + Spell* spell; + lua_State* state; + string file_name; + Timer timer; + bool is_recast_timer; + int16 num_calls; + int16 num_triggers; + int8 slot_pos; + int32 damage_remaining; + bool resisted; + bool has_damaged; + bool is_damage_spell; + bool interrupted; + bool crit; + bool last_spellattack_hit; + bool cancel_after_all_triggers; + bool had_triggers; + bool had_dmg_remaining; + Mutex MSpellTargets; + int32 effect_bitmask; + bool restored; // restored spell cross zone + +}; + +class LUAUserData{ +public: + LUAUserData(); + virtual ~LUAUserData(){}; + virtual bool IsCorrectlyInitialized(); + virtual bool IsConversationOption(); + virtual bool IsOptionWindow(); + virtual bool IsSpawn(); + virtual bool IsQuest(); + virtual bool IsZone(); + virtual bool IsItem(); + virtual bool IsSkill(); + virtual bool IsSpell(); + bool correctly_initialized; + Item* item; + ZoneServer* zone; + Spawn* spawn; + vector* conversation_options; + vector* option_window_option; + vector* spawn_list; + Quest* quest; + Skill* skill; + LuaSpell* spell; +}; + +class LUAConversationOptionWrapper : public LUAUserData{ +public: + LUAConversationOptionWrapper(); + bool IsConversationOption(); +}; + +class LUAOptionWindowWrapper : public LUAUserData { +public: + LUAOptionWindowWrapper(); + bool IsOptionWindow(); +}; + +class LUASpawnWrapper : public LUAUserData{ +public: + LUASpawnWrapper(); + bool IsSpawn(); +}; + +class LUAZoneWrapper : public LUAUserData{ +public: + LUAZoneWrapper(); + bool IsZone(); +}; + +class LUAQuestWrapper : public LUAUserData{ +public: + LUAQuestWrapper(); + bool IsQuest(); +}; + +class LUAItemWrapper : public LUAUserData{ +public: + LUAItemWrapper(); + bool IsItem(); +}; + +class LUASkillWrapper: public LUAUserData { +public: + LUASkillWrapper(); + bool IsSkill(); +}; + +class LUASpellWrapper : public LUAUserData { +public: + LUASpellWrapper(); + bool IsSpell(); +}; + +class LuaInterface { +public: + LuaInterface(); + ~LuaInterface(); + int GetNumberOfArgs(lua_State* state); + bool LoadLuaSpell(const char* name); + bool LoadLuaSpell(string name); + bool LoadItemScript(string name); + bool LoadItemScript(const char* name); + bool LoadSpawnScript(string name); + bool LoadSpawnScript(const char* name); + bool LoadZoneScript(string name); + bool LoadZoneScript(const char* name); + bool LoadRegionScript(string name); + bool LoadRegionScript(const char* name); + void RemoveSpell(LuaSpell* spell, bool call_remove_function = true, bool can_delete = true, string reason = "", bool removing_all_spells = false); + Spawn* GetSpawn(lua_State* state, int8 arg_num = 1); + Item* GetItem(lua_State* state, int8 arg_num = 1); + Quest* GetQuest(lua_State* state, int8 arg_num = 1); + ZoneServer* GetZone(lua_State* state, int8 arg_num = 1); + Skill* GetSkill(lua_State* state, int8 arg_num = 1); + LuaSpell* GetSpell(lua_State* state, int8 arg_num = 1); + vector* GetConversation(lua_State* state, int8 arg_num = 1); + + vector* GetOptionWindow(lua_State* state, int8 arg_num = 1); + int8 GetInt8Value(lua_State* state, int8 arg_num = 1); + int16 GetInt16Value(lua_State* state, int8 arg_num = 1); + int32 GetInt32Value(lua_State* state, int8 arg_num = 1); + sint32 GetSInt32Value(lua_State* state, int8 arg_num = 1); + int64 GetInt64Value(lua_State* state, int8 arg_num = 1); + sint64 GetSInt64Value(lua_State* state, int8 arg_num = 1); + float GetFloatValue(lua_State* state, int8 arg_num = 1); + string GetStringValue(lua_State* state, int8 arg_num = 1); + bool GetBooleanValue(lua_State*state, int8 arg_num = 1); + + void Process(); + + void SetInt32Value(lua_State* state, int32 value); + void SetSInt32Value(lua_State* state, sint32 value); + void SetInt64Value(lua_State* state, int64 value); + void SetSInt64Value(lua_State* state, sint64 value); + void SetFloatValue(lua_State* state, float value); + void SetBooleanValue(lua_State* state, bool value); + void SetStringValue(lua_State* state, const char* value); + void SetSpawnValue(lua_State* state, Spawn* spawn); + void SetSkillValue(lua_State* state, Skill* skill); + void SetItemValue(lua_State* state, Item* item); + void SetQuestValue(lua_State* state, Quest* quest); + void SetZoneValue(lua_State* state, ZoneServer* zone); + void SetSpellValue(lua_State* state, LuaSpell* spell); + void SetConversationValue(lua_State* state, vector* conversation); + void SetOptionWindowValue(lua_State* state, vector* optionWindow); + + std::string AddSpawnPointers(LuaSpell* spell, bool first_cast, bool precast = false, const char* function = 0, SpellScriptTimer* timer = 0, bool passLuaSpell=false, Spawn* altTarget = 0); + LuaSpell* GetCurrentSpell(lua_State* state, bool needsLock = true); + void RemoveCurrentSpell(lua_State* state, bool needsLock = true); + bool CallSpellProcess(LuaSpell* spell, int8 num_parameters, std::string functionCalled); + LuaSpell* GetSpell(const char* name); + void UseItemScript(const char* name, lua_State* state, bool val); + void UseSpawnScript(const char* name, lua_State* state, bool val); + void UseZoneScript(const char* name, lua_State* state, bool val); + void UseRegionScript(const char* name, lua_State* state, bool val); + lua_State* GetItemScript(const char* name, bool create_new = true, bool use = false); + lua_State* GetSpawnScript(const char* name, bool create_new = true, bool use = false); + lua_State* GetZoneScript(const char* name, bool create_new = true, bool use = false); + lua_State* GetRegionScript(const char* name, bool create_new = true, bool use = false); + Quest* LoadQuest(int32 id, const char* name, const char* type, const char* zone, int8 level, const char* description, char* script_name); + + const char* GetScriptName(lua_State* state); + + void RemoveSpawnScript(const char* name); + bool RunItemScript(string script_name, const char* function_name, Item* item, Spawn* spawn = 0, Spawn* target = 0, sint64* returnValue = 0); + bool RunItemScriptWithReturnString(string script_name, const char* function_name, Item* item, Spawn* spawn = 0, std::string* returnValue = 0); + bool CallItemScript(lua_State* state, int8 num_parameters, std::string* returnValue = 0); + bool CallItemScript(lua_State* state, int8 num_parameters, sint64* returnValue = 0); + bool RunSpawnScript(string script_name, const char* function_name, Spawn* npc, Spawn* spawn = 0, const char* message = 0, bool is_door_open = false, sint32 input_value = 0, sint32* return_value = 0); + bool CallSpawnScript(lua_State* state, int8 num_parameters); + bool RunZoneScript(string script_name, const char* function_name, ZoneServer* zone, Spawn* spawn = 0, int32 int32_arg1 = 0, const char* str_arg1 = 0, Spawn* spawn_arg1 = 0, int32 int32_arg2 = 0, const char* str_arg2 = 0, Spawn* spawn_arg2 = 0); + bool RunZoneScriptWithReturn(string script_name, const char* function_name, ZoneServer* zone, Spawn* spawn, int32 int32_arg1, int32 int32_arg2, int32 int32_arg3, int32* returnValue = 0); + bool CallScriptInt32(lua_State* state, int8 num_parameters, int32* returnValue = 0); + bool CallScriptSInt32(lua_State* state, int8 num_parameters, sint32* returnValue = 0); + bool RunRegionScript(string script_name, const char* function_name, ZoneServer* zone, Spawn* spawn = 0, sint32 int32_arg1 = 0, int32* returnValue = 0); + bool CallRegionScript(lua_State* state, int8 num_parameters, int32* returnValue); + void ResetFunctionStack(lua_State* state); + void DestroySpells(); + void DestroySpawnScripts(); + void DestroyItemScripts(); + void ReloadSpells(); + void DestroyQuests(bool reload = false); + void DestroyZoneScripts(); + void DestroyRegionScripts(); + void SimpleLogError(const char* error); + void LogError(const char* error, ...); + + + bool CallQuestFunction(Quest* quest, const char* function, Spawn* player, int32 step_id = 0xFFFFFFFF, int32* returnValue = 0); + void RemoveDebugClients(Client* client); + void UpdateDebugClients(Client* client); + void ProcessErrorMessage(const char* message); + map GetDebugClients(){ return debug_clients; } + void AddUserDataPtr(LUAUserData* data, void* data_ptr = 0); + void DeleteUserDataPtrs(bool all); + void DeletePendingSpells(bool all); + void DeletePendingSpell(LuaSpell* spell); + Mutex* GetSpawnScriptMutex(const char* name); + Mutex* GetItemScriptMutex(const char* name); + Mutex* GetZoneScriptMutex(const char* name); + Mutex* GetRegionScriptMutex(const char* name); + Mutex* GetQuestMutex(Quest* quest); + + void SetLuaSystemReloading(bool val) { lua_system_reloading = val; } + bool IsLuaSystemReloading() { return lua_system_reloading; } + + void AddPendingSpellDelete(LuaSpell* spell); + + void AddCustomSpell(LuaSpell* spell); + void RemoveCustomSpell(int32 id); + + void FindCustomSpellLock() { MCustomSpell.readlock(); } + void FindCustomSpellUnlock() { MCustomSpell.releasereadlock(); } + LuaSpell* FindCustomSpell(int32 id); + + int32 GetFreeCustomSpellID(); + + void SetLuaUserDataStale(void* ptr); + +private: + bool shutting_down; + bool lua_system_reloading; + map spells_pending_delete; + Timer* user_data_timer; + Timer* spell_delete_timer; + map user_data; + map user_data_ptr; + map debug_clients; + map current_spells; + vector* GetDirectoryListing(const char* directory); + lua_State* LoadLuaFile(const char* name); + void RegisterFunctions(lua_State* state); + map spells; + map inverse_spells; + + map quests; + map quest_states; + map > item_scripts; + map > spawn_scripts; + map > zone_scripts; + map > region_scripts; + + map custom_spells; + std::deque custom_free_spell_ids; + + map item_inverse_scripts; + map spawn_inverse_scripts; + map zone_inverse_scripts; + map region_inverse_scripts; + + map item_scripts_mutex; + map spawn_scripts_mutex; + map zone_scripts_mutex; + map quests_mutex; + map region_scripts_mutex; + + Mutex MDebugClients; + Mutex MSpells; + Mutex MSpawnScripts; + Mutex MItemScripts; + Mutex MZoneScripts; + Mutex MQuests; + Mutex MLUAMain; + Mutex MSpellDelete; + Mutex MCustomSpell; + Mutex MRegionScripts; + + mutable std::shared_mutex MLUAUserData; +}; +#endif diff --git a/source/WorldServer/MutexHelper.h b/source/WorldServer/MutexHelper.h new file mode 100644 index 0000000..58c3aa7 --- /dev/null +++ b/source/WorldServer/MutexHelper.h @@ -0,0 +1,202 @@ +/* + 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 . +*/ +#ifndef MUTEXHELPER_H +#define MUTEXHELPER_H + +#include "../common/timer.h" +#include "../common/Mutex.h" +#include +#include + +template +class IsPointer { +public: + static bool ValidPointer(T key){ + return false; + } + + static void Delete(T key){ + } +}; + +class Locker{ +public: + Locker(){ + #ifdef WIN32 + InitializeCriticalSection(&CSMutex); + #else + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE_NP); + pthread_mutex_init(&CSMutex, &attr); + pthread_mutexattr_destroy(&attr); + #endif + } + Locker(const Locker& locker){ + #ifdef WIN32 + InitializeCriticalSection(&CSMutex); + #else + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE_NP); + pthread_mutex_init(&CSMutex, &attr); + pthread_mutexattr_destroy(&attr); + #endif + } + ~Locker(){ + #ifdef WIN32 + DeleteCriticalSection(&CSMutex); + #else + // pthread_mutex_destroy(&CSMutex); + #endif + } + void lock(){ + #ifdef WIN32 + EnterCriticalSection(&CSMutex); + #else + pthread_mutex_lock(&CSMutex); + #endif + } + void unlock(){ + #ifdef WIN32 + LeaveCriticalSection(&CSMutex); + #else + pthread_mutex_unlock(&CSMutex); + #endif + } + +private: + #ifdef WIN32 + CRITICAL_SECTION CSMutex; + #else + pthread_mutex_t CSMutex; + #endif +}; + +template +class IsPointer { +public: + static bool ValidPointer(T* key){ + return true; + } + static void Delete(T* key){ + if(key){ + delete key; + key = 0; + } + } +}; + +template +class DeleteData{ +public: + + void SetData(int type, KeyT key, ValueT value, unsigned int time){ + this->type = type; + this->key = key; + this->value = value; + this->time = time; + } + + void DeleteKey(){ + IsPointer::Delete(key); + } + + void DeleteValue(){ + IsPointer::Delete(value); + } + + unsigned int GetTime(){ + return time; + } + + int GetType(){ + return type; + } +private: + int type; + KeyT key; + ValueT value; + unsigned int time; +}; + +template +class HandleDeletes { +public: + HandleDeletes(){ + access_count = 0; + next_delete_attempt = 0; + changing = false; + has_pending_deletes = false; + } + ~HandleDeletes(){ + CheckDeletes(true); + } + void AddPendingDelete(T value, unsigned int time){ + if(IsPointer::ValidPointer(value)){ + while(changing){ + Sleep(1); + } + ++access_count; + pending_deletes[value] = time; + has_pending_deletes = true; + --access_count; + } + } + + void CheckDeletes(bool force = false){ + while(changing){ + Sleep(1); + } + if(has_pending_deletes && (force || (Timer::GetCurrentTime2() > next_delete_attempt && access_count == 0))){ + changing = true; + while(access_count > 0){ + Sleep(1); + } + ++access_count; + next_delete_attempt = Timer::GetCurrentTime2(); + std::list deletes; + typename std::map::iterator pending_delete_itr; + for(pending_delete_itr = pending_deletes.begin(); pending_delete_itr != pending_deletes.end(); pending_delete_itr++){ + if(force || next_delete_attempt >= pending_delete_itr->second) + deletes.push_back(pending_delete_itr->first); + } + if(deletes.size() > 0){ + typename std::list::iterator delete_itr; + for(delete_itr = deletes.begin(); delete_itr != deletes.end(); delete_itr++){ + IsPointer::Delete(*delete_itr); + pending_deletes.erase(*delete_itr); + } + has_pending_deletes = (pending_deletes.size() > 0); + } + next_delete_attempt += 1000; + --access_count; + changing = false; + } + } + +private: + volatile bool changing; + volatile int access_count; + volatile unsigned int next_delete_attempt; + volatile bool has_pending_deletes; + std::map pending_deletes; +}; +#endif diff --git a/source/WorldServer/MutexList.h b/source/WorldServer/MutexList.h new file mode 100644 index 0000000..818c050 --- /dev/null +++ b/source/WorldServer/MutexList.h @@ -0,0 +1,277 @@ +/* + 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 . +*/ +#ifndef MUTEXLIST_H +#define MUTEXLIST_H +#include +#include "MutexHelper.h" + +#define MUTEXLIST_PENDING_ADD 1 +#define MUTEXLIST_PENDING_REMOVE 2 +#define MUTEXLIST_PENDING_DELETE 3 + +template +class MutexList{ +public: + MutexList(){ + pending_changing = false; + has_pending_data = false; + pending_clear = false; + changing = false; + access_count = 0; + access_pending = 0; + } + MutexList(const MutexList& list){ + pending_changing = false; + has_pending_data = false; + pending_clear = false; + changing = false; + access_count = 0; + access_pending = 0; + /*if(list.has_pending_data) + pending_data = list.pending_data; + current_data = list.current_data; */ + } + ~MutexList(){ + while(!update(true)){ + Sleep(1); + } + } + class iterator { + private: + typename std::list::iterator itr; // Current element + MutexList* list; + bool first_itr; + public: + iterator(){ + } + iterator(MutexList* list){ + if(list){ + this->list = list; + list->update(); + this->list->AddAccess(); + first_itr = true; + itr = list->current_data.begin(); + if(itr != list->current_data.end()) + value = *itr; + } + else + this->list = 0; + } + ~iterator(){ + if(list) + list->RemoveAccess(); + } + + bool HasNext(){ + return itr != list->current_data.end(); + } + + bool Next(){ + if(list->pending_clear) + return false; + if(first_itr) + first_itr = false; + else + itr++; + if(itr != list->current_data.end()){ + value = *itr; + if(list->PendingContains(value)) //pending delete + return Next(); + return true; + } + return false; + } + iterator* operator->() { + return this; + } + T value; +}; + void SetChanging(){ + ChangingLock.lock(); + changing = true; + ChangingLock.unlock(); + } + + void SetNotChanging(){ + ChangingLock.lock(); + changing = false; + ChangingLock.unlock(); + } + + void AddAccess(){ + AccessLock.lock(); + ++access_count; + AccessLock.unlock(); + } + + void RemoveAccess(){ + AccessLock.lock(); + --access_count; + AccessLock.unlock(); + } + + unsigned int size(bool include_pending = false){ + if(include_pending){ + update(); + return current_data.size() + pending_data.size(); + } + return current_data.size(); + } + iterator begin(){ + return iterator(this); + } + void clear(bool erase_all = false){ + pending_clear = true; + if(erase_all){ + AddAccess(); + PendingLock.lock(); + typename std::list::iterator itr; + for(itr = current_data.begin(); itr != current_data.end(); itr++){ + RemoveData(*itr); + } + PendingLock.unlock(); + RemoveAccess(); + } + update(); + } + + bool PendingContains(T key){ + if(!has_pending_data) + return false; + bool ret = false; + PendingLock.lock(); + ret = (pending_data.count(key) > 0 && pending_data[key] == false); + PendingLock.unlock(); + return ret; + } + + unsigned int count(T key){ + unsigned int ret = 0; + while(changing){ + Sleep(1); + } + AddAccess(); + bool retry = false; + if(!changing){ + typename std::list::iterator iter; + for(iter = current_data.begin(); iter != current_data.end(); iter++){ + if(*iter == key) + ret++; + } + } + else + retry = true; + RemoveAccess(); + if(retry) + return count(key); //only occurs whenever we change to changing state at the same time as a reading state + return ret; + } + + void RemoveData(T key, int32 erase_time = 0){ + handle_deletes.AddPendingDelete(key, Timer::GetCurrentTime2() + erase_time); + } + + void Remove(T key, bool erase = false, int32 erase_time = 0){ + while(changing){ + Sleep(1); + } + AddAccess(); + PendingLock.lock(); + pending_data[key] = false; + PendingLock.unlock(); + if(erase) + RemoveData(key, erase_time); + has_pending_data = true; + RemoveAccess(); + update(); + } + + void Add(T key){ + if(count(key) > 0) + return; + while(changing){ + Sleep(1); + } + AddAccess(); + PendingLock.lock(); + pending_data[key] = true; + PendingLock.unlock(); + has_pending_data = true; + RemoveAccess(); + update(); + } +private: + bool update(bool force = false){ + //if(access_count > 5) + // cout << "Possible error.\n"; + while(changing){ + Sleep(1); + } + if(pending_clear && access_count == 0){ + SetChanging(); + while(access_count > 0){ + Sleep(1); + } + AddAccess(); + PendingLock.lock(); + current_data.clear(); + has_pending_data = (pending_data.size() > 0); + PendingLock.unlock(); + pending_clear = false; + RemoveAccess(); + SetNotChanging(); + } + if(!pending_clear && has_pending_data && access_count == 0){ + SetChanging(); + while(access_count > 0){ + Sleep(1); + } + AddAccess(); + PendingLock.lock(); + typename std::map::iterator pending_itr; + for(pending_itr = pending_data.begin(); pending_itr != pending_data.end(); pending_itr++){ + if(pending_itr->second) + current_data.push_back(pending_itr->first); + else + current_data.remove(pending_itr->first); + } + pending_data.clear(); + PendingLock.unlock(); + has_pending_data = false; + RemoveAccess(); + SetNotChanging(); + } + handle_deletes.CheckDeletes(force); + return !pending_clear && !has_pending_data; + } + Locker PendingLock; + Locker AccessLock; + Locker ChangingLock; + volatile int access_count; + std::list current_data; + std::map pending_data; + HandleDeletes handle_deletes; + volatile int access_pending; + volatile bool pending_changing; + volatile bool changing; + volatile bool has_pending_data; + volatile bool pending_clear; +}; +#endif diff --git a/source/WorldServer/MutexMap.h b/source/WorldServer/MutexMap.h new file mode 100644 index 0000000..afa33b0 --- /dev/null +++ b/source/WorldServer/MutexMap.h @@ -0,0 +1,304 @@ +/* + 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 . +*/ +#ifndef MUTEXMAP_H +#define MUTEXMAP_H +#include +#include "MutexHelper.h" + +#define MUTEXMAP_DELETE_TYPE_KEY 1 +#define MUTEXMAP_DELETE_TYPE_VALUE 2 + +template +class MutexMap{ +public: + MutexMap(){ + has_pending_data = false; + pending_clear = false; + changing = false; + access_count = 0; + has_pending_deletes = false; + next_delete_attempt = 0; + delete_all = false; + } + ~MutexMap(){ + update(true); + PendingLock.lock(); + pending_add.clear(); + pending_remove.clear(); + PendingLock.unlock(); + } + class iterator { + private: + typename std::map::iterator itr; // Current element + MutexMap* map; + bool first_itr; + public: + iterator(){ + } + iterator(MutexMap* map){ + this->map = map; + map->update(); + map->SetChanging(); + this->map->AddAccess(); + map->SetNotChanging(); + first_itr = true; + itr = map->current_data.begin(); + if(itr != map->current_data.end()){ + first = itr->first; + second = itr->second; + } + } + ~iterator(){ + map->RemoveAccess(); + } + + bool HasNext(){ + return itr != map->current_data.end(); + } + + bool Next(){ + if(map->pending_clear) + return false; + if(first_itr) + first_itr = false; + else + itr++; + if(itr != map->current_data.end()){ + first = itr->first; + second = itr->second; + map->PendingLock.lock(); + if(map->pending_remove.count(first) > 0){ + map->PendingLock.unlock(); + return Next(); + } + map->PendingLock.unlock(); + return true; + } + return false; + } + iterator* operator->() { + return this; + } + KeyT first; + ValueT second; + }; + int count(KeyT key, bool include_pending = false){ + while(changing){ + Sleep(1); + } + AddAccess(); + int ret = current_data.count(key); + if(include_pending){ + PendingLock.lock(); + ret += pending_add.count(key); + PendingLock.unlock(); + } + RemoveAccess(); + return ret; + } + void clear(bool delete_all = false){ + pending_clear = true; + if(delete_all){ + SetChanging(); + while(access_count > 0){ + Sleep(1); + } + AddAccess(); + PendingLock.lock(); + typename std::map::iterator itr; + for(itr = current_data.begin(); itr != current_data.end(); itr++){ + deleteData(itr->first, MUTEXMAP_DELETE_TYPE_VALUE); + } + PendingLock.unlock(); + RemoveAccess(); + SetNotChanging(); + } + update(); + } + unsigned int size(bool include_pending = false){ + if(include_pending) + return current_data.size() + pending_add.size(); + return current_data.size(); + } + void deleteData(KeyT key, int8 type, int32 erase_time = 0){ + DeleteData* del = new DeleteData(); + del->SetData(type, key, current_data[key], Timer::GetCurrentTime2() + erase_time); + pending_deletes[del] = true; + has_pending_deletes = true; + } + void erase(KeyT key, bool erase_key = false, bool erase_value = false, int32 erase_time = 0){ + while(changing){ + Sleep(1); + } + AddAccess(); + if(current_data.count(key) != 0){ + PendingLock.lock(); + pending_remove[key] = true; + if(erase_key || erase_value){ + int type = 0; + if(erase_key) + type = MUTEXMAP_DELETE_TYPE_KEY; + if(erase_value) + type += MUTEXMAP_DELETE_TYPE_VALUE; + deleteData(key, type, erase_time); + } + has_pending_data = true; + PendingLock.unlock(); + } + RemoveAccess(); + update(); + } + iterator begin(){ + return iterator(this); + } + void Put(KeyT key, ValueT value){ + while(changing){ + Sleep(1); + } + AddAccess(); + PendingLock.lock(); + pending_add[key] = value; + has_pending_data = true; + PendingLock.unlock(); + RemoveAccess(); + update(); + } + ValueT& Get(KeyT key){ + while(changing){ + Sleep(1); + } + AddAccess(); + if(current_data.count(key) > 0 || pending_add.count(key) == 0){ + RemoveAccess(); + return current_data[key]; + } + RemoveAccess(); + return pending_add[key]; + } +private: + void AddAccess(){ + AccessLock.lock(); + ++access_count; + AccessLock.unlock(); + } + + void RemoveAccess(){ + AccessLock.lock(); + --access_count; + AccessLock.unlock(); + } + + void SetChanging(){ + ChangingLock.lock(); + changing = true; + } + + void SetNotChanging(){ + changing = false; + ChangingLock.unlock(); + } + + void update(bool force = false){ + if(pending_clear && (force || access_count == 0)){ + SetChanging(); + while(access_count > 0){ + Sleep(1); + } + AddAccess(); + PendingLock.lock(); + current_data.clear(); + has_pending_data = (pending_add.size() > 0 || pending_remove.size() > 0); + pending_clear = false; + PendingLock.unlock(); + RemoveAccess(); + SetNotChanging(); + } + if(!pending_clear && has_pending_data && (force || access_count == 0)){ + SetChanging(); + while(access_count > 0){ + Sleep(1); + } + AddAccess(); + PendingLock.lock(); + typename std::map::iterator remove_itr; + for(remove_itr = pending_remove.begin(); remove_itr != pending_remove.end(); remove_itr++){ + current_data.erase(remove_itr->first); + } + typename std::map::iterator add_itr; + for(add_itr = pending_add.begin(); add_itr != pending_add.end(); add_itr++){ + current_data[add_itr->first] = add_itr->second; + } + pending_add.clear(); + pending_remove.clear(); + has_pending_data = false; + PendingLock.unlock(); + RemoveAccess(); + SetNotChanging(); + } + if(has_pending_deletes && (force || (Timer::GetCurrentTime2() > next_delete_attempt && access_count == 0))){ + SetChanging(); + while(access_count > 0){ + Sleep(1); + } + AddAccess(); + PendingLock.lock(); + unsigned int time = Timer::GetCurrentTime2(); + typename std::list*> deleteData; + typename std::map*, bool>::iterator remove_itr; + for(remove_itr = pending_deletes.begin(); remove_itr != pending_deletes.end(); remove_itr++){ + if(force || time >= remove_itr->first->GetTime()) + deleteData.push_back(remove_itr->first); + } + DeleteData* data = 0; + typename std::list*>::iterator remove_data_itr; + for(remove_data_itr = deleteData.begin(); remove_data_itr != deleteData.end(); remove_data_itr++){ + data = *remove_data_itr; + if((data->GetType() & MUTEXMAP_DELETE_TYPE_KEY) == MUTEXMAP_DELETE_TYPE_KEY){ + data->DeleteKey(); + } + if((data->GetType() & MUTEXMAP_DELETE_TYPE_VALUE) == MUTEXMAP_DELETE_TYPE_VALUE){ + data->DeleteValue(); + } + pending_deletes.erase(data); + delete data; + } + next_delete_attempt = Timer::GetCurrentTime2() + 1000; + PendingLock.unlock(); + RemoveAccess(); + SetNotChanging(); + has_pending_deletes = (pending_deletes.size() > 0); + } + } + Locker PendingLock; + Locker AccessLock; + Locker ChangingLock; + std::map current_data; + std::map pending_add; + std::map*, bool > pending_deletes; + std::map pending_remove; + volatile unsigned int next_delete_attempt; + volatile int access_count; + volatile bool delete_all; + volatile bool changing; + volatile bool has_pending_data; + volatile bool has_pending_deletes; + volatile bool pending_clear; +}; +#endif diff --git a/source/WorldServer/MutexVector.h b/source/WorldServer/MutexVector.h new file mode 100644 index 0000000..1ca3c28 --- /dev/null +++ b/source/WorldServer/MutexVector.h @@ -0,0 +1,202 @@ +/* + 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 . +*/ +#ifndef MUTEXVECTOR_H +#define MUTEXVECTOR_H +#include +#include "MutexHelper.h" + +#define MUTEXVECTOR_PENDING_ADD 1 +#define MUTEXVECTOR_PENDING_REMOVE 2 +#define MUTEXVECTOR_PENDING_DELETE 3 + +template +class MutexVector{ +public: + MutexVector(){ + has_pending_data = false; + pending_clear = false; + changing = false; + access_count = 0; + } + ~MutexVector(){ + + } + class iterator { + private: + typename std::vector::iterator itr; // Current element + MutexVector* vector; + bool first_itr; + public: + iterator(){ + } + iterator(MutexVector* vector){ + if(vector){ + this->vector = vector; + vector->update(); + ++this->vector->access_count; + first_itr = true; + itr = vector->current_data.begin(); + if(itr != vector->current_data.end()) + value = *itr; + } + else + this->vector = 0; + } + ~iterator(){ + if(vector) + --vector->access_count; + } + + bool HasNext(){ + return itr != vector->current_data.end(); + } + + bool Next(){ + if(vector->pending_clear) + return false; + if(first_itr) + first_itr = false; + else + itr++; + if(itr != vector->current_data.end()){ + value = *itr; + if(vector->pending_data.count(value) > 0 && vector->pending_data[value] == false) //pending delete + return Next(); + return true; + } + return false; + } + iterator* operator->() { + return this; + } + T value; +}; + void update(){ + //if(access_count > 5) + // cout << "Possible error.\n"; + while(changing){ + Sleep(1); + } + if(pending_clear && access_count == 0){ + changing = true; + while(access_count > 0){ + Sleep(1); + } + ++access_count; + current_data.clear(); + has_pending_data = (pending_data.size() > 0); + pending_clear = false; + --access_count; + changing = false; + } + if(!pending_clear && has_pending_data && access_count == 0){ + changing = true; + while(access_count > 0){ + Sleep(1); + } + ++access_count; + typename std::map::iterator pending_itr; + for(pending_itr = pending_data.begin(); pending_itr != pending_data.end(); pending_itr++){ + if(pending_itr->second) + current_data.push_back(pending_itr->first); + else{ + typename std::vector::iterator data_itr; + for(data_itr = current_data.begin(); data_itr != current_data.end(); data_itr++){ + if(*data_itr == pending_itr->first){ + current_data.erase(data_itr); + break; + } + } + } + } + pending_data.clear(); + has_pending_data = false; + --access_count; + changing = false; + } + handle_deletes.CheckDeletes(); + } + unsigned int size(bool include_pending = false){ + if(include_pending) + return current_data.size() + pending_data.size(); + return current_data.size(); + } + iterator begin(){ + return iterator(this); + } + void clear(){ + pending_clear = true; + update(); + } + + unsigned int count(T key){ + unsigned int ret = 0; + while(changing){ + Sleep(1); + } + ++access_count; + typename std::list::iterator iter; + for(iter = current_data.begin(); iter != current_data.end(); iter++){ + if(*iter == key) + ret++; + } + --access_count; + return ret; + } + + void Remove(T key, bool erase = false, unsigned int erase_time = 0){ + while(changing){ + Sleep(1); + } + ++access_count; + pending_data[key] = false; + if(erase) + handle_deletes.AddPendingDelete(key, erase_time); + has_pending_data = true; + --access_count; + update(); + } + + void Add(T key){ + while(changing){ + Sleep(1); + } + ++access_count; + pending_data[key] = true; + has_pending_data = true; + --access_count; + update(); + } + T Get(unsigned int index){ + while(changing){ + Sleep(1); + } + return current_data[index]; + } +private: + volatile int access_count; + std::vector current_data; + std::map pending_data; + HandleDeletes handle_deletes; + volatile bool changing; + volatile bool has_pending_data; + volatile bool pending_clear; +}; +#endif diff --git a/source/WorldServer/NPC.cpp b/source/WorldServer/NPC.cpp new file mode 100644 index 0000000..912cd46 --- /dev/null +++ b/source/WorldServer/NPC.cpp @@ -0,0 +1,1075 @@ +/* + 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 . +*/ +#include "NPC.h" +#include "WorldDatabase.h" +#include +#include "client.h" +#include "World.h" +#include "races.h" +#include "../common/Log.h" +#include "NPC_AI.h" +#include "Appearances.h" +#include "SpellProcess.h" +#include "Skills.h" +#include "Rules/Rules.h" + +extern MasterSpellList master_spell_list; +extern ConfigReader configReader; +extern WorldDatabase database; +extern World world; +extern Races races; +extern Appearance master_appearance_list; +extern MasterSkillList master_skill_list; +extern RuleManager rule_manager; + +NPC::NPC(){ + Initialize(); + if (GetMaxSpeed() > 0) + SetSpeed(GetMaxSpeed()); +} + +NPC::NPC(NPC* old_npc){ + Initialize(); + if(old_npc){ + if(old_npc->GetSizeOffset() > 0){ + int8 offset = old_npc->GetSizeOffset()+1; + sint32 tmp_size = old_npc->size + (rand()%offset - rand()%offset); + if(tmp_size < 0) + tmp_size = 1; + else if(tmp_size >= 0xFFFF) + tmp_size = 0xFFFF; + size = (int16)tmp_size; + } + else + size = old_npc->size; + SetCollector(old_npc->IsCollector()); + SetMerchantID(old_npc->GetMerchantID()); + SetMerchantType(old_npc->GetMerchantType()); + SetMerchantLevelRange(old_npc->GetMerchantMinLevel(), old_npc->GetMerchantMaxLevel()); + SetPrimaryCommands(&old_npc->primary_command_list); + SetSecondaryCommands(&old_npc->secondary_command_list); + appearance_id = old_npc->appearance_id; + database_id = old_npc->database_id; + primary_command_list_id = old_npc->primary_command_list_id; + secondary_command_list_id = old_npc->secondary_command_list_id; + this->SetInfoStruct(old_npc->GetInfoStruct()); + //memcpy(GetInfoStruct(), old_npc->GetInfoStruct(), sizeof(InfoStruct)); + memcpy(&appearance, &old_npc->appearance, sizeof(AppearanceData)); + memcpy(&features, &old_npc->features, sizeof(CharFeatures)); + memcpy(&equipment, &old_npc->equipment, sizeof(EQ2_Equipment)); + if(appearance.min_level < appearance.max_level) + SetLevel(appearance.min_level + rand()%((appearance.max_level - appearance.min_level)+1)); + target = 0; + SetTotalHPBase(old_npc->GetTotalHPBase()); + SetTotalHPBaseInstance(old_npc->GetTotalHPBase()); + SetTotalPowerBase(old_npc->GetTotalPowerBase()); + SetTotalPowerBaseInstance(old_npc->GetTotalPowerBase()); + faction_id = old_npc->faction_id; + movement_interrupted = false; + old_npc->SetQuestsRequired(this); + SetTransporterID(old_npc->GetTransporterID()); + SetAIStrategy(old_npc->GetAIStrategy()); + SetPrimarySpellList(old_npc->GetPrimarySpellList()); + SetSecondarySpellList(old_npc->GetSecondarySpellList()); + SetPrimarySkillList(old_npc->GetPrimarySkillList()); + SetSecondarySkillList(old_npc->GetSecondarySkillList()); + SetEquipmentListID(old_npc->GetEquipmentListID()); + SetAggroRadius(old_npc->GetBaseAggroRadius()); + SetCastPercentage(old_npc->GetCastPercentage()); + SetRandomize(old_npc->GetRandomize()); + if(appearance.randomize > 0) + Randomize(this, appearance.randomize); + CalculateBonuses(); + SetHP(GetTotalHP()); + SetPower(GetTotalPower()); + UpdateWeapons(); + SetSoundsDisabled(old_npc->IsSoundsDisabled()); + SetFlyingCreature(); + SetWaterCreature(); + SetOmittedByDBFlag(old_npc->IsOmittedByDBFlag()); + SetLootTier(old_npc->GetLootTier()); + SetLootDropType(old_npc->GetLootDropType()); + has_spells = old_npc->HasSpells(); + SetScaredByStrongPlayers(old_npc->IsScaredByStrongPlayers()); + } +} + +NPC::~NPC(){ + ResetMovement(); + if(skills){ + map::iterator skill_itr; + for(skill_itr = skills->begin(); skill_itr != skills->end(); skill_itr++){ + safe_delete(skill_itr->second); + } + safe_delete(skills); + } + if(spells) { + vector::iterator itr; + for(itr = spells->begin(); itr != spells->end(); itr++){ + safe_delete((*itr)); + } + spells->clear(); + } + safe_delete(spells); + MutexMap::iterator sb_itr = skill_bonus_list.begin(); + while (sb_itr.Next()) + RemoveSkillBonus(sb_itr.first); + safe_delete(runback); + safe_delete(m_brain); +} + +void NPC::Initialize(){ + ai_strategy = 0; + attack_type = 0; + movement_index = 0; + resume_movement = true; + movement_start_time = 0; + spawn_type = 2; + movement_interrupted = false; + attack_resume_needed = false; + MMovementLoop.SetName("NPC::MMovementLoop"); + last_movement_update = Timer::GetCurrentTime2(); + aggro_radius = 0.0f; + base_aggro_radius = 0.0f; + skills = 0; + spells = 0; + runback = 0; + m_brain = new ::Brain(this); + MBrain.SetName("NPC::m_brain"); + m_runningBack = false; + m_runbackHeadingDir1 = m_runbackHeadingDir2 = 0; + following = false; + SetFollowTarget(0); + m_petDismissing = false; + m_ShardID = 0; + m_ShardCharID = 0; + m_ShardCreatedTimestamp = 0; + m_call_runback = false; + has_spells = false; + cast_on_aggro_completed = false; +} + +EQ2Packet* NPC::serialize(Player* player, int16 version){ + return spawn_serialize(player, version); +} + +void NPC::SetSkills(map* in_skills){ + if (skills) { + map::iterator skill_itr; + for(skill_itr = skills->begin(); skill_itr != skills->end(); skill_itr++){ + safe_delete(skill_itr->second); + } + + safe_delete(skills); + } + + skills = in_skills; +} + +void NPC::SetSpells(vector* in_spells){ + for(int i=0;i::iterator itr; + for(itr = spells->begin(); itr != spells->end(); itr++){ + safe_delete((*itr)); + } + } + safe_delete(spells); + + spells = in_spells; + + if(spells && spells->size() > 0) { + has_spells = true; + + vector::iterator itr; + for(itr = spells->begin(); itr != spells->end();){ + if((*itr)->cast_on_spawn) { + cast_on_spells[CAST_TYPE::CAST_ON_SPAWN].push_back((*itr)); + itr = spells->erase(itr); // we don't keep on the master list + continue; + } + if((*itr)->cast_on_initial_aggro) { + cast_on_spells[CAST_TYPE::CAST_ON_AGGRO].push_back((*itr)); + itr = spells->erase(itr); // we don't keep on the master list + continue; + } + + // didn't hit a continue case, iterate + itr++; + } + } + else { + has_spells = false; + } +} + +void NPC::SetRunbackLocation(float x, float y, float z, int32 gridid, bool set_hp_runback){ + safe_delete(runback); + runback = new MovementLocation; + runback->x = x; + runback->y = y; + runback->z = z; + runback->gridid = gridid; + runback->stage = 0; + runback->reset_hp_on_runback = set_hp_runback; +} + +MovementLocation* NPC::GetRunbackLocation(){ + return runback; +} + +float NPC::GetRunbackDistance(){ + if(!runback) + return 0; + return GetDistance(runback->x, runback->y, runback->z); +} + +void NPC::Runback(float distance, bool stopFollowing){ + if(!runback) + return; + + if ( distance == 0.0f ) + distance = GetRunbackDistance(); // gotta make sure its true, lua doesn't send the distance + + if(stopFollowing) + following = false; + + if (!m_runningBack) + { + ClearRunningLocations(); + GetZone()->movementMgr->StopNavigation((Entity*)this); + } + + m_runningBack = true; + SetSpeed(GetMaxSpeed()*2); + + if (CheckLoS(glm::vec3(runback->x, runback->z, runback->y + 1.0f), glm::vec3(GetX(), GetZ(), GetY() + 1.0f))) + { + FaceTarget(runback->x, runback->z); + ClearRunningLocations(); + GetZone()->movementMgr->DisruptNavigation((Entity*)this); + if (GetRunbackLocation()->gridid > 0) + SetLocation(GetRunbackLocation()->gridid); + AddRunningLocation(runback->x, runback->y, runback->z, GetSpeed(), 0, true, true, "", true); + } + else + GetZone()->movementMgr->NavigateTo((Entity*)this, runback->x, runback->y, runback->z); + + //AddRunningLocation(runback->x, runback->y, runback->z, GetSpeed(), 0, false); + last_movement_update = Timer::GetCurrentTime2(); +} + +void NPC::ClearRunback(){ + safe_delete(runback); + m_runningBack = false; + m_runbackHeadingDir1 = m_runbackHeadingDir2 = 0; + resume_movement = true; + NeedsToResumeMovement(false); +} + +void NPC::StartRunback(bool reset_hp_on_runback) +{ + if(GetRunbackLocation()) + return; + + SetRunbackLocation(GetX(), GetY(), GetZ(), GetLocation(), reset_hp_on_runback); + m_runbackHeadingDir1 = appearance.pos.Dir1; + m_runbackHeadingDir2 = appearance.pos.Dir2; +} + +bool NPC::PauseMovement(int32 period_of_time_ms) +{ + if(period_of_time_ms < 1) + period_of_time_ms = 1; + + if(HasMovementLoop() || HasMovementLocations()) + StartRunback(); + + RunToLocation(GetX(),GetY(),GetZ()); + + pause_timer.Start(period_of_time_ms, true); + + return true; +} + +bool NPC::IsPauseMovementTimerActive() +{ + if(pause_timer.Check()) + { + pause_timer.Disable(); + m_call_runback = true; + } + + return pause_timer.Enabled(); +} + +void NPC::InCombat(bool val){ + if (in_combat == val) + return; + + if(in_combat == false && val && GetZone()){ + LogWrite(NPC__DEBUG, 3, "NPC", "'%s' engaged in combat with '%s'", this->GetName(), ( GetTarget() ) ? GetTarget()->GetName() : "Unknown" ); + GetZone()->CallSpawnScript(this, SPAWN_SCRIPT_ATTACKED, GetTarget()); + SetTempActionState(0); // disable action states in combat + } + if(!in_combat && val){ + // if not a pet and no current run back location set then set one to the current location + bool hadRunback = (GetRunbackLocation() != nullptr); + if(hadRunback) { + pause_timer.Disable(); + if(!GetRunbackLocation()->reset_hp_on_runback) // if we aren't resetting hp it isn't a real reset point, just face target swings + ClearRunback(); + } + if(!IsPet()) { + StartRunback(true); + } + } + + int8 ruleAutoLockEncounter = rule_manager.GetGlobalRule(R_World, AutoLockEncounter)->GetInt8(); + in_combat = val; + if(val){ + LogWrite(NPC__DEBUG, 3, "NPC", "'%s' engaged in combat with '%s'", this->GetName(), ( GetTarget() ) ? GetTarget()->GetName() : "Unknown" ); + if(ruleAutoLockEncounter) { + SetLockedNoLoot(ENCOUNTER_STATE_LOCKED); + } + AddIconValue(64); + // In combat so lets set the NPC's speed to its max speed + if (GetMaxSpeed() > 0) + SetSpeed(GetMaxSpeed()); + } + else{ + SetLockedNoLoot(ENCOUNTER_STATE_AVAILABLE); + RemoveIconValue(64); + if (GetHP() > 0){ + SetTempActionState(-1); //re-enable action states on exiting combat + GetZone()->CallSpawnScript(this, SPAWN_SCRIPT_COMBAT_RESET); + // Stop all HO's attached to this NPC + GetZone()->GetSpellProcess()->KillHOBySpawnID(GetID()); + } + } + if(!MovementInterrupted() && val && GetSpeed() > 0 && movement_loop.size() > 0){ + CalculateRunningLocation(true); + } + MovementInterrupted(val); +} + +bool NPC::HandleUse(Client* client, string type){ + if(!client || type.length() == 0 || (appearance.show_command_icon == 0 && appearance.display_hand_icon == 0)) + return false; + EntityCommand* entity_command = FindEntityCommand(type); + if (entity_command) { + client->GetCurrentZone()->ProcessEntityCommand(entity_command, client->GetPlayer(), client->GetPlayer()->GetTarget()); + return true; + } + return false; + /*Spell* spell = master_spell_list.GetSpellByName((char*)type.c_str()); + if(spell) + client->GetCurrentZone()->ProcessSpell(spell, client->GetPlayer(), client->GetPlayer()->GetTarget()); + else if(GetSpawnScript()) + client->GetCurrentZone()->CallSpawnScript(this, SPAWN_SCRIPT_CUSTOM, client->GetPlayer(), (char*)type.c_str()); + else + return false; + return true;*/ +} + + +bool NPC::CheckSameAppearance(string name, int16 id) +{ + // need to iterate through master_appearance_list finding if id contains name + return true; +} + + +void NPC::Randomize(NPC* npc, int32 flags) +{ + int8 random = 0; + int8 min_val = 0; + int8 max_val = 255; + + /* We need to check if gender is going to be randomized first because if the race is going to be + * randomized, we need to know its gender so we can choose the proper model. + * We also need to make sure the model gets set properly if the player chooses to randomize the gender + * and not the race. */ + int8 old_gender = npc->GetGender(); + + if (flags & RANDOMIZE_GENDER) + { + random = MakeRandomInt(1, 2); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Gender: %i", random); + npc->SetGender(random); + } + + if ((flags & RANDOMIZE_RACE) || (flags & RANDOMIZE_GENDER && old_gender != npc->GetGender()) || (flags & RANDOMIZE_MODEL_TYPE)) + { + string race_string = ""; + int8 gender = npc->GetGender(); + + if (gender == 1 || gender == 2) + { + + if (flags & RANDOMIZE_RACE) + { + if(npc->GetAlignment() == 1) // Good + random = races.GetRaceNameGood(); + else if(npc->GetAlignment() < 0) // Evil + random = races.GetRaceNameEvil(); + else // All + random = MakeRandomInt(0, 20); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Race: %s (%i)", races.GetRaceNameCase(random), random); + npc->SetRace(random); + } + + switch (npc->GetRace()) + { + case BARBARIAN: + // JA: If randomize has "4" (model) and race=0, barbarians show up in place of the real models. + // Started working on a solution (CheckSameAppearance) but cannot get it to work yet. + // Solution for now is to not randomize models for race=0. Set to race=255, or turn off model randomize + race_string = "/barbarian/barbarian"; + break; + case DARK_ELF: + race_string = "/darkelf/darkelf"; + break; + case DWARF: + race_string = "/dwarf/dwarf"; + break; + case ERUDITE: + race_string = "/erudite/erudite"; + break; + case FROGLOK: + race_string = "/froglok/froglok"; + break; + case GNOME: + race_string = "/gnome/gnome"; + break; + case HALF_ELF: + race_string = "/halfelf/halfelf"; + break; + case HALFLING: + race_string = "/halfling/halfling"; + break; + case HIGH_ELF: + race_string = "/highelf/highelf"; + break; + case HUMAN: + race_string = "/human/human"; + break; + case IKSAR: + race_string = "/iksar/iksar"; + break; + case KERRA: + race_string = "/kerra/kerra"; + break; + case OGRE: + race_string = "/ogre/ogre"; + break; + case RATONGA: + race_string = "/ratonga/ratonga"; + break; + case TROLL: + race_string = "/troll/troll"; + break; + case WOOD_ELF: + race_string = "/woodelf/woodelf"; + break; + case FAE: + race_string = "/fae/fae_light"; + break; + case ARASAI: + race_string = "/fae/fae_dark"; + break; + case SARNAK: + gender == 1 ? race_string = "01/sarnak_male/sarnak" : race_string = "01/sarnak_female/sarnak"; + break; + case VAMPIRE: + race_string = "/vampire/vampire"; + break; + case AERAKYN: + race_string = "/aerakyn/aerakyn"; + break; + } + + if (race_string.length() > 0) + { + string gender_string; + + gender == 1 ? gender_string = "male" : gender_string = "female"; + + vector* id_list = database.GetAppearanceIDsLikeName("ec/pc" + race_string + "_" + gender_string); + + if (id_list) + { + int32 index = MakeRandomInt(0, id_list->size() - 1); + npc->SetModelType(id_list->at(index)); + npc->SetSogaModelType(id_list->at(index)); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Model Type: %i", npc->GetModelType()); + int16 wing_type = 0; + + if (npc->GetRace() == FAE) + { + vector* id_list_wings = database.GetAppearanceIDsLikeName("ec/pc/fae_wings/fae_wing"); + if (id_list_wings) { + wing_type = id_list_wings->at(MakeRandomInt(0, id_list_wings->size() - 1)); + safe_delete(id_list_wings); + } + } + else if (npc->GetRace() == ARASAI) + { + vector* id_list_wings = database.GetAppearanceIDsLikeName("ec/pc/fae_wings/fae_d_wing"); + if (id_list_wings) { + wing_type = id_list_wings->at(MakeRandomInt(0, id_list_wings->size() - 1)); + safe_delete(id_list_wings); + } + } + else if (npc->GetRace() == AERAKYN) + { + vector* id_list_wings = database.GetAppearanceIDsLikeName("ec/pc/aerakyn/aerakyn_male_wings"); + if (id_list_wings) { + wing_type = id_list_wings->at(MakeRandomInt(0, id_list_wings->size() - 1)); + safe_delete(id_list_wings); + } + } + if (wing_type > 0) + { + EQ2_Color color1; + EQ2_Color color2; + color1.red = MakeRandomInt(0, 255); + color1.green = MakeRandomInt(0, 255); + color1.blue = MakeRandomInt(0, 255); + color2.red = MakeRandomInt(0, 255); + color2.green = MakeRandomInt(0, 255); + color2.blue = MakeRandomInt(0, 255); + npc->SetWingColor1(color1); + npc->SetWingColor2(color2); + } + npc->SetWingType(wing_type); + safe_delete(id_list); + } + } + } + } + + if (flags & RANDOMIZE_FACIAL_HAIR_TYPE) { + vector* id_list = database.GetAppearanceIDsLikeName("accessories/hair/face"); + if (id_list) { + int32 index = MakeRandomInt(0, id_list->size() - 1); + npc->SetFacialHairType(id_list->at(index)); + npc->SetSogaFacialHairType(id_list->at(index)); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Facial Hair: %i", npc->GetFacialHairType()); + safe_delete(id_list); + } + } + if (flags & RANDOMIZE_HAIR_TYPE) { + vector* id_list = database.GetAppearanceIDsLikeName("accessories/hair/hair"); + if (id_list) { + int32 index = MakeRandomInt(0, id_list->size() - 1); + npc->SetHairType(id_list->at(index)); + npc->SetSogaHairType(id_list->at(index)); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Hair: %i", npc->GetHairType()); + safe_delete(id_list); + } + } + if (flags & RANDOMIZE_WING_TYPE) { + int16 wing_type = 0; + if (npc->GetRace() == FAE) { + vector* id_list_wings = database.GetAppearanceIDsLikeName("ec/pc/fae_wings/fae_wing"); + if (id_list_wings) { + wing_type = id_list_wings->at(MakeRandomInt(0, id_list_wings->size() - 1)); + safe_delete(id_list_wings); + } + } + else if (npc->GetRace() == ARASAI) { + vector* id_list_wings = database.GetAppearanceIDsLikeName("ec/pc/fae_wings/fae_d_wing"); + if (id_list_wings) { + wing_type = id_list_wings->at(MakeRandomInt(0, id_list_wings->size() - 1)); + safe_delete(id_list_wings); + } + } + else if (npc->GetRace() == AERAKYN) + { + vector* id_list_wings = database.GetAppearanceIDsLikeName("ec/pc/aerakyn/aerakyn_male_wings"); + if (id_list_wings) { + wing_type = id_list_wings->at(MakeRandomInt(0, id_list_wings->size() - 1)); + safe_delete(id_list_wings); + } + } + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Wing Type: %i", wing_type); + npc->SetWingType(wing_type); + } + + if (flags & RANDOMIZE_CHEEK_TYPE) { + for(int i=0;i<3;i++) { + random = MakeRandomFloat(-100, 100); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Cheek[%i]: %i", i, random); + npc->features.cheek_type[i] = random; + } + } + if (flags & RANDOMIZE_CHIN_TYPE) { + for(int i=0;i<3;i++) { + random = MakeRandomFloat(-100, 100); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Chin[%i]: %i", i, random); + npc->features.chin_type[i] = MakeRandomFloat(-100, 100); + } + } + if (flags & RANDOMIZE_EAR_TYPE) { + for(int i=0;i<3;i++) { + random = MakeRandomFloat(-100, 100); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Ear[%i]: %i", i, random); + npc->features.ear_type[i] = MakeRandomFloat(-100, 100); + } + } + if (flags & RANDOMIZE_EYE_BROW_TYPE) { + for(int i=0;i<3;i++) { + random = MakeRandomFloat(-100, 100); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Eyebrow[%i]: %i", i, random); + npc->features.eye_brow_type[i] = MakeRandomFloat(-100, 100); + } + } + if (flags & RANDOMIZE_EYE_TYPE) { + for(int i=0;i<3;i++) { + random = MakeRandomFloat(-100, 100); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Eye[%i]: %i", i, random); + npc->features.eye_type[i] = MakeRandomFloat(-100, 100); + } + } + if (flags & RANDOMIZE_LIP_TYPE) { + for(int i=0;i<3;i++) { + random = MakeRandomFloat(-100, 100); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Lip[%i]: %i", i, random); + npc->features.lip_type[i] = MakeRandomFloat(-100, 100); + } + } + if (flags & RANDOMIZE_NOSE_TYPE) { + for(int i=0;i<3;i++) { + random = MakeRandomFloat(-100, 100); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Nose[%i]: %i", i, random); + npc->features.nose_type[i] = MakeRandomFloat(-100, 100); + } + } + + /* Randomize Colors */ + random = MakeRandomInt(0, 255); + if(random > 30) { + min_val = random - MakeRandomInt(0, 30); + max_val = random + MakeRandomInt(0, 30); + } + if(max_val > 255) + max_val = 255; + + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Color Ranges, random: %i, min: %i, max: %i", random, min_val, max_val); + + if (flags & RANDOMIZE_EYE_COLOR) { + npc->features.eye_color.red = MakeRandomInt(min_val, max_val); + npc->features.eye_color.green = MakeRandomInt(min_val, max_val); + npc->features.eye_color.blue = MakeRandomInt(min_val, max_val); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Eye Color - R: %i, G: %i, B: %i", npc->features.eye_color.red, npc->features.eye_color.green, npc->features.eye_color.blue); + } + if (flags & RANDOMIZE_HAIR_COLOR1) { + npc->features.hair_color1.red = MakeRandomInt(min_val, max_val); + npc->features.hair_color1.green = MakeRandomInt(min_val, max_val); + npc->features.hair_color1.blue = MakeRandomInt(min_val, max_val); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Hair Color 1 - R: %i, G: %i, B: %i", npc->features.hair_color1.red, npc->features.hair_color1.green, npc->features.hair_color1.blue); + } + if (flags & RANDOMIZE_HAIR_COLOR2) { + npc->features.hair_color2.red = MakeRandomInt(min_val, max_val); + npc->features.hair_color2.green = MakeRandomInt(min_val, max_val); + npc->features.hair_color2.blue = MakeRandomInt(min_val, max_val); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Hair Color 2 - R: %i, G: %i, B: %i", npc->features.hair_color2.red, npc->features.hair_color2.green, npc->features.hair_color2.blue); + } + if (flags & RANDOMIZE_HAIR_HIGHLIGHT) { + npc->features.hair_highlight_color.red = MakeRandomInt(min_val, max_val); + npc->features.hair_highlight_color.green = MakeRandomInt(min_val, max_val); + npc->features.hair_highlight_color.blue = MakeRandomInt(min_val, max_val); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Hair Highlight - R: %i, G: %i, B: %i", npc->features.hair_highlight_color.red, npc->features.hair_highlight_color.green, npc->features.hair_highlight_color.blue); + } + if (flags & RANDOMIZE_HAIR_FACE_COLOR) { + EQ2_Color color1; + color1.red = MakeRandomInt(min_val, max_val); + color1.green = MakeRandomInt(min_val, max_val); + color1.blue = MakeRandomInt(min_val, max_val); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Facial Hair Color - R: %i, G: %i, B: %i", color1.red, color1.green, color1.blue); + npc->SetFacialHairColor(color1); + } + if (flags & RANDOMIZE_HAIR_FACE_HIGHLIGHT_COLOR) { + EQ2_Color color1; + color1.red = MakeRandomInt(min_val, max_val); + color1.green = MakeRandomInt(min_val, max_val); + color1.blue = MakeRandomInt(min_val, max_val); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Facial Hair Highlight - R: %i, G: %i, B: %i", color1.red, color1.green, color1.blue); + npc->SetFacialHairHighlightColor(color1); + } + if (flags & RANDOMIZE_HAIR_TYPE_COLOR) { + EQ2_Color color1; + color1.red = MakeRandomInt(min_val, max_val); + color1.green = MakeRandomInt(min_val, max_val); + color1.blue = MakeRandomInt(min_val, max_val); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Hair Type Color - R: %i, G: %i, B: %i", color1.red, color1.green, color1.blue); + npc->SetHairColor(color1); + } + if (flags & RANDOMIZE_HAIR_TYPE_HIGHLIGHT_COLOR) { + EQ2_Color color1; + color1.red = MakeRandomInt(min_val, max_val); + color1.green = MakeRandomInt(min_val, max_val); + color1.blue = MakeRandomInt(min_val, max_val); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Hair Type Highlight - R: %i, G: %i, B: %i", color1.red, color1.green, color1.blue); + npc->SetHairTypeHighlightColor(color1); + } + if (flags & RANDOMIZE_SKIN_COLOR) { + npc->features.skin_color.red = MakeRandomInt(min_val, max_val); + npc->features.skin_color.green = MakeRandomInt(min_val, max_val); + npc->features.skin_color.blue = MakeRandomInt(min_val, max_val); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Skin Color - R: %i, G: %i, B: %i", npc->features.eye_color.red, npc->features.eye_color.green, npc->features.eye_color.blue); + } + if (flags & RANDOMIZE_WING_COLOR1) { + EQ2_Color color1; + color1.red = MakeRandomInt(min_val, max_val); + color1.green = MakeRandomInt(min_val, max_val); + color1.blue = MakeRandomInt(min_val, max_val); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Wing Color 1 - R: %i, G: %i, B: %i", color1.red, color1.green, color1.blue); + npc->SetWingColor1(color1); + } + if (flags & RANDOMIZE_WING_COLOR2) { + EQ2_Color color1; + color1.red = MakeRandomInt(min_val, max_val); + color1.green = MakeRandomInt(min_val, max_val); + color1.blue = MakeRandomInt(min_val, max_val); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Wing Color 2 - R: %i, G: %i, B: %i", color1.red, color1.green, color1.blue); + npc->SetWingColor2(color1); + } +} + +Skill* NPC::GetSkillByName(const char* name, bool check_update){ + if(skills && skills->count(name) > 0){ + Skill* ret = (*skills)[name]; + if(ret && check_update && ret->current_val < ret->max_val && (rand()%100) >= 90) + ret->current_val++; + return ret; + } + return 0; +} + +Skill* NPC::GetSkillByID(int32 id, bool check_update){ + Skill* skill = master_skill_list.GetSkill(id); + + if(skill && skills && skills->count(skill->name.data) > 0){ + Skill* ret = (*skills)[skill->name.data]; + if(ret && check_update && ret->current_val < ret->max_val && (rand()%100) >= 90) + ret->current_val++; + return ret; + } + return 0; +} + +int8 NPC::GetAttackType(){ + return attack_type; +} + +void NPC::SetAIStrategy(int8 strategy){ + ai_strategy = strategy; +} + +int8 NPC::GetAIStrategy(){ + return ai_strategy; +} + +void NPC::SetPrimarySpellList(int32 id){ + primary_spell_list = id; +} + +int32 NPC::GetPrimarySpellList(){ + return primary_spell_list; +} + +void NPC::SetSecondarySpellList(int32 id){ + secondary_spell_list = id; +} + +int32 NPC::GetSecondarySpellList(){ + return secondary_spell_list; +} + +void NPC::SetPrimarySkillList(int32 id){ + primary_skill_list = id; +} + +int32 NPC::GetPrimarySkillList(){ + return primary_skill_list; +} + +void NPC::SetSecondarySkillList(int32 id){ + secondary_skill_list = id; +} + +int32 NPC::GetSecondarySkillList(){ + return secondary_skill_list; +} + +void NPC::SetEquipmentListID(int32 id){ + equipment_list_id = id; +} + +int32 NPC::GetEquipmentListID(){ + return equipment_list_id; +} + +Spell* NPC::GetNextSpell(Spawn* target, float distance){ + if(!cast_on_aggro_completed) { + Spell* ret = nullptr; + Spell* tmpSpell = nullptr; + vector::iterator itr; + Spawn* tmpTarget = target; + for (itr = cast_on_spells[CAST_ON_AGGRO].begin(); itr != cast_on_spells[CAST_ON_AGGRO].end(); itr++) { + tmpSpell = master_spell_list.GetSpell((*itr)->spell_id, (*itr)->tier); + + if(!tmpSpell) + continue; + if (tmpSpell->GetSpellData()->friendly_spell > 0) { + tmpTarget = (Spawn*)this; + } + if (tmpSpell->GetSpellData()) { + SpellEffects* effect = ((Entity*)tmpTarget)->GetSpellEffect(tmpSpell->GetSpellID()); + if (!effect) { + ret = tmpSpell; + + if (tmpSpell->GetSpellData()->friendly_spell > 0) { + tmpTarget = target; + } + break; + } + } + if (tmpSpell->GetSpellData()->friendly_spell > 0) { + tmpTarget = target; + } + } + + if(ret) { + return ret; + } + else { + cast_on_aggro_completed = true; + } + } + + int8 val = rand()%100; + if(ai_strategy == AI_STRATEGY_OFFENSIVE){ + if(val >= 20)//80% chance to cast offensive spell if Offensive AI + return GetNextSpell(distance, AI_STRATEGY_OFFENSIVE); + return GetNextSpell(distance, AI_STRATEGY_DEFENSIVE); + } + else if(ai_strategy == AI_STRATEGY_DEFENSIVE){ + if(val >= 20)//80% chance to cast defensive spell if Defensive AI + return GetNextSpell(distance, AI_STRATEGY_DEFENSIVE); + return GetNextSpell(distance, AI_STRATEGY_OFFENSIVE); + } + return GetNextSpell(distance, AI_STRATEGY_BALANCED); +} + +Spell* NPC::GetNextSpell(float distance, int8 type){ + Spell* ret = 0; + if(spells){ + if(distance < 0) + distance = 0; + Spell* tmpSpell = 0; + vector::iterator itr; + for(itr = spells->begin(); itr != spells->end(); itr++){ + // if positive, then say the hp ratio must be GREATER than OR EQUAL TO + if((*itr)->required_hp_ratio > 0 && (*itr)->required_hp_ratio < 101 && GetIntHPRatio() >= (*itr)->required_hp_ratio) + continue; + // if negative, then say the hp ratio must be LESS than OR EQUAL TO + if((*itr)->required_hp_ratio < 0 && (*itr)->required_hp_ratio > -101 && (-(*itr)->required_hp_ratio) >= GetIntHPRatio()) + continue; + tmpSpell = master_spell_list.GetSpell((*itr)->spell_id, (*itr)->tier); + if(!tmpSpell || (type == AI_STRATEGY_OFFENSIVE && tmpSpell->GetSpellData()->friendly_spell > 0)) + continue; + if (tmpSpell->GetSpellData()->cast_type == SPELL_CAST_TYPE_TOGGLE) + continue; + if(type == AI_STRATEGY_DEFENSIVE && tmpSpell->GetSpellData()->friendly_spell == 0) + continue; + if(distance <= tmpSpell->GetSpellData()->range && distance >= tmpSpell->GetSpellData()->min_range && GetPower() >= tmpSpell->GetPowerRequired(this)){ + ret = tmpSpell; + if((rand()%100) >= 70) //30% chance to stop after finding the first match, this will give the appearance of the NPC randomly choosing a spell to cast + break; + } + } + if(!ret && type != AI_STRATEGY_BALANCED) + ret = GetNextSpell(distance, AI_STRATEGY_BALANCED); //wasnt able to find a valid match, so find any spell that the NPC has + } + return ret; +} + +Spell* NPC::GetNextBuffSpell(Spawn* target) { + if(!target) { + target = (Spawn*)this; + } + + Spell* ret = 0; + + if(!target->IsEntity()) { + return ret; + } + + if (spells && GetZone()->GetSpellProcess()) { + Spell* tmpSpell = 0; + vector::iterator itr; + for (itr = cast_on_spells[CAST_ON_SPAWN].begin(); itr != cast_on_spells[CAST_ON_SPAWN].end(); itr++) { + tmpSpell = master_spell_list.GetSpell((*itr)->spell_id, (*itr)->tier); + if (tmpSpell && tmpSpell->GetSpellData()) { + SpellEffects* effect = ((Entity*)target)->GetSpellEffect(tmpSpell->GetSpellID()); + if (effect) { + if (effect->tier < tmpSpell->GetSpellTier()) { + ret = tmpSpell; + break; + } + } + else { + ret = tmpSpell; + break; + } + } + } + + for (itr = spells->begin(); itr != spells->end(); itr++) { + // if positive, then say the hp ratio must be GREATER than OR EQUAL TO + if((*itr)->required_hp_ratio > 0 && (*itr)->required_hp_ratio < 101 && GetIntHPRatio() >= (*itr)->required_hp_ratio) + continue; + // if negative, then say the hp ratio must be LESS than OR EQUAL TO + if((*itr)->required_hp_ratio < 0 && (*itr)->required_hp_ratio > -101 && (-(*itr)->required_hp_ratio) >= GetIntHPRatio()) + continue; + tmpSpell = master_spell_list.GetSpell((*itr)->spell_id, (*itr)->tier); + if (tmpSpell && tmpSpell->GetSpellData() && tmpSpell->GetSpellData()->cast_type == SPELL_CAST_TYPE_TOGGLE) { + SpellEffects* effect = ((Entity*)target)->GetSpellEffect(tmpSpell->GetSpellID()); + if (effect) { + if (effect->tier < tmpSpell->GetSpellTier()) { + ret = tmpSpell; + break; + } + } + else { + ret = tmpSpell; + break; + } + } + } + } + return ret; +} + +void NPC::SetAggroRadius(float radius, bool overrideBaseValue){ + if (base_aggro_radius == 0.0f || overrideBaseValue) + base_aggro_radius = radius; + + aggro_radius = radius; +} + +float NPC::GetAggroRadius(){ + return aggro_radius; +} + +void NPC::SetCastPercentage(int8 percentage){ + cast_percentage = percentage; +} + +int8 NPC::GetCastPercentage(){ + return cast_percentage; +} + +void NPC::AddSkillBonus(int32 spell_id, int32 skill_id, float value) { + if (value != 0) { + SkillBonus* sb; + if (skill_bonus_list.count(spell_id) == 0) { + sb = new SkillBonus; + sb->spell_id = spell_id; + skill_bonus_list.Put(spell_id, sb); + } + else + sb = skill_bonus_list.Get(spell_id); + if (sb->skills[skill_id] == 0) { + SkillBonusValue* sbv = new SkillBonusValue; + sbv->skill_id = skill_id; + sbv->value = value; + sb->skills[skill_id] = sbv; + if (skills) { + map::iterator itr; + for (itr = skills->begin(); itr != skills->end(); itr++) { + Skill* skill = itr->second; + if (skill->skill_id == sbv->skill_id) { + skill->current_val += (int16)sbv->value; + skill->max_val += (int16)sbv->value; + } + } + } + } + } +} + +void NPC::RemoveSkillBonus(int32 spell_id) { + if (skill_bonus_list.count(spell_id) > 0) { + SkillBonus* sb = skill_bonus_list.Get(spell_id); + skill_bonus_list.erase(spell_id); + map::iterator itr; + for (itr = sb->skills.begin(); itr != sb->skills.end(); itr++) { + SkillBonusValue* sbv = itr->second; + if (skills) { + map::iterator skill_itr; + for (skill_itr = skills->begin(); skill_itr != skills->end(); skill_itr++) { + Skill* skill = skill_itr->second; + if (sbv->skill_id == skill->skill_id) { + skill->current_val -= (int16)sbv->value; + skill->max_val -= (int16)sbv->value; + } + } + } + safe_delete(sbv); + } + safe_delete(sb); + } +} + +void NPC::SetBrain(::Brain* brain) { + // Again, had to use the '::' to refer to the Brain class and not the function defined in the NPC class + MBrain.writelock(__FUNCTION__, __LINE__); + // Check to make sure the NPC the brain controls matches this npc + if (brain && brain->GetBody() != this) { + LogWrite(NPC_AI__ERROR, 0, "NPC_AI", "Brain body does not match the npc we tried to assign the brain to."); + MBrain.releasewritelock(__FUNCTION__, __LINE__); + return; + } + + // Store the old brain in a temp pointer so we can delete it later + ::Brain* old_brain = m_brain; + // Set the brain for this NPC to the new brain + m_brain = brain; + // Release the lock + MBrain.releasewritelock(__FUNCTION__, __LINE__); + // Delete the old brain + safe_delete(old_brain); +} + +void NPC::SetZone(ZoneServer* in_zone, int32 version) { + Spawn::SetZone(in_zone, version); + if (in_zone){ + GetZone()->SetNPCEquipment(this); + SetSkills(GetZone()->GetNPCSkills(primary_skill_list, secondary_skill_list)); + SetSpells(world.GetNPCSpells(primary_spell_list, secondary_spell_list)); + } +} diff --git a/source/WorldServer/NPC.h b/source/WorldServer/NPC.h new file mode 100644 index 0000000..22cca52 --- /dev/null +++ b/source/WorldServer/NPC.h @@ -0,0 +1,217 @@ +/* + 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 . +*/ +#ifndef __EQ2_NPC__ +#define __EQ2_NPC__ +#include +#include "Entity.h" +#include "MutexMap.h" + +#define AI_STRATEGY_BALANCED 1 +#define AI_STRATEGY_OFFENSIVE 2 +#define AI_STRATEGY_DEFENSIVE 3 + + +// Randomize Appearances +#define RANDOMIZE_GENDER 1 +#define RANDOMIZE_RACE 2 +#define RANDOMIZE_MODEL_TYPE 4 + +// Randomize appearance id (spawn_npcs table values) +#define RANDOMIZE_FACIAL_HAIR_TYPE 8 // was RANDOMIZE_FACIAL_HAIR +#define RANDOMIZE_HAIR_TYPE 16 // was RANDOMIZE_HAIR +//#define RANDOMIZE_LEGS_TYPE 32 // spare! +#define RANDOMIZE_WING_TYPE 64 + +// Randomize parameters (npc_appearances, sInt values) +#define RANDOMIZE_CHEEK_TYPE 128 +#define RANDOMIZE_CHIN_TYPE 256 +#define RANDOMIZE_EAR_TYPE 512 +#define RANDOMIZE_EYE_BROW_TYPE 1024 +#define RANDOMIZE_EYE_TYPE 2048 +#define RANDOMIZE_LIP_TYPE 4096 +#define RANDOMIZE_NOSE_TYPE 8192 + +// Randomize colors/hues (npc_appearances, RGB values) +#define RANDOMIZE_EYE_COLOR 16384 +#define RANDOMIZE_HAIR_COLOR1 32768 +#define RANDOMIZE_HAIR_COLOR2 65536 +#define RANDOMIZE_HAIR_HIGHLIGHT 131072 +#define RANDOMIZE_HAIR_FACE_COLOR 262144 // was RANDOMIZE_FACIAL_HAIR_COLOR +#define RANDOMIZE_HAIR_FACE_HIGHLIGHT_COLOR 524288 +#define RANDOMIZE_HAIR_TYPE_COLOR 1048576 // was RANDOMIZE_HAIR_COLOR +#define RANDOMIZE_HAIR_TYPE_HIGHLIGHT_COLOR 2097152 +#define RANDOMIZE_SKIN_COLOR 4194304 +#define RANDOMIZE_WING_COLOR1 8388608 +#define RANDOMIZE_WING_COLOR2 16777216 + +// All Flags On: 33554431 + +#define PET_TYPE_COMBAT 1 +#define PET_TYPE_CHARMED 2 +#define PET_TYPE_DEITY 3 +#define PET_TYPE_COSMETIC 4 +#define PET_TYPE_DUMBFIRE 5 + +enum CAST_TYPE { + CAST_ON_SPAWN=0, + CAST_ON_AGGRO=1, + MAX_CAST_TYPES=2 +}; +class Brain; + +class NPCSpell { +public: + NPCSpell() { + + } + + NPCSpell(NPCSpell* inherit) { + list_id = inherit->list_id; + spell_id = inherit->spell_id; + tier = inherit->tier; + cast_on_spawn = inherit->cast_on_spawn; + cast_on_initial_aggro = inherit->cast_on_initial_aggro; + required_hp_ratio = inherit->required_hp_ratio; + } + + int32 list_id; + int32 spell_id; + int8 tier; + bool cast_on_spawn; + bool cast_on_initial_aggro; + sint8 required_hp_ratio; +}; + +class NPC : public Entity { +public: + NPC(); + NPC(NPC* old_npc); + virtual ~NPC(); + void Initialize(); + EQ2Packet* serialize(Player* player, int16 version); + void SetAppearanceID(int32 id){ appearance_id = id; } + int32 GetAppearanceID(){ return appearance_id; } + bool IsNPC(){ return true; } + void StartRunback(bool reset_hp_on_runback = false); + void InCombat(bool val); + bool HandleUse(Client* client, string type); + void SetRandomize(int32 value) {appearance.randomize = value;} + void AddRandomize(sint32 value) {appearance.randomize += value;} + int32 GetRandomize() {return appearance.randomize;} + bool CheckSameAppearance(string name, int16 id); + void Randomize(NPC* npc, int32 flags); + Skill* GetSkillByName(const char* name, bool check_update = false); + Skill* GetSkillByID(int32 id, bool check_update = false); + int8 GetAttackType(); + void SetAIStrategy(int8 strategy); + int8 GetAIStrategy(); + void SetPrimarySpellList(int32 id); + int32 GetPrimarySpellList(); + void SetSecondarySpellList(int32 id); + int32 GetSecondarySpellList(); + void SetPrimarySkillList(int32 id); + int32 GetPrimarySkillList(); + void SetSecondarySkillList(int32 id); + int32 GetSecondarySkillList(); + void SetEquipmentListID(int32 id); + int32 GetEquipmentListID(); + Spell* GetNextSpell(Spawn* target, float distance); + virtual Spell* GetNextBuffSpell(Spawn* target = 0); + void SetAggroRadius(float radius, bool overrideBaseValue = false); + float GetAggroRadius(); + float GetBaseAggroRadius() { return base_aggro_radius; } + void SetCastPercentage(int8 percentage); + int8 GetCastPercentage(); + void SetSkills(map* in_skills); + void SetSpells(vector* in_spells); + void SetRunbackLocation(float x, float y, float z, int32 gridid, bool set_hp_runback = false); + MovementLocation* GetRunbackLocation(); + float GetRunbackDistance(); + void Runback(float distance=0.0f, bool stopFollowing = true); + void ClearRunback(); + + virtual bool PauseMovement(int32 period_of_time_ms); + virtual bool IsPauseMovementTimerActive(); + + void AddSkillBonus(int32 spell_id, int32 skill_id, float value); + virtual void RemoveSkillBonus(int32 spell_id); + virtual void SetZone(ZoneServer* zone, int32 version=0); + + void SetMaxPetLevel(int8 val) { m_petMaxLevel = val; } + int8 GetMaxPetLevel() { return m_petMaxLevel; } + + void ProcessCombat(); + + /// Sets the brain this NPC should use + /// The brain this npc should use + void SetBrain(Brain* brain); + /// Gets the current brain this NPC uses + /// The Brain this NPC uses + ::Brain* Brain() { return m_brain; } + bool m_runningBack; + sint16 m_runbackHeadingDir1; + sint16 m_runbackHeadingDir2; + + int32 GetShardID() { return m_ShardID; } + void SetShardID(int32 shardid) { m_ShardID = shardid; } + + int32 GetShardCharID() { return m_ShardCharID; } + void SetShardCharID(int32 charid) { m_ShardCharID = charid; } + + sint64 GetShardCreatedTimestamp() { return m_ShardCreatedTimestamp; } + void SetShardCreatedTimestamp(sint64 timestamp) { m_ShardCreatedTimestamp = timestamp; } + + bool HasSpells() { return has_spells; } + + std::atomic m_call_runback; + std::atomic cast_on_aggro_completed; +private: + MovementLocation* runback; + int8 cast_percentage; + float aggro_radius; + float base_aggro_radius; + Spell* GetNextSpell(float distance, int8 type); + map* skills; + vector* spells; + vector cast_on_spells[CAST_TYPE::MAX_CAST_TYPES]; + int32 primary_spell_list; + int32 secondary_spell_list; + int32 primary_skill_list; + int32 secondary_skill_list; + int32 equipment_list_id; + int8 attack_type; + int8 ai_strategy; + int32 appearance_id; + int32 npc_id; + MutexMap skill_bonus_list; + int8 m_petMaxLevel; + + // Because I named the get function Brain() as well we need to use '::' to specify we are refering to + // the brain class and not the function defined above + ::Brain* m_brain; + Mutex MBrain; + + int32 m_ShardID; + int32 m_ShardCharID; + sint64 m_ShardCreatedTimestamp; + bool has_spells; +}; +#endif + diff --git a/source/WorldServer/NPC_AI.cpp b/source/WorldServer/NPC_AI.cpp new file mode 100644 index 0000000..ff89588 --- /dev/null +++ b/source/WorldServer/NPC_AI.cpp @@ -0,0 +1,902 @@ +/* + 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 . +*/ + +#include "NPC_AI.h" +#include "Combat.h" +#include "zoneserver.h" +#include "Spells.h" +#include "../common/Log.h" +#include "LuaInterface.h" +#include "World.h" +#include "Rules/Rules.h" + +extern RuleManager rule_manager; + +extern LuaInterface* lua_interface; +extern World world; + +/* The NEW AI code */ + +Brain::Brain(NPC* npc) { + // Set the npc this brain will controll + m_body = npc; + // Set the default time between calls to think to 250 miliseconds (1/4 a second) + m_tick = 250; + m_lastTick = Timer::GetCurrentTime2(); + m_spellRecovery = 0; + m_playerInEncounter = false; + // Set up the mutex for the hate list + MHateList.SetName("Brain::m_hatelist"); + // Set up the mutex for the encounter list + MEncounter.SetName("Brain::m_encounter"); +} + +Brain::~Brain() { + +} + +void Brain::Think() { + if (m_body->IsPet() && m_body->GetOwner() && m_body->GetOwner()->IsPlayer()) { + Player* player = (Player*)m_body->GetOwner(); + if(player->GetInfoStruct()->get_pet_id() == 0) { + player->GetInfoStruct()->set_pet_id(player->GetIDWithPlayerSpawn(m_body)); + player->SetCharSheetChanged(true); + } + } + // Get the entity this NPC hates the most, + // GetMostHated() will handle dead spawns so no need to check the health in this function + Entity* target = GetMostHated(); + + // If mezzed, stunned or feared we can't do anything so skip + if (!m_body->IsMezzedOrStunned()) { + // Not mezzed or stunned + + // Get the distance to the runback location + float run_back_distance = m_body->GetRunbackDistance(); + + if (target) { + LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "%s has %s targeted.", m_body->GetName(), target->GetName()); + // NPC has an entity that it hates + // Set the NPC's target to the most hated entity if it is not already. + if (m_body->GetTarget() != target) { + m_body->SetTarget(target); + } + m_body->FaceTarget(target, false); + // target needs to be set before in combat is engaged + + // If the NPC is not in combat then put them in combat + if (!m_body->EngagedInCombat()) { + m_body->ClearRunningLocations(); + m_body->InCombat(true); + m_body->cast_on_aggro_completed = false; + m_body->GetZone()->CallSpawnScript(m_body, SPAWN_SCRIPT_AGGRO, target); + } + + bool breakWaterPursuit = false; + if (m_body->IsWaterCreature() && !m_body->IsFlyingCreature() && !target->InWater()) + breakWaterPursuit = true; + // Check to see if the NPC has exceeded the max chase distance + if (run_back_distance > MAX_CHASE_DISTANCE || breakWaterPursuit) { + LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "Run back distance is greater then max chase distance, run_back_distance = %f", run_back_distance); + // Over the max chase distance, Check to see if the target is is a client + if (target->IsPlayer() && ((Player*)target)->GetClient()) + { + // Target is a client so send encounter break messages + if (m_body->HasSpawnGroup()) + ((Player*)target)->GetClient()->SimpleMessage(CHANNEL_NARRATIVE, "This encounter will no longer give encounter rewards."); + else + ((Player*)target)->GetClient()->Message(CHANNEL_NARRATIVE, "%s is no longer worth any experience or treasure.", m_body->GetName()); + } + + // Clear the hate list for this NPC + ClearHate(); + // Clear the encounter list + ClearEncounter(); + } + else { + // Still within max chase distance lets to the combat stuff now + + float distance = m_body->GetDistance(target); + + if(!m_body->IsCasting() && (!HasRecovered() || !ProcessSpell(target, distance))) { + LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "%s is attempting melee on %s.", m_body->GetName(), target->GetName()); + m_body->FaceTarget(target, false); + ProcessMelee(target, distance); + } + } + } + else { + // Nothing in the hate list + bool wasInCombat = m_body->EngagedInCombat(); + // Check to see if the NPC is still flagged as in combat for some reason + if (m_body->EngagedInCombat()) { + // If it is set the combat flag to false + m_body->InCombat(false); + + // Do not set a players pet to full health once they stop combat + if (!m_body->IsPet() || (m_body->IsPet() && m_body->GetOwner() && !m_body->GetOwner()->IsPlayer())) + m_body->SetHP(m_body->GetTotalHP()); + } + + CheckBuffs(); + + // If run back distance is greater then 0 then run back + if(!m_body->EngagedInCombat() && !m_body->IsPauseMovementTimerActive()) + { + if (run_back_distance > 1 || (m_body->m_call_runback && !m_body->following)) { + m_body->SetLockedNoLoot(ENCOUNTER_STATE_BROKEN); + m_body->UpdateEncounterState(ENCOUNTER_STATE_BROKEN); + m_body->GetZone()->AddChangedSpawn(m_body); + m_body->Runback(run_back_distance); + m_body->m_call_runback = false; + } + else if (m_body->GetRunbackLocation()) + { + switch(m_body->GetRunbackLocation()->stage) + { + case 0: + m_body->GetZone()->movementMgr->StopNavigation((Entity*)m_body); + m_body->ClearRunningLocations(); + m_body->SetX(m_body->GetRunbackLocation()->x,false); + m_body->SetZ(m_body->GetRunbackLocation()->z,false); + m_body->SetY(m_body->GetRunbackLocation()->y,false); + m_body->CalculateRunningLocation(true); + m_body->GetRunbackLocation()->stage = 1; + + m_body->GetZone()->AddChangedSpawn(m_body); + break; + case 6: // artificially 1500ms per 250ms Think() call + if (m_body->GetRunbackLocation()->gridid > 0) + m_body->SetLocation(m_body->GetRunbackLocation()->gridid); + if(m_body->GetTempActionState() == 0) + m_body->SetTempActionState(-1); + + m_body->SetHeading(m_body->m_runbackHeadingDir1,m_body->m_runbackHeadingDir2,false); + + if(m_body->GetRunbackLocation()->reset_hp_on_runback) + m_body->SetHP(m_body->GetTotalHP()); + + m_body->ClearRunback(); + + if(m_body->GetLockedNoLoot() != ENCOUNTER_STATE_AVAILABLE && m_body->Alive()) { + m_body->SetLockedNoLoot(ENCOUNTER_STATE_AVAILABLE); + m_body->UpdateEncounterState(ENCOUNTER_STATE_AVAILABLE); + } + + m_body->GetZone()->AddChangedSpawn(m_body); + break; + default: // captures case 1 up to case 5 to turn around / reset hp + m_body->GetRunbackLocation()->stage++; // artificially delay + break; + + } + } + } + // If encounter size is greater then 0 then clear it + if (GetEncounterSize() > 0) + ClearEncounter(); + } + } +} + +sint32 Brain::GetHate(Entity* entity) { + // We will use this variable to return the value, default to 0 + sint32 ret = 0; + + // Lock the hate list, not altering it so do a read lock + MHateList.readlock(__FUNCTION__, __LINE__); + + // First check to see if the given entity is even in the hate list + if (m_hatelist.count(entity->GetID()) > 0) + // Entity in the hate list so get the hate value for the entity + ret = m_hatelist[entity->GetID()]; + + // Unlock the hate list + MHateList.releasereadlock(__FUNCTION__, __LINE__); + + // return the hate + return ret; +} + +void Brain::AddHate(Entity* entity, sint32 hate) { + // do not aggro when running back, despite taking damage + if (m_body->IsNPC() && ((NPC*)m_body)->m_runningBack) + return; + else if (m_body->IsPet() && m_body->IsEntity() && ((Entity*)m_body)->GetOwner() == entity) + return; + + if(m_body->IsImmune(IMMUNITY_TYPE_TAUNT)) + { + LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "%s is immune to taunt from entity %s.", m_body->GetName(), entity ? entity->GetName() : "(null)"); + if(entity && entity->IsPlayer()) + ((Player*)entity)->GetClient()->GetCurrentZone()->SendDamagePacket((Spawn*)entity, (Spawn*)m_body, DAMAGE_PACKET_TYPE_RANGE_SPELL_DMG, DAMAGE_PACKET_RESULT_IMMUNE, 0, 0, "Hate"); + return; + } + + // Lock the hate list, we are altering the list so use write lock + MHateList.writelock(__FUNCTION__, __LINE__); + + if (m_hatelist.count(entity->GetID()) > 0) { + m_hatelist[entity->GetID()] += hate; + // take into consideration that 0 or negative hate is not valid, we need to properly reset the value + if(m_hatelist[entity->GetID()] < 1) { + m_hatelist[entity->GetID()] = 1; + } + } + else + m_hatelist.insert(std::pair(entity->GetID(), hate)); + + entity->MHatedBy.lock(); + if (entity->HatedBy.count(m_body->GetID()) == 0) + entity->HatedBy.insert(m_body->GetID()); + entity->MHatedBy.unlock(); + + // Unlock the list + bool ownerExistsAddHate = false; + + if(entity->IsPet() && entity->GetOwner()) { + map::iterator itr = m_hatelist.find(entity->GetOwner()->GetID()); + if(itr == m_hatelist.end()) { + ownerExistsAddHate = true; + } + } + MHateList.releasewritelock(__FUNCTION__, __LINE__); + if(ownerExistsAddHate) { + AddHate(entity->GetOwner(), 0); + } +} + +void Brain::ClearHate() { + // Lock the hate list, we are altering the list so use a write lock + MHateList.writelock(__FUNCTION__, __LINE__); + + map::iterator itr; + for (itr = m_hatelist.begin(); itr != m_hatelist.end(); itr++) { + Spawn* spawn = m_body->GetZone()->GetSpawnByID(itr->first); + if (spawn && spawn->IsEntity()) + { + ((Entity*)spawn)->MHatedBy.lock(); + ((Entity*)spawn)->HatedBy.erase(m_body->GetID()); + ((Entity*)spawn)->MHatedBy.unlock(); + } + } + + // Clear the list + m_hatelist.clear(); + // Unlock the hate list + MHateList.releasewritelock(__FUNCTION__, __LINE__); +} + +void Brain::ClearHate(Entity* entity) { + // Lock the hate list, we could potentially modify the list so use write lock + MHateList.writelock(__FUNCTION__, __LINE__); + + // Check to see if the given entity is in the hate list + if (m_hatelist.count(entity->GetID()) > 0) + // Erase the entity from the hate list + m_hatelist.erase(entity->GetID()); + + entity->MHatedBy.lock(); + entity->HatedBy.erase(m_body->GetID()); + entity->MHatedBy.unlock(); + + // Unlock the hate list + MHateList.releasewritelock(__FUNCTION__, __LINE__); +} + +Entity* Brain::GetMostHated() { + map::iterator itr; + int32 ret = 0; + sint32 hate = 0; + + // Lock the hate list, not going to alter it so use a read lock + MHateList.readlock(__FUNCTION__, __LINE__); + + if (m_hatelist.size() > 0) { + // Loop through the list looking for the entity that this NPC hates the most + for(itr = m_hatelist.begin(); itr != m_hatelist.end(); itr++) { + // Compare the hate value for the current iteration to our stored highest value + if(itr->second > hate) { + // New high value store the entity + ret = itr->first; + // Store the value to compare with the rest of the entities + hate = itr->second; + } + } + } + // Unlock the list + MHateList.releasereadlock(__FUNCTION__, __LINE__); + Entity* hated = (Entity*)GetBody()->GetZone()->GetSpawnByID(ret); + // Check the reult to see if it is still alive + if(hated && hated->GetHP() <= 0) { + // Entity we got was dead so remove it from the list + ClearHate(hated); + // Call this function again now that we removed the dead entity + hated = GetMostHated(); + } + + // Return our result + return hated; +} + +sint8 Brain::GetHatePercentage(Entity* entity) { + float percentage = 0.0; + MHateList.readlock(__FUNCTION__, __LINE__); + if (entity && m_hatelist.count(entity->GetID()) > 0 && m_hatelist[entity->GetID()] > 0) { + sint32 total_hate = 0; + map::iterator itr; + for (itr = m_hatelist.begin(); itr != m_hatelist.end(); itr++) + total_hate += itr->second; + percentage = m_hatelist[entity->GetID()] / total_hate; + } + MHateList.releasereadlock(__FUNCTION__, __LINE__); + + return (sint8)(percentage * 100); +} + +void Brain::SendHateList(Client* client) { + MHateList.readlock(__FUNCTION__, __LINE__); + + client->Message(CHANNEL_COLOR_YELLOW, "%s's HateList", m_body->GetName()); + client->Message(CHANNEL_COLOR_YELLOW, "-------------------"); + map::iterator itr; + if (m_hatelist.size() > 0) { + // Loop through the list looking for the entity that this NPC hates the most + for(itr = m_hatelist.begin(); itr != m_hatelist.end(); itr++) { + Entity* ent = (Entity*)GetBody()->GetZone()->GetSpawnByID(itr->first); + // Compare the hate value for the current iteration to our stored highest value + if(ent) { + client->Message(CHANNEL_COLOR_YELLOW, "%s : %i", ent->GetName(), itr->second); + } + else { + client->Message(CHANNEL_COLOR_YELLOW, "%u (cannot identity spawn id->entity) : %i", itr->first, itr->second); + } + } + } + client->Message(CHANNEL_COLOR_YELLOW, "-------------------"); + MHateList.releasereadlock(__FUNCTION__, __LINE__); +} + +vector* Brain::GetHateList() { + vector* ret = new vector; + map::iterator itr; + + // Lock the list + MHateList.readlock(__FUNCTION__, __LINE__); + // Loop over the list storing the values into the new list + for (itr = m_hatelist.begin(); itr != m_hatelist.end(); itr++) { + Entity* ent = (Entity*)GetBody()->GetZone()->GetSpawnByID(itr->first); + if (ent) + ret->push_back(ent); + } + // Unlock the list + MHateList.releasereadlock(__FUNCTION__, __LINE__); + + // Return the copy of the list + return ret; +} + +void Brain::MoveCloser(Spawn* target) { + if (target && m_body->GetFollowTarget() != target) + m_body->SetFollowTarget(target, rule_manager.GetGlobalRule(R_Combat, MaxCombatRange)->GetFloat()); + + if (m_body->GetFollowTarget() && !m_body->following) { + m_body->CalculateRunningLocation(true); + //m_body->ClearRunningLocations(); + m_body->following = true; + } +} + +bool Brain::ProcessSpell(Entity* target, float distance) { + if(rand()%100 > m_body->GetCastPercentage() || m_body->IsStifled() || m_body->IsFeared()) + return false; + Spell* spell = m_body->GetNextSpell(target, distance); + if(spell){ + Spawn* spell_target = 0; + if(spell->GetSpellData()->friendly_spell == 1){ + vector* group = m_body->GetSpawnGroup(); + if(group && group->size() > 0){ + vector::iterator itr; + for(itr = group->begin(); itr != group->end(); itr++){ + if((!spell_target && (*itr)->GetHP() > 0 && (*itr)->GetHP() < (*itr)->GetTotalHP()) || (spell_target && (*itr)->GetHP() > 0 && spell_target->GetHP() > (*itr)->GetHP())) + spell_target = *itr; + } + } + if(!spell_target) + spell_target = m_body; + + safe_delete(group); + } + else + spell_target = target; + + BrainCastSpell(spell, spell_target, false); + return true; + } + return false; +} + +bool Brain::BrainCastSpell(Spell* spell, Spawn* cast_on, bool calculate_run_loc) { + if (spell) { + if(calculate_run_loc) { + m_body->CalculateRunningLocation(true); + } + m_body->GetZone()->ProcessSpell(spell, m_body, cast_on); + m_spellRecovery = (int32)(Timer::GetCurrentTime2() + (spell->GetSpellData()->cast_time * 10) + (spell->GetSpellData()->recovery * 10) + 2000); + return true; + } + return false; +} + +bool Brain::CheckBuffs() { + if (!m_body->GetZone()->GetSpellProcess() || m_body->EngagedInCombat() || m_body->IsCasting() || m_body->IsMezzedOrStunned() || !m_body->Alive() || m_body->IsStifled() || !HasRecovered()) + return false; + + Spell* spell = m_body->GetNextBuffSpell(m_body); + + bool casted_on = false; + if(!(casted_on = BrainCastSpell(spell, m_body)) && m_body->IsNPC() && ((NPC*)m_body)->HasSpells()) { + Spawn* target = nullptr; + + vector* group = m_body->GetSpawnGroup(); + if(group && group->size() > 0){ + vector::iterator itr; + for(itr = group->begin(); itr != group->end(); itr++){ + Spawn* spawn = (*itr); + if(spawn->IsEntity() && spawn != m_body) { + if(target) { + Spell* spell = m_body->GetNextBuffSpell(spawn); + SpellEffects* se = ((Entity*)spawn)->GetSpellEffect(spell->GetSpellData()->id); + if(!se && BrainCastSpell(spell, spawn)) { + casted_on = true; + break; + } + } + } + } + } + safe_delete(group); + } + return casted_on; +} + +void Brain::ProcessMelee(Entity* target, float distance) { + if(distance > rule_manager.GetGlobalRule(R_Combat, MaxCombatRange)->GetFloat()) + MoveCloser((Spawn*)target); + else { + if (target) { + LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "%s is within melee range of %s.", m_body->GetName(), target->GetName()); + if (m_body->AttackAllowed(target)) { + LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "%s is allowed to attack %s.", m_body->GetName(), target->GetName()); + if (m_body->PrimaryWeaponReady() && !m_body->IsDazed() && !m_body->IsFeared()) { + LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "%s swings its primary weapon at %s.", m_body->GetName(), target->GetName()); + m_body->SetPrimaryLastAttackTime(Timer::GetCurrentTime2()); + m_body->MeleeAttack(target, distance, true); + m_body->GetZone()->CallSpawnScript(m_body, SPAWN_SCRIPT_AUTO_ATTACK_TICK, target); + } + if (m_body->SecondaryWeaponReady() && !m_body->IsDazed()) { + m_body->SetSecondaryLastAttackTime(Timer::GetCurrentTime2()); + m_body->MeleeAttack(target, distance, false); + } + } + } + } +} + +bool Brain::HasRecovered() { + if(m_spellRecovery > Timer::GetCurrentTime2()) + return false; + + m_spellRecovery = 0; + return true; +} + +void Brain::AddToEncounter(Entity* entity) { + // If player pet then set the entity to the pets owner + if (entity->IsPet() && entity->GetOwner() && !entity->IsBot()) { + MEncounter.writelock(__FUNCTION__, __LINE__); + bool success = AddToEncounter(entity->GetID()); + MEncounter.releasewritelock(__FUNCTION__, __LINE__); + if(!success) + return; + entity = entity->GetOwner(); + } + else if(entity->HasPet() && entity->GetPet()) { + MEncounter.writelock(__FUNCTION__, __LINE__); + bool success = AddToEncounter(entity->GetPet()->GetID()); + MEncounter.releasewritelock(__FUNCTION__, __LINE__); + if(!success) + return; + } + + // If player or bot then get the group + int32 group_id = 0; + if (entity->IsPlayer() || entity->IsBot()) { + m_playerInEncounter = true; + if (entity->GetGroupMemberInfo()) + group_id = entity->GetGroupMemberInfo()->group_id; + } + + // Insert the entity into the encounter list, if there is a group add all group members as well + // TODO: add raid members + MEncounter.writelock(__FUNCTION__, __LINE__); + if (group_id > 0) { + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + + deque::iterator itr; + PlayerGroup* group = world.GetGroupManager()->GetGroup(group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + for (itr = members->begin(); itr != members->end(); itr++) { + if ((*itr)->member) + { + bool success = AddToEncounter((*itr)->member->GetID()); + if((*itr)->client && success) { + m_encounter_playerlist.insert(make_pair((*itr)->client->GetPlayer()->GetCharacterID(), (*itr)->client->GetPlayer()->GetID())); + } + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } + else { + bool success = AddToEncounter(entity->GetID()); + if (success && entity->IsPlayer()) + { + Player* plyr = (Player*)entity; + m_encounter_playerlist.insert(make_pair(plyr->GetCharacterID(), entity->GetID())); + } + } + MEncounter.releasewritelock(__FUNCTION__, __LINE__); +} + +bool Brain::CheckLootAllowed(Entity* entity) { + bool ret = false; + vector::iterator itr; + + if (m_body) + { + if ((m_body->GetLootMethod() != GroupLootMethod::METHOD_LOTTO && m_body->GetLootMethod() != GroupLootMethod::METHOD_NEED_BEFORE_GREED) && m_body->GetLooterSpawnID() > 0 && m_body->GetLooterSpawnID() != entity->GetID()) { + LogWrite(LOOT__INFO, 0, "Loot", "%s: CheckLootAllowed failed, looter spawn id %u does not match received %s(%u)", GetBody()->GetName(), m_body->GetLooterSpawnID(), entity->GetName(), entity->GetID()); + return false; + } + if (rule_manager.GetGlobalRule(R_Loot, AllowChestUnlockByDropTime)->GetInt8() + && m_body->GetChestDropTime() > 0 && Timer::GetCurrentTime2() >= m_body->GetChestDropTime() + (rule_manager.GetGlobalRule(R_Loot, ChestUnlockedTimeDrop)->GetInt32() * 1000)) { + return true; + } + if (rule_manager.GetGlobalRule(R_Loot, AllowChestUnlockByTrapTime)->GetInt8() + && m_body->GetTrapOpenedTime() > 0 && Timer::GetCurrentTime2() >= m_body->GetChestDropTime() + (rule_manager.GetGlobalRule(R_Loot, ChestUnlockedTimeTrap)->GetInt32() * 1000)) { + return true; + } + if ((m_body->GetLootMethod() == GroupLootMethod::METHOD_LOTTO || m_body->GetLootMethod() == GroupLootMethod::METHOD_NEED_BEFORE_GREED) && m_body->HasSpawnLootWindowCompleted(entity->GetID())) { + LogWrite(LOOT__INFO, 0, "Loot", "%s: CheckLootAllowed failed, looter %s(%u) has already completed their lotto selections.", GetBody()->GetName(), entity->GetName(), entity->GetID()); + return false; + } + } + // Check the encounter list to see if the given entity is in it, if so return true. + MEncounter.readlock(__FUNCTION__, __LINE__); + if (entity->IsPlayer()) + { + Player* plyr = (Player*)entity; + + map::iterator itr = m_encounter_playerlist.find(plyr->GetCharacterID()); + if (itr != m_encounter_playerlist.end()) + { + MEncounter.releasereadlock(__FUNCTION__, __LINE__); + return true; + } + + MEncounter.releasereadlock(__FUNCTION__, __LINE__); + return false; + } + + for (itr = m_encounter.begin(); itr != m_encounter.end(); itr++) { + if ((*itr) == entity->GetID()) { + // found the entity in the encounter list, set return value to true and break the loop + ret = true; + break; + } + } + MEncounter.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +int8 Brain::GetEncounterSize() { + int8 ret = 0; + + MEncounter.readlock(__FUNCTION__, __LINE__); + ret = (int8)m_encounter.size(); + MEncounter.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +vector* Brain::GetEncounter() { + vector* ret = new vector; + vector::iterator itr; + + // Lock the list + MEncounter.readlock(__FUNCTION__, __LINE__); + // Loop over the list storing the values into the new list + for (itr = m_encounter.begin(); itr != m_encounter.end(); itr++) + ret->push_back(*itr); + // Unlock the list + MEncounter.releasereadlock(__FUNCTION__, __LINE__); + + // Return the copy of the list + return ret; +} + +bool Brain::IsPlayerInEncounter(int32 char_id) { + bool ret = false; + MEncounter.readlock(__FUNCTION__, __LINE__); + std::map::iterator itr = m_encounter_playerlist.find(char_id); + if(itr != m_encounter_playerlist.end()) { + ret = true; + } + MEncounter.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +bool Brain::IsEntityInEncounter(int32 id, bool skip_read_lock) { + bool ret = false; + if(!skip_read_lock) { + MEncounter.readlock(__FUNCTION__, __LINE__); + } + vector::iterator itr; + for (itr = m_encounter.begin(); itr != m_encounter.end(); itr++) { + if ((*itr) == id) { + // found the entity in the encounter list, set return value to true and break the loop + ret = true; + break; + } + } + if(!skip_read_lock) { + MEncounter.releasereadlock(__FUNCTION__, __LINE__); + } + return ret; +} + +int32 Brain::CountPlayerBotInEncounter() { + int32 count = 0; + vector::iterator itr; + MEncounter.readlock(__FUNCTION__, __LINE__); + for (itr = m_encounter.begin(); itr != m_encounter.end(); itr++) { + Entity* ent = (Entity*)GetBody()->GetZone()->GetSpawnByID((*itr)); + if (ent && (ent->IsPlayer() || ent->IsBot())) { + count++; + } + } + MEncounter.releasereadlock(__FUNCTION__, __LINE__); + return count; +} + +bool Brain::AddToEncounter(int32 id) { + if(!IsEntityInEncounter(id, true)) { + m_encounter.push_back(id); + return true; + } + return false; +} + +void Brain::ClearEncounter() { + MEncounter.writelock(__FUNCTION__, __LINE__); + if(m_body) { + m_body->RemoveSpells(true); + } + m_encounter.clear(); + m_encounter_playerlist.clear(); + m_playerInEncounter = false; + MEncounter.releasewritelock(__FUNCTION__, __LINE__); +} + +void Brain::SendEncounterList(Client* client) { + client->Message(CHANNEL_COLOR_YELLOW, "%s's EncounterList", m_body->GetName()); + client->Message(CHANNEL_COLOR_YELLOW, "-------------------"); + vector::iterator itr; + + // Check the encounter list to see if the given entity is in it, if so return true. + MEncounter.readlock(__FUNCTION__, __LINE__); + + for (itr = m_encounter.begin(); itr != m_encounter.end(); itr++) { + Entity* ent = (Entity*)GetBody()->GetZone()->GetSpawnByID((*itr)); + // Compare the hate value for the current iteration to our stored highest value + if(ent) { + client->Message(CHANNEL_COLOR_YELLOW, "%s", ent->GetName()); + } + else { + client->Message(CHANNEL_COLOR_YELLOW, "%u (cannot identity spawn id->entity)", (*itr)); + } + } + client->Message(CHANNEL_COLOR_YELLOW, "-------------------"); + MEncounter.releasereadlock(__FUNCTION__, __LINE__); +} + +/* Example of how to extend the default AI */ + + +CombatPetBrain::CombatPetBrain(NPC* body) : Brain(body) { + // Make sure to have the " : Brain(body)" so it calls the parent class constructor + // to set up the AI properly +} + +CombatPetBrain::~CombatPetBrain() { + +} + +void CombatPetBrain::Think() { + // We are extending the base brain so make sure to call the parent Think() function. + // If we want to override then we could remove Brain::Think() + Brain::Think(); + + // All this Brain does is make the pet follow its owner, the combat comes from the default brain + + if (GetBody()->EngagedInCombat() || !GetBody()->IsPet() || GetBody()->IsMezzedOrStunned()) + return; + + LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "Pet AI code called for %s", GetBody()->GetName()); + + // If owner is a player and player has stay set then return out + if (GetBody()->GetOwner() && GetBody()->GetOwner()->IsPlayer() && ((Player*)GetBody()->GetOwner())->GetInfoStruct()->get_pet_movement() == 1) + return; + + // Set target to owner + Entity* target = GetBody()->GetOwner(); + GetBody()->SetTarget(target); + + // Get distance from the owner + float distance = GetBody()->GetDistance(target); + + // If out of melee range then move closer + if (distance > rule_manager.GetGlobalRule(R_Combat, MaxCombatRange)->GetFloat()) + MoveCloser((Spawn*)target); +} + +/* Example of how to override the default AI */ + + +NonCombatPetBrain::NonCombatPetBrain(NPC* body) : Brain(body) { + // Make sure to have the " : Brain(body)" so it calls the parent class constructor + // to set up the AI properly +} + +NonCombatPetBrain::~NonCombatPetBrain() { + +} + +void NonCombatPetBrain::Think() { + // All this Brain does is make the pet follow its owner + + if (!GetBody()->IsPet() || GetBody()->IsMezzedOrStunned()) + return; + + LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "Pet AI code called for %s", GetBody()->GetName()); + + // Set target to owner + Entity* target = GetBody()->GetOwner(); + GetBody()->SetTarget(target); + + // Get distance from the owner + float distance = GetBody()->GetDistance(target); + + // If out of melee range then move closer + if (distance > rule_manager.GetGlobalRule(R_Combat, MaxCombatRange)->GetFloat()) + MoveCloser((Spawn*)target); +} + +BlankBrain::BlankBrain(NPC* body) : Brain(body) { + // Make sure to have the " : Brain(body)" so it calls the parent class constructor + // to set up the AI properly + SetTick(50000); +} + +BlankBrain::~BlankBrain() { + +} + +void BlankBrain::Think() { + +} + +LuaBrain::LuaBrain(NPC* body) : Brain(body) { + +} + +LuaBrain::~LuaBrain() { +} + +void LuaBrain::Think() { + if (!lua_interface) + return; + + const char* script = GetBody()->GetSpawnScript(); + if(script) { + if (!lua_interface->RunSpawnScript(script, "Think", GetBody(), GetBody()->GetTarget())) { + lua_interface->LogError("LUA LuaBrain error: was unable to call the Think function in the spawn script (%s)", script); + } + } + else { + LogWrite(NPC_AI__ERROR, 0, "NPC_AI", "Lua brain set on a spawn that doesn't have a script..."); + } +} + +DumbFirePetBrain::DumbFirePetBrain(NPC* body, Entity* target, int32 expire_time) : Brain(body) { + m_expireTime = Timer::GetCurrentTime2() + expire_time; + AddHate(target, INT_MAX); +} + +DumbFirePetBrain::~DumbFirePetBrain() { + +} + +void DumbFirePetBrain::AddHate(Entity* entity, sint32 hate) { + if (!GetMostHated()) + Brain::AddHate(entity, hate); +} + +void DumbFirePetBrain::Think() { + + Entity* target = GetMostHated(); + + if (target) { + if (!GetBody()->IsMezzedOrStunned()) { + // Set the NPC's target to the most hated entity if it is not already. + if (GetBody()->GetTarget() != target) { + GetBody()->SetTarget(target); + GetBody()->FaceTarget(target, false); + } + // target needs to be identified before combat setting + + // If the NPC is not in combat then put them in combat + if (!GetBody()->EngagedInCombat()) { + //GetBody()->ClearRunningLocations(); + GetBody()->CalculateRunningLocation(true); + GetBody()->InCombat(true); + } + + float distance = GetBody()->GetDistance(target); + + if(GetBody()->CheckLoS(target) && !GetBody()->IsCasting() && (!HasRecovered() || !ProcessSpell(target, distance))) { + LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "%s is attempting melee on %s.", GetBody()->GetName(), target->GetName()); + GetBody()->FaceTarget(target, false); + ProcessMelee(target, distance); + } + } + } + else { + // No hated target or time expired, kill this mob + if (GetBody()->GetHP() > 0) { + GetBody()->KillSpawn(GetBody()); + LogWrite(NPC_AI__DEBUG, 7, "NPC AI", "Dumbfire being killed because there is no target."); + } + } + + if (Timer::GetCurrentTime2() > m_expireTime) { + if (GetBody()->GetHP() > 0) { + GetBody()->KillSpawn(GetBody()); + LogWrite(NPC_AI__DEBUG, 7, "NPC AI", "Dumbfire being killed because timer expired."); + } + } +} diff --git a/source/WorldServer/NPC_AI.h b/source/WorldServer/NPC_AI.h new file mode 100644 index 0000000..cbb8166 --- /dev/null +++ b/source/WorldServer/NPC_AI.h @@ -0,0 +1,198 @@ +/* + 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 . +*/ +#ifndef __NPC_AI_H__ +#define __NPC_AI_H__ +#include "NPC.h" +#include +#include + +using namespace std; + +class Brain { +public: + Brain(NPC* npc); + virtual ~Brain(); + + /// The main loop for the brain. This will do all the AI work + virtual void Think(); + + /* Timer related functions */ + + /// Gets the time between calls to Think() + /// Time in miliseconds between calls to Think() + int16 Tick() { return m_tick; } + /// Sets the time between calls to Think() + /// Time in miliseconds + void SetTick(int16 time) { m_tick = time; } + /// Gets the timestamp of the last call to Think() + /// Timestamp of the last call to Think() + int32 LastTick() { return m_lastTick; } + /// Sets the last tick to the given time + /// The time to set the last tick to + void SetLastTick(int32 time) { m_lastTick = time; } + + /* Hate related functions */ + + /// Gets the amount of hate this npc has towards the given entity + /// The entity to check + /// The amount of hate towards the given entity + sint32 GetHate(Entity* entity); + /// Add hate for the given entity to this NPC + /// The entity we are adding to this NPC's hate list + /// The amount of hate to add + virtual void AddHate(Entity* entity, sint32 hate); + /// Completely clears the hate list for this npc + void ClearHate(); + /// Removes the given entity from this NPC's hate list + /// Entity to remove from this NPC's hate list + void ClearHate(Entity* entity); + /// Get the entity this NPC hates the most + /// The entity this NPC hates the most + Entity* GetMostHated(); + /// Gets a percentage of hate owned by the given entity + /// Entity to get the percentage for + /// Percentage of hate as a sint8 + sint8 GetHatePercentage(Entity* entity); + + void SendHateList(Client* client); + + ///Gets a list of all the entities in the hate list + vector* GetHateList(); + + /* Combat related functions */ + + bool BrainCastSpell(Spell* spell, Spawn* cast_on, bool calculate_run_loc = true); + + /// + /// + /// + virtual bool ProcessSpell(Entity* target, float distance); + /// + /// True if a buff starts casting + bool CheckBuffs(); + + /// Has the NPC make a melee attack + /// The target to attack + /// The current distance from the target + void ProcessMelee(Entity* target, float distance); + + /* Encounter related functions */ + + /// Adds the given entity and its group and raid members to the encounter list + /// Entity we are adding to the encounter list + void AddToEncounter(Entity* entity); + /// Checks to see if the given entity can loot the corpse + /// Entity trying to loot + /// True if the entity can loot + bool CheckLootAllowed(Entity* entity); + /// Gets the size of the encounter list + /// The size of the list as an int8 + int8 GetEncounterSize(); + /// Clears the encounter list + void ClearEncounter(); + + void SendEncounterList(Client* client); + + /// Gets a copy of the encounter list + /// A copy of the encounter list as a vector* + vector* GetEncounter(); + /// Checks to see if a player is in the encounter + /// True if the encounter list contains a player + bool PlayerInEncounter() { return m_playerInEncounter; } + bool IsPlayerInEncounter(int32 char_id); + bool IsEntityInEncounter(int32 id, bool skip_read_lock = false); + int32 CountPlayerBotInEncounter(); + bool AddToEncounter(int32 id); + /* Helper functions*/ + + /// Gets the NPC this brain controls + /// The NPC this brain controls + NPC* GetBody() { return m_body; } + /// Checks to see if the NPC can cast + /// True if the NPC can cast + bool HasRecovered(); + /// Tells the NPC to move closer to the given target + /// The target to move closer to + void MoveCloser(Spawn* target); + +protected: + // m_body = the npc this brain controls + NPC* m_body; + // m_spellRecovery = time stamp for when the npc can cast again + int32 m_spellRecovery; +private: + // MHateList = mutex to lock and unlock the hate list + Mutex MHateList; + // m_hatelist = the list that stores all the hate, + // entity is the entity this npc hates and the int32 is the value for how much we hate the entity + map m_hatelist; + // m_lastTick = the last time we ran this brain + int32 m_lastTick; + // m_tick = the amount of time between Think() calls in milliseconds + int16 m_tick; + // m_encounter = list of players (entities) that will get a reward (xp/loot) for killing this npc + vector m_encounter; + map m_encounter_playerlist; + + // MEncounter = mutex to lock and unlock the encounter list + Mutex MEncounter; + //m_playerInEncounter = true if a player is added to the encounter + bool m_playerInEncounter; +}; + +// Extension of the default brain for combat pets +class CombatPetBrain : public Brain { +public: + CombatPetBrain(NPC* body); + virtual ~CombatPetBrain(); + void Think(); +}; + +class NonCombatPetBrain : public Brain { +public: + NonCombatPetBrain(NPC* body); + virtual ~NonCombatPetBrain(); + void Think(); +}; + +class BlankBrain : public Brain { +public: + BlankBrain(NPC* body); + virtual ~BlankBrain(); + void Think(); +}; + +class LuaBrain : public Brain { +public: + LuaBrain(NPC* body); + virtual ~LuaBrain(); + void Think(); +}; + +class DumbFirePetBrain : public Brain { +public: + DumbFirePetBrain(NPC* body, Entity* target, int32 expire_time); + virtual ~DumbFirePetBrain(); + void Think(); + void AddHate(Entity* entity, sint32 hate); +private: + int32 m_expireTime; +}; +#endif diff --git a/source/WorldServer/Object.cpp b/source/WorldServer/Object.cpp new file mode 100644 index 0000000..971e789 --- /dev/null +++ b/source/WorldServer/Object.cpp @@ -0,0 +1,99 @@ +/* + 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 . +*/ +#include "World.h" +#include "Object.h" +#include "Spells.h" + +extern World world; +extern ConfigReader configReader; +extern MasterSpellList master_spell_list; + +Object::Object(){ + clickable = false; + zone_name = 0; + packet_num = 0; + appearance.activity_status = 64; + appearance.pos.state = 1; + appearance.difficulty = 0; + spawn_type = 2; + m_deviceID = 0; +} +Object::~Object(){ + +} + +EQ2Packet* Object::serialize(Player* player, int16 version){ + return spawn_serialize(player, version); +} + +void Object::HandleUse(Client* client, string command){ + vector destinations; + if(GetTransporterID() > 0) + GetZone()->GetTransporters(&destinations, client, GetTransporterID()); + if (destinations.size()) + { + client->SetTemporaryTransportID(0); + client->ProcessTeleport(this, &destinations, GetTransporterID()); + } + else if (client && command.length() > 0 && appearance.show_command_icon == 1 && MeetsSpawnAccessRequirements(client->GetPlayer())){ + EntityCommand* entity_command = FindEntityCommand(command); + if (entity_command) + client->GetCurrentZone()->ProcessEntityCommand(entity_command, client->GetPlayer(), client->GetPlayer()->GetTarget()); + } +} + +Object* Object::Copy(){ + Object* new_spawn = new Object(); + new_spawn->SetCollector(IsCollector()); + new_spawn->SetMerchantID(merchant_id); + new_spawn->SetMerchantType(merchant_type); + new_spawn->SetMerchantLevelRange(GetMerchantMinLevel(), GetMerchantMaxLevel()); + if(GetSizeOffset() > 0){ + int8 offset = GetSizeOffset()+1; + sint32 tmp_size = size + (rand()%offset - rand()%offset); + if(tmp_size < 0) + tmp_size = 1; + else if(tmp_size >= 0xFFFF) + tmp_size = 0xFFFF; + new_spawn->size = (int16)tmp_size; + } + else + new_spawn->size = size; + new_spawn->SetPrimaryCommands(&primary_command_list); + new_spawn->SetSecondaryCommands(&secondary_command_list); + new_spawn->database_id = database_id; + new_spawn->primary_command_list_id = primary_command_list_id; + new_spawn->secondary_command_list_id = secondary_command_list_id; + memcpy(&new_spawn->appearance, &appearance, sizeof(AppearanceData)); + new_spawn->faction_id = faction_id; + new_spawn->target = 0; + new_spawn->SetTotalHP(GetTotalHP()); + new_spawn->SetTotalPower(GetTotalPower()); + new_spawn->SetHP(GetHP()); + new_spawn->SetPower(GetPower()); + SetQuestsRequired(new_spawn); + new_spawn->SetTransporterID(GetTransporterID()); + new_spawn->SetDeviceID(GetDeviceID()); + new_spawn->SetSoundsDisabled(IsSoundsDisabled()); + new_spawn->SetOmittedByDBFlag(IsOmittedByDBFlag()); + new_spawn->SetLootTier(GetLootTier()); + new_spawn->SetLootDropType(GetLootDropType()); + return new_spawn; +} diff --git a/source/WorldServer/Object.h b/source/WorldServer/Object.h new file mode 100644 index 0000000..1a6098b --- /dev/null +++ b/source/WorldServer/Object.h @@ -0,0 +1,49 @@ +/* + 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 . +*/ +#ifndef __EQ2_OBJECT__ +#define __EQ2_OBJECT__ + +#include "Spawn.h" + +class Object : public Spawn{ +public: + Object(); + virtual ~Object(); + void SetClickable(bool click){ + clickable = click; + } + void SetZone(char* zone){ + zone_name = zone; + } + Object* Copy(); + bool IsObject(){ return true; } + void HandleUse(Client* client, string command); + bool clickable; + char* zone_name; + EQ2Packet* serialize(Player* player, int16 version); + + void SetDeviceID(int8 val) { m_deviceID = val; } + int8 GetDeviceID() { return m_deviceID; } + +private: + int8 m_deviceID; +}; +#endif + diff --git a/source/WorldServer/Player.cpp b/source/WorldServer/Player.cpp new file mode 100644 index 0000000..ade5fbe --- /dev/null +++ b/source/WorldServer/Player.cpp @@ -0,0 +1,7549 @@ +/* + 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 . +*/ +#include "Player.h" +#include "../common/MiscFunctions.h" +#include "World.h" +#include "WorldDatabase.h" +#include +#include "classes.h" +#include "LuaInterface.h" +#include "../common/Log.h" +#include "Rules/Rules.h" +#include "Titles.h" +#include "Languages.h" +#include "SpellProcess.h" +#include +#include +#include "ClientPacketFunctions.h" + +extern Classes classes; +extern WorldDatabase database; +extern World world; +extern ConfigReader configReader; +extern MasterSkillList master_skill_list; +extern MasterSpellList master_spell_list; +extern MasterQuestList master_quest_list; +extern Variables variables; +extern LuaInterface* lua_interface; +extern MasterItemList master_item_list; +extern RuleManager rule_manager; +extern MasterTitlesList master_titles_list; +extern MasterLanguagesList master_languages_list; + +Player::Player(){ + tutorial_step = 0; + char_id = 0; + group = 0; + appearance.pos.grid_id = 0; + spawn_index = 1; + info = 0; + movement_packet = 0; + last_movement_activity = 0; + //speed = 0; + packet_num = 0; + range_attack = false; + old_movement_packet = 0; + charsheet_changed = false; + quickbar_updated = false; + custNPC = false; + spawn_tmp_vis_xor_packet = 0; + spawn_tmp_pos_xor_packet = 0; + spawn_tmp_info_xor_packet = 0; + pending_collection_reward = 0; + pos_packet_speed = 0; + + appearance.display_name = 1; + appearance.show_command_icon = 1; + appearance.player_flag = 1; + appearance.targetable = 1; + appearance.show_level = 1; + spell_count = 0; + spell_orig_packet = 0; + spell_xor_packet = 0; + resurrecting = false; + spawn_id = 1; + spawn_type = 4; + player_spawn_id_map[1] = this; + player_spawn_reverse_id_map[this] = 1; + MPlayerQuests.SetName("Player::MPlayerQuests"); + test_time = 0; + returning_from_ld = false; + away_message = "Sorry, I am A.F.K. (Away From Keyboard)"; + AddSecondaryEntityCommand("Inspect", 10000, "inspect_player", "", 0, 0); + AddSecondaryEntityCommand("Who", 10000, "who", "", 0, 0); + // commented out commands a player canNOT use on themselves... move these to Client::HandleVerbRequest()? + //AddSecondaryEntityCommand("Assist", 10, "assist", "", 0, 0); + //AddSecondaryEntityCommand("Duel", 10, "duel", "", 0, 0); + //AddSecondaryEntityCommand("Duel Bet", 10, "duelbet", "", 0, 0); + //AddSecondaryEntityCommand("Trade", 10, "trade", "", 0, 0); + is_tracking = false; + guild = 0; + following = false; + combat_target = 0; + //InitXPTable(); + pending_deletion = false; + spawn_vis_struct = 0; + spawn_pos_struct = 0; + spawn_info_struct = 0; + spawn_header_struct = 0; + spawn_footer_struct = 0; + widget_footer_struct = 0; + sign_footer_struct = 0; + pos_xor_size = 0; + info_xor_size = 0; + vis_xor_size = 0; + pos_mutex.SetName("Player::pos_mutex"); + vis_mutex.SetName("Player::vis_mutex"); + info_mutex.SetName("Player::info_mutex"); + index_mutex.SetName("Player::index_mutex"); + spawn_mutex.SetName("Player::spawn_mutex"); + m_playerSpawnQuestsRequired.SetName("Player::player_spawn_quests_required"); + m_playerSpawnHistoryRequired.SetName("Player::player_spawn_history_required"); + gm_vision = false; + SetSaveSpellEffects(true); + reset_mentorship = false; + all_spells_locked = false; + current_language_id = 0; + active_reward = false; + + SortedTraitList = new map > >; + ClassTraining = new map >; + RaceTraits = new map >; + InnateRaceTraits = new map >; + FocusEffects = new map >; + need_trait_update = true; + active_food_unique_id = 0; + active_drink_unique_id = 0; +} +Player::~Player(){ + SetSaveSpellEffects(true); + for(int32 i=0;i*>::iterator itr; + for (itr = player_spawn_quests_required.begin(); itr != player_spawn_quests_required.end(); itr++){ + safe_delete(itr->second); + } + player_spawn_quests_required.clear(); + + for (itr = player_spawn_history_required.begin(); itr != player_spawn_history_required.end(); itr++){ + safe_delete(itr->second); + } + player_spawn_history_required.clear(); + + map > >::iterator itr1; + map >::iterator itr2; + vector::iterator itr3; + // Type + for (itr1 = m_characterHistory.begin(); itr1 != m_characterHistory.end(); itr1++) { + // Sub type + for (itr2 = itr1->second.begin(); itr2 != itr1->second.end(); itr2++) { + // vector of data + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++) { + safe_delete(*itr3); + } + } + } + m_characterHistory.clear(); + + mLUAHistory.writelock(); + map::iterator itr4; + for (itr4 = m_charLuaHistory.begin(); itr4 != m_charLuaHistory.end(); itr4++) { + safe_delete(itr4->second); + } + m_charLuaHistory.clear(); + mLUAHistory.releasewritelock(); + + safe_delete_array(movement_packet); + safe_delete_array(old_movement_packet); + safe_delete_array(spawn_tmp_info_xor_packet); + safe_delete_array(spawn_tmp_vis_xor_packet); + safe_delete_array(spawn_tmp_pos_xor_packet); + safe_delete_array(spell_xor_packet); + safe_delete_array(spell_orig_packet); + DestroyQuests(); + WritePlayerStatistics(); + RemovePlayerStatistics(); + DeleteMail(); + world.RemoveLottoPlayer(GetCharacterID()); + safe_delete(info); + index_mutex.writelock(__FUNCTION__, __LINE__); + player_spawn_reverse_id_map.clear(); + player_spawn_id_map.clear(); + index_mutex.releasewritelock(__FUNCTION__, __LINE__); + + info_mutex.writelock(__FUNCTION__, __LINE__); + spawn_info_packet_list.clear(); + info_mutex.releasewritelock(__FUNCTION__, __LINE__); + vis_mutex.writelock(__FUNCTION__, __LINE__); + spawn_vis_packet_list.clear(); + vis_mutex.releasewritelock(__FUNCTION__, __LINE__); + pos_mutex.writelock(__FUNCTION__, __LINE__); + spawn_pos_packet_list.clear(); + pos_mutex.releasewritelock(__FUNCTION__, __LINE__); + + safe_delete(spawn_header_struct); + safe_delete(spawn_footer_struct); + safe_delete(sign_footer_struct); + safe_delete(widget_footer_struct); + safe_delete(spawn_info_struct); + safe_delete(spawn_vis_struct); + safe_delete(spawn_pos_struct); + ClearPendingSelectableItemRewards(0, true); + ClearPendingItemRewards(); + ClearEverything(); + + safe_delete(SortedTraitList); + safe_delete(ClassTraining); + safe_delete(RaceTraits); + safe_delete(InnateRaceTraits); + safe_delete(FocusEffects); + // leak fix on Language* pointer from Player::AddLanguage + player_languages_list.Clear(); +} + +EQ2Packet* Player::serialize(Player* player, int16 version){ + return spawn_serialize(player, version); +} + +EQ2Packet* Player::Move(float x, float y, float z, int16 version, float heading){ + PacketStruct* packet = configReader.getStruct("WS_MoveClient", version); + if(packet){ + packet->setDataByName("x", x); + packet->setDataByName("y", y); + packet->setDataByName("z", z); + packet->setDataByName("unknown", 1); // 1 seems to force the client to re-render the zone at the new location + packet->setDataByName("location", 0xFFFFFFFF); //added in 869 + if (heading != -1.0f) + packet->setDataByName("heading", heading); + EQ2Packet* outapp = packet->serialize(); + safe_delete(packet); + return outapp; + } + return 0; +} + +void Player::DestroyQuests(){ + MPlayerQuests.writelock(__FUNCTION__, __LINE__); + map::iterator itr; + for(itr = completed_quests.begin(); itr != completed_quests.end(); itr++){ + if(itr->second) { + safe_delete(itr->second); + } + } + completed_quests.clear(); + for(itr = player_quests.begin(); itr != player_quests.end(); itr++){ + if(itr->second) { + safe_delete(itr->second); + } + } + player_quests.clear(); + for(itr = pending_quests.begin(); itr != pending_quests.end(); itr++){ + if(itr->second) { + safe_delete(itr->second); + } + } + pending_quests.clear(); + MPlayerQuests.releasewritelock(__FUNCTION__, __LINE__); +} + +PlayerInfo* Player::GetPlayerInfo(){ + if(info == 0) + info = new PlayerInfo(this); + return info; +} + +void PlayerInfo::CalculateXPPercentages(){ + int32 xp_needed = info_struct->get_xp_needed(); + if(xp_needed > 0){ + double div_percent = ((double)info_struct->get_xp() / xp_needed) * 100.0; + int16 percentage = (int16)(div_percent) * 10; + double whole, fractional = 0.0; + fractional = std::modf(div_percent, &whole); + info_struct->set_xp_yellow(percentage); + info_struct->set_xp_blue((int16)(fractional * 1000)); + + // vitality bars probably need a revisit + info_struct->set_xp_blue_vitality_bar(0); + info_struct->set_xp_yellow_vitality_bar(0); + if(player->GetXPVitality() > 0){ + float vitality_total = player->GetXPVitality()*10 + percentage; + vitality_total -= ((int)(percentage/100)*100); + if(vitality_total < 100){ //10% + info_struct->set_xp_blue_vitality_bar(info_struct->get_xp_blue() + (int16)(player->GetXPVitality() *10)); + } + else + info_struct->set_xp_yellow_vitality_bar(info_struct->get_xp_yellow() + (int16)(player->GetXPVitality() *10)); + } + } +} + +void PlayerInfo::CalculateTSXPPercentages(){ + int32 ts_xp_needed = info_struct->get_ts_xp_needed(); + if(ts_xp_needed > 0){ + float percentage = ((double)info_struct->get_ts_xp() / ts_xp_needed) * 1000; + info_struct->set_tradeskill_exp_yellow((int16)percentage); + info_struct->set_tradeskill_exp_blue((int16)((percentage - info_struct->get_tradeskill_exp_yellow()) * 1000)); + /*info_struct->xp_blue_vitality_bar = 0; + info_struct->xp_yellow_vitality_bar = 0; + if(player->GetXPVitality() > 0){ + float vitality_total = player->GetXPVitality()*10 + percentage; + vitality_total -= ((int)(percentage/100)*100); + if(vitality_total < 100){ //10% + info_struct->xp_blue_vitality_bar = info_struct->xp_blue + (int16)(player->GetXPVitality() *10); + } + else + info_struct->xp_yellow_vitality_bar = info_struct->xp_yellow + (int16)(player->GetXPVitality() *10); + }*/ + } +} + +void PlayerInfo::SetHouseZone(int32 id){ + house_zone_id = id; +} + +void PlayerInfo::SetBindZone(int32 id){ + bind_zone_id = id; +} + +void PlayerInfo::SetBindX(float x){ + bind_x = x; +} + +void PlayerInfo::SetBindY(float y){ + bind_y = y; +} + +void PlayerInfo::SetBindZ(float z){ + bind_z = z; +} + +void PlayerInfo::SetBindHeading(float heading){ + bind_heading = heading; +} + +int32 PlayerInfo::GetHouseZoneID(){ + return house_zone_id; +} + +int32 PlayerInfo::GetBindZoneID(){ + return bind_zone_id; +} + +float PlayerInfo::GetBindZoneX(){ + return bind_x; +} + +float PlayerInfo::GetBindZoneY(){ + return bind_y; +} + +float PlayerInfo::GetBindZoneZ(){ + return bind_z; +} + +float PlayerInfo::GetBindZoneHeading(){ + return bind_heading; +} + +PacketStruct* PlayerInfo::serialize2(int16 version){ + PacketStruct* packet = configReader.getStruct("WS_CharacterSheet", version); + if(packet){ + //TODO: 2021 FIX THIS CASTING + char deity[32]; + strncpy(deity, info_struct->get_deity().c_str(), 32); + packet->setDataByName("deity", deity); + + char name[40]; + strncpy(name, info_struct->get_name().c_str(), 40); + packet->setDataByName("character_name", name); + packet->setDataByName("race", info_struct->get_race()); + packet->setDataByName("gender", info_struct->get_gender()); + packet->setDataByName("class1", info_struct->get_class1()); + packet->setDataByName("class2", info_struct->get_class2()); + packet->setDataByName("class3", info_struct->get_class3()); + packet->setDataByName("tradeskill_class1", info_struct->get_tradeskill_class1()); + packet->setDataByName("tradeskill_class2", info_struct->get_tradeskill_class2()); + packet->setDataByName("tradeskill_class3", info_struct->get_tradeskill_class3()); + packet->setDataByName("level", info_struct->get_level()); + packet->setDataByName("effective_level", info_struct->get_effective_level() != 0 ? info_struct->get_effective_level() : info_struct->get_level()); + packet->setDataByName("tradeskill_level", info_struct->get_tradeskill_level()); + packet->setDataByName("account_age_base", info_struct->get_account_age_base()); + +// for(int8 i=0;i<19;i++) +// { +// packet->setDataByName("account_age_bonus", info_struct->get_account_age_bonus(i)); +// } + + // + packet->setDataByName("current_hp", player->GetHP()); + packet->setDataByName("max_hp",player-> GetTotalHP()); + packet->setDataByName("base_hp", player->GetTotalHPBase()); + float bonus_health = floor( (float)(info_struct->get_sta() * player->CalculateBonusMod())); + packet->setDataByName("bonus_health", bonus_health); + packet->setDataByName("stat_bonus_health", player->CalculateBonusMod()); + packet->setDataByName("current_power", player->GetPower()); + packet->setDataByName("max_power", player->GetTotalPower()); + packet->setDataByName("base_power", player->GetTotalPowerBase()); + packet->setDataByName("bonus_power", floor( (float)(player->GetPrimaryStat() * player->CalculateBonusMod()))); + packet->setDataByName("stat_bonus_power", player->CalculateBonusMod()); + packet->setDataByName("conc_used", info_struct->get_cur_concentration()); + packet->setDataByName("conc_max", info_struct->get_max_concentration()); + packet->setDataByName("attack", info_struct->get_cur_attack()); + packet->setDataByName("attack_base", info_struct->get_attack_base()); + packet->setDataByName("absorb", info_struct->get_absorb()); + packet->setDataByName("mitigation_skill1", info_struct->get_mitigation_skill1()); + packet->setDataByName("mitigation_skill2", info_struct->get_mitigation_skill2()); + packet->setDataByName("mitigation_skill3", info_struct->get_mitigation_skill3()); + CalculateXPPercentages(); + packet->setDataByName("exp_yellow", info_struct->get_xp_yellow()); + packet->setDataByName("exp_blue", info_struct->get_xp_blue()); + packet->setDataByName("tradeskill_exp_yellow", info_struct->get_tradeskill_exp_yellow()); + packet->setDataByName("tradeskill_exp_blue", info_struct->get_tradeskill_exp_blue()); + packet->setDataByName("flags", info_struct->get_flags()); + packet->setDataByName("flags2", info_struct->get_flags2()); + + packet->setDataByName("avoidance_pct", (int16)info_struct->get_avoidance_display()*10.0f);//avoidance_pct 192 = 19.2% // confirmed DoV + packet->setDataByName("avoidance_base", (int16)info_struct->get_avoidance_base()*10.0f); // confirmed DoV + packet->setDataByName("avoidance", info_struct->get_cur_avoidance()); + packet->setDataByName("base_avoidance_pct", info_struct->get_base_avoidance_pct());// confirmed DoV + float parry_pct = info_struct->get_parry(); // client works off of int16, but we use floats to track the actual x/100% + packet->setDataByName("parry",(int16)(parry_pct*10.0f));// confirmed DoV + + float block_pct = info_struct->get_block()*10.0f; + + packet->setDataByName("block", (int16)block_pct);// confirmed DoV + packet->setDataByName("uncontested_block", info_struct->get_uncontested_block());// confirmed DoV + + packet->setDataByName("str", info_struct->get_str()); + packet->setDataByName("sta", info_struct->get_sta()); + packet->setDataByName("agi", info_struct->get_agi()); + packet->setDataByName("wis", info_struct->get_wis()); + packet->setDataByName("int", info_struct->get_intel()); + packet->setDataByName("str_base", info_struct->get_str_base()); + packet->setDataByName("sta_base", info_struct->get_sta_base()); + packet->setDataByName("agi_base", info_struct->get_agi_base()); + packet->setDataByName("wis_base", info_struct->get_wis_base()); + packet->setDataByName("int_base", info_struct->get_intel_base()); + packet->setDataByName("mitigation_cur", info_struct->get_cur_mitigation()); + packet->setDataByName("mitigation_max", info_struct->get_max_mitigation()); + packet->setDataByName("mitigation_base", info_struct->get_mitigation_base()); + packet->setDataByName("heat", info_struct->get_heat()); + packet->setDataByName("cold", info_struct->get_cold()); + packet->setDataByName("magic", info_struct->get_magic()); + packet->setDataByName("mental", info_struct->get_mental()); + packet->setDataByName("divine", info_struct->get_divine()); + packet->setDataByName("disease", info_struct->get_disease()); + packet->setDataByName("poison", info_struct->get_poison()); + packet->setDataByName("heat_base", info_struct->get_heat_base()); + packet->setDataByName("cold_base", info_struct->get_cold_base()); + packet->setDataByName("magic_base", info_struct->get_magic_base()); + packet->setDataByName("mental_base", info_struct->get_mental_base()); + packet->setDataByName("divine_base", info_struct->get_divine_base()); + packet->setDataByName("disease_base", info_struct->get_disease_base()); + packet->setDataByName("poison_base", info_struct->get_poison_base()); + packet->setDataByName("mitigation_cur2", info_struct->get_cur_mitigation()); + packet->setDataByName("mitigation_max2", info_struct->get_max_mitigation()); + packet->setDataByName("mitigation_base2", info_struct->get_mitigation_base()); + packet->setDataByName("coins_copper", info_struct->get_coin_copper()); + packet->setDataByName("coins_silver", info_struct->get_coin_silver()); + packet->setDataByName("coins_gold", info_struct->get_coin_gold()); + packet->setDataByName("coins_plat", info_struct->get_coin_plat()); + packet->setDataByName("weight", info_struct->get_weight()); + packet->setDataByName("max_weight", info_struct->get_max_weight()); + + if(info_struct->get_pet_id() != 0xFFFFFFFF) { + char pet_name[32]; + strncpy(pet_name, info_struct->get_pet_name().c_str(), version <= 373 ? 16 : 32); + packet->setDataByName("pet_name", pet_name); + } + else { + packet->setDataByName("pet_name", "No Pet"); + } + + packet->setDataByName("pet_health_pct", info_struct->get_pet_health_pct()); + packet->setDataByName("pet_power_pct", info_struct->get_pet_power_pct()); + + packet->setDataByName("pet_movement", info_struct->get_pet_movement()); + packet->setDataByName("pet_behavior", info_struct->get_pet_behavior()); + + packet->setDataByName("status_points", info_struct->get_status_points()); + if(bind_zone_id > 0){ + string bind_name = database.GetZoneName(bind_zone_id); + if (bind_name.length() > 0) + packet->setDataByName("bind_zone", bind_name.c_str()); + } + else + packet->setDataByName("bind_zone", "None"); + if(house_zone_id > 0){ + string house_name = database.GetZoneName(house_zone_id); + if (house_name.length() > 0) + packet->setDataByName("house_zone", house_name.c_str()); + } + else + packet->setDataByName("house_zone", "None"); + //packet->setDataByName("account_age_base", 14); + packet->setDataByName("hp_regen", info_struct->get_hp_regen()); + packet->setDataByName("power_regen", info_struct->get_power_regen()); + /*packet->setDataByName("unknown11", -1, 0); + packet->setDataByName("unknown11", -1, 1); + packet->setDataByName("unknown13", 201, 0); + packet->setDataByName("unknown13", 201, 1); + packet->setDataByName("unknown13", 234, 2); + packet->setDataByName("unknown13", 201, 3); + packet->setDataByName("unknown13", 214, 4); + packet->setDataByName("unknown13", 234, 5); + packet->setDataByName("unknown13", 234, 6); + + packet->setDataByName("unknown14", 78); + */ + packet->setDataByName("adventure_exp_vitality", (int16)(player->GetXPVitality() *10)); + //packet->setDataByName("unknown15b", 9911); + packet->setDataByName("unknown15a", 78); + packet->setDataByName("xp_yellow_vitality_bar", info_struct->get_xp_yellow_vitality_bar()); + packet->setDataByName("xp_blue_vitality_bar", info_struct->get_xp_blue_vitality_bar()); + packet->setDataByName("tradeskill_exp_vitality", 100); + packet->setDataByName("unknown15c", 200); + + //packet->setDataByName("unknown15", 100, 10); + packet->setDataByName("unknown18", 16880, 1); + /*packet->setDataByName("unknown19", 1); + packet->setDataByName("unknown19", 3, 1); + packet->setDataByName("unknown19", 1074301064, 2); + packet->setDataByName("unknown19", 1, 3); + packet->setDataByName("unknown19", 3, 4); + packet->setDataByName("unknown19", 1074301064, 5); + packet->setDataByName("unknown19", 6, 6); + packet->setDataByName("unknown19", 14, 7); + packet->setDataByName("unknown19", 1083179008, 8);*/ + player->SetGroupInformation(packet); + packet->setDataByName("unknown20", 1, 107); + packet->setDataByName("unknown20", 1, 108); + packet->setDataByName("unknown20", 1, 109); + packet->setDataByName("unknown20", 1, 110); + packet->setDataByName("unknown20", 1, 111); + //packet->setDataByName("unknown20b", 255); + //packet->setDataByName("unknown20b", 255, 1); + //packet->setDataByName("unknown20b", 255, 2); + packet->setDataByName("unknown11", 123); + packet->setDataByName("unknown11", 234, 1); + + //packet->setDataByName("in_combat", 32768); + //make name flash red + /*packet->setDataByName("unknown20", 8); + packet->setDataByName("unknown20", 38, 70); + packet->setDataByName("unknown20", 17, 77); + packet->setDataByName("unknown20", 1, 112); //melee stats and such + packet->setDataByName("unknown20", 1, 113); + packet->setDataByName("unknown20", 1, 114); + packet->setDataByName("unknown20", 1, 115); + + packet->setDataByName("unknown20", 4294967295, 309); + packet->setDataByName("unknown22", 2, 4); + packet->setDataByName("unknown23", 2, 29); + */ + //packet->setDataByName("unknown20b", 1, i); // pet bar in here + // for(int i=0;i<19;i++) + // packet->setDataByName("unknown7", 257, i); + //packet->setDataByName("unknown21", info_struct->rain, 2); + packet->setDataByName("rain", info_struct->get_rain()); + packet->setDataByName("rain2", info_struct->get_wind()); //-102.24); + /*packet->setDataByName("unknown22", 3, 4); + packet->setDataByName("unknown23", 3, 161); + packet->setDataByName("unknown20", 103); + packet->setDataByName("unknown20", 1280, 70); + packet->setDataByName("unknown20", 9, 71); + packet->setDataByName("unknown20", 5, 72); + packet->setDataByName("unknown20", 4294967271, 73); + packet->setDataByName("unknown20", 5, 75); + packet->setDataByName("unknown20", 1051, 77); + packet->setDataByName("unknown20", 3, 78); + packet->setDataByName("unknown20", 6, 104); + packet->setDataByName("unknown20", 1, 105); + packet->setDataByName("unknown20", 20, 106); + packet->setDataByName("unknown20", 3, 107); + packet->setDataByName("unknown20", 1, 108); + packet->setDataByName("unknown20", 1, 109); + packet->setDataByName("unknown20", 4278190080, 494); + packet->setDataByName("unknown20b", 255); + packet->setDataByName("unknown20b", 255, 1); + packet->setDataByName("unknown20b", 255, 2); + packet->setDataByName("unknown20", 50, 75); + */ + //packet->setDataByName("rain2", -102.24); + for(int i=0;i<45;i++){ + if(i < 30){ + packet->setSubstructDataByName("maintained_effects", "name", info_struct->maintained_effects[i].name, i, 0); + packet->setSubstructDataByName("maintained_effects", "target", info_struct->maintained_effects[i].target, i, 0); + packet->setSubstructDataByName("maintained_effects", "spell_id", info_struct->maintained_effects[i].spell_id, i, 0); + packet->setSubstructDataByName("maintained_effects", "slot_pos", info_struct->maintained_effects[i].slot_pos, i, 0); + packet->setSubstructDataByName("maintained_effects", "icon", info_struct->maintained_effects[i].icon, i, 0); + packet->setSubstructDataByName("maintained_effects", "icon_type", info_struct->maintained_effects[i].icon_backdrop, i, 0); + packet->setSubstructDataByName("maintained_effects", "conc_used", info_struct->maintained_effects[i].conc_used, i, 0); + packet->setSubstructDataByName("maintained_effects", "unknown3", 1, i, 0); + packet->setSubstructDataByName("maintained_effects", "total_time", info_struct->maintained_effects[i].total_time, i, 0); + packet->setSubstructDataByName("maintained_effects", "expire_timestamp", info_struct->maintained_effects[i].expire_timestamp, i, 0); + } + else if(version < 942)//version 942 added 15 additional spell effect slots + break; + packet->setSubstructDataByName("spell_effects", "spell_id", info_struct->spell_effects[i].spell_id, i, 0); + if(info_struct->spell_effects[i].spell_id > 0 && info_struct->spell_effects[i].spell_id < 0xFFFFFFFF) + packet->setSubstructDataByName("spell_effects", "unknown2", 514, i, 0); + packet->setSubstructDataByName("spell_effects", "total_time", info_struct->spell_effects[i].total_time, i, 0); + packet->setSubstructDataByName("spell_effects", "expire_timestamp", info_struct->spell_effects[i].expire_timestamp, i, 0); + packet->setSubstructDataByName("spell_effects", "icon", info_struct->spell_effects[i].icon, i, 0); + packet->setSubstructDataByName("spell_effects", "icon_type", info_struct->spell_effects[i].icon_backdrop, i, 0); + } + return packet; + } + return 0; +} + +EQ2Packet* PlayerInfo::serialize3(PacketStruct* packet, int16 version){ + if(packet){ + string* data = packet->serializeString(); + int32 size = data->length(); + //DumpPacket((uchar*)data->c_str(), size); + uchar* tmp = new uchar[size]; + if(!changes){ + orig_packet = new uchar[size]; + changes = new uchar[size]; + memcpy(orig_packet, (uchar*)data->c_str(), size); + size = Pack(tmp, (uchar*)data->c_str(), size, size, version); + } + else{ + memcpy(changes, (uchar*)data->c_str(), size); + Encode(changes, orig_packet, size); + size = Pack(tmp, changes, size, size, version); + //cout << "INFO HERE:\n"; + //DumpPacket(tmp, size); + } + EQ2Packet* ret_packet = new EQ2Packet(OP_UpdateCharacterSheetMsg, tmp, size+4); + safe_delete_array(tmp); + safe_delete(packet); + return ret_packet; + } + return 0; +} + +void PlayerInfo::SetAccountAge(int32 age){ + info_struct->set_account_age_base(age); +} + +EQ2Packet* PlayerInfo::serialize(int16 version, int16 modifyPos, int32 modifyValue) { + PacketStruct* packet = configReader.getStruct("WS_CharacterSheet", version); + //0-69, locked screen movement + //30-69 normal movement + //10-30 normal movement + + if (packet) { + char name[40]; + strncpy(name,info_struct->get_name().c_str(),40); + packet->setDataByName("character_name", name); + packet->setDataByName("race", info_struct->get_race()); + packet->setDataByName("gender", info_struct->get_gender()); + packet->setDataByName("exiled", 0); // need exiled data + packet->setDataByName("class1", info_struct->get_class1()); + packet->setDataByName("class2", info_struct->get_class2()); + packet->setDataByName("class3", info_struct->get_class3()); + packet->setDataByName("tradeskill_class1", info_struct->get_tradeskill_class1()); + packet->setDataByName("tradeskill_class2", info_struct->get_tradeskill_class2()); + packet->setDataByName("tradeskill_class3", info_struct->get_tradeskill_class3()); + packet->setDataByName("level", info_struct->get_level()); + packet->setDataByName("effective_level", info_struct->get_effective_level() != 0 ? info_struct->get_effective_level() : info_struct->get_level()); + packet->setDataByName("tradeskill_level", info_struct->get_tradeskill_level()); + packet->setDataByName("account_age_base", info_struct->get_account_age_base()); + + //TODO: 2021 FIX THIS CASTING + for (int8 i = 0; i < 19; i++) + packet->setDataByName("account_age_bonus", 0); + //TODO: 2021 FIX THIS CASTING + char deity[32]; + strncpy(deity, info_struct->get_deity().c_str(), 32); + packet->setDataByName("deity", deity); + + packet->setDataByName("last_name", player->GetLastName()); + packet->setDataByName("current_hp", player->GetHP()); + packet->setDataByName("max_hp", player->GetTotalHP()); + packet->setDataByName("base_hp", player->GetTotalHPBase()); + + packet->setDataByName("current_power", player->GetPower()); + packet->setDataByName("max_power", player->GetTotalPower()); + packet->setDataByName("base_power", player->GetTotalPowerBase()); + packet->setDataByName("conc_used", info_struct->get_cur_concentration()); + packet->setDataByName("conc_max", info_struct->get_max_concentration()); + packet->setDataByName("hp_regen", player->GetInfoStruct()->get_hp_regen()); + packet->setDataByName("power_regen", player->GetInfoStruct()->get_power_regen()); + + packet->setDataByName("stat_bonus_health", player->CalculateBonusMod());//bonus health and bonus power getting same value? + packet->setDataByName("stat_bonus_power", player->CalculateBonusMod());//bonus health and bonus power getting same value? + float bonus_health = floor((float)(info_struct->get_sta() * player->CalculateBonusMod())); + packet->setDataByName("bonus_health", bonus_health); + packet->setDataByName("bonus_power", floor((float)(player->GetPrimaryStat() * player->CalculateBonusMod()))); + packet->setDataByName("stat_bonus_damage", 95); //stat_bonus_damage + packet->setDataByName("mitigation_cur", info_struct->get_cur_mitigation());// confirmed DoV + packet->setDataByName("mitigation_base", info_struct->get_mitigation_base());// confirmed DoV + + packet->setDataByName("mitigation_pct_pve", info_struct->get_mitigation_pve()); // % calculation Mitigation % vs PvE 392 = 39.2%// confirmed DoV + packet->setDataByName("mitigation_pct_pvp", info_struct->get_mitigation_pvp()); // % calculation Mitigation % vs PvP 559 = 55.9%// confirmed DoV + packet->setDataByName("toughness", 0);//toughness// confirmed DoV + packet->setDataByName("toughness_resist_dmg_pvp", 0);//toughness_resist_dmg_pvp 73 = 7300% // confirmed DoV + packet->setDataByName("avoidance_pct", (int16)info_struct->get_avoidance_display()*10.0f);//avoidance_pct 192 = 19.2% // confirmed DoV + packet->setDataByName("avoidance_base", (int16)info_struct->get_avoidance_base()*10.0f); // confirmed DoV + packet->setDataByName("avoidance", info_struct->get_cur_avoidance()); + packet->setDataByName("base_avoidance_pct", info_struct->get_base_avoidance_pct());// confirmed DoV + float parry_pct = info_struct->get_parry(); // client works off of int16, but we use floats to track the actual x/100% + packet->setDataByName("parry",(int16)(parry_pct*10.0f));// confirmed DoV + + float block_pct = info_struct->get_block()*10.0f; + + packet->setDataByName("block", (int16)block_pct);// confirmed DoV + packet->setDataByName("uncontested_block", info_struct->get_uncontested_block());// confirmed DoV + packet->setDataByName("str", info_struct->get_str());// confirmed DoV + packet->setDataByName("sta", info_struct->get_sta());// confirmed DoV + packet->setDataByName("agi", info_struct->get_agi());// confirmed DoV + packet->setDataByName("wis", info_struct->get_wis());// confirmed DoV + packet->setDataByName("int", info_struct->get_intel());// confirmed DoV + packet->setDataByName("str_base", info_struct->get_str_base()); // confirmed DoV + packet->setDataByName("sta_base", info_struct->get_sta_base());// confirmed DoV + packet->setDataByName("agi_base", info_struct->get_agi_base());// confirmed DoV + packet->setDataByName("wis_base", info_struct->get_wis_base());// confirmed DoV + packet->setDataByName("int_base", info_struct->get_intel_base());// confirmed DoV + if (version <= 996) { + packet->setDataByName("heat", info_struct->get_heat()); + packet->setDataByName("cold", info_struct->get_cold()); + packet->setDataByName("magic", info_struct->get_magic()); + packet->setDataByName("mental", info_struct->get_mental()); + packet->setDataByName("divine", info_struct->get_divine()); + packet->setDataByName("disease", info_struct->get_disease()); + packet->setDataByName("poison", info_struct->get_poison()); + packet->setDataByName("heat_base", info_struct->get_heat_base()); + packet->setDataByName("cold_base", info_struct->get_cold_base()); + packet->setDataByName("magic_base", info_struct->get_magic_base()); + packet->setDataByName("mental_base", info_struct->get_mental_base()); + packet->setDataByName("divine_base", info_struct->get_divine_base()); + packet->setDataByName("disease_base", info_struct->get_disease_base()); + packet->setDataByName("poison_base", info_struct->get_poison_base()); + } + else { + packet->setDataByName("elemental", info_struct->get_heat());// confirmed DoV + packet->setDataByName("noxious", info_struct->get_poison());// confirmed DoV + packet->setDataByName("arcane", info_struct->get_magic());// confirmed DoV + packet->setDataByName("elemental_base", info_struct->get_elemental_base());// confirmed DoV + packet->setDataByName("noxious_base", info_struct->get_noxious_base());// confirmed DoV + packet->setDataByName("arcane_base", info_struct->get_arcane_base());// confirmed DoV + } + packet->setDataByName("elemental_absorb_pve", 0); //210 = 21.0% confirmed DoV + packet->setDataByName("noxious_absorb_pve", 0);//210 = 21.0% confirmed DoV + packet->setDataByName("arcane_absorb_pve", 0);//210 = 21.0% confirmed DoV + packet->setDataByName("elemental_absorb_pvp", 0);//210 = 21.0% confirmed DoV + packet->setDataByName("noxious_absorb_pvp", 0);//210 = 21.0% confirmed DoV + packet->setDataByName("arcane_absorb_pvp", 0);//210 = 21.0% confirmed DoV + packet->setDataByName("elemental_dmg_reduction", 0);// confirmed DoV + packet->setDataByName("noxious_dmg_reduction", 0);// confirmed DoV + packet->setDataByName("arcane_dmg_reduction", 0);// confirmed DoV + packet->setDataByName("elemental_dmg_reduction_pct", 0);//210 = 21.0% confirmed DoV + packet->setDataByName("noxious_dmg_reduction_pct", 0);//210 = 21.0% confirmed DoV + packet->setDataByName("arcane_dmg_reduction_pct", 0);//210 = 21.0% confirmed DoV + CalculateXPPercentages(); + packet->setDataByName("current_adv_xp", info_struct->get_xp()); // confirmed DoV + packet->setDataByName("needed_adv_xp", info_struct->get_xp_needed());// confirmed DoV + + if(version >= 60114) + { + // AoM ends up the debt_adv_xp field is the percentage of xp to the next level needed to advance out of debt (WHYY CANT THIS JUST BE A PERCENTAGE LIKE DOV!) + float currentPctOfLevel = (float)info_struct->get_xp() / (float)info_struct->get_xp_needed(); + float neededPctAdvanceOutOfDebt = currentPctOfLevel + (info_struct->get_xp_debt() / 100.0f); + packet->setDataByName("debt_adv_xp", neededPctAdvanceOutOfDebt); + } + else + { + double currentPctOfLevel = (double)info_struct->get_xp() / (double)info_struct->get_xp_needed(); + double neededPctAdvanceOutOfDebt = (currentPctOfLevel + ((double)info_struct->get_xp_debt() / 100.0)) * 1000.0; + packet->setDataByName("exp_debt", (int16)(neededPctAdvanceOutOfDebt));//95= 9500% //confirmed DoV + } + + packet->setDataByName("current_trade_xp", info_struct->get_ts_xp());// confirmed DoV + packet->setDataByName("needed_trade_xp", info_struct->get_ts_xp_needed());// confirmed DoV + + packet->setDataByName("debt_trade_xp", 0);//95= 9500% //confirmed DoV + packet->setDataByName("server_bonus", 0);//confirmed DoV + packet->setDataByName("adventure_vet_bonus", 145);//confirmed DoV + packet->setDataByName("tradeskill_vet_bonus", 123);//confirmed DoV + packet->setDataByName("recruit_friend", 110);// 110 = 11000% //confirmed DoV + packet->setDataByName("recruit_friend_bonus", 0);//confirmed DoV + + packet->setDataByName("adventure_vitality", (int16)(player->GetXPVitality() * 10)); // a %% + packet->setDataByName("adventure_vitality_yellow_arrow", info_struct->get_xp_yellow_vitality_bar()); //change info_struct to match struct + packet->setDataByName("adventure_vitality_blue_arrow", info_struct->get_xp_blue_vitality_bar()); //change info_struct to match struct + + packet->setDataByName("tradeskill_vitality", 300); //300 = 30% + + packet->setDataByName("tradeskill_vitality_purple_arrow", 0);// dov confirmed + packet->setDataByName("tradeskill_vitality_blue_arrow", 0);// dov confirmed + packet->setDataByName("mentor_bonus", 50);//mentor_bonus //this converts wrong says mentor bonus enabled but earning 0 + + packet->setDataByName("assigned_aa", player->GetAssignedAA()); + packet->setDataByName("max_aa", rule_manager.GetGlobalRule(R_Player, MaxAA)->GetInt16()); + packet->setDataByName("unassigned_aa", player->GetUnassignedAA()); // dov confirmed + packet->setDataByName("aa_green_bar", 0);// dov confirmed + packet->setDataByName("adv_xp_to_aa_xp_slider", 0); // aa slider max // dov confirmed + packet->setDataByName("adv_xp_to_aa_xp_max", 100); // aa slider position // dov confirmed + packet->setDataByName("aa_blue_bar", 0);// dov confirmed + packet->setDataByName("bonus_achievement_xp", 0); // dov confirmed + + packet->setDataByName("level_events", 32);// dov confirmed + packet->setDataByName("items_found", 62);// dov confirmed + packet->setDataByName("named_npcs_killed", 192);// dov confirmed + packet->setDataByName("quests_completed", 670);// dov confirmed + packet->setDataByName("exploration_events", 435);// dov confirmed + packet->setDataByName("completed_collections", 144);// dov confirmed + packet->setDataByName("unknown_1096_13_MJ", 80);//unknown_1096_13_MJ + packet->setDataByName("unknown_1096_14_MJ", 50);//unknown_1096_14_MJ + packet->setDataByName("coins_copper", info_struct->get_coin_copper());// dov confirmed + packet->setDataByName("coins_silver", info_struct->get_coin_silver());// dov confirmed + packet->setDataByName("coins_gold", info_struct->get_coin_gold());// dov confirmed + packet->setDataByName("coins_plat", info_struct->get_coin_plat());// dov confirmed + + Skill* skill = player->GetSkillByName("Swimming", false); + float breath_modifier = rule_manager.GetGlobalRule(R_Player, SwimmingSkillMinBreathLength)->GetFloat(); + if(skill) { + int32 max_val = 450; + if(skill->max_val > 0) + max_val = skill->max_val; + float diff = (float)(skill->current_val + player->GetStat(ITEM_STAT_SWIMMING)) / (float)max_val; + float max_breath_mod = rule_manager.GetGlobalRule(R_Player, SwimmingSkillMaxBreathLength)->GetFloat(); + float diff_mod = max_breath_mod * diff; + if(diff_mod > max_breath_mod) + breath_modifier = max_breath_mod; + else if(diff_mod > breath_modifier) + breath_modifier = diff_mod; + } + packet->setDataByName("breath", breath_modifier); + + packet->setDataByName("melee_pri_dmg_min", player->GetPrimaryWeaponMinDamage());// dov confirmed + packet->setDataByName("melee_pri_dmg_max", player->GetPrimaryWeaponMaxDamage());// dov confirmed + packet->setDataByName("melee_sec_dmg_min", player->GetSecondaryWeaponMinDamage());// dov confirmed + packet->setDataByName("melee_sec_dmg_max", player->GetSecondaryWeaponMaxDamage());// dov confirmed // this is off when using 2 handed weapon + packet->setDataByName("ranged_dmg_min", player->GetRangedWeaponMinDamage());// dov confirmed + packet->setDataByName("ranged_dmg_max", player->GetRangedWeaponMaxDamage());// dov confirmed + if (info_struct->get_attackspeed() > 0) { + packet->setDataByName("melee_pri_delay", (((float)player->GetPrimaryWeaponDelay() * 1.33) / player->CalculateAttackSpeedMod()) * .001);// dov confirmed + packet->setDataByName("melee_sec_delay", (((float)player->GetSecondaryWeaponDelay() * 1.33) / player->CalculateAttackSpeedMod()) * .001);// dov confirmed + packet->setDataByName("ranged_delay", (((float)player->GetRangeWeaponDelay() * 1.33) / player->CalculateAttackSpeedMod()) * .001);// dov confirmed + } + else { + packet->setDataByName("melee_pri_delay", (float)player->GetPrimaryWeaponDelay() * .001);// dov confirmed + packet->setDataByName("melee_sec_delay", (float)player->GetSecondaryWeaponDelay() * .001);// dov confirmed + packet->setDataByName("ranged_delay", (float)player->GetRangeWeaponDelay() * .001);// dov confirmed + } + + packet->setDataByName("ability_mod_pve", info_struct->get_ability_modifier());// dov confirmed + packet->setDataByName("base_melee_crit", 85);//85 = 8500% dov confirmed + packet->setDataByName("base_spell_crit", 84);// dov confirmed + packet->setDataByName("base_taunt_crit", 83);// dov confirmed + packet->setDataByName("base_heal_crit", 82);// dov confirmed + packet->setDataByName("flags", info_struct->get_flags()); + packet->setDataByName("flags2", info_struct->get_flags2()); + if (version == 546) { + if (player->get_character_flag(CF_ANONYMOUS)) + packet->setDataByName("flags_anonymous", 1); + if (player->get_character_flag(CF_ROLEPLAYING)) + packet->setDataByName("flags_roleplaying", 1); + if (player->get_character_flag(CF_AFK)) + packet->setDataByName("flags_afk", 1); + if (player->get_character_flag(CF_LFG)) + packet->setDataByName("flags_lfg", 1); + if (player->get_character_flag(CF_LFW)) + packet->setDataByName("flags_lfw", 1); + if (!player->get_character_flag(CF_HIDE_HOOD) && !player->get_character_flag(CF_HIDE_HELM)) + packet->setDataByName("flags_show_hood", 1); + if (player->get_character_flag(CF_SHOW_ILLUSION)) + packet->setDataByName("flags_show_illusion_form", 1); + if (player->get_character_flag(CF_ALLOW_DUEL_INVITES)) + packet->setDataByName("flags_show_duel_invites", 1); + if (player->get_character_flag(CF_ALLOW_TRADE_INVITES)) + packet->setDataByName("flags_show_trade_invites", 1); + if (player->get_character_flag(CF_ALLOW_GROUP_INVITES)) + packet->setDataByName("flags_show_group_invites", 1); + if (player->get_character_flag(CF_ALLOW_RAID_INVITES)) + packet->setDataByName("flags_show_raid_invites", 1); + if (player->get_character_flag(CF_ALLOW_GUILD_INVITES)) + packet->setDataByName("flags_show_guild_invites", 1); + } + + packet->setDataByName("haste", info_struct->get_haste());// dov confirmed + packet->setDataByName("drunk", info_struct->get_drunk());// dov confirmed + + packet->setDataByName("hate_mod", info_struct->get_hate_mod());// dov confirmed + packet->setDataByName("adventure_effects_bonus", 55);// NEED an adventure_effects_bonus// dov confirmed + packet->setDataByName("tradeskill_effects_bonus", 56);// NEED an tradeskill_effects_bonus// dov confirmed + packet->setDataByName("dps", info_struct->get_dps());// dov confirmed + packet->setDataByName("melee_ae", info_struct->get_melee_ae());// dov confirmed + packet->setDataByName("multi_attack", info_struct->get_multi_attack());// dov confirmed + packet->setDataByName("spell_multi_attack", info_struct->get_spell_multi_attack());// dov confirmed + packet->setDataByName("block_chance", info_struct->get_block_chance());// dov confirmed + packet->setDataByName("crit_chance", info_struct->get_crit_chance());// dov confirmed + packet->setDataByName("crit_bonus", info_struct->get_crit_bonus());// dov confirmed + + packet->setDataByName("potency", info_struct->get_potency());//info_struct->get_potency);// dov confirmed + + packet->setDataByName("reuse_speed", info_struct->get_reuse_speed());// dov confirmed + packet->setDataByName("recovery_speed", info_struct->get_recovery_speed());// dov confirmed + packet->setDataByName("casting_speed", info_struct->get_casting_speed());// dov confirmed + packet->setDataByName("spell_reuse_speed", info_struct->get_spell_reuse_speed());// dov confirmed + packet->setDataByName("strikethrough", info_struct->get_strikethrough());//dov confirmed + packet->setDataByName("accuracy", info_struct->get_accuracy());//dov confirmed + packet->setDataByName("critical_mit", info_struct->get_critical_mitigation());//dov /confirmed + + ((Entity*)player)->MStats.lock(); + packet->setDataByName("durability_mod", player->stats[ITEM_STAT_DURABILITY_MOD]);// dov confirmed + packet->setDataByName("durability_add", player->stats[ITEM_STAT_DURABILITY_ADD]);// dov confirmed + packet->setDataByName("progress_mod", player->stats[ITEM_STAT_PROGRESS_MOD]);// dov confirmed + packet->setDataByName("progress_add", player->stats[ITEM_STAT_PROGRESS_ADD]);// dov confirmed + packet->setDataByName("success_mod", player->stats[ITEM_STAT_SUCCESS_MOD]);// dov confirmed + packet->setDataByName("crit_success_mod", player->stats[ITEM_STAT_CRIT_SUCCESS_MOD]);// dov confirmed + ((Entity*)player)->MStats.unlock(); + + if (version <= 373 && info_struct->get_pet_id() == 0xFFFFFFFF) + packet->setDataByName("pet_id", 0); + else { + packet->setDataByName("pet_id", info_struct->get_pet_id()); + char pet_name[32]; + strncpy(pet_name, info_struct->get_pet_name().c_str(), version <= 373 ? 16 : 32); + packet->setDataByName("pet_name", pet_name); + } + + packet->setDataByName("pet_health_pct", info_struct->get_pet_health_pct()); + packet->setDataByName("pet_power_pct", info_struct->get_pet_power_pct()); + + packet->setDataByName("pet_movement", info_struct->get_pet_movement()); + packet->setDataByName("pet_behavior", info_struct->get_pet_behavior()); + packet->setDataByName("rain", info_struct->get_rain()); + packet->setDataByName("rain2", info_struct->get_wind()); //-102.24); + packet->setDataByName("status_points", info_struct->get_status_points()); + packet->setDataByName("guild_status", 888888); + + if (house_zone_id > 0){ + string house_name = database.GetZoneName(house_zone_id); + if(house_name.length() > 0) + packet->setDataByName("house_zone", house_name.c_str()); + } + else + packet->setDataByName("house_zone", "None"); + + if (bind_zone_id > 0){ + string bind_name = database.GetZoneName(bind_zone_id); + if(bind_name.length() > 0) + packet->setDataByName("bind_zone", bind_name.c_str()); + } + else + packet->setDataByName("bind_zone", "None"); + + + ((Entity*)player)->MStats.lock(); + packet->setDataByName("rare_harvest_chance", player->stats[ITEM_STAT_RARE_HARVEST_CHANCE]); + packet->setDataByName("max_crafting", player->stats[ITEM_STAT_MAX_CRAFTING]); + packet->setDataByName("component_refund", player->stats[ITEM_STAT_COMPONENT_REFUND]); + packet->setDataByName("ex_durability_mod", player->stats[ITEM_STAT_EX_DURABILITY_MOD]); + packet->setDataByName("ex_durability_add", player->stats[ITEM_STAT_EX_DURABILITY_ADD]); + packet->setDataByName("ex_crit_success_mod", player->stats[ITEM_STAT_EX_CRIT_SUCCESS_MOD]); + packet->setDataByName("ex_crit_failure_mod", player->stats[ITEM_STAT_EX_CRIT_FAILURE_MOD]); + packet->setDataByName("ex_progress_mod", player->stats[ITEM_STAT_EX_PROGRESS_MOD]); + packet->setDataByName("ex_progress_add", player->stats[ITEM_STAT_EX_PROGRESS_ADD]); + packet->setDataByName("ex_success_mod", player->stats[ITEM_STAT_EX_SUCCESS_MOD]); + ((Entity*)player)->MStats.unlock(); + + packet->setDataByName("flurry", info_struct->get_flurry()); + packet->setDataByName("unknown153", 153); + packet->setDataByName("bountiful_harvest", 0); // need bountiful harvest + + packet->setDataByName("unknown156", 156); + packet->setDataByName("unknown157", 157); + + packet->setDataByName("unknown159", 159); + packet->setDataByName("unknown160", 160); + + + packet->setDataByName("unknown163", 163); + + + packet->setDataByName("unknown168", 168); + packet->setDataByName("decrease_falling_dmg", 169); + + if (version <= 561) { + packet->setDataByName("exp_yellow", info_struct->get_xp_yellow() / 10); + packet->setDataByName("exp_blue", info_struct->get_xp_blue()/10); + } + else { + packet->setDataByName("exp_yellow", info_struct->get_xp_yellow()); + packet->setDataByName("exp_blue", info_struct->get_xp_blue()); + } + + if (version <= 561) { + packet->setDataByName("tradeskill_exp_yellow", info_struct->get_tradeskill_exp_yellow() / 10); + packet->setDataByName("tradeskill_exp_blue", info_struct->get_tradeskill_exp_blue() / 10); + } + else { + packet->setDataByName("tradeskill_exp_yellow", info_struct->get_tradeskill_exp_yellow()); + packet->setDataByName("tradeskill_exp_blue", info_struct->get_tradeskill_exp_blue()); + } + + packet->setDataByName("attack", info_struct->get_cur_attack()); + packet->setDataByName("attack_base", info_struct->get_attack_base()); + packet->setDataByName("absorb", info_struct->get_absorb()); + packet->setDataByName("mitigation_skill1", info_struct->get_mitigation_skill1()); + packet->setDataByName("mitigation_skill2", info_struct->get_mitigation_skill2()); + packet->setDataByName("mitigation_skill3", info_struct->get_mitigation_skill3()); + + packet->setDataByName("mitigation_max", info_struct->get_max_mitigation()); + + packet->setDataByName("savagery", 250); + packet->setDataByName("max_savagery", 500); + packet->setDataByName("savagery_level", 1); + packet->setDataByName("max_savagery_level", 5); + packet->setDataByName("dissonance", 5000); + packet->setDataByName("max_dissonance", 10000); + + packet->setDataByName("mitigation_cur2", info_struct->get_cur_mitigation()); + packet->setDataByName("mitigation_max2", info_struct->get_max_mitigation()); + packet->setDataByName("mitigation_base2", info_struct->get_mitigation_base()); + + packet->setDataByName("weight", info_struct->get_weight()); + packet->setDataByName("max_weight", info_struct->get_max_weight()); + packet->setDataByName("unknownint32a", 777777); + packet->setDataByName("unknownint32b", 666666); + packet->setDataByName("mitigation2_cur", 2367); + packet->setDataByName("uncontested_riposte", info_struct->get_uncontested_riposte()); + packet->setDataByName("uncontested_dodge", info_struct->get_uncontested_dodge()); + packet->setDataByName("uncontested_parry", info_struct->get_uncontested_parry()); //???? + packet->setDataByName("uncontested_riposte_pve", 0); //???? + packet->setDataByName("uncontested_parry_pve", 0); //???? + packet->setDataByName("total_prestige_points", player->GetPrestigeAA()); + packet->setDataByName("unassigned_prestige_points", player->GetUnassignedPretigeAA()); + packet->setDataByName("total_tradeskill_points", player->GetTradeskillAA()); + packet->setDataByName("unassigned_tradeskill_points", player->GetUnassignedTradeskillAA()); + packet->setDataByName("total_tradeskill_prestige_points", player->GetTradeskillPrestigeAA()); + packet->setDataByName("unassigned_tradeskill_prestige_points", player->GetUnassignedTradeskillPrestigeAA()); + + // unknown14c = percent aa exp to next level + packet->setDataByName("unknown14d", 100, 0); + packet->setDataByName("unknown20", 1084227584, 72); + packet->setDataByName("unknown15c", 200); + + player->SetGroupInformation(packet); + + packet->setDataByName("in_combat_movement_speed", 125); + + packet->setDataByName("increase_max_power", 127); + packet->setDataByName("increase_max_power2", 128); + + packet->setDataByName("vision", info_struct->get_vision()); + packet->setDataByName("breathe_underwater", info_struct->get_breathe_underwater()); + + int32 expireTimestamp = 0; + Spawn* maintained_target = 0; + player->GetSpellEffectMutex()->readlock(__FUNCTION__, __LINE__); + player->GetMaintainedMutex()->readlock(__FUNCTION__, __LINE__); + for (int i = 0; i < 45; i++) { + if (i < 30) { + maintained_target = player->GetZone()->GetSpawnByID(info_struct->maintained_effects[i].target); + packet->setSubstructDataByName("maintained_effects", "name", info_struct->maintained_effects[i].name, i, 0); + if (maintained_target) + packet->setSubstructDataByName("maintained_effects", "target", player->GetIDWithPlayerSpawn(maintained_target), i, 0); + packet->setSubstructDataByName("maintained_effects", "target_type", info_struct->maintained_effects[i].target_type, i, 0); + packet->setSubstructDataByName("maintained_effects", "spell_id", info_struct->maintained_effects[i].spell_id, i, 0); + packet->setSubstructDataByName("maintained_effects", "slot_pos", info_struct->maintained_effects[i].slot_pos, i, 0); + packet->setSubstructDataByName("maintained_effects", "icon", info_struct->maintained_effects[i].icon, i, 0); + packet->setSubstructDataByName("maintained_effects", "icon_type", info_struct->maintained_effects[i].icon_backdrop, i, 0); + packet->setSubstructDataByName("maintained_effects", "conc_used", info_struct->maintained_effects[i].conc_used, i, 0); + packet->setSubstructDataByName("maintained_effects", "unknown3", 1, i, 0); + packet->setSubstructDataByName("maintained_effects", "total_time", info_struct->maintained_effects[i].total_time, i, 0); + expireTimestamp = info_struct->maintained_effects[i].expire_timestamp; + if (expireTimestamp == 0xFFFFFFFF) + expireTimestamp = 0; + packet->setSubstructDataByName("maintained_effects", "expire_timestamp", expireTimestamp, i, 0); + } + else if (version < 942)//version 942 added 15 additional spell effect slots + break; + packet->setSubstructDataByName("spell_effects", "spell_id", info_struct->spell_effects[i].spell_id, i, 0); + packet->setSubstructDataByName("spell_effects", "total_time", info_struct->spell_effects[i].total_time, i, 0); + expireTimestamp = info_struct->spell_effects[i].expire_timestamp; + if (expireTimestamp == 0xFFFFFFFF) + expireTimestamp = 0; + packet->setSubstructDataByName("spell_effects", "expire_timestamp", expireTimestamp, i, 0); + packet->setSubstructDataByName("spell_effects", "icon", info_struct->spell_effects[i].icon, i, 0); + packet->setSubstructDataByName("spell_effects", "icon_type", info_struct->spell_effects[i].icon_backdrop, i, 0); + if(info_struct->spell_effects[i].spell && info_struct->spell_effects[i].spell->spell && info_struct->spell_effects[i].spell->spell->GetSpellData()->friendly_spell == 1) + packet->setSubstructDataByName("spell_effects", "cancellable", 1, i); + } + player->GetMaintainedMutex()->releasereadlock(__FUNCTION__, __LINE__); + player->GetSpellEffectMutex()->releasereadlock(__FUNCTION__, __LINE__); + + int8 det_count = 0; + //Send detriment counts as 255 if all dets of that type are incurable + det_count = player->GetTraumaCount(); + if (det_count > 0) { + if (!player->HasCurableDetrimentType(DET_TYPE_TRAUMA)) + det_count = 255; + } + packet->setDataByName("trauma_count", det_count); + + det_count = player->GetArcaneCount(); + if (det_count > 0) { + if (!player->HasCurableDetrimentType(DET_TYPE_ARCANE)) + det_count = 255; + } + packet->setDataByName("arcane_count", det_count); + + det_count = player->GetNoxiousCount(); + if (det_count > 0) { + if (!player->HasCurableDetrimentType(DET_TYPE_NOXIOUS)) + det_count = 255; + } + packet->setDataByName("noxious_count", det_count); + + det_count = player->GetElementalCount(); + if (det_count > 0) { + if (!player->HasCurableDetrimentType(DET_TYPE_ELEMENTAL)) + det_count = 255; + } + packet->setDataByName("elemental_count", det_count); + + det_count = player->GetCurseCount(); + if (det_count > 0) { + if (!player->HasCurableDetrimentType(DET_TYPE_CURSE)) + det_count = 255; + } + packet->setDataByName("curse_count", det_count); + + player->GetDetrimentMutex()->readlock(__FUNCTION__, __LINE__); + vector* det_list = player->GetDetrimentalSpellEffects(); + DetrimentalEffects det; + int32 i = 0; + for (i = 0; i < det_list->size(); i++) { + det = det_list->at(i); + packet->setSubstructDataByName("detrimental_spell_effects", "spell_id", det.spell_id, i); + packet->setSubstructDataByName("detrimental_spell_effects", "total_time", det.total_time, i); + packet->setSubstructDataByName("detrimental_spell_effects", "icon", det.icon, i); + packet->setSubstructDataByName("detrimental_spell_effects", "icon_type", det.icon_backdrop, i); + expireTimestamp = det.expire_timestamp; + if (expireTimestamp == 0xFFFFFFFF) + expireTimestamp = 0; + packet->setSubstructDataByName("detrimental_spell_effects", "expire_timestamp", expireTimestamp, i); + packet->setSubstructDataByName("detrimental_spell_effects", "unknown2", 2, i); + if (i == 30) { + if (version < 942) + break; + } + else if (i == 45) + break; + } + if (version < 942) { + while (i < 30) { + packet->setSubstructDataByName("detrimental_spell_effects", "spell_id", 0xFFFFFFFF, i); + i++; + } + } + else { + while (i < 45) { + packet->setSubstructDataByName("detrimental_spell_effects", "spell_id", 0xFFFFFFFF, i); + i++; + } + } + player->GetDetrimentMutex()->releasereadlock(__FUNCTION__, __LINE__); + + // disabling as not in use right now + //packet->setDataByName("spirit_rank", 2); + //packet->setDataByName("spirit", 1); + //packet->setDataByName("spirit_progress", .67); + + packet->setDataByName("combat_exp_enabled", 1); + + string* data = packet->serializeString(); + int32 size = data->length(); + + printf("CharSheet size: %u for version %u\n", size, version); + //DumpPacket((uchar*)data->c_str(), data->size()); + //packet->PrintPacket(); + uchar* tmp = new uchar[size]; + bool reverse = version > 373; + if (!changes) { + orig_packet = new uchar[size]; + changes = new uchar[size]; + memcpy(orig_packet, (uchar*)data->c_str(), size); + size = Pack(tmp, orig_packet, size, size, version, reverse); + } + else { + memcpy(changes, (uchar*)data->c_str(), size); + if (modifyPos > 0) { + uchar* ptr2 = (uchar*)changes; + ptr2 += modifyPos - 1; + if (modifyValue > 0xFFFF) { + memcpy(ptr2, (uchar*)&modifyValue, 4); + } + else if (modifyValue > 0xFF) { + memcpy(ptr2, (uchar*)&modifyValue, 2); + } + else + memcpy(ptr2, (uchar*)&modifyValue, 1); + } + Encode(changes, orig_packet, size); + if (modifyPos > 0) { + uchar* ptr2 = (uchar*)orig_packet; + if (modifyPos > 64) + ptr2 += modifyPos - 64; + int16 tmpsize = modifyPos + 128; + if (tmpsize > size) + tmpsize = size; + } + size = Pack(tmp, changes, size, size, version, reverse); + } + + if (version >= 546 && player->GetClient()) { + player->GetClient()->SendControlGhost(); + } + + EQ2Packet* ret_packet = new EQ2Packet(OP_UpdateCharacterSheetMsg, tmp, size); + safe_delete(packet); + safe_delete_array(tmp); + return ret_packet; + } + return 0; +} + +EQ2Packet* PlayerInfo::serializePet(int16 version) { + PacketStruct* packet = configReader.getStruct("WS_CharacterPet", version); + if(packet) { + Spawn* pet = 0; + pet = player->GetPet(); + if (!pet) + pet = player->GetCharmedPet(); + + if (pet) { + packet->setDataByName("current_hp", pet->GetHP()); + packet->setDataByName("max_hp", pet->GetTotalHP()); + packet->setDataByName("base_hp", pet->GetTotalHPBase()); + + packet->setDataByName("current_power", pet->GetPower()); + packet->setDataByName("max_power", pet->GetTotalPower()); + packet->setDataByName("base_power", pet->GetTotalPowerBase()); + + packet->setDataByName("spawn_id", info_struct->get_pet_id()); + packet->setDataByName("spawn_id2", info_struct->get_pet_id()); + + if(info_struct->get_pet_id() != 0xFFFFFFFF) { + packet->setDataByName("pet_id", info_struct->get_pet_id()); + char pet_name[32]; + strncpy(pet_name, info_struct->get_pet_name().c_str(), 32); + packet->setDataByName("name", pet_name); + } + else { + packet->setDataByName("name", "No Pet"); + packet->setDataByName("no_pet", "No Pet"); + } + + if (version >= 57000) { + packet->setDataByName("current_power3", pet->GetPower()); + packet->setDataByName("max_power3", pet->GetTotalPower()); + packet->setDataByName("health_pct_tooltip", (double)info_struct->get_pet_health_pct()); + packet->setDataByName("health_pct_bar", (double)info_struct->get_pet_health_pct()); + } + else { + packet->setDataByName("health_pct_tooltip", info_struct->get_pet_health_pct()); + packet->setDataByName("health_pct_bar", info_struct->get_pet_health_pct()); + } + packet->setDataByName("power_pct_tooltip", info_struct->get_pet_power_pct()); + packet->setDataByName("power_pct_bar", info_struct->get_pet_power_pct()); + packet->setDataByName("unknown5", 255); // Hate % maybe + packet->setDataByName("movement", info_struct->get_pet_movement()); + packet->setDataByName("behavior", info_struct->get_pet_behavior()); + } + else { + packet->setDataByName("current_hp", 0); + packet->setDataByName("max_hp", 0); + packet->setDataByName("base_hp", 0); + packet->setDataByName("current_power", 0); + packet->setDataByName("max_power", 0); + packet->setDataByName("base_power", 0); + + packet->setDataByName("spawn_id", 0); + packet->setDataByName("spawn_id2", 0xFFFFFFFF); + packet->setDataByName("name", ""); + packet->setDataByName("no_pet", "No Pet"); + packet->setDataByName("health_pct_tooltip", 0); + packet->setDataByName("health_pct_bar", 0); + packet->setDataByName("power_pct_tooltip", 0); + packet->setDataByName("power_pct_bar", 0); + packet->setDataByName("unknown5", 0); + packet->setDataByName("movement", 0); + packet->setDataByName("behavior", 0); + } + + + string* data = packet->serializeString(); + int32 size = data->length(); + uchar* tmp = new uchar[size]; + // if this is the first time sending this packet create the buffers + if(!pet_changes){ + pet_orig_packet = new uchar[size]; + pet_changes = new uchar[size]; + // copy the packet into the pet_orig_packet so we can xor against it in the future + memcpy(pet_orig_packet, (uchar*)data->c_str(), size); + // pack the packet, result ends up in tmp + size = Pack(tmp, (uchar*)data->c_str(), size, size, version); + } + else{ + // copy the packet into pet_changes + memcpy(pet_changes, (uchar*)data->c_str(), size); + // XOR's the packet to the original, stores the new packet in the orig packet (will xor against that for the next update) + // puts the xor packet into pet_changes. + Encode(pet_changes, pet_orig_packet, size); + // Pack the pet_changes packet, will put the packed size at the start, result ends up in tmp + size = Pack(tmp, pet_changes, size, size, version); + } + + // Create the packet that we will send + EQ2Packet* ret_packet = new EQ2Packet(OP_CharacterPet, tmp, size+4); + // Clean up + safe_delete_array(tmp); + safe_delete(packet); + // Return the packet that will be sent to the client + return ret_packet; + } + return 0; +} + +bool Player::DamageEquippedItems(int8 amount, Client* client) { + bool ret = false; + int8 item_type; + Item* item = 0; + equipment_list.MEquipmentItems.readlock(__FUNCTION__, __LINE__); + for(int8 i=0;igeneric_info.item_type; + if (item->details.item_id > 0 && item_type != ITEM_TYPE_FOOD && item_type != ITEM_TYPE_BAUBLE && item_type != ITEM_TYPE_THROWN && + !item->CheckFlag2(INDESTRUCTABLE)){ + ret = true; + if((item->generic_info.condition - amount) > 0) + item->generic_info.condition -= amount; + else + item->generic_info.condition = 0; + item->save_needed = true; + if (client) + client->QueuePacket(item->serialize(client->GetVersion(), false, this)); + } + } + } + equipment_list.MEquipmentItems.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +int16 Player::ConvertSlotToClient(int8 slot, int16 version) { + if (version <= 373) { + if (slot == EQ2_FOOD_SLOT) + slot = EQ2_ORIG_FOOD_SLOT; + else if (slot == EQ2_DRINK_SLOT) + slot = EQ2_ORIG_DRINK_SLOT; + else if (slot > EQ2_EARS_SLOT_1 && slot <= EQ2_WAIST_SLOT) + slot -= 1; + } + else if (version <= 561) { + if (slot == EQ2_FOOD_SLOT) + slot = EQ2_DOF_FOOD_SLOT; + else if (slot == EQ2_DRINK_SLOT) + slot = EQ2_DOF_DRINK_SLOT; + else if (slot > EQ2_EARS_SLOT_1 && slot <= EQ2_WAIST_SLOT) + slot -= 1; + } + return slot; +} + +int16 Player::ConvertSlotFromClient(int8 slot, int16 version) { + if (version <= 373) { + if (slot == EQ2_ORIG_FOOD_SLOT) + slot = EQ2_FOOD_SLOT; + else if (slot == EQ2_ORIG_DRINK_SLOT) + slot = EQ2_DRINK_SLOT; + else if (slot > EQ2_EARS_SLOT_1 && slot <= EQ2_WAIST_SLOT) + slot += 1; + } + else if (version <= 561) { + if (slot == EQ2_DOF_FOOD_SLOT) + slot = EQ2_FOOD_SLOT; + else if (slot == EQ2_DOF_DRINK_SLOT) + slot = EQ2_DRINK_SLOT; + else if (slot > EQ2_EARS_SLOT_1 && slot <= EQ2_WAIST_SLOT) + slot += 1; + } + return slot; +} + +int16 Player::GetNumSlotsEquip(int16 version) { + if(version <= 561) { + return CLASSIC_NUM_SLOTS; + } + + return NUM_SLOTS; +} + +int8 Player::GetMaxBagSlots(int16 version) { + if(version <= 373) { + return CLASSIC_EQ_MAX_BAG_SLOTS; + } + else if(version <= 561) { + return DOF_EQ_MAX_BAG_SLOTS; + } + + return 255; +} + +vector Player::UnequipItem(int16 index, sint32 bag_id, int8 slot, int16 version, int8 appearance_type, bool send_item_updates) { + vector packets; + EquipmentItemList* equipList = &equipment_list; + + if(appearance_type) + equipList = &appearance_equipment_list; + + if(index >= NUM_SLOTS) { + LogWrite(PLAYER__ERROR, 0, "Player", "%u index is out of range for equip items, bag_id: %i, slot: %u, version: %u, appearance: %u", index, bag_id, slot, version, appearance_type); + return packets; + } + equipList->MEquipmentItems.readlock(__FUNCTION__, __LINE__); + Item* item = equipList->items[index]; + + if(item && !IsAllowedCombatEquip(item->details.slot_id, true)) { + LogWrite(PLAYER__ERROR, 0, "Player", "Attempt to unequip item %s (%u) FAILED in combat!", item->name.c_str(), item->details.item_id); + equipList->MEquipmentItems.releasereadlock(__FUNCTION__, __LINE__); + return packets; + } + equipList->MEquipmentItems.releasereadlock(__FUNCTION__, __LINE__); + + if (item && bag_id == -999) { + int8 old_slot = item->details.slot_id; + if(item->details.equip_slot_id) { + if (item->GetItemScript() && lua_interface) + lua_interface->RunItemScript(item->GetItemScript(), "unequipped", item, this); + const char* zone_script = world.GetZoneScript(GetZone()->GetZoneID()); + if (zone_script && lua_interface) + lua_interface->RunZoneScript(zone_script, "item_unequipped", GetZone(), this, item->details.item_id, item->name.c_str(), 0, item->details.unique_id); + item->save_needed = true; + EQ2Packet* outapp = item_list.serialize(this, version); + if (outapp) { + packets.push_back(outapp); + packets.push_back(item->serialize(version, false)); + EQ2Packet* bag_packet = SendBagUpdate(item->details.inv_slot_id, version); + if (bag_packet) + packets.push_back(bag_packet); + } + sint16 equip_slot_id = item->details.equip_slot_id; + item->details.equip_slot_id = 0; + equipList->RemoveItem(index); + SetEquippedItemAppearances(); + packets.push_back(equipList->serialize(version, this)); + SetCharSheetChanged(true); + SetEquipment(0, equip_slot_id ? equip_slot_id : old_slot); + } + else if (item_list.AssignItemToFreeSlot(item)) { + if(appearance_type) + database.DeleteItem(GetCharacterID(), item, "APPEARANCE"); + else + database.DeleteItem(GetCharacterID(), item, "EQUIPPED"); + + if (item->GetItemScript() && lua_interface) + lua_interface->RunItemScript(item->GetItemScript(), "unequipped", item, this); + const char* zone_script = world.GetZoneScript(GetZone()->GetZoneID()); + if (zone_script && lua_interface) + lua_interface->RunZoneScript(zone_script, "item_unequipped", GetZone(), this, item->details.item_id, item->name.c_str(), 0, item->details.unique_id); + item->save_needed = true; + EQ2Packet* outapp = item_list.serialize(this, version); + if (outapp) { + packets.push_back(outapp); + packets.push_back(item->serialize(version, false)); + EQ2Packet* bag_packet = SendBagUpdate(item->details.inv_slot_id, version); + if (bag_packet) + packets.push_back(bag_packet); + } + equipList->RemoveItem(index); + SetEquippedItemAppearances(); + packets.push_back(equipList->serialize(version, this)); + SetCharSheetChanged(true); + SetEquipment(0, old_slot); + } + else { + PacketStruct* packet = configReader.getStruct("WS_DisplayText", version); + if (packet) { + packet->setDataByName("color", CHANNEL_COLOR_YELLOW); + packet->setMediumStringByName("text", "Unable to unequip item: no free inventory locations."); + packet->setDataByName("unknown02", 0x00ff); + packets.push_back(packet->serialize()); + safe_delete(packet); + } + } + } + else if (item) { + Item* to_item = 0; + if(appearance_type && slot == 255) + { + sint16 tmpSlot = 0; + item_list.GetFirstFreeSlot(&bag_id, &tmpSlot); + if(tmpSlot >= 0 && tmpSlot < 255) + slot = tmpSlot; + else + bag_id = 0; + } + + item_list.MPlayerItems.readlock(__FUNCTION__, __LINE__); + if (item_list.items.count(bag_id) > 0 && item_list.items[bag_id][BASE_EQUIPMENT].count(slot) > 0) + to_item = item_list.items[bag_id][BASE_EQUIPMENT][slot]; + + bool canEquipToSlot = false; + if (to_item && equipList->CanItemBeEquippedInSlot(to_item, item->details.slot_id)) { + canEquipToSlot = true; + } + item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + + if (canEquipToSlot) { + equipList->RemoveItem(index); + if(item->details.appearance_type) + database.DeleteItem(GetCharacterID(), item, "APPEARANCE"); + else + database.DeleteItem(GetCharacterID(), item, "EQUIPPED"); + + database.DeleteItem(GetCharacterID(), to_item, "NOT-EQUIPPED"); + + if (item->GetItemScript() && lua_interface) + lua_interface->RunItemScript(item->GetItemScript(), "unequipped", item, this); + + if (to_item->GetItemScript() && lua_interface) + lua_interface->RunItemScript(to_item->GetItemScript(), "equipped", to_item, this); + + if(item->IsBag() && ( item->details.inv_slot_id != bag_id || item->details.slot_id != slot)) { + item_list.EraseItem(item); + } + item_list.RemoveItem(to_item); + equipList->SetItem(item->details.slot_id, to_item); + to_item->save_needed = true; + packets.push_back(to_item->serialize(version, false)); + SetEquipment(to_item); + item->details.inv_slot_id = bag_id; + item->details.slot_id = slot; + item->details.appearance_type = 0; + item->details.equip_slot_id = 0; + + if(!item->IsBag() && item_list.AddItem(item)) { // bags are omitted because they are equipped while remaining in inventory + item->save_needed = true; + SetEquippedItemAppearances(); + // SerializeItemPackets serves item and equipList in opposite order is why we don't use that function here.. + packets.push_back(item->serialize(version, false)); + packets.push_back(equipList->serialize(version, this)); + packets.push_back(item_list.serialize(this, version)); + } + else if(item->IsBag()) { + // already in inventory + } + else { + LogWrite(PLAYER__ERROR, 0, "Player", "failed to add item to item_list during UnequipItem, index %u, bag id %i, slot %u, version %u, appearance type %u", index, bag_id, slot, version, appearance_type); + } + } + else if (to_item && to_item->IsBag() && to_item->details.num_slots > 0) { + bool free_slot = false; + for (int8 i = 0; i < to_item->details.num_slots; i++) { + item_list.MPlayerItems.readlock(__FUNCTION__, __LINE__); + int32 count = item_list.items[to_item->details.bag_id][appearance_type].count(i); + item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + if (count == 0) { + SetEquipment(0, item->details.equip_slot_id ? item->details.equip_slot_id : item->details.slot_id); + + if(item->details.appearance_type) + database.DeleteItem(GetCharacterID(), item, "APPEARANCE"); + else + database.DeleteItem(GetCharacterID(), item, "EQUIPPED"); + + if (item->GetItemScript() && lua_interface) + lua_interface->RunItemScript(item->GetItemScript(), "unequipped", item, this); + + if(item->IsBag() && item != to_item) { + item_list.EraseItem(item); + } + + equipList->RemoveItem(index); + if(!item->IsBag()) { + item->details.inv_slot_id = to_item->details.bag_id; + item->details.slot_id = i; + item->details.appearance_type = to_item->details.appearance_type; + } + else { + item->details.appearance_type = 0; + } + item->details.equip_slot_id = 0; + + SerializeItemPackets(equipList, &packets, item, version, to_item); + free_slot = true; + break; + } + } + if (!free_slot) { + PacketStruct* packet = configReader.getStruct("WS_DisplayText", version); + if (packet) { + packet->setDataByName("color", CHANNEL_COLOR_YELLOW); + packet->setMediumStringByName("text", "Unable to unequip item: no free space in the bag."); + packet->setDataByName("unknown02", 0x00ff); + packets.push_back(packet->serialize()); + safe_delete(packet); + } + } + } + else if (to_item) { + PacketStruct* packet = configReader.getStruct("WS_DisplayText", version); + if (packet) { + packet->setDataByName("color", CHANNEL_COLOR_YELLOW); + packet->setMediumStringByName("text", "Unable to swap items: that item cannot be equipped there."); + packet->setDataByName("unknown02", 0x00ff); + packets.push_back(packet->serialize()); + safe_delete(packet); + } + } + else { + if ((bag_id == 0 && slot < NUM_INV_SLOTS) || (bag_id == -3 && slot < NUM_BANK_SLOTS) || (bag_id == -4 && slot < NUM_SHARED_BANK_SLOTS)) { + if (bag_id == -4 && item->CheckFlag(NO_TRADE)) { + PacketStruct* packet = configReader.getStruct("WS_DisplayText", version); + if (packet) { + packet->setDataByName("color", CHANNEL_COLOR_YELLOW); + packet->setMediumStringByName("text", "Unable to unequip item: that item cannot be traded."); + packet->setDataByName("unknown02", 0x00ff); + packets.push_back(packet->serialize()); + safe_delete(packet); + } + } + else { + // need to check if appearance slot vs equipped + SetEquipment(0, item->details.equip_slot_id ? item->details.equip_slot_id : item->details.slot_id); + if(item->details.appearance_type) + database.DeleteItem(GetCharacterID(), item, "APPEARANCE"); + else + database.DeleteItem(GetCharacterID(), item, "EQUIPPED"); + + if (item->GetItemScript() && lua_interface) + lua_interface->RunItemScript(item->GetItemScript(), "unequipped", item, this); + + if(item->IsBag() && (item->details.inv_slot_id != bag_id || item->details.slot_id != slot)) { + item_list.EraseItem(item); + } + equipList->RemoveItem(index); + item->details.inv_slot_id = bag_id; + item->details.slot_id = slot; + item->details.appearance_type = 0; + item->details.equip_slot_id = 0; + SerializeItemPackets(equipList, &packets, item, version); + } + } + else { + Item* bag = item_list.GetItemFromUniqueID(bag_id, true); + if (bag && bag->IsBag() && slot < bag->details.num_slots) { + SetEquipment(0, item->details.equip_slot_id ? item->details.equip_slot_id : item->details.slot_id); + if(item->details.appearance_type) + database.DeleteItem(GetCharacterID(), item, "APPEARANCE"); + else + database.DeleteItem(GetCharacterID(), item, "EQUIPPED"); + + if (item->GetItemScript() && lua_interface) + lua_interface->RunItemScript(item->GetItemScript(), "unequipped", item, this); + + if(item->IsBag() && ( item->details.inv_slot_id != bag_id || item->details.slot_id != slot)) { + item_list.EraseItem(item); + } + equipList->RemoveItem(index); + item->details.inv_slot_id = bag_id; + item->details.slot_id = slot; + item->details.appearance_type = 0; + item->details.equip_slot_id = 0; + SerializeItemPackets(equipList, &packets, item, version); + } + } + } + Item* bag = item_list.GetItemFromUniqueID(bag_id, true); + if (bag && bag->IsBag()) + packets.push_back(bag->serialize(version, false, this)); + } + + if(send_item_updates && GetClient()) + { + GetClient()->UpdateSentSpellList(); + GetClient()->ClearSentSpellList(); + } + + return packets; +} + +map* Player::GetItemList(){ + return item_list.GetAllItems(); +} + +vector* Player::GetEquippedItemList(){ + return equipment_list.GetAllEquippedItems(); +} + +vector* Player::GetAppearanceEquippedItemList(){ + return appearance_equipment_list.GetAllEquippedItems(); +} + +EQ2Packet* Player::SendBagUpdate(int32 bag_unique_id, int16 version){ + Item* bag = 0; + if(bag_unique_id > 0) + bag = item_list.GetItemFromUniqueID(bag_unique_id, true); + + if(bag && bag->IsBag()) + return bag->serialize(version, false, this); + return 0; +} + +void Player::SetEquippedItemAppearances(){ + vector* items = GetEquipmentList()->GetAllEquippedItems(); + vector* appearance_items = GetAppearanceEquipmentList()->GetAllEquippedItems(); + if(items){ + for(int32 i=0;isize();i++) + SetEquipment(items->at(i)); + + // just have appearance items brute force replace the slots after the fact + for(int32 i=0;isize();i++) + SetEquipment(appearance_items->at(i)); + } + safe_delete(items); + safe_delete(appearance_items); + info_changed = true; + GetZone()->SendSpawnChanges(this); +} + +EQ2Packet* Player::SwapEquippedItems(int8 slot1, int8 slot2, int16 version, int16 equip_type){ + EquipmentItemList* equipList = &equipment_list; + + // right now client seems to pass 3 for this? Not sure why when other fields has appearance equipment as type 1 + if(equip_type == 3) + equipList = &appearance_equipment_list; + + equipList->MEquipmentItems.readlock(__FUNCTION__, __LINE__); + Item* item_from = equipList->items[slot1]; + Item* item_to = equipList->items[slot2]; + equipList->MEquipmentItems.releasereadlock(__FUNCTION__, __LINE__); + + if(item_from && equipList->CanItemBeEquippedInSlot(item_from, slot2)){ + if(item_to){ + if(!equipList->CanItemBeEquippedInSlot(item_to, slot1)) + return 0; + } + equipList->MEquipmentItems.writelock(__FUNCTION__, __LINE__); + equipList->items[slot1] = nullptr; + equipList->MEquipmentItems.releasewritelock(__FUNCTION__, __LINE__); + equipList->SetItem(slot2, item_from); + if(item_to) + { + equipList->SetItem(slot1, item_to); + item_to->save_needed = true; + } + item_from->save_needed = true; + + if (GetClient()) + { + //EquipmentItemList* equipList = &equipment_list; + + //if(appearance_type) + // equipList = &appearance_equipment_list; + + if(item_to) + GetClient()->QueuePacket(item_to->serialize(version, false, this)); + GetClient()->QueuePacket(item_from->serialize(version, false, this)); + GetClient()->QueuePacket(item_list.serialize(this, version)); + } + return equipList->serialize(version, this); + } + return 0; +} +bool Player::CanEquipItem(Item* item, int8 slot) { + if(client && client->GetVersion() <= 561 && slot == EQ2_EARS_SLOT_2) + return false; + + if (item) { + Client* client = GetClient(); + if (client) { + if (item->IsWeapon() && slot == 1) { + bool dwable = item->IsDualWieldAble(client, item, slot); + + if (dwable == 0) { + return false; + } + } + + if (item->CheckFlag(EVIL_ONLY) && GetAlignment() != ALIGNMENT_EVIL) { + client->Message(0, "%s requires an evil race.", item->name.c_str()); + } + else if (item->CheckFlag(GOOD_ONLY) && GetAlignment() != ALIGNMENT_GOOD) { + client->Message(0, "%s requires a good race.", item->name.c_str()); + } + else if (item->IsArmor() || item->IsWeapon() || item->IsFood() || item->IsRanged() || item->IsShield() || item->IsBauble() || item->IsAmmo() || item->IsThrown()) { + if (((item->generic_info.skill_req1 == 0 || item->generic_info.skill_req1 == 0xFFFFFFFF || skill_list.HasSkill(item->generic_info.skill_req1)) && (item->generic_info.skill_req2 == 0 || item->generic_info.skill_req2 == 0xFFFFFFFF || skill_list.HasSkill(item->generic_info.skill_req2)))) { + int16 override_level = item->GetOverrideLevel(GetAdventureClass(), GetTradeskillClass()); + if (override_level > 0 && override_level <= GetLevel()) + return true; + if (item->CheckClass(GetAdventureClass(), GetTradeskillClass())) + if (item->CheckLevel(GetAdventureClass(), GetTradeskillClass(), GetLevel())) + return true; + else + client->Message(CHANNEL_COLOR_RED, "You must be at least level %u to equip %s.", item->generic_info.adventure_default_level, item->CreateItemLink(client->GetVersion()).c_str()); + else + client->Message(CHANNEL_COLOR_RED, "Your class may not equip %s.", item->CreateItemLink(client->GetVersion()).c_str()); + } + else { + Skill* firstSkill = master_skill_list.GetSkill(item->generic_info.skill_req1); + Skill* secondSkill = master_skill_list.GetSkill(item->generic_info.skill_req2); + std::string msg(""); + if(GetClient()->GetAdminStatus() >= 200) { + if(firstSkill && !skill_list.HasSkill(item->generic_info.skill_req1)) { + msg += "(" + std::string(firstSkill->name.data.c_str()); + } + + if(secondSkill && !skill_list.HasSkill(item->generic_info.skill_req2)) { + if(msg.length() > 0) { + msg += ", "; + } + else { + msg = "("; + } + msg += std::string(secondSkill->name.data.c_str()); + } + + if(msg.length() > 0) { + msg += ") "; + } + } + client->Message(0, "You lack the skill %srequired to equip this item.",msg.c_str()); + } + } + else + client->Message(0, "Item %s isn't equipable.", item->name.c_str()); + } + } + return false; +} + +vector Player::EquipItem(int16 index, int16 version, int8 appearance_type, int8 slot_id) { + + EquipmentItemList* equipList = &equipment_list; + if(appearance_type) + equipList = &appearance_equipment_list; + + vector packets; + item_list.MPlayerItems.readlock(__FUNCTION__, __LINE__); + if (item_list.indexed_items.count(index) == 0) { + item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return packets; + } + Item* item = item_list.indexed_items[index]; + int8 orig_slot_id = slot_id; + int8 slot = 255; + if (item) { + if(orig_slot_id == 255 && item->CheckFlag2(APPEARANCE_ONLY)) { + appearance_type = 1; + equipList = &appearance_equipment_list; + } + if (slot_id != 255 && !item->HasSlot(slot_id)) { + item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return packets; + } + slot = equipList->GetFreeSlot(item, slot_id, version); + + bool canEquip = CanEquipItem(item,slot); + int32 conflictSlot = 0; + + if(canEquip && !appearance_type && item->CheckFlag2(APPEARANCE_ONLY)) + { + item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + if(GetClient()) { + GetClient()->SimpleMessage(CHANNEL_COLOR_RED, "This item is for appearance slots only."); + } + return packets; + } + else if(canEquip && (conflictSlot = equipList->CheckSlotConflict(item)) > 0) { + bool abort = true; + switch(conflictSlot) { + case LORE: + if(GetClient()) + GetClient()->SimpleMessage(CHANNEL_COLOR_RED, "Lore conflict, cannot equip this item."); + break; + case LORE_EQUIP: + if(GetClient()) + GetClient()->SimpleMessage(CHANNEL_COLOR_RED, "You already have this item equipped, you cannot equip another."); + break; + case STACK_LORE: + if(GetClient()) + GetClient()->SimpleMessage(CHANNEL_COLOR_RED, "Cannot equip as it exceeds lore stack."); + break; + default: + abort = false; + break; + } + if(abort) { + item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return packets; + } + } + else if (canEquip && item->CheckFlag(ATTUNEABLE)) { + PacketStruct* packet = configReader.getStruct("WS_ChoiceWindow", version); + char text[255]; + sprintf(text, "%s must be attuned before it can be equipped. Would you like to attune it now?", item->name.c_str()); + char accept_command[25]; + sprintf(accept_command, "attune_inv %i 1 0 -1", index); + packet->setDataByName("text", text); + packet->setDataByName("accept_text", "Attune"); + packet->setDataByName("accept_command", accept_command); + packet->setDataByName("cancel_text", "Cancel"); + // No clue if we even need the following 2 unknowns, just added them so the packet matches what live sends + packet->setDataByName("max_length", 50); + packet->setDataByName("unknown4", 1); + packets.push_back(packet->serialize()); + safe_delete(packet); + item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return packets; + } + if (canEquip && slot == 255) + { + if (slot_id == 255) { + if(item->slot_data.size() > 0) { + slot = item->slot_data.at(0); + if(!IsAllowedCombatEquip(slot, true)) { + LogWrite(PLAYER__ERROR, 0, "Player", "Attempt to equip item %s (%u) with FAILED in combat!", item->name.c_str(), item->details.item_id); + item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return packets; + } + } + else { + LogWrite(PLAYER__ERROR, 0, "Player", "Attempt to equip item %s (%u) with auto equip FAILED, no slot_data exists! Check items table, 'slots' column value should not be 0.", item->name.c_str(), item->details.item_id); + item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return packets; + } + } + else + slot = slot_id; + item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + packets = UnequipItem(slot, item->details.inv_slot_id, item->details.slot_id, version, appearance_type, false); + // grab player items lock again and assure item still present + item_list.MPlayerItems.readlock(__FUNCTION__, __LINE__); + if (item_list.indexed_items.count(index) == 0) { + item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return packets; + } + // If item is a 2handed weapon and something is in the secondary, unequip the secondary + if (item->IsWeapon() && item->weapon_info->wield_type == ITEM_WIELD_TYPE_TWO_HAND && equipList->GetItem(EQ2_SECONDARY_SLOT) != 0) { + item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + vector tmp_packets = UnequipItem(EQ2_SECONDARY_SLOT, -999, 0, version, appearance_type, false); + //packets.reserve(packets.size() + tmp_packets.size()); + packets.insert(packets.end(), tmp_packets.begin(), tmp_packets.end()); + } + else { + // release for delete item / scripting etc + item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + } + } + else if (canEquip && slot < 255) { + + if(!IsAllowedCombatEquip(slot, true)) { + LogWrite(PLAYER__ERROR, 0, "Player", "Attempt to equip item %s (%u) with auto equip FAILED in combat!", item->name.c_str(), item->details.item_id); + item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return packets; + } + // If item is a 2handed weapon and something is in the secondary, unequip the secondary + if (item->IsWeapon() && item->weapon_info->wield_type == ITEM_WIELD_TYPE_TWO_HAND && equipList->GetItem(EQ2_SECONDARY_SLOT) != 0) { + item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + vector tmp_packets = UnequipItem(EQ2_SECONDARY_SLOT, -999, 0, version, appearance_type, false); + //packets.reserve(packets.size() + tmp_packets.size()); + packets.insert(packets.end(), tmp_packets.begin(), tmp_packets.end()); + } + else { + // release for delete item / scripting etc + item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + } + + database.DeleteItem(GetCharacterID(), item, "NOT-EQUIPPED"); + + if (item->GetItemScript() && lua_interface) + lua_interface->RunItemScript(item->GetItemScript(), "equipped", item, this); + + if(!item->IsBag()) { + item_list.RemoveItem(item); + } + equipList->SetItem(slot, item); + item->save_needed = true; + packets.push_back(item->serialize(version, false)); + SetEquipment(item); + const char* zone_script = world.GetZoneScript(GetZone()->GetZoneID()); + if (zone_script && lua_interface) + lua_interface->RunZoneScript(zone_script, "item_equipped", GetZone(), this, item->details.item_id, item->name.c_str(), 0, item->details.unique_id); + int32 bag_id = item->details.inv_slot_id; + if (item->generic_info.condition == 0) { + Client* client = GetClient(); + if (client) { + string popup_text = "Your "; + string popup_item = item->CreateItemLink(client->GetVersion(), true).c_str(); + string popup_textcont = " is worn out and will not be effective until repaired."; + popup_text.append(popup_item); + popup_text.append(popup_textcont); + //devn00b: decided to use "crimson" for the color. (220,20,60 rgb) + client->SendPopupMessage(10, popup_text.c_str(), "", 5, 0xDC, 0x14, 0x3C); + client->Message(CHANNEL_COLOR_RED, "Your %s is worn out and will not be effective until repaired.", item->CreateItemLink(client->GetVersion(), true).c_str()); + } + } + SetEquippedItemAppearances(); + packets.push_back(equipList->serialize(version, this)); + EQ2Packet* outapp = item_list.serialize(this, version); + if (outapp) { + packets.push_back(outapp); + EQ2Packet* bag_packet = SendBagUpdate(bag_id, version); + if (bag_packet) + packets.push_back(bag_packet); + } + SetCharSheetChanged(true); + } + else { + // clear items lock + item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + } + } + else { + // clear items lock + item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + } + + if(slot < 255) { + if (slot == EQ2_FOOD_SLOT && item->IsFoodFood() && get_character_flag(CF_FOOD_AUTO_CONSUME)) { + Item* item = GetEquipmentList()->GetItem(EQ2_FOOD_SLOT); + if(item && GetClient() && GetClient()->CheckConsumptionAllowed(slot, false)) + GetClient()->ConsumeFoodDrink(item, EQ2_FOOD_SLOT); + + if(item) + SetActiveFoodUniqueID(item->details.unique_id); + } + else if (slot == EQ2_DRINK_SLOT && item->IsFoodDrink() && get_character_flag(CF_DRINK_AUTO_CONSUME)) { + Item* item = GetEquipmentList()->GetItem(EQ2_DRINK_SLOT); + if(item && GetClient() && GetClient()->CheckConsumptionAllowed(slot, false)) + GetClient()->ConsumeFoodDrink(item, EQ2_DRINK_SLOT); + + if(item) + SetActiveDrinkUniqueID(item->details.unique_id); + } + } + + client->UpdateSentSpellList(); + client->ClearSentSpellList(); + + return packets; +} +bool Player::AddItem(Item* item, AddItemType type) { + int32 conflictItemList = 0, conflictequipmentList = 0, conflictAppearanceEquipmentList = 0; + int16 lore_stack_count = 0; + if (item && item->details.item_id > 0) { + if( ((conflictItemList = item_list.CheckSlotConflict(item, true, true, &lore_stack_count)) == LORE || + (conflictequipmentList = equipment_list.CheckSlotConflict(item, true, &lore_stack_count)) == LORE || + (conflictAppearanceEquipmentList = appearance_equipment_list.CheckSlotConflict(item, true, &lore_stack_count)) == LORE) && !item->CheckFlag(STACK_LORE)) { + + switch(type) + { + case AddItemType::BUY_FROM_BROKER: + client->Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "You already own this item and cannot have another."); + break; + default: + client->Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "You cannot obtain %s due to lore conflict.", item->name.c_str()); + break; + } + safe_delete(item); + return false; + } + else if(conflictItemList == STACK_LORE || conflictequipmentList == STACK_LORE || + conflictAppearanceEquipmentList == STACK_LORE) { + switch(type) + { + default: + client->Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "You already have one stack of the LORE item: %s.", item->name.c_str()); + break; + } + safe_delete(item); + return false; + } + else if (item_list.AssignItemToFreeSlot(item)) { + item->save_needed = true; + CalculateApplyWeight(); + return true; + } + else if (item_list.AddOverflowItem(item)) { + CalculateApplyWeight(); + return true; + } + } + return false; +} +bool Player::AddItemToBank(Item* item) { + + if (item && item->details.item_id > 0) { + + sint32 bag = -3; + sint16 slot = -1; + if (item_list.GetFirstFreeBankSlot(&bag, &slot)) { + item->details.inv_slot_id = bag; + item->details.slot_id = slot; + item->save_needed = true; + + return item_list.AddItem(item); + } + else if (item_list.AddOverflowItem(item)) + return true; + } + return false; +} +EQ2Packet* Player::SendInventoryUpdate(int16 version) { + // assure any inventory updates are reflected in sell window + if(GetClient() && GetClient()->GetMerchantTransaction()) + GetClient()->SendSellMerchantList(); + + return item_list.serialize(this, version); +} + +void Player::UpdateInventory(int32 bag_id) { + + EQ2Packet* outapp = client->GetPlayer()->SendInventoryUpdate(client->GetVersion()); + client->QueuePacket(outapp); + + outapp = client->GetPlayer()->SendBagUpdate(bag_id, client->GetVersion()); + + if (outapp) + client->QueuePacket(outapp); + +} +EQ2Packet* Player::MoveInventoryItem(sint32 to_bag_id, int16 from_index, int8 new_slot, int8 charges, int8 appearance_type, bool* item_deleted, int16 version) { + Item* item = item_list.GetItemFromIndex(from_index); + int8 result = item_list.MoveItem(to_bag_id, from_index, new_slot, appearance_type, charges); + if (result == 1) { + if (item) { + if (!item->needs_deletion) + item->save_needed = true; + else if (item->needs_deletion) { + database.DeleteItem(GetCharacterID(), item, 0); + client->GetPlayer()->item_list.DestroyItem(from_index); + client->GetPlayer()->UpdateInventory(to_bag_id); + if(item_deleted) + *item_deleted = true; + } + } + return item_list.serialize(this, version); + } + else { + PacketStruct* packet = configReader.getStruct("WS_DisplayText", version); + if (packet) { + packet->setDataByName("color", CHANNEL_COLOR_YELLOW); + packet->setMediumStringByName("text", "Could not move item to that location."); + packet->setDataByName("unknown02", 0x00ff); + EQ2Packet* outapp = packet->serialize(); + safe_delete(packet); + return outapp; + } + } + return 0; +} + +int32 Player::GetCoinsCopper(){ + return GetInfoStruct()->get_coin_copper(); +} + +int32 Player::GetCoinsSilver(){ + return GetInfoStruct()->get_coin_silver(); +} + +int32 Player::GetCoinsGold(){ + return GetInfoStruct()->get_coin_gold(); +} + +int32 Player::GetCoinsPlat(){ + return GetInfoStruct()->get_coin_plat(); +} + +int32 Player::GetBankCoinsCopper(){ + return GetInfoStruct()->get_bank_coin_copper(); +} + +int32 Player::GetBankCoinsSilver(){ + return GetInfoStruct()->get_bank_coin_silver(); +} + +int32 Player::GetBankCoinsGold(){ + return GetInfoStruct()->get_bank_coin_gold(); +} + +int32 Player::GetBankCoinsPlat(){ + return GetInfoStruct()->get_bank_coin_plat(); +} + +int32 Player::GetStatusPoints(){ + return GetInfoStruct()->get_status_points(); +} + +vector* Player::GetQuickbar(){ + return &quickbar_items; +} + +bool Player::UpdateQuickbarNeeded(){ + return quickbar_updated; +} + +void Player::ResetQuickbarNeeded(){ + quickbar_updated = false; +} + +void Player::AddQuickbarItem(int32 bar, int32 slot, int32 type, int16 icon, int16 icon_type, int32 id, int8 tier, int32 unique_id, const char* text, bool update){ + RemoveQuickbarItem(bar, slot, false); + QuickBarItem* ability = new QuickBarItem; + ability->deleted = false; + ability->hotbar = bar; + ability->slot = slot; + ability->type = type; + ability->icon = icon; + ability->tier = tier; + ability->icon_type = icon_type; + ability->id = id; + if(unique_id == 0) + unique_id = database.NextUniqueHotbarID(); + ability->unique_id = unique_id; + if(type == QUICKBAR_TEXT_CMD && text){ + ability->text.data = string(text); + ability->text.size = ability->text.data.length(); + } + else + ability->text.size = 0; + quickbar_items.push_back(ability); + if(update) + quickbar_updated = true; +} + +void Player::RemoveQuickbarItem(int32 bar, int32 slot, bool update){ + vector::iterator itr; + QuickBarItem* qbi = 0; + for(itr=quickbar_items.begin();itr!=quickbar_items.end();itr++){ + qbi = *itr; + if(qbi && qbi->deleted == false && qbi->hotbar == bar && qbi->slot == slot){ + qbi->deleted = true; + break; + } + } + if(update) + quickbar_updated = true; +} + +void Player::ClearQuickbarItems(){ + quickbar_items.clear(); +} + +EQ2Packet* Player::GetQuickbarPacket(int16 version){ + PacketStruct* packet = configReader.getStruct("WS_QuickBarInit", version); + if(packet){ + vector::iterator itr; + packet->setArrayLengthByName("num_abilities", quickbar_items.size()); + int16 i=0; + for(itr=quickbar_items.begin();itr != quickbar_items.end(); itr++){ + QuickBarItem* ability = *itr; + if(!ability || ability->deleted) + continue; + packet->setArrayDataByName("hotbar", ability->hotbar, i); + packet->setArrayDataByName("slot", ability->slot, i); + packet->setArrayDataByName("type", ability->type, i); + packet->setArrayDataByName("icon", ability->icon, i); + packet->setArrayDataByName("icon_type", ability->icon_type, i); + packet->setArrayDataByName("id", ability->id, i); + packet->setArrayDataByName("unique_id", ability->tier, i); + packet->setArrayDataByName("text", &ability->text, i); + i++; + } + EQ2Packet* app = packet->serialize(); + safe_delete(packet); + return app; + } + return 0; +} + +void Player::AddSpellBookEntry(int32 spell_id, int8 tier, sint32 slot, int32 type, int32 timer, bool save_needed){ + SpellBookEntry* spell = new SpellBookEntry; + spell->status = 169; + spell->slot = slot; + spell->spell_id = spell_id; + spell->type = type; + spell->tier = tier; + spell->timer = timer; + spell->save_needed = save_needed; + spell->recast = 0; + spell->recast_available = 0; + spell->player = this; + spell->visible = true; + spell->in_use = false; + spell->in_remiss = false; + MSpellsBook.lock(); + spells.push_back(spell); + MSpellsBook.unlock(); + + if (type == SPELL_BOOK_TYPE_NOT_SHOWN) + AddPassiveSpell(spell_id, tier); +} + +void Player::DeleteSpellBook(int8 type_selection){ + MSpellsBook.lock(); + vector::iterator itr; + SpellBookEntry* spell = 0; + for(itr = spells.begin(); itr != spells.end();){ + spell = *itr; + if((type_selection & DELETE_TRADESKILLS) == 0 && spell->type == SPELL_BOOK_TYPE_TRADESKILL) { + itr++; + continue; + } + else if((type_selection & DELETE_SPELLS) == 0 && spell->type == SPELL_BOOK_TYPE_SPELL) { + itr++; + continue; + } + else if((type_selection & DELETE_COMBAT_ART) == 0 && spell->type == SPELL_BOOK_TYPE_COMBAT_ART) { + itr++; + continue; + } + else if((type_selection & DELETE_ABILITY) == 0 && spell->type == SPELL_BOOK_TYPE_ABILITY) { + itr++; + continue; + } + else if((type_selection & DELETE_NOT_SHOWN) == 0 && spell->type == SPELL_BOOK_TYPE_NOT_SHOWN) { + itr++; + continue; + } + database.DeleteCharacterSpell(GetCharacterID(), spell->spell_id); + if (spell->type == SPELL_BOOK_TYPE_NOT_SHOWN) + RemovePassive(spell->spell_id, spell->tier, true); + itr = spells.erase(itr); + } + MSpellsBook.unlock(); +} + +void Player::RemoveSpellBookEntry(int32 spell_id, bool remove_passives_from_list){ + MSpellsBook.lock(); + vector::iterator itr; + SpellBookEntry* spell = 0; + for(itr = spells.begin(); itr != spells.end(); itr++){ + spell = *itr; + if(spell->spell_id == spell_id){ + if (spell->type == SPELL_BOOK_TYPE_NOT_SHOWN) + RemovePassive(spell->spell_id, spell->tier, remove_passives_from_list); + spells.erase(itr); + break; + } + } + MSpellsBook.unlock(); +} + +void Player::ResortSpellBook(int32 sort_by, int32 order, int32 pattern, int32 maxlvl_only, int32 book_type) +{ + //sort_by : 0 - alpha, 1 - level, 2 - category + //order : 0 - ascending, 1 - descending + //pattern : 0 - zigzag, 1 - down, 2 - across + MSpellsBook.lock(); + + std::vector sort_spells(spells); + + if (!maxlvl_only) + { + switch (sort_by) + { + case 0: + if (!order) + stable_sort(sort_spells.begin(), sort_spells.end(), SortSpellEntryByName); + else + stable_sort(sort_spells.begin(), sort_spells.end(), SortSpellEntryByNameReverse); + break; + case 1: + if (!order) + stable_sort(sort_spells.begin(), sort_spells.end(), SortSpellEntryByLevel); + else + stable_sort(sort_spells.begin(), sort_spells.end(), SortSpellEntryByLevelReverse); + break; + case 2: + if (!order) + stable_sort(sort_spells.begin(), sort_spells.end(), SortSpellEntryByCategory); + else + stable_sort(sort_spells.begin(), sort_spells.end(), SortSpellEntryByCategoryReverse); + break; + } + } + + vector::iterator itr; + SpellBookEntry* spell = 0; + map tmpSpells; + vector resultSpells; + + int32 i = 0; + int8 page_book_count = 0; + int32 last_start_point = 0; + + for (itr = sort_spells.begin(); itr != sort_spells.end(); itr++) { + spell = *itr; + + if (spell->type != book_type) + continue; + + if (maxlvl_only) + { + Spell* actual_spell = 0; + actual_spell = master_spell_list.GetSpell(spell->spell_id, spell->tier); + if(!actual_spell) { + // we have a spell that doesn't exist here! + continue; + } + std::regex re("^(.*?)(\\s(I{1,}[VX]{0,}|V{1,}[IVX]{0,})|X{1,}[IVX]{0,})$"); + std::string output = std::regex_replace(string(actual_spell->GetName()), re, "$1", std::regex_constants::format_no_copy); + + if ( output.size() < 1 ) + output = string(actual_spell->GetName()); + + map::iterator tmpItr = tmpSpells.find(output); + if (tmpItr != tmpSpells.end()) + { + Spell* tmpSpell = master_spell_list.GetSpell(tmpItr->second->spell_id, tmpItr->second->tier); + if (actual_spell->GetLevelRequired(this) > tmpSpell->GetLevelRequired(this)) + { + tmpItr->second->visible = false; + tmpItr->second->slot = 0xFFFF; + + std::vector::iterator it; + it = find(resultSpells.begin(), resultSpells.end(), (SpellBookEntry*)tmpItr->second); + if (it != resultSpells.end()) + resultSpells.erase(it); + + tmpSpells.erase(tmpItr); + } + else + continue; // leave as-is we have the newer spell + } + + spell->visible = true; + tmpSpells.insert(make_pair(output, spell)); + resultSpells.push_back(spell); + } + spell->slot = i; + + GetSpellBookSlotSort(pattern, &i, &page_book_count, &last_start_point); + } // end for loop for setting slots + + if (maxlvl_only) + { + switch (sort_by) + { + case 0: + if (!order) + stable_sort(resultSpells.begin(), resultSpells.end(), SortSpellEntryByName); + else + stable_sort(resultSpells.begin(), resultSpells.end(), SortSpellEntryByNameReverse); + break; + case 1: + if (!order) + stable_sort(resultSpells.begin(), resultSpells.end(), SortSpellEntryByLevel); + else + stable_sort(resultSpells.begin(), resultSpells.end(), SortSpellEntryByLevelReverse); + break; + case 2: + if (!order) + stable_sort(resultSpells.begin(), resultSpells.end(), SortSpellEntryByCategory); + else + stable_sort(resultSpells.begin(), resultSpells.end(), SortSpellEntryByCategoryReverse); + break; + } + + i = 0; + page_book_count = 0; + last_start_point = 0; + vector::iterator tmpItr; + for (tmpItr = resultSpells.begin(); tmpItr != resultSpells.end(); tmpItr++) { + ((SpellBookEntry*)*tmpItr)->slot = i; + GetSpellBookSlotSort(pattern, &i, &page_book_count, &last_start_point); + } + } + + MSpellsBook.unlock(); +} + +bool Player::SortSpellEntryByName(SpellBookEntry* s1, SpellBookEntry* s2) +{ + Spell* spell1 = master_spell_list.GetSpell(s1->spell_id, s1->tier); + Spell* spell2 = master_spell_list.GetSpell(s2->spell_id, s2->tier); + + if (!spell1 || !spell2) + return false; + + return (string(spell1->GetName()) < string(spell2->GetName())); +} + +bool Player::SortSpellEntryByCategory(SpellBookEntry* s1, SpellBookEntry* s2) +{ + Spell* spell1 = master_spell_list.GetSpell(s1->spell_id, s1->tier); + Spell* spell2 = master_spell_list.GetSpell(s2->spell_id, s2->tier); + + if (!spell1 || !spell2) + return false; + + return (spell1->GetSpellIconBackdrop() < spell2->GetSpellIconBackdrop()); +} + +bool Player::SortSpellEntryByLevel(SpellBookEntry* s1, SpellBookEntry* s2) +{ + Spell* spell1 = master_spell_list.GetSpell(s1->spell_id, s1->tier); + Spell* spell2 = master_spell_list.GetSpell(s2->spell_id, s2->tier); + + if (!spell1 || !spell2) + return false; + + int16 lvl1 = spell1->GetLevelRequired(s1->player); + int16 lvl2 = spell2->GetLevelRequired(s2->player); + if (lvl1 == 0xFFFF) + lvl1 = 0; + if (lvl2 == 0xFFFF) + lvl2 = 0; + + return (lvl1 < lvl2); +} + +bool Player::SortSpellEntryByNameReverse(SpellBookEntry* s1, SpellBookEntry* s2) +{ + Spell* spell1 = master_spell_list.GetSpell(s1->spell_id, s1->tier); + Spell* spell2 = master_spell_list.GetSpell(s2->spell_id, s2->tier); + + if (!spell1 || !spell2) + return false; + + return (string(spell2->GetName()) < string(spell1->GetName())); +} + +bool Player::SortSpellEntryByCategoryReverse(SpellBookEntry* s1, SpellBookEntry* s2) +{ + Spell* spell1 = master_spell_list.GetSpell(s1->spell_id, s1->tier); + Spell* spell2 = master_spell_list.GetSpell(s2->spell_id, s2->tier); + if (!spell1 || !spell2) + return false; + return (spell2->GetSpellIconBackdrop() < spell1->GetSpellIconBackdrop()); +} + +bool Player::SortSpellEntryByLevelReverse(SpellBookEntry* s1, SpellBookEntry* s2) +{ + Spell* spell1 = master_spell_list.GetSpell(s1->spell_id, s1->tier); + Spell* spell2 = master_spell_list.GetSpell(s2->spell_id, s2->tier); + + if (!spell1 || !spell2) + return false; + + int16 lvl1 = spell1->GetLevelRequired(s1->player); + int16 lvl2 = spell2->GetLevelRequired(s2->player); + if (lvl1 == 0xFFFF) + lvl1 = 0; + if (lvl2 == 0xFFFF) + lvl2 = 0; + + return (lvl2 < lvl1); +} + +int8 Player::GetSpellSlot(int32 spell_id){ + MSpellsBook.lock(); + vector::iterator itr; + SpellBookEntry* spell = 0; + for(itr = spells.begin(); itr != spells.end(); itr++){ + spell = *itr; + if(spell->spell_id == spell_id) + { + int8 slot = spell->slot; + MSpellsBook.unlock(); + return slot; + } + } + MSpellsBook.unlock(); + return 0; +} + +void Player::AddSkill(int32 skill_id, int16 current_val, int16 max_val, bool save_needed){ + Skill* master_skill = master_skill_list.GetSkill(skill_id); + if (master_skill) { + Skill* skill = new Skill(master_skill); + skill->current_val = current_val; + skill->previous_val = current_val; + skill->max_val = max_val; + if (save_needed) + skill->save_needed = true; + skill_list.AddSkill(skill); + } +} + +void Player::RemovePlayerSkill(int32 skill_id, bool save) { + Skill* skill = skill_list.GetSkill(skill_id); + if (skill) + RemoveSkillFromDB(skill, save); +} + +void Player::RemoveSkillFromDB(Skill* skill, bool save) { + skill_list.RemoveSkill(skill); + if (save) + database.DeleteCharacterSkill(GetCharacterID(), skill); +} + +int16 Player::GetSpellSlotMappingCount(){ + int16 ret = 0; + MSpellsBook.lock(); + for(int32 i=0;islot >= 0 && spell->spell_id > 0 && spell->type != SPELL_BOOK_TYPE_NOT_SHOWN) + ret++; + } + MSpellsBook.unlock(); + return ret; +} + +int8 Player::GetSpellTier(int32 id){ + int8 ret = 0; + MSpellsBook.lock(); + for(int32 i=0;ispell_id == id){ + ret = spell->tier; + break; + } + } + MSpellsBook.unlock(); + return ret; +} + +int16 Player::GetSpellPacketCount(){ + int16 ret = 0; + MSpellsBook.lock(); + for(int32 i=0;ispell_id > 0 && spell->type != SPELL_BOOK_TYPE_NOT_SHOWN) + ret++; + } + MSpellsBook.unlock(); + return ret; +} + +void Player::LockAllSpells() { + vector::iterator itr; + + MSpellsBook.writelock(__FUNCTION__, __LINE__); + for (itr = spells.begin(); itr != spells.end(); itr++) { + if ((*itr)->type != SPELL_BOOK_TYPE_TRADESKILL) + RemoveSpellStatus((*itr), SPELL_STATUS_LOCK, false); + } + + all_spells_locked = true; + + MSpellsBook.releasewritelock(__FUNCTION__, __LINE__); +} + +void Player::UnlockAllSpells(bool modify_recast, Spell* exception) { + vector::iterator itr; + int32 exception_spell_id = 0; + if (exception) + exception_spell_id = exception->GetSpellID(); + MSpellsBook.writelock(__FUNCTION__, __LINE__); + for (itr = spells.begin(); itr != spells.end(); itr++) { + MaintainedEffects* effect = 0; + if((effect = GetMaintainedSpell((*itr)->spell_id)) && effect->spell->spell->GetSpellData()->duration_until_cancel) + continue; + + if ((*itr)->in_use == false && + (((*itr)->spell_id != exception_spell_id || + (*itr)->timer > 0 && (*itr)->timer != exception->GetSpellData()->linked_timer) + && (*itr)->type != SPELL_BOOK_TYPE_TRADESKILL)) { + AddSpellStatus((*itr), SPELL_STATUS_LOCK, modify_recast); + (*itr)->recast_available = 0; + } + else if((*itr)->in_remiss) + { + AddSpellStatus((*itr), SPELL_STATUS_LOCK); + (*itr)->recast_available = 0; + (*itr)->in_remiss = false; + } + } + + all_spells_locked = false; + + MSpellsBook.releasewritelock(__FUNCTION__, __LINE__); +} + +void Player::LockSpell(Spell* spell, int16 recast) { + vector::iterator itr; + SpellBookEntry* spell2; + + MSpellsBook.writelock(__FUNCTION__, __LINE__); + for (itr = spells.begin(); itr != spells.end(); itr++) { + spell2 = *itr; + if (spell2->spell_id == spell->GetSpellID() || (spell->GetSpellData()->linked_timer > 0 && spell->GetSpellData()->linked_timer == spell2->timer)) + { + spell2->in_use = true; + RemoveSpellStatus(spell2, SPELL_STATUS_LOCK, true, recast); + } + else if(spell2->in_use) + RemoveSpellStatus(spell2, SPELL_STATUS_LOCK, false, 0); + } + MSpellsBook.releasewritelock(__FUNCTION__, __LINE__); +} + +void Player::UnlockSpell(Spell* spell) { + if (spell->GetStayLocked()) + return; + vector::iterator itr; + SpellBookEntry* spell2; + MSpellsBook.writelock(__FUNCTION__, __LINE__); + for (itr = spells.begin(); itr != spells.end(); itr++) { + spell2 = *itr; + if (spell2->spell_id == spell->GetSpellID() || (spell->GetSpellData()->linked_timer > 0 && spell->GetSpellData()->linked_timer == spell2->timer)) + { + spell2->in_use = false; + spell2->recast_available = 0; + if(all_spells_locked) + spell2->in_remiss = true; + else + AddSpellStatus(spell2, SPELL_STATUS_LOCK, false); + } + } + MSpellsBook.releasewritelock(__FUNCTION__, __LINE__); +} + +void Player::LockTSSpells() { + vector::iterator itr; + + MSpellsBook.writelock(__FUNCTION__, __LINE__); + for (itr = spells.begin(); itr != spells.end(); itr++) { + if ((*itr)->type == SPELL_BOOK_TYPE_TRADESKILL) + RemoveSpellStatus(*itr, SPELL_STATUS_LOCK); + } + + MSpellsBook.releasewritelock(__FUNCTION__, __LINE__); + // Unlock all other types + UnlockAllSpells(); +} + +void Player::UnlockTSSpells() { + vector::iterator itr; + + MSpellsBook.writelock(__FUNCTION__, __LINE__); + for (itr = spells.begin(); itr != spells.end(); itr++) { + if ((*itr)->type == SPELL_BOOK_TYPE_TRADESKILL) + AddSpellStatus(*itr, SPELL_STATUS_LOCK); + } + + MSpellsBook.releasewritelock(__FUNCTION__, __LINE__); + // Lock all other types + LockAllSpells(); +} + +void Player::QueueSpell(Spell* spell) { + vector::iterator itr; + SpellBookEntry* spell2; + MSpellsBook.writelock(__FUNCTION__, __LINE__); + for (itr = spells.begin(); itr != spells.end(); itr++) { + spell2 = *itr; + if (spell2->spell_id == spell->GetSpellID()) + AddSpellStatus(spell2, SPELL_STATUS_QUEUE, false); + } + MSpellsBook.releasewritelock(__FUNCTION__, __LINE__); +} + +void Player::UnQueueSpell(Spell* spell) { + vector::iterator itr; + SpellBookEntry* spell2; + MSpellsBook.writelock(__FUNCTION__, __LINE__); + for (itr = spells.begin(); itr != spells.end(); itr++) { + spell2 = *itr; + if (spell2->spell_id == spell->GetSpellID()) + RemoveSpellStatus(spell2, SPELL_STATUS_QUEUE, false); + } + MSpellsBook.releasewritelock(__FUNCTION__, __LINE__); +} + +vector Player::GetSpellBookSpellsByTimer(Spell* spell, int32 timerID) { + vector ret; + vector::iterator itr; + MSpellsBook.readlock(__FUNCTION__, __LINE__); + for (itr = spells.begin(); itr != spells.end(); itr++) { + if ((*itr)->timer == timerID && spell->GetSpellID() != (*itr)->spell_id) + ret.push_back(master_spell_list.GetSpell((*itr)->spell_id, (*itr)->tier)); + } + MSpellsBook.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +void Player::ModifySpellStatus(SpellBookEntry* spell, sint16 value, bool modify_recast, int16 recast) { + SetSpellEntryRecast(spell, modify_recast, recast); + if (modify_recast || spell->recast_available <= Timer::GetCurrentTime2() || value == 4) { + spell->status += value; // use set/remove spell status now + } +} + +void Player::AddSpellStatus(SpellBookEntry* spell, sint16 value, bool modify_recast, int16 recast) { + SetSpellEntryRecast(spell, modify_recast, recast); + if (modify_recast || spell->recast_available <= Timer::GetCurrentTime2() || value == 4) { + spell->status = spell->status | value; + } +} + +void Player::RemoveSpellStatus(SpellBookEntry* spell, sint16 value, bool modify_recast, int16 recast) { + SetSpellEntryRecast(spell, modify_recast, recast); + if (modify_recast || spell->recast_available <= Timer::GetCurrentTime2() || value == 4) { + spell->status = spell->status & ~value; + } +} + +void Player::SetSpellStatus(Spell* spell, int8 status){ + MSpellsBook.lock(); + vector::iterator itr; + SpellBookEntry* spell2 = 0; + for(itr = spells.begin(); itr != spells.end(); itr++){ + spell2 = *itr; + if(spell2->spell_id == spell->GetSpellData()->id){ + spell2->status = spell2->status | status; + break; + } + } + MSpellsBook.unlock(); +} + +void Player::SetSpellEntryRecast(SpellBookEntry* spell, bool modify_recast, int16 recast) { + if (modify_recast) { + spell->recast = recast / 100; + Spell* spell_ = master_spell_list.GetSpell(spell->spell_id, spell->tier); + if(spell_) { + float override_recast = 0.0f; + if(recast > 0) { + override_recast = static_cast(recast); + } + int32 recast_time = spell_->CalculateRecastTimer(this, override_recast); + + spell->recast = recast_time / 100; + spell->recast_available = Timer::GetCurrentTime2() + recast_time; + } + else { + spell->recast_available = Timer::GetCurrentTime2() + recast; + } + } +} + +vector* Player::GetSpellsSaveNeeded(){ + vector* ret = 0; + vector::iterator itr; + MSpellsBook.lock(); + SpellBookEntry* spell = 0; + for(itr = spells.begin(); itr != spells.end(); itr++){ + spell = *itr; + if(spell->save_needed){ + if(!ret) + ret = new vector; + ret->push_back(spell); + } + } + MSpellsBook.unlock(); + return ret; +} + +int16 Player::GetTierUp(int16 tier) +{ + switch(tier) + { + case 0: + break; + case 7: + case 9: + tier -= 2; + break; + default: + tier -= 1; + break; + } + + return tier; +} +bool Player::HasSpell(int32 spell_id, int8 tier, bool include_higher_tiers, bool include_possible_scribe){ + bool ret = false; + vector::iterator itr; + MSpellsBook.lock(); + SpellBookEntry* spell = 0; + for(itr = spells.begin(); itr != spells.end(); itr++){ + spell = *itr; + if(spell->spell_id == spell_id && (tier == 255 || spell->tier == tier || (include_higher_tiers && spell->tier > tier) || (include_possible_scribe && tier <= spell->tier))){ + ret = true; + break; + } + } + MSpellsBook.unlock(); + return ret; +} + +sint32 Player::GetFreeSpellBookSlot(int32 type){ + sint32 ret = 0; + MSpellsBook.lock(); + vector::iterator itr; + SpellBookEntry* spell = 0; + for(itr = spells.begin(); itr != spells.end(); itr++){ + spell = *itr; + if(spell->type == type && spell->slot > ret) //get last slot (add 1 to it on return) + ret = spell->slot; + } + MSpellsBook.unlock(); + return ret+1; +} + +SpellBookEntry* Player::GetSpellBookSpell(int32 spell_id){ + MSpellsBook.lock(); + vector::iterator itr; + SpellBookEntry* ret = 0; + SpellBookEntry* spell = 0; + for(itr = spells.begin(); itr != spells.end(); itr++){ + spell = *itr; + if(spell->spell_id == spell_id){ + ret = spell; + break; + } + } + MSpellsBook.unlock(); + return ret; +} + +vector Player::GetSpellBookSpellIDBySkill(int32 skill_id) { + vector ret; + + MSpellsBook.readlock(__FUNCTION__, __LINE__); + vector::iterator itr; + Spell* spell = 0; + for(itr = spells.begin(); itr != spells.end(); itr++){ + spell = master_spell_list.GetSpell((*itr)->spell_id, (*itr)->tier); + if(spell && spell->GetSpellData()->mastery_skill == skill_id) + ret.push_back(spell->GetSpellData()->id); + } + MSpellsBook.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + + +EQ2Packet* Player::GetSpellSlotMappingPacket(int16 version){ + PacketStruct* packet = configReader.getStruct("WS_SpellSlotMapping", version); + if(packet){ + int16 count = GetSpellSlotMappingCount(); + int16 ptr = 0; + if(count > 0){ + packet->setArrayLengthByName("spell_count", count); + MSpellsBook.lock(); + for(int32 i=0;itype == SPELL_BOOK_TYPE_NOT_SHOWN || spell->slot < 0 || spell->spell_id == 0) + continue; + packet->setArrayDataByName("spell_id", spell->spell_id, ptr); + packet->setArrayDataByName("slot_id", (int16)spell->slot, ptr); + ptr++; + } + MSpellsBook.unlock(); + EQ2Packet* ret = packet->serialize(); + safe_delete(packet); + return ret; + } + safe_delete(packet); + } + return 0; +} + +EQ2Packet* Player::GetSpellBookUpdatePacket(int16 version) { + std::unique_lock lock(spell_packet_update_mutex); + PacketStruct* packet = configReader.getStruct("WS_UpdateSpellBook", version); + EQ2Packet* ret = 0; + if (packet) { + Spell* spell = 0; + SpellBookEntry* spell_entry = 0; + int16 count = GetSpellPacketCount(); + int16 ptr = 0; + // Get the packet size + PacketStruct* packet2 = configReader.getStruct("SubStruct_UpdateSpellBook", version); + int32 total_bytes = packet2->GetTotalPacketSize(); + safe_delete(packet2); + packet->setArrayLengthByName("spell_count", count); + + LogWrite(PLAYER__DEBUG, 5, "Player", "%s: GetSpellBookUpdatePacket Spell Count: %u, Spell Entry Book Size: %u", GetName(), count, total_bytes); + + if (count > 0) { + if (count > spell_count) { + uchar* tmp = 0; + if (spell_orig_packet) { + tmp = new uchar[count * total_bytes]; + memset(tmp, 0, total_bytes * count); + memcpy(tmp, spell_orig_packet, spell_count * total_bytes); + safe_delete_array(spell_orig_packet); + safe_delete_array(spell_xor_packet); + spell_orig_packet = tmp; + } + else { + spell_orig_packet = new uchar[count * total_bytes]; + memset(spell_orig_packet, 0, total_bytes * count); + } + spell_xor_packet = new uchar[count * total_bytes]; + memset(spell_xor_packet, 0, count * total_bytes); + } + spell_count = count; + MSpellsBook.lock(); + for (int32 i = 0; i < spells.size(); i++) { + spell_entry = (SpellBookEntry*)spells[i]; + if (spell_entry->spell_id == 0 || spell_entry->type == SPELL_BOOK_TYPE_NOT_SHOWN) + continue; + spell = master_spell_list.GetSpell(spell_entry->spell_id, spell_entry->tier); + if (spell) { + if (spell_entry->recast_available == 0 || Timer::GetCurrentTime2() > spell_entry->recast_available) { + packet->setSubstructArrayDataByName("spells", "available", 1, 0, ptr); + } + LogWrite(PLAYER__DEBUG, 9, "Player", "%s: GetSpellBookUpdatePacket Send Spell %u in position %u\n",GetName(), spell_entry->spell_id, ptr); + packet->setSubstructArrayDataByName("spells", "spell_id", spell_entry->spell_id, 0, ptr); + packet->setSubstructArrayDataByName("spells", "type", spell_entry->type, 0, ptr); + packet->setSubstructArrayDataByName("spells", "recast_available", spell_entry->recast_available, 0, ptr); + packet->setSubstructArrayDataByName("spells", "recast_time", spell_entry->recast, 0, ptr); + packet->setSubstructArrayDataByName("spells", "status", spell_entry->status, 0, ptr); + packet->setSubstructArrayDataByName("spells", "icon", (spell->TranslateClientSpellIcon(version) * -1) - 1, 0, ptr); + packet->setSubstructArrayDataByName("spells", "icon_type", spell->GetSpellIconBackdrop(), 0, ptr); + packet->setSubstructArrayDataByName("spells", "icon2", spell->GetSpellIconHeroicOp(), 0, ptr); + packet->setSubstructArrayDataByName("spells", "unique_id", (spell_entry->tier + 1) * -1, 0, ptr); //this is actually GetSpellNameCrc(spell->GetName()), but hijacking it for spell tier + packet->setSubstructArrayDataByName("spells", "charges", 255, 0, ptr); + // Beastlord and Channeler spell support + if (spell->GetSpellData()->savage_bar == 1) + packet->setSubstructArrayDataByName("spells", "unknown6", 32, 0, ptr); // advantages + else if (spell->GetSpellData()->savage_bar == 2) + packet->setSubstructArrayDataByName("spells", "unknown6", 64, 0, ptr); // primal + else if (spell->GetSpellData()->savage_bar == 3) { + packet->setSubstructArrayDataByName("spells", "unknown6", 6, 1, ptr); // 6 = channeler + // Slot req for channelers + // bitmask for slots 1 = slot 1, 2 = slot 2, 4 = slot 3, 8 = slot 4, 16 = slot 5, 32 = slot 6, 64 = slot 7, 128 = slot 8 + packet->setSubstructArrayDataByName("spells", "savage_bar_slot", spell->GetSpellData()->savage_bar_slot, 0, ptr); + } + + ptr++; + } + } + MSpellsBook.unlock(); + } + ret = packet->serializeCountPacket(version, 0, spell_orig_packet, spell_xor_packet); + //packet->PrintPacket(); + //DumpPacket(ret); + safe_delete(packet); + } + return ret; +} + +PlayerInfo::~PlayerInfo(){ + RemoveOldPackets(); +} + +PlayerInfo::PlayerInfo(Player* in_player){ + orig_packet = 0; + changes = 0; + pet_orig_packet = 0; + pet_changes = 0; + player = in_player; + info_struct = player->GetInfoStruct(); + info_struct->set_name(std::string(player->GetName())); + info_struct->set_deity(std::string("None")); + + info_struct->set_class1(classes.GetBaseClass(player->GetAdventureClass())); + info_struct->set_class2(classes.GetSecondaryBaseClass(player->GetAdventureClass())); + info_struct->set_class3(player->GetAdventureClass()); + + info_struct->set_race(player->GetRace()); + info_struct->set_gender(player->GetGender()); + info_struct->set_level(player->GetLevel()); + info_struct->set_tradeskill_level(player->GetTSLevel()); + info_struct->set_tradeskill_class1(classes.GetTSBaseClass(player->GetTradeskillClass())); + info_struct->set_tradeskill_class2(classes.GetSecondaryTSBaseClass(player->GetTradeskillClass())); + info_struct->set_tradeskill_class3(player->GetTradeskillClass()); + + for(int i=0;i<45;i++){ + if(i<30){ + info_struct->maintained_effects[i].spell_id = 0xFFFFFFFF; + info_struct->maintained_effects[i].icon = 0xFFFF; + info_struct->maintained_effects[i].spell = nullptr; + } + info_struct->spell_effects[i].spell_id = 0xFFFFFFFF; + info_struct->spell_effects[i].spell = nullptr; + } + + house_zone_id = 0; + bind_zone_id = 0; + bind_x = 0; + bind_y = 0; + bind_z = 0; + bind_heading = 0; + boat_x_offset = 0; + boat_y_offset = 0; + boat_z_offset = 0; + boat_spawn = 0; +} + +MaintainedEffects* Player::GetFreeMaintainedSpellSlot(){ + MaintainedEffects* ret = 0; + InfoStruct* info = GetInfoStruct(); + GetMaintainedMutex()->readlock(__FUNCTION__, __LINE__); + for(int i=0;imaintained_effects[i].spell_id == 0xFFFFFFFF){ + ret = &info->maintained_effects[i]; + ret->spell_id = 0; + ret->slot_pos = i; + break; + } + } + GetMaintainedMutex()->releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +MaintainedEffects* Player::GetMaintainedSpell(int32 id){ + MaintainedEffects* ret = 0; + InfoStruct* info = GetInfoStruct(); + GetMaintainedMutex()->readlock(__FUNCTION__, __LINE__); + for(int i=0;imaintained_effects[i].spell_id == id){ + ret = &info->maintained_effects[i]; + break; + } + } + GetMaintainedMutex()->releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +MaintainedEffects* Player::GetMaintainedSpellBySlot(int8 slot){ + MaintainedEffects* ret = 0; + InfoStruct* info = GetInfoStruct(); + GetMaintainedMutex()->readlock(__FUNCTION__, __LINE__); + for(int i=0;imaintained_effects[i].slot_pos == slot){ + ret = &info->maintained_effects[i]; + break; + } + } + GetMaintainedMutex()->releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +MaintainedEffects* Player::GetMaintainedSpells() { + return GetInfoStruct()->maintained_effects; +} + +SpellEffects* Player::GetFreeSpellEffectSlot(){ + SpellEffects* ret = 0; + InfoStruct* info = GetInfoStruct(); + GetSpellEffectMutex()->readlock(__FUNCTION__, __LINE__); + for(int i=0;i<45;i++){ + if(info->spell_effects[i].spell_id == 0xFFFFFFFF){ + ret = &info->spell_effects[i]; + ret->spell_id = 0; + break; + } + } + GetSpellEffectMutex()->releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +SpellEffects* Player::GetSpellEffects() { + return GetInfoStruct()->spell_effects; +} + +// call inside info_mutex +void Player::ClearRemovalTimers(){ + map::iterator itr; + for(itr = spawn_state_list.begin(); itr != spawn_state_list.end();) { + SpawnQueueState* sr = itr->second; + itr = spawn_state_list.erase(itr); + safe_delete(sr); + } +} + +void Player::ClearEverything(){ + index_mutex.writelock(__FUNCTION__, __LINE__); + player_spawn_id_map.clear(); + player_spawn_reverse_id_map.clear(); + index_mutex.releasewritelock(__FUNCTION__, __LINE__); + map*>::iterator itr; + m_playerSpawnQuestsRequired.writelock(__FUNCTION__, __LINE__); + for (itr = player_spawn_quests_required.begin(); itr != player_spawn_quests_required.end(); itr++){ + safe_delete(itr->second); + } + player_spawn_quests_required.clear(); + m_playerSpawnQuestsRequired.releasewritelock(__FUNCTION__, __LINE__); + + m_playerSpawnHistoryRequired.writelock(__FUNCTION__, __LINE__); + for (itr = player_spawn_history_required.begin(); itr != player_spawn_history_required.end(); itr++){ + safe_delete(itr->second); + } + player_spawn_history_required.clear(); + m_playerSpawnHistoryRequired.releasewritelock(__FUNCTION__, __LINE__); + + spawn_mutex.writelock(__FUNCTION__, __LINE__); + ClearRemovalTimers(); + spawn_packet_sent.clear(); + spawn_mutex.releasewritelock(__FUNCTION__, __LINE__); + + info_mutex.writelock(__FUNCTION__, __LINE__); + spawn_info_packet_list.clear(); + info_mutex.releasewritelock(__FUNCTION__, __LINE__); + + vis_mutex.writelock(__FUNCTION__, __LINE__); + spawn_vis_packet_list.clear(); + vis_mutex.releasewritelock(__FUNCTION__, __LINE__); + + pos_mutex.writelock(__FUNCTION__, __LINE__); + spawn_pos_packet_list.clear(); + pos_mutex.releasewritelock(__FUNCTION__, __LINE__); +} +bool Player::IsResurrecting(){ + return resurrecting; +} +void Player::SetResurrecting(bool val){ + resurrecting = val; +} +void Player::AddMaintainedSpell(LuaSpell* luaspell){ + if(!luaspell) + return; + + Spell* spell = luaspell->spell; + MaintainedEffects* effect = GetFreeMaintainedSpellSlot(); + int32 target_type = 0; + Spawn* spawn = 0; + + if(effect && luaspell->caster && luaspell->caster->GetZone()){ + GetMaintainedMutex()->writelock(__FUNCTION__, __LINE__); + strcpy(effect->name, spell->GetSpellData()->name.data.c_str()); + effect->target = luaspell->initial_target; + + spawn = luaspell->caster->GetZone()->GetSpawnByID(luaspell->initial_target); + if (spawn){ + if (spawn == this) + target_type = 0; + else if (GetPet() == spawn || GetCharmedPet() == spawn) + target_type = 1; + else + target_type = 2; + } + effect->target_type = target_type; + + effect->spell = luaspell; + if(!luaspell->slot_pos) + luaspell->slot_pos = effect->slot_pos; + effect->spell_id = spell->GetSpellData()->id; + LogWrite(PLAYER__DEBUG, 5, "Player", "AddMaintainedSpell Spell ID: %u, req concentration: %u", spell->GetSpellData()->id, spell->GetSpellData()->req_concentration); + effect->icon = spell->GetSpellData()->icon; + effect->icon_backdrop = spell->GetSpellData()->icon_backdrop; + effect->conc_used = spell->GetSpellData()->req_concentration; + effect->total_time = spell->GetSpellDuration()/10; + effect->tier = spell->GetSpellData()->tier; + if (spell->GetSpellData()->duration_until_cancel) + effect->expire_timestamp = 0xFFFFFFFF; + else + effect->expire_timestamp = Timer::GetCurrentTime2() + (spell->GetSpellDuration()*100); + GetMaintainedMutex()->releasewritelock(__FUNCTION__, __LINE__); + charsheet_changed = true; + } +} +void Player::AddSpellEffect(LuaSpell* luaspell, int32 override_expire_time){ + if(!luaspell || !luaspell->caster) + return; + + Spell* spell = luaspell->spell; + SpellEffects* old_effect = GetSpellEffect(spell->GetSpellID(), luaspell->caster); + SpellEffects* effect = 0; + if (old_effect){ + GetZone()->RemoveTargetFromSpell(old_effect->spell, this); + RemoveSpellEffect(old_effect->spell); + } + + LogWrite(SPELL__DEBUG, 0, "Spell", "%s AddSpellEffect %s (%u).", spell->GetName(), GetName(), GetID()); + + effect = GetFreeSpellEffectSlot(); + + if(effect){ + GetSpellEffectMutex()->writelock(__FUNCTION__, __LINE__); + effect->spell = luaspell; + effect->spell_id = spell->GetSpellData()->id; + effect->caster = luaspell->caster; + effect->total_time = spell->GetSpellDuration()/10; + if (spell->GetSpellData()->duration_until_cancel) + effect->expire_timestamp = 0xFFFFFFFF; + else if(override_expire_time) + effect->expire_timestamp = Timer::GetCurrentTime2() + override_expire_time; + else + effect->expire_timestamp = Timer::GetCurrentTime2() + (spell->GetSpellDuration()*100); + effect->icon = spell->GetSpellData()->icon; + effect->icon_backdrop = spell->GetSpellData()->icon_backdrop; + effect->tier = spell->GetSpellTier(); + GetSpellEffectMutex()->releasewritelock(__FUNCTION__, __LINE__); + charsheet_changed = true; + + if(luaspell->caster && luaspell->caster->IsPlayer() && luaspell->caster != this) + { + if(GetClient()) { + GetClient()->TriggerSpellSave(); + } + if(((Player*)luaspell->caster)->GetClient()) { + ((Player*)luaspell->caster)->GetClient()->TriggerSpellSave(); + } + } + } +} + +void Player::RemoveMaintainedSpell(LuaSpell* luaspell){ + if(!luaspell) + return; + + bool found = false; + Client* client = GetClient(); + LuaSpell* old_spell = 0; + LuaSpell* current_spell = 0; + GetMaintainedMutex()->writelock(__FUNCTION__, __LINE__); + for(int i=0;i<30;i++){ + // If we already found the spell then we are bumping all other up one so there are no gaps in the ui + // This check needs to be first so found can never be true on the first iteration (i = 0) + if (found) { + old_spell = GetInfoStruct()->maintained_effects[i - 1].spell; + current_spell = GetInfoStruct()->maintained_effects[i].spell; + + //Update the maintained window uses_remaining and damage_remaining values + if (current_spell && current_spell->num_triggers > 0) + ClientPacketFunctions::SendMaintainedExamineUpdate(client, i - 1, current_spell->num_triggers, 0); + else if (current_spell && current_spell->damage_remaining > 0) + ClientPacketFunctions::SendMaintainedExamineUpdate(client, i - 1, current_spell->damage_remaining, 1); + else if (old_spell && old_spell->had_triggers) + ClientPacketFunctions::SendMaintainedExamineUpdate(client, i - 1, 0, 0); + else if (old_spell && old_spell->had_dmg_remaining) + ClientPacketFunctions::SendMaintainedExamineUpdate(client, i - 1, 0, 1); + + + GetInfoStruct()->maintained_effects[i].slot_pos = i - 1; + GetInfoStruct()->maintained_effects[i - 1] = GetInfoStruct()->maintained_effects[i]; + if (current_spell) + current_spell->slot_pos = i - 1; + } + // Compare spells, if we found a match set the found flag + if(GetInfoStruct()->maintained_effects[i].spell == luaspell) + found = true; + } + // if we found the spell in the array then we need to flag the char sheet as changed and set the last element to empty + if (found) { + memset(&GetInfoStruct()->maintained_effects[29], 0, sizeof(MaintainedEffects)); + GetInfoStruct()->maintained_effects[29].spell_id = 0xFFFFFFFF; + GetInfoStruct()->maintained_effects[29].icon = 0xFFFF; + GetInfoStruct()->maintained_effects[29].spell = nullptr; + charsheet_changed = true; + } + GetMaintainedMutex()->releasewritelock(__FUNCTION__, __LINE__); +} + +void Player::RemoveSpellEffect(LuaSpell* spell){ + bool found = false; + GetSpellEffectMutex()->writelock(__FUNCTION__, __LINE__); + for(int i=0;i<45;i++){ + if (found) { + GetInfoStruct()->spell_effects[i-1] = GetInfoStruct()->spell_effects[i]; + } + if(GetInfoStruct()->spell_effects[i].spell == spell) + found = true; + } + if (found) { + memset(&GetInfoStruct()->spell_effects[44], 0, sizeof(SpellEffects)); + GetInfoStruct()->spell_effects[44].spell_id = 0xFFFFFFFF; + changed = true; + info_changed = true; + AddChangedZoneSpawn(); + charsheet_changed = true; + } + GetSpellEffectMutex()->releasewritelock(__FUNCTION__, __LINE__); +} + +void Player::PrepareIncomingMovementPacket(int32 len, uchar* data, int16 version) +{ + if(GetClient() && GetClient()->IsReloadingZone()) + return; + + LogWrite(PLAYER__DEBUG, 7, "Player", "Enter: %s", __FUNCTION__); // trace + + // XML structs may be to slow to use in this portion of the code as a single + // client sends a LOT of these packets when they are moving. I have commented + // out all the code for xml structs, to switch to it just uncomment + // the code and comment the 2 if/else if/else blocks, both have a comment + // above them to let you know wich ones they are. + + //PacketStruct* update = configReader.getStruct("WS_PlayerPosUpdate", version); + int16 total_bytes; // = update->GetTotalPacketSize(); + + // Comment out this if/else if/else block if you switch to xml structs + if (version >= 1144) + total_bytes = sizeof(Player_Update1144); + else if (version >= 1096) + total_bytes = sizeof(Player_Update1096); + else if (version <= 373) + total_bytes = sizeof(Player_Update283); + else + total_bytes = sizeof(Player_Update); + + if (!movement_packet) + movement_packet = new uchar[total_bytes]; + else if (!old_movement_packet) + old_movement_packet = new uchar[total_bytes]; + if (movement_packet && old_movement_packet) + memcpy(old_movement_packet, movement_packet, total_bytes); + bool reverse = version > 373; + Unpack(len, data, movement_packet, total_bytes, 0, reverse); + if (!movement_packet || !old_movement_packet) + return; + Decode(movement_packet, old_movement_packet, total_bytes); + + //update->LoadPacketData(movement_packet, total_bytes); + + int32 activity; // = update->getType_int32_ByName("activity"); + int32 grid_id; // = update->getType_int32_ByName("grid_location"); + float direction1; // = update->getType_float_ByName("direction1"); + float direction2; // = update->getType_float_ByName("direction2");; + float speed; // = update->getType_float_ByName("speed");; + float side_speed; + float vert_speed; + float x; // = update->getType_float_ByName("x");; + float y; // = update->getType_float_ByName("y");; + float z; // = update->getType_float_ByName("z");; + float x_speed; + float y_speed; + float z_speed; + float client_pitch; + + // comment out this if/else if/else block if you use xml structs + if (version >= 1144) { + Player_Update1144* update = (Player_Update1144*)movement_packet; + activity = update->activity; + grid_id = update->grid_location; + direction1 = update->direction1; + direction2 = update->direction2; + speed = update->speed; + side_speed = update->side_speed; + vert_speed = update->vert_speed; + x = update->x; + y = update->y; + z = update->z; + x_speed = update->speed_x; + y_speed = update->speed_y; + z_speed = update->speed_z; + client_pitch = update->pitch; + + SetPitch(180 + update->pitch); + } + else if (version >= 1096) { + Player_Update1096* update = (Player_Update1096*)movement_packet; + activity = update->activity; + grid_id = update->grid_location; + direction1 = update->direction1; + direction2 = update->direction2; + speed = update->speed; + side_speed = update->side_speed; + vert_speed = update->vert_speed; + x = update->x; + y = update->y; + z = update->z; + x_speed = update->speed_x; + y_speed = update->speed_y; + z_speed = update->speed_z; + client_pitch = update->pitch; + + SetPitch(180 + update->pitch); + } + else if (version <= 373) { + Player_Update283* update = (Player_Update283*)movement_packet; + activity = update->activity; + grid_id = update->grid_location; + direction1 = update->direction1; + direction2 = update->direction2; + speed = update->speed; + side_speed = update->side_speed; + vert_speed = update->vert_speed; + client_pitch = update->pitch; + + x = update->x; + y = update->y; + z = update->z; + x_speed = update->speed_x; + y_speed = update->speed_y; + z_speed = update->speed_z; + appearance.pos.X2 = update->orig_x; + appearance.pos.Y2 = update->orig_y; + appearance.pos.Z2 = update->orig_z; + appearance.pos.X3 = update->orig_x2; + appearance.pos.Y3 = update->orig_y2; + appearance.pos.Z3 = update->orig_z2; + if (update->pitch != 0) + SetPitch(180 + update->pitch); + } + else { + Player_Update* update = (Player_Update*)movement_packet; + activity = update->activity; + grid_id = update->grid_location; + direction1 = update->direction1; + direction2 = update->direction2; + speed = update->speed; + side_speed = update->side_speed; + vert_speed = update->vert_speed; + x = update->x; + y = update->y; + z = update->z; + x_speed = update->speed_x; + y_speed = update->speed_y; + z_speed = update->speed_z; + appearance.pos.X2 = update->orig_x; + appearance.pos.Y2 = update->orig_y; + appearance.pos.Z2 = update->orig_z; + appearance.pos.X3 = update->orig_x2; + appearance.pos.Y3 = update->orig_y2; + appearance.pos.Z3 = update->orig_z2; + client_pitch = update->pitch; + + SetPitch(180 + update->pitch); + } + + SetHeading((sint16)(direction1 * 64), (sint16)(direction2 * 64)); + + if (activity != last_movement_activity) { + switch(activity) { + case UPDATE_ACTIVITY_RUNNING: + case UPDATE_ACTIVITY_RUNNING_AOM: + case UPDATE_ACTIVITY_IN_WATER_ABOVE: + case UPDATE_ACTIVITY_IN_WATER_BELOW: + case UPDATE_ACTIVITY_MOVE_WATER_ABOVE_AOM: + case UPDATE_ACTIVITY_MOVE_WATER_BELOW_AOM: { + if(GetZone() && GetZone()->GetDrowningVictim(this)) + GetZone()->RemoveDrowningVictim(this); + + break; + } + case UPDATE_ACTIVITY_DROWNING: + case UPDATE_ACTIVITY_DROWNING2: + case UPDATE_ACTIVITY_DROWNING_AOM: + case UPDATE_ACTIVITY_DROWNING2_AOM: { + if(GetZone() && !GetInvulnerable()) { + GetZone()->AddDrowningVictim(this); + } + break; + } + case UPDATE_ACTIVITY_JUMPING: + case UPDATE_ACTIVITY_JUMPING_AOM: + case UPDATE_ACTIVITY_FALLING: + case UPDATE_ACTIVITY_FALLING_AOM: { + if(IsCasting()) { + GetZone()->Interrupted(this, 0, SPELL_ERROR_INTERRUPTED, false, true); + } + if(GetInitialState() != 1024) { + SetInitialState(1024); + } + else if(GetInitialState() == 1024) { + if(activity == UPDATE_ACTIVITY_JUMPING_AOM) { + SetInitialState(UPDATE_ACTIVITY_JUMPING_AOM); + } + else { + SetInitialState(16512); + } + } + break; + } + } + + last_movement_activity = activity; + } + //Player is riding a lift, update lift XYZ offsets and the lift's spawn pointer + if (activity & UPDATE_ACTIVITY_RIDING_BOAT) { + Spawn* boat = 0; + + float boat_x = x; + float boat_y = y; + float boat_z = z; + + if (GetBoatSpawn() == 0 && GetZone()) { + boat = GetZone()->GetClosestTransportSpawn(GetX(), GetY(), GetZ()); + SetBoatSpawn(boat); + if(boat) + { + LogWrite(PLAYER__DEBUG, 0, "Player", "Set Player %s (%u) on Boat: %s", + GetName(), GetCharacterID(), boat ? boat->GetName() : "notset"); + boat->AddRailPassenger(GetCharacterID()); + GetZone()->CallSpawnScript(boat, SPAWN_SCRIPT_BOARD, this); + } + } + + if (boat || (GetBoatSpawn() && GetZone())) { + if (!boat) + boat = GetZone()->GetSpawnByID(GetBoatSpawn()); + + if (boat && boat->IsWidget() && ((Widget*)boat)->GetMultiFloorLift()) { + boat_x -= boat->GetX(); + boat_y -= boat->GetY(); + boat_z -= boat->GetZ(); + } + } + + SetBoatX(boat_x); + SetBoatY(boat_y); + SetBoatZ(boat_z); + pos_packet_speed = speed; + grid_id = GetLocation(); + } + else if (GetBoatSpawn() > 0 && !lift_cooldown.Enabled()) + { + lift_cooldown.Start(100, true); + } + else if(lift_cooldown.Check()) + { + if(GetBoatSpawn()) + { + Spawn* boat = GetZone()->GetSpawnByID(GetBoatSpawn()); + if(boat) + { + LogWrite(PLAYER__DEBUG, 0, "Player", "Remove Player %s (%u) from Boat: %s", + GetName(), GetCharacterID(), boat ? boat->GetName() : "notset"); + boat->RemoveRailPassenger(GetCharacterID()); + GetZone()->CallSpawnScript(boat, SPAWN_SCRIPT_DEBOARD, this); + } + } + SetBoatSpawn(0); + lift_cooldown.Disable(); + } + + if (!IsResurrecting() && !GetBoatSpawn()) + { + if (!IsRooted() && !IsMezzedOrStunned()) { + SetX(x); + SetY(y, true, true); + SetZ(z); + SetSpeedX(x_speed); + SetSpeedY(y_speed); + SetSpeedZ(z_speed); + SetSideSpeed(side_speed); + SetVertSpeed(vert_speed); + SetClientHeading1(direction1); + SetClientHeading2(direction2); + SetClientPitch(client_pitch); + if(version > 373) { + pos_packet_speed = speed; + } + } + else { + SetSpeedX(0.0f); + SetSpeedY(0.0f); + SetSpeedZ(0.0f); + SetSideSpeed(0.0f); + SetVertSpeed(0.0f); + SetClientHeading1(direction1); + SetClientHeading2(direction2); + SetClientPitch(client_pitch); + pos_packet_speed = 0; + } + } + + if (GetLocation() != grid_id) + { + LogWrite(PLAYER__DEBUG, 0, "Player", "%s left grid %u and entered grid %u", appearance.name, GetLocation(), grid_id); + const char* zone_script = world.GetZoneScript(GetZone()->GetZoneID()); + + if (zone_script && lua_interface) { + lua_interface->RunZoneScript(zone_script, "leave_location", GetZone(), this, GetLocation()); + } + + SetLocation(grid_id); + + if (zone_script && lua_interface) { + lua_interface->RunZoneScript(zone_script, "enter_location", GetZone(), this, grid_id); + } + } + if (activity == UPDATE_ACTIVITY_IN_WATER_ABOVE || activity == UPDATE_ACTIVITY_IN_WATER_BELOW || + activity == UPDATE_ACTIVITY_MOVE_WATER_BELOW_AOM || activity == UPDATE_ACTIVITY_MOVE_WATER_ABOVE_AOM) { + if (MakeRandomFloat(0, 100) < 25 && InWater()) + GetSkillByName("Swimming", true); + } + // don't have to uncomment the print packet but you MUST uncomment the safe_delete() for xml structs + //update->PrintPacket(); + //safe_delete(update); + + LogWrite(PLAYER__DEBUG, 7, "Player", "Exit: %s", __FUNCTION__); // trace +} + +int16 Player::GetLastMovementActivity(){ + return last_movement_activity; +} + +void Player::AddSpawnInfoPacketForXOR(int32 spawn_id, uchar* packet, int16 packet_size){ + spawn_info_packet_list[spawn_id] = string((char*)packet, packet_size); +} + +void Player::AddSpawnPosPacketForXOR(int32 spawn_id, uchar* packet, int16 packet_size){ + spawn_pos_packet_list[spawn_id] = string((char*)packet, packet_size); +} + +uchar* Player::GetSpawnPosPacketForXOR(int32 spawn_id){ + uchar* ret = 0; + if(spawn_pos_packet_list.count(spawn_id) == 1) + ret = (uchar*)spawn_pos_packet_list[spawn_id].c_str(); + return ret; +} +uchar* Player::GetSpawnInfoPacketForXOR(int32 spawn_id){ + uchar* ret = 0; + if(spawn_info_packet_list.count(spawn_id) == 1) + ret = (uchar*)spawn_info_packet_list[spawn_id].c_str(); + return ret; +} +void Player::AddSpawnVisPacketForXOR(int32 spawn_id, uchar* packet, int16 packet_size){ + spawn_vis_packet_list[spawn_id] = string((char*)packet, packet_size); +} + +uchar* Player::GetSpawnVisPacketForXOR(int32 spawn_id){ + uchar* ret = 0; + if(spawn_vis_packet_list.count(spawn_id) == 1) + ret = (uchar*)spawn_vis_packet_list[spawn_id].c_str(); + return ret; +} + +uchar* Player::GetTempInfoPacketForXOR(){ + return spawn_tmp_info_xor_packet; +} + +uchar* Player::GetTempVisPacketForXOR(){ + return spawn_tmp_vis_xor_packet; +} + +uchar* Player::GetTempPosPacketForXOR(){ + return spawn_tmp_pos_xor_packet; +} + +uchar* Player::SetTempInfoPacketForXOR(int16 size){ + spawn_tmp_info_xor_packet = new uchar[size]; + info_xor_size = size; + return spawn_tmp_info_xor_packet; +} + +uchar* Player::SetTempVisPacketForXOR(int16 size){ + spawn_tmp_vis_xor_packet = new uchar[size]; + vis_xor_size = size; + return spawn_tmp_vis_xor_packet; +} + +uchar* Player::SetTempPosPacketForXOR(int16 size){ + spawn_tmp_pos_xor_packet = new uchar[size]; + pos_xor_size = size; + return spawn_tmp_pos_xor_packet; +} + +bool Player::CheckPlayerInfo(){ + return info != 0; +} + +bool Player::SetSpawnSentState(Spawn* spawn, SpawnState state) { + bool val = true; + spawn_mutex.writelock(__FUNCTION__, __LINE__); + int16 index = GetIndexForSpawn(spawn); + if(index > 0 && (state == SpawnState::SPAWN_STATE_SENDING)) { + LogWrite(PLAYER__WARNING, 0, "Player", "Spawn ALREADY INDEXED for Player %s (%u). Spawn %s (index %u) attempted to state %u.", + GetName(), GetCharacterID(), spawn->GetName(), index, state); + if(GetClient() && GetClient()->IsReloadingZone()) { + spawn_packet_sent.insert(make_pair(spawn->GetID(), state)); + val = false; + } + // we don't do anything this spawn is already populated by the player + } + else { + LogWrite(PLAYER__DEBUG, 0, "Player", "Spawn for Player %s (%u). Spawn %s (index %u) in state %u.", + GetName(), GetCharacterID(), spawn->GetName(), index, state); + + map::iterator itr = spawn_packet_sent.find(spawn->GetID()); + if(itr != spawn_packet_sent.end()) + itr->second = state; + else + spawn_packet_sent.insert(make_pair(spawn->GetID(), state)); + if(state == SPAWN_STATE_SENT_WAIT) { + map::iterator state_itr; + if((state_itr = spawn_state_list.find(spawn->GetID())) != spawn_state_list.end()) { + safe_delete(state_itr->second); + spawn_state_list.erase(state_itr); + } + + SpawnQueueState* removal = new SpawnQueueState; + removal->index_id = index; + removal->spawn_state_timer = Timer(500, true); + removal->spawn_state_timer.Start(); + spawn_state_list.insert(make_pair(spawn->GetID(),removal)); + } + else if(state == SpawnState::SPAWN_STATE_REMOVING && + spawn_state_list.count(spawn->GetID()) == 0) { + SpawnQueueState* removal = new SpawnQueueState; + removal->index_id = index; + removal->spawn_state_timer = Timer(1000, true); + removal->spawn_state_timer.Start(); + spawn_state_list.insert(make_pair(spawn->GetID(),removal)); + } + } + spawn_mutex.releasewritelock(__FUNCTION__, __LINE__); + return val; +} + +void Player::CheckSpawnStateQueue() { + if(!GetClient() || !GetClient()->IsReadyForUpdates()) + return; + + spawn_mutex.writelock(__FUNCTION__, __LINE__); + map::iterator itr; + for(itr = spawn_state_list.begin(); itr != spawn_state_list.end();) { + if(itr->second->spawn_state_timer.Check()) { + map::iterator sent_itr = spawn_packet_sent.find(itr->first); + LogWrite(PLAYER__DEBUG, 0, "Player", "Spawn for Player %s (%u). Spawn index %u in state %u.", + GetName(), GetCharacterID(), itr->second->index_id, sent_itr->second); + switch(sent_itr->second) { + case SpawnState::SPAWN_STATE_SENT_WAIT: { + sent_itr->second = SpawnState::SPAWN_STATE_SENT; + SpawnQueueState* sr = itr->second; + itr = spawn_state_list.erase(itr); + safe_delete(sr); + break; + } + case SpawnState::SPAWN_STATE_REMOVING: { + if(itr->first == GetID() && GetClient()->IsReloadingZone()) { + itr->second->spawn_state_timer.Disable(); + continue; + } + + if(itr->second->index_id) { + PacketStruct* packet = packet = configReader.getStruct("WS_DestroyGhostCmd", GetClient()->GetVersion()); + packet->setDataByName("spawn_index", itr->second->index_id); + packet->setDataByName("delete", 1); + GetClient()->QueuePacket(packet->serialize()); + safe_delete(packet); + } + sent_itr->second = SpawnState::SPAWN_STATE_REMOVING_SLEEP; + itr++; + break; + } + case SpawnState::SPAWN_STATE_REMOVING_SLEEP: { + map::iterator sent_itr = spawn_packet_sent.find(itr->first); + sent_itr->second = SpawnState::SPAWN_STATE_REMOVED; + SpawnQueueState* sr = itr->second; + itr = spawn_state_list.erase(itr); + safe_delete(sr); + break; + } + default: { + // reset + itr->second->spawn_state_timer.Disable(); + break; + } + } + } + else + itr++; + } + spawn_mutex.releasewritelock(__FUNCTION__, __LINE__); +} + +bool Player::WasSentSpawn(int32 spawn_id){ + if(GetID() == spawn_id) + return true; + + bool ret = false; + spawn_mutex.readlock(__FUNCTION__, __LINE__); + map::iterator itr = spawn_packet_sent.find(spawn_id); + if(itr != spawn_packet_sent.end() && itr->second == SpawnState::SPAWN_STATE_SENT) { + ret = true; + } + spawn_mutex.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +bool Player::IsSendingSpawn(int32 spawn_id){ + bool ret = false; + spawn_mutex.readlock(__FUNCTION__, __LINE__); + map::iterator itr = spawn_packet_sent.find(spawn_id); + if(itr != spawn_packet_sent.end() && (itr->second == SpawnState::SPAWN_STATE_SENDING || itr->second == SPAWN_STATE_SENT_WAIT)) { + ret = true; + } + spawn_mutex.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +bool Player::IsRemovingSpawn(int32 spawn_id){ + bool ret = false; + spawn_mutex.readlock(__FUNCTION__, __LINE__); + map::iterator itr = spawn_packet_sent.find(spawn_id); + if(itr != spawn_packet_sent.end() && + (itr->second == SpawnState::SPAWN_STATE_REMOVING || itr->second == SpawnState::SPAWN_STATE_REMOVING_SLEEP)) { + ret = true; + } + spawn_mutex.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +PlayerSkillList* Player::GetSkills(){ + return &skill_list; +} + +void Player::InCombat(bool val, bool range) { + if (val) + GetInfoStruct()->set_flags(GetInfoStruct()->get_flags() | (1 << (range?CF_RANGED_AUTO_ATTACK:CF_AUTO_ATTACK))); + else + GetInfoStruct()->set_flags(GetInfoStruct()->get_flags() & ~(1 << (range?CF_RANGED_AUTO_ATTACK:CF_AUTO_ATTACK))); + + bool changeCombatState = false; + + if((in_combat && !val) || (!in_combat && val)) + changeCombatState = true; + + in_combat = val; + if(in_combat) + AddIconValue(64); + else + RemoveIconValue(64); + + bool update_regen = false; + if(GetInfoStruct()->get_engaged_encounter()) { + if(!IsAggroed() || !IsEngagedInEncounter()) { + GetInfoStruct()->set_engaged_encounter(0); + update_regen = true; + } + } + + if(changeCombatState || update_regen) + SetRegenValues((GetInfoStruct()->get_effective_level() > 0) ? GetInfoStruct()->get_effective_level() : GetLevel()); + + charsheet_changed = true; + info_changed = true; +} + +void Player::SetCharSheetChanged(bool val){ + charsheet_changed = val; +} + +bool Player::GetCharSheetChanged(){ + return charsheet_changed; +} + +bool Player::AdventureXPEnabled(){ + return (GetInfoStruct()->get_flags() & (1 << CF_COMBAT_EXPERIENCE_ENABLED)); +} + +bool Player::TradeskillXPEnabled() { + // TODO: need to identify the flag to togle tradeskill xp + return true; +} + +void Player::set_character_flag(int flag){ + LogWrite(PLAYER__DEBUG, 0, "Player", "Flag: %u", flag); + LogWrite(PLAYER__DEBUG, 0, "Player", "Flags before: %u, Flags2: %u", GetInfoStruct()->get_flags(), GetInfoStruct()->get_flags2()); + + if (flag > CF_MAXIMUM_FLAG) return; + if (flag < 32) GetInfoStruct()->set_flags(GetInfoStruct()->get_flags() | (1 << flag)); + else GetInfoStruct()->set_flags2(GetInfoStruct()->get_flags2() | (1 << (flag - 32))); + charsheet_changed = true; + info_changed = true; + + LogWrite(PLAYER__DEBUG, 0, "Player", "Flags after: %u, Flags2: %u", GetInfoStruct()->get_flags(), GetInfoStruct()->get_flags2()); +} + +void Player::reset_character_flag(int flag){ + LogWrite(PLAYER__DEBUG, 0, "Player", "Flag: %u", flag); + LogWrite(PLAYER__DEBUG, 0, "Player", "Flags before: %u, Flags2: %u", GetInfoStruct()->get_flags(), GetInfoStruct()->get_flags2()); + + if (flag > CF_MAXIMUM_FLAG) return; + if (flag < 32) + { + int8 origflag = GetInfoStruct()->get_flags(); + GetInfoStruct()->set_flags(origflag &= ~(1 << flag)); + } + else + { + int8 flag2 = GetInfoStruct()->get_flags2(); + GetInfoStruct()->set_flags2(flag2 &= ~(1 << (flag - 32))); + } + charsheet_changed = true; + info_changed = true; + + LogWrite(PLAYER__DEBUG, 0, "Player", "Flags after: %u, Flags2: %u", GetInfoStruct()->get_flags(), GetInfoStruct()->get_flags2()); +} + +void Player::toggle_character_flag(int flag){ + LogWrite(PLAYER__DEBUG, 0, "Player", "Flag: %u", flag); + LogWrite(PLAYER__DEBUG, 0, "Player", "Flags before: %u, Flags2: %u", GetInfoStruct()->get_flags(), GetInfoStruct()->get_flags2()); + + if (flag > CF_MAXIMUM_FLAG) return; + if (flag < 32) + { + int32 origflag = GetInfoStruct()->get_flags(); + GetInfoStruct()->set_flags(origflag ^= (1 << flag)); + } + else + { + int32 flag2 = GetInfoStruct()->get_flags2(); + GetInfoStruct()->set_flags2(flag2 ^= (1 << (flag - 32))); + } + charsheet_changed = true; + info_changed = true; + + LogWrite(PLAYER__DEBUG, 0, "Player", "Flags after: %u, Flags2: %u", GetInfoStruct()->get_flags(), GetInfoStruct()->get_flags2()); +} + +bool Player::get_character_flag(int flag){ + bool ret = false; + + if (flag > CF_MAXIMUM_FLAG){ + LogWrite(PLAYER__DEBUG, 0, "Player", "Player::get_character_flag error: attempted to check flag %i", flag); + return ret; + } + if (flag < 32) ret = ((GetInfoStruct()->get_flags()) >> flag & 1); + else ret = ((GetInfoStruct()->get_flags2()) >> (flag - 32) & 1); + + return ret; +} + +float Player::GetXPVitality(){ + return GetInfoStruct()->get_xp_vitality(); +} + +float Player::GetTSXPVitality() { + return GetInfoStruct()->get_tradeskill_xp_vitality(); +} + +bool Player::DoubleXPEnabled(){ + return GetInfoStruct()->get_xp_vitality() > 0; +} + +void Player::SetCharacterID(int32 new_id){ + char_id = new_id; +} + +int32 Player::GetCharacterID(){ + return char_id; +} + +float Player::CalculateXP(Spawn* victim){ + if(AdventureXPEnabled() == false || !victim) + return 0; + float multiplier = 0; + + float zone_xp_modifier = 1; // let's be safe!! + if( GetZone()->GetXPModifier() != 0 ) { + zone_xp_modifier = GetZone()->GetXPModifier(); + LogWrite(PLAYER__DEBUG, 5, "XP", "Zone XP Modifier = %.2f", zone_xp_modifier); + } + + switch(GetArrowColor(victim->GetLevel())){ + case ARROW_COLOR_GRAY: + LogWrite(PLAYER__DEBUG, 5, "XP", "Gray Arrow = No XP"); + return 0.0f; + break; + case ARROW_COLOR_GREEN: + multiplier = 3.25; + LogWrite(PLAYER__DEBUG, 5, "XP", "Green Arrow Multiplier = %.2f", multiplier); + break; + case ARROW_COLOR_BLUE: + multiplier = 3.5; + LogWrite(PLAYER__DEBUG, 5, "XP", "Blue Arrow Multiplier = %.2f", multiplier); + break; + case ARROW_COLOR_WHITE: + multiplier = 4; + LogWrite(PLAYER__DEBUG, 5, "XP", "White Arrow Multiplier = %.2f", multiplier); + break; + case ARROW_COLOR_YELLOW: + multiplier = 4.25; + LogWrite(PLAYER__DEBUG, 5, "XP", "Yellow Arrow Multiplier = %.2f", multiplier); + break; + case ARROW_COLOR_ORANGE: + multiplier = 4.5; + LogWrite(PLAYER__DEBUG, 5, "XP", "Orange Arrow Multiplier = %.2f", multiplier); + break; + case ARROW_COLOR_RED: + multiplier = 6; + LogWrite(PLAYER__DEBUG, 5, "XP", "Red Arrow Multiplier = %.2f", multiplier); + break; + } + float total = multiplier * 8; + LogWrite(PLAYER__DEBUG, 5, "XP", "Multiplier * 8 = %.2f", total); + + if(victim->GetDifficulty() > 6) { // no need to multiply by 1 if this is a normal mob + total *= (victim->GetDifficulty() - 5); + LogWrite(PLAYER__DEBUG, 5, "XP", "Encounter > 6, total = %.2f", total); + } + else if(victim->GetDifficulty() <= 5) { + total /= (7 - victim->GetDifficulty()); //1 down mobs are worth half credit, 2 down worth .25, etc + LogWrite(PLAYER__DEBUG, 5, "XP", "Encounter <= 5, total = %.2f", total); + } + + if(victim->GetHeroic() > 1) { + total *= victim->GetHeroic(); + LogWrite(PLAYER__DEBUG, 5, "XP", "Heroic, total = %.2f", total); + } + if(DoubleXPEnabled()) { + LogWrite(PLAYER__DEBUG, 5, "XP", "Calculating Double XP!"); + + float percent = (((float)(total))/GetNeededXP()) *100; + LogWrite(PLAYER__DEBUG, 5, "XP", "Percent of total / XP Needed * 100, percent = %.2f", percent); + float xp_vitality = GetXPVitality(); + if(xp_vitality >= percent) { + GetInfoStruct()->set_xp_vitality(xp_vitality - percent); + total *= 2; + LogWrite(PLAYER__DEBUG, 5, "XP", "Vitality >= Percent, total = %.2f", total); + } + else { + total += ((GetXPVitality() / percent) *2)*total; + GetInfoStruct()->set_xp_vitality(0); + LogWrite(PLAYER__DEBUG, 5, "XP", "Vitality < Percent, total = %.2f", total); + } + } + LogWrite(PLAYER__DEBUG, 5, "XP", "Final total = %.2f", (total * world.GetXPRate() * zone_xp_modifier)); + return total * world.GetXPRate() * zone_xp_modifier; +} + +float Player::CalculateTSXP(int8 level){ + if(TradeskillXPEnabled() == false) + return 0; + float multiplier = 0; + + float zone_xp_modifier = 1; // let's be safe!! + if( GetZone()->GetXPModifier() != 0 ) { + zone_xp_modifier = GetZone()->GetXPModifier(); + LogWrite(PLAYER__DEBUG, 5, "XP", "Zone XP Modifier = %.2f", zone_xp_modifier); + } + + sint16 diff = level - GetTSLevel(); + if(GetTSLevel() < 10) + diff *= 3; + else if(GetTSLevel() <= 20) + diff *= 2; + if(diff >= 9) + multiplier = 6; + else if(diff >= 5) + multiplier = 4.5; + else if(diff >= 1) + multiplier = 4.25; + else if(diff == 0) + multiplier = 4; + else if(diff <= -11) + multiplier = 0; + else if(diff <= -6) + multiplier = 3.25; + else //if(diff < 0) + multiplier = 3.5; + + + float total = multiplier * 8; + LogWrite(PLAYER__DEBUG, 5, "XP", "Multiplier * 8 = %.2f", total); + + if(DoubleXPEnabled()) { + LogWrite(PLAYER__DEBUG, 5, "XP", "Calculating Double XP!"); + + float percent = (((float)(total))/GetNeededTSXP()) *100; + LogWrite(PLAYER__DEBUG, 5, "XP", "Percent of total / XP Needed * 100, percent = %.2f", percent); + + float ts_xp_vitality = GetTSXPVitality(); + if(ts_xp_vitality >= percent) { + GetInfoStruct()->set_tradeskill_xp_vitality(ts_xp_vitality - percent); + total *= 2; + LogWrite(PLAYER__DEBUG, 5, "XP", "Vitality >= Percent, total = %.2f", total); + } + else { + total += ((GetTSXPVitality() / percent) *2)*total; + GetInfoStruct()->set_tradeskill_xp_vitality(0); + LogWrite(PLAYER__DEBUG, 5, "XP", "Vitality < Percent, total = %.2f", total); + } + } + LogWrite(PLAYER__DEBUG, 5, "XP", "Final total = %.2f", (total * world.GetXPRate() * zone_xp_modifier)); + return total * world.GetXPRate() * zone_xp_modifier; +} + +void Player::CalculateOfflineDebtRecovery(int32 unix_timestamp) +{ + float xpDebt = GetXPDebt(); + // not a real timestamp to work with + if(unix_timestamp < 1 || xpDebt == 0.0f) + return; + + uint32 diff = (Timer::GetUnixTimeStamp() - unix_timestamp)/1000; + + float recoveryDebtPercentage = rule_manager.GetGlobalRule(R_Combat, ExperienceDebtRecoveryPercent)->GetFloat()/100.0f; + int32 recoveryPeriodSeconds = rule_manager.GetGlobalRule(R_Combat, ExperienceDebtRecoveryPeriod)->GetInt32(); + if(recoveryDebtPercentage == 0.0f || recoveryPeriodSeconds < 1) + return; + + + float periodsPassed = (float)diff/(float)recoveryPeriodSeconds; + + // not enough time passed to calculate debt xp recovered + if(periodsPassed < 1.0f) + return; + + float debtToSubtract = xpDebt * ((recoveryDebtPercentage*periodsPassed)/100.0f); + + if(debtToSubtract >= xpDebt) + GetInfoStruct()->set_xp_debt(0.0f); + else + GetInfoStruct()->set_xp_debt(xpDebt - debtToSubtract); +} + +void Player::SetNeededXP(int32 val){ + GetInfoStruct()->set_xp_needed(val); +} + +void Player::SetNeededXP(){ + //GetInfoStruct()->xp_needed = GetLevel() * 100; + // Get xp needed to get to the next level + int16 level = GetLevel() + 1; + // If next level is beyond what we have in the map multiply the last value we have by how many levels we are over plus one + if (level > 95) + SetNeededXP(database.GetMysqlExpCurve(95)* ((level - 95) + 1)); + else + SetNeededXP(database.GetMysqlExpCurve(level)); +} + +void Player::SetXP(int32 val){ + GetInfoStruct()->set_xp(val); +} + +void Player::SetNeededTSXP(int32 val) { + GetInfoStruct()->set_ts_xp_needed(val); +} + +void Player::SetNeededTSXP() { + GetInfoStruct()->set_ts_xp_needed(GetTSLevel() * 100); +} + +void Player::SetTSXP(int32 val) { + GetInfoStruct()->set_ts_xp(val); +} + +float Player::GetXPDebt(){ + return GetInfoStruct()->get_xp_debt(); +} + +int32 Player::GetNeededXP(){ + return GetInfoStruct()->get_xp_needed(); +} + +int32 Player::GetXP(){ + return GetInfoStruct()->get_xp(); +} + +int32 Player::GetNeededTSXP() { + return GetInfoStruct()->get_ts_xp_needed(); +} + +int32 Player::GetTSXP() { + return GetInfoStruct()->get_ts_xp(); +} + +bool Player::AddXP(int32 xp_amount){ + if(!GetClient()) // potential linkdead player + return false; + + MStats.lock(); + xp_amount += (int32)(((float)xp_amount) * stats[ITEM_STAT_COMBATEXPMOD]) / 100; + MStats.unlock(); + + if(GetInfoStruct()->get_xp_debt()) + { + float expRatioToDebt = rule_manager.GetGlobalRule(R_Combat, ExperienceToDebt)->GetFloat()/100.0f; + int32 amountToTakeFromDebt = (int32)((float)expRatioToDebt * (float)xp_amount); + int32 amountRequiredClearDebt = (GetInfoStruct()->get_xp_debt()/100.0f) * xp_amount; + + if(amountToTakeFromDebt > amountRequiredClearDebt) + { + GetInfoStruct()->set_xp_debt(0.0f); + if(amountRequiredClearDebt > xp_amount) + xp_amount = 0; + else + xp_amount -= amountRequiredClearDebt; + } + else + { + float amountRemovedPct = ((float)amountToTakeFromDebt/(float)amountRequiredClearDebt); + GetInfoStruct()->set_xp_debt(GetInfoStruct()->get_xp_debt()-amountRemovedPct); + if(amountToTakeFromDebt > xp_amount) + xp_amount = 0; + else + xp_amount -= amountToTakeFromDebt; + } + } + + // used up in xp debt + if(!xp_amount) { + SetCharSheetChanged(true); + return true; + } + + int32 prev_level = GetLevel(); + float current_xp_percent = ((float)GetXP()/(float)GetNeededXP())*100; + float miniding_min_percent = ((int)(current_xp_percent/10)+1)*10; + while((xp_amount + GetXP()) >= GetNeededXP()){ + if (!CheckLevelStatus(GetLevel() + 1)) { + if(GetClient()) { + GetClient()->SimpleMessage(CHANNEL_COLOR_RED, "You do not have the required status to level up anymore!"); + } + SetCharSheetChanged(true); + return false; + } + xp_amount -= GetNeededXP() - GetXP(); + SetLevel(GetLevel() + 1); + } + SetXP(GetXP() + xp_amount); + GetPlayerInfo()->CalculateXPPercentages(); + current_xp_percent = ((float)GetXP()/(float)GetNeededXP())*100; + if(current_xp_percent >= miniding_min_percent){ + SetHP(GetTotalHP()); + SetPower(GetTotalPower()); + GetZone()->SendCastSpellPacket(332, this, this); //send mini level up spell effect + } + + if(GetClient()) { + GetClient()->Message(CHANNEL_REWARD, "You gain %u experience!", (int32)xp_amount); + + if (prev_level != GetLevel()) + GetClient()->ChangeLevel(prev_level, GetLevel()); + } + + SetCharSheetChanged(true); + return true; +} + +bool Player::AddTSXP(int32 xp_amount){ + MStats.lock(); + xp_amount += ((xp_amount)*stats[ITEM_STAT_TRADESKILLEXPMOD]) / 100; + MStats.unlock(); + + float current_xp_percent = ((float)GetTSXP()/(float)GetNeededTSXP())*100; + float miniding_min_percent = ((int)(current_xp_percent/10)+1)*10; + while((xp_amount + GetTSXP()) >= GetNeededTSXP()){ + if (!CheckLevelStatus(GetTSLevel() + 1)) { + if(GetClient()) { + GetClient()->SimpleMessage(CHANNEL_COLOR_RED, "You do not have the required status to level up anymore!"); + } + return false; + } + xp_amount -= GetNeededTSXP() - GetTSXP(); + SetTSLevel(GetTSLevel() + 1); + SetTSXP(0); + SetNeededTSXP(); + } + SetTSXP(GetTSXP() + xp_amount); + GetPlayerInfo()->CalculateXPPercentages(); + current_xp_percent = ((float)GetTSXP()/(float)GetNeededTSXP())*100; + if(current_xp_percent >= miniding_min_percent){ + SetHP(GetTotalHP()); + SetPower(GetTotalPower()); + } + + if (GetTradeskillClass() == 0){ + SetTradeskillClass(1); + GetInfoStruct()->set_tradeskill_class1(1); + GetInfoStruct()->set_tradeskill_class2(1); + GetInfoStruct()->set_tradeskill_class3(1); + } + return true; +} + +void Player::CalculateLocation(){ + if(GetSpeed() > 0 ){ + if(GetHeading() >= 270 && GetHeading() <= 360){ + SetX(GetX() + (GetSpeed()*.5)*((360-GetHeading())/90)); + SetZ(GetZ() - (GetSpeed()*.5)*((GetHeading()-270)/90)); + } + else if(GetHeading() >= 180 && GetHeading() < 270){ + SetX(GetX() + (GetSpeed()*.5)*((GetHeading()-180)/90)); + SetZ(GetZ() + (GetSpeed()*.5)*((270-GetHeading())/90)); + } + else if(GetHeading() >= 90 && GetHeading() < 180){ + SetX(GetX() - (GetSpeed()*.5)*((180-GetHeading())/90)); + SetZ(GetZ() + (GetSpeed()*.5)*((GetHeading()-90)/90)); + } + else if(GetHeading() >= 0 && GetHeading() < 90){ + SetX(GetX() - (GetSpeed()*.5)*(GetHeading()/90)); + SetZ(GetZ() - (GetSpeed()*.5)*((90-GetHeading())/90)); + } + } +} + +Spawn* Player::GetSpawnByIndex(int16 index){ + Spawn* spawn = 0; + + index_mutex.readlock(__FUNCTION__, __LINE__); + if(player_spawn_id_map.count(index) > 0) + spawn = player_spawn_id_map[index]; + index_mutex.releasereadlock(__FUNCTION__, __LINE__); + + return spawn; +} + +int16 Player::GetIndexForSpawn(Spawn* spawn) { + int16 val = 0; + + index_mutex.readlock(__FUNCTION__, __LINE__); + if(player_spawn_reverse_id_map.count(spawn) > 0) + val = player_spawn_reverse_id_map[spawn]; + index_mutex.releasereadlock(__FUNCTION__, __LINE__); + + return val; +} + +bool Player::WasSpawnRemoved(Spawn* spawn){ + bool wasRemoved = false; + + if(IsRemovingSpawn(spawn->GetID())) + return false; + + spawn_mutex.readlock(__FUNCTION__, __LINE__); + map::iterator itr = spawn_packet_sent.find(spawn_id); + if(itr != spawn_packet_sent.end() && itr->second == SpawnState::SPAWN_STATE_REMOVED) { + wasRemoved = true; + } + spawn_mutex.releasereadlock(__FUNCTION__, __LINE__); + + return wasRemoved; +} + +void Player::RemoveSpawn(Spawn* spawn, bool delete_spawn) +{ + LogWrite(PLAYER__DEBUG, 3, "Player", "Remove Spawn '%s' (%u)", spawn->GetName(), spawn->GetID()); + + SetSpawnSentState(spawn, delete_spawn ? SpawnState::SPAWN_STATE_REMOVING : SpawnState::SPAWN_STATE_REMOVING_SLEEP); + + info_mutex.writelock(__FUNCTION__, __LINE__); + vis_mutex.writelock(__FUNCTION__, __LINE__); + pos_mutex.writelock(__FUNCTION__, __LINE__); + + index_mutex.writelock(__FUNCTION__, __LINE__); + + if (player_spawn_reverse_id_map[spawn] && player_spawn_id_map.count(player_spawn_reverse_id_map[spawn]) > 0) + player_spawn_id_map.erase(player_spawn_reverse_id_map[spawn]); + + if (player_spawn_reverse_id_map.count(spawn) > 0) + player_spawn_reverse_id_map.erase(spawn); + + if (player_spawn_id_map.count(spawn->GetID()) && player_spawn_id_map[spawn->GetID()] == spawn) + player_spawn_id_map.erase(spawn->GetID()); + + int32 id = spawn->GetID(); + if (spawn_info_packet_list.count(id)) + spawn_info_packet_list.erase(id); + + if (spawn_pos_packet_list.count(id)) + spawn_pos_packet_list.erase(id); + + if (spawn_vis_packet_list.count(id)) + spawn_vis_packet_list.erase(id); + + index_mutex.releasewritelock(__FUNCTION__, __LINE__); + + info_mutex.releasewritelock(__FUNCTION__, __LINE__); + pos_mutex.releasewritelock(__FUNCTION__, __LINE__); + vis_mutex.releasewritelock(__FUNCTION__, __LINE__); +} + +vector Player::GetQuestIDs(){ + vector ret; + map::iterator itr; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + for(itr = player_quests.begin(); itr != player_quests.end(); itr++){ + if(itr->second) + ret.push_back(itr->second->GetQuestID()); + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +vector* Player::CheckQuestsItemUpdate(Item* item){ + vector* quest_updates = 0; + map::iterator itr; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + for(itr = player_quests.begin(); itr != player_quests.end(); itr++){ + if(itr->second && itr->second->CheckQuestItemUpdate(item->details.item_id, item->details.count)){ + if(!quest_updates) + quest_updates = new vector(); + quest_updates->push_back(itr->second); + } + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + return quest_updates; +} + +void Player::CheckQuestsCraftUpdate(Item* item, int32 qty){ + map::iterator itr; + vector* update_list = new vector; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + for(itr = player_quests.begin(); itr != player_quests.end(); itr++){ + if(itr->second){ + if(item && qty > 0){ + if(itr->second->CheckQuestRefIDUpdate(item->details.item_id, qty)){ + update_list->push_back(itr->second); + } + } + } + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + if(update_list && update_list->size() > 0){ + Client* client = GetClient(); + if(client){ + for(int8 i=0;isize(); i++){ + client->SendQuestUpdate(update_list->at(i)); + client->SendQuestFailure(update_list->at(i)); + } + } + } + update_list->clear(); + safe_delete(update_list); +} + +void Player::CheckQuestsHarvestUpdate(Item* item, int32 qty){ + map::iterator itr; + vector* update_list = new vector; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + for(itr = player_quests.begin(); itr != player_quests.end(); itr++){ + if(itr->second){ + if(item && qty > 0){ + if(itr->second->CheckQuestRefIDUpdate(item->details.item_id, qty)){ + update_list->push_back(itr->second); + } + } + } + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + if(update_list && update_list->size() > 0){ + Client* client = GetClient(); + if(client){ + for(int8 i=0;isize(); i++){ + client->SendQuestUpdate(update_list->at(i)); + client->SendQuestFailure(update_list->at(i)); + } + } + } + update_list->clear(); + safe_delete(update_list); +} + +vector* Player::CheckQuestsSpellUpdate(Spell* spell) { + vector* quest_updates = 0; + map::iterator itr; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + for (itr = player_quests.begin(); itr != player_quests.end(); itr++){ + if (itr->second && itr->second->CheckQuestSpellUpdate(spell)) { + if (!quest_updates) + quest_updates = new vector(); + quest_updates->push_back(itr->second); + } + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + return quest_updates; +} + +PacketStruct* Player::GetQuestJournalPacket(bool all_quests, int16 version, int32 crc, int32 current_quest_id, bool updated){ + PacketStruct* packet = configReader.getStruct("WS_QuestJournalUpdate", version); + Quest* quest = 0; + if(packet){ + int16 total_quests_num = 0; + int16 total_completed_quests = 0; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + map total_quests = player_quests; + if(all_quests && completed_quests.size() > 0) + total_quests.insert(completed_quests.begin(), completed_quests.end()); + if(total_quests.size() > 0){ + map quest_types; + map::iterator itr; + int16 zone_id = 0; + for(itr = total_quests.begin(); itr != total_quests.end(); itr++){ + if(itr->first && itr->second){ + if(current_quest_id == 0 && itr->second->GetTurnedIn() == false) + current_quest_id = itr->first; + if(itr->second->GetTurnedIn()) + total_completed_quests++; + if(itr->second->GetType()){ + if(quest_types.count(itr->second->GetType()) == 0){ + quest_types[itr->second->GetType()] = zone_id; + zone_id++; + } + } + if(itr->second->GetZone()){ + if(quest_types.count(itr->second->GetZone()) == 0){ + quest_types[itr->second->GetZone()] = zone_id; // Fix #490 - incorrect ordering of quests in journal + zone_id++; + } + } + total_quests_num++; + } + else + continue; + } + packet->setArrayLengthByName("num_quests", total_quests_num); + int16 i = 0; + for(itr = total_quests.begin(); itr != total_quests.end(); itr++){ + if(i == 0 && quest_types.size() > 0){ + packet->setArrayLengthByName("num_quest_zones", quest_types.size()); + map::iterator type_itr; + int16 x = 0; + for(type_itr = quest_types.begin(); type_itr != quest_types.end(); type_itr++){ + packet->setArrayDataByName("quest_zones_zone", type_itr->first.c_str(), x); + packet->setArrayDataByName("quest_zones_zone_id", type_itr->second, x); + x++; + } + } + if(itr->first == 0 || !itr->second) + continue; + if(!all_quests && !itr->second->GetUpdateRequired()) + continue; + quest = itr->second; + if(!quest->GetDeleted()) + packet->setArrayDataByName("active", 1, i); + packet->setArrayDataByName("name", quest->GetName(), i); + packet->setArrayDataByName("quest_type", quest->GetType(), i); + packet->setArrayDataByName("quest_zone", quest->GetZone(), i); + int8 display_status = QUEST_DISPLAY_STATUS_SHOW; + if(itr->second->GetCompleted()) + packet->setArrayDataByName("completed", 1, i); + if(itr->second->GetTurnedIn()){ + packet->setArrayDataByName("turned_in", 1, i); + packet->setArrayDataByName("completed", 1, i); + packet->setArrayDataByName("visible", 1, i); + packet->setArrayDataByName("unknown3", 1, i); + display_status += QUEST_DISPLAY_STATUS_COMPLETED; + } + if (updated) { + packet->setArrayDataByName("quest_updated", 1, i); + packet->setArrayDataByName("journal_updated", 1, i); + } + packet->setArrayDataByName("quest_id", quest->GetQuestID(), i); + packet->setArrayDataByName("day", quest->GetDay(), i); + packet->setArrayDataByName("month", quest->GetMonth(), i); + packet->setArrayDataByName("year", quest->GetYear(), i); + packet->setArrayDataByName("level", quest->GetQuestLevel(), i); + int8 difficulty = 0; + string category = quest->GetType(); + if(category == "Tradeskill") + difficulty = GetTSArrowColor(quest->GetQuestLevel()); + else + difficulty = GetArrowColor(quest->GetQuestLevel()); + packet->setArrayDataByName("difficulty", difficulty, i); + if (itr->second->GetEncounterLevel() > 4) + packet->setArrayDataByName("encounter_level", quest->GetEncounterLevel(), i); + else + packet->setArrayDataByName("encounter_level", 4, i); + if(version >= 931 && quest_types.count(quest->GetType()) > 0) + packet->setArrayDataByName("zonetype_id", quest_types[quest->GetType()], i); + if(version >= 931 && quest_types.count(quest->GetZone()) > 0) + packet->setArrayDataByName("zone_id", quest_types[quest->GetZone()], i); + if(version >= 931 && quest->GetVisible()){ + if (quest->GetCompletedFlag()) + display_status += QUEST_DISPLAY_STATUS_COMPLETE_FLAG; + else if (quest->IsRepeatable()) + display_status += QUEST_DISPLAY_STATUS_REPEATABLE; + if (quest->GetYellowName() || quest->CheckCategoryYellow()) + display_status += QUEST_DISPLAY_STATUS_YELLOW; + + if (quest->IsTracked()) + display_status += QUEST_DISPLAY_STATUS_CHECK; + else + display_status += QUEST_DISPLAY_STATUS_NO_CHECK; + + if (quest->IsHidden() && !quest->GetTurnedIn()) { + display_status += QUEST_DISPLAY_STATUS_HIDDEN; + display_status -= QUEST_DISPLAY_STATUS_SHOW; + } + + if(quest->CanShareQuestCriteria(GetClient(),false)) { + display_status += QUEST_DISPLAY_STATUS_CAN_SHARE; + } + } + else + packet->setArrayDataByName("visible", quest->GetVisible(), i); + if (itr->second->IsRepeatable()) + packet->setArrayDataByName("repeatable", 1, i); + + packet->setArrayDataByName("display_status", display_status, i); + i++; + } + //packet->setDataByName("unknown4", 0); + packet->setDataByName("visible_quest_id", current_quest_id); + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + packet->setDataByName("player_crc", crc); + packet->setDataByName("player_name", GetName()); + packet->setDataByName("used_quests", total_quests_num - total_completed_quests); + packet->setDataByName("max_quests", 75); + + LogWrite(PLAYER__PACKET, 0, "Player", "Dump/Print Packet in func: %s, line: %i", __FUNCTION__, __LINE__); +#if EQDEBUG >= 9 + packet->PrintPacket(); +#endif + } + return packet; +} + +PacketStruct* Player::GetQuestJournalPacket(Quest* quest, int16 version, int32 crc, bool updated) { + if (!quest) + return 0; + + PacketStruct* packet = configReader.getStruct("WS_QuestJournalUpdate", version); + if (packet) { + packet->setArrayLengthByName("num_quests", 1); + packet->setArrayLengthByName("num_quest_zones", 1); + packet->setArrayDataByName("quest_zones_zone", quest->GetType()); + packet->setArrayDataByName("quest_zones_zone_id", 0); + + if(!quest->GetDeleted() && !quest->GetCompleted()) + packet->setArrayDataByName("active", 1); + + packet->setArrayDataByName("name", quest->GetName()); + // don't see these two in the struct + packet->setArrayDataByName("quest_type", quest->GetType()); + packet->setArrayDataByName("quest_zone", quest->GetZone()); + + int8 display_status = QUEST_DISPLAY_STATUS_SHOW; + if(quest->GetCompleted()) + packet->setArrayDataByName("completed", 1); + if(quest->GetTurnedIn()) { + packet->setArrayDataByName("turned_in", 1); + packet->setArrayDataByName("completed", 1); + packet->setArrayDataByName("visible", 1); + display_status += QUEST_DISPLAY_STATUS_COMPLETED; + } + packet->setArrayDataByName("quest_id", quest->GetQuestID()); + packet->setArrayDataByName("day", quest->GetDay()); + packet->setArrayDataByName("month", quest->GetMonth()); + packet->setArrayDataByName("year", quest->GetYear()); + packet->setArrayDataByName("level", quest->GetQuestLevel()); + int8 difficulty = 0; + string category = quest->GetType(); + if(category == "Tradeskill") + difficulty = GetTSArrowColor(quest->GetQuestLevel()); + else + difficulty = GetArrowColor(quest->GetQuestLevel()); + + packet->setArrayDataByName("difficulty", difficulty); + if (quest->GetEncounterLevel() > 4) + packet->setArrayDataByName("encounter_level", quest->GetEncounterLevel()); + else + packet->setArrayDataByName("encounter_level", 4); + + if (version >= 931) { + packet->setArrayDataByName("zonetype_id", 0); + packet->setArrayDataByName("zone_id", 0); + } + if(version >= 931 && quest->GetVisible()){ + if (quest->GetCompletedFlag()) + display_status += QUEST_DISPLAY_STATUS_COMPLETE_FLAG; + else if (quest->IsRepeatable()) + display_status += QUEST_DISPLAY_STATUS_REPEATABLE; + if (quest->GetYellowName() || quest->CheckCategoryYellow()) + display_status += QUEST_DISPLAY_STATUS_YELLOW; + + if (quest->IsTracked()) + display_status += QUEST_DISPLAY_STATUS_CHECK; + else + display_status += QUEST_DISPLAY_STATUS_NO_CHECK; + + if (quest->IsHidden() && !quest->GetTurnedIn()) { + display_status += QUEST_DISPLAY_STATUS_HIDDEN; + display_status -= QUEST_DISPLAY_STATUS_SHOW; + } + + if(quest->CanShareQuestCriteria(GetClient(),false)) { + display_status += QUEST_DISPLAY_STATUS_CAN_SHARE; + } + } + else + packet->setArrayDataByName("visible", quest->GetVisible()); + if (quest->IsRepeatable()) + packet->setArrayDataByName("repeatable", 1); + + packet->setArrayDataByName("display_status", display_status); + if (updated) { + packet->setArrayDataByName("quest_updated", 1); + packet->setArrayDataByName("journal_updated", 1); + } + if(version >= 546) + packet->setDataByName("unknown3", 1); + packet->setDataByName("visible_quest_id", quest->GetQuestID()); + packet->setDataByName("player_crc", crc); + packet->setDataByName("player_name", GetName()); + packet->setDataByName("used_quests", player_quests.size()); + packet->setDataByName("unknown4a", 1); + packet->setDataByName("max_quests", 75); + } + + return packet; +} + +Quest* Player::SetStepComplete(int32 id, int32 step){ + Quest* ret = 0; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + if(player_quests.count(id) > 0){ + if(player_quests[id] && player_quests[id]->SetStepComplete(step)) + ret = player_quests[id]; + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +Quest* Player::AddStepProgress(int32 quest_id, int32 step, int32 progress) { + Quest* ret = 0; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + if (player_quests.count(quest_id) > 0) { + if (player_quests[quest_id] && player_quests[quest_id]->AddStepProgress(step, progress)) + ret = player_quests[quest_id]; + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +int32 Player::GetStepProgress(int32 quest_id, int32 step_id) { + int32 ret = 0; + + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + if (player_quests.count(quest_id) > 0 && player_quests[quest_id]) + ret = player_quests[quest_id]->GetStepProgress(step_id); + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +void Player::RemoveQuest(int32 id, bool delete_quest){ + MPlayerQuests.writelock(__FUNCTION__, __LINE__); + map::iterator itr = player_quests.find(id); + if(itr != player_quests.end()) { + player_quests.erase(itr); + } + + if(delete_quest){ + safe_delete(player_quests[id]); + } + + MPlayerQuests.releasewritelock(__FUNCTION__, __LINE__); + SendQuestRequiredSpawns(id); +} + +vector* Player::CheckQuestsLocationUpdate(){ + vector* quest_updates = 0; + map::iterator itr; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + for(itr = player_quests.begin(); itr != player_quests.end(); itr++){ + if(itr->second && itr->second->CheckQuestLocationUpdate(GetX(), GetY(), GetZ(), (GetZone() ? GetZone()->GetZoneID() : 0))){ + if(!quest_updates) + quest_updates = new vector(); + quest_updates->push_back(itr->second); + } + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + return quest_updates; +} + +vector* Player::CheckQuestsFailures(){ + vector* quest_failures = 0; + map::iterator itr; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + for(itr = player_quests.begin(); itr != player_quests.end(); itr++){ + if(itr->second && itr->second->GetQuestFailures()->size() > 0){ + if(!quest_failures) + quest_failures = new vector(); + quest_failures->push_back(itr->second); + } + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + return quest_failures; +} + +vector* Player::CheckQuestsKillUpdate(Spawn* spawn, bool update){ + vector* quest_updates = 0; + map::iterator itr; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + for(itr = player_quests.begin(); itr != player_quests.end(); itr++){ + if(itr->second && itr->second->CheckQuestKillUpdate(spawn, update)){ + if(!quest_updates) + quest_updates = new vector(); + quest_updates->push_back(itr->second); + } + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + return quest_updates; +} + +bool Player::HasQuestUpdateRequirement(Spawn* spawn){ + bool reqMet = false; + map::iterator itr; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + for(itr = player_quests.begin(); itr != player_quests.end(); itr++){ + if(itr->second && itr->second->CheckQuestReferencedSpawns(spawn)){ + reqMet = true; + break; + } + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + return reqMet; +} + +vector* Player::CheckQuestsChatUpdate(Spawn* spawn){ + vector* quest_updates = 0; + map::iterator itr; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + for(itr = player_quests.begin(); itr != player_quests.end(); itr++){ + if(itr->second && itr->second->CheckQuestChatUpdate(spawn->GetDatabaseID())){ + if(!quest_updates) + quest_updates = new vector(); + quest_updates->push_back(itr->second); + } + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + return quest_updates; +} + +int16 Player::GetTaskGroupStep(int32 quest_id){ + Quest* quest = 0; + int16 step = 0; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + if(player_quests.count(quest_id) > 0){ + quest = player_quests[quest_id]; + if(quest) { + step = quest->GetTaskGroupStep(); + } + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + return step; +} + +bool Player::GetQuestStepComplete(int32 quest_id, int32 step_id){ + bool ret = false; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + if(player_quests.count(quest_id) > 0){ + Quest* quest = player_quests[quest_id]; + if ( quest != NULL ) + ret = quest->GetQuestStepCompleted(step_id); + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +int16 Player::GetQuestStep(int32 quest_id){ + Quest* quest = 0; + int16 step = 0; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + if(player_quests.count(quest_id) > 0){ + quest = player_quests[quest_id]; + if(quest) { + step = quest->GetQuestStep(); + } + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + return step; +} + +map* Player::GetPlayerQuests(){ + return &player_quests; +} + +map* Player::GetCompletedPlayerQuests(){ + return &completed_quests; +} + +Quest* Player::GetAnyQuest(int32 quest_id) { + if(player_quests.count(quest_id) > 0) + return player_quests[quest_id]; + if(completed_quests.count(quest_id) > 0) + return completed_quests[quest_id]; + + return 0; +} +Quest* Player::GetCompletedQuest(int32 quest_id){ + if(completed_quests.count(quest_id) > 0) + return completed_quests[quest_id]; + return 0; +} + +bool Player::HasQuestBeenCompleted(int32 quest_id){ + bool ret = false; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + if(completed_quests.count(quest_id) > 0 && completed_quests[quest_id]) + ret = true; + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +bool Player::HasActiveQuest(int32 quest_id){ + bool ret = false; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + if(player_quests.count(quest_id) > 0 && player_quests[quest_id]) + ret = true; + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +bool Player::HasAnyQuest(int32 quest_id){ + bool ret = false; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + if(player_quests.count(quest_id) > 0) + ret = true; + if(completed_quests.count(quest_id) > 0) + ret = true; + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +int32 Player::GetQuestCompletedCount(int32 quest_id) { + int32 count = 0; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + Quest* quest = GetCompletedQuest(quest_id); + if(quest) { + count = quest->GetCompleteCount(); + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + return count; +} + +Quest* Player::GetQuest(int32 quest_id){ + if(player_quests.count(quest_id) > 0) + return player_quests[quest_id]; + return 0; +} + +void Player::AddCompletedQuest(Quest* quest){ + Quest* existing = GetCompletedQuest(quest->GetQuestID()); + MPlayerQuests.writelock(__FUNCTION__, __LINE__); + completed_quests[quest->GetQuestID()] = quest; + if(existing && existing != quest) { + safe_delete(existing); + } + + quest->SetSaveNeeded(true); + quest->SetTurnedIn(true); + if(quest->GetCompletedDescription()) + quest->SetDescription(string(quest->GetCompletedDescription())); + quest->SetUpdateRequired(true); + MPlayerQuests.releasewritelock(__FUNCTION__, __LINE__); +} + +bool Player::CheckQuestRemoveFlag(Spawn* spawn){ + if(current_quest_flagged.count(spawn) > 0){ + current_quest_flagged.erase(spawn); + return true; + } + return false; +} + +bool Player::CheckQuestRequired(Spawn* spawn){ + if(spawn) + return spawn->MeetsSpawnAccessRequirements(this); + return false; +} + +int8 Player::CheckQuestFlag(Spawn* spawn){ + int8 ret = 0; + + if (!spawn) { + LogWrite(PLAYER__ERROR, 0, "Player", "CheckQuestFlag() called with an invalid spawn"); + return ret; + } + if(spawn->HasProvidedQuests()){ + vector* quests = spawn->GetProvidedQuests(); + Quest* quest = 0; + for(int32 i=0;isize();i++){ + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + if(player_quests.count(quests->at(i)) > 0){ + if(player_quests[quests->at(i)] && player_quests[quests->at(i)]->GetCompleted() && player_quests[quests->at(i)]->GetQuestReturnNPC() == spawn->GetDatabaseID()){ + ret = 2; + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + break; + } + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + int8 flag = 0; + if (CanReceiveQuest(quests->at(i), &flag)){ + if(flag) { + ret = flag; + break; + } + master_quest_list.LockQuests(); + quest = master_quest_list.GetQuest(quests->at(i), false); + master_quest_list.UnlockQuests(); + if(quest){ + int8 color = quest->GetFeatherColor(); + // purple + if (color == 1) + ret = 16; + // green + else if (color == 2) + ret = 32; + // blue + else if (color == 3) + ret = 64; + // normal + else + ret = 1; + break; + } + } + } + } + map::iterator itr; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + for(itr = player_quests.begin(); itr != player_quests.end(); itr++){ + // must make sure the quest ptr is alive or nullptr + if(itr->second && itr->second->CheckQuestChatUpdate(spawn->GetDatabaseID(), false)) + ret = 2; + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + if(ret > 0) + current_quest_flagged[spawn] = true; + return ret; +} + +bool Player::CanReceiveQuest(int32 quest_id, int8* ret){ + bool passed = true; + int32 x; + master_quest_list.LockQuests(); + Quest* quest = master_quest_list.GetQuest(quest_id, false); + master_quest_list.UnlockQuests(); + if (!quest) + passed = false; + //check if quest is already completed, and not repeatable + else if (HasQuestBeenCompleted(quest_id) && !quest->IsRepeatable()) + passed = false; + //check if the player already has this quest + else if (player_quests.count(quest_id) > 0) + passed = false; + //Check Prereq Adv Levels + else if (quest->GetPrereqLevel() > GetLevel()) + passed = false; + else if (quest->GetPrereqMaxLevel() > 0){ + if (GetLevel() > quest->GetPrereqMaxLevel()) + passed = false; + } + //Check Prereq TS Levels + else if (quest->GetPrereqTSLevel() > GetTSLevel()) + passed = false; + else if (quest->GetPrereqMaxTSLevel() > 0){ + if (GetTSLevel() > quest->GetPrereqMaxLevel()) + passed = false; + } + + + // Check quest pre req + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + vector* prereq_quests = quest->GetPrereqQuests(); + if(passed && prereq_quests && prereq_quests->size() > 0){ + for(int32 x=0;xsize();x++){ + if(completed_quests.count(prereq_quests->at(x)) == 0){ + passed = false; + break; + } + } + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + + //Check Prereq Classes + vector* prereq_classes = quest->GetPrereqClasses(); + if(passed && prereq_classes && prereq_classes->size() > 0){ + for(int32 x=0;xsize();x++){ + if(prereq_classes->at(x) == GetAdventureClass()){ + passed = true; + break; + } + else + passed = false; + } + } + + //Check Prereq TS Classes + vector* prereq_tsclasses = quest->GetPrereqTradeskillClasses(); + if(passed && prereq_tsclasses && prereq_tsclasses->size() > 0){ + for( x=0;xsize();x++){ + if(prereq_tsclasses->at(x) == GetTradeskillClass()){ + passed = true; + break; + } + else + passed = false; + } + } + + + // Check model prereq + vector* prereq_model_types = quest->GetPrereqModelTypes(); + if(passed && prereq_model_types && prereq_model_types->size() > 0){ + for(x=0;xsize();x++){ + if(prereq_model_types->at(x) == GetModelType()){ + passed = true; + break; + } + else + passed = false; + } + } + + + // Check faction pre req + vector* prereq_factions = quest->GetPrereqFactions(); + if(passed && prereq_factions && prereq_factions->size() > 0){ + sint32 val = 0; + for(x=0;xsize();x++){ + val = GetFactions()->GetFactionValue(prereq_factions->at(x).faction_id); + if(val >= prereq_factions->at(x).min && (prereq_factions->at(x).max == 0 || val <= prereq_factions->at(x).max)){ + passed = true; + break; + } + else + passed = false; + } + } + + LogWrite(MISC__TODO, 1, "TODO", "Check prereq items\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__); + + // Check race pre req + vector* prereq_races = quest->GetPrereqRaces(); + if(passed && prereq_races && prereq_races->size() > 0){ + for(x=0;xsize();x++){ + if(prereq_races->at(x) == GetRace()){ + passed = true; + break; + } + else + passed = false; + } + } + + int32 flag = 0; + if(lua_interface->CallQuestFunction(quest, "ReceiveQuestCriteria", this, 0xFFFFFFFF, &flag)) { + if(ret) + *ret = flag; + if(!flag) { + passed = false; + } + else { + passed = true; + } + } + + return passed; +} + +bool Player::UpdateQuestReward(int32 quest_id, QuestRewardData* qrd) { + if(!GetClient()) + return false; + + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + Quest* quest = GetAnyQuest(quest_id); + + if(!quest) { + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + return false; + } + + quest->SetQuestTemporaryState(qrd->is_temporary, qrd->description); + if(qrd->is_temporary) { + quest->SetStatusTmpReward(qrd->tmp_status); + quest->SetCoinTmpReward(qrd->tmp_coin); + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + + GetClient()->GiveQuestReward(quest, qrd->has_displayed); + SetActiveReward(true); + + return true; +} + + +Quest* Player::PendingQuestAcceptance(int32 quest_id, int32 item_id, bool* quest_exists) { + vector* items = 0; + bool ret = false; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + Quest* quest = GetAnyQuest(quest_id); + if(!quest) { + if(quest_exists) { + *quest_exists = false; + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + return nullptr; + } + + if(quest_exists) { + *quest_exists = true; + } + if(quest->GetQuestTemporaryState()) + items = quest->GetTmpRewardItems(); + else + items = quest->GetRewardItems(); + if (item_id == 0) { + ret = true; + } + else { + items = quest->GetSelectableRewardItems(); + if (items && items->size() > 0) { + for (int32 i = 0; i < items->size(); i++) { + if (items->at(i)->details.item_id == item_id) { + ret = true; + break; + } + } + } + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + + return quest; +} + + +bool Player::AcceptQuestReward(int32 item_id, int32 selectable_item_id) { + if(!GetClient()) { + return false; + } + + Collection *collection = 0; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + Quest* quest = client->GetPendingQuestAcceptance(item_id); + if(quest){ + GetClient()->AcceptQuestReward(quest, item_id); + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + return true; + } + bool collectedItems = false; + if (client->GetPlayer()->HasPendingItemRewards()) { + vector items = client->GetPlayer()->GetPendingItemRewards(); + if (items.size() > 0) { + collectedItems = true; + for (int i = 0; i < items.size(); i++) { + client->GetPlayer()->AddItem(new Item(items[i])); + } + client->GetPlayer()->ClearPendingItemRewards(); + client->GetPlayer()->SetActiveReward(false); + } + map selectable_item = client->GetPlayer()->GetPendingSelectableItemReward(item_id); + if (selectable_item.size() > 0) { + collectedItems = true; + map::iterator itr; + for (itr = selectable_item.begin(); itr != selectable_item.end(); itr++) { + client->GetPlayer()->AddItem(new Item(itr->second)); + client->GetPlayer()->ClearPendingSelectableItemRewards(itr->first); + } + client->GetPlayer()->SetActiveReward(false); + } + } + else if (collection = GetPendingCollectionReward()) + { + client->AcceptCollectionRewards(collection, (selectable_item_id > 0) ? selectable_item_id : item_id); + collectedItems = true; + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + + return collectedItems; +} + + +bool Player::SendQuestStepUpdate(int32 quest_id, int32 quest_step_id, bool display_quest_helper) { + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + Quest* quest = GetAnyQuest(quest_id); + if(!quest) { + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + return false; + } + + QuestStep* quest_step = quest->GetQuestStep(quest_step_id); + if (quest_step) { + if(GetClient()) { + GetClient()->QueuePacket(quest->QuestJournalReply(GetClient()->GetVersion(), GetClient()->GetNameCRC(), this, quest_step, 1, false, false, display_quest_helper)); + } + quest_step->WasUpdated(false); + } + bool turnedIn = quest->GetTurnedIn(); + + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + + if(turnedIn && GetClient()) //update the journal so the old quest isn't the one displayed in the client's quest helper + GetClient()->SendQuestJournal(); + + return true; +} + +void Player::SendQuest(int32 quest_id) { + if(!GetClient()) { + return; + } + + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + Quest* quest = GetQuest(quest_id); + if (quest) + GetClient()->QueuePacket(quest->QuestJournalReply(GetClient()->GetVersion(), GetClient()->GetNameCRC(), this)); + else { + quest = GetCompletedQuest(quest_id); + + if (quest) + GetClient()->QueuePacket(quest->QuestJournalReply(GetClient()->GetVersion(), GetClient()->GetNameCRC(), this, 0, 1, true)); + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); +} + + +void Player::UpdateQuestCompleteCount(int32 quest_id) { + if(!GetClient()) { + return; + } + + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + // If character has already completed this quest once update the given date in the database + Quest* quest = GetQuest(id); + Quest* quest2 = GetCompletedQuest(id); + if (quest && quest2) { + quest->SetCompleteCount(quest2->GetCompleteCount()); + database.SaveCharRepeatableQuest(GetClient(), id, quest->GetCompleteCount()); + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); +} + +void Player::GetQuestTemporaryRewards(int32 quest_id, std::vector* items) { + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + Quest* quest = GetAnyQuest(quest_id); + if(quest) { + quest->GetTmpRewardItemsByID(items); + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); +} + +void Player::AddQuestTemporaryReward(int32 quest_id, int32 item_id, int16 item_count) { + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + Quest* quest = GetAnyQuest(quest_id); + if(quest) { + Item* item = master_item_list.GetItem(item_id); + if(item) { + Item* tmpItem = new Item(item); + tmpItem->details.count = item_count; + quest->AddTmpRewardItem(tmpItem); + } + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); +} + +bool Player::ShouldSendSpawn(Spawn* spawn){ + if(spawn->IsDeletedSpawn() || IsSendingSpawn(spawn->GetID()) || IsRemovingSpawn(spawn->GetID())) + return false; + else if((WasSentSpawn(spawn->GetID()) == false) && (!spawn->IsPrivateSpawn() || spawn->AllowedAccess(this))) + return true; + + return false; +} + +int8 Player::GetTSArrowColor(int8 level){ + int8 color = 0; + sint16 diff = level - GetTSLevel(); + if(GetLevel() < 10) + diff *= 3; + else if(GetLevel() <= 20) + diff *= 2; + if(diff >= 9) + color = ARROW_COLOR_RED; + else if(diff >= 5) + color = ARROW_COLOR_ORANGE; + else if(diff >= 1) + color = ARROW_COLOR_YELLOW; + else if(diff == 0) + color = ARROW_COLOR_WHITE; + else if(diff <= -11) + color = ARROW_COLOR_GRAY; + else if(diff <= -6) + color = ARROW_COLOR_GREEN; + else //if(diff < 0) + color = ARROW_COLOR_BLUE; + return color; +} + +void Player::AddCoins(int64 val){ + int32 tmp = 0; + UpdatePlayerStatistic(STAT_PLAYER_TOTAL_WEALTH, (GetCoinsCopper() + (GetCoinsSilver() * 100) + (GetCoinsGold() * 10000) + (GetCoinsPlat() * 1000000)) + val, true); + if(val >= 1000000){ + tmp = val / 1000000; + val -= tmp*1000000; + GetInfoStruct()->add_coin_plat(tmp); + } + if(val >= 10000){ + tmp = val / 10000; + val -= tmp*10000; + GetInfoStruct()->add_coin_gold(tmp); + } + if(val >= 100){ + tmp = val / 100; + val -= tmp*100; + GetInfoStruct()->add_coin_silver(tmp); + } + GetInfoStruct()->add_coin_copper(val); + int32 new_copper_value = GetInfoStruct()->get_coin_copper(); + if(new_copper_value >= 100){ + tmp = new_copper_value/100; + GetInfoStruct()->set_coin_copper(new_copper_value - (100 * tmp)); + GetInfoStruct()->add_coin_silver(tmp); + } + int32 new_silver_value = GetInfoStruct()->get_coin_silver(); + if(new_silver_value >= 100){ + tmp = new_silver_value/100; + GetInfoStruct()->set_coin_silver(new_silver_value - (100 * tmp)); + GetInfoStruct()->add_coin_gold(tmp); + } + int32 new_gold_value = GetInfoStruct()->get_coin_gold(); + if(new_gold_value >= 100){ + tmp = new_gold_value/100; + GetInfoStruct()->set_coin_gold(new_gold_value - (100 * tmp)); + GetInfoStruct()->add_coin_plat(tmp); + } + charsheet_changed = true; +} + +bool Player::RemoveCoins(int64 val){ + int64 total_coins = GetInfoStruct()->get_coin_plat()*1000000; + total_coins += GetInfoStruct()->get_coin_gold()*10000; + total_coins += GetInfoStruct()->get_coin_silver()*100; + total_coins += GetInfoStruct()->get_coin_copper(); + if(total_coins >= val){ + total_coins -= val; + GetInfoStruct()->set_coin_plat(0); + GetInfoStruct()->set_coin_gold(0); + GetInfoStruct()->set_coin_silver(0); + GetInfoStruct()->set_coin_copper(0); + AddCoins(total_coins); + return true; + } + return false; +} + +bool Player::HasCoins(int64 val) { + int64 total_coins = GetInfoStruct()->get_coin_plat()*1000000; + total_coins += GetInfoStruct()->get_coin_gold()*10000; + total_coins += GetInfoStruct()->get_coin_silver()*100; + total_coins += GetInfoStruct()->get_coin_copper(); + if(total_coins >= val) + return true; + + return false; +} + +bool Player::HasPendingLootItems(int32 id){ + return (pending_loot_items.count(id) > 0 && pending_loot_items[id].size() > 0); +} + +bool Player::HasPendingLootItem(int32 id, int32 item_id){ + return (pending_loot_items.count(id) > 0 && pending_loot_items[id].count(item_id) > 0); +} +vector* Player::GetPendingLootItems(int32 id){ + vector* ret = 0; + if(pending_loot_items.count(id) > 0){ + ret = new vector(); + map::iterator itr; + for(itr = pending_loot_items[id].begin(); itr != pending_loot_items[id].end(); itr++){ + if(master_item_list.GetItem(itr->first)) + ret->push_back(master_item_list.GetItem(itr->first)); + } + } + return ret; +} + +void Player::RemovePendingLootItem(int32 id, int32 item_id){ + if(pending_loot_items.count(id) > 0){ + pending_loot_items[id].erase(item_id); + if(pending_loot_items[id].size() == 0) + pending_loot_items.erase(id); + } +} + +void Player::RemovePendingLootItems(int32 id){ + if(pending_loot_items.count(id) > 0) + pending_loot_items.erase(id); +} + +void Player::AddPendingLootItems(int32 id, vector* items){ + if(items){ + Item* item = 0; + for(int32 i=0;isize();i++){ + item = items->at(i); + if(item) + pending_loot_items[id][item->details.item_id] = true; + } + } +} + +void Player::AddPlayerStatistic(int32 stat_id, sint32 stat_value, int32 stat_date) { + if (statistics.count(stat_id) == 0) { + Statistic* stat = new Statistic; + stat->stat_id = stat_id; + stat->stat_value = stat_value; + stat->stat_date = stat_date; + stat->save_needed = false; + statistics[stat_id] = stat; + } +} + +void Player::UpdatePlayerStatistic(int32 stat_id, sint32 stat_value, bool overwrite) { + if (statistics.count(stat_id) == 0) + AddPlayerStatistic(stat_id, 0, 0); + Statistic* stat = statistics[stat_id]; + overwrite == true ? stat->stat_value = stat_value : stat->stat_value += stat_value; + stat->stat_date = Timer::GetUnixTimeStamp(); + stat->save_needed = true; +} + +void Player::WritePlayerStatistics() { + map::iterator stat_itr; + for (stat_itr = statistics.begin(); stat_itr != statistics.end(); stat_itr++) { + Statistic* stat = stat_itr->second; + if (stat->save_needed) { + stat->save_needed = false; + database.WritePlayerStatistic(this, stat); + } + } +} + +sint64 Player::GetPlayerStatisticValue(int32 stat_id) { + if (stat_id >= 0 && statistics.count(stat_id) > 0) + return statistics[stat_id]->stat_value; + return 0; +} + +void Player::RemovePlayerStatistics() { + map::iterator stat_itr; + for (stat_itr = statistics.begin(); stat_itr != statistics.end(); stat_itr++) + safe_delete(stat_itr->second); + statistics.clear(); +} + +void Player::SetGroup(PlayerGroup* new_group){ + group = new_group; +} + +/*PlayerGroup* Player::GetGroup(){ + return group; +}*/ + +bool Player::IsGroupMember(Entity* player) { + bool ret = false; + if (GetGroupMemberInfo() && player) { + //world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + ret = world.GetGroupManager()->IsInGroup(GetGroupMemberInfo()->group_id, player); + + /*deque::iterator itr; + deque* members = world.GetGroupManager()->GetGroupMembers(GetGroupMemberInfo()->group_id); + for (itr = members->begin(); itr != members->end(); itr++) { + GroupMemberInfo* gmi = *itr; + if (gmi->client && gmi->client->GetPlayer() == player) { + ret = true; + break; + } + } + + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__);*/ + } + return ret; +} + + + + + +void Player::SetGroupInformation(PacketStruct* packet){ + int8 det_count = 0; + Entity* member = 0; + + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + if (GetGroupMemberInfo()) { + PlayerGroup* group = world.GetGroupManager()->GetGroup(GetGroupMemberInfo()->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + deque::iterator itr; + GroupMemberInfo* info = 0; + int x = 0; + + for (itr = members->begin(); itr != members->end(); itr++) { + info = *itr; + + if (info == GetGroupMemberInfo()) { + if (info->leader) + packet->setDataByName("group_leader_id", 0xFFFFFFFF); // If this player is the group leader then fill this element with FF FF FF FF + + continue; + } + else { + if (info->leader) + packet->setDataByName("group_leader_id", x); // If leader is some one else then fill with the slot number they are in + } + + member = info->member; + + if (member && member->GetZone() == GetZone()) { + packet->setSubstructDataByName("group_members", "spawn_id", GetIDWithPlayerSpawn(member), x); + + if (member->HasPet()) { + if (member->GetPet()) + packet->setSubstructDataByName("group_members", "pet_id", GetIDWithPlayerSpawn(member->GetPet()), x); + else + packet->setSubstructDataByName("group_members", "pet_id", GetIDWithPlayerSpawn(member->GetCharmedPet()), x); + } + else + packet->setSubstructDataByName("group_members", "pet_id", 0xFFFFFFFF, x); + + //Send detriment counts as 255 if all dets of that type are incurable + det_count = member->GetTraumaCount(); + if (det_count > 0) { + if (!member->HasCurableDetrimentType(DET_TYPE_TRAUMA)) + det_count = 255; + } + packet->setSubstructDataByName("group_members", "trauma_count", det_count, x); + + det_count = member->GetArcaneCount(); + if (det_count > 0) { + if (!member->HasCurableDetrimentType(DET_TYPE_ARCANE)) + det_count = 255; + } + packet->setSubstructDataByName("group_members", "arcane_count", det_count, x); + + det_count = member->GetNoxiousCount(); + if (det_count > 0) { + if (!member->HasCurableDetrimentType(DET_TYPE_NOXIOUS)) + det_count = 255; + } + packet->setSubstructDataByName("group_members", "noxious_count", det_count, x); + + det_count = member->GetElementalCount(); + if (det_count > 0) { + if (!member->HasCurableDetrimentType(DET_TYPE_ELEMENTAL)) + det_count = 255; + } + packet->setSubstructDataByName("group_members", "elemental_count", det_count, x); + + det_count = member->GetCurseCount(); + if (det_count > 0) { + if (!member->HasCurableDetrimentType(DET_TYPE_CURSE)) + det_count = 255; + } + packet->setSubstructDataByName("group_members", "curse_count", det_count, x); + + packet->setSubstructDataByName("group_members", "zone_status", 1, x); + } + else { + packet->setSubstructDataByName("group_members", "pet_id", 0xFFFFFFFF, x); + //packet->setSubstructDataByName("group_members", "unknown5", 1, x, 1); // unknown5 > 1 = name is blue + packet->setSubstructDataByName("group_members", "zone_status", 2, x); + } + + packet->setSubstructDataByName("group_members", "name", info->name.c_str(), x); + packet->setSubstructDataByName("group_members", "hp_current", info->hp_current, x); + packet->setSubstructDataByName("group_members", "hp_max", info->hp_max, x); + packet->setSubstructDataByName("group_members", "power_current", info->power_current, x); + packet->setSubstructDataByName("group_members", "power_max", info->power_max, x); + packet->setSubstructDataByName("group_members", "level_current", info->level_current, x); + packet->setSubstructDataByName("group_members", "level_max", info->level_max, x); + packet->setSubstructDataByName("group_members", "zone", info->zone.c_str(), x); + packet->setSubstructDataByName("group_members", "race_id", info->race_id, x); + packet->setSubstructDataByName("group_members", "class_id", info->class_id, x); + + x++; + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + //packet->PrintPacket(); + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); +} + +PlayerItemList* Player::GetPlayerItemList(){ + return &item_list; +} + +void Player::ResetSavedSpawns(){ + spawn_mutex.writelock(__FUNCTION__, __LINE__); + ClearRemovalTimers(); + spawn_packet_sent.clear(); + spawn_mutex.releasewritelock(__FUNCTION__, __LINE__); + + info_mutex.writelock(__FUNCTION__, __LINE__); + spawn_info_packet_list.clear(); + info_mutex.releasewritelock(__FUNCTION__, __LINE__); + + vis_mutex.writelock(__FUNCTION__, __LINE__); + spawn_vis_packet_list.clear(); + vis_mutex.releasewritelock(__FUNCTION__, __LINE__); + + pos_mutex.writelock(__FUNCTION__, __LINE__); + spawn_pos_packet_list.clear(); + pos_mutex.releasewritelock(__FUNCTION__, __LINE__); + + index_mutex.writelock(__FUNCTION__, __LINE__); + player_spawn_reverse_id_map.clear(); + player_spawn_id_map.clear(); + player_spawn_id_map[1] = this; + player_spawn_reverse_id_map[this] = 1; + spawn_index = 1; + index_mutex.releasewritelock(__FUNCTION__, __LINE__); + + m_playerSpawnQuestsRequired.writelock(__FUNCTION__, __LINE__); + player_spawn_quests_required.clear(); + m_playerSpawnQuestsRequired.releasewritelock(__FUNCTION__, __LINE__); + if(info) + info->RemoveOldPackets(); + + safe_delete_array(movement_packet); + safe_delete_array(old_movement_packet); +} + +void Player::SetReturningFromLD(bool val){ + std::unique_lock lock(spell_packet_update_mutex); + if(val && val != returning_from_ld) + { + if(GetPlayerItemList()) + GetPlayerItemList()->ResetPackets(); + + GetEquipmentList()->ResetPackets(); + GetAppearanceEquipmentList()->ResetPackets(); + skill_list.ResetPackets(); + safe_delete_array(spell_orig_packet); + safe_delete_array(spell_xor_packet); + spell_orig_packet=0; + spell_xor_packet=0; + spell_count = 0; + + reset_character_flag(CF_IS_SITTING); + if (GetActivityStatus() & ACTIVITY_STATUS_CAMPING) + SetActivityStatus(GetActivityStatus() - ACTIVITY_STATUS_CAMPING); + + if (GetActivityStatus() & ACTIVITY_STATUS_LINKDEAD) + SetActivityStatus(GetActivityStatus() - ACTIVITY_STATUS_LINKDEAD); + + SetTempVisualState(0); + + safe_delete_array(spawn_tmp_info_xor_packet); + safe_delete_array(spawn_tmp_vis_xor_packet); + safe_delete_array(spawn_tmp_pos_xor_packet); + spawn_tmp_info_xor_packet = 0; + spawn_tmp_vis_xor_packet = 0; + spawn_tmp_pos_xor_packet = 0; + pos_xor_size = 0; + info_xor_size = 0; + vis_xor_size = 0; + + index_mutex.writelock(__FUNCTION__, __LINE__); + player_spawn_id_map[1] = this; + player_spawn_reverse_id_map[this] = 1; + spawn_index = 1; + index_mutex.releasewritelock(__FUNCTION__, __LINE__); + } + + returning_from_ld = val; +} + +bool Player::IsReturningFromLD(){ + return returning_from_ld; +} + +void Player::AddFriend(const char* name, bool save){ + if(name){ + if(save) + friend_list[name] = 1; + else + friend_list[name] = 0; + } +} + +bool Player::IsFriend(const char* name){ + if(name && friend_list.count(name) > 0 && friend_list[name] < 2) + return true; + return false; +} + +void Player::RemoveFriend(const char* name){ + if(friend_list.count(name) > 0) + friend_list[name] = 2; +} + +map* Player::GetFriends(){ + return &friend_list; +} + +void Player::AddIgnore(const char* name, bool save){ + if(name){ + if(save) + ignore_list[name] = 1; + else + ignore_list[name] = 0; + } +} + +bool Player::IsIgnored(const char* name){ + if(name && ignore_list.count(name) > 0 && ignore_list[name] < 2) + return true; + return false; +} + +void Player::RemoveIgnore(const char* name){ + if(name && ignore_list.count(name) > 0) + ignore_list[name] = 2; +} + +map* Player::GetIgnoredPlayers(){ + return &ignore_list; +} + +bool Player::CheckLevelStatus(int16 new_level) { + int16 LevelCap = rule_manager.GetGlobalRule(R_Player, MaxLevel)->GetInt16(); + int16 LevelCapOverrideStatus = rule_manager.GetGlobalRule(R_Player, MaxLevelOverrideStatus)->GetInt16(); + if ( GetClient() && (LevelCap < new_level) && (LevelCapOverrideStatus > GetClient()->GetAdminStatus()) ) + return false; + return true; +} + +Skill* Player::GetSkillByName(const char* name, bool check_update){ + Skill* ret = skill_list.GetSkillByName(name); + if(check_update) + { + if(skill_list.CheckSkillIncrease(ret)) + CalculateBonuses(); + } + return ret; +} + +Skill* Player::GetSkillByID(int32 id, bool check_update){ + Skill* ret = skill_list.GetSkill(id); + if(check_update) + { + if(skill_list.CheckSkillIncrease(ret)) + CalculateBonuses(); + } + return ret; +} + +void Player::SetRangeAttack(bool val){ + range_attack = val; +} + +bool Player::GetRangeAttack(){ + return range_attack; +} + +bool Player::AddMail(Mail* mail) { + bool ret = false; + if (mail) { + mail_list.Put(mail->mail_id, mail); + ret = true; + } + return ret; +} + +MutexMap* Player::GetMail() { + return &mail_list; +} + +Mail* Player::GetMail(int32 mail_id) { + Mail* mail = 0; + if (mail_list.count(mail_id) > 0) + mail = mail_list.Get(mail_id); + return mail; +} + +void Player::DeleteMail(bool from_database) { + MutexMap::iterator itr = mail_list.begin(); + while (itr.Next()) + DeleteMail(itr->first, from_database); + mail_list.clear(); +} + +void Player::DeleteMail(int32 mail_id, bool from_database) { + if (mail_list.count(mail_id) > 0) { + if (from_database) + database.DeletePlayerMail(mail_list.Get(mail_id)); + mail_list.erase(mail_id, true, true); // need to delete the mail ptr + } +} + +ZoneServer* Player::GetGroupMemberInZone(int32 zone_id) { + ZoneServer* ret = nullptr; + + GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo(); + // If the player has a group and destination zone id + if (gmi && zone_id) { + deque::iterator itr; + + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + + PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + // Loop through the group members + for (itr = members->begin(); itr != members->end(); itr++) { + // If a group member matches a target + if ((*itr)->member && (*itr)->member != this && (*itr)->member->GetZone() && (*itr)->member->GetZone()->GetInstanceID() > 0 && + (*itr)->member->GetZone()->GetZoneID() == zone_id) { + // toggle the flag and break the loop + ret = (*itr)->member->GetZone(); + break; + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } + return ret; +} + + +/* CharacterInstances */ + +CharacterInstances::CharacterInstances() { + m_instanceList.SetName("Mutex::m_instanceList"); +} + +CharacterInstances::~CharacterInstances() { + RemoveInstances(); +} + +void CharacterInstances::AddInstance(int32 db_id, int32 instance_id, int32 last_success_timestamp, int32 last_failure_timestamp, int32 success_lockout_time, int32 failure_lockout_time, int32 zone_id, int8 zone_instancetype, string zone_name) { + InstanceData data; + data.db_id = db_id; + data.instance_id = instance_id; + data.zone_id = zone_id; + data.zone_instance_type = zone_instancetype; + data.zone_name = zone_name; + data.last_success_timestamp = last_success_timestamp; + data.last_failure_timestamp = last_failure_timestamp; + data.success_lockout_time = success_lockout_time; + data.failure_lockout_time = failure_lockout_time; + instanceList.push_back(data); +} + +void CharacterInstances::RemoveInstances() { + instanceList.clear(); +} + +bool CharacterInstances::RemoveInstanceByZoneID(int32 zone_id) { + vector::iterator itr; + m_instanceList.writelock(__FUNCTION__, __LINE__); + for(itr = instanceList.begin(); itr != instanceList.end(); itr++) { + InstanceData* data = &(*itr); + if (data->zone_id == zone_id) { + instanceList.erase(itr); + m_instanceList.releasewritelock(__FUNCTION__, __LINE__); + return true; + } + } + m_instanceList.releasewritelock(__FUNCTION__, __LINE__); + return false; +} + +bool CharacterInstances::RemoveInstanceByInstanceID(int32 instance_id) { + vector::iterator itr; + m_instanceList.writelock(__FUNCTION__, __LINE__); + for(itr = instanceList.begin(); itr != instanceList.end(); itr++) { + InstanceData* data = &(*itr); + if (data->instance_id == instance_id) { + instanceList.erase(itr); + m_instanceList.releasewritelock(__FUNCTION__, __LINE__); + return true; + } + } + m_instanceList.releasewritelock(__FUNCTION__, __LINE__); + return false; +} + +InstanceData* CharacterInstances::FindInstanceByZoneID(int32 zone_id) { + m_instanceList.readlock(__FUNCTION__, __LINE__); + for(int32 i = 0; i < instanceList.size(); i++) { + InstanceData* data = &instanceList.at(i); + if (data->zone_id == zone_id) { + m_instanceList.releasereadlock(__FUNCTION__, __LINE__); + return data; + } + } + m_instanceList.releasereadlock(__FUNCTION__, __LINE__); + + return 0; +} + +InstanceData* CharacterInstances::FindInstanceByDBID(int32 db_id) { + m_instanceList.readlock(__FUNCTION__, __LINE__); + for(int32 i = 0; i < instanceList.size(); i++) { + InstanceData* data = &instanceList.at(i); + if (data->db_id == db_id) { + m_instanceList.releasereadlock(__FUNCTION__, __LINE__); + return data; + } + } + m_instanceList.releasereadlock(__FUNCTION__, __LINE__); + + return 0; +} + +InstanceData* CharacterInstances::FindInstanceByInstanceID(int32 instance_id) { + m_instanceList.readlock(__FUNCTION__, __LINE__); + for(int32 i = 0; i < instanceList.size(); i++) { + InstanceData* data = &instanceList.at(i); + if (data->instance_id == instance_id) { + m_instanceList.releasereadlock(__FUNCTION__, __LINE__); + return data; + } + } + m_instanceList.releasereadlock(__FUNCTION__, __LINE__); + + return 0; +} +vector CharacterInstances::GetLockoutInstances() { + vector ret; + m_instanceList.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < instanceList.size(); i++) { + InstanceData* data = &instanceList.at(i); + if (data->zone_instance_type == SOLO_LOCKOUT_INSTANCE || data->zone_instance_type == GROUP_LOCKOUT_INSTANCE || data->zone_instance_type == RAID_LOCKOUT_INSTANCE) + ret.push_back(*data); + } + m_instanceList.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +vector CharacterInstances::GetPersistentInstances() { + vector ret; + m_instanceList.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < instanceList.size(); i++) { + InstanceData* data = &instanceList.at(i); + if (data->zone_instance_type == SOLO_PERSIST_INSTANCE || data->zone_instance_type == GROUP_PERSIST_INSTANCE || data->zone_instance_type == RAID_PERSIST_INSTANCE) + ret.push_back(*data); + } + m_instanceList.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +void CharacterInstances::ProcessInstanceTimers(Player* player) { + + // Only need to check persistent instances here, lockout should be handled by zone shutting down + + // Check instance id, if > 0 check timers, if timers expired set instance id to 0 and update the db `character_instance` to instance id 0, + // delete instance from `instances` if no more characters assigned to it + + m_instanceList.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < instanceList.size(); i++) { + InstanceData* data = &instanceList.at(i); + + // Check to see if we have a valid instance and if it is persistant + if (data->instance_id > 0) { + + if (data->zone_instance_type == SOLO_PERSIST_INSTANCE || data->zone_instance_type == GROUP_PERSIST_INSTANCE || data->zone_instance_type == RAID_PERSIST_INSTANCE) { + // Check max duration (last success + success time) + if (Timer::GetUnixTimeStamp() >= (data->last_success_timestamp + data->success_lockout_time)) { + // Max duration has passed, instance has expired lets remove the player from it, + // this will also delete the instace if all players have been removed from it + database.DeleteCharacterFromInstance(player->GetCharacterID(), data->instance_id); + data->instance_id = 0; + } + } + + if (data->zone_instance_type == SOLO_LOCKOUT_INSTANCE || data->zone_instance_type == GROUP_LOCKOUT_INSTANCE || data->zone_instance_type == RAID_LOCKOUT_INSTANCE) { + // Need to check lockout instance ids to ensure they are still valid. + if (!database.VerifyInstanceID(player->GetCharacterID(), data->instance_id)) + data->instance_id = 0; + } + } + } + m_instanceList.releasereadlock(__FUNCTION__, __LINE__); + + /*for(int32 i=0;isize();i++) + { + bool valuesUpdated = false; + InstanceData data = instanceList->at(i); + if ( data.grant_reenter_time_left > 0 ) + { + if ( ( data.grant_reenter_time_left - msDiff ) < 1 ) + data.grant_reenter_time_left = 0; + else + data.grant_reenter_time_left -= msDiff; + + valuesUpdated = true; + } + if ( data.grant_reset_time_left > 0 ) + { + if ( ( data.grant_reset_time_left - msDiff ) < 1 ) + data.grant_reset_time_left = 0; + else + data.grant_reset_time_left -= msDiff; + + valuesUpdated = true; + } + if ( data.lockout_time > 0 ) + { + if ( ( data.lockout_time - msDiff ) < 1 ) + { + data.lockout_time = 0; + // this means that their timer ran out and we need to clear it from db and player + RemoveInstanceByInstanceID(data.instance_id); + database.DeleteCharacterFromInstance(player->GetCharacterID(),data.instance_id); + } + else + data.lockout_time -= msDiff; + + valuesUpdated = true; + } + + if ( valuesUpdated ) + database.UpdateCharacterInstanceTimers(player->GetCharacterID(),data.instance_id,data.lockout_time,data.grant_reset_time_left,data.grant_reenter_time_left); + }*/ +} + +int32 CharacterInstances::GetInstanceCount() { + return instanceList.size(); +} + +void Player::SetPlayerAdventureClass(int8 new_class){ + SetAdventureClass(new_class); + GetInfoStruct()->set_class1(classes.GetBaseClass(new_class)); + GetInfoStruct()->set_class2(classes.GetSecondaryBaseClass(new_class)); + GetInfoStruct()->set_class3(new_class); + charsheet_changed = true; + if(GetZone()) + GetZone()->TriggerCharSheetTimer(); +} + +void Player::AddSkillBonus(int32 spell_id, int32 skill_id, float value) { + GetSkills()->AddSkillBonus(spell_id, skill_id, value); +} + +SkillBonus* Player::GetSkillBonus(int32 spell_id) { + return GetSkills()->GetSkillBonus(spell_id); +} + +void Player::RemoveSkillBonus(int32 spell_id) { + GetSkills()->RemoveSkillBonus(spell_id); +} + +bool Player::HasFreeBankSlot() { + return item_list.HasFreeBankSlot(); +} + +int8 Player::FindFreeBankSlot() { + return item_list.FindFreeBankSlot(); +} + +void Player::AddTitle(sint32 title_id, const char *name, int8 prefix, bool save_needed){ + Title* new_title = new Title; + new_title->SetID(title_id); + new_title->SetName(name); + new_title->SetPrefix(prefix); + new_title->SetSaveNeeded(save_needed); + player_titles_list.Add(new_title); +} + +void Player::AddAAEntry(int16 template_id, int8 tab_id, int32 aa_id, int16 order,int8 treeid) { + + + +} +void Player::AddLanguage(int32 id, const char *name, bool save_needed){ + Skill* skill = master_skill_list.GetSkillByName(name); + if(skill && !GetSkills()->HasSkill(skill->skill_id)) { + AddSkill(skill->skill_id, 1, skill->max_val, true); + } + // Check to see if the player already has the language + if (HasLanguage(id)) + return; + + // Doesn't already have the language so add it + Language* new_language = new Language; + new_language->SetID(id); + new_language->SetName(name); + player_languages_list.Add(new_language); + + if (save_needed) + database.SaveCharacterLang(GetCharacterID(), id); +} + +bool Player::HasLanguage(int32 id){ + list* languages = player_languages_list.GetAllLanguages(); + list::iterator itr; + Language* language = 0; + bool ret = false; + for(itr = languages->begin(); itr != languages->end(); itr++){ + language = *itr; + if(language->GetID() == id){ + ret = true; + break; + } + } + return ret; +} + +bool Player::HasLanguage(const char* name){ + list* languages = player_languages_list.GetAllLanguages(); + list::iterator itr; + Language* language = 0; + bool ret = false; + for(itr = languages->begin(); itr != languages->end(); itr++){ + language = *itr; + if(!strncmp(language->GetName(), name, strlen(name))){ + ret = true; + break; + } + } + return ret; +} + +void Player::AddPassiveSpell(int32 id, int8 tier) +{ + // Add the spell to the list of passives this player currently has + // list is used for quickly going over only the passive spells the + // player has instead of going through all their spells. + passive_spells.push_back(id); + + Client* client = GetClient(); + + // Don not apply passives if the client is null, zoning, or reviving + if (client == NULL || client->IsZoning() || IsResurrecting()) + return; + + Spell* spell = 0; + spell = master_spell_list.GetSpell(id, tier); + if (spell) { + SpellProcess* spellProcess = 0; + // Get the current zones spell process + spellProcess = GetZone()->GetSpellProcess(); + // Cast the spell, CastPassives() bypasses the spell queue + spellProcess->CastPassives(spell, this); + } +} + +void Player::ApplyPassiveSpells() +{ + Spell* spell = 0; + SpellBookEntry* spell2 = 0; + vector::iterator itr; + SpellProcess* spellProcess = 0; + spellProcess = GetZone()->GetSpellProcess(); + + for (itr = passive_spells.begin(); itr != passive_spells.end(); itr++) + { + spell2 = GetSpellBookSpell((*itr)); + spell = master_spell_list.GetSpell(spell2->spell_id, spell2->tier); + if (spell) { + spellProcess->CastPassives(spell, this); + } + } +} + +void Player::RemovePassive(int32 id, int8 tier, bool remove_from_list) +{ + Spell* spell = 0; + spell = master_spell_list.GetSpell(id, tier); + if (spell) { + SpellProcess* spellProcess = 0; + spellProcess = GetZone()->GetSpellProcess(); + spellProcess->CastPassives(spell, this, true); + + if (remove_from_list) { + vector::iterator remove; + remove = find(passive_spells.begin(), passive_spells.end(), id); + if (remove != passive_spells.end()) + passive_spells.erase(remove); + } + + database.DeleteCharacterSpell(GetCharacterID(), spell->GetSpellID()); + } +} + +void Player::RemoveAllPassives() +{ + vector::iterator itr; + for (itr = passive_spells.begin(); itr != passive_spells.end(); itr++) + RemoveSpellBookEntry((*itr), false); + + passive_spells.clear(); +} + +void Player::ResetPetInfo() { + GetInfoStruct()->set_pet_id(0xFFFFFFFF); + GetInfoStruct()->set_pet_movement(0); + GetInfoStruct()->set_pet_behavior(0); + GetInfoStruct()->set_pet_health_pct(0.0f); + GetInfoStruct()->set_pet_power_pct(0.0f); + // Make sure the values get sent to the client + SetCharSheetChanged(true); +} + +bool Player::HasRecipeBook(int32 recipe_id){ + return recipebook_list.HasRecipeBook(recipe_id); +} + +bool Player::DiscoveredLocation(int32 locationID) { + bool ret = false; + + // No discovery type entry then return false + if (m_characterHistory.count(HISTORY_TYPE_DISCOVERY) == 0) + return false; + + // Is a discovery type entry but not a location subtype return false + if (m_characterHistory[HISTORY_TYPE_DISCOVERY].count(HISTORY_SUBTYPE_LOCATION) == 0) + return false; + + vector::iterator itr; + + for (itr = m_characterHistory[HISTORY_TYPE_DISCOVERY][HISTORY_SUBTYPE_LOCATION].begin(); itr != m_characterHistory[HISTORY_TYPE_DISCOVERY][HISTORY_SUBTYPE_LOCATION].end(); itr++) { + if ((*itr)->Value == locationID) { + ret = true; + break; + } + } + + return ret; +} + +void Player::UpdatePlayerHistory(int8 type, int8 subtype, int32 value, int32 value2) { + switch (type) { + case HISTORY_TYPE_NONE: + HandleHistoryNone(subtype, value, value2); + break; + case HISTORY_TYPE_DEATH: + HandleHistoryDeath(subtype, value, value2); + break; + case HISTORY_TYPE_DISCOVERY: + HandleHistoryDiscovery(subtype, value, value2); + break; + case HISTORY_TYPE_XP: + HandleHistoryXP(subtype, value, value2); + break; + default: + // Not a valid history event so return out before trying to save into the db + return; + } +} + +void Player::HandleHistoryNone(int8 subtype, int32 value, int32 value2) { +} + +void Player::HandleHistoryDeath(int8 subtype, int32 value, int32 value2) { +} + +void Player::HandleHistoryDiscovery(int8 subtype, int32 value, int32 value2) { + switch (subtype) { + case HISTORY_SUBTYPE_NONE: + break; + case HISTORY_SUBTYPE_ADVENTURE: + break; + case HISTORY_SUBTYPE_TRADESKILL: + break; + case HISTORY_SUBTYPE_QUEST: + break; + case HISTORY_SUBTYPE_AA: + break; + case HISTORY_SUBTYPE_ITEM: + break; + case HISTORY_SUBTYPE_LOCATION: { + HistoryData* hd = new HistoryData; + hd->Value = value; + hd->Value2 = value2; + hd->EventDate = Timer::GetUnixTimeStamp(); + strcpy(hd->Location, GetZone()->GetZoneName()); + hd->needs_save = true; + + m_characterHistory[HISTORY_TYPE_DISCOVERY][HISTORY_SUBTYPE_LOCATION].push_back(hd); + break; + } + default: + // Invalid subtype, default to NONE + break; + } +} + +void Player::HandleHistoryXP(int8 subtype, int32 value, int32 value2) { + switch (subtype) { + case HISTORY_SUBTYPE_NONE: + break; + case HISTORY_SUBTYPE_ADVENTURE: { + HistoryData* hd = new HistoryData; + hd->Value = value; + hd->Value2 = value2; + hd->EventDate = Timer::GetUnixTimeStamp(); + strcpy(hd->Location, GetZone()->GetZoneName()); + hd->needs_save = true; + + m_characterHistory[HISTORY_TYPE_XP][HISTORY_SUBTYPE_ADVENTURE].push_back(hd); + } + break; + case HISTORY_SUBTYPE_TRADESKILL: + break; + case HISTORY_SUBTYPE_QUEST: + break; + case HISTORY_SUBTYPE_AA: + break; + case HISTORY_SUBTYPE_ITEM: + break; + case HISTORY_SUBTYPE_LOCATION: + break; + default: + // Invalid subtype, default to NONE + break; + } +} + +void Player::LoadPlayerHistory(int8 type, int8 subtype, HistoryData* hd) { + m_characterHistory[type][subtype].push_back(hd); +} + +void Player::SaveHistory() { + LogWrite(PLAYER__DEBUG, 0, "Player", "Saving History for Player: '%s'", GetName()); + + map > >::iterator itr; + map >::iterator itr2; + vector::iterator itr3; + for (itr = m_characterHistory.begin(); itr != m_characterHistory.end(); itr++) { + for (itr2 = itr->second.begin(); itr2 != itr->second.end(); itr2++) { + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++) { + + if((*itr3)->needs_save) { + database.SaveCharacterHistory(this, itr->first, itr2->first, (*itr3)->Value, (*itr3)->Value2, (*itr3)->Location, (*itr3)->EventDate); + (*itr3)->needs_save = false; + } + } + } + } +} + +void Player::InitXPTable() { + int i = 2; + while (i >= 2 && i <= 95) { + m_levelXPReq[i] = database.GetMysqlExpCurve(i); + i++; + } +} + +void Player::SendQuestRequiredSpawns(int32 quest_id){ + bool locked = true; + m_playerSpawnQuestsRequired.readlock(__FUNCTION__, __LINE__); + if (player_spawn_quests_required.size() > 0 ) { + ZoneServer* zone = GetZone(); + Client* client = GetClient(); + if (!client){ + m_playerSpawnQuestsRequired.releasereadlock(__FUNCTION__, __LINE__); + return; + } + int xxx = player_spawn_quests_required.count(quest_id); + if (player_spawn_quests_required.count(quest_id) > 0){ + vector spawns = *player_spawn_quests_required[quest_id]; + m_playerSpawnQuestsRequired.releasereadlock(__FUNCTION__, __LINE__); + Spawn* spawn = 0; + vector::iterator itr; + for (itr = spawns.begin(); itr != spawns.end();){ + spawn = zone->GetSpawnByID(*itr); + if (spawn) + zone->SendSpawnChanges(spawn, client, false, true); + else { + itr = spawns.erase(itr); + continue; + } + itr++; + } + locked = false; + } + } + if (locked) + m_playerSpawnQuestsRequired.releasereadlock(__FUNCTION__, __LINE__); +} + +void Player::SendHistoryRequiredSpawns(int32 event_id){ + bool locked = true; + m_playerSpawnHistoryRequired.readlock(__FUNCTION__, __LINE__); + if (player_spawn_history_required.size() > 0) { + ZoneServer* zone = GetZone(); + Client* client = GetClient(); + if (!client){ + m_playerSpawnHistoryRequired.releasereadlock(__FUNCTION__, __LINE__); + return; + } + if (player_spawn_history_required.count(event_id) > 0){ + vector spawns = *player_spawn_history_required[event_id]; + m_playerSpawnHistoryRequired.releasereadlock(__FUNCTION__, __LINE__); + Spawn* spawn = 0; + vector::iterator itr; + for (itr = spawns.begin(); itr != spawns.end();){ + spawn = zone->GetSpawnByID(*itr); + if (spawn) + zone->SendSpawnChanges(spawn, client, false, true); + else { + itr = spawns.erase(itr); + continue; + } + itr++; + } + locked = false; + } + } + if (locked) + m_playerSpawnHistoryRequired.releasereadlock(__FUNCTION__, __LINE__); +} + +void Player::AddQuestRequiredSpawn(Spawn* spawn, int32 quest_id){ + if(!spawn || !quest_id) + return; + m_playerSpawnQuestsRequired.writelock(__FUNCTION__, __LINE__); + if(player_spawn_quests_required.count(quest_id) == 0) + player_spawn_quests_required[quest_id] = new vector; + vector* quest_spawns = player_spawn_quests_required[quest_id]; + int32 current_spawn = 0; + for(int32 i=0;isize();i++){ + current_spawn = quest_spawns->at(i); + if (current_spawn == spawn->GetID()){ + m_playerSpawnQuestsRequired.releasewritelock(__FUNCTION__, __LINE__); + return; + } + } + player_spawn_quests_required[quest_id]->push_back(spawn->GetID()); + m_playerSpawnQuestsRequired.releasewritelock(__FUNCTION__, __LINE__); +} + +void Player::AddHistoryRequiredSpawn(Spawn* spawn, int32 event_id){ + if (!spawn || !event_id) + return; + m_playerSpawnHistoryRequired.writelock(__FUNCTION__, __LINE__); + if (player_spawn_history_required.count(event_id) == 0) + player_spawn_history_required[event_id] = new vector; + vector* history_spawns = player_spawn_history_required[event_id]; + vector::iterator itr = find(history_spawns->begin(), history_spawns->end(), spawn->GetID()); + if (itr == history_spawns->end()) + history_spawns->push_back(spawn->GetID()); + m_playerSpawnHistoryRequired.releasewritelock(__FUNCTION__, __LINE__); +} + +int32 PlayerInfo::GetBoatSpawn(){ + return boat_spawn; +} + +void PlayerInfo::SetBoatSpawn(Spawn* spawn){ + if(spawn) + boat_spawn = spawn->GetID(); + else + boat_spawn = 0; +} + +void PlayerInfo::RemoveOldPackets(){ + safe_delete_array(changes); + safe_delete_array(orig_packet); + safe_delete_array(pet_changes); + safe_delete_array(pet_orig_packet); + changes = 0; + orig_packet = 0; + pet_changes = 0; + pet_orig_packet = 0; +} + +PlayerControlFlags::PlayerControlFlags(){ + MControlFlags.SetName("PlayerControlFlags::MControlFlags"); + MFlagChanges.SetName("PlayerControlFlags::MFlagChanges"); + flags_changed = false; + flag_changes.clear(); + current_flags.clear(); +} + +PlayerControlFlags::~PlayerControlFlags(){ + flag_changes.clear(); + current_flags.clear(); +} + +bool PlayerControlFlags::ControlFlagsChanged(){ + bool ret; + MFlagChanges.writelock(__FUNCTION__, __LINE__); + ret = flags_changed; + MFlagChanges.releasewritelock(__FUNCTION__, __LINE__); + return ret; +} + +void PlayerControlFlags::SetPlayerControlFlag(int8 param, int8 param_value, bool is_active){ + if (!param || !param_value) + return; + + bool active_changed = false; + MControlFlags.writelock(__FUNCTION__, __LINE__); + active_changed = (current_flags[param][param_value] != is_active); + if (active_changed){ + current_flags[param][param_value] = is_active; + MFlagChanges.writelock(__FUNCTION__, __LINE__); + flag_changes[param][param_value] = is_active ? 1 : 0; + flags_changed = true; + MFlagChanges.releasewritelock(__FUNCTION__, __LINE__); + } + MControlFlags.releasewritelock(__FUNCTION__, __LINE__); +} + +void PlayerControlFlags::SendControlFlagUpdates(Client* client){ + if (!client) + return; + + map* ptr = 0; + map >::iterator itr; + map::iterator itr2; + + MFlagChanges.writelock(__FUNCTION__, __LINE__); + for (itr = flag_changes.begin(); itr != flag_changes.end(); itr++){ + ptr = &itr->second; + for (itr2 = ptr->begin(); itr2 != ptr->end(); itr2++){ + int32 param = itr2->first; + if(client->GetVersion() <= 561) { + if(itr->first == 1) { // first set of flags DoF only supports these + bool skip = false; + switch(itr2->first) { + case 1: // flymode for DoF + case 2: // no clip mode for DoF + case 4: // we don't know + case 32: { // safe fall (DoF is low gravity for this parameter) + skip = true; + break; + } + } + + if(skip) { + continue; + } + } + + bool bypassFlag = true; + // remap control effects to old DoF from AoM + switch(itr->first) { + case 4: { + if(itr2->first == 64) { // stun + ClientPacketFunctions::SendServerControlFlagsClassic(client, 8, itr2->second); + param = 16; + bypassFlag = false; + } + break; + } + } + if(itr->first > 1 && bypassFlag) { + continue; // we don't support flag sets higher than 1 for DoF + } + ClientPacketFunctions::SendServerControlFlagsClassic(client, param, itr2->second); + } + else { + ClientPacketFunctions::SendServerControlFlags(client, itr->first, itr2->first, itr2->second); + } + } + } + flag_changes.clear(); + flags_changed = false; + MFlagChanges.releasewritelock(__FUNCTION__, __LINE__); +} + +bool Player::ControlFlagsChanged(){ + return control_flags.ControlFlagsChanged(); +} + +void Player::SetPlayerControlFlag(int8 param, int8 param_value, bool is_active){ + control_flags.SetPlayerControlFlag(param, param_value, is_active); +} + +void Player::SendControlFlagUpdates(Client* client){ + control_flags.SendControlFlagUpdates(client); +} + +void Player::LoadLUAHistory(int32 event_id, LUAHistory* history) { + mLUAHistory.writelock(); + if (m_charLuaHistory.count(event_id) > 0) { + LogWrite(PLAYER__ERROR, 0, "Player", "Attempted to added a dupicate event (%u) to character LUA history", event_id); + safe_delete(history); + mLUAHistory.releasewritelock(); + return; + } + + m_charLuaHistory.insert(make_pair(event_id,history)); + mLUAHistory.releasewritelock(); +} + +void Player::SaveLUAHistory() { + mLUAHistory.readlock(); + LogWrite(PLAYER__DEBUG, 0, "Player", "Saving LUA History for Player: '%s'", GetName()); + + map::iterator itr; + for (itr = m_charLuaHistory.begin(); itr != m_charLuaHistory.end(); itr++) { + if (itr->second->SaveNeeded) { + database.SaveCharacterLUAHistory(this, itr->first, itr->second->Value, itr->second->Value2); + itr->second->SaveNeeded = false; + } + } + mLUAHistory.releasereadlock(); +} + +void Player::UpdateLUAHistory(int32 event_id, int32 value, int32 value2) { + mLUAHistory.writelock(); + LUAHistory* hd = 0; + + if (m_charLuaHistory.count(event_id) > 0) + hd = m_charLuaHistory[event_id]; + else { + hd = new LUAHistory; + m_charLuaHistory.insert(make_pair(event_id,hd)); + } + + hd->Value = value; + hd->Value2 = value2; + hd->SaveNeeded = true; + mLUAHistory.releasewritelock(); + // release the mLUAHistory lock, we will maintain a readlock to avoid any further writes until we complete SendHistoryRequiredSpawns + // through Spawn::SendSpawnChanges -> Spawn::InitializeVisPacketData -> Spawn::MeetsSpawnAccessRequirements-> Player::GetLUAHistory (this was causing a deadlock) + mLUAHistory.readlock(); + SendHistoryRequiredSpawns(event_id); + mLUAHistory.releasereadlock(); +} + +LUAHistory* Player::GetLUAHistory(int32 event_id) { + LUAHistory* ret = 0; + + mLUAHistory.readlock(); + + if (m_charLuaHistory.count(event_id) > 0) + ret = m_charLuaHistory[event_id]; + + mLUAHistory.releasereadlock(); + + return ret; +} + +bool Player::CanSeeInvis(Entity* target) +{ + if (!target->IsStealthed() && !target->IsInvis()) + return true; + if (target->IsStealthed() && HasSeeHideSpell()) + return true; + else if (target->IsInvis() && HasSeeInvisSpell()) + return true; + + sint32 radius = rule_manager.GetGlobalRule(R_PVP, InvisPlayerDiscoveryRange)->GetSInt32(); + + if (radius == 0) // radius of 0 is always seen + return true; + // radius of -1 is never seen except through items/spells, radius > -1 means we will show the player if they get into the inner radius + else if (radius > -1 && this->GetDistance((Spawn*)target) < (float)radius) + return true; + + // TODO: Implement See Invis Spells! http://cutpon.com:3000/devn00b/EQ2EMu/issues/43 + + Item* item = 0; + vector* equipped_list = GetEquippedItemList(); + bool seeInvis = false; + bool seeStealth = false; + for (int32 i = 0; i < equipped_list->size(); i++) + { + item = equipped_list->at(i); + seeInvis = item->HasStat(ITEM_STAT_SEEINVIS); + seeStealth = item->HasStat(ITEM_STAT_SEESTEALTH); + if (target->IsStealthed() && seeStealth) + return true; + else if (target->IsInvis() && seeInvis) + return true; + } + + return false; +} + +// returns true if we need to update target info due to see invis status change +bool Player::CheckChangeInvisHistory(Entity* target) +{ + std::map::iterator it; + + it = target_invis_history.find(target->GetID()); + if (it != target_invis_history.end()) + { + //canSeeStatus + if (it->second) + { + if (!this->CanSeeInvis(target)) + { + UpdateTargetInvisHistory(target->GetID(), false); + return true; + } + else + return false; + } + else + { + if (this->CanSeeInvis(target)) + { + UpdateTargetInvisHistory(target->GetID(), true); + return true; + } + else + return false; + } + } + + if (!this->CanSeeInvis(target)) + UpdateTargetInvisHistory(target->GetID(), false); + else + UpdateTargetInvisHistory(target->GetID(), true); + + return true; +} + +void Player::UpdateTargetInvisHistory(int32 targetID, bool canSeeStatus) +{ + target_invis_history[targetID] = canSeeStatus; +} + +void Player::RemoveTargetInvisHistory(int32 targetID) +{ + target_invis_history.erase(targetID); +} + +int16 Player::GetNextSpawnIndex(Spawn* spawn, bool set_lock) +{ + if(set_lock) + index_mutex.writelock(__FUNCTION__, __LINE__); + int16 next_index = 0; + int16 max_count = 0; + bool not_found = true; + do { + next_index = (spawn_index++); + max_count++; + if(max_count > 0xFFFE) { + LogWrite(PLAYER__ERROR, 0, "Player", "%s: This is bad we ran out of spawn indexes!", GetName()); + break; + } + if(next_index == 1 && spawn != this) { // only self can occupy index 1 + continue; + } + if(next_index == 0 || next_index == 255) { // avoid 0 and overloads (255) + continue; + } + Spawn* tmp_spawn = nullptr; + if(player_spawn_id_map.count(next_index) > 0) + tmp_spawn = player_spawn_id_map[next_index]; + + if(tmp_spawn && tmp_spawn != spawn) { // spawn index already taken and it is not this spawn + continue; + } + not_found = false; + } + while(not_found); + + if(set_lock) + index_mutex.releasewritelock(__FUNCTION__, __LINE__); + + return next_index; +} + +bool Player::SetSpawnMap(Spawn* spawn) +{ + if(!client->GetPlayer()->SetSpawnSentState(spawn, SpawnState::SPAWN_STATE_SENDING)) { + return false; + } + + index_mutex.writelock(__FUNCTION__, __LINE__); + int32 tmp_id = GetNextSpawnIndex(spawn, false); + + player_spawn_id_map[tmp_id] = spawn; + + if(player_spawn_reverse_id_map.count(spawn)) + player_spawn_reverse_id_map.erase(spawn); + + player_spawn_reverse_id_map.insert(make_pair(spawn,tmp_id)); + index_mutex.releasewritelock(__FUNCTION__, __LINE__); + return true; +} + +int16 Player::SetSpawnMapAndIndex(Spawn* spawn) +{ + index_mutex.writelock(__FUNCTION__, __LINE__); + int32 new_index = GetNextSpawnIndex(spawn, false); + + player_spawn_id_map[new_index] = spawn; + player_spawn_reverse_id_map[spawn] = new_index; + index_mutex.releasewritelock(__FUNCTION__, __LINE__); + + return new_index; +} + +NPC* Player::InstantiateSpiritShard(float origX, float origY, float origZ, float origHeading, int32 origGridID, ZoneServer* origZone) +{ + NPC* npc = new NPC(); + string newName(GetName()); + newName.append("'s spirit shard"); + + strcpy(npc->appearance.name, newName.c_str()); + /*vector* primary_command_list = zone->GetEntityCommandList(result.GetInt32(9)); + vector* secondary_command_list = zone->GetEntityCommandList(result.GetInt32(10)); + if(primary_command_list){ + npc->SetPrimaryCommands(primary_command_list); + npc->primary_command_list_id = result.GetInt32(9); + } + if(secondary_command_list){ + npc->SetSecondaryCommands(secondary_command_list); + npc->secondary_command_list_id = result.GetInt32(10); + }*/ + npc->appearance.level = GetLevel(); + npc->appearance.race = GetRace(); + npc->appearance.gender = GetGender(); + npc->appearance.adventure_class = GetAdventureClass(); + + npc->appearance.model_type = GetModelType(); + npc->appearance.soga_model_type = GetSogaModelType(); + npc->appearance.display_name = 1; + npc->features.hair_type = GetHairType(); + npc->features.hair_face_type = GetFacialHairType(); + npc->features.wing_type = GetWingType(); + npc->features.chest_type = GetChestType(); + npc->features.legs_type = GetLegsType(); + npc->features.soga_hair_type = GetSogaHairType(); + npc->features.soga_hair_face_type = GetSogaFacialHairType(); + npc->appearance.attackable = 0; + npc->appearance.show_level = 1; + npc->appearance.targetable = 1; + npc->appearance.show_command_icon = 1; + npc->appearance.display_hand_icon = 0; + npc->appearance.hide_hood = GetHideHood(); + npc->size = GetSize(); + npc->appearance.pos.collision_radius = appearance.pos.collision_radius; + npc->appearance.action_state = appearance.action_state; + npc->appearance.visual_state = 6193; // ghostly look + npc->appearance.mood_state = appearance.mood_state; + npc->appearance.emote_state = appearance.emote_state; + npc->appearance.pos.state = appearance.pos.state; + npc->appearance.activity_status = appearance.activity_status; + strncpy(npc->appearance.sub_title, appearance.sub_title, sizeof(npc->appearance.sub_title)); + npc->SetPrefixTitle(GetPrefixTitle()); + npc->SetSuffixTitle(GetSuffixTitle()); + npc->SetLastName(GetLastName()); + npc->SetX(origX); + npc->SetY(origY); + npc->SetZ(origZ); + npc->SetHeading(origHeading); + npc->SetSpawnOrigX(origX); + npc->SetSpawnOrigY(origY); + npc->SetSpawnOrigZ(origZ); + npc->SetSpawnOrigHeading(origHeading); + npc->SetLocation(origGridID); + npc->SetAlive(false); + const char* script = rule_manager.GetGlobalRule(R_Combat, SpiritShardSpawnScript)->GetString(); + + int32 dbid = database.CreateSpiritShard(newName.c_str(), GetLevel(), GetRace(), GetGender(), GetAdventureClass(), GetModelType(), GetSogaModelType(), + GetHairType(), GetFacialHairType(), GetWingType(), GetChestType(), GetLegsType(), GetSogaHairType(), GetSogaFacialHairType(), GetHideHood(), + GetSize(), npc->appearance.pos.collision_radius, npc->appearance.action_state, npc->appearance.visual_state, npc->appearance.mood_state, + npc->appearance.emote_state, npc->appearance.pos.state, npc->appearance.activity_status, npc->appearance.sub_title, GetPrefixTitle(), GetSuffixTitle(), + GetLastName(), origX, origY, origZ, origHeading, origGridID, GetCharacterID(), origZone->GetZoneID(), origZone->GetInstanceID()); + + npc->SetShardID(dbid); + npc->SetShardCharID(GetCharacterID()); + npc->SetShardCreatedTimestamp(Timer::GetCurrentTime2()); + + if(script) + npc->SetSpawnScript(script); + + return npc; +} + +void Player::SaveSpellEffects() +{ + if(stop_save_spell_effects) + { + LogWrite(PLAYER__WARNING, 0, "Player", "SaveSpellEffects called while player constructing / deconstructing!"); + return; + } + + SpellProcess* spellProcess = 0; + // Get the current zones spell process + spellProcess = GetZone()->GetSpellProcess(); + + Query savedEffects; + savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete from character_spell_effects where charid=%u", GetCharacterID()); + savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete from character_spell_effect_targets where caster_char_id=%u", GetCharacterID()); + InfoStruct* info = GetInfoStruct(); + MSpellEffects.readlock(__FUNCTION__, __LINE__); + MMaintainedSpells.readlock(__FUNCTION__, __LINE__); + for(int i = 0; i < 45; i++) { + if(info->spell_effects[i].spell_id != 0xFFFFFFFF) + { + Spawn* spawn = nullptr; + int32 target_char_id = 0; + if(info->spell_effects[i].spell->initial_target_char_id != 0) + target_char_id = info->spell_effects[i].spell->initial_target_char_id; + else if((spawn = GetZone()->GetSpawnByID(info->spell_effects[i].spell->initial_target)) != nullptr && spawn->IsPlayer()) + target_char_id = ((Player*)spawn)->GetCharacterID(); + + int32 timestamp = 0xFFFFFFFF; + if(info->spell_effects[i].spell->spell->GetSpellData() && !info->spell_effects[i].spell->spell->GetSpellData()->duration_until_cancel) + timestamp = info->spell_effects[i].expire_timestamp - Timer::GetCurrentTime2(); + + int32 caster_char_id = info->spell_effects[i].spell->initial_caster_char_id; + + if(caster_char_id == 0) + continue; + + savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_INSERT, + "insert into character_spell_effects (name, caster_char_id, target_char_id, target_type, db_effect_type, spell_id, effect_slot, slot_pos, icon, icon_backdrop, conc_used, tier, total_time, expire_timestamp, lua_file, custom_spell, charid, damage_remaining, effect_bitmask, num_triggers, had_triggers, cancel_after_triggers, crit, last_spellattack_hit, interrupted, resisted, has_damaged, custom_function) values ('%s', %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %f, %u, '%s', %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, '%s')", + database.getSafeEscapeString(info->spell_effects[i].spell->spell->GetName()).c_str(), caster_char_id, + target_char_id, 0 /*no target_type for spell_effects*/, DB_TYPE_SPELLEFFECTS /* db_effect_type for spell_effects */, info->spell_effects[i].spell->spell->IsCopiedSpell() ? info->spell_effects[i].spell->spell->GetSpellData()->inherited_spell_id : info->spell_effects[i].spell_id, i, info->spell_effects[i].spell->slot_pos, + info->spell_effects[i].icon, info->spell_effects[i].icon_backdrop, 0 /* no conc_used for spell_effects */, info->spell_effects[i].tier, + info->spell_effects[i].total_time, timestamp, database.getSafeEscapeString(info->spell_effects[i].spell->file_name.c_str()).c_str(), info->spell_effects[i].spell->spell->IsCopiedSpell(), GetCharacterID(), + info->spell_effects[i].spell->damage_remaining, info->spell_effects[i].spell->effect_bitmask, info->spell_effects[i].spell->num_triggers, info->spell_effects[i].spell->had_triggers, info->spell_effects[i].spell->cancel_after_all_triggers, + info->spell_effects[i].spell->crit, info->spell_effects[i].spell->last_spellattack_hit, info->spell_effects[i].spell->interrupted, info->spell_effects[i].spell->resisted, info->spell_effects[i].spell->has_damaged, (info->maintained_effects[i].expire_timestamp) == 0xFFFFFFFF ? "" : database.getSafeEscapeString(spellProcess->SpellScriptTimerCustomFunction(info->spell_effects[i].spell).c_str()).c_str()); + } + if (i < NUM_MAINTAINED_EFFECTS && info->maintained_effects[i].spell_id != 0xFFFFFFFF){ + Spawn* spawn = GetZone()->GetSpawnByID(info->maintained_effects[i].spell->initial_target); + + int32 target_char_id = 0; + + if(info->maintained_effects[i].spell->initial_target_char_id != 0) + target_char_id = info->maintained_effects[i].spell->initial_target_char_id; + else if(!info->maintained_effects[i].spell->initial_target) + target_char_id = GetCharacterID(); + else if(spawn && spawn->IsPlayer()) + target_char_id = ((Player*)spawn)->GetCharacterID(); + else if (spawn && spawn->IsPet() && ((Entity*)spawn)->GetOwner() == (Entity*)this) + target_char_id = 0xFFFFFFFF; + + int32 caster_char_id = (info->maintained_effects[i].spell->caster && info->maintained_effects[i].spell->caster->IsPlayer()) ? ((Player*)info->maintained_effects[i].spell->caster)->GetCharacterID() : 0; + + int32 timestamp = 0xFFFFFFFF; + if(info->maintained_effects[i].spell->spell->GetSpellData() && !info->maintained_effects[i].spell->spell->GetSpellData()->duration_until_cancel) + timestamp = info->maintained_effects[i].expire_timestamp - Timer::GetCurrentTime2(); + savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_INSERT, + "insert into character_spell_effects (name, caster_char_id, target_char_id, target_type, db_effect_type, spell_id, effect_slot, slot_pos, icon, icon_backdrop, conc_used, tier, total_time, expire_timestamp, lua_file, custom_spell, charid, damage_remaining, effect_bitmask, num_triggers, had_triggers, cancel_after_triggers, crit, last_spellattack_hit, interrupted, resisted, has_damaged, custom_function) values ('%s', %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %f, %u, '%s', %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, '%s')", + database.getSafeEscapeString(info->maintained_effects[i].name).c_str(), caster_char_id, target_char_id, info->maintained_effects[i].target_type, DB_TYPE_MAINTAINEDEFFECTS /* db_effect_type for maintained_effects */, info->maintained_effects[i].spell->spell->IsCopiedSpell() ? info->maintained_effects[i].spell->spell->GetSpellData()->inherited_spell_id : info->maintained_effects[i].spell_id, i, info->maintained_effects[i].slot_pos, + info->maintained_effects[i].icon, info->maintained_effects[i].icon_backdrop, info->maintained_effects[i].conc_used, info->maintained_effects[i].tier, + info->maintained_effects[i].total_time, timestamp, database.getSafeEscapeString(info->maintained_effects[i].spell->file_name.c_str()).c_str(), info->maintained_effects[i].spell->spell->IsCopiedSpell(), GetCharacterID(), + info->maintained_effects[i].spell->damage_remaining, info->maintained_effects[i].spell->effect_bitmask, info->maintained_effects[i].spell->num_triggers, info->maintained_effects[i].spell->had_triggers, info->maintained_effects[i].spell->cancel_after_all_triggers, + info->maintained_effects[i].spell->crit, info->maintained_effects[i].spell->last_spellattack_hit, info->maintained_effects[i].spell->interrupted, info->maintained_effects[i].spell->resisted, info->maintained_effects[i].spell->has_damaged, (info->maintained_effects[i].expire_timestamp) == 0xFFFFFFFF ? "" : database.getSafeEscapeString(spellProcess->SpellScriptTimerCustomFunction(info->maintained_effects[i].spell).c_str()).c_str()); + + info->maintained_effects[i].spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + std::string insertTargets = string("insert into character_spell_effect_targets (caster_char_id, target_char_id, target_type, db_effect_type, spell_id, effect_slot, slot_pos) values "); + bool firstTarget = true; + map targetsInserted; + for (int8 t = 0; t < info->maintained_effects[i].spell->targets.size(); t++) { + int32 spawn_id = info->maintained_effects[i].spell->targets.at(t); + Spawn* spawn = GetZone()->GetSpawnByID(spawn_id); + LogWrite(SPELL__DEBUG, 0, "Spell", "%s has target %u to identify for spell %s", GetName(), spawn_id, info->maintained_effects[i].spell->spell->GetName()); + if(spawn && (spawn->IsPlayer() || spawn->IsPet())) + { + int32 tmpCharID = 0; + int8 type = 0; + + if(targetsInserted.find(spawn) != targetsInserted.end()) + continue; + + if(spawn->IsPlayer()) + tmpCharID = ((Player*)spawn)->GetCharacterID(); + else if (spawn->IsPet() && ((Entity*)spawn)->GetOwner() == (Entity*)this) + { + tmpCharID = 0xFFFFFFFF; + } + else if(spawn->IsPet() && ((Entity*)spawn)->GetOwner() && + ((Entity*)spawn)->GetOwner()->IsPlayer()) + { + type = ((Entity*)spawn)->GetPetType(); + Player* petOwner = (Player*)((Entity*)spawn)->GetOwner(); + tmpCharID = petOwner->GetCharacterID(); + } + + if(!firstTarget) + insertTargets.append(", "); + + targetsInserted.insert(make_pair(spawn, true)); + + + LogWrite(SPELL__DEBUG, 0, "Spell", "%s has target %s (%u) added to spell %s", GetName(), spawn ? spawn->GetName() : "NA", tmpCharID, info->maintained_effects[i].spell->spell->GetName()); + insertTargets.append("(" + std::to_string(caster_char_id) + ", " + std::to_string(tmpCharID) + ", " + std::to_string(type) + ", " + + std::to_string(DB_TYPE_MAINTAINEDEFFECTS) + ", " + std::to_string(info->maintained_effects[i].spell_id) + ", " + std::to_string(i) + + ", " + std::to_string(info->maintained_effects[i].slot_pos) + ")"); + firstTarget = false; + } + } + multimap::iterator entries; + for(entries = info->maintained_effects[i].spell->char_id_targets.begin(); entries != info->maintained_effects[i].spell->char_id_targets.end(); entries++) + { + if(!firstTarget) + insertTargets.append(", "); + + LogWrite(SPELL__DEBUG, 0, "Spell", "%s has target %s (%u) added to spell %s", GetName(), spawn ? spawn->GetName() : "NA", entries->first, info->maintained_effects[i].spell->spell->GetName()); + insertTargets.append("(" + std::to_string(caster_char_id) + ", " + std::to_string(entries->first) + ", " + std::to_string(entries->second) + ", " + + std::to_string(DB_TYPE_MAINTAINEDEFFECTS) + ", " + std::to_string(info->maintained_effects[i].spell_id) + ", " + std::to_string(i) + + ", " + std::to_string(info->maintained_effects[i].slot_pos) + ")"); + + firstTarget = false; + } + info->maintained_effects[i].spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + if(!firstTarget) { + savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_INSERT, insertTargets.c_str()); + } + } + } + MMaintainedSpells.releasereadlock(__FUNCTION__, __LINE__); + MSpellEffects.releasereadlock(__FUNCTION__, __LINE__); +} + +void Player::MentorTarget() +{ + if(client->GetPlayer()->GetGroupMemberInfo() && client->GetPlayer()->GetGroupMemberInfo()->mentor_target_char_id) + { + client->GetPlayer()->GetGroupMemberInfo()->mentor_target_char_id = 0; + reset_mentorship = true; + client->Message(CHANNEL_COMMAND_TEXT, "You stop mentoring, and return to level %u.", client->GetPlayer()->GetLevel()); + } + else if(!reset_mentorship && client->GetPlayer()->GetTarget()) + { + if(client->GetPlayer()->GetTarget()->IsPlayer()) + { + Player* tmpPlayer = (Player*)client->GetPlayer()->GetTarget(); + if(tmpPlayer->GetGroupMemberInfo() && tmpPlayer->GetGroupMemberInfo()->mentor_target_char_id) + { + client->Message(CHANNEL_COMMAND_TEXT, "You cannot mentor %s at this time.",tmpPlayer->GetName()); + return; + } + if(client->GetPlayer()->group_id > 0 && client->GetPlayer()->GetTarget()->group_id == client->GetPlayer()->group_id) + { + if(client->GetPlayer()->GetGroupMemberInfo() && !client->GetPlayer()->GetGroupMemberInfo()->mentor_target_char_id && client->GetPlayer()->GetZone() == client->GetPlayer()->GetTarget()->GetZone() && client->GetPlayer()->GetTarget()->GetName() != client->GetPlayer()->GetName()) + { + SetMentorStats(client->GetPlayer()->GetTarget()->GetLevel(), tmpPlayer->GetCharacterID()); + client->Message(CHANNEL_COMMAND_TEXT, "You are now mentoring %s, reducing your effective level to %u.",client->GetPlayer()->GetTarget()->GetName(), client->GetPlayer()->GetTarget()->GetLevel()); + } + if(client->GetPlayer()->GetTarget()->GetName() == client->GetPlayer()->GetName()) { + client->Message(CHANNEL_COMMAND_TEXT, "You cannot mentor yourself."); + } + } + } + } +} + +void Player::SetMentorStats(int32 effective_level, int32 target_char_id, bool update_stats) +{ + if(update_stats) { + RemoveSpells(); + } + if(client->GetPlayer()->GetGroupMemberInfo()) + client->GetPlayer()->GetGroupMemberInfo()->mentor_target_char_id = target_char_id; + InfoStruct* info = GetInfoStruct(); + info->set_effective_level(effective_level); + CalculatePlayerHPPower(effective_level); + client->GetPlayer()->CalculateBonuses(); + if(update_stats) { + client->GetPlayer()->SetHP(GetTotalHP()); + client->GetPlayer()->SetPower(GetTotalPower()); + } + /*info->set_agi_base(effective_level * 2 + 15); + info->set_intel_base(effective_level * 2 + 15); + info->set_wis_base(effective_level * 2 + 15); + info->set_str_base(effective_level * 2 + 15); + info->set_sta_base(effective_level * 2 + 15); + info->set_cold_base((int16)(effective_level * 1.5 + 10)); + info->set_heat_base((int16)(effective_level * 1.5 + 10)); + info->set_disease_base((int16)(effective_level * 1.5 + 10)); + info->set_mental_base((int16)(effective_level * 1.5 + 10)); + info->set_magic_base((int16)(effective_level * 1.5 + 10)); + info->set_divine_base((int16)(effective_level * 1.5 + 10)); + info->set_poison_base((int16)(effective_level * 1.5 + 10));*/ + GetClient()->ClearSentItemDetails(); + if(GetClient()) + { + EQ2Packet* app = GetEquipmentList()->serialize(GetClient()->GetVersion(), this); + if (app) { + GetClient()->QueuePacket(app); + } + } + GetEquipmentList()->SendEquippedItems(this); +} + +void Player::SetLevel(int16 level, bool setUpdateFlags) { + if(!GetGroupMemberInfo() || GetGroupMemberInfo()->mentor_target_char_id == 0) { + GetInfoStruct()->set_effective_level(level); + } + SetInfo(&appearance.level, level, setUpdateFlags); + SetXP(0); + SetNeededXP(); +} + +bool Player::SerializeItemPackets(EquipmentItemList* equipList, vector* packets, Item* item, int16 version, Item* to_item) { + if(item_list.AddItem(item)) { + item->save_needed = true; + SetEquippedItemAppearances(); + packets->push_back(equipList->serialize(version, this)); + packets->push_back(item->serialize(version, false)); + if(to_item) + packets->push_back(to_item->serialize(version, false, this)); + packets->push_back(item_list.serialize(this, version)); + return true; + } + else { + LogWrite(PLAYER__ERROR, 0, "Player", "failed to add item to item_list"); + } + return false; +} + +void Player::AddGMVisualFilter(int32 filter_type, int32 filter_value, char* filter_search_str, int16 visual_tag) { + if(MatchGMVisualFilter(filter_type, filter_value, filter_search_str) > 0) + return; + + vis_mutex.writelock(__FUNCTION__, __LINE__); + GMTagFilter filter; + filter.filter_type = filter_type; + filter.filter_value = filter_value; + memset(filter.filter_search_criteria, 0, sizeof(filter.filter_search_criteria)); + if(filter_search_str) + memcpy(&filter.filter_search_criteria, filter_search_str, strnlen(filter_search_str, sizeof(filter.filter_search_criteria))); + + filter.visual_tag = visual_tag; + gm_visual_filters.push_back(filter); + vis_mutex.releasewritelock(__FUNCTION__, __LINE__); +} + +int16 Player::MatchGMVisualFilter(int32 filter_type, int32 filter_value, char* filter_search_str, bool in_vismutex_lock) { + if(!in_vismutex_lock) + vis_mutex.readlock(__FUNCTION__, __LINE__); + int16 tag_id = 0; + vector::iterator itr = gm_visual_filters.begin(); + for(;itr != gm_visual_filters.end();itr++) { + if(itr->filter_type == filter_type && itr->filter_value == filter_value) { + if(filter_search_str && !strcasecmp(filter_search_str, itr->filter_search_criteria)) { + tag_id = itr->visual_tag; + break; + } + } + } + if(!in_vismutex_lock) + vis_mutex.releasereadlock(__FUNCTION__, __LINE__); + return tag_id; +} +void Player::ClearGMVisualFilters() { + vis_mutex.writelock(__FUNCTION__, __LINE__); + gm_visual_filters.clear(); + vis_mutex.releasewritelock(__FUNCTION__, __LINE__); +} + +int Player::GetPVPAlignment(){ + int bind_zone = GetPlayerInfo()->GetBindZoneID(); + int alignment = 0; + + if(bind_zone && bind_zone != 0){ + //0 is good. + //1 is evil. + //2 is neutral aka haven players. + switch(bind_zone){ + //good zones + case 114: //Gfay + case 221: //Qeynos Harbor + case 222: //North Qeynos + case 231: //South Qeynos + case 233: //Nettleville + case 234: //Starcrest + case 235: //Graystone + case 236: //CastleView + case 237: //Willowood + case 238: //Baubbleshire + case 470: //Frostfang + case 589: //Qeynos Combined 1 + case 660: //Qeynos Combined 2 + alignment = 0; //good + break; + //evil zones + case 128: //East Freeport + case 134: //Big Bend + case 135: //Stonestair + case 136: //Temple St. + case 137: //Beggars Ct. + case 138: //Longshadow + case 139: //Scale Yard + case 144: //North Freeport + case 166: //South Freeport + case 168: //West Freeport + case 184: //Neriak + case 644: //BigBend2 + case 645: //Stonestair2 + case 646: //Temple St2 + case 647: //Beggars Ct2 + case 648: //LongShadow2 + case 649: //Scale Yard2 + alignment = 1; //evil + break; + //Neutral (MajDul?) + case 45: //haven + case 46: //MajDul + alignment = 2; + break; + + default: + alignment = -1; //error + } + //return -1 (error), 0 (good), 1 (evil), or 2 (Neutral) + return alignment; + } + return -1; //error +} + +void Player::GetSpellBookSlotSort(int32 pattern, int32* i, int8* page_book_count, int32* last_start_point) { + switch(pattern) { + case 1: { // down + *i = (*i) + 2; + (*page_book_count)++; + if(*page_book_count > 3) { + if(((*i) % 2) == 0) { + (*i) = (*last_start_point) + 1; + } + else { + (*last_start_point) = (*last_start_point) + 8; + (*i) = (*last_start_point); + } + (*page_book_count) = 0; + } + break; + } + case 2: { // across + (*page_book_count)++; + switch(*page_book_count) { + case 1: + case 3: { + (*i)++; + break; + } + case 2: { + (*i) = (*i) + 7; + break; + } + case 4: { + (*last_start_point) = (*last_start_point) + 2; + (*i) = (*last_start_point); + (*page_book_count) = 0; + break; + } + } + break; + } + default: { // zig-zag + (*i)++; + break; + } + } +} + + +bool Player::IsSpawnInRangeList(int32 spawn_id) { + std::shared_lock lock(spawn_aggro_range_mutex); + map::iterator spawn_itr = player_aggro_range_spawns.find(spawn_id); + if(spawn_itr != player_aggro_range_spawns.end()) { + return spawn_itr->second; + } + return false; +} + +void Player::SetSpawnInRangeList(int32 spawn_id, bool in_range) { + std::unique_lock lock(spawn_aggro_range_mutex); + player_aggro_range_spawns[spawn_id] = in_range; +} + +void Player::ProcessSpawnRangeUpdates() { + std::unique_lock lock(spawn_aggro_range_mutex); + if(GetClient()->GetCurrentZone() == nullptr) { + return; + } + + map::iterator spawn_itr; + for(spawn_itr = player_aggro_range_spawns.begin(); spawn_itr != player_aggro_range_spawns.end();) { + if(spawn_itr->second) { + Spawn* spawn = GetClient()->GetCurrentZone()->GetSpawnByID(spawn_itr->first); + if(spawn && spawn->IsNPC() && (GetDistance(spawn)) > ((NPC*)spawn)->GetAggroRadius()) { + GetClient()->GetCurrentZone()->SendSpawnChanges((NPC*)spawn, GetClient(), true, true); + spawn_itr->second = false; + spawn_itr = player_aggro_range_spawns.erase(spawn_itr); + continue; + } + } + spawn_itr++; + } +} + +void Player::CalculatePlayerHPPower(int16 new_level) { + if(IsPlayer()) { + int16 effective_level = GetInfoStruct()->get_effective_level() != 0 ? GetInfoStruct()->get_effective_level() : GetLevel(); + if(new_level < 1) { + new_level = effective_level; + } + + float hp_rule_mod = rule_manager.GetGlobalRule(R_Player, StartHPLevelMod)->GetFloat(); + float power_rule_mod = rule_manager.GetGlobalRule(R_Player, StartPowerLevelMod)->GetFloat(); + + sint32 base_hp = rule_manager.GetGlobalRule(R_Player, StartHPBase)->GetFloat(); + sint32 base_power = rule_manager.GetGlobalRule(R_Player, StartPowerBase)->GetSInt32(); + + sint32 new_hp = (sint32)((float)new_level * (float)new_level * hp_rule_mod + base_hp); + sint32 new_power = (sint32)((float)new_level * (float)new_level * power_rule_mod + base_power); + + if(new_hp < 1) { + LogWrite(PLAYER__WARNING, 0, "Player", "Player HP Calculation for %s too low at level %u due to ruleset, StartPowerLevelMod %f, BasePower %i", GetName(), new_level, hp_rule_mod, base_hp); + new_hp = 1; + } + if(new_power < 1) { + LogWrite(PLAYER__WARNING, 0, "Player", "Player Power Calculations for %s too low at level %u due to ruleset, StartPowerLevelMod %f, BasePower %i", GetName(), new_level, power_rule_mod, base_power); + new_power = 1; + } + + SetTotalHPBase(new_hp); + SetTotalHPBaseInstance(new_hp); // we need the hp base to override the instance as the new default + + SetTotalPowerBase(new_power); + SetTotalPowerBaseInstance(new_power); // we need the hp base to override the instance as the new default + + LogWrite(PLAYER__INFO, 0, "Player", "Player %s: Level %u, Set Base HP %i, Set Base Power: %i", GetName(), new_level, power_rule_mod, base_power); + } +} + +bool Player::IsAllowedCombatEquip(int8 slot, bool send_message) { + bool rule_pass = true; + if(EngagedInCombat() && rule_manager.GetGlobalRule(R_Player, AllowPlayerEquipCombat)->GetInt8() == 0) { + switch(slot) { + case EQ2_PRIMARY_SLOT: + case EQ2_SECONDARY_SLOT: + case EQ2_RANGE_SLOT: + case EQ2_AMMO_SLOT: { + // good to go! + break; + } + default: { + if(send_message && GetClient()) { + GetClient()->SimpleMessage(CHANNEL_COLOR_RED, "You may not unequip/equip items while in combat."); + } + rule_pass = false; + break; + } + } + } + return rule_pass; +} + +void Player::SetActiveFoodUniqueID(int32 unique_id, bool update_db) { + active_food_unique_id = unique_id; + if(update_db) { + database.insertCharacterProperty(client, CHAR_PROPERTY_SETACTIVEFOOD, (char*)std::to_string(unique_id).c_str()); + } +} + +void Player::SetActiveDrinkUniqueID(int32 unique_id, bool update_db) { + active_drink_unique_id = unique_id; + if(update_db) { + database.insertCharacterProperty(client, CHAR_PROPERTY_SETACTIVEDRINK, (char*)std::to_string(unique_id).c_str()); + } +} + \ No newline at end of file diff --git a/source/WorldServer/Player.h b/source/WorldServer/Player.h new file mode 100644 index 0000000..b1491ac --- /dev/null +++ b/source/WorldServer/Player.h @@ -0,0 +1,1258 @@ +/* + 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 . +*/ +#ifndef __EQ2_PLAYER__ +#define __EQ2_PLAYER__ + +#include "Entity.h" +#include "Items/Items.h" +#include "Factions.h" +#include "Skills.h" +#include "Quests.h" +#include "MutexMap.h" +#include "Guilds/Guild.h" +#include "Collections/Collections.h" +#include "Recipes/Recipe.h" +#include "Titles.h" +#include "Languages.h" +#include "Achievements/Achievements.h" +#include "Traits/Traits.h" +#include +#include + +#define CF_COMBAT_EXPERIENCE_ENABLED 0 +#define CF_ENABLE_CHANGE_LASTNAME 1 +#define CF_FOOD_AUTO_CONSUME 2 +#define CF_DRINK_AUTO_CONSUME 3 +#define CF_AUTO_ATTACK 4 +#define CF_RANGED_AUTO_ATTACK 5 +#define CF_QUEST_EXPERIENCE_ENABLED 6 +#define CF_CHASE_CAMERA_MAYBE 7 +#define CF_100 8 +#define CF_200 9 +#define CF_IS_SITTING 10 /*CAN'T CAST OR ATTACK*/ +#define CF_800 11 +#define CF_ANONYMOUS 12 +#define CF_ROLEPLAYING 13 +#define CF_AFK 14 +#define CF_LFG 15 +#define CF_LFW 16 +#define CF_HIDE_HOOD 17 +#define CF_HIDE_HELM 18 +#define CF_SHOW_ILLUSION 19 +#define CF_ALLOW_DUEL_INVITES 20 +#define CF_ALLOW_TRADE_INVITES 21 +#define CF_ALLOW_GROUP_INVITES 22 +#define CF_ALLOW_RAID_INVITES 23 +#define CF_ALLOW_GUILD_INVITES 24 +#define CF_2000000 25 +#define CF_4000000 26 +#define CF_DEFENSE_SKILLS_AT_MAX_QUESTIONABLE 27 +#define CF_SHOW_GUILD_HERALDRY 28 +#define CF_SHOW_CLOAK 29 +#define CF_IN_PVP 30 +#define CF_IS_HATED 31 +#define CF2_1 32 +#define CF2_2 33 +#define CF2_4 34 +#define CF2_ALLOW_LON_INVITES 35 +#define CF2_SHOW_RANGED 36 +#define CF2_ALLOW_VOICE_INVITES 37 +#define CF2_CHARACTER_BONUS_EXPERIENCE_ENABLED 38 +#define CF2_80 39 +#define CF2_100 40 /* hide achievments*/ +#define CF2_200 41 +#define CF2_400 42 +#define CF2_800 43 /* enable facebook updates*/ +#define CF2_1000 44 /* enable twitter updates*/ +#define CF2_2000 45 /* enable eq2 player updates */ +#define CF2_4000 46 /*eq2 players, link to alt chars */ +#define CF2_8000 47 +#define CF2_10000 48 +#define CF2_20000 49 +#define CF2_40000 50 +#define CF2_80000 51 +#define CF2_100000 52 +#define CF2_200000 53 +#define CF2_400000 54 +#define CF2_800000 55 +#define CF2_1000000 56 +#define CF2_2000000 57 +#define CF2_4000000 58 +#define CF2_8000000 59 +#define CF2_10000000 60 +#define CF2_20000000 61 +#define CF2_40000000 62 +#define CF2_80000000 63 +#define CF_MAXIMUM_FLAG 63 +#define CF_HIDE_STATUS 49 /* !!FORTESTING ONLY!! */ +#define CF_GM_HIDDEN 50 /* !!FOR TESTING ONLY!! */ + +#define UPDATE_ACTIVITY_FALLING 0 +#define UPDATE_ACTIVITY_RUNNING 128 +#define UPDATE_ACTIVITY_RIDING_BOAT 256 +#define UPDATE_ACTIVITY_JUMPING 1024 +#define UPDATE_ACTIVITY_IN_WATER_ABOVE 6144 +#define UPDATE_ACTIVITY_IN_WATER_BELOW 6272 +#define UPDATE_ACTIVITY_SITING 6336 +#define UPDATE_ACTIVITY_DROWNING 14464 +#define UPDATE_ACTIVITY_DROWNING2 14336 + + + +#define UPDATE_ACTIVITY_FALLING_AOM 16384 +#define UPDATE_ACTIVITY_RIDING_BOAT_AOM 256 +#define UPDATE_ACTIVITY_RUNNING_AOM 16512 +#define UPDATE_ACTIVITY_JUMPING_AOM 17408 +#define UPDATE_ACTIVITY_MOVE_WATER_BELOW_AOM 22528 +#define UPDATE_ACTIVITY_MOVE_WATER_ABOVE_AOM 22656 +#define UPDATE_ACTIVITY_SITTING_AOM 22720 +#define UPDATE_ACTIVITY_DROWNING_AOM 30720 +#define UPDATE_ACTIVITY_DROWNING2_AOM 30848 + +#define NUM_MAINTAINED_EFFECTS 30 +#define NUM_SPELL_EFFECTS 45 + +/* Character History Type Defines */ +#define HISTORY_TYPE_NONE 0 +#define HISTORY_TYPE_DEATH 1 +#define HISTORY_TYPE_DISCOVERY 2 +#define HISTORY_TYPE_XP 3 + +/* Spell Status */ +#define SPELL_STATUS_QUEUE 4 +#define SPELL_STATUS_LOCK 66 + +/* Character History Sub Type Defines */ +#define HISTORY_SUBTYPE_NONE 0 +#define HISTORY_SUBTYPE_ADVENTURE 1 +#define HISTORY_SUBTYPE_TRADESKILL 2 +#define HISTORY_SUBTYPE_QUEST 3 +#define HISTORY_SUBTYPE_AA 4 +#define HISTORY_SUBTYPE_ITEM 5 +#define HISTORY_SUBTYPE_LOCATION 6 + +/// Character history data, should match the `character_history` table in the DB +struct HistoryData { + int32 Value; + int32 Value2; + char Location[200]; + int32 EventID; + int32 EventDate; + bool needs_save; +}; + +/// History set through the LUA system +struct LUAHistory { + int32 Value; + int32 Value2; + bool SaveNeeded; +}; + +struct SpellBookEntry{ + int32 spell_id; + int8 tier; + int32 type; + sint32 slot; + int32 recast_available; + int8 status; + int16 recast; + int32 timer; + bool save_needed; + bool in_use; + bool in_remiss; + Player* player; + bool visible; +}; + +struct GMTagFilter { + int32 filter_type; + int32 filter_value; + char filter_search_criteria[256]; + int16 visual_tag; +}; + +enum GMTagFilterType { + GMFILTERTYPE_NONE=0, + GMFILTERTYPE_FACTION=1, + GMFILTERTYPE_SPAWNGROUP=2, + GMFILTERTYPE_RACE=3, + GMFILTERTYPE_GROUNDSPAWN=4 +}; +enum SpawnState{ + SPAWN_STATE_NONE=0, + SPAWN_STATE_SENDING=1, + SPAWN_STATE_SENT_WAIT=2, + SPAWN_STATE_SENT=3, + SPAWN_STATE_REMOVING=4, + SPAWN_STATE_REMOVING_SLEEP=5, + SPAWN_STATE_REMOVED=6 +}; +#define QUICKBAR_NORMAL 1 +#define QUICKBAR_INV_SLOT 2 +#define QUICKBAR_MACRO 3 +#define QUICKBAR_TEXT_CMD 4 +#define QUICKBAR_ITEM 6 + +#define EXP_DISABLED_STATE 0 +#define EXP_ENABLED_STATE 1 +#define MELEE_COMBAT_STATE 16 +#define RANGE_COMBAT_STATE 32 + +struct QuickBarItem{ + bool deleted; + int32 hotbar; + int32 slot; + int32 type; + int16 icon; + int16 icon_type; + int32 id; + int8 tier; + int32 unique_id; + EQ2_16BitString text; +}; + +struct LoginAppearances { + bool deleted; + int16 equip_type; + int8 red; + int8 green; + int8 blue; + int8 h_red; + int8 h_green; + int8 h_blue; + bool update_needed; +}; + +struct SpawnQueueState { + Timer spawn_state_timer; + int16 index_id; +}; + +class PlayerLoginAppearance { +public: + PlayerLoginAppearance() { appearanceList = new map; } + ~PlayerLoginAppearance() { } + + void AddEquipmentToUpdate(int8 slot_id, LoginAppearances* equip) + { + //LoginAppearances data; + //data.equip_type = equip->equip_type; + //appearanceList[slot_id] = data; + } + + void DeleteEquipmentFromUpdate(int8 slot_id, LoginAppearances* equip) + { + //LoginAppearances data; + //data.deleted = equip->deleted; + //data.update_needed = true; + //appearanceList[slot_id] = data; + } + + void RemoveEquipmentUpdates() + { + appearanceList->clear(); + safe_delete(appearanceList); + } + +private: + map* appearanceList; +}; + + +struct InstanceData{ + int32 db_id; + int32 instance_id; + int32 zone_id; + int8 zone_instance_type; + string zone_name; + int32 last_success_timestamp; + int32 last_failure_timestamp; + int32 success_lockout_time; + int32 failure_lockout_time; +}; + +class CharacterInstances { +public: + CharacterInstances(); + ~CharacterInstances(); + + ///Adds an instance data to the player with the given data + ///The unique id for this record in the database + ///The id of the instance + ///The success timestamp + ///The failure timestamp + ///The lockout time, in secs, for completing the instance + ///The lockout time, in secs, for failing the instance + ///The id of the zone + ///The type of instance of the zone + ///The name of the zone + void AddInstance(int32 db_id, int32 instance_id, int32 last_success_timestamp, int32 last_failure_timestamp, int32 success_lockout_time, int32 failure_lockout_time, int32 zone_id, int8 zone_instancetype, string zone_name); + + ///Clears all instance data + void RemoveInstances(); + + ///Removes the instace with the given zone id + ///The zone id of the instance to remove + ///True if the instance was found and removed + bool RemoveInstanceByZoneID(int32 zone_id); + + ///Removes the instance with the given instance id + ///the instance id of the instance to remove + ///True if instance was found and removed + bool RemoveInstanceByInstanceID(int32 instance_id); + + ///Gets the instance with the given zone id + ///The zone id of the instance to get + ///InstanceData* of the instance record for the given zone id + InstanceData* FindInstanceByZoneID(int32 zone_id); + + ///Gets the instance with the given database id + ///The database id of the instance to get + ///InstanceData* of the instance record for the given database id + InstanceData* FindInstanceByDBID(int32 db_id); + + ///Gets the instance with the given instance id + ///The instance id of the instance to get + ///InstanceData* of the instance record for the given instance id + InstanceData* FindInstanceByInstanceID(int32 instance_id); + + ///Gets a list of all the lockout instances + vector GetLockoutInstances(); + + ///Gets a list of all the persistent instances + vector GetPersistentInstances(); + + ///Check the timers for the instances + ///player we are checking the timers for + void ProcessInstanceTimers(Player* player); + + ///Gets the total number of instances + int32 GetInstanceCount(); +private: + vector instanceList; + Mutex m_instanceList; +}; + +class Player; +struct PlayerGroup; +struct GroupMemberInfo; +struct Statistic; +struct Mail; +class PlayerInfo { +public: + ~PlayerInfo(); + PlayerInfo(Player* in_player); + + EQ2Packet* serialize(int16 version, int16 modifyPos = 0, int32 modifyValue = 0); + PacketStruct* serialize2(int16 version); + EQ2Packet* serialize3(PacketStruct* packet, int16 version); + EQ2Packet* serializePet(int16 version); + void CalculateXPPercentages(); + void CalculateTSXPPercentages(); + void SetHouseZone(int32 id); + void SetBindZone(int32 id); + void SetBindX(float x); + void SetBindY(float y); + void SetBindZ(float z); + void SetBindHeading(float heading); + void SetAccountAge(int32 days); + int32 GetHouseZoneID(); + int32 GetBindZoneID(); + float GetBindZoneX(); + float GetBindZoneY(); + float GetBindZoneZ(); + float GetBindZoneHeading(); + float GetBoatX() { return boat_x_offset; } + float GetBoatY() { return boat_y_offset; } + float GetBoatZ() { return boat_z_offset; } + int32 GetBoatSpawn(); + void SetBoatX(float x) { boat_x_offset = x; } + void SetBoatY(float y) { boat_y_offset = y; } + void SetBoatZ(float z) { boat_z_offset = z; } + void SetBoatSpawn(Spawn* boat); + void RemoveOldPackets(); + +private: + int32 house_zone_id; + int32 bind_zone_id; + float bind_x; + float bind_y; + float bind_z; + float bind_heading; + uchar* changes; + uchar* orig_packet; + uchar* pet_changes; + uchar* pet_orig_packet; + InfoStruct* info_struct; + Player* player; + float boat_x_offset; + float boat_y_offset; + float boat_z_offset; + int32 boat_spawn; +}; + +class PlayerControlFlags{ +public: + PlayerControlFlags(); + ~PlayerControlFlags(); + + void SetPlayerControlFlag(int8 param, int8 param_value, bool is_active); + bool ControlFlagsChanged(); + void SendControlFlagUpdates(Client* client); +private: + bool flags_changed; + map > flag_changes; + map > current_flags; + Mutex MControlFlags; + Mutex MFlagChanges; +}; + +class Player : public Entity{ +public: + Player(); + virtual ~Player(); + EQ2Packet* serialize(Player* player, int16 version); + //int8 GetMaxArtLevel(){ return info->GetInfo()->max_art_level; } + //int8 GetArtLevel(){ return info->GetInfo()->art_level; } + + Client* GetClient() { return client; } + void SetClient(Client* client) { this->client = client; } + PlayerInfo* GetPlayerInfo(); + void SetCharSheetChanged(bool val); + bool GetCharSheetChanged(); + void AddFriend(const char* name, bool save); + bool IsFriend(const char* name); + void RemoveFriend(const char* name); + map* GetFriends(); + void AddIgnore(const char* name, bool save); + bool IsIgnored(const char* name); + void RemoveIgnore(const char* name); + map* GetIgnoredPlayers(); + + // JA: POI Discoveries + map >* GetPlayerDiscoveredPOIs(); + void AddPlayerDiscoveredPOI(int32 location_id); + // + + EQ2Packet* Move(float x, float y, float z, int16 version, float heading = -1.0f); + + /*void SetMaxArtLevel(int8 new_max){ + max_art_level = new_max; + } + void SetArtLevel(int8 new_lvl){ + art_level = new_lvl; + }*/ + bool WasSentSpawn(int32 spawn_id); + bool IsSendingSpawn(int32 spawn_id); + bool IsRemovingSpawn(int32 spawn_id); + bool SetSpawnSentState(Spawn* spawn, SpawnState state); + void CheckSpawnStateQueue(); + void SetSideSpeed(float side_speed, bool updateFlags = true) { + SetPos(&appearance.pos.SideSpeed, side_speed, updateFlags); + } + float GetSideSpeed() { + return appearance.pos.SideSpeed; + } + void SetVertSpeed(float vert_speed, bool updateFlags = true) { + SetPos(&appearance.pos.VertSpeed, vert_speed, updateFlags); + } + float GetVertSpeed() { + return appearance.pos.VertSpeed; + } + + void SetClientHeading1(float heading, bool updateFlags = true) { + SetPos(&appearance.pos.ClientHeading1, heading, updateFlags); + } + float GetClientHeading1() { + return appearance.pos.ClientHeading1; + } + + void SetClientHeading2(float heading, bool updateFlags = true) { + SetPos(&appearance.pos.ClientHeading2, heading, updateFlags); + } + float GetClientHeading2() { + return appearance.pos.ClientHeading2; + } + + void SetClientPitch(float pitch, bool updateFlags = true) { + SetPos(&appearance.pos.ClientPitch, pitch, updateFlags); + } + float GetClientPitch() { + return appearance.pos.ClientPitch; + } + + int8 GetTutorialStep() { + return tutorial_step; + } + void SetTutorialStep(int8 val) { + tutorial_step = val; + } + void AddMaintainedSpell(LuaSpell* spell); + void AddSpellEffect(LuaSpell* spell, int32 override_expire_time = 0); + void RemoveMaintainedSpell(LuaSpell* spell); + void RemoveSpellEffect(LuaSpell* spell); + void AddQuickbarItem(int32 bar, int32 slot, int32 type, int16 icon, int16 icon_type, int32 id, int8 tier, int32 unique_id, const char* text, bool update = true); + void RemoveQuickbarItem(int32 bar, int32 slot, bool update = true); + void MoveQuickbarItem(int32 id, int32 new_slot); + void ClearQuickbarItems(); + PlayerItemList* GetPlayerItemList(); + PlayerItemList item_list; + PlayerSkillList skill_list; + Skill* GetSkillByName(const char* name, bool check_update = false); + Skill* GetSkillByID(int32 skill_id, bool check_update = false); + PlayerSkillList* GetSkills(); + bool DamageEquippedItems(int8 amount = 10, Client* client = 0); + vector EquipItem(int16 index, int16 version, int8 appearance_type, int8 slot_id = 255); + bool CanEquipItem(Item* item, int8 slot); + void SetEquippedItemAppearances(); + vector UnequipItem(int16 index, sint32 bag_id, int8 slot, int16 version, int8 appearance_type = 0, bool send_item_updates = true); + int16 ConvertSlotToClient(int8 slot, int16 version); + int16 ConvertSlotFromClient(int8 slot, int16 version); + int16 GetNumSlotsEquip(int16 version); + int8 GetMaxBagSlots(int16 version); + EQ2Packet* SwapEquippedItems(int8 slot1, int8 slot2, int16 version, int16 equiptype); + EQ2Packet* RemoveInventoryItem(int8 bag_slot, int8 slot); + EQ2Packet* SendInventoryUpdate(int16 version); + EQ2Packet* SendBagUpdate(int32 bag_unique_id, int16 version); + void SendQuestRequiredSpawns(int32 quest_id); + void SendHistoryRequiredSpawns(int32 event_id); + map* GetItemList(); + map* GetBankItemList(); + vector* GetEquippedItemList(); + vector* GetAppearanceEquippedItemList(); + Quest* SetStepComplete(int32 quest_id, int32 step); + Quest* AddStepProgress(int32 quest_id, int32 step, int32 progress); + int32 GetStepProgress(int32 quest_id, int32 step_id); + Quest* GetQuestByPositionID(int32 list_position_id); + bool AddItem(Item* item, AddItemType type = AddItemType::NOT_SET); + bool AddItemToBank(Item* item); + int16 GetSpellSlotMappingCount(); + int16 GetSpellPacketCount(); + Quest* GetQuest(int32 quest_id); + bool GetQuestStepComplete(int32 quest_id, int32 step_id); + int16 GetQuestStep(int32 quest_id); + int16 GetTaskGroupStep(int32 quest_id); + int8 GetSpellTier(int32 id); + void SetSpellStatus(Spell* spell, int8 status); + void RemoveSpellStatus(Spell* spell, int8 status); + EQ2Packet* GetSpellBookUpdatePacket(int16 version); + EQ2Packet* GetSpellSlotMappingPacket(int16 version); + int32 GetCharacterID(); + void SetCharacterID(int32 new_id); + EQ2Packet* GetQuickbarPacket(int16 version); + vector* GetQuickbar(); + bool UpdateQuickbarNeeded(); + void ResetQuickbarNeeded(); + void set_character_flag(int flag); + void reset_character_flag(int flag); + void toggle_character_flag(int flag); + bool get_character_flag(int flag); + void AddCoins(int64 val); + bool RemoveCoins(int64 val); + /// Checks to see if the player has the given amount of coins + /// Amount of coins to check + /// True if the player has enough coins + bool HasCoins(int64 val); + void AddSkill(int32 skill_id, int16 current_val, int16 max_val, bool save_needed = false); + void RemovePlayerSkill(int32 skill_id, bool save = false); + void RemoveSkillFromDB(Skill* skill, bool save = false); + void AddSpellBookEntry(int32 spell_id, int8 tier, sint32 slot, int32 type, int32 timer, bool save_needed = false); + SpellBookEntry* GetSpellBookSpell(int32 spell_id); + vector* GetSpellsSaveNeeded(); + sint32 GetFreeSpellBookSlot(int32 type); + /// Get a vector of spell ids for all spells in the spell book for the given skill + /// The id of the skill to check + /// A vector of int32's of the spell id's + vector GetSpellBookSpellIDBySkill(int32 skill_id); + void UpdateInventory(int32 bag_id); + EQ2Packet* MoveInventoryItem(sint32 to_bag_id, int16 from_index, int8 new_slot, int8 charges, int8 appearance_type, bool* item_deleted, int16 version = 1); + bool IsPlayer(){ return true; } + MaintainedEffects* GetFreeMaintainedSpellSlot(); + MaintainedEffects* GetMaintainedSpell(int32 id); + MaintainedEffects* GetMaintainedSpellBySlot(int8 slot); + MaintainedEffects* GetMaintainedSpells(); + SpellEffects* GetFreeSpellEffectSlot(); + SpellEffects* GetSpellEffects(); + int32 GetCoinsCopper(); + int32 GetCoinsSilver(); + int32 GetCoinsGold(); + int32 GetCoinsPlat(); + int32 GetBankCoinsCopper(); + int32 GetBankCoinsSilver(); + int32 GetBankCoinsGold(); + int32 GetBankCoinsPlat(); + int32 GetStatusPoints(); + float GetXPVitality(); + float GetTSXPVitality(); + bool AdventureXPEnabled(); + bool TradeskillXPEnabled(); + void SetNeededXP(int32 val); + void SetNeededXP(); + void SetXP(int32 val); + void SetNeededTSXP(int32 val); + void SetNeededTSXP(); + void SetTSXP(int32 val); + int32 GetNeededXP(); + float GetXPDebt(); + int32 GetXP(); + int32 GetNeededTSXP(); + int32 GetTSXP(); + bool AddXP(int32 xp_amount); + bool AddTSXP(int32 xp_amount); + bool DoubleXPEnabled(); + float CalculateXP(Spawn* victim); + float CalculateTSXP(int8 level); + void CalculateOfflineDebtRecovery(int32 unix_timestamp); + void InCombat(bool val, bool range = false); + void PrepareIncomingMovementPacket(int32 len, uchar* data, int16 version); + uchar* GetMovementPacketData(){ + return movement_packet; + } + void AddSpawnInfoPacketForXOR(int32 spawn_id, uchar* packet, int16 packet_size); + uchar* GetSpawnInfoPacketForXOR(int32 spawn_id); + void AddSpawnVisPacketForXOR(int32 spawn_id, uchar* packet, int16 packet_size); + uchar* GetSpawnVisPacketForXOR(int32 spawn_id); + void AddSpawnPosPacketForXOR(int32 spawn_id, uchar* packet, int16 packet_size); + uchar* GetSpawnPosPacketForXOR(int32 spawn_id); + uchar* GetTempInfoPacketForXOR(); + uchar* GetTempVisPacketForXOR(); + uchar* GetTempPosPacketForXOR(); + uchar* SetTempInfoPacketForXOR(int16 size); + uchar* SetTempVisPacketForXOR(int16 size); + uchar* SetTempPosPacketForXOR(int16 size); + int32 GetTempInfoXorSize() { return info_xor_size; } + int32 GetTempVisXorSize() { return vis_xor_size; } + int32 GetTempPosXorSize() { return pos_xor_size; } + bool CheckPlayerInfo(); + void CalculateLocation(); + void SetSpawnDeleteTime(int32 id, int32 time); + int32 GetSpawnDeleteTime(int32 id); + void ClearRemovalTimers(); + void ClearEverything(); + bool IsResurrecting(); + void SetResurrecting(bool val); + int8 GetTSArrowColor(int8 level); + Spawn* GetSpawnByIndex(int16 index); + int16 GetIndexForSpawn(Spawn* spawn); + bool WasSpawnRemoved(Spawn* spawn); + void RemoveSpawn(Spawn* spawn, bool delete_spawn = true); + bool ShouldSendSpawn(Spawn* spawn); + Client* client = 0; + void SetLevel(int16 level, bool setUpdateFlags = true); + + Spawn* GetSpawnWithPlayerID(int32 id){ + Spawn* spawn = 0; + + index_mutex.readlock(__FUNCTION__, __LINE__); + if (player_spawn_id_map.count(id) > 0) + spawn = player_spawn_id_map[id]; + index_mutex.releasereadlock(__FUNCTION__, __LINE__); + return spawn; + } + int32 GetIDWithPlayerSpawn(Spawn* spawn){ + int32 id = 0; + + index_mutex.readlock(__FUNCTION__, __LINE__); + if (player_spawn_reverse_id_map.count(spawn) > 0) + id = player_spawn_reverse_id_map[spawn]; + index_mutex.releasereadlock(__FUNCTION__, __LINE__); + + return id; + } + + int16 GetNextSpawnIndex(Spawn* spawn, bool set_lock = true); + bool SetSpawnMap(Spawn* spawn); + + void SetSpawnMapIndex(Spawn* spawn, int32 index) + { + index_mutex.writelock(__FUNCTION__, __LINE__); + if (player_spawn_id_map.count(index)) + player_spawn_id_map[index] = spawn; + else + player_spawn_id_map[index] = spawn; + index_mutex.releasewritelock(__FUNCTION__, __LINE__); + } + + int16 SetSpawnMapAndIndex(Spawn* spawn); + + PacketStruct* GetQuestJournalPacket(bool all_quests, int16 version, int32 crc, int32 current_quest_id, bool updated = true); + void RemoveQuest(int32 id, bool delete_quest); + vector* CheckQuestsChatUpdate(Spawn* spawn); + vector* CheckQuestsItemUpdate(Item* item); + vector* CheckQuestsLocationUpdate(); + vector* CheckQuestsKillUpdate(Spawn* spawn,bool update = true); + bool HasQuestUpdateRequirement(Spawn* spawn); + vector* CheckQuestsSpellUpdate(Spell* spell); + void CheckQuestsCraftUpdate(Item* item, int32 qty); + void CheckQuestsHarvestUpdate(Item* item, int32 qty); + vector* CheckQuestsFailures(); + bool CheckQuestRemoveFlag(Spawn* spawn); + int8 CheckQuestFlag(Spawn* spawn); + bool UpdateQuestReward(int32 quest_id, QuestRewardData* qrd); + Quest* PendingQuestAcceptance(int32 quest_id, int32 item_id, bool* quest_exists); + bool AcceptQuestReward(int32 item_id, int32 selectable_item_id); + + bool SendQuestStepUpdate(int32 quest_id, int32 quest_step_id, bool display_quest_helper); + void SendQuest(int32 quest_id); + void UpdateQuestCompleteCount(int32 quest_id); + void GetQuestTemporaryRewards(int32 quest_id, std::vector* items); + void AddQuestTemporaryReward(int32 quest_id, int32 item_id, int16 item_count); + + bool CheckQuestRequired(Spawn* spawn); + void AddQuestRequiredSpawn(Spawn* spawn, int32 quest_id); + void AddHistoryRequiredSpawn(Spawn* spawn, int32 event_id); + int16 spawn_index; + int32 spawn_id; + int8 tutorial_step; + map*> player_spawn_quests_required; + map*> player_spawn_history_required; + Mutex m_playerSpawnQuestsRequired; + Mutex m_playerSpawnHistoryRequired; + bool HasQuestBeenCompleted(int32 quest_id); + int32 GetQuestCompletedCount(int32 quest_id); + void AddCompletedQuest(Quest* quest); + bool HasActiveQuest(int32 quest_id); + bool HasAnyQuest(int32 quest_id); + map pending_quests; + map player_quests; + map* GetPlayerQuests(); + map* GetCompletedPlayerQuests(); + void SetFactionValue(int32 faction_id, sint32 value){ + factions.SetFactionValue(faction_id, value); + } + PlayerFaction* GetFactions(){ + return &factions; + } + vector GetQuestIDs(); + map macro_icons; + + bool HasPendingLootItems(int32 id); + bool HasPendingLootItem(int32 id, int32 item_id); + vector* GetPendingLootItems(int32 id); + void RemovePendingLootItem(int32 id, int32 item_id); + void RemovePendingLootItems(int32 id); + void AddPendingLootItems(int32 id, vector* items); + int16 GetTierUp(int16 tier); + bool HasSpell(int32 spell_id, int8 tier = 255, bool include_higher_tiers = false, bool include_possible_scribe = false); + bool HasRecipeBook(int32 recipe_id); + void AddPlayerStatistic(int32 stat_id, sint32 stat_value, int32 stat_date); + void UpdatePlayerStatistic(int32 stat_id, sint32 stat_value, bool overwrite = false); + sint64 GetPlayerStatisticValue(int32 stat_id); + void WritePlayerStatistics(); + + + + //PlayerGroup* GetGroup(); + void SetGroup(PlayerGroup* group); + bool IsGroupMember(Entity* player); + void SetGroupInformation(PacketStruct* packet); + + + void ResetSavedSpawns(); + bool IsReturningFromLD(); + void SetReturningFromLD(bool val); + bool CheckLevelStatus(int16 new_level); + int16 GetLastMovementActivity(); + void DestroyQuests(); + string GetAwayMessage() const { return away_message; } + void SetAwayMessage(string val) { away_message = val; } + void SetRangeAttack(bool val); + bool GetRangeAttack(); + ZoneServer* GetGroupMemberInZone(int32 zone_id); + bool AddMail(Mail* mail); + MutexMap* GetMail(); + Mail* GetMail(int32 mail_id); + void DeleteMail(bool from_database = false); + void DeleteMail(int32 mail_id, bool from_database = false); + CharacterInstances* GetCharacterInstances() { return &character_instances; } + void SetIsTracking(bool val) { is_tracking = val; } + bool GetIsTracking() const { return is_tracking; } + void SetBiography(string new_biography) { biography = new_biography; } + string GetBiography() const { return biography; } + void SetPlayerAdventureClass(int8 new_class); + void SetGuild(Guild* new_guild) { guild = new_guild; } + Guild* GetGuild() { return guild; } + void AddSkillBonus(int32 spell_id, int32 skill_id, float value); + SkillBonus* GetSkillBonus(int32 spell_id); + virtual void RemoveSkillBonus(int32 spell_id); + + virtual bool CanSeeInvis(Entity* target); + bool CheckChangeInvisHistory(Entity* target); + void UpdateTargetInvisHistory(int32 targetID, bool canSeeStatus); + void RemoveTargetInvisHistory(int32 targetID); + + bool HasFreeBankSlot(); + int8 FindFreeBankSlot(); + PlayerCollectionList * GetCollectionList() { return &collection_list; } + PlayerRecipeList * GetRecipeList() { return &recipe_list; } + PlayerRecipeBookList * GetRecipeBookList() { return &recipebook_list; } + PlayerAchievementList * GetAchievementList() { return &achievement_list; } + PlayerAchievementUpdateList * GetAchievementUpdateList() { return &achievement_update_list; } + void SetPendingCollectionReward(Collection *collection) { pending_collection_reward = collection; } + Collection * GetPendingCollectionReward() { return pending_collection_reward; } + void AddPendingSelectableItemReward(int32 source_id, Item* item) { + if (pending_selectable_item_rewards.count(source_id) == 0) + pending_selectable_item_rewards[source_id] = vector(); + pending_selectable_item_rewards[source_id].push_back(item); + } + void AddPendingItemReward(Item* item) { + pending_item_rewards.push_back(item); + } + bool HasPendingItemRewards() { return (pending_item_rewards.size() > 0 || pending_selectable_item_rewards.size() > 0); } + vector GetPendingItemRewards() { return pending_item_rewards; } + map GetPendingSelectableItemReward(int32 item_id) { //since the client sends the selected item id, we need to have the associated source and remove all of them. Yes, there is an edge case if multiple sources have the same Item in them, but limited on what the client sends (just a single item id) + map ret; + if (pending_selectable_item_rewards.size() > 0) { + map>::iterator map_itr; + for (map_itr = pending_selectable_item_rewards.begin(); map_itr != pending_selectable_item_rewards.end(); map_itr++) { + vector::iterator itr; + for (itr = map_itr->second.begin(); itr != map_itr->second.end(); itr++) { + if ((*itr)->details.item_id == item_id) { + ret[map_itr->first] = *itr; + break; + } + } + if (ret.size() > 0) + break; + } + } + return map(); + } + void ClearPendingSelectableItemRewards(int32 source_id, bool all = false) { + if (pending_selectable_item_rewards.size() > 0) { + map>::iterator map_itr; + if (all) { + for (map_itr = pending_selectable_item_rewards.begin(); map_itr != pending_selectable_item_rewards.end(); map_itr++) { + vector::iterator itr; + for (itr = map_itr->second.begin(); itr != map_itr->second.end(); itr++) { + safe_delete(*itr); + } + } + pending_selectable_item_rewards.clear(); + } + else { + if (pending_selectable_item_rewards.count(source_id) > 0) { + vector::iterator itr; + for (itr = pending_selectable_item_rewards[source_id].begin(); itr != pending_selectable_item_rewards[source_id].end(); itr++) { + safe_delete(*itr); + } + pending_selectable_item_rewards.erase(source_id); + } + } + } + } + void ClearPendingItemRewards() { //the client doesn't send any reference to where the pending rewards came from, so if they collect one, we should just them all of them at once + if (pending_item_rewards.size() > 0) { + vector::iterator itr; + for (itr = pending_item_rewards.begin(); itr != pending_item_rewards.end(); itr++) { + safe_delete(*itr); + } + pending_item_rewards.clear(); + } + } + + enum DELETE_BOOK_TYPE { + DELETE_TRADESKILLS = 1, + DELETE_SPELLS = 2, + DELETE_COMBAT_ART = 4, + DELETE_ABILITY = 8, + DELETE_NOT_SHOWN = 16 + }; + void DeleteSpellBook(int8 type_selection = 0); + void RemoveSpellBookEntry(int32 spell_id, bool remove_passives_from_list = true); + void ResortSpellBook(int32 sort_by, int32 order, int32 pattern, int32 maxlvl_only, int32 book_type); + void GetSpellBookSlotSort(int32 pattern, int32* i, int8* page_book_count, int32* last_start_point); + static bool SortSpellEntryByName(SpellBookEntry* s1, SpellBookEntry* s2); + static bool SortSpellEntryByCategory(SpellBookEntry* s1, SpellBookEntry* s2); + static bool SortSpellEntryByLevel(SpellBookEntry* s1, SpellBookEntry* s2); + static bool SortSpellEntryByNameReverse(SpellBookEntry* s1, SpellBookEntry* s2); + static bool SortSpellEntryByCategoryReverse(SpellBookEntry* s1, SpellBookEntry* s2); + static bool SortSpellEntryByLevelReverse(SpellBookEntry* s1, SpellBookEntry* s2); + + int8 GetSpellSlot(int32 spell_id); + void AddTitle(sint32 title_id, const char *name, int8 prefix, bool save_needed = false); + void AddAAEntry(int16 template_id, int8 tab_id, int32 aa_id, int16 order, int8 treeid); + PlayerTitlesList* GetPlayerTitles() { return &player_titles_list; } + void AddLanguage(int32 id, const char *name, bool save_needed = false); + PlayerLanguagesList* GetPlayerLanguages() { return &player_languages_list; } + bool HasLanguage(int32 id); + bool HasLanguage(const char* name); + bool CanReceiveQuest(int32 quest_id, int8* ret = 0); + float GetBoatX() { if (info) return info->GetBoatX(); return 0; } + float GetBoatY() { if (info) return info->GetBoatY(); return 0; } + float GetBoatZ() { if (info) return info->GetBoatZ(); return 0; } + int32 GetBoatSpawn() { if (info) return info->GetBoatSpawn(); return 0; } + void SetBoatX(float x) { if (info) info->SetBoatX(x); } + void SetBoatY(float y) { if (info) info->SetBoatY(y); } + void SetBoatZ(float z) { if (info) info->SetBoatZ(z); } + void SetBoatSpawn(Spawn* boat) { if (info) info->SetBoatSpawn(boat); } + Mutex* GetGroupBuffMutex(); + void SetPendingDeletion(bool val) { pending_deletion = val; } + bool GetPendingDeletion() { return pending_deletion; } + float GetPosPacketSpeed() { return pos_packet_speed; } + bool ControlFlagsChanged(); + void SetPlayerControlFlag(int8 param, int8 param_value, bool is_active); + void SendControlFlagUpdates(Client* client); + + /// Casts all the passive spells for the player, only call after zoning is complete. + void ApplyPassiveSpells(); + + /// Removes all passive spell effects from the player and clears the passive list + void RemoveAllPassives(); + + /// Gets the current recipie ID + int32 GetCurrentRecipe() { return current_recipe; } + + /// Sets the current recipie ID + /// Id of the new recipe + void SetCurrentRecipe(int32 val) { current_recipe = val; } + + /// Reset the pet window info + void ResetPetInfo(); + + void ProcessCombat(); + + /* Character history stuff */ + + /// Adds a new history event to the player + /// The history type + /// The history sub type + /// The first history value + /// The second history value + void UpdatePlayerHistory(int8 type, int8 subtype, int32 value, int32 value2 = 0); + + /// Checks to see if the player has discovered the location + /// The ID of the location to check + /// True if the player has discovered the location + bool DiscoveredLocation(int32 locationID); + + /// Load the players history from the database + /// The history type + /// The history sub type + /// The history data + void LoadPlayerHistory(int8 type, int8 subtype, HistoryData* hd); + + /// Save the player's history to the database + void SaveHistory(); + + + /* New functions for spell locking and unlocking*/ + /// Lock all Spells, Combat arts, and Abilities (not trade skill spells) + void LockAllSpells(); + + /// Unlocks all Spells, Combat arts, and Abilities (not trade skill spells) + void UnlockAllSpells(bool modify_recast = false, Spell* exception = 0); + + /// Locks the given spell as well as all spells with a shared timer + void LockSpell(Spell* spell, int16 recast); + + /// Unlocks the given spell as well as all spells with shared timers + void UnlockSpell(Spell* spell); + + /// Locks all ts spells and unlocks all normal spells + void LockTSSpells(); + + /// Unlocks all ts spells and locks all normal spells + void UnlockTSSpells(); + + /// Queue the given spell + void QueueSpell(Spell* spell); + + /// Unqueue the given spell + void UnQueueSpell(Spell* spell); + + ///Get all the spells the player has with the given id + vector GetSpellBookSpellsByTimer(Spell* spell, int32 timerID); + + PacketStruct* GetQuestJournalPacket(Quest* quest, int16 version, int32 crc, bool updated = true); + + void SetSpawnInfoStruct(PacketStruct* packet) { safe_delete(spawn_info_struct); spawn_info_struct = packet; } + void SetSpawnVisStruct(PacketStruct* packet) { safe_delete(spawn_vis_struct); spawn_vis_struct = packet; } + void SetSpawnPosStruct(PacketStruct* packet) { safe_delete(spawn_pos_struct); spawn_pos_struct = packet; } + void SetSpawnHeaderStruct(PacketStruct* packet) { safe_delete(spawn_header_struct); spawn_header_struct = packet; } + void SetSpawnFooterStruct(PacketStruct* packet) { safe_delete(spawn_footer_struct); spawn_footer_struct = packet; } + void SetSignFooterStruct(PacketStruct* packet) { safe_delete(sign_footer_struct); sign_footer_struct = packet; } + void SetWidgetFooterStruct(PacketStruct* packet) { safe_delete(widget_footer_struct); widget_footer_struct = packet; } + + PacketStruct* GetSpawnInfoStruct() { return spawn_info_struct; } + PacketStruct* GetSpawnVisStruct() { return spawn_vis_struct; } + PacketStruct* GetSpawnPosStruct() { return spawn_pos_struct; } + PacketStruct* GetSpawnHeaderStruct() { return spawn_header_struct; } + PacketStruct* GetSpawnFooterStruct() { return spawn_footer_struct; } + PacketStruct* GetSignFooterStruct() { return sign_footer_struct; } + PacketStruct* GetWidgetFooterStruct() { return widget_footer_struct; } + + Mutex info_mutex; + Mutex pos_mutex; + Mutex vis_mutex; + Mutex index_mutex; + Mutex spawn_mutex; + mutable std::shared_mutex spawn_aggro_range_mutex; + + void SetTempMount(int32 id) { tmp_mount_model = id; } + int32 GetTempMount() { return tmp_mount_model; } + + void SetTempMountColor(EQ2_Color* color) { tmp_mount_color = *color; } + EQ2_Color GetTempMountColor() { return tmp_mount_color; } + + void SetTempMountSaddleColor(EQ2_Color* color) { tmp_mount_saddle_color = *color; } + EQ2_Color GetTempMountSaddleColor() { return tmp_mount_saddle_color; } + + + void LoadLUAHistory(int32 event_id, LUAHistory* history); + void SaveLUAHistory(); + void UpdateLUAHistory(int32 event_id, int32 value, int32 value2); + LUAHistory* GetLUAHistory(int32 event_id); + + bool HasGMVision() { return gm_vision; } + void SetGMVision(bool val) { gm_vision = val; } + + void StopCombat(int8 type=0) { + switch(type) + { + case 2: + SetRangeAttack(false); + InCombat(false, true); + break; + default: + InCombat(false); + InCombat(false, true); + SetRangeAttack(false); + break; + } + } + + NPC* InstantiateSpiritShard(float origX, float origY, float origZ, float origHeading, int32 origGridID, ZoneServer* origZone); + + void DismissAllPets(); + + void SaveSpellEffects(); + + void SetSaveSpellEffects(bool val) { stop_save_spell_effects = val; } + AppearanceData SavedApp; + CharFeatures SavedFeatures; + bool custNPC; + Entity* custNPCTarget; + // bot index, spawn id + map SpawnedBots; + bool StopSaveSpellEffects() { return stop_save_spell_effects; } + + void MentorTarget(); + void SetMentorStats(int32 effective_level, int32 target_char_id = 0, bool update_stats = true); + + bool ResetMentorship() { + bool mentorship_status = reset_mentorship; + if(mentorship_status) + { + SetMentorStats(GetLevel()); + } + reset_mentorship = false; + return mentorship_status; + } + + void EnableResetMentorship() { + reset_mentorship = true; + } + + bool SerializeItemPackets(EquipmentItemList* equipList, vector* packets, Item* item, int16 version, Item* to_item = 0); + + void AddGMVisualFilter(int32 filter_type, int32 filter_value, char* filter_search_str, int16 visual_tag); + int16 MatchGMVisualFilter(int32 filter_type, int32 filter_value, char* filter_search_str, bool in_vismutex_lock = false); + void ClearGMVisualFilters(); + int GetPVPAlignment(); + + int32 GetCurrentLanguage() { return current_language_id; } + void SetCurrentLanguage(int32 language_id) { current_language_id = language_id; } + + void SetActiveReward(bool val) { active_reward = val; } + bool IsActiveReward() { return active_reward; } + + + bool IsSpawnInRangeList(int32 spawn_id); + void SetSpawnInRangeList(int32 spawn_id, bool in_range); + void ProcessSpawnRangeUpdates(); + void CalculatePlayerHPPower(int16 new_level = 0); + bool IsAllowedCombatEquip(int8 slot = 255, bool send_message = false); + + void SetActiveFoodUniqueID(int32 unique_id, bool update_db = true); + void SetActiveDrinkUniqueID(int32 unique_id, bool update_db = true); + + int32 GetActiveFoodUniqueID() { return active_food_unique_id; } + int32 GetActiveDrinkUniqueID() { return active_drink_unique_id; } + + Mutex MPlayerQuests; + float pos_packet_speed; + + map > >* SortedTraitList; + map >* ClassTraining; + map >* RaceTraits; + map >* InnateRaceTraits; + map >* FocusEffects; + mutable std::shared_mutex trait_mutex; + std::atomic need_trait_update; + + void InitXPTable(); + map m_levelXPReq; + + mutable std::shared_mutex spell_packet_update_mutex; +private: + bool reset_mentorship; + bool range_attack; + int16 last_movement_activity; + bool returning_from_ld; + PlayerGroup* group; + + float test_x; + float test_y; + float test_z; + int32 test_time; + map > pending_loot_items; + Mutex MSpellsBook; + Mutex MRecipeBook; + map current_quest_flagged; + PlayerFaction factions; + map completed_quests; + bool charsheet_changed; + map spawn_vis_packet_list; + map spawn_info_packet_list; + map spawn_pos_packet_list; + map spawn_packet_sent; + map spawn_state_list; + uchar* movement_packet; + uchar* old_movement_packet; + uchar* spell_orig_packet; + uchar* spell_xor_packet; + int16 spell_count; + //float speed; + int16 target_id; + Spawn* combat_target; + int32 char_id; + bool quickbar_updated; + bool resurrecting; + PlayerInfo* info; + vector spells; + vector quickbar_items; + map statistics; + void RemovePlayerStatistics(); + map friend_list; + map ignore_list; + bool pending_deletion; + PlayerControlFlags control_flags; + + map target_invis_history; + + // JA: POI Discoveries + map > players_poi_list; + + // Jabantiz: Passive spell list, just stores spell id's + vector passive_spells; + + /// Adds a new passive spell to the list + /// Spell id to add + /// Tier of spell to add + void AddPassiveSpell(int32 id, int8 tier); + + /// Removes a passive spell from the list + /// Spell id to remove + /// Tier of spell to remove + /// Remove the spell from this players passive list, default true + void RemovePassive(int32 id, int8 tier, bool remove_from_list = true); + + CharacterInstances character_instances; + string away_message; + string biography; + MutexMap mail_list; + bool is_tracking; + Guild* guild; + PlayerCollectionList collection_list; + Collection * pending_collection_reward; + vector pending_item_rewards; + map> pending_selectable_item_rewards; + PlayerTitlesList player_titles_list; + PlayerRecipeList recipe_list; + PlayerLanguagesList player_languages_list; + PlayerRecipeBookList recipebook_list; + PlayerAchievementList achievement_list; + PlayerAchievementUpdateList achievement_update_list; + // Need to keep track of the recipe the player is crafting as not all crafting packets have this info + int32 current_recipe; + + void HandleHistoryNone(int8 subtype, int32 value, int32 value2); + void HandleHistoryDeath(int8 subtype, int32 value, int32 value2); + void HandleHistoryDiscovery(int8 subtype, int32 value, int32 value2); + void HandleHistoryXP(int8 subtype, int32 value, int32 value2); + + /// + void ModifySpellStatus(SpellBookEntry* spell, sint16 value, bool modify_recast = true, int16 recast = 0); + void AddSpellStatus(SpellBookEntry* spell, sint16 value, bool modify_recast = true, int16 recast = 0); + void RemoveSpellStatus(SpellBookEntry* spell, sint16 value, bool modify_recast = true, int16 recast = 0); + void SetSpellEntryRecast(SpellBookEntry* spell, bool modify_recast, int16 recast); +// void InitXPTable(); +// map m_levelXPReq; + + //The following variables are for serializing spawn packets + PacketStruct* spawn_pos_struct; + PacketStruct* spawn_info_struct; + PacketStruct* spawn_vis_struct; + PacketStruct* spawn_header_struct; + PacketStruct* spawn_footer_struct; + PacketStruct* sign_footer_struct; + PacketStruct* widget_footer_struct; + uchar* spawn_tmp_vis_xor_packet; + uchar* spawn_tmp_pos_xor_packet; + uchar* spawn_tmp_info_xor_packet; + int32 vis_xor_size; + int32 pos_xor_size; + int32 info_xor_size; + + // Character history, map > > + map > > m_characterHistory; + + map m_charLuaHistory; + Mutex mLUAHistory; + + int32 tmp_mount_model; + EQ2_Color tmp_mount_color; + EQ2_Color tmp_mount_saddle_color; + + bool gm_vision; + bool stop_save_spell_effects; + + map player_spawn_id_map; + map player_spawn_reverse_id_map; + map player_aggro_range_spawns; + + bool all_spells_locked; + Timer lift_cooldown; + + vector gm_visual_filters; + + int32 current_language_id; + + bool active_reward; + + Quest* GetAnyQuest(int32 quest_id); + Quest* GetCompletedQuest(int32 quest_id); + + std::atomic active_food_unique_id; + std::atomic active_drink_unique_id; +}; +#pragma pack() +#endif diff --git a/source/WorldServer/PlayerGroups.cpp b/source/WorldServer/PlayerGroups.cpp new file mode 100644 index 0000000..483b007 --- /dev/null +++ b/source/WorldServer/PlayerGroups.cpp @@ -0,0 +1,1000 @@ +/* +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 . +*/ + +#include "PlayerGroups.h" +#include "../common/Log.h" +#include "World.h" +#include "Spells.h" +#include "LuaInterface.h" +#include "Bots/Bot.h" +#include "SpellProcess.h" +#include "Rules/Rules.h" + +extern ZoneList zone_list; +extern RuleManager rule_manager; + +/******************************************************** PlayerGroup ********************************************************/ + +PlayerGroup::PlayerGroup(int32 id) { + m_id = id; + MGroupMembers.SetName("MGroupMembers"); + SetDefaultGroupOptions(); +} + +PlayerGroup::~PlayerGroup() { + Disband(); +} + +bool PlayerGroup::AddMember(Entity* member) { + // Check to make sure the entity we are adding is valid + if (!member) { + LogWrite(GROUP__ERROR, 0, "Group", "New member is null"); + return false; + } + + // Make sure entity we are adding isn't already in a group by checking if it has a GroupMemberInfo pointer + if (member->GetGroupMemberInfo()) { + LogWrite(GROUP__ERROR, 0, "Group", "New member (%s) already has a group", member->GetName()); + return false; + } + + // Create a new GroupMemberInfo and assign it to the new member + GroupMemberInfo* gmi = new GroupMemberInfo; + gmi->group_id = m_id; + gmi->member = member; + gmi->leader = false; + if (member->IsPlayer()) + gmi->client = ((Player*)member)->GetClient(); + else + gmi->client = 0; + gmi->mentor_target_char_id = 0; + + member->SetGroupMemberInfo(gmi); + member->group_id = gmi->group_id; + MGroupMembers.writelock(); + m_members.push_back(gmi); + member->UpdateGroupMemberInfo(true, true); + MGroupMembers.releasewritelock(); + + SendGroupUpdate(); + return true; +} + +bool PlayerGroup::RemoveMember(Entity* member) { + GroupMemberInfo* gmi = member->GetGroupMemberInfo(); + if (!gmi) { + return false; + } + + bool ret = false; + + MGroupMembers.writelock(); + member->SetGroupMemberInfo(0); + + deque::iterator erase_itr = m_members.end(); + deque::iterator itr; + for (itr = m_members.begin(); itr != m_members.end(); itr++) { + if (gmi == *itr) + erase_itr = itr; + + if(member->IsPlayer() && (*itr)->mentor_target_char_id == ((Player*)member)->GetCharacterID() && (*itr)->client) + { + (*itr)->mentor_target_char_id = 0; + (*itr)->client->GetPlayer()->EnableResetMentorship(); + } + + if ((*itr)->client) + (*itr)->client->GetPlayer()->SetCharSheetChanged(true); + } + if (erase_itr != m_members.end()) { + ret = true; + m_members.erase(erase_itr); + } + MGroupMembers.releasewritelock(); + + member->SetGroupMemberInfo(nullptr); + safe_delete(gmi); + if (member->IsBot()) + ((Bot*)member)->Camp(); + + return ret; +} + +void PlayerGroup::Disband() { + deque::iterator itr; + MGroupMembers.writelock(); + for (itr = m_members.begin(); itr != m_members.end(); itr++) { + if ((*itr)->member) { + (*itr)->member->SetGroupMemberInfo(0); + if ((*itr)->member->IsBot()) + ((Bot*)(*itr)->member)->Camp(); + } + if((*itr)->mentor_target_char_id && (*itr)->client) + { + (*itr)->mentor_target_char_id = 0; + (*itr)->client->GetPlayer()->EnableResetMentorship(); + } + + if ((*itr)->client) + (*itr)->client->GetPlayer()->SetCharSheetChanged(true); + + safe_delete(*itr); + } + + m_members.clear(); + MGroupMembers.releasewritelock(); +} + +void PlayerGroup::SendGroupUpdate(Client* exclude) { + deque::iterator itr; + MGroupMembers.readlock(__FUNCTION__, __LINE__); + for (itr = m_members.begin(); itr != m_members.end(); itr++) { + GroupMemberInfo* gmi = *itr; + if (gmi->client && gmi->client != exclude && !gmi->client->IsZoning()) + gmi->client->GetPlayer()->SetCharSheetChanged(true); + } + MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); +} + +void PlayerGroup::SimpleGroupMessage(const char* message) { + deque::iterator itr; + MGroupMembers.readlock(__FUNCTION__, __LINE__); + for(itr = m_members.begin(); itr != m_members.end(); itr++) { + GroupMemberInfo* info = *itr; + if(info->client) + info->client->SimpleMessage(CHANNEL_GROUP_CHAT, message); + } + MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); +} + +void PlayerGroup::SendGroupMessage(int8 type, const char* message, ...) { + va_list argptr; + char buffer[4096]; + buffer[0] = 0; + va_start(argptr, message); + vsnprintf(buffer, sizeof(buffer), message, argptr); + va_end(argptr); + + deque::iterator itr; + MGroupMembers.readlock(__FUNCTION__, __LINE__); + for (itr = m_members.begin(); itr != m_members.end(); itr++) { + GroupMemberInfo* info = *itr; + if (info->client) + info->client->SimpleMessage(type, buffer); + } + MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); +} + +void PlayerGroup::GroupChatMessage(Spawn* from, int32 language, const char* message) { + deque::iterator itr; + MGroupMembers.readlock(__FUNCTION__, __LINE__); + for(itr = m_members.begin(); itr != m_members.end(); itr++) { + GroupMemberInfo* info = *itr; + if(info && info->client && info->client->GetCurrentZone()) + info->client->GetCurrentZone()->HandleChatMessage(info->client, from, 0, CHANNEL_GROUP_SAY, message, 0, 0, true, language); + } + MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); +} + +bool PlayerGroup::MakeLeader(Entity* new_leader) { + deque::iterator itr; + MGroupMembers.readlock(__FUNCTION__, __LINE__); + for (itr = m_members.begin(); itr != m_members.end(); itr++) { + GroupMemberInfo* info = *itr; + if (info->leader) { + info->leader = false; + break; + } + } + MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + + new_leader->GetGroupMemberInfo()->leader = true; + SendGroupUpdate(); + + return true; +} + +bool PlayerGroup::ShareQuestWithGroup(Client* quest_sharer, Quest* quest) { + if(!quest || !quest_sharer) + return false; + + bool canShare = quest->CanShareQuestCriteria(quest_sharer); + + if(!canShare) { + return false; + } + + deque::iterator itr; + MGroupMembers.readlock(__FUNCTION__, __LINE__); + for(itr = m_members.begin(); itr != m_members.end(); itr++) { + GroupMemberInfo* info = *itr; + if(info && info->client && info->client->GetCurrentZone()) { + if( quest_sharer != info->client && info->client->GetPlayer()->HasAnyQuest(quest->GetQuestID()) == 0 ) { + info->client->AddPendingQuest(new Quest(quest)); + info->client->Message(CHANNEL_COLOR_YELLOW, "%s has shared the quest %s with you.", quest_sharer->GetPlayer()->GetName(), quest->GetName()); + } + } + } + MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + + return true; +} + + + +/******************************************************** PlayerGroupManager ********************************************************/ + +PlayerGroupManager::PlayerGroupManager() { + m_nextGroupID = 1; + + MPendingInvites.SetName("PlayerGroupManager::m_pendingInvites"); +} + +PlayerGroupManager::~PlayerGroupManager() { + MPendingInvites.writelock(__FUNCTION__, __LINE__); + m_pendingInvites.clear(); + MPendingInvites.releasewritelock(__FUNCTION__, __LINE__); + + std::unique_lock lock(MGroups); + map::iterator itr; + for (itr = m_groups.begin(); itr != m_groups.end(); itr++) + safe_delete(itr->second); + + m_groups.clear(); +} + +bool PlayerGroupManager::AddGroupMember(int32 group_id, Entity* member) { + std::shared_lock lock(MGroups); + bool ret = false; + + if (m_groups.count(group_id) > 0) { + PlayerGroup* group = m_groups[group_id]; + ret = group->AddMember(member); + } + + return ret; +} + +bool PlayerGroupManager::RemoveGroupMember(int32 group_id, Entity* member) { + bool ret = false; + bool remove = false; + Client* client = 0; + if(member->GetGroupMemberInfo()->mentor_target_char_id) + { + if(member->IsPlayer()) + { + Player* tmpPlayer = (Player*)member; + member->GetGroupMemberInfo()->mentor_target_char_id = 0; + tmpPlayer->EnableResetMentorship(); + } + } + + GroupLock(__FUNCTION__, __LINE__); + + if (m_groups.count(group_id) > 0) { + PlayerGroup* group = m_groups[group_id]; + + if (member->IsPlayer()) + client = member->GetGroupMemberInfo()->client; + + ret = group->RemoveMember(member); + + // If only 1 person left in the group set a flag to remove the group + if (group->Size() == 1) + remove = true; + } + + ReleaseGroupLock(__FUNCTION__, __LINE__); + + if (client) + RemoveGroupBuffs(group_id, client); + + // Call RemoveGroup outside the locks as it uses the same locks + if (remove) + RemoveGroup(group_id); + + return ret; +} + +void PlayerGroupManager::NewGroup(Entity* leader) { + std::unique_lock lock(MGroups); + + // Highly doubt this will ever be needed but putting it in any way, basically bump the id and ensure + // no active group is currently using this id, if we hit the max for an int32 then reset the id to 1 + while (m_groups.count(m_nextGroupID) > 0) { + // If m_nextGroupID is at its max then reset it to 1, else increment it + if (m_nextGroupID == 4294967295) + m_nextGroupID = 1; + else + m_nextGroupID++; + } + + // Create a new group with the valid ID we got from above + PlayerGroup* new_group = new PlayerGroup(m_nextGroupID); + + GroupOptions goptions; + goptions.loot_method = leader->GetInfoStruct()->get_group_loot_method(); + goptions.loot_items_rarity = leader->GetInfoStruct()->get_group_loot_items_rarity(); + goptions.auto_split = leader->GetInfoStruct()->get_group_auto_split(); + goptions.default_yell = leader->GetInfoStruct()->get_group_default_yell(); + goptions.group_autolock = leader->GetInfoStruct()->get_group_autolock(); + goptions.group_lock_method = leader->GetInfoStruct()->get_group_lock_method(); + goptions.solo_autolock = leader->GetInfoStruct()->get_group_solo_autolock(); + goptions.auto_loot_method = leader->GetInfoStruct()->get_group_auto_loot_method(); + new_group->SetDefaultGroupOptions(&goptions); + + // Add the new group to the list (need to do this first, AddMember needs ref to the PlayerGroup ptr -> UpdateGroupMemberInfo) + m_groups[m_nextGroupID] = new_group; + + // Add the leader to the group + new_group->AddMember(leader); + + leader->GetGroupMemberInfo()->leader = true; +} + +void PlayerGroupManager::RemoveGroup(int32 group_id) { + std::unique_lock lock(MGroups); + + // Check to see if the id is in the list + if (m_groups.count(group_id) > 0) { + // Get a pointer to the group + PlayerGroup* group = m_groups[group_id]; + // Erase the group from the list + m_groups.erase(group_id); + // Delete the group + safe_delete(group); + } +} + +int8 PlayerGroupManager::Invite(Player* leader, Entity* member) { + int8 ret = 255; // Should be changed, if it is not then we have an unknown error + + // Lock the pending invite list so we can work with it + MPendingInvites.writelock(__FUNCTION__, __LINE__); + + if (!member || (member->IsBot() && ((Bot*)member)->IsImmediateCamp())) + ret = 6; // failure, not a valid target + else if (member->IsNPC() && (!member->IsBot() /*|| !member->IsMec()*/)) + ret = 6; + else if (leader == member) + ret = 5; // failure, can't invite yourself + else if (member->GetGroupMemberInfo()) + ret = 1; // failure, member already in a group + // Check to see if the target of the invite already has a pending invite + else if (m_pendingInvites.count(member->GetName()) > 0) + ret = 2; // Target already has an invite + // Check to see if the player that invited is already in a group + else if (leader->GetGroupMemberInfo()) { + // Read lock the group list so we can get the size of the inviters group + GroupLock(__FUNCTION__, __LINE__); + int32 group_size = m_groups[leader->GetGroupMemberInfo()->group_id]->Size(); + ReleaseGroupLock(__FUNCTION__, __LINE__); + + // Check to see if the group is full + if (m_groups[leader->GetGroupMemberInfo()->group_id]->Size() >= 6) + ret = 3; // Group full + // Group isn't full so add the member to the pending invite list + else { + m_pendingInvites[member->GetName()] = leader->GetName(); + ret = 0; // Success + } + } + // Inviter is not in a group + else { + // Check to see if the inviter has a pending invite himself + if (m_pendingInvites.count(leader->GetName()) > 0) + ret = 4; // inviter already has a pending group invite + // No pending invites for the inviter add both the inviter and the target of the invite to the list + else { + m_pendingInvites[leader->GetName()] = leader->GetName(); + m_pendingInvites[member->GetName()] = leader->GetName(); + ret = 0; // success + } + } + // Release the lock on pending invites + MPendingInvites.releasewritelock(__FUNCTION__, __LINE__); + + /* testing purposes only */ + if (ret == 0 && member->IsNPC()) + AcceptInvite(member); + + return ret; +} + +int8 PlayerGroupManager::AcceptInvite(Entity* member) { + int8 ret = 3; // default to unknown error + + MPendingInvites.writelock(__FUNCTION__, __LINE__); + + if (m_pendingInvites.count(member->GetName()) > 0) { + string leader = m_pendingInvites[member->GetName()]; + Client* client_leader = zone_list.GetClientByCharName(leader); + + if (client_leader) { + if (m_pendingInvites.count(leader) > 0) { + NewGroup(client_leader->GetPlayer()); + m_pendingInvites.erase(leader); + } + + // Remove from invite list and add to the group + m_pendingInvites.erase(member->GetName()); + + GroupMessage(client_leader->GetPlayer()->GetGroupMemberInfo()->group_id, "%s has joined the group.", member->GetName()); + AddGroupMember(client_leader->GetPlayer()->GetGroupMemberInfo()->group_id, member); + ret = 0; // success + } + else { + // Was unable to find the leader, remove from the invite list + m_pendingInvites.erase(member->GetName()); + ret = 2; // failure, can't find leader + } + } + else + ret = 1; // failure, no pending invite + + MPendingInvites.releasewritelock(__FUNCTION__, __LINE__); + + return ret; +} + +void PlayerGroupManager::DeclineInvite(Entity* member) { + MPendingInvites.writelock(__FUNCTION__, __LINE__); + + if (m_pendingInvites.count(member->GetName()) > 0) { + string leader = m_pendingInvites[member->GetName()]; + m_pendingInvites.erase(member->GetName()); + if (m_pendingInvites.count(leader) > 0) + m_pendingInvites.erase(leader); + } + + MPendingInvites.releasewritelock(__FUNCTION__, __LINE__); +} + +bool PlayerGroupManager::IsGroupIDValid(int32 group_id) { + std::shared_lock lock(MGroups); + bool ret = false; + ret = m_groups.count(group_id) > 0; + return ret; +} + +void PlayerGroupManager::SendGroupUpdate(int32 group_id, Client* exclude) { + std::shared_lock lock(MGroups); + + if (m_groups.count(group_id) > 0) { + m_groups[group_id]->SendGroupUpdate(exclude); + } +} + +PlayerGroup* PlayerGroupManager::GetGroup(int32 group_id) { + if (m_groups.count(group_id) > 0) + return m_groups[group_id]; + + return 0; +} + +void PlayerGroupManager::ClearPendingInvite(Entity* member) { + MPendingInvites.writelock(__FUNCTION__, __LINE__); + + if (m_pendingInvites.count(member->GetName()) > 0) + m_pendingInvites.erase(member->GetName()); + + MPendingInvites.releasewritelock(__FUNCTION__, __LINE__); +} + +void PlayerGroupManager::RemoveGroupBuffs(int32 group_id, Client* client) { + SpellEffects* se = 0; + Spell* spell = 0; + LuaSpell* luaspell = 0; + EQ2Packet* packet = 0; + Entity* pet = 0; + Player* player = 0; + Entity* charmed_pet = 0; + PlayerGroup* group = 0; + + MGroups.lock_shared(); + if (m_groups.count(group_id) > 0) + group = m_groups[group_id]; + + if (group && client) { + /* first remove all spell effects this group member has on them from other group members */ + player = client->GetPlayer(); + bool recoup_lock = true; + for (int i = 0; i < NUM_SPELL_EFFECTS; i++) { + if(recoup_lock) { + player->GetSpellEffectMutex()->readlock(__FUNCTION__, __LINE__); + recoup_lock = false; + se = player->GetSpellEffects(); + } + if (se && se[i].spell_id != 0xFFFFFFFF) { + //If the client is the caster, don't remove the spell + if (se[i].caster == player) + continue; + + luaspell = se[i].spell; + spell = luaspell->spell; + /* is this a friendly group spell? */ + if (spell && spell->GetSpellData()->group_spell && spell->GetSpellData()->friendly_spell) { + + player->GetSpellEffectMutex()->releasereadlock(__FUNCTION__, __LINE__); + recoup_lock = true; + // we have to remove our spell effect mutex lock since RemoveSpellEffect needs a write lock to remove it + //Remove all group buffs not cast by this player + player->RemoveSpellEffect(luaspell); + player->RemoveSpellBonus(luaspell); + player->RemoveSkillBonus(spell->GetSpellID()); + + //Also remove group buffs from pets + pet = 0; + charmed_pet = 0; + if (player->HasPet()){ + pet = player->GetPet(); + pet = player->GetCharmedPet(); + } + if (pet){ + pet->RemoveSpellEffect(luaspell); + pet->RemoveSpellBonus(luaspell); + } + if (charmed_pet){ + charmed_pet->RemoveSpellEffect(luaspell); + charmed_pet->RemoveSpellBonus(luaspell); + } + } + } + } + if(!recoup_lock) { // we previously set a readlock that we now release + player->GetSpellEffectMutex()->releasereadlock(__FUNCTION__, __LINE__); + } + packet = client->GetPlayer()->GetSkills()->GetSkillPacket(client->GetVersion()); + if (packet) + client->QueuePacket(packet); + } + MGroups.unlock_shared(); +} + +int32 PlayerGroupManager::GetGroupSize(int32 group_id) { + std::shared_lock lock(MGroups); + int32 ret = 0; + + if (m_groups.count(group_id) > 0) + ret = m_groups[group_id]->Size(); + + return ret; +} + +void PlayerGroupManager::SendGroupQuests(int32 group_id, Client* client) { + std::shared_lock lock(MGroups); + GroupMemberInfo* info = 0; + if (m_groups.count(group_id) > 0) { + m_groups[group_id]->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = m_groups[group_id]->GetMembers(); + deque::iterator itr; + for (itr = members->begin(); itr != members->end(); itr++) { + info = *itr; + if (info->client) { + LogWrite(PLAYER__DEBUG, 0, "Player", "Send Quest Journal..."); + info->client->SendQuestJournal(false, client); + client->SendQuestJournal(false, info->client); + } + } + m_groups[group_id]->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } +} + +bool PlayerGroupManager::HasGroupCompletedQuest(int32 group_id, int32 quest_id) { + std::shared_lock lock(MGroups); + bool questComplete = true; + GroupMemberInfo* info = 0; + if (m_groups.count(group_id) > 0) { + m_groups[group_id]->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = m_groups[group_id]->GetMembers(); + deque::iterator itr; + for (itr = members->begin(); itr != members->end(); itr++) { + info = *itr; + if (info->client) { + bool isComplete = info->client->GetPlayer()->HasQuestBeenCompleted(quest_id); + if(!isComplete) + { + questComplete = isComplete; + break; + } + } + } + m_groups[group_id]->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + return questComplete; +} + +void PlayerGroupManager::SimpleGroupMessage(int32 group_id, const char* message) { + std::shared_lock lock(MGroups); + + if (m_groups.count(group_id) > 0) + m_groups[group_id]->SimpleGroupMessage(message); +} + +void PlayerGroupManager::SendGroupMessage(int32 group_id, int8 type, const char* message, ...) { + std::shared_lock lock(MGroups); + + va_list argptr; + char buffer[4096]; + buffer[0] = 0; + va_start(argptr, message); + vsnprintf(buffer, sizeof(buffer), message, argptr); + va_end(argptr); + + if (m_groups.count(group_id) > 0) + m_groups[group_id]->SendGroupMessage(type, buffer); +} + +void PlayerGroupManager::GroupMessage(int32 group_id, const char* message, ...) { + va_list argptr; + char buffer[4096]; + buffer[0] = 0; + va_start(argptr, message); + vsnprintf(buffer, sizeof(buffer), message, argptr); + va_end(argptr); + SimpleGroupMessage(group_id, buffer); +} + +void PlayerGroupManager::GroupChatMessage(int32 group_id, Spawn* from, int32 language, const char* message) { + std::shared_lock lock(MGroups); + + if (m_groups.count(group_id) > 0) + m_groups[group_id]->GroupChatMessage(from, language, message); +} + +bool PlayerGroupManager::MakeLeader(int32 group_id, Entity* new_leader) { + std::shared_lock lock(MGroups); + + if (m_groups.count(group_id) > 0) + return m_groups[group_id]->MakeLeader(new_leader); + + return false; +} + +void PlayerGroupManager::UpdateGroupBuffs() { + map::iterator itr; + deque::iterator member_itr; + deque::iterator target_itr; + map::iterator itr_skills; + MaintainedEffects* me = nullptr; + LuaSpell* luaspell = nullptr; + Spell* spell = nullptr; + Entity* group_member = nullptr; + SkillBonus* sb = nullptr; + EQ2Packet* packet = nullptr; + int32 i = 0; + PlayerGroup* group = nullptr; + Player* caster = nullptr; + vector new_target_list; + vector char_list; + Client* client = nullptr; + bool has_effect = false; + vector* sb_list = nullptr; + BonusValues* bv = nullptr; + Entity* pet = nullptr; + Entity* charmed_pet = nullptr; + + + + for (itr = m_groups.begin(); itr != m_groups.end(); itr++) { + group = itr->second; + + /* loop through the group members and see if any of them have any maintained spells that are group buffs and friendly. + if so, update the list of targets and apply/remove effects as needed */ + vector players; + + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + for (member_itr = group->GetMembers()->begin(); member_itr != group->GetMembers()->end(); member_itr++) { + if ((*member_itr)->client) + caster = (*member_itr)->client->GetPlayer(); + else caster = 0; + + if (!caster) + continue; + + if (!caster->GetMaintainedSpellBySlot(0)) + continue; + + players.push_back(caster); + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + + vector::iterator vitr; + + for (vitr = players.begin(); vitr != players.end(); vitr++) { + caster = *vitr; + caster->GetMaintainedMutex()->readlock(__FUNCTION__, __LINE__); + // go through the player's maintained spells + me = caster->GetMaintainedSpells(); + for (i = 0; i < NUM_MAINTAINED_EFFECTS; i++) { + if (me[i].spell_id == 0xFFFFFFFF) + continue; + luaspell = me[i].spell; + + if (!luaspell) + continue; + + if (!luaspell->caster) + { + LogWrite(PLAYER__ERROR, 0, "Player", "Bad luaspell, caster is NULL, spellid: %u", me[i].spell_id); + continue; + } + + spell = luaspell->spell; + + if (spell && spell->GetSpellData()->group_spell && spell->GetSpellData()->friendly_spell && + (spell->GetSpellData()->target_type == SPELL_TARGET_GROUP_AE || spell->GetSpellData()->target_type == SPELL_TARGET_RAID_AE)) { + + luaspell->MSpellTargets.writelock(__FUNCTION__, __LINE__); + luaspell->char_id_targets.clear(); + + for (target_itr = group->GetMembers()->begin(); target_itr != group->GetMembers()->end(); target_itr++) { + group_member = (*target_itr)->member; + + if (!group_member) + continue; + + if (group_member == caster) + continue; + + client = (*target_itr)->client; + + has_effect = false; + + if (group_member->GetSpellEffect(spell->GetSpellID(), caster)) { + has_effect = true; + } + if(!has_effect && (std::find(luaspell->removed_targets.begin(), + luaspell->removed_targets.end(), group_member->GetID()) != luaspell->removed_targets.end())) { + continue; + } + // Check if player is within range of the caster + if (!rule_manager.GetGlobalRule(R_Spells, EnableCrossZoneGroupBuffs)->GetInt8() && + (group_member->GetZone() != caster->GetZone() || caster->GetDistance(group_member) > spell->GetSpellData()->radius)) { + if (has_effect) { + group_member->RemoveSpellEffect(luaspell); + group_member->RemoveSpellBonus(luaspell); + group_member->RemoveSkillBonus(spell->GetSpellID()); + if (client) { + packet = ((Player*)group_member)->GetSkills()->GetSkillPacket(client->GetVersion()); + if (packet) + client->QueuePacket(packet); + } + //Also remove group buffs from pet + if (group_member->HasPet()) { + pet = group_member->GetPet(); + charmed_pet = group_member->GetCharmedPet(); + if (pet) { + pet->RemoveSpellEffect(luaspell); + pet->RemoveSpellBonus(luaspell); + } + if (charmed_pet) { + charmed_pet->RemoveSpellEffect(luaspell); + charmed_pet->RemoveSpellBonus(luaspell); + } + } + } + continue; + } + + if(group_member->GetZone() != caster->GetZone()) + { + SpellProcess::AddSelfAndPetToCharTargets(luaspell, group_member); + } + else + { + //this group member is a target of the spell + new_target_list.push_back(group_member->GetID()); + } + + if (has_effect) + continue; + + pet = 0; + charmed_pet = 0; + + if (group_member->HasPet()) { + pet = group_member->GetPet(); + charmed_pet = group_member->GetCharmedPet(); + } + + group_member->AddSpellEffect(luaspell, luaspell->timer.GetRemainingTime() != 0 ? luaspell->timer.GetRemainingTime() : 0); + if (pet) + pet->AddSpellEffect(luaspell, luaspell->timer.GetRemainingTime() != 0 ? luaspell->timer.GetRemainingTime() : 0); + if (charmed_pet) + charmed_pet->AddSpellEffect(luaspell, luaspell->timer.GetRemainingTime() != 0 ? luaspell->timer.GetRemainingTime() : 0); + + if (pet) + new_target_list.push_back(pet->GetID()); + if (charmed_pet) + new_target_list.push_back(charmed_pet->GetID()); + + + // look for a spell bonus on caster's spell + sb_list = caster->GetAllSpellBonuses(luaspell); + for (int32 x = 0; x < sb_list->size(); x++) { + bv = sb_list->at(x); + group_member->AddSpellBonus(luaspell, bv->type, bv->value, bv->class_req, bv->race_req, bv->faction_req); + if (pet) + pet->AddSpellBonus(luaspell, bv->type, bv->value, bv->class_req, bv->race_req, bv->faction_req); + if (charmed_pet) + charmed_pet->AddSpellBonus(luaspell, bv->type, bv->value, bv->class_req, bv->race_req, bv->faction_req); + } + + sb_list->clear(); + safe_delete(sb_list); + + // look for a skill bonus on the caster's spell + sb = caster->GetSkillBonus(me[i].spell_id); + if (sb) { + for (itr_skills = sb->skills.begin(); itr_skills != sb->skills.end(); itr_skills++) + group_member->AddSkillBonus(sb->spell_id, (*itr_skills).second->skill_id, (*itr_skills).second->value); + } + + if (client) { + packet = ((Player*)group_member)->GetSkills()->GetSkillPacket(client->GetVersion()); + if (packet) + client->QueuePacket(packet); + } + } + + luaspell->targets.swap(new_target_list); + SpellProcess::AddSelfAndPet(luaspell, caster); + luaspell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__); + new_target_list.clear(); + } + } + caster->GetMaintainedMutex()->releasereadlock(__FUNCTION__, __LINE__); + } + } +} + +bool PlayerGroupManager::IsInGroup(int32 group_id, Entity* member) { + std::shared_lock lock(MGroups); + bool ret = false; + + if (m_groups.count(group_id) > 0) { + m_groups[group_id]->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = m_groups[group_id]->GetMembers(); + for (int8 i = 0; i < members->size(); i++) { + if (member == members->at(i)->member) { + ret = true; + break; + } + } + m_groups[group_id]->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + return ret; +} + +Entity* PlayerGroupManager::IsPlayerInGroup(int32 group_id, int32 character_id) { + std::shared_lock lock(MGroups); + + Entity* ret = nullptr; + + if (m_groups.count(group_id) > 0) { + m_groups[group_id]->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = m_groups[group_id]->GetMembers(); + for (int8 i = 0; i < members->size(); i++) { + if (members->at(i)->member && members->at(i)->member->IsPlayer() && character_id == ((Player*)members->at(i)->member)->GetCharacterID()) { + ret = members->at(i)->member; + break; + } + } + m_groups[group_id]->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + return ret; +} + +void PlayerGroup::RemoveClientReference(Client* remove) { + deque::iterator itr; + MGroupMembers.writelock(); + for (itr = m_members.begin(); itr != m_members.end(); itr++) { + GroupMemberInfo* gmi = *itr; + if (gmi->client && gmi->client == remove) + { + gmi->client = 0; + gmi->member = 0; + break; + } + } + MGroupMembers.releasewritelock(); +} + +void PlayerGroup::UpdateGroupMemberInfo(Entity* ent, bool groupMembersLocked) { + Player* player = (Player*)ent; + + if (!player || !player->GetGroupMemberInfo()) + return; + + if(!groupMembersLocked) + MGroupMembers.writelock(); + + GroupMemberInfo* group_member_info = player->GetGroupMemberInfo(); + player->GetGroupMemberInfo()->class_id = player->GetAdventureClass(); + group_member_info->hp_max = player->GetTotalHP(); + group_member_info->hp_current = player->GetHP(); + group_member_info->level_max = player->GetLevel(); + group_member_info->level_current = player->GetLevel(); + group_member_info->name = string(player->GetName()); + group_member_info->power_current = player->GetPower(); + group_member_info->power_max = player->GetTotalPower(); + group_member_info->race_id = player->GetRace(); + if (player->GetZone()) + group_member_info->zone = player->GetZone()->GetZoneDescription(); + else + group_member_info->zone = "Unknown"; + + if(!groupMembersLocked) + MGroupMembers.releasewritelock(); +} + +Entity* PlayerGroup::GetGroupMemberByPosition(Entity* seeker, int32 mapped_position) { + Entity* ret = nullptr; + + deque::iterator itr; + + MGroupMembers.readlock(); + + int32 count = 1; + for (itr = m_members.begin(); itr != m_members.end(); itr++) { + if ((*itr)->member == seeker) { + continue; + } + count++; + if(count >= mapped_position) { + ret = (Entity*)(*itr)->member; + break; + } + } + + MGroupMembers.releasereadlock(); + + return ret; +} + +void PlayerGroup::SetDefaultGroupOptions(GroupOptions* options) { + MGroupMembers.writelock(); + if (options != nullptr) { + group_options.loot_method = options->loot_method; + group_options.loot_items_rarity = options->loot_items_rarity; + group_options.auto_split = options->auto_split; + group_options.default_yell = options->default_yell; + group_options.group_lock_method = options->group_lock_method; + group_options.group_autolock = options->group_autolock; + group_options.solo_autolock = options->solo_autolock; + group_options.auto_loot_method = options->auto_loot_method; + } + else { + group_options.loot_method = 1; + group_options.loot_items_rarity = 0; + group_options.auto_split = 1; + group_options.default_yell = 1; + group_options.group_lock_method = 0; + group_options.group_autolock = 0; + group_options.solo_autolock = 0; + group_options.auto_loot_method = 0; + group_options.last_looted_index = 0; + } + + MGroupMembers.releasewritelock(); +} diff --git a/source/WorldServer/PlayerGroups.h b/source/WorldServer/PlayerGroups.h new file mode 100644 index 0000000..e7b9b27 --- /dev/null +++ b/source/WorldServer/PlayerGroups.h @@ -0,0 +1,221 @@ +/* +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 . +*/ + +#ifndef __PLAYERGROUPS_H__ +#define __PLAYERGROUPS_H__ + +#include +#include +#include +#include +#include "Spells.h" + +#include "../common/types.h" +#include "Entity.h" + +using namespace std; + +// GroupOptions isn't used yet +struct GroupOptions{ + int8 loot_method; + int8 loot_items_rarity; + int8 auto_split; + int8 default_yell; + int8 group_lock_method; + int8 group_autolock; + int8 solo_autolock; + int8 auto_loot_method; + int8 last_looted_index; +}; + +/// All the generic info for the group window, plus a client pointer for players +struct GroupMemberInfo { + int32 group_id; + string name; + string zone; + sint32 hp_current; + sint32 hp_max; + sint32 power_current; + sint32 power_max; + int16 level_current; + int16 level_max; + int8 race_id; + int8 class_id; + bool leader; + Client* client; + Entity* member; + int32 mentor_target_char_id; +}; + +/// Represents a players group in game +class PlayerGroup { +public: + PlayerGroup(int32 id); + ~PlayerGroup(); + + /// Adds a new member to the players group + /// Entity to add to the group, can be a Player or NPC + /// True if the member was added + bool AddMember(Entity* member); + + /// Removes a member from the players group + /// Entity to remove from the player group + /// True if the member was removed + bool RemoveMember(Entity* member); + + /// Removes all members from the group and destroys the group + void Disband(); + + /// Sends updates to all the clients in the group + /// Client to exclude from the update + void SendGroupUpdate(Client* exclude = 0); + + /// Gets the total number of members in the group + /// int32, number of members in the group + int32 Size() { return m_members.size(); } + + /// Gets a pointer to the list of members + /// deque pointer + deque* GetMembers() { return &m_members; } + + + void SimpleGroupMessage(const char* message); + void SendGroupMessage(int8 type, const char* message, ...); + void GroupChatMessage(Spawn* from, int32 language, const char* message); + bool MakeLeader(Entity* new_leader); + + bool ShareQuestWithGroup(Client* quest_sharer, Quest* quest); + + void RemoveClientReference(Client* remove); + void UpdateGroupMemberInfo(Entity* ent, bool groupMembersLocked=false); + Entity* GetGroupMemberByPosition(Entity* seeker, int32 mapped_position); + + void SetDefaultGroupOptions(GroupOptions* options=nullptr); + + GroupOptions* GetGroupOptions() { return &group_options; } + int8 GetLastLooterIndex() { return group_options.last_looted_index; } + void SetNextLooterIndex(int8 new_index) { group_options.last_looted_index = new_index; } + Mutex MGroupMembers; // Mutex for the group members +private: + GroupOptions group_options; + int32 m_id; // ID of this group + deque m_members; // List of members in this group + +}; + +/// Responsible for managing all the player groups in the world +class PlayerGroupManager { +public: + PlayerGroupManager(); + ~PlayerGroupManager(); + + /// Adds a member to a group + /// ID of the group to add a member to + /// Entity* to add to the group + /// True if the member was added to the group + bool AddGroupMember(int32 group_id, Entity* member); + + /// Removes a member from a group + /// ID of the group to remove a member from + /// Entity* to remove from the group + /// True if the member was removed from the group + bool RemoveGroupMember(int32 group_id, Entity* member); + + /// Creates a new group with the provided Entity* as the leader + /// The Entity* that will be the leader of the group + void NewGroup(Entity* leader); + + /// Removes the group from the group manager + /// ID of the group to remove + void RemoveGroup(int32 group_id); + + /// Handles a player inviting another player or NPC to a group + /// Player that sent the invite + /// Player or NPC that is the target of the invite + /// Error code if invite was unsuccessful, 0 if successful + int8 Invite(Player* leader, Entity* member); + + /// Handles accepting of a group invite + /// Entity* that is accepting the invite + /// Error code if accepting the invite failed, 0 if successful + int8 AcceptInvite(Entity* member); + + /// Handles declining of a group invite + /// Entity* that is declining the invite + void DeclineInvite(Entity* member); + + /// Checks to see if there is a group with the given id in the group manager + /// ID to check for + /// True if a group with the given ID is found + bool IsGroupIDValid(int32 group_id); + + /// Send updates to all the clients in the group + /// ID of the group to send updates to + /// Client* to exclude from the update, usually the one that triggers the update + void SendGroupUpdate(int32 group_id, Client* exclude = 0); + + + PlayerGroup* GetGroup(int32 group_id); + + /// Read locks the group list, no changes to the list should be made when using this + /// Name of the function called from, used for better debugging in the event of a deadlock + /// Line number that this was called from, used for better debugging in the event of a deadlock + void GroupHardLock(const char* function = 0, int32 line = 0U) { MGroups.lock(); } + void GroupLock(const char* function = 0, int32 line = 0U) { MGroups.lock_shared(); } + + /// Releases the readlock acquired from GroupLock() + /// Name of the function called from, used for better debugging in the event of a deadlock + /// Line number that this was called from, used for better debugging in the event of a deadlock + void ReleaseGroupHardLock(const char* function = 0, int32 line = 0U) { MGroups.unlock(); } + void ReleaseGroupLock(const char* function = 0, int32 line = 0U) { MGroups.unlock_shared(); } + + void ClearPendingInvite(Entity* member); + + void RemoveGroupBuffs(int32 group_id, Client* client); + + int32 GetGroupSize(int32 group_id); + + void SendGroupQuests(int32 group_id, Client* client); + bool HasGroupCompletedQuest(int32 group_id, int32 quest_id); + + void SimpleGroupMessage(int32 group_id, const char* message); + void SendGroupMessage(int32 group_id, int8 type, const char* message, ...); + void GroupMessage(int32 group_id, const char* message, ...); + void GroupChatMessage(int32 group_id, Spawn* from, int32 language, const char* message); + bool MakeLeader(int32 group_id, Entity* new_leader); + void UpdateGroupBuffs(); + + bool IsInGroup(int32 group_id, Entity* member); + Entity* IsPlayerInGroup(int32 group_id, int32 char_id); + // TODO: Any function below this comment + bool IsSpawnInGroup(int32 group_id, string name); // used in follow + Player* GetGroupLeader(int32 group_id); + +private: + int32 m_nextGroupID; // Used to generate a new unique id for new groups + + map m_groups; // int32 is the group id, PlayerGroup* is a pointer to the actual group + map m_pendingInvites; // First string is the person invited to the group, second string is the leader of the group + + mutable std::shared_mutex MGroups; // Mutex for the group map (m_groups) + Mutex MPendingInvites; // Mutex for the pending invites map (m_pendingInvites) +}; + +#endif \ No newline at end of file diff --git a/source/WorldServer/Quests.cpp b/source/WorldServer/Quests.cpp new file mode 100644 index 0000000..81fd2ed --- /dev/null +++ b/source/WorldServer/Quests.cpp @@ -0,0 +1,1867 @@ +/* + 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 . +*/ +#include "Quests.h" +#include "../common/ConfigReader.h" +#include "Player.h" +#include "LuaInterface.h" +#include "Spells.h" +#include "RaceTypes/RaceTypes.h" +#include "../common/Log.h" + +#ifdef WIN32 + #include +#else + #include +#endif + +extern LuaInterface* lua_interface; +extern ConfigReader configReader; +extern MasterFactionList master_faction_list; +extern MasterRaceTypeList race_types_list; + +QuestStep::QuestStep(int32 in_id, int8 in_type, string in_description, vector* in_ids, int32 in_quantity, const char* in_task_group, vector* in_locations, float in_max_variation, float in_percentage, int32 in_usableitemid){ + type = in_type; + description = in_description; + ids = 0; + locations = 0; + if(in_task_group) + task_group = string(in_task_group); + if(type != QUEST_STEP_TYPE_LOCATION) { + if (in_ids){ + ids = new std::map(); + for(int32 i=0;isize();i++) + ids->insert(make_pair(in_ids->at(i), true)); + } + } + else { // location step + if (in_locations) { + locations = new vector; + for(int32 i=0; i < in_locations->size(); i++) + locations->push_back(in_locations->at(i)); + } + } + max_variation = in_max_variation; + quantity = in_quantity; + step_progress = 0; + icon = 11; + id = in_id; + updated = false; + percentage = in_percentage; + usableitemid = in_usableitemid; +} + +QuestStep::QuestStep(QuestStep* old_step){ + type = old_step->type; + description = old_step->description; + task_group = old_step->task_group; + quantity = old_step->quantity; + max_variation = old_step->max_variation; + step_progress = 0; + ids = 0; + locations = 0; + if(type != QUEST_STEP_TYPE_LOCATION) { + if (old_step->ids){ + ids = new std::map(); + std::map::iterator itr; + for(itr = old_step->ids->begin();itr != old_step->ids->end();itr++) + ids->insert(make_pair(itr->first, itr->second)); + } + } + else { // location step + if (old_step->locations) { + locations = new vector; + for(int32 i=0; i < old_step->locations->size(); i++) + locations->push_back(old_step->locations->at(i)); + } + } + icon = old_step->icon; + id = old_step->id; + updated = false; + percentage = old_step->percentage; + usableitemid = old_step->usableitemid; +} + +QuestStep::~QuestStep(){ + safe_delete(ids); + safe_delete(locations); +} + +bool QuestStep::WasUpdated(){ + return updated; +} + +void QuestStep::WasUpdated(bool val){ + updated = val; +} + +float QuestStep::GetPercentage(){ + return percentage; +} + +int32 QuestStep::GetStepID(){ + return id; +} +int32 QuestStep::GetItemID() { + return usableitemid; +} +void QuestStep::SetComplete(){ + step_progress = quantity; + updated = true; +} + +bool QuestStep::Complete(){ + return step_progress >= quantity; +} + +int8 QuestStep::GetStepType(){ + return type; +} + +bool QuestStep::CheckStepReferencedID(int32 id){ + bool ret = false; + if(ids){ + std::map::iterator itr; + itr = ids->find(id); + if(itr != ids->end()) + ret = true; + } + return ret; +} + +bool QuestStep::CheckStepKillRaceReqUpdate(Spawn* spawn){ + bool ret = false; + if(ids){ + std::map::iterator itr; + for(itr = ids->begin();itr != ids->end();itr++){ + int32 curid = itr->first; + if(curid == spawn->GetRace() || + curid == race_types_list.GetRaceType(spawn->GetModelType()) || + curid == race_types_list.GetRaceBaseType(spawn->GetModelType())){ + ret = true; + break; + } + } + } + return ret; +} + +int16 QuestStep::GetIcon(){ + return icon; +} + +void QuestStep::SetIcon(int16 in_icon){ + icon = in_icon; +} + +const char* QuestStep::GetUpdateName(){ + if(update_name.length() > 0) + return update_name.c_str(); + return 0; +} + +void QuestStep::SetUpdateName(const char* name){ + update_name = string(name); +} + +const char* QuestStep::GetUpdateTargetName(){ + if(update_target_name.length() > 0) + return update_target_name.c_str(); + return 0; +} + +void QuestStep::SetUpdateTargetName(const char* name){ + update_target_name = string(name); +} + +bool QuestStep::CheckStepLocationUpdate(float char_x, float char_y, float char_z, int32 zone_id){ + bool ret = false; + if (locations) { + for (int32 i=0; i < locations->size(); i++) { + float total_diff = 0; + Location loc = locations->at(i); + if(loc.zone_id > 0 && loc.zone_id != zone_id) + continue; + + float diff = loc.x - char_x; //Check X + if(diff < 0) + diff *= -1; + if(diff <= max_variation) { + total_diff += diff; + + diff = loc.z - char_z; //Check Z (we check Z first because it is far more likely to be a much greater variation than y) + if(diff < 0) + diff *= -1; + if(diff <= max_variation) { + total_diff += diff; + if(total_diff <= max_variation) { //Check Total + + diff = loc.y - char_y; //Check Y + if(diff < 0) + diff *= -1; + if(diff <= max_variation) { + total_diff += diff; + if(total_diff <= max_variation) { //Check Total + ret = true; + break; + } + } + } + } + } + } + } + return ret; +} + +void QuestStep::SetStepProgress(int32 val){ + step_progress = val; +} + +int32 QuestStep::AddStepProgress(int32 val){ + updated = true; + if (val > (quantity - step_progress)){ + step_progress += (quantity - step_progress); + return (quantity - step_progress); + } + else + step_progress += val; + return val; +} + +int32 QuestStep::GetStepProgress() { + return step_progress; +} + +void QuestStep::ResetTaskGroup(){ + task_group = ""; +} + +const char* QuestStep::GetTaskGroup(){ + if(task_group.length() > 0) + return task_group.c_str(); + return 0; +} + +const char* QuestStep::GetDescription(){ + if(description.length() > 0) + return description.c_str(); + return 0; +} + +void QuestStep::SetDescription(string desc){ + description = desc; +} + +int16 QuestStep::GetQuestCurrentQuantity(){ + return (int16)step_progress; +} + +int16 QuestStep::GetQuestNeededQuantity(){ + return (int16)quantity; +} + +Quest::Quest(int32 in_id){ + id = in_id; + reward_status = 0; + quest_giver = 0; + deleted = false; + turned_in = false; + update_needed = true; + player = 0; + return_id = 0; + task_group_num = 1; + prereq_level = 1; + prereq_tslevel = 0; + prereq_max_level = 0; + prereq_max_tslevel = 0; + reward_coins = 0; + reward_coins_max = 0; + completed_flag = false; + has_sent_last_update = false; + enc_level = 0; + reward_exp = 0; + reward_tsexp = 0; + m_featherColor = 0; + m_repeatable = false; + yellow_name = false; + m_hidden = false; + generated_coin = 0; + m_questFlags = 0; + m_timestamp = 0; + m_completeCount = 0; + MQuestSteps.SetName("Quest::MQuestSteps"); + MCompletedActions.SetName("Quest::MCompletedActions"); + MProgressActions.SetName("Quest::MProgressActions"); + MFailedActions.SetName("Quest::failed_Actions"); + quest_state_temporary = false; + tmp_reward_status = 0; + tmp_reward_coins = 0; + completed_description = string(""); + quest_temporary_description = string(""); + quest_shareable_flag = 0; + can_delete_quest = false; +} + +Quest::Quest(Quest* old_quest){ + task_group_num = 1; + name = old_quest->name; + type = old_quest->type; + zone = old_quest->zone; + level = old_quest->level; + enc_level = old_quest->enc_level; + description = old_quest->description; + prereq_level = old_quest->prereq_level; + prereq_tslevel = old_quest->prereq_tslevel; + prereq_max_level = old_quest->prereq_max_level; + prereq_max_tslevel = old_quest->prereq_max_tslevel; + prereq_quests = old_quest->prereq_quests; + prereq_classes = old_quest->prereq_classes; + prereq_tradeskillclass = old_quest->prereq_tradeskillclass; + prereq_races = old_quest->prereq_races; + prereq_factions = old_quest->prereq_factions; + reward_coins = old_quest->reward_coins; + reward_coins_max = old_quest->reward_coins_max; + reward_factions = old_quest->reward_factions; + reward_status = old_quest->reward_status; + reward_comment = old_quest->reward_comment; + reward_exp = old_quest->reward_exp; + reward_tsexp = old_quest->reward_tsexp; + generated_coin = old_quest->generated_coin; + completed_flag = old_quest->completed_flag; + has_sent_last_update = old_quest->has_sent_last_update; + yellow_name = old_quest->yellow_name; + m_questFlags = old_quest->m_questFlags; + id = old_quest->id; + + vector quest_steps; + for(int32 i=0;iquest_steps.size();i++) + AddQuestStep(new QuestStep(old_quest->quest_steps[i])); + for(int32 i=0;iprereq_items.size();i++) + AddPrereqItem(new Item(old_quest->prereq_items[i])); + for(int32 i=0;ireward_items.size();i++) + AddRewardItem(new Item(old_quest->reward_items[i])); + for(int32 i=0;iselectable_reward_items.size();i++) + AddSelectableRewardItem(new Item(old_quest->selectable_reward_items[i])); + complete_actions = old_quest->complete_actions; + progress_actions = old_quest->progress_actions; + failed_actions = old_quest->failed_actions; + time_t raw; + struct tm *newtime; + time(&raw); + newtime = localtime(&raw); + day = newtime->tm_mday; + month = newtime->tm_mon + 1; + year = newtime->tm_year - 100; + visible = 1; + step_updates.clear(); + step_failures.clear(); + completed_description = old_quest->completed_description; + deleted = old_quest->deleted; + update_needed = old_quest->update_needed; + turned_in = old_quest->turned_in; + quest_giver = old_quest->quest_giver; + player = old_quest->player; + return_id = old_quest->return_id; + m_featherColor = old_quest->m_featherColor; + m_repeatable = old_quest->IsRepeatable(); + m_hidden = false; + m_timestamp = 0; + m_completeCount = old_quest->GetCompleteCount(); + MQuestSteps.SetName("Quest::MQuestSteps"); + MProgressActions.SetName("Quest::MProgressActions"); + MCompletedActions.SetName("Quest::MCompletedActions"); + quest_state_temporary = false; + tmp_reward_status = 0; + tmp_reward_coins = 0; + quest_temporary_description = string(""); + quest_shareable_flag = old_quest->GetQuestShareableFlag(); + can_delete_quest = old_quest->CanDeleteQuest(); +} + +Quest::~Quest(){ + + MQuestSteps.lock(); + for(int32 i=0;iGetStepID() == step_id){ + ret = quest_steps[i]; + break; + } + } + MQuestSteps.unlock(); + return ret; +} + +vector* Quest::GetQuestSteps(){ + return &quest_steps; +} + +vector* Quest::GetQuestUpdates(){ + return &step_updates; +} + +vector* Quest::GetQuestFailures(){ + return &step_failures; +} + +bool Quest::CheckQuestReferencedSpawns(Spawn* spawn){ + QuestStep* step = 0; + bool ret = false; + int32 spawnDBID = spawn->GetDatabaseID(); + + MQuestSteps.lock(); + for(int32 i=0;iComplete()) + continue; + switch(step->GetStepType()) + { + case QUEST_STEP_TYPE_KILL: + case QUEST_STEP_TYPE_NORMAL: { + if(step->CheckStepReferencedID(spawnDBID)) + ret = true; + + break; + } + case QUEST_STEP_TYPE_KILL_RACE_REQ: { + if(step->CheckStepKillRaceReqUpdate(spawn)) + ret = true; + + break; + } + } + } + MQuestSteps.unlock(); + return ret; +} + +bool Quest::CheckQuestKillUpdate(Spawn* spawn, bool update){ + QuestStep* step = 0; + bool ret = false; + int32 id = spawn->GetDatabaseID(); + int32 prog_added = 0; + MQuestSteps.lock(); + for(int32 i=0;iGetStepType() == QUEST_STEP_TYPE_KILL && !step->Complete() && step->CheckStepReferencedID(id)) || + (step->GetStepType() == QUEST_STEP_TYPE_KILL_RACE_REQ && !step->Complete() && step->CheckStepKillRaceReqUpdate(spawn))) + { + if (update == true) { + bool passed = true; + if (step->GetPercentage() < 100) + passed = (step->GetPercentage() > MakeRandomFloat(0, 100)); + if (passed) { + //Call the progress action function with the total amount of progress actually granted + prog_added = step->AddStepProgress(1); + if (lua_interface && progress_actions[step->GetStepID()].length() > 0 && prog_added > 0) + lua_interface->CallQuestFunction(this, progress_actions[step->GetStepID()].c_str(), player, prog_added); + step_updates.push_back(step); + step->SetUpdateName(spawn->GetName()); + ret = true; + } + else + step_failures.push_back(step); + } + else { + ret = true; + } + } + } + MQuestSteps.unlock(); + if(ret && update) + SetSaveNeeded(true); + return ret; +} + +int16 Quest::GetTaskGroupStep(){ + int16 ret = task_group_order.size(); + map::iterator itr; + MQuestSteps.lock(); + for(itr = task_group_order.begin(); itr != task_group_order.end(); itr++){ + if(task_group.count(itr->second) > 0){ + vector tmp_steps = task_group[itr->second]; + bool complete = true; + for(int32 i=0;iComplete() == false) + complete = false; + } + if(!complete){ + if(itr->first < ret) + ret = itr->first; + } + } + } + MQuestSteps.unlock(); + return ret; +} + +bool Quest::SetStepComplete(int32 step){ + QuestStep* quest_step = 0; + bool ret = false; + MQuestSteps.lock(); + for(int32 i=0;iGetStepID() == step){ + quest_step->SetComplete(); + step_updates.push_back(quest_step); + ret = true; + break; + } + } + MQuestSteps.unlock(); + if (ret) { + SetSaveNeeded(true); + } + + return ret; +} + +bool Quest::AddStepProgress(int32 step_id, int32 progress) { + QuestStep* quest_step = 0; + bool ret = false; + int32 prog_added = 0; + MQuestSteps.lock(); + for (int32 i = 0; i < quest_steps.size(); i++) { + quest_step = quest_steps[i]; + if (quest_step && quest_step->GetStepID() == step_id) { + bool passed = true; + if(quest_step->GetPercentage() < 100 && quest_step->GetPercentage() != 0) + passed = (quest_step->GetPercentage() > MakeRandomFloat(0, 100)); + if(passed) { + //Call the progress action function with the total amount of progress actually granted + prog_added = quest_step->AddStepProgress(progress); + if(lua_interface && progress_actions[step_id].length() > 0 && prog_added > 0) + lua_interface->CallQuestFunction(this, progress_actions[step_id].c_str(), player, prog_added); + step_updates.push_back(quest_step); + ret = true; + break; + } + else { + step_failures.push_back(quest_step); + break; + } + } + } + MQuestSteps.unlock(); + if(ret) + SetSaveNeeded(true); + return ret; +} + +int32 Quest::GetStepProgress(int32 step_id) { + QuestStep* quest_step = 0; + int32 ret = 0; + + MQuestSteps.lock(); + for (int32 i = 0; i < quest_steps.size(); i++) { + quest_step = quest_steps[i]; + if (quest_step && quest_step->GetStepID() == step_id) { + ret = quest_step->GetStepProgress(); + break; + } + } + MQuestSteps.unlock(); + + return ret; +} + +bool Quest::GetQuestStepCompleted(int32 step_id){ + bool ret = false; + QuestStep* quest_step = 0; + MQuestSteps.lock(); + for(int32 i=0;iGetStepID() == step_id && quest_step->Complete()){ + ret = true; + break; + } + } + MQuestSteps.unlock(); + return ret; +} + +int16 Quest::GetQuestStep(){ + int16 ret = 0; + QuestStep* step = 0; + MQuestSteps.lock(); + for(int32 i=0;iComplete()){ + ret = step->GetStepID(); + break; + } + } + MQuestSteps.unlock(); + return ret; +} + +bool Quest::QuestStepIsActive(int16 quest_step_id) { + bool ret = false; + QuestStep* step = 0; + MQuestSteps.lock(); + for (int32 i = 0; i < quest_steps.size(); i++) { + step = quest_steps[i]; + if (step->GetStepID() == quest_step_id && !step->Complete()) { + ret = true; + break; + } + } + MQuestSteps.unlock(); + return ret; +} + +bool Quest::CheckQuestChatUpdate(int32 id, bool update){ + QuestStep* step = 0; + bool ret = false; + int32 prog_added = 0; + MQuestSteps.lock(); + for(int32 i=0;iGetStepType() == QUEST_STEP_TYPE_CHAT && !step->Complete() && step->CheckStepReferencedID(id)){ + if(update){ + //Call the progress action function with the total amount of progress actually granted + prog_added = step->AddStepProgress(1); + if(lua_interface && progress_actions[step->GetStepID()].length() > 0 && prog_added > 0) + lua_interface->CallQuestFunction(this, progress_actions[step->GetStepID()].c_str(), player, prog_added); + step_updates.push_back(step); + } + ret = true; + } + } + MQuestSteps.unlock(); + if(ret) + SetSaveNeeded(true); + return ret; +} + +bool Quest::CheckQuestItemUpdate(int32 id, int8 quantity){ + QuestStep* step = 0; + bool ret = false; + int32 prog_added = 0; + MQuestSteps.lock(); + for(int32 i=0;iGetStepType() == QUEST_STEP_TYPE_OBTAIN_ITEM && !step->Complete() && step->CheckStepReferencedID(id)){ + bool passed = true; + if(step->GetPercentage() < 100) + passed = (step->GetPercentage() > MakeRandomFloat(0, 100)); + if(passed){ + //Call the progress action function with the total amount of progress actually granted + prog_added = step->AddStepProgress(quantity); + if(lua_interface && progress_actions[step->GetStepID()].length() > 0 && prog_added > 0) + lua_interface->CallQuestFunction(this, progress_actions[step->GetStepID()].c_str(), player, prog_added); + step_updates.push_back(step); + ret = true; + } + else + step_failures.push_back(step); + } + } + MQuestSteps.unlock(); + if(ret) + SetSaveNeeded(true); + return ret; +} + +bool Quest::CheckQuestRefIDUpdate(int32 id, int32 quantity){ + QuestStep* step = 0; + bool ret = false; + int32 prog_added = 0; + MQuestSteps.lock(); + for(int32 i=0;iComplete()) + continue; + + switch(step->GetStepType()) { + case QUEST_STEP_TYPE_HARVEST: + case QUEST_STEP_TYPE_CRAFT: { + map* id_list = step->GetUpdateIDs(); + map::iterator itr; + for(itr = id_list->begin();itr != id_list->end(); itr++){ + int32 update_id = itr->first; + if(update_id == id){ + bool passed = true; + if(step->GetPercentage() < 100) + passed = (step->GetPercentage() > MakeRandomFloat(0, 100)); + if(passed){ + //Call the progress action function with the total amount of progress actually granted + prog_added = step->AddStepProgress(quantity); + if(lua_interface && progress_actions[step->GetStepID()].length() > 0 && prog_added > 0) + lua_interface->CallQuestFunction(this, progress_actions[step->GetStepID()].c_str(), player, prog_added); + step_updates.push_back(step); + } + else + step_failures.push_back(step); + ret = true; + } + } + break; + } + } + } + } + MQuestSteps.unlock(); + if(ret) + SetSaveNeeded(true); + return ret; +} + +bool Quest::CheckQuestLocationUpdate(float char_x, float char_y, float char_z, int32 zone_id){ + QuestStep* step = 0; + bool ret = false; + int32 prog_added = 0; + MQuestSteps.lock(); + for(int32 i=0;iGetStepType() == QUEST_STEP_TYPE_LOCATION && !step->Complete() && step->CheckStepLocationUpdate(char_x, char_y, char_z, zone_id)){ + //Call the progress action function with the total amount of progress actually granted + prog_added = step->AddStepProgress(1); + if(lua_interface && progress_actions[step->GetStepID()].length() > 0 && prog_added > 0) + lua_interface->CallQuestFunction(this, progress_actions[step->GetStepID()].c_str(), player, prog_added); + step_updates.push_back(step); + ret = true; + } + } + MQuestSteps.unlock(); + if(ret) + SetSaveNeeded(true); + return ret; +} + +bool Quest::CheckQuestSpellUpdate(Spell* spell){ + QuestStep* step = 0; + bool ret = false; + int32 id = spell->GetSpellID(); + int32 prog_added = 0; + MQuestSteps.lock(); + for (int32 i = 0; i < quest_steps.size(); i++) { + step = quest_steps[i]; + if(step && step->GetStepType() == QUEST_STEP_TYPE_SPELL && !step->Complete() && step->CheckStepReferencedID(id)){ + bool passed = true; + if(step->GetPercentage() < 100) + passed = (step->GetPercentage() > MakeRandomFloat(0, 100)); + if(passed) { + //Call the progress action function with the total amount of progress actually granted + prog_added = step->AddStepProgress(1); + if(lua_interface && progress_actions[step->GetStepID()].length() > 0 && prog_added > 0) + lua_interface->CallQuestFunction(this, progress_actions[step->GetStepID()].c_str(), player, prog_added); + step_updates.push_back(step); + //step->SetUpdateName(spawn->GetName()); + ret = true; + } + else + step_failures.push_back(step); + } + } + MQuestSteps.unlock(); + if (ret) + SetSaveNeeded(true); + return ret; +} + +void Quest::SetQuestID(int32 in_id){ + id = in_id; +} + +EQ2Packet* Quest::OfferQuest(int16 version, Player* player){ + PacketStruct* packet = configReader.getStruct("WS_OfferQuest", version); + if(packet){ + packet->setDataByName("reward", "Quest Reward!"); + if(packet->GetVersion() <= 373) { + std::string quotedName = std::string("\"" + name + "\""); + packet->setDataByName("title", quotedName.c_str()); + } + else { + packet->setDataByName("title", name.c_str()); + } + packet->setDataByName("description", description.c_str()); + if(type == "Tradeskill") + packet->setDataByName("quest_difficulty", player->GetTSArrowColor(level)); + else + packet->setDataByName("quest_difficulty", player->GetArrowColor(level)); + packet->setDataByName("encounter_level", enc_level); + packet->setDataByName("unknown0", 255); + if (version >= 1188) + packet->setDataByName("unknown4b", 1); + else + packet->setDataByName("unknown", 5); + packet->setDataByName("level", level); + if(reward_coins > 0){ + packet->setDataByName("min_coin", reward_coins); + if (reward_coins_max) + packet->setDataByName("max_coin", reward_coins_max); + } + packet->setDataByName("status_points", reward_status); + if(reward_comment.length() > 0) + packet->setDataByName("text", reward_comment.c_str()); + if(reward_items.size() > 0){ + player->GetClient()->PopulateQuestRewardItems(&reward_items, packet); + } + if(selectable_reward_items.size() > 0){ + player->GetClient()->PopulateQuestRewardItems(&selectable_reward_items, packet, std::string("num_select_rewards"), + std::string("select_reward_id"), std::string("select_item")); + } + map* reward_factions = GetRewardFactions(); + if (reward_factions && reward_factions->size() > 0) { + packet->setArrayLengthByName("num_factions", reward_factions->size()); + map::iterator itr; + int16 index = 0; + for (itr = reward_factions->begin(); itr != reward_factions->end(); itr++) { + int32 faction_id = itr->first; + sint32 amount = itr->second; + const char* faction_name = master_faction_list.GetFactionNameByID(faction_id); + if (faction_name) { + packet->setArrayDataByName("faction_name", const_cast(faction_name), index); + packet->setArrayDataByName("amount", amount, index); + } + index++; + } + } + char accept[35] = {0}; + char decline[35] = {0}; + sprintf(accept, "q_accept_pending_quest %u", id); + sprintf(decline, "q_deny_pending_quest %u", id); + packet->setDataByName("accept_command", accept); + packet->setDataByName("decline_command", decline); + EQ2Packet* outapp = packet->serialize(); +#if EQDEBUG >= 9 + DumpPacket(outapp); +#endif + safe_delete(packet); + return outapp; + } + return 0; +} + +int32 Quest::GetQuestID(){ + return id; +} + +int8 Quest::GetQuestLevel(){ + return level; +} + +int8 Quest::GetVisible(){ + return visible; +} + +EQ2Packet* Quest::QuestJournalReply(int16 version, int32 player_crc, Player* player, QuestStep* updateStep, int8 update_count, bool old_completed_quest, bool quest_failure, bool display_quest_helper) { + PacketStruct* packet = configReader.getStruct("WS_QuestJournalReply", version); + if (packet) { + packet->setDataByName("quest_id", id); + packet->setDataByName("player_crc", player_crc); + packet->setDataByName("name", name.c_str()); + packet->setDataByName("description", description.c_str()); + packet->setDataByName("zone", zone.c_str()); + packet->setDataByName("type", type.c_str()); + packet->setDataByName("complete_header", "To complete this quest, I must do the following tasks:"); + packet->setDataByName("day", day); + packet->setDataByName("month", month); + packet->setDataByName("year", year); + packet->setDataByName("level", level); + packet->setDataByName("visible", 1); + /* To get the quest timer to work you need to set unknown, index 4 to 1 and the time stamp + to the current time + the time in seconds you want to show in the journal*/ + if (m_timestamp > 0) { + packet->setDataByName("unknown", 1, 4); + packet->setDataByName("time_stamp", m_timestamp); + } + + int8 difficulty = 0; + if (type == "Tradeskill") + difficulty = player->GetTSArrowColor(level); + else + difficulty = player->GetArrowColor(level); + packet->setDataByName("difficulty", difficulty); + if (enc_level > 4) + packet->setDataByName("encounter_level", enc_level); + else + packet->setDataByName("encounter_level", 4); + packet->setDataByName("unknown2b", 4); + int16 task_groups_completed = 0; + int16 total_task_groups = 0; + vector primary_order; + vector secondary_order; + packet->setDataByName("unknown8", 1, 1); + packet->setSubstructArrayDataByName("reward_data", "unknown8", 1, 1); + packet->setDataByName("unknown8b", 255); + + if (version >= 1096) { + packet->setDataByName("deletable", (int8)CanDeleteQuest()); + packet->setDataByName("shareable", (int8)CanShareQuestCriteria(player->GetClient(),false)); + } + else { + packet->setDataByName("unknown3", 1, 5); // this supposed to be CanDeleteQuest? + packet->setDataByName("unknown3", 1, 6); // this supposed to be CanShareQuestCriteria? + } + + int8 map_data_count = 0; + + packet->setDataByName("bullets", 1); + if (old_completed_quest) { + if (version >= 1096 || version == 546 || version == 561) { + packet->setDataByName("complete", 1); + packet->setDataByName("complete2", 1); + packet->setDataByName("complete3", 1); + } + else { + packet->setDataByName("unknown3", 1); + packet->setDataByName("unknown3", 1, 1); + packet->setDataByName("unknown3", 1, 2); + packet->setDataByName("unknown3", 1, 5); + packet->setDataByName("unknown3", 1, 6); + } + } + // must always send for newer clients like AoM or else crash! + else if (GetCompleted() && ((version >= 1096) || ((version == 561 || version == 546) && HasSentLastUpdate()))) { //need to send last quest update before erasing all progress of the quest + packet->setDataByName("complete", 1); + packet->setDataByName("complete2", 1); + packet->setDataByName("complete3", 1); + packet->setDataByName("num_task_groups", 1); + packet->setArrayDataByName("task_group", completed_description.c_str()); + packet->setArrayDataByName("unknown4", 0xFFFFFFFF); + packet->setDataByName("onscreen_update", 1); + packet->setDataByName("update_task_number", 1); + packet->setDataByName("onscreen_update", 1); + packet->setDataByName("onscreen_update_text", completed_description.c_str()); + if (updateStep) + packet->setDataByName("onscreen_update_icon", updateStep->GetIcon()); + else + packet->setDataByName("onscreen_update_icon", 0); + // Is Heritage or db entry for classic eq quest turn in sound + packet->setDataByName("classic_eq_sound", 0); + // No clue what this does, set it to mach live packets + packet->setDataByName("unknown12b", 1, 0); + } + else { + map task_group_names; + map::iterator order_itr; + vector* steps = 0; + MQuestSteps.lock(); + for (order_itr = task_group_order.begin(); order_itr != task_group_order.end(); order_itr++) { + //The following is kind of crazy, but necessary to order the quests with completed ones first. This is to make the packet like live's packet + //for(itr = task_group.begin(); itr != task_group.end(); itr++){ + bool complete = true; + if (task_group.count(order_itr->second) > 0) { + steps = &(task_group[order_itr->second]); + for (int32 i = 0; i < steps->size(); i++) { + if (steps->at(i)->Complete() == false) + complete = false; + } + if (complete) { + for (int32 i = 0; i < steps->size(); i++) { + if (i == 0) + task_group_names[steps->at(i)] = order_itr->second; + primary_order.push_back(steps->at(i)); + } + task_groups_completed++; + total_task_groups++; + } + else { + for (int32 i = 0; i < steps->size(); i++) { + if (i == 0) + task_group_names[steps->at(i)] = order_itr->second; + secondary_order.push_back(steps->at(i)); + } + total_task_groups++; + } + } + } + packet->setDataByName("task_groups_completed", task_groups_completed); + packet->setArrayLengthByName("num_task_groups", total_task_groups); + if (IsTracked() /*display_quest_helper*/ && task_groups_completed < total_task_groups) + packet->setDataByName("display_quest_helper", 1); + int16 index = 0; + QuestStep* step = 0; + for (int32 i = 0; i < primary_order.size(); i++) { + if (primary_order[i]->GetTaskGroup()) { + if (task_group_names.count(primary_order[i]) > 0) + packet->setArrayDataByName("task_group", primary_order[i]->GetTaskGroup(), index); + else + continue; + packet->setSubArrayLengthByName("num_tasks", task_group[primary_order[i]->GetTaskGroup()].size(), index); + packet->setSubArrayLengthByName("num_updates", task_group[primary_order[i]->GetTaskGroup()].size(), index); + map_data_count += task_group[primary_order[i]->GetTaskGroup()].size(); + if (task_group[primary_order[i]->GetTaskGroup()].size() > 0) { + packet->setDataByName("bullets", 1); + } + for (int32 x = 0; x < task_group[primary_order[i]->GetTaskGroup()].size(); x++) { + step = task_group[primary_order[i]->GetTaskGroup()].at(x); + if (!step) + continue; + if (step->GetDescription()) + packet->setSubArrayDataByName("task", step->GetDescription(), index, x); + packet->setSubArrayDataByName("task_completed", 1, index, x); + packet->setSubArrayDataByName("index", x, index, x); + packet->setSubArrayDataByName("update_currentval", step->GetQuestCurrentQuantity(), index, x); + if(step->GetQuestCurrentQuantity() > 0) + packet->setDataByName("journal_updated", 1); + packet->setSubArrayDataByName("update_maxval", step->GetQuestNeededQuantity(), index, x); + if (step->GetUpdateTargetName()) + packet->setSubArrayDataByName("update_target_name", step->GetUpdateTargetName(), index, x); + packet->setSubArrayDataByName("icon", step->GetIcon(), index, x); + if (updateStep && step == updateStep) { + packet->setDataByName("update", 1); + // packet->setDataByName("unknown5d", 1); + if (!quest_failure) + packet->setDataByName("onscreen_update", 1); + packet->setDataByName("onscreen_update_count", update_count); + packet->setDataByName("onscreen_update_icon", step->GetIcon()); + if (step->GetUpdateName()) + packet->setDataByName("onscreen_update_text", step->GetUpdateName()); + else if (step->GetDescription()) + packet->setDataByName("onscreen_update_text", step->GetDescription()); + packet->setDataByName("update_task_number", x); + packet->setDataByName("update_taskgroup_number", index); + } + packet->setArrayDataByName("unknown4", 0xFFFFFFFF, x); + } + index++; + } + else { + if (task_group_names.count(primary_order[i]) > 0) { + step = primary_order[i]; + if (updateStep && step == updateStep) { + packet->setDataByName("update", 1); + // packet->setDataByName("unknown5d", 1); + if (!quest_failure) + packet->setDataByName("onscreen_update", 1); + packet->setDataByName("onscreen_update_count", update_count); + packet->setDataByName("onscreen_update_icon", step->GetIcon()); + if (step->GetUpdateName()) + packet->setDataByName("onscreen_update_text", step->GetUpdateName()); + else if (step->GetDescription()) + packet->setDataByName("onscreen_update_text", step->GetDescription()); + packet->setDataByName("update_task_number", i); + packet->setDataByName("update_taskgroup_number", index); + } + packet->setArrayDataByName("task_group", primary_order[i]->GetDescription(), index); + index++; + } + } + } + for (int32 i = 0; i < secondary_order.size(); i++) { + if (secondary_order[i]->GetTaskGroup()) { + if (task_group_names.count(secondary_order[i]) > 0) + packet->setArrayDataByName("task_group", secondary_order[i]->GetTaskGroup(), index); + else + continue; + packet->setSubArrayLengthByName("num_tasks", task_group[secondary_order[i]->GetTaskGroup()].size(), index); + packet->setSubArrayLengthByName("num_updates", task_group[secondary_order[i]->GetTaskGroup()].size(), index); + map_data_count += task_group[secondary_order[i]->GetTaskGroup()].size(); + if (task_group[secondary_order[i]->GetTaskGroup()].size() > 0) + packet->setDataByName("bullets", 1); + for (int32 x = 0; x < task_group[secondary_order[i]->GetTaskGroup()].size(); x++) { + step = task_group[secondary_order[i]->GetTaskGroup()].at(x); + if (!step) + continue; + if (step->GetDescription()) + packet->setSubArrayDataByName("task", step->GetDescription(), index, x); + if (step->Complete()) + packet->setSubArrayDataByName("task_completed", 1, index, x); + else + packet->setSubArrayDataByName("task_completed", 0, index, x); + packet->setSubArrayDataByName("index", x, index, x); + packet->setSubArrayDataByName("update_currentval", step->GetQuestCurrentQuantity(), index, x); + if (step->GetQuestCurrentQuantity() > 0) + packet->setDataByName("journal_updated", 1); + packet->setSubArrayDataByName("update_maxval", step->GetQuestNeededQuantity(), index, x); + packet->setSubArrayDataByName("icon", step->GetIcon(), index, x); + if (step->GetUpdateTargetName()) + packet->setSubArrayDataByName("update_target_name", step->GetUpdateTargetName(), index, x); + if (updateStep && step == updateStep) { + packet->setDataByName("update", 1); + if (!quest_failure) + packet->setDataByName("onscreen_update", 1); + packet->setDataByName("onscreen_update_count", update_count); + packet->setDataByName("onscreen_update_icon", step->GetIcon()); + if (step->GetUpdateName()) + packet->setDataByName("onscreen_update_text", step->GetUpdateName()); + else if (step->GetDescription()) + packet->setDataByName("onscreen_update_text", step->GetDescription()); + if (quest_failure) + packet->setDataByName("onscreen_update_text2", "failed"); + packet->setDataByName("update_task_number", x); + packet->setDataByName("update_taskgroup_number", index); + } + packet->setArrayDataByName("unknown4", 0xFFFFFFFF, x); + } + index++; + } + else { + if (task_group_names.count(secondary_order[i]) > 0) { + step = secondary_order[i]; + if (updateStep && step == updateStep) { + packet->setDataByName("update", 1); + if (!quest_failure) + packet->setDataByName("onscreen_update", 1); + packet->setDataByName("onscreen_update_count", update_count); + packet->setDataByName("onscreen_update_icon", step->GetIcon()); + if (step->GetUpdateName()) + packet->setDataByName("onscreen_update_text", step->GetUpdateName()); + else if (step->GetDescription()) + packet->setDataByName("onscreen_update_text", step->GetDescription()); + packet->setDataByName("update_task_number", i); + packet->setDataByName("update_taskgroup_number", index); + } + if (task_group_names.size() == 1) { + packet->setSubArrayLengthByName("num_tasks", 1, index); + packet->setSubArrayDataByName("task", secondary_order[i]->GetDescription(), index); + packet->setDataByName("bullets", 0); + } + else + packet->setArrayDataByName("task_group", secondary_order[i]->GetDescription(), index); + index++; + } + } + } + + for (int16 i = 0; i < total_task_groups; i++) + packet->setArrayDataByName("unknown4", 0xFFFFFFFF, i); + + if (step && step->GetItemID() > 0) { + packet->setArrayLengthByName("usable_item_count", 1); + Item* item = player->GetPlayerItemList()->GetItemFromID(step->GetItemID(), 1); + if (item) { + packet->setArrayDataByName("item_id", item->details.item_id, 0); + packet->setArrayDataByName("item_unique_id", item->details.unique_id, 0); + packet->setArrayDataByName("item_icon", item->GetIcon(version), 0); + packet->setArrayDataByName("unknown2", 0xFFFFFFFF, 0);//item->details.item_id, 0); + + } + } + if (GetCompleted()) { //mark the last update as being sent, next time we send the quest reply, it will only be a brief portion + SetSentLastUpdate(true); + } + } + MQuestSteps.unlock(); + + + string reward_str = ""; + if (version >= 1096 || version == 546 || version == 561) + reward_str = "reward_data_"; + string tmp = reward_str + "reward"; + packet->setDataByName(tmp.c_str(), "Quest Reward!"); + if (reward_coins > 0) { + tmp = reward_str + "min_coin"; + packet->setDataByName(tmp.c_str(), reward_coins); + tmp = reward_str + "max_coin"; + packet->setDataByName(tmp.c_str(), reward_coins_max); + } + tmp = reward_str + "status_points"; + packet->setDataByName(tmp.c_str(), reward_status); + if (reward_comment.length() > 0) { + tmp = reward_str + "text"; + packet->setDataByName(tmp.c_str(), reward_comment.c_str()); + } + if (reward_items.size() > 0) { + tmp = reward_str + "num_rewards"; + packet->setArrayLengthByName(tmp.c_str(), reward_items.size()); + Item* item = 0; + for (int32 i = 0; i < reward_items.size(); i++) { + item = reward_items[i]; + packet->setArrayDataByName("reward_id", item->details.item_id, i); + if (version < 860) + packet->setItemArrayDataByName("item", item, player, i, 0, version <= 373 ? -2 : -1); + else if (version < 1193) + packet->setItemArrayDataByName("item", item, player, i); + else + packet->setItemArrayDataByName("item", item, player, i, 0, 2); + } + } + if (selectable_reward_items.size() > 0) { + tmp = reward_str + "num_select_rewards"; + packet->setArrayLengthByName(tmp.c_str(), selectable_reward_items.size()); + Item* item = 0; + for (int32 i = 0; i < selectable_reward_items.size(); i++) { + item = selectable_reward_items[i]; + packet->setArrayDataByName("select_reward_id", item->details.item_id, i); + if (version < 860) + packet->setItemArrayDataByName("select_item", item, player, i, 0, version <= 373 ? -2 : -1); + else if (version < 1193) + packet->setItemArrayDataByName("select_item", item, player, i); + else + packet->setItemArrayDataByName("select_item", item, player, i, 0, 2); + } + } + map* reward_factions = GetRewardFactions(); + if (reward_factions && reward_factions->size() > 0) { + tmp = reward_str + "num_factions"; + packet->setArrayLengthByName(tmp.c_str(), reward_factions->size()); + map::iterator itr; + int16 index = 0; + for (itr = reward_factions->begin(); itr != reward_factions->end(); itr++) { + int32 faction_id = itr->first; + sint32 amount = itr->second; + const char* faction_name = master_faction_list.GetFactionNameByID(faction_id); + if (faction_name) { + packet->setArrayDataByName("faction_name", const_cast(faction_name), index); + packet->setArrayDataByName("amount", amount, index); + } + index++; + } + } + + //packet->setArrayLengthByName("map_data_array_size", map_data_count); + + EQ2Packet* outapp = packet->serialize(); + //packet->PrintPacket(); + //DumpPacket(outapp); + safe_delete(packet); + return outapp; + } + + return 0; +} + +bool Quest::AddQuestStep(QuestStep* step){ + bool ret = true; + MQuestSteps.lock(); + if(quest_step_map.count(step->GetStepID()) == 0){ + quest_steps.push_back(step); + quest_step_reverse_map[step] = step->GetStepID(); + quest_step_map[step->GetStepID()] = step; + if(step->GetTaskGroup()){ + string tmp_task_group = string(step->GetTaskGroup()); + if(task_group.count(tmp_task_group) == 0){ + task_group_order[task_group_num] = tmp_task_group; + task_group_num++; + } + task_group[tmp_task_group].push_back(step); + } + else{ + string tmp_task_group = string(step->GetDescription()); + if(task_group.count(tmp_task_group) == 0){ + task_group_order[task_group_num] = tmp_task_group; + task_group_num++; + } + task_group[tmp_task_group].push_back(step); + } + } + else{ + LogWrite(QUEST__ERROR, 0, "Quest", "Quest Warning in '%s': step %u used multiple times!", GetName(), step->GetStepID()); + ret = false; + } + MQuestSteps.unlock(); + return ret; +} + +bool Quest::RemoveQuestStep(int32 step, Client* client) { + bool ret = true; + MQuestSteps.writelock(__FUNCTION__, __LINE__); + if (quest_step_map.count(step) > 0) { + QuestStep* quest_step = quest_step_map[step]; + if (quest_step) { + client->QueuePacket(QuestJournalReply(client->GetVersion(), client->GetNameCRC(), client->GetPlayer(), quest_step, 0, 0, true)); + int32 step2 = step - 1; + QuestStep* reset_step = quest_step_map[step2]; + reset_step->SetStepProgress(0); + MCompletedActions.lock(); + complete_actions.erase(step); + MCompletedActions.unlock(); + string tmp_task_group = string(quest_step->GetTaskGroup()); + if(task_group.count(tmp_task_group) > 0) { + task_group.erase(tmp_task_group); + int task_num = 0; + map::iterator itr; + for (itr = task_group_order.begin(); itr != task_group_order.end(); itr++) { + if (itr->second == tmp_task_group) { + task_num = itr->first; + break; + } + } + if (task_num > 0) { + task_group_order.erase(task_num); + task_group_num--; + } + } + + // Remove the step from the various maps before we delete it + quest_step_map.erase(step); + quest_step_reverse_map.erase(quest_step); + vector::iterator itr; + for (itr = quest_steps.begin(); itr != quest_steps.end(); itr++) { + if ((*itr) == quest_step) { + quest_steps.erase(itr); + break; + } + } + safe_delete(quest_step); + } + else { + LogWrite(QUEST__ERROR, 0, "Quest", "Unable to get a valid step (%u) for quest %s", step, GetName()); + ret = false; + } + } + else { + LogWrite(QUEST__ERROR, 0, "Quest", "Unable to remove step (%u) for quest %s", step, GetName()); + ret = false; + } + MQuestSteps.releasewritelock(__FUNCTION__, __LINE__); + return ret; +} + +QuestStep* Quest::AddQuestStep(int32 id, int8 in_type, string in_description, vector* in_ids, int32 in_quantity, const char* in_task_group, vector* in_locations, float in_max_variation, float in_percentage,int32 in_usableitemid){ + QuestStep* step = new QuestStep(id, in_type, in_description, in_ids, in_quantity, in_task_group, in_locations, in_max_variation, in_percentage, in_usableitemid); + if(!AddQuestStep(step)){ + safe_delete(step); + return 0; + } + return step; +} + +void Quest::AddPrereqClass(int8 class_id){ + prereq_classes.push_back(class_id); +} + +void Quest::AddPrereqTradeskillClass(int8 class_id) { + prereq_tradeskillclass.push_back(class_id); +} + +void Quest::AddPrereqModelType(int16 model_type){ + prereq_model_types.push_back(model_type); +} + +void Quest::AddPrereqRace(int8 race){ + prereq_races.push_back(race); +} + +void Quest::SetPrereqLevel(int8 lvl){ + prereq_level = lvl; +} + +void Quest::SetPrereqTSLevel(int8 lvl) { + prereq_tslevel = lvl; +} + +void Quest::AddPrereqQuest(int32 quest_id){ + prereq_quests.push_back(quest_id); +} + +void Quest::AddPrereqFaction(int32 faction_id, sint32 min, sint32 max){ + QuestFactionPrereq faction; + faction.faction_id = faction_id; + faction.min = min; + faction.max = max; + prereq_factions.push_back(faction); +} + +void Quest::AddPrereqItem(Item* item){ + prereq_items.push_back(item); +} + +void Quest::AddRewardItemVec(vector* items, Item* item, bool combine_items) { + if(!items || !item) + return; + + bool stacked = false; + + if(combine_items) { + for(int32 i=0;isize();i++) { + Item* cur_item = (Item*)items->at(i); + if(cur_item->stack_count > 1) { + if(cur_item->details.item_id == item->details.item_id && cur_item->details.count+1 < cur_item->stack_count) { + if(!cur_item->details.count) // sometimes the count is 0, so we want it to be 2 now + cur_item->details.count = 2; + else + cur_item->details.count += 1; + stacked = true; + break; + } + } + } + } + + if(!stacked) { + items->push_back(item); + } +} + +void Quest::AddSelectableRewardItem(Item* item){ + AddRewardItemVec(&selectable_reward_items, item, false); +} + +void Quest::AddRewardItem(Item* item){ + AddRewardItemVec(&reward_items, item); +} + +void Quest::AddTmpRewardItem(Item* item){ + AddRewardItemVec(&tmp_reward_items, item); +} + +void Quest::GetTmpRewardItemsByID(std::vector* items) { + if(!items) + return; + for(int32 i=0;ipush_back(tmp_reward_items[i]); +} + +void Quest::AddRewardCoins(int32 copper, int32 silver, int32 gold, int32 plat){ + reward_coins = copper + (silver*100) + (gold*10000) + ((int64)plat*1000000); +} + +void Quest::AddRewardCoinsMax(int64 coins){ + reward_coins_max = coins; +} + +void Quest::AddRewardFaction(int32 faction_id, sint32 amount){ + reward_factions[faction_id] = amount; +} + +void Quest::SetRewardStatus(int32 amount){ + reward_status = amount; +} + +void Quest::SetRewardComment(string comment){ + reward_comment = comment; +} + +void Quest::SetRewardXP(int32 xp){ + reward_exp = xp; +} + +vector* Quest::GetPrereqClasses(){ + return &prereq_classes; +} + +vector* Quest::GetPrereqTradeskillClasses(){ + return &prereq_tradeskillclass; +} + +vector* Quest::GetPrereqFactions(){ + return &prereq_factions; +} + +vector* Quest::GetPrereqRaces(){ + return &prereq_races; +} + +vector* Quest::GetPrereqModelTypes(){ + return &prereq_model_types; +} + +int8 Quest::GetPrereqLevel(){ + return prereq_level; +} + +int8 Quest::GetPrereqTSLevel() { + return prereq_tslevel; +} + +vector* Quest::GetPrereqQuests(){ + return &prereq_quests; +} + +vector* Quest::GetPrereqItems(){ + return &prereq_items; +} + +vector* Quest::GetRewardItems(){ + return &reward_items; +} + +vector* Quest::GetTmpRewardItems(){ + return &tmp_reward_items; +} + +vector* Quest::GetSelectableRewardItems(){ + return &selectable_reward_items; +} + +map* Quest::GetRewardFactions() { + return &reward_factions; +} + +void Quest::SetTaskGroupDescription(int32 step, string desc, bool display_bullets){ + MQuestSteps.lock(); + if(step <= task_group_num && task_group_order.count(step) > 0){ + string old_desc = task_group_order[step]; + if(display_bullets) + task_group[desc] = task_group[old_desc]; + else{ + if(task_group[old_desc].size() > 0){ + task_group[desc].push_back(task_group[old_desc].at(0)); + task_group[desc].at(0)->ResetTaskGroup(); + task_group[desc].at(0)->SetDescription(desc); + } + } + task_group.erase(old_desc); + task_group_order[step] = desc; + } + MQuestSteps.unlock(); +} + +void Quest::SetStepDescription(int32 step, string desc){ + MQuestSteps.lock(); + for(int32 i=0;iGetStepID() == step){ + quest_steps[i]->SetDescription(desc); + break; + } + } + MQuestSteps.unlock(); +} + +void Quest::SetDescription(string desc){ + description = desc; +} + +void Quest::SetCompletedDescription(string desc){ + completed_description = desc; +} + +const char* Quest::GetCompletedDescription(){ + return completed_description.c_str(); +} + +void Quest::AddCompleteAction(int32 step, string action){ + MCompletedActions.lock(); + complete_actions[step] = action; + MCompletedActions.unlock(); +} + +void Quest::AddProgressAction(int32 step, string action){ + MProgressActions.lock(); + progress_actions[step] = action; + MProgressActions.unlock(); +} + +void Quest::AddFailedAction(int32 step, string action) { + MFailedActions.writelock(__FUNCTION__, __LINE__); + failed_actions[step] = action; + MFailedActions.releasewritelock(__FUNCTION__, __LINE__); +} + +void Quest::SetCompleteAction(string action){ + complete_action = action; +} + +const char* Quest::GetCompleteAction(){ + if(complete_action.length() > 0) + return complete_action.c_str(); + return 0; +} + +const char* Quest::GetCompleteAction(int32 step){ + const char* ret = 0; + MCompletedActions.lock(); + if(complete_actions.count(step) > 0) + ret = complete_actions[step].c_str(); + MCompletedActions.unlock(); + return ret; +} + +void Quest::SetQuestReturnNPC(int32 id){ + return_id = id; +} + +int32 Quest::GetQuestReturnNPC(){ + return return_id; +} + +void Quest::SetQuestGiver(int32 id){ + quest_giver = id; + if(return_id == 0) + return_id = id; +} + +bool Quest::GetCompleted(){ + bool ret = true; + for(int32 i=0;iComplete() == false){ + ret = false; + break; + } + } + return ret; +} + +bool Quest::CheckCategoryYellow(){ + bool ret = false; + string category = GetType(); + + //Check for these category names, return true to set category as yellow in the quest journal + if (category == "Signature" || category == "Heritage" || category == "Hallmark" || category == "Deity" || category == "Miscellaneaous" || category == "Language" || category == "Lore and Legend" || category == "World Event" || category == "Tradeskill") + ret = true; + return ret; +} + +Player* Quest::GetPlayer(){ + return player; +} + +void Quest::SetPlayer(Player* in_player){ + player = in_player; +} + +void Quest::SetGeneratedCoin(int64 coin){ + generated_coin = coin; +} + +int32 Quest::GetQuestGiver(){ + return quest_giver; +} + +int64 Quest::GetCoinsReward(){ + return reward_coins; +} + +int64 Quest::GetCoinsRewardMax(){ + return reward_coins_max; +} + +int64 Quest::GetGeneratedCoin(){ + return generated_coin; +} + +int32 Quest::GetExpReward(){ + return reward_exp; +} + +void Quest::GiveQuestReward(Player* player){ + if(reward_coins > 0) + player->AddCoins(reward_coins); + + if(!GetQuestTemporaryState()) + reward_items.clear(); +} + +bool Quest::GetDeleted(){ + return deleted; +} + +void Quest::SetDeleted(bool val){ + deleted = val; +} + +bool Quest::GetUpdateRequired(){ + return update_needed; +} + +void Quest::SetUpdateRequired(bool val){ + update_needed = val; +} + +void Quest::SetTurnedIn(bool val){ + turned_in = val; + visible = 0; +} + +bool Quest::GetTurnedIn(){ + return turned_in; +} + +void Quest::SetName(string in_name) { + name = in_name; +} + +void Quest::SetType(string in_type) { + type = in_type; +} + +void Quest::SetLevel(int8 in_level) { + level = in_level; +} + +void Quest::SetCompletedFlag(bool val) { + completed_flag = val; + SetUpdateRequired(true); + if(player && player->GetClient()) + player->GetClient()->SendQuestJournalUpdate(this, true); +} + +void Quest::SetStepTimer(int32 duration) { + m_timestamp = duration == 0 ? 0 : Timer::GetUnixTimeStamp() + duration; +} + +void Quest::StepFailed(int32 step) { + if(lua_interface && failed_actions.count(step) > 0 && failed_actions[step].length() > 0) + lua_interface->CallQuestFunction(this, failed_actions[step].c_str(), player); +} + +MasterQuestList::MasterQuestList(){ + MQuests.SetName("MasterQuestList::MQuests"); +} + +void MasterQuestList::Reload(){ + MQuests.lock(); + quests.clear(); //deletes taken care of in LuaInterface + if(lua_interface) + lua_interface->DestroyQuests(true); + MQuests.unlock(); +} + +void MasterQuestList::LockQuests(){ + MQuests.lock(); +} + +void MasterQuestList::UnlockQuests(){ + MQuests.unlock(); +} + +void Quest::SetQuestTemporaryState(bool tempState, std::string customDescription) +{ + if(!tempState) + { + tmp_reward_coins = 0; + tmp_reward_status = 0; + + for(int32 i=0;iGetPlayer()->GetQuest(GetQuestID()); // gets active quest if available + if(((GetQuestShareableFlag() & QUEST_SHAREABLE_COMPLETED) == 0) && quest_sharer->GetPlayer()->HasQuestBeenCompleted(GetQuestID())) { + if(display_client_msg) + quest_sharer->SimpleMessage(CHANNEL_COLOR_RED, "You cannot share this quest after it is already completed."); + return false; + } + else if((GetQuestShareableFlag() == QUEST_SHAREABLE_COMPLETED) && !quest_sharer->GetPlayer()->HasQuestBeenCompleted(GetQuestID())) { + if(display_client_msg) + quest_sharer->SimpleMessage(CHANNEL_COLOR_RED, "You cannot share this quest until it is completed."); + return false; + } + else if(((GetQuestShareableFlag() & QUEST_SHAREABLE_DURING) == 0) && clientQuest && clientQuest->GetQuestStep() > 1) { + if(display_client_msg) + quest_sharer->SimpleMessage(CHANNEL_COLOR_RED, "You cannot share this quest after already completing a step."); + return false; + } + else if(((GetQuestShareableFlag() & QUEST_SHAREABLE_ACTIVE) == 0) && clientQuest) { + if(display_client_msg) + quest_sharer->SimpleMessage(CHANNEL_COLOR_RED, "You cannot share this quest while it is active."); + return false; + } + else if(!GetQuestShareableFlag()) { + if(display_client_msg) + quest_sharer->SimpleMessage(CHANNEL_COLOR_RED, "You cannot share this quest."); + return false; + } + else if(((GetQuestShareableFlag() & QUEST_SHAREABLE_COMPLETED) == 0) && clientQuest == nullptr) { + if(display_client_msg) + quest_sharer->SimpleMessage(CHANNEL_COLOR_RED, "You do not have this quest."); + return false; + } + + return true; +} \ No newline at end of file diff --git a/source/WorldServer/Quests.h b/source/WorldServer/Quests.h new file mode 100644 index 0000000..a70942f --- /dev/null +++ b/source/WorldServer/Quests.h @@ -0,0 +1,447 @@ +/* + 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 . +*/ +#ifndef QUESTS_H +#define QUESTS_H + +#include +#include +#include "Items/Items.h" + +#define QUEST_STEP_TYPE_KILL 1 +#define QUEST_STEP_TYPE_CHAT 2 +#define QUEST_STEP_TYPE_OBTAIN_ITEM 3 +#define QUEST_STEP_TYPE_LOCATION 4 +#define QUEST_STEP_TYPE_SPELL 5 +#define QUEST_STEP_TYPE_NORMAL 6 +#define QUEST_STEP_TYPE_CRAFT 7 +#define QUEST_STEP_TYPE_HARVEST 8 +#define QUEST_STEP_TYPE_KILL_RACE_REQ 9 // kill using race type requirement instead of npc db id + +#define QUEST_DISPLAY_STATUS_HIDDEN 0 +#define QUEST_DISPLAY_STATUS_NO_CHECK 1 +#define QUEST_DISPLAY_STATUS_YELLOW 2 +#define QUEST_DISPLAY_STATUS_COMPLETED 4 +#define QUEST_DISPLAY_STATUS_REPEATABLE 8 +#define QUEST_DISPLAY_STATUS_CAN_SHARE 16 +#define QUEST_DISPLAY_STATUS_COMPLETE_FLAG 32 +#define QUEST_DISPLAY_STATUS_SHOW 64 +#define QUEST_DISPLAY_STATUS_CHECK 128 + + +#define QUEST_SHAREABLE_NONE 0 +#define QUEST_SHAREABLE_ACTIVE 1 +#define QUEST_SHAREABLE_DURING 2 +#define QUEST_SHAREABLE_COMPLETED 4 + +struct QuestFactionPrereq{ + int32 faction_id; + sint32 min; + sint32 max; +}; + +struct Location { + int32 id; + float x; + float y; + float z; + int32 zone_id; +}; + +class QuestStep{ +public: + QuestStep(int32 in_id, int8 in_type, string in_description, vector* in_ids, int32 in_quantity, const char* in_task_group, vector* in_locations, float in_max_variation, float in_percentage, int32 in_usableitemid); + QuestStep(QuestStep* old_step); + ~QuestStep(); + bool CheckStepKillRaceReqUpdate(Spawn* spawn); + bool CheckStepReferencedID(int32 id); + bool CheckStepLocationUpdate(float char_x, float char_y, float char_z, int32 zone_id); + int32 AddStepProgress(int32 val); + void SetStepProgress(int32 val); + int32 GetStepProgress(); + int8 GetStepType(); + bool Complete(); + void SetComplete(); + void ResetTaskGroup(); + const char* GetTaskGroup(); + const char* GetDescription(); + void SetDescription(string desc); + int16 GetQuestCurrentQuantity(); + int16 GetQuestNeededQuantity(); + map* GetUpdateIDs() { return ids; } + int16 GetIcon(); + void SetIcon(int16 in_icon); + const char* GetUpdateTargetName(); + void SetUpdateTargetName(const char* name); + const char* GetUpdateName(); + void SetUpdateName(const char* name); + int32 GetStepID(); + int32 GetItemID(); + bool WasUpdated(); + void WasUpdated(bool val); + float GetPercentage(); + + void SetTaskGroup(string val) { task_group = val; } + +private: + bool updated; + int32 id; + string update_name; + string update_target_name; + int16 icon; + int8 type; + string description; + std::map* ids; + int32 quantity; + string task_group; + vector* locations; + float max_variation; + float percentage; + int32 usableitemid; + int32 step_progress; +}; +class Player; +class Spell; + +class Quest{ +public: + Quest(int32 in_id); + Quest(Quest* old_quest); + ~Quest(); + EQ2Packet* OfferQuest(int16 version, Player* player); + EQ2Packet* QuestJournalReply(int16 version, int32 player_crc, Player* player, QuestStep* updateStep = 0, int8 update_count = 1, bool old_completed_quest=false, bool quest_failure = false, bool display_quest_helper = true); + + void RegisterQuest(string in_name, string in_type, string in_zone, int8 in_level, string in_desc); + + void SetPrereqLevel(int8 lvl); + void SetPrereqTSLevel(int8 lvl); + void SetPrereqMaxLevel(int8 lvl) {prereq_max_level = lvl;} + void SetPrereqMaxTSLevel(int8 lvl) {prereq_max_tslevel = lvl;} + void AddPrereqClass(int8 class_id); + void AddPrereqTradeskillClass(int8 class_id); + void AddPrereqModelType(int16 model_type); + void AddPrereqRace(int8 race); + void AddPrereqQuest(int32 quest_id); + void AddPrereqFaction(int32 faction_id, sint32 min, sint32 max = 0); + void AddPrereqItem(Item* item); + + void AddRewardItem(Item* item); + void AddTmpRewardItem(Item* item); + void GetTmpRewardItemsByID(std::vector* items); + void AddRewardItemVec(vector* items, Item* item, bool combine_items = true); + void AddSelectableRewardItem(Item* item); + void AddRewardCoins(int32 copper, int32 silver, int32 gold, int32 plat); + void AddRewardCoinsMax(int64 coins); + void AddRewardFaction(int32 faction_id, sint32 amount); + void SetRewardStatus(int32 amount); + void SetRewardComment(string comment); + void SetRewardXP(int32 xp); + void SetRewardTSXP(int32 xp) { reward_tsexp = xp; } + + bool AddQuestStep(QuestStep* step); + QuestStep* AddQuestStep(int32 id, int8 in_type, string in_description, vector* in_ids, int32 in_quantity, const char* in_task_group = 0, vector* in_locations = 0, float in_max_variation = 0, float in_percentage = 0,int32 in_usableitemid = 0); + bool SetStepComplete(int32 step); + bool AddStepProgress(int32 step_id, int32 progress); + int16 GetQuestStep(); + int32 GetStepProgress(int32 step_id); + int16 GetTaskGroupStep(); + bool QuestStepIsActive(int16 quest_step_id); + bool CheckQuestReferencedSpawns(Spawn* spawn); + bool CheckQuestKillUpdate(Spawn* spawn, bool update = true); + bool CheckQuestChatUpdate(int32 id, bool update = true); + bool CheckQuestItemUpdate(int32 id, int8 quantity = 1); + bool CheckQuestLocationUpdate(float char_x, float char_y, float char_z, int32 zone_id); + bool CheckQuestSpellUpdate(Spell* spell); + bool CheckQuestRefIDUpdate(int32 id, int32 quantity = 1); + + int8 GetQuestLevel(); + int8 GetVisible(); + int32 GetQuestID(); + void SetQuestID(int32 in_id); + int8 GetPrereqLevel(); + int8 GetPrereqTSLevel(); + int8 GetPrereqMaxLevel() {return prereq_max_level;} + int8 GetPrereqMaxTSLevel() {return prereq_max_tslevel;} + vector* GetPrereqFactions(); + vector* GetPrereqRaces(); + vector* GetPrereqModelTypes(); + vector* GetPrereqClasses(); + vector* GetPrereqTradeskillClasses(); + vector* GetPrereqQuests(); + vector* GetPrereqItems(); + vector* GetRewardItems(); + vector* GetTmpRewardItems(); + vector* GetSelectableRewardItems(); + map* GetRewardFactions(); + void GiveQuestReward(Player* player); + void AddCompleteAction(int32 step, string action); + void AddProgressAction(int32 step, string action); + void SetName(string in_name); + void SetType(string in_type); + void SetLevel(int8 in_level); + void SetEncounterLevel(int8 level) {enc_level = level;} + void SetDescription(string desc); + void SetStepDescription(int32 step, string desc); + void SetTaskGroupDescription(int32 step, string desc, bool display_bullets); + + void SetStatusTmpReward(int32 status) { tmp_reward_status = status; } + int64 GetStatusTmpReward() { return tmp_reward_status; } + + void SetCoinTmpReward(int64 coins) { tmp_reward_coins = coins; } + int64 GetCoinTmpReward() { return tmp_reward_coins; } + int64 GetCoinsReward(); + int64 GetCoinsRewardMax(); + int64 GetGeneratedCoin(); + void SetGeneratedCoin(int64 coin); + int8 GetLevel(); + int8 GetEncounterLevel() { return enc_level; } + const char* GetName(); + const char* GetDescription(); + const char* GetType(); + void SetZone(string in_zone); + const char* GetZone(); + int8 GetDay(); + int8 GetMonth(); + int8 GetYear(); + int32 GetStatusPoints(); + void SetDay(int8 value); + void SetMonth(int8 value); + void SetYear(int8 value); + vector* GetQuestUpdates(); + vector* GetQuestFailures(); + vector* GetQuestSteps(); + bool GetQuestStepCompleted(int32 step_id); + QuestStep* GetQuestStep(int32 step_id); + void SetCompleteAction(string action); + const char* GetCompleteAction(); + const char* GetCompleteAction(int32 step); + void SetQuestGiver(int32 id); + int32 GetQuestGiver(); + void SetQuestReturnNPC(int32 id); + int32 GetQuestReturnNPC(); + Player* GetPlayer(); + void SetPlayer(Player* in_player); + bool GetCompleted(); + bool HasSentLastUpdate() { return has_sent_last_update; } + void SetSentLastUpdate(bool val) { has_sent_last_update = val; } + void SetCompletedDescription(string desc); + const char* GetCompletedDescription(); + int32 GetExpReward(); + int32 GetTSExpReward() { return reward_tsexp; } + bool GetDeleted(); + void SetDeleted(bool val); + bool GetUpdateRequired(); + void SetUpdateRequired(bool val); + void SetTurnedIn(bool val); + bool GetTurnedIn(); + bool GetSaveNeeded(){ return needs_save; } + void SetSaveNeeded(bool val){ needs_save = val; } + + void SetFeatherColor(int8 val) { m_featherColor = val; } + int8 GetFeatherColor() { return m_featherColor; } + + void SetRepeatable(bool val) { m_repeatable = val; } + bool IsRepeatable() { return m_repeatable; } + + void SetTracked(bool val) { m_tracked = val; } + bool GetTracked() { return m_tracked; } + bool IsTracked() { return m_tracked && !m_hidden; } + void SetCompletedFlag(bool val); + bool GetCompletedFlag() {return completed_flag;} + bool GetYellowName() {return yellow_name;} + void SetYellowName(bool val) {yellow_name = val;} + bool CheckCategoryYellow(); + + ///Sets the custom flags for use in lua + ///Value to set the flags to + void SetQuestFlags(int32 flags) { m_questFlags = flags; SetSaveNeeded(true); } + + ///Gets the custom lua flags + ///The current flags (int32) + int32 GetQuestFlags() { return m_questFlags; } + + ///Checks to see if the quest is hidden + ///True if the quest is hidden + bool IsHidden() { return m_hidden; } + + ///Sets the quest hidden flag + ///Value to set the hidden flag to + void SetHidden(bool val) { m_hidden = val; SetSaveNeeded(true); } + + ///Gets the step timer + ///Unix timestamp (int32) + int32 GetStepTimer() { return m_timestamp; } + + ///Sets the step timer + ///How long to set the timer for, in seconds + void SetStepTimer(int32 duration); + + ///Sets the step that the timer is for + ///Step to set the timer for + void SetTimerStep(int32 step) { m_timerStep = step; } + + ///Gets the step that the timer is for + int32 GetTimerStep() { return m_timerStep; } + + ///Adds a lua call back function for when the step fails + ///The step to add the call back for + ///The lua callback function + void AddFailedAction(int32 step, string action); + + ///Fail the given step + ///The step to fail + void StepFailed(int32 step); + + ///Removes the given step from the quest + ///The step id to remove + ///The client who has this quest + ///True if able to remove the quest + bool RemoveQuestStep(int32 step, Client* client); + + int16 GetCompleteCount() { return m_completeCount; } + void SetCompleteCount(int16 val) { m_completeCount = val; } + void IncrementCompleteCount() { m_completeCount += 1; } + + void SetQuestTemporaryState(bool tempState, std::string customDescription = string("")); + bool GetQuestTemporaryState() { return quest_state_temporary; } + std::string GetQuestTemporaryDescription() { return quest_temporary_description; } + + void SetQuestShareableFlag(int32 flag) { quest_shareable_flag = flag; } + void SetCanDeleteQuest(bool newval) { can_delete_quest = newval; } + + int32 GetQuestShareableFlag() { return quest_shareable_flag; } + bool CanDeleteQuest() { return can_delete_quest; } + + bool CanShareQuestCriteria(Client* quest_sharer, bool display_client_msg = true); + Mutex MQuestSteps; +protected: + bool needs_save; + Mutex MCompletedActions; + Mutex MProgressActions; + Mutex MFailedActions; + bool turned_in; + bool update_needed; + bool deleted; + bool has_sent_last_update; + + string completed_description; + + map complete_actions; + map progress_actions; + map failed_actions; + int8 day; //only here to make our lives easier + int8 month; + int8 year; + int8 visible; + + int32 id; + string name; + string type; + string zone; + int8 level; + int8 enc_level; + string description; + string complete_action; + + Player* player; + vector prereq_factions; + int8 prereq_level; + int8 prereq_tslevel; + int8 prereq_max_level; + int8 prereq_max_tslevel; + vector prereq_model_types; + vector prereq_races; + vector prereq_classes; + vector prereq_tradeskillclass; + vector prereq_quests; + vector prereq_items; + vector reward_items; + vector selectable_reward_items; + vector tmp_reward_items; + int64 reward_coins; + int64 reward_coins_max; + int32 tmp_reward_status; + int64 tmp_reward_coins; + int64 generated_coin; + map reward_factions; + int32 reward_status; + string reward_comment; + int32 reward_exp; + int32 reward_tsexp; + vector step_updates; + vector step_failures; + map quest_step_map; + map quest_step_reverse_map; + vector quest_steps; + map task_group_order; + int16 task_group_num; + map > task_group; + int32 quest_giver; + int32 return_id; + int8 m_featherColor; + bool m_repeatable; + bool m_tracked; + bool completed_flag; + bool yellow_name; + int32 m_questFlags; + bool m_hidden; + + int32 m_timestamp; // timer for a quest step + int32 m_timerStep; // used for the fail action when timer expires + + int16 m_completeCount; + + bool quest_state_temporary; + std::string quest_temporary_description; + int32 quest_shareable_flag; + bool can_delete_quest; +}; + +class MasterQuestList{ +public: + MasterQuestList(); + Quest* GetQuest(int32 id, bool copyQuest = true){ + if(quests.count(id) > 0) + { + if(copyQuest) + return new Quest(quests[id]); + else + return quests[id]; + } + else + return 0; + } + + map* GetQuests(){ + return &quests; + } + + void AddQuest(int32 id, Quest* quest){ + quests[id] = quest; + } + void Reload(); + void LockQuests(); + void UnlockQuests(); + +private: + Mutex MQuests; + map quests; +}; + +#endif diff --git a/source/WorldServer/RaceTypes/RaceTypes.cpp b/source/WorldServer/RaceTypes/RaceTypes.cpp new file mode 100644 index 0000000..0a2734c --- /dev/null +++ b/source/WorldServer/RaceTypes/RaceTypes.cpp @@ -0,0 +1,120 @@ +/* + 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 . +*/ + +#include "RaceTypes.h" +#include + +MasterRaceTypeList::MasterRaceTypeList() { + +} + +MasterRaceTypeList::~MasterRaceTypeList() { + +} + +bool MasterRaceTypeList::AddRaceType(int16 model_id, int16 race_type_id, const char* category, const char* subcategory, const char* modelname, bool allow_override) { + if (m_raceList.count(model_id) == 0 || allow_override) { + RaceTypeStructure rts; + m_raceList[model_id].race_type_id = race_type_id; + if(category != NULL) { + strncpy(m_raceList[model_id].category, category, 64); + } else { + strcpy(m_raceList[model_id].category,""); + } + + if(subcategory != NULL) { + strncpy(m_raceList[model_id].subcategory, subcategory, 64); + } else { + strcpy(m_raceList[model_id].subcategory,""); + } + + if(modelname != NULL) { + strncpy(m_raceList[model_id].modelname, modelname, 64); + } else { + strcpy(m_raceList[model_id].modelname,""); + } + + return true; + } + + return false; +} + +int16 MasterRaceTypeList::GetRaceType(int16 model_id) { + int16 ret = 0; + + if (m_raceList.count(model_id) > 0) + ret = m_raceList[model_id].race_type_id; + + return ret; +} + +char* MasterRaceTypeList::GetRaceTypeCategory(int16 model_id) { + if(m_raceList.count(model_id) > 0 && strlen(m_raceList[model_id].category) > 0) + return m_raceList[model_id].category; + + return ""; +} + +char* MasterRaceTypeList::GetRaceTypeSubCategory(int16 model_id) { + if(m_raceList.count(model_id) > 0 && strlen(m_raceList[model_id].subcategory) > 0) + return m_raceList[model_id].subcategory; + + return ""; +} + + +char* MasterRaceTypeList::GetRaceTypeModelName(int16 model_id) { + if(m_raceList.count(model_id) > 0 && strlen(m_raceList[model_id].modelname) > 0) + return m_raceList[model_id].modelname; + + return ""; +} + +int16 MasterRaceTypeList::GetRaceBaseType(int16 model_id) { + int16 ret = 0; + + if (m_raceList.count(model_id) == 0) + return ret; + + int16 race = m_raceList[model_id].race_type_id; + if (race >= DRAGONKIND && race < FAY) + ret = DRAGONKIND; + else if (race >= FAY && race < MAGICAL) + ret = FAY; + else if (race >= MAGICAL && race < MECHANIMAGICAL) + ret = MAGICAL; + else if (race >= MECHANIMAGICAL && race < NATURAL) + ret = MECHANIMAGICAL; + else if (race >= NATURAL && race < PLANAR) + ret = NATURAL; + else if (race >= PLANAR && race < PLANT) + ret = PLANAR; + else if (race >= PLANT && race < SENTIENT) + ret = PLANT; + else if (race >= SENTIENT && race < UNDEAD) + ret = SENTIENT; + else if (race >= UNDEAD && race < WERE) + ret = UNDEAD; + else if (race >= WERE) + ret = WERE; + + return ret; +} \ No newline at end of file diff --git a/source/WorldServer/RaceTypes/RaceTypes.h b/source/WorldServer/RaceTypes/RaceTypes.h new file mode 100644 index 0000000..899fbab --- /dev/null +++ b/source/WorldServer/RaceTypes/RaceTypes.h @@ -0,0 +1,321 @@ +/* + 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 . +*/ + +#ifndef __RACETYPES_H__ +#define __RACETYPES_H__ + +#include "../../common/types.h" +#include + +#define DRAGONKIND 101 +#define DRAGON 102 +#define DRAKE 103 +#define DRAKOTA 104 +#define DROAG 105 +#define FAEDRAKE 106 +//FLYINGSNAKE Defined in natural as well, think is a better fit there then here +#define SOKOKAR 107 +#define WURM 108 +#define WYRM 109 +#define WYVERN 110 + +#define FAY 111 +#define ARASAI_NPC 112 +#define BIXIE 113 +#define BROWNIE 114 +#define DRYAD 115 +#define FAE_NPC 116 +#define FAIRY 117 +#define SIREN 118 +#define SPIRIT 119 +#define SPRITE 120 +#define TREANT 121 //L&L 8 +#define WISP 122 + +#define MAGICAL 123 +#define AMORPH 124 +#define CONSTRUCT 125 +#define ANIMATION 126 +#define BONEGOLEM 127 +#define BOVOCH 128 +#define CARRIONGOLEM 129 +#define CLAYGOLEM 130 +#define CUBE 131 +#define DERVISH 132 +#define DEVOURER 133 +#define GARGOYLE 134 +#define GOLEM 135 +#define GOO 136 +#define HARPY 137 +#define IMP 138 +#define LIVINGSTATUE 139 +#define MANNEQUIN 140 +#define MIMIC 141 +#define MOPPET 142 +#define NAGA 143 +#define NAYAD 144 +#define OOZE 145 +#define RUMBLER 146 +#define RUSTMONSTER 147 +#define SATYR 148 +#define SCARECROW 149 +#define SPHEROID 150 +#define TENTACLETERROR 151 +#define TOME 152 +#define UNICORN 153 +#define WOODELEMENTAL 154 + +#define MECHANIMAGICAL 155 +#define CLOCKWORK 156 +#define IRONGUARDIAN 157 + +#define NATURAL 158 +#define ANIMAL 159 +#define AQUATIC 160 +#define AVIAN 161 +#define CANINE 162 +#define EQUINE 163 +#define FELINE 164 +#define INSECT 165 +#define PRIMATE 166 +#define REPTILE 167 +#define ANEMONE 168 +#define APOPHELI 169 +#define ARMADILLO 170 +#define BADGER 171 +#define BARRACUDA 172 +#define BASILISK 173 +#define BAT 174 +#define BEAR 175 +#define BEAVER 176 +#define BEETLE 177 +#define BOVINE 178 +#define BRONTOTHERIUM 179 +#define BRUTE 180 +#define CAMEL 181 +#define CAT 182 +#define CENTIPEDE 183 +#define CERBERUS 184 +#define CHIMERA 185 +#define CHOKIDAI 186 +#define COBRA 187 +#define COCKATRICE 188 +#define CRAB 189 +#define CROCODILE 190 +#define DEER 191 +#define DRAGONFLY 192 +#define DUCK 193 +#define EEL 194 +#define ELEPHANT 195 +#define FLYINGSNAKE 196 +#define FROG 197 +#define GOAT 198 +#define GORILLA 199 +#define GRIFFIN 200 +#define HAWK 201 +#define HIVEQUEEN 202 +#define HORSE 203 +#define HYENA 204 +#define KHOALRAT 205 +#define KYBUR 206 +#define LEECH 207 +#define LEOPARD 208 +#define LION 209 +#define LIZARD 210 +#define MAMMOTH 211 +#define MANTARAY 212 +#define MOLERAT 213 +#define MONKEY 214 +#define MYTHICAL 215 +#define OCTOPUS 216 +#define OWLBEAR 217 +#define PIG 218 +#define PIRANHA 219 +#define RAPTOR 220 +#define RAT 221 +#define RHINOCEROS 222 +#define ROCKCRAWLER 223 +#define SABERTOOTH 224 +#define SCORPION 225 +#define SEATURTLE 226 +#define SHARK 227 +#define SHEEP 228 +#define SLUG 229 +#define SNAKE 230 +#define SPIDER 231 +#define STIRGE 232 +#define SWORDFISH 233 +#define TIGER 234 +#define TURTLE 235 +#define VERMIN 236 +#define VULRICH 237 +#define WOLF 238 +#define YETI 239 + +#define PLANAR 240 +#define ABOMINATION 241 +#define AIRELEMENTAL 242 +#define AMYGDALAN 243 +#define AVATAR 244 +#define CYCLOPS 245 +#define DEMON 246 +#define DJINN 247 +#define EARTHELEMENTAL 248 +#define EFREETI 249 +#define ELEMENTAL 250 +#define ETHEREAL 251 +#define ETHERPINE 252 +#define EVILEYE 253 +#define FIREELEMENTAL 254 +#define GAZER 255 +#define GEHEIN 256 +#define GEONID 257 +#define GIANT 258 //L&L 5 +#define SALAMANDER 259 +#define SHADOWEDMAN 260 +#define SPHINX 261 +#define SPORE 262 +#define SUCCUBUS 263 +#define VALKYRIE 264 +#define VOIDBEAST 265 +#define WATERELEMENTAL 266 +#define WRAITH 267 + +#define PLANT 268 +#define CARNIVOROUSPLANT 269 +#define CATOPLEBAS 270 +#define MANTRAP 271 +#define ROOTABOMINATION 272 +#define ROOTHORROR 273 +#define SUCCULENT 274 + +#define SENTIENT 275 +#define ASHLOK 276 +#define AVIAK 277 +#define BARBARIAN_NPC 278 +#define BIRDMAN 279 +#define BOARFIEND 280 +#define BUGBEAR 281 +#define BURYNAI 282 +#define CENTAUR 283 ////L&L 4 +#define COLDAIN 284 +#define DAL 285 +#define DARKELF_NPC 286 +#define DIZOK 287 +#define DRACHNID 288 +#define DRAFLING 289 +#define DROLVARG 290 +#define DWARF_NPC 291 +#define ERUDITE_NPC 292 +#define ETTIN 293 +#define FREEBLOOD_NPC 294 +#define FROGLOK_NPC 295 +#define FROSTFELLELF 296 +#define FUNGUSMAN 297 +#define GNOLL 298 //L&L 1 +#define GNOME_NPC 299 +#define GOBLIN 300 //L&L 3 +#define GRUENGACH 301 +#define HALFELF_NPC 302 // Not on the list from wikia but all other races were here so added them +#define HALFLING_NPC 303 +#define HIGHELF_NPC 304 // Not on the list from wikia but all other races were here so added them +#define HOLGRESH 305 +#define HOOLUK 306 +#define HUAMEIN 307 +#define HUMAN_NPC 308 +#define HUMANOID 309 +#define IKSAR_NPC 310 +#define KERIGDAL 311 +#define KERRAN_NPC 312 +#define KOBOLD 313 +#define LIZARDMAN 314 +#define MINOTAUR 315 +#define OGRE_NPC 316 +#define ORC 317 //L&L 2 +#define OTHMIR 318 +#define RATONGA_NPC 319 +#define RAVASECT 320 +#define RENDADAL 321 +#define ROEKILLIK 322 +#define SARNAK_NPC 323 +#define SKORPIKIS 324 +#define SPIROC 325 +#define TROGLODYTE 326 +#define TROLL_NPC 327 +#define ULTHORK 328 +#define VULTAK 329 +#define WOODELF_NPC 330 +#define WRAITHGUARD 331 +#define YHALEI 332 + +#define UNDEAD 333 +#define GHOST 334 +#define GHOUL 335 +#define GUNTHAK 336 +#define HORROR 337 +#define MUMMY 338 +#define SHINREEORCS 339 +#define SKELETON 340 //L&L 6 +#define SPECTRE 341 +#define VAMPIRE_NPC 342 +#define ZOMBIE 343 //L&L 7 + +#define WERE 344 +#define AHROUNWEREWOLVES 345 +#define LYKULAKWEREWOLVES 346 +#define WEREWOLF 347 + +struct RaceTypeStructure { + int16 race_type_id; + char category[64]; + char subcategory[64]; + char modelname[250]; +}; + +class MasterRaceTypeList { +public: + MasterRaceTypeList(); + ~MasterRaceTypeList(); + + /// Add a race type define to the list + /// The id of the model + /// The id of the race type + /// The category of the race type + /// The subcategory of the race type + /// The model name of the model id + bool AddRaceType(int16 model_id, int16 race_type_id, const char* category, const char* subcategory, const char* modelname, bool allow_override = false); + + /// Gets the race type for the given model + /// The model id to get the race type for + int16 GetRaceType(int16 model_id); + char* GetRaceTypeCategory(int16 model_id); + char* GetRaceTypeSubCategory(int16 model_id); + char* GetRaceTypeModelName(int16 model_id); + + /// Gets the base race type for the given model + /// The model id to get the base race type for + int16 GetRaceBaseType(int16 model_id); + +private: + // model id, race type id + map m_raceList; +}; + +#endif \ No newline at end of file diff --git a/source/WorldServer/RaceTypes/RaceTypesDB.cpp b/source/WorldServer/RaceTypes/RaceTypesDB.cpp new file mode 100644 index 0000000..9b4008a --- /dev/null +++ b/source/WorldServer/RaceTypes/RaceTypesDB.cpp @@ -0,0 +1,43 @@ +/* + 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 . +*/ + +#include "../WorldDatabase.h" +#include "../../common/Log.h" +#include "RaceTypes.h" + +extern MasterRaceTypeList race_types_list; + +void WorldDatabase::LoadRaceTypes() { + DatabaseResult result; + + if(database_new.Select(&result, "SELECT `model_type`, `race_id`, `category`, `subcategory`, `model_name` FROM `race_types`")) { + int32 count = 0; + + while (result.Next()) { + int16 race_id = result.GetInt16Str("race_id"); + if (race_id > 0) { + race_types_list.AddRaceType(result.GetInt16Str("model_type"), race_id, result.GetStringStr("category"), result.GetStringStr("subcategory"), result.GetStringStr("model_name")); + count++; + } + } + + LogWrite(WORLD__INFO, 0, "World", "- Loaded %u Race Types", count); + } +} \ No newline at end of file diff --git a/source/WorldServer/Recipes/Recipe.cpp b/source/WorldServer/Recipes/Recipe.cpp new file mode 100644 index 0000000..b005209 --- /dev/null +++ b/source/WorldServer/Recipes/Recipe.cpp @@ -0,0 +1,778 @@ +/* + 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 . +*/ +#include +#include "../../common/debug.h" +#include "../../common/Log.h" +#include "../../common/database.h" +#include "Recipe.h" +#include "../../common/ConfigReader.h" +#include "../Items/Items.h" +#include "../World.h" + +extern ConfigReader configReader; +extern MasterItemList master_item_list; +extern World world; + + +Recipe::Recipe() { + id = 0; + book_id = 0; + memset(name, 0, sizeof(name)); + memset(book_name, 0, sizeof(book_name)); + memset(book, 0, sizeof(book)); + memset(device, 0, sizeof(device)); + level = 0; + tier = 0; + icon = 0; + skill = 0; + technique = 0; + knowledge = 0; + classes = 0; + unknown2 = 0; + unknown3 = 0; + unknown4 = 0; +} + +Recipe::~Recipe() { + map::iterator itr; + for (itr = products.begin(); itr != products.end(); itr++) + safe_delete(itr->second); +} + +Recipe::Recipe(Recipe *in){ + assert(in); + id = in->GetID(); + soe_id = in->GetSoeID(); + book_id = in->GetBookID(); + strncpy(name, in->GetName(), sizeof(name)); + strncpy(description, in->GetDescription(), sizeof(description)); + strncpy(book_name, in->GetBookName(), sizeof(book_name)); + strncpy(book, in->GetBook(), sizeof(book)); + strncpy(device, in->GetDevice(), sizeof(device)); + + level = in->GetLevel(); + tier = in->GetTier(); + icon = in->GetIcon(); + skill = in->GetSkill(); + technique = in->GetTechnique(); + knowledge = in->GetKnowledge(); + device_sub_type = in->GetDevice_Sub_Type(); + classes = in->GetClasses(); + unknown1 = in->GetUnknown1(); + unknown2 = in->GetUnknown2(); + unknown3 = in->GetUnknown3(); + unknown4 = in->GetUnknown4(); + + product_item_id = in->GetProductID(); + strncpy(product_name, in->product_name, sizeof(product_name)); + product_qty = in->GetProductQuantity(); + + strncpy(primary_build_comp_title, in->primary_build_comp_title, sizeof(primary_build_comp_title)); + strncpy(build1_comp_title, in->build1_comp_title, sizeof(build1_comp_title)); + strncpy(build2_comp_title, in->build2_comp_title, sizeof(build2_comp_title)); + strncpy(build3_comp_title, in->build3_comp_title, sizeof(build3_comp_title)); + strncpy(build4_comp_title, in->build4_comp_title, sizeof(build4_comp_title)); + + strncpy(fuel_comp_title, in->fuel_comp_title, sizeof(fuel_comp_title)); + build1_comp_qty = in->GetBuild1ComponentQuantity(); + build2_comp_qty = in->GetBuild2ComponentQuantity(); + build3_comp_qty = in->GetBuild3ComponentQuantity(); + build4_comp_qty = in->GetBuild4ComponentQuantity(); + fuel_comp_qty = in->GetFuelComponentQuantity(); + primary_comp_qty = in->GetPrimaryComponentQuantity(); + highestStage = in->GetHighestStage(); + + std::map::iterator itr; + for (itr = in->products.begin(); itr != in->products.end(); itr++) { + RecipeProducts* rp = new RecipeProducts; + rp->product_id = itr->second->product_id; + rp->byproduct_id = itr->second->byproduct_id; + rp->product_qty = itr->second->product_qty; + rp->byproduct_qty = itr->second->byproduct_qty; + products.insert(make_pair(itr->first, rp)); + } + + std::map>::iterator itr2; + for (itr2 = in->components.begin(); itr2 != in->components.end(); itr2++) { + std::vector recipe_component; + std::copy(itr2->second.begin(), itr2->second.end(), + std::back_inserter(recipe_component)); + components.insert(make_pair(itr2->first, recipe_component)); + } +} + +MasterRecipeList::MasterRecipeList() { + m_recipes.SetName("MasterRecipeList::recipes"); +} + +MasterRecipeList::~MasterRecipeList() { + ClearRecipes(); +} + +bool MasterRecipeList::AddRecipe(Recipe *recipe) { + bool ret = false; + int32 id; + + assert(recipe); + + id = recipe->GetID(); + m_recipes.writelock(__FUNCTION__, __LINE__); + if (recipes.count(id) == 0) { + recipes[id] = recipe; + recipes_crc[recipe->GetSoeID()] = recipe; + ret = true; + } + m_recipes.releasewritelock(__FUNCTION__, __LINE__); + + return ret; +} + +Recipe* MasterRecipeList::GetRecipe(int32 recipe_id) { + Recipe *ret = 0; + + m_recipes.readlock(__FUNCTION__, __LINE__); + if (recipes.count(recipe_id) > 0) + ret = recipes[recipe_id]; + m_recipes.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +Recipe* MasterRecipeList::GetRecipeByCRC(int32 recipe_crc) { + Recipe *ret = 0; + + m_recipes.readlock(__FUNCTION__, __LINE__); + if (recipes_crc.count(recipe_crc) > 0) + ret = recipes_crc[recipe_crc]; + m_recipes.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +Recipe* MasterRecipeList::GetRecipeByName(const char* name) { + Recipe* ret = 0; + map::iterator itr; + + m_recipes.readlock(__FUNCTION__, __LINE__); + for (itr = recipes.begin(); itr != recipes.end(); itr++) { + if (::ToLower(string(name)) == ::ToLower(string(itr->second->GetName()))) { + ret = itr->second; + break; + } + } + m_recipes.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +void MasterRecipeList::ClearRecipes() { + map::iterator itr; + + m_recipes.writelock(__FUNCTION__, __LINE__); + for (itr = recipes.begin(); itr != recipes.end(); itr++) + safe_delete(itr->second); + recipes.clear(); + recipes_crc.clear(); + m_recipes.releasewritelock(__FUNCTION__, __LINE__); +} + +int32 MasterRecipeList::Size() { + int32 ret; + + m_recipes.readlock(__FUNCTION__, __LINE__); + ret = (int32)recipes.size(); + m_recipes.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +vector MasterRecipeList::GetRecipes(const char* book_name) { + vector ret; + map::iterator itr; + + m_recipes.writelock(__FUNCTION__, __LINE__); + for (itr = recipes.begin(); itr != recipes.end(); itr++) { + if (::ToLower(string(book_name)) == ::ToLower(string(itr->second->GetBook()))) + ret.push_back(itr->second); + } + m_recipes.releasewritelock(__FUNCTION__, __LINE__); + + return ret; +} + +PlayerRecipeList::PlayerRecipeList(){ +} + +PlayerRecipeList::~PlayerRecipeList(){ + ClearRecipes(); +} + +bool PlayerRecipeList::AddRecipe(Recipe *recipe){ + std::unique_lock lock(player_recipe_mutex); + assert(recipe); + + if(recipes.count(recipe->GetID()) == 0){ + recipes[recipe->GetID()] = recipe; + return true; + } + return false; +} + +Recipe * PlayerRecipeList::GetRecipe(int32 recipe_id){ + std::shared_lock lock(player_recipe_mutex); + if (recipes.count(recipe_id) > 0) + return recipes[recipe_id]; + return 0; +} + +void PlayerRecipeList::ClearRecipes(){ + std::unique_lock lock(player_recipe_mutex); + map::iterator itr; + + for (itr = recipes.begin(); itr != recipes.end(); itr++) + safe_delete(itr->second); + recipes.clear(); +} + +bool PlayerRecipeList::RemoveRecipe(int32 recipe_id) { + std::unique_lock lock(player_recipe_mutex); + bool ret = false; + if (recipes.count(recipe_id) > 0) { + recipes.erase(recipe_id); + ret = true; + } + return ret; +} + + +int32 PlayerRecipeList::Size() { + std::unique_lock lock(player_recipe_mutex); + return recipes.size(); +} + +MasterRecipeBookList::MasterRecipeBookList(){ + m_recipeBooks.SetName("MasterRecipeBookList::recipeBooks"); +} + +MasterRecipeBookList::~MasterRecipeBookList(){ + ClearRecipeBooks(); +} + +bool MasterRecipeBookList::AddRecipeBook(Recipe *recipe){ + bool ret = false; + int32 id = 0; + + assert(recipe); + + id = recipe->GetBookID(); + m_recipeBooks.writelock(__FUNCTION__, __LINE__); + if(recipeBooks.count(id) == 0){ + recipeBooks[id] = recipe; + ret = true; + } + m_recipeBooks.releasewritelock(__FUNCTION__, __LINE__); + return ret; +} + +Recipe * MasterRecipeBookList::GetRecipeBooks(int32 recipebook_id){ + Recipe *ret = 0; + + m_recipeBooks.readlock(__FUNCTION__, __LINE__); + if (recipeBooks.count(recipebook_id) > 0) + ret = recipeBooks[recipebook_id]; + m_recipeBooks.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +void MasterRecipeBookList::ClearRecipeBooks(){ + map::iterator itr; + + m_recipeBooks.writelock(__FUNCTION__, __LINE__); + for (itr = recipeBooks.begin(); itr != recipeBooks.end(); itr++) + safe_delete(itr->second); + recipeBooks.clear(); + m_recipeBooks.releasewritelock(__FUNCTION__, __LINE__); +} + +int32 MasterRecipeBookList::Size(){ + int32 ret = 0; + + m_recipeBooks.readlock(__FUNCTION__, __LINE__); + ret = (int32)recipeBooks.size(); + m_recipeBooks.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +EQ2Packet* MasterRecipeList::GetRecipePacket(int32 recipe_id, Client* client, bool display, int8 packet_type){ + Recipe *recipe = GetRecipe(recipe_id); + if(recipe){ + LogWrite(TRADESKILL__DEBUG, 5, "Recipes", "Recipe ID: %u Recipe Name: %s", recipe->GetID(), recipe->GetName()); + return recipe->SerializeRecipe(client, recipe, display, packet_type); + } + return 0; +} + +PlayerRecipeBookList::PlayerRecipeBookList(){ +} + +PlayerRecipeBookList::~PlayerRecipeBookList(){ + ClearRecipeBooks(); +} + +bool PlayerRecipeBookList::AddRecipeBook(Recipe *recipe){ + assert(recipe); + + if(recipeBooks.count(recipe->GetBookID()) == 0){ + recipeBooks[recipe->GetBookID()] = recipe; + return true; + } + return false; +} + +Recipe * PlayerRecipeBookList::GetRecipeBook(int32 recipebook_id){ + if(recipeBooks.count(recipebook_id) > 0) + return recipeBooks[recipebook_id]; + return 0; +} + +bool PlayerRecipeBookList::HasRecipeBook(int32 book_id) { + if (recipeBooks.count(book_id) > 0) + return true; + return false; +} + +void PlayerRecipeBookList::ClearRecipeBooks(){ + map::iterator itr; + + for(itr = recipeBooks.begin(); itr != recipeBooks.end(); itr++) + safe_delete(itr->second); + recipeBooks.clear(); +} + +EQ2Packet * Recipe::SerializeRecipe(Client *client, Recipe *recipe, bool display, int8 packet_type, int8 subpacket_type, const char *struct_name){ + int16 version = 1; + Item* item = 0; + RecipeProducts* rp = 0; + vector::iterator itr; + vector comp_list; + + int8 i = 0; + int32 firstID = 0; + int32 primary_comp_id = 0; + if(client) + version = client->GetVersion(); + if(!struct_name) + struct_name = "WS_ExamineRecipeInfo"; + PacketStruct *packet = configReader.getStruct(struct_name, version); + if(display) + packet->setSubstructDataByName("info_header", "show_name", 1); + else + packet->setSubstructDataByName("info_header", "show_popup", 1); + + if(client->GetVersion() <= 561) { + packet->setSubstructDataByName("info_header", "packettype", 0x02); + } + else if(packet_type > 0) + packet->setSubstructDataByName("info_header", "packettype", GetItemPacketType(packet->GetVersion())); + else + if(version == 1096) + packet->setSubstructDataByName("info_header", "packettype",0x35FE); + if (version == 1208) + packet->setSubstructDataByName("info_header", "packettype", 0x45FE); + if(version >= 57048) + packet->setSubstructDataByName("info_header", "packettype",0x48FE); + if(subpacket_type == 0) + subpacket_type = 0x02; + packet->setSubstructDataByName("info_header", "packetsubtype", subpacket_type); + + packet->setSubstructDataByName("recipe_info", "id", recipe->GetID()); + packet->setSubstructDataByName("recipe_info", "unknown", 3); + packet->setSubstructDataByName("recipe_info", "level", recipe->GetLevel()); + packet->setSubstructDataByName("recipe_info", "technique", recipe->GetTechnique()); + packet->setSubstructDataByName("recipe_info", "skill_level", 50); //50 + packet->setSubstructDataByName("recipe_info", "knowledge", recipe->GetKnowledge()); + packet->setSubstructDataByName("recipe_info", "device", recipe->GetDevice()); + packet->setSubstructDataByName("recipe_info", "icon", recipe->GetIcon()); + packet->setSubstructDataByName("recipe_info", "unknown3", 1); + packet->setSubstructDataByName("recipe_info", "adventure_id", 0xFF); + packet->setSubstructDataByName("recipe_info", "tradeskill_id", client ? client->GetPlayer()->GetTradeskillClass() : 0); + packet->setSubstructDataByName("recipe_info", "unknown4a", 100); + packet->setSubstructDataByName("recipe_info", "unknown4aa", 1); + packet->setSubstructDataByName("recipe_info", "unknown5a", 20);//level *10 + packet->setSubstructDataByName("recipe_info", "product_classes", recipe->GetClasses()); + int32 HS = 0; + if (client->GetPlayer()->GetRecipeList()->GetRecipe(recipe->GetID()) == NULL) + HS = 0; + else + HS = client->GetPlayer()->GetRecipeList()->GetRecipe(recipe->GetID())->highestStage; + + + packet->setSubstructDataByName("recipe_info", "show_previous", HS);// recipe->highestStage); + + + rp = recipe->products[1]; + if (rp->product_id > 0) { + item = 0; + item = master_item_list.GetItem(rp->product_id); + if (item) { + packet->setSubstructDataByName("recipe_info", "previous1_icon", item->GetIcon(client->GetVersion())); + packet->setSubstructDataByName("recipe_info", "previous1_name", "previous1_name"); + packet->setSubstructDataByName("recipe_info", "previous1_qty", recipe->products[1]->product_qty); + packet->setSubstructDataByName("recipe_info", "previous1_item_id", recipe->products[1]->product_id); + packet->setSubstructDataByName("recipe_info", "previous1_item_crc", -853046774); + packet->setSubstructDataByName("recipe_info", "firstbar_icon", item->GetIcon(client->GetVersion())); + packet->setSubstructDataByName("recipe_info", "firstbar_name", "firstbar_name"); + packet->setSubstructDataByName("recipe_info", "firstbar_qty", recipe->products[1]->product_qty); + packet->setSubstructDataByName("recipe_info", "firstbar_item_id", recipe->products[2]->product_id); + packet->setSubstructDataByName("recipe_info", "firstbar_item_crc", -853046774); + } + } + rp = recipe->products[2]; + if (rp->product_id > 0) { + item = 0; + item = master_item_list.GetItem(rp->product_id); + if (item) { + packet->setSubstructDataByName("recipe_info", "previous2_icon", item->GetIcon(client->GetVersion())); + packet->setSubstructDataByName("recipe_info", "previous2_name", "previous2_name"); + packet->setSubstructDataByName("recipe_info", "previous2_qty", recipe->products[2]->product_qty); + packet->setSubstructDataByName("recipe_info", "previous2_item_id", recipe->products[2]->product_id); + packet->setSubstructDataByName("recipe_info", "previous2_item_crc", -853046774); + packet->setSubstructDataByName("recipe_info", "secondbar_icon", item->GetIcon(client->GetVersion())); + packet->setSubstructDataByName("recipe_info", "secondbar_name", "secondbar_name"); + packet->setSubstructDataByName("recipe_info", "secondbar_qty", recipe->products[2]->product_qty); + packet->setSubstructDataByName("recipe_info", "secondbar_item_id", recipe->products[2]->product_id); + packet->setSubstructDataByName("recipe_info", "secondbar_item_crc", -853046774); + } + } + rp = recipe->products[3]; + if (rp->product_id > 0) { + item = 0; + item = master_item_list.GetItem(rp->product_id); + if (item) { + packet->setSubstructDataByName("recipe_info", "previous3_icon", item->GetIcon(client->GetVersion())); + packet->setSubstructDataByName("recipe_info", "previous3_name", "previous3_name"); + packet->setSubstructDataByName("recipe_info", "previous3_qty", recipe->products[3]->product_qty); + packet->setSubstructDataByName("recipe_info", "previous3_item_id", recipe->products[3]->product_id); + packet->setSubstructDataByName("recipe_info", "previous3_item_crc", -853046774); + packet->setSubstructDataByName("recipe_info", "thirdbar_icon", item->GetIcon(client->GetVersion())); + packet->setSubstructDataByName("recipe_info", "thirdbar_name", "thirdbar_name"); + packet->setSubstructDataByName("recipe_info", "thirdbar_qty", recipe->products[3]->product_qty); + packet->setSubstructDataByName("recipe_info", "thirdbar_item_id", recipe->products[3]->product_id); + packet->setSubstructDataByName("recipe_info", "thirdbar_item_crc", -2065846136); + } + } + + + + + //item = master_item_list.GetItemByName(recipe->GetName());// TODO: MJ we should be getting item by item number in case of multiple items with same name + item = master_item_list.GetItem(recipe->GetProductID()); + if(item) { + packet->setSubstructDataByName("recipe_info", "product_icon", item->GetIcon(client->GetVersion())); //item->details.icon); + packet->setSubstructDataByName("recipe_info", "product_name", item->name.c_str()); //item->name); + packet->setSubstructDataByName("recipe_info", "product_qty", 1); + packet->setSubstructDataByName("recipe_info", "product_item_id", item->details.item_id); //item->details.item_id); + packet->setSubstructDataByName("recipe_info", "product_item_crc", 0); //item->details.item_crc); + } + + rp = recipe->products[0]; + if (rp->byproduct_id > 0) { + item = 0; + item = master_item_list.GetItem(rp->byproduct_id); + if (item) { + packet->setSubstructDataByName("recipe_info", "byproduct_icon", item->GetIcon(client->GetVersion()));//11 + packet->setSubstructDataByName("recipe_info", "byproduct_id", item->details.item_id); + } + + } + rp = recipe->products[1]; + if (rp->product_id > 0) { + item = 0; + item = master_item_list.GetItem(rp->product_id); + if (item) { + packet->setSubstructDataByName("recipe_info", "byproduct_icon", item->GetIcon(client->GetVersion()));//11 + packet->setSubstructDataByName("recipe_info", "byproduct_id", item->details.item_id); + } + + } + + item = 0; + + // Check to see if we have a primary component (slot = 0) + vector itemss; + if (recipe->components.count(0) > 0) { + if(client->GetVersion() <= 561) { + packet->setSubstructDataByName("recipe_info", "primary_count", 1); + } + + int16 have_qty = 0; + vector rc = recipe->components[0]; + for (itr = rc.begin(); itr != rc.end(); itr++, i++) { + item = master_item_list.GetItem(*itr); + packet->setSubstructDataByName("recipe_info", "primary_comp", recipe->primary_build_comp_title); + packet->setSubstructDataByName("recipe_info", "primary_qty", recipe->GetPrimaryComponentQuantity()); + item = 0; + itemss = client->GetPlayer()->item_list.GetAllItemsFromID((*itr)); + if (itemss.size() > 0) { + int16 needed_qty = recipe->GetPrimaryComponentQuantity(); + for (int8 i = 0; i < itemss.size(); i++) { + have_qty += itemss[i]->details.count; + } + } + } + packet->setSubstructDataByName("recipe_info", "primary_qty_avail", have_qty); + } + + + + int8 total_build_components = recipe->GetTotalBuildComponents(); + + int8 index = 0; + int8 count = 0; + if (total_build_components > 0) { + packet->setSubstructArrayLengthByName("recipe_info", "num_comps", total_build_components); + for (index = 0; index < 4; index++) { + if (recipe->components.count(index + 1) == 0) + continue; + + count++; + vector rc = recipe->components[index + 1]; + int16 have_qty = 0; + string comp_title; + int8 comp_qty; + for (itr = rc.begin(); itr != rc.end(); itr++, i++) { + if (index == 0) { + comp_title = recipe->build1_comp_title; + comp_qty = recipe->build1_comp_qty; + } + else if (index == 1) { + comp_title = recipe->build2_comp_title; + comp_qty = recipe->build2_comp_qty; + } + else if (index == 2) { + comp_title = recipe->build3_comp_title; + comp_qty = recipe->build3_comp_qty; + } + else if (index == 3) { + comp_title = recipe->build4_comp_title; + comp_qty = recipe->build4_comp_qty; + } + itemss = client->GetPlayer()->item_list.GetAllItemsFromID((*itr)); + for (int8 j = 0; j < itemss.size(); j++) { + have_qty += itemss[j]->details.count; + } + } + packet->setArrayDataByName("build_comp", comp_title.c_str(), index); + packet->setArrayDataByName("build_comp_qty", comp_qty, index); + packet->setArrayDataByName("build_comp_qty_avail", have_qty, index); + } + + } + + if(client->GetVersion() <= 561) { + packet->setSubstructDataByName("recipe_info", "fuel_count", 1); + packet->setSubstructDataByName("recipe_info", "fuel_comp", recipe->fuel_comp_title); + packet->setSubstructDataByName("recipe_info", "fuel_comp_qty", recipe->fuel_comp_qty); + } + // Check to see if we have a fuel component (slot = 5) + else if (recipe->components.count(5) > 0) { + vector rc = recipe->components[5]; + for (itr = rc.begin(); itr != rc.end(); itr++, i++) { + item = master_item_list.GetItem(*itr); + packet->setSubstructDataByName("recipe_info", "fuel_comp", recipe->fuel_comp_title); + packet->setSubstructDataByName("recipe_info", "fuel_comp_qty", recipe->fuel_comp_qty); + item = 0; + itemss = client->GetPlayer()->item_list.GetAllItemsFromID((*itr)); + if (itemss.size() > 0) { + int16 have_qty = 0; + for (int8 i = 0; i < itemss.size(); i++) { + have_qty += itemss[i]->details.count; + } + packet->setSubstructDataByName("recipe_info", "fuel_comp_qty_avail", have_qty); + break; + } + } + } + packet->setSubstructDataByName("recipe_info", "build_comp_qty_avail_flag", 1); + packet->setSubstructDataByName("recipe_info", "unknown6", 4, 0); + packet->setSubstructDataByName("recipe_info", "min_product", 1); + packet->setSubstructDataByName("recipe_info", "max_product", 1); + packet->setSubstructDataByName("recipe_info", "available_flag", 4); + packet->setSubstructDataByName("recipe_info", "not_commissionable", 1); + packet->setSubstructDataByName("recipe_info", "recipe_name", recipe->GetName()); + packet->setSubstructDataByName("recipe_info", "recipe_description", recipe->GetDescription()); + //packet->PrintPacket(); + EQ2Packet* data = packet->serialize(); + EQ2Packet* app = new EQ2Packet(OP_ClientCmdMsg, data->pBuffer, data->size); + safe_delete(packet); + safe_delete(data); + //DumpPacket(app); + return app; +} + +void Recipe::AddBuildComp(int32 itemID, int8 slot, bool preffered) { + if (preffered) + components[slot].insert(components[slot].begin(), itemID); + else + components[slot].push_back(itemID); +} + +int8 Recipe::GetTotalBuildComponents() { + int8 total_build_components = 0; + for(int i=1;i<=4;i++) { + if (components.count(i) > 0) + total_build_components++; + } + return total_build_components; +} + +bool Recipe::ProvidedAllRequiredComponents(Client* client, vector* player_components, vector>* player_component_pair_qty) { + vector::iterator itr; + std::vector> player_comp_itr; + + // Check to see if we have a fuel component (slot = 5) + bool matched = false; + bool hasfuel = false; + if (components.count(5) > 0) { + vector rc = components[5]; + for (itr = rc.begin(); itr != rc.end(); itr++) { + hasfuel = true; + LogWrite(TRADESKILL__INFO, 5, "Recipes", "Recipe ID: %u Recipe Name: %s, item %s (%u), fuel quantity required %u", GetID(), GetName(), fuel_comp_title, (*itr), fuel_comp_qty); + if(PlayerHasComponentByItemID(client, player_components, player_component_pair_qty, (*itr), fuel_comp_qty)) { + matched = true; + break; + } + } + } + if(hasfuel && !matched) { + LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Recipe ID: %u Recipe Name: %s, item %s (%u), lacking fuel quantity required %u", GetID(), GetName(), fuel_comp_title, (*itr), fuel_comp_qty); + return false; + } + + for (int8 index = 0; index < GetTotalBuildComponents(); index++) { + if (components.count(index + 1) == 0) + continue; + + vector rc = components[index + 1]; + string comp_title; + int8 comp_qty; + matched = false; + for (itr = rc.begin(); itr != rc.end(); itr++) { + if (index == 0) { + comp_title = build1_comp_title; + comp_qty = build1_comp_qty; + } + else if (index == 1) { + comp_title = build2_comp_title; + comp_qty = build2_comp_qty; + } + else if (index == 2) { + comp_title = build3_comp_title; + comp_qty = build3_comp_qty; + } + else if (index == 3) { + comp_title = build4_comp_title; + comp_qty = build4_comp_qty; + } + LogWrite(TRADESKILL__INFO, 5, "Recipes", "Recipe ID: %u Recipe Name: %s, item %s (%u), index %u quantity required %u", GetID(), GetName(), comp_title.c_str(), (*itr), index, comp_qty); + if(PlayerHasComponentByItemID(client, player_components, player_component_pair_qty, (*itr), comp_qty)) { + matched = true; + break; + } + } + if(!matched) { + return false; + } + } + return true; +} + +bool Recipe::PlayerHasComponentByItemID(Client* client, vector* player_components, vector>* player_component_pair_qty, int32 item_id, int8 required_qty) { + vector::iterator itr; + int16 have_qty = 0; + for(itr = player_components->begin(); itr != player_components->end(); itr++) { + LogWrite(TRADESKILL__DEBUG, 0, "Recipes", "PlayerHasComponentByItemID %u to match %u, qty %u, qtyreq: %u", (*itr)->details.item_id, item_id, (*itr)->details.count, required_qty); + if((*itr) && (*itr)->details.item_id == item_id && (*itr)->details.count >= required_qty) { + return true; + } + } + + vector itemss = client->GetPlayer()->item_list.GetAllItemsFromID(item_id); + if (itemss.size() > 0) { + for (int8 i = 0; i < itemss.size(); i++) { + have_qty += itemss[i]->details.count; + } + } + + int16 track_req_qty = required_qty; + if(have_qty >= required_qty) { + LogWrite(TRADESKILL__DEBUG, 0, "Recipes", "PlayerHasComponentByItemID OVERRIDE! Inventory has item id %u, more than required for quantity %u, have %u", item_id, required_qty, have_qty); + have_qty = 0; + for (int8 i = 0; i < itemss.size(); i++) { + have_qty += itemss[i]->details.count; + int8 cur_qty = itemss[i]->details.count; + if(cur_qty > track_req_qty) + cur_qty = track_req_qty; + + track_req_qty -= cur_qty; + itemss[i]->details.item_locked = true; + player_component_pair_qty->push_back({itemss[i]->details.unique_id, cur_qty}); + player_components->push_back(itemss[i]); + if(have_qty >= required_qty) + break; + } + return true; + } + + return false; +} + +int8 Recipe::GetItemRequiredQuantity(int32 item_id) { + vector::iterator itr; + int8 comp_qty = 0, qty = 0; + for (int8 index = 0; index < GetTotalBuildComponents(); index++) { + if (components.count(index + 1) == 0) + continue; + + vector rc = components[index + 1]; + string comp_title; + int8 comp_qty; + bool matched = false; + for (itr = rc.begin(); itr != rc.end(); itr++) { + if (index == 0) { + comp_qty = build1_comp_qty; + } + else if (index == 1) { + comp_qty = build2_comp_qty; + } + else if (index == 2) { + comp_qty = build3_comp_qty; + } + else if (index == 3) { + comp_qty = build4_comp_qty; + } + if((*itr) == item_id) + qty += comp_qty; + } + } + return qty; +} \ No newline at end of file diff --git a/source/WorldServer/Recipes/Recipe.h b/source/WorldServer/Recipes/Recipe.h new file mode 100644 index 0000000..596e3fc --- /dev/null +++ b/source/WorldServer/Recipes/Recipe.h @@ -0,0 +1,272 @@ +/* + 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 . +*/ +#ifndef RECIPE_H_ +#define RECIPE_H_ + +#include "../../common/types.h" +#include "../../common/Mutex.h" +#include "../classes.h" + +#include +#include + +class Item; +using namespace std; + +struct RecipeComp + +{ + int32 RecipeComp; + +}; +struct RecipeProducts { + int32 product_id; + int32 byproduct_id; + int8 product_qty; + int8 byproduct_qty; +}; + +class Recipe { +public: + Recipe(); + Recipe(Recipe *in); + virtual ~Recipe(); + + EQ2Packet *SerializeRecipe(Client *client, Recipe *recipe, bool display, int8 packet_type = 0, int8 sub_packet_type = 0, const char *struct_name = 0); + void SetID(int32 id) {this->id = id;} + void SetSoeID(int32 soe_id) { this->soe_id = soe_id; } + void SetBookID(int32 book_id) {this->book_id = book_id;} + void SetName(const char *name) {strncpy(this->name, name, sizeof(this->name));} + void SetDescription(const char* description) { strncpy(this->description, description, sizeof(this->description)); } + void SetBookName(const char *book_name) {strncpy(this->book_name, book_name, sizeof(this->book_name));} + void SetBook(const char *book) {strncpy(this->book, book, sizeof(this->book));} + void SetDevice(const char *device) {strncpy(this->device, device, sizeof(this->device));} + void SetLevel(int8 level) {this->level = level;} + void SetTier(int8 tier) {this->tier = tier;} + void SetIcon(int16 icon) {this->icon = icon;} + void SetSkill(int32 skill) {this->skill = skill;} + void SetTechnique(int32 technique) {this->technique = technique;} + void SetKnowledge(int32 knowledge) {this->knowledge = knowledge;} + void SetClasses(int32 classes) {this->classes = classes;} + void SetDevice_Sub_Type(int8 device_sub_type) {this->device_sub_type = device_sub_type;} + void SetUnknown1(int8 unknown1) {this->unknown1 = unknown1;} + void SetUnknown2(int32 unknown2) {this->unknown2 = unknown2;} + void SetUnknown3(int32 unknown3) {this->unknown3 = unknown3;} + void SetUnknown4(int32 unknown4) {this->unknown4 = unknown4;} + void SetProductID(int32 itemID) { product_item_id = itemID; } + void SetProductQuantity(int8 qty) { product_qty = qty; } + void SetProductName(const char* productName) { strncpy(product_name, productName, sizeof(product_name)); } + void SetBuild1ComponentTitle(const char* title) { strncpy(build1_comp_title, title, sizeof(build1_comp_title)); } + void SetBuild2ComponentTitle(const char* title) { strncpy(build2_comp_title, title, sizeof(build2_comp_title)); } + void SetBuild3ComponentTitle(const char* title) { strncpy(build3_comp_title, title, sizeof(build3_comp_title)); } + void SetBuild4ComponentTitle(const char* title) { strncpy(build4_comp_title, title, sizeof(build4_comp_title)); } + void SetFuelComponentTitle(const char* title) { strncpy(fuel_comp_title, title, sizeof(fuel_comp_title)); } + void SetPrimaryComponentTitle(const char* title) { strncpy(primary_build_comp_title, title, sizeof(primary_build_comp_title)); } + void SetBuild1ComponentQuantity(int8 qty) { build1_comp_qty = qty; } + void SetBuild2ComponentQuantity(int8 qty) { build2_comp_qty = qty; } + void SetBuild3ComponentQuantity(int8 qty) { build3_comp_qty = qty; } + void SetBuild4ComponentQuantity(int8 qty) { build4_comp_qty = qty; } + void SetFuelComponentQuantity(int8 qty) { fuel_comp_qty = qty; } + void SetPrimaryComponentQuantity(int8 qty) { primary_comp_qty = qty; } + + int32 GetID() {return id;} + int32 GetSoeID() { return soe_id; } + int32 GetBookID() {return book_id;} + const char * GetName() {return name;} + const char* GetDescription() { return description; } + const char * GetBookName() {return book_name;} + const char * GetBook() {return book;} + const char * GetDevice() {return device;} + int8 GetLevel() {return level;} + int8 GetTier() {return tier;} + int16 GetIcon() {return icon;} + int32 GetSkill() {return skill;} + int32 GetTechnique() {return technique;} + int32 GetKnowledge() {return knowledge;} + int32 GetClasses() {return classes;} + //class_id = classes.GetTSBaseClass(spawn->GetTradeskillClass()) bit-match on class ids 1-13 + //secondary_class_id = classes.GetSecondaryTSBaseClass(spawn->GetTradeskillClass()) bit-match on class ids 1-13 + //tertiary_class_id = spawn->GetTradeskillClass() (direct match) + bool CanUseRecipeByClass(Item* item, int8 class_id) { + /* any can use bit combination of 1+2 + adornments = 1 + artisan = 2 + */ + return item->generic_info.tradeskill_classes < 4 || (1 << class_id) & item->generic_info.tradeskill_classes; + } + int8 GetDevice_Sub_Type() {return device_sub_type;} + int8 GetUnknown1() {return unknown1;} + int32 GetUnknown2() {return unknown2;} + int32 GetUnknown3() {return unknown3;} + int32 GetUnknown4() {return unknown4;} + + int32 GetProductID() { return product_item_id; } + const char* GetProductTitle() { return product_name; } + int8 GetProductQuantity() { return product_qty; } + const char* GetPrimaryBuildComponentTitle() { return primary_build_comp_title; } + const char* GetBuild1ComponentTitle() { return build1_comp_title; } + const char* GetBuild2ComponentTitle() { return build2_comp_title; } + const char* GetBuild3ComponentTitle() { return build3_comp_title; } + const char* GetBuild4ComponentTitle() { return build4_comp_title; } + const char* GetFuelComponentTitle() { return fuel_comp_title; } + int16 GetBuild1ComponentQuantity() { return build1_comp_qty; } + int16 GetBuild2ComponentQuantity() { return build2_comp_qty; } + int16 GetBuild3ComponentQuantity() { return build3_comp_qty; } + int16 GetBuild4ComponentQuantity() { return build4_comp_qty; } + int16 GetFuelComponentQuantity() { return fuel_comp_qty; } + int16 GetPrimaryComponentQuantity() { return primary_comp_qty; } + + ///Add a build component to this recipe + ///Item id of the component + ///Slot id for this component + void AddBuildComp(int32 itemID, int8 slot, bool preferred = 0); + + // int8 = slot, vector = itemid + map > components; + + // int8 = stage, RecipeProducts = products/byproducts for this stage + map products; + + int8 GetHighestStage() { return highestStage; } + void SetHighestStage(int8 val) { highestStage = val; } + + int8 GetTotalBuildComponents(); + bool ProvidedAllRequiredComponents(Client* client, vector* player_components, vector>* player_component_pair_qty); + bool PlayerHasComponentByItemID(Client* client, vector* player_components, vector>* player_component_pair_qty, int32 item_id, int8 required_qty); + int8 GetItemRequiredQuantity(int32 item_id); +private: + int32 id; + int32 soe_id; + int32 book_id; + char name[256]; + char description[256]; + char book_name[256]; + char book[256]; + char device[30]; + int8 level; + int8 tier; + int16 icon; + int32 skill; + int32 technique; + int32 knowledge; + int8 device_sub_type; + int32 classes; + int8 unknown1; + int32 unknown2; + int32 unknown3; + int32 unknown4; + + int32 product_item_id; + char product_name[256]; + int8 product_qty; + char primary_build_comp_title[256]; + char build1_comp_title[256]; + char build2_comp_title[256]; + char build3_comp_title[256]; + char build4_comp_title[256]; + char fuel_comp_title[256]; + int16 build1_comp_qty; + int16 build2_comp_qty; + int16 build3_comp_qty; + int16 build4_comp_qty; + int16 fuel_comp_qty; + int16 primary_comp_qty; + int8 highestStage; + +}; + +class MasterRecipeList { +public: + MasterRecipeList(); + virtual ~MasterRecipeList(); + + bool AddRecipe(Recipe *recipe); + Recipe* GetRecipe(int32 recipe_id); + Recipe* GetRecipeByCRC(int32 recipe_crc); + void ClearRecipes(); + int32 Size(); + EQ2Packet* GetRecipePacket(int32 recipe_id, Client *client = 0, bool display = false, int8 packet_type = 0); + + /// Gets all the recipes for the given book name + /// Book name to get recipes for + /// A vector of all the recipes for the given book + vector GetRecipes(const char* book_name); + + /// Gets a recipe with the given name + /// The name of the recipe to get + /// Recipe* whos name matches the given name + Recipe* GetRecipeByName(const char* name); + +private: + Mutex m_recipes; + map recipes; + map recipes_crc; +}; + +class MasterRecipeBookList { +public: + MasterRecipeBookList(); + virtual ~MasterRecipeBookList(); + + bool AddRecipeBook(Recipe *recipe); + Recipe * GetRecipeBooks(int32 recipe_id); + void ClearRecipeBooks(); + int32 Size(); + +private: + Mutex m_recipeBooks; + map recipeBooks; +}; + +class PlayerRecipeList { +public: + PlayerRecipeList(); + virtual ~PlayerRecipeList(); + + bool AddRecipe(Recipe *recipe); + Recipe * GetRecipe(int32 recipe_id); + void ClearRecipes(); + bool RemoveRecipe(int32 recipe_id); + int32 Size(); + + map * GetRecipes() {return &recipes;} + +private: + map recipes; + mutable std::shared_mutex player_recipe_mutex; +}; + +class PlayerRecipeBookList { +public: + PlayerRecipeBookList(); + virtual ~PlayerRecipeBookList(); + + bool AddRecipeBook(Recipe *recipe); + bool HasRecipeBook(int32 book_id); + Recipe * GetRecipeBook(int32 recipe_id); + void ClearRecipeBooks(); + int32 Size(); + + map * GetRecipeBooks() {return &recipeBooks;} + +private: + map recipeBooks; +}; +#endif diff --git a/source/WorldServer/Recipes/RecipeDB.cpp b/source/WorldServer/Recipes/RecipeDB.cpp new file mode 100644 index 0000000..785b34f --- /dev/null +++ b/source/WorldServer/Recipes/RecipeDB.cpp @@ -0,0 +1,296 @@ +/* + 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 . +*/ +#ifdef WIN32 + #include + #include +#endif +#include +#include +#include "../../common/Log.h" +#include "../WorldDatabase.h" +#include "Recipe.h" + +extern MasterRecipeList master_recipe_list; +extern MasterRecipeBookList master_recipebook_list; +extern MasterItemList master_item_list; + +void WorldDatabase::LoadRecipes() { + DatabaseResult res; + + bool status = database_new.Select(&res, + "SELECT r.`id`,r.`soe_id`,r.`level`,r.`icon`,r.`skill_level`,r.`technique`,r.`knowledge`,r.`name`,r.`description`,i.`name` as `book`,r.`bench`,ipc.`adventure_classes`, " + "r.`stage4_id`, r.`name`, r.`stage4_qty`, pcl.`name` as primary_comp_title,r.primary_comp_qty, fcl.name as `fuel_comp_title`, r.fuel_comp_qty, " + "bc.`name` AS build_comp_title, bc.qty AS build_comp_qty, bc2.`name` AS build2_comp_title, bc2.qty AS build2_comp_qty, " + "bc3.`name` AS build3_comp_title, bc3.qty AS build3_comp_qty, bc4.`name` AS build4_comp_title, bc4.qty AS build4_comp_qty,\n" + "r.stage0_id, r.stage1_id, r.stage2_id, r.stage3_id, r.stage4_id, r.stage0_qty, r.stage1_qty, r.stage2_qty, r.stage3_qty, r.stage4_qty,\n" + "r.stage0_byp_id, r.stage1_byp_id, r.stage2_byp_id, r.stage3_byp_id, r.stage4_byp_id, r.stage0_byp_qty, r.stage1_byp_qty, r.stage2_byp_qty, r.stage3_byp_qty, r.stage4_byp_qty\n" + "FROM `recipe` r\n" + "LEFT JOIN ((SELECT recipe_id, soe_recipe_crc FROM item_details_recipe_items GROUP BY soe_recipe_crc) as idri) ON idri.soe_recipe_crc = r.soe_id\n" + "LEFT JOIN items i ON idri.recipe_id = i.id\n" + "INNER JOIN items ipc ON r.stage4_id = ipc.id\n" + "INNER JOIN recipe_comp_list pcl ON r.primary_comp_list = pcl.id\n" + "INNER JOIN recipe_comp_list fcl ON r.fuel_comp_list = fcl.id\n" + "LEFT JOIN (SELECT rsc.recipe_id, rsc.comp_list, rsc.`index`, rcl.`name`, rsc.qty FROM recipe_secondary_comp rsc INNER JOIN recipe_comp_list rcl ON rcl.id = rsc.comp_list WHERE `index` = 0) AS bc ON bc.recipe_id = r.id\n" + "LEFT JOIN (SELECT rsc.recipe_id, rsc.comp_list, rsc.`index`, rcl.`name`, rsc.qty FROM recipe_secondary_comp rsc INNER JOIN recipe_comp_list rcl ON rcl.id = rsc.comp_list WHERE `index` = 1) AS bc2 ON bc2.recipe_id = r.id\n" + "LEFT JOIN (SELECT rsc.recipe_id, rsc.comp_list, rsc.`index`, rcl.`name`, rsc.qty FROM recipe_secondary_comp rsc INNER JOIN recipe_comp_list rcl ON rcl.id = rsc.comp_list WHERE `index` = 2) AS bc3 ON bc3.recipe_id = r.id\n" + "LEFT JOIN (SELECT rsc.recipe_id, rsc.comp_list, rsc.`index`, rcl.`name`, rsc.qty FROM recipe_secondary_comp rsc INNER JOIN recipe_comp_list rcl ON rcl.id = rsc.comp_list WHERE `index` = 3) AS bc4 ON bc4.recipe_id = r.id\n" + "WHERE r.bHaveAllProducts"); + + if (!status) + return; + + while (res.Next()) { + int32 i = 0; + Recipe* recipe = new Recipe(); + recipe->SetID(res.GetInt32(i++)); + recipe->SetSoeID(res.GetInt32(i++)); + recipe->SetLevel(res.GetInt32(i++)); + recipe->SetTier(recipe->GetLevel() / 10 + 1); + recipe->SetIcon(res.GetInt32(i++)); + recipe->SetSkill(res.GetInt32(i++)); + recipe->SetTechnique(res.GetInt32(i++)); + recipe->SetKnowledge(res.GetInt32(i++)); + recipe->SetName(res.GetString(i++)); + recipe->SetDescription(res.GetString(i++)); + recipe->SetBook(res.GetString(i++)); + + //Convert the device string + string device = res.GetString(i++); + int32 deviceID = 0; + int8 deviceSubType = 0; + + recipe->SetDevice(GetDeviceName(device).c_str()); + recipe->SetUnknown2(deviceID); + recipe->SetDevice_Sub_Type(deviceSubType); + recipe->SetClasses(res.GetInt64(i++)); + recipe->SetUnknown3(0); + recipe->SetUnknown4(0); + + LogWrite(TRADESKILL__DEBUG, 5, "Recipes", "Loading recipe: %s (%u)", recipe->GetName(), recipe->GetID()); + + recipe->SetProductID(res.GetInt32(i++)); + recipe->SetProductName(res.GetString(i++)); + recipe->SetProductQuantity(res.GetInt8(i++)); + recipe->SetPrimaryComponentTitle(res.GetString(i++)); + recipe->SetPrimaryComponentQuantity(res.GetInt16(i++)); + recipe->SetFuelComponentTitle(res.GetString(i++)); + recipe->SetFuelComponentQuantity(res.GetInt16(i++)); + + recipe->SetBuild1ComponentTitle(res.GetString(i++)); + recipe->SetBuild1ComponentQuantity(res.GetInt16(i++)); + recipe->SetBuild2ComponentTitle(res.GetString(i++)); + recipe->SetBuild2ComponentQuantity(res.GetInt16(i++)); + recipe->SetBuild3ComponentTitle(res.GetString(i++)); + recipe->SetBuild3ComponentQuantity(res.GetInt16(i++)); + recipe->SetBuild4ComponentTitle(res.GetString(i++)); + recipe->SetBuild4ComponentQuantity(res.GetInt16(i++)); + + LogWrite(TRADESKILL__DEBUG, 7, "Recipes", "Loading recipe: %s (%u)", recipe->GetName(), recipe->GetID()); + + if (!master_recipe_list.AddRecipe(recipe)) { + LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Error adding recipe '%s' - duplicate ID: %u", recipe->GetName(), recipe->GetID()); + delete recipe; + continue; + } + + //Products/By-Products + for (int8 stage = 0; stage < 5; stage++) { + RecipeProducts* rp = new RecipeProducts; + rp->product_id = res.GetInt32(i); + rp->product_qty = res.GetInt8(i + 5); + rp->byproduct_id = res.GetInt32(i + 10); + rp->byproduct_qty = res.GetInt8(i + 15); + recipe->products[stage] = rp; + i++; + } + //Advance i past all the product info + //i += 15; + } + LoadRecipeComponents(); + + LogWrite(TRADESKILL__DEBUG, 0, "Recipes", "\tLoaded %u recipes", master_recipe_list.Size()); +} + +void WorldDatabase::LoadRecipeBooks(){ + Recipe *recipe; + Query query; + MYSQL_ROW row; + MYSQL_RES *res; + + res = query.RunQuery2(Q_SELECT, "SELECT id, name, tradeskill_default_level FROM items WHERE item_type='Recipe'"); + if (res){ + while ((row = mysql_fetch_row(res))){ + recipe = new Recipe(); + recipe->SetBookID(atoul(row[0])); + recipe->SetBookName(row[1]); + recipe->SetLevel(atoi(row[2])); + LogWrite(TRADESKILL__DEBUG, 5, "Recipes", "Loading Recipe Books: %s (%u)", recipe->GetBookName(), recipe->GetBookID()); + + if (!master_recipebook_list.AddRecipeBook(recipe)){ + LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Error adding Recipe Book '%s' - duplicate ID: %u", recipe->GetBookName(), recipe->GetBookID()); + safe_delete(recipe); + continue; + } + } + } + LogWrite(TRADESKILL__DEBUG, 0, "Recipes", "\tLoaded %u Recipe Books ", master_recipebook_list.Size()); +} + +void WorldDatabase::LoadPlayerRecipes(Player *player){ + Recipe *recipe; + Query query; + MYSQL_ROW row; + MYSQL_RES *res; + int16 total = 0; + + assert(player); + + res = query.RunQuery2(Q_SELECT, "SELECT recipe_id, highest_stage FROM character_recipes WHERE char_id = %u", player->GetCharacterID()); + if (res) { + while ((row = mysql_fetch_row(res))){ + int32 recipe_id = atoul(row[0]); + Recipe* master_recipe = master_recipe_list.GetRecipe(recipe_id); + if(!master_recipe) { + LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Error adding recipe %u to player '%s' - duplicate ID", atoul(row[0]), player->GetName()); + continue; + } + recipe = new Recipe(master_recipe); + recipe->SetHighestStage(atoi(row[1])); + + LogWrite(TRADESKILL__DEBUG, 5, "Recipes", "Loading recipe: %s (%u) for player: %s (%u)", recipe->GetName(), recipe->GetID(), player->GetName(), player->GetCharacterID()); + + if (!player->GetRecipeList()->AddRecipe(recipe)){ + LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Error adding recipe %u to player '%s' - duplicate ID", recipe->GetID(), player->GetName()); + safe_delete(recipe); + continue; + } + total++; + } + LogWrite(TRADESKILL__DEBUG, 0, "Recipes", "Loaded %u recipes for player: %s (%u)", total, player->GetName(), player->GetCharacterID()); + } +} + +int32 WorldDatabase::LoadPlayerRecipeBooks(int32 char_id, Player *player) { + assert(player); + + LogWrite(TRADESKILL__DEBUG, 0, "Recipes", "Loading Character Recipe Books for player '%s' ...", player->GetName()); + Query query; + MYSQL_ROW row; + MYSQL_RES *res; + int32 count = 0; + int32 old_id = 0; + int32 new_id = 0; + Recipe* recipe; + + res = query.RunQuery2(Q_SELECT, "SELECT recipebook_id FROM character_recipe_books WHERE char_id = %u", char_id); + if (res && mysql_num_rows(res) > 0) { + while (res && (row = mysql_fetch_row(res))){ + count++; + new_id = atoul(row[0]); + if(new_id == old_id) + continue; + + Item* item = master_item_list.GetItem(new_id); + if (!item) + continue; + + recipe = new Recipe(); + recipe->SetBookID(new_id); + recipe->SetBookName(item->name.c_str()); + + LogWrite(TRADESKILL__DEBUG, 5, "Recipes", "Loading Recipe Books: %s (%u)", recipe->GetBookName(), recipe->GetBookID()); + + if (!player->GetRecipeBookList()->AddRecipeBook(recipe)) { + LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Error adding player Recipe Book '%s' - duplicate ID: %u", recipe->GetBookName(), recipe->GetBookID()); + safe_delete(recipe); + continue; + } + old_id = new_id; + } + } + return count; +} + +void WorldDatabase::SavePlayerRecipeBook(Player* player, int32 recipebook_id){ + Query query; + query.AddQueryAsync(player->GetCharacterID(), this, Q_INSERT, "INSERT INTO character_recipe_books (char_id, recipebook_id) VALUES(%u, %u)", player->GetCharacterID(), recipebook_id); + //if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF) + //LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Error in SavePlayerRecipeBook query '%s' : %s", query.GetQuery(), query.GetError()); +} + +void WorldDatabase::SavePlayerRecipe(Player* player, int32 recipe_id) { + Query query; + query.AddQueryAsync(player->GetCharacterID(), this, Q_INSERT, "INSERT INTO character_recipes (char_id, recipe_id) VALUES (%u, %u)", player->GetCharacterID(), recipe_id); + //if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF) + //LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Error in SavePlayerRecipeBook query '%s' : %s", query.GetQuery(), query.GetError()); +} + +void WorldDatabase::LoadRecipeComponents() { + DatabaseResult res; + bool status = database_new.Select(&res, + "SELECT r.id, pc.item_id AS primary_comp, fc.item_id AS fuel_comp, sc.item_id as secondary_comp, rsc.`index` + 1 AS slot\n" + "FROM recipe r\n" + "INNER JOIN (select comp_list, item_id FROM recipe_comp_list_item ) as pc ON r.primary_comp_list = pc.comp_list\n" + "INNER JOIN (select comp_list, item_id FROM recipe_comp_list_item ) as fc ON r.fuel_comp_list = fc.comp_list\n" + "LEFT JOIN recipe_secondary_comp rsc ON rsc.recipe_id = r.id\n" + "LEFT JOIN (select comp_list, item_id FROM recipe_comp_list_item) as sc ON rsc.comp_list = sc.comp_list\n" + "WHERE r.bHaveAllProducts\n" + "ORDER BY r.id, rsc.`index` ASC"); + + if (!status) { + return; + } + + Recipe* recipe = 0; + int32 id = 0; + while (res.Next()) { + int32 tmp = res.GetInt32(0); + if (id != tmp) { + id = tmp; + recipe = master_recipe_list.GetRecipe(id); + + if (!recipe) { + continue; + } + + } + if (recipe && !res.IsNull(3)) { + if (find(recipe->components[0].begin(), recipe->components[0].end(), res.GetInt32(1)) == recipe->components[0].end()) + recipe->AddBuildComp(res.GetInt32(1), 0); + if (find(recipe->components[5].begin(), recipe->components[5].end(), res.GetInt32(2)) == recipe->components[5].end()) + recipe->AddBuildComp(res.GetInt32(2), 5); + if (find(recipe->components[res.GetInt8(4)].begin(), recipe->components[res.GetInt8(4)].end(), res.GetInt32(3)) == recipe->components[res.GetInt8(4)].end()) + recipe->AddBuildComp(res.GetInt32(3), res.GetInt8(4)); + } + //else + //LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Error loading `recipe_build_comps`, Recipe ID: %u", id); + } +} + +void WorldDatabase::UpdatePlayerRecipe(Player* player, int32 recipe_id, int8 highest_stage) { + Query query; + query.AddQueryAsync(player->GetCharacterID(), this, Q_UPDATE, "UPDATE `character_recipes` SET `highest_stage` = %i WHERE `char_id` = %u AND `recipe_id` = %u", highest_stage, player->GetCharacterID(), recipe_id); +} + +/* +ALTER TABLE `character_recipes` + ADD COLUMN `highest_stage` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0' AFTER `recipe_id`; + +*/ \ No newline at end of file diff --git a/source/WorldServer/Rules/Rules.cpp b/source/WorldServer/Rules/Rules.cpp new file mode 100644 index 0000000..066a3c2 --- /dev/null +++ b/source/WorldServer/Rules/Rules.cpp @@ -0,0 +1,532 @@ +/* + 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 . +*/ + +#include +#include "../../common/debug.h" +#include "../../common/Log.h" +#include "../../common/database.h" +#include "Rules.h" + +extern RuleManager rule_manager; + +Rule::Rule() { + category = 0; + type = 0; + strncpy(value, "", sizeof(value)); + strncpy(combined, "NONE", sizeof(combined)); +} + +Rule::Rule(int32 category, int32 type, const char *value, const char *combined) { + this->category = category; + this->type = type; + strncpy(this->value, value, sizeof(this->value)); + strncpy(this->combined, combined, sizeof(this->combined)); +} + +Rule::Rule (Rule *rule_in) { + category = rule_in->GetCategory(); + type = rule_in->GetType(); + strncpy(value, rule_in->GetValue(), sizeof(value)); + strncpy(combined, rule_in->GetCombined(), sizeof(combined)); +} + +Rule::~Rule() { +} + +RuleSet::RuleSet() { + id = 0; + memset(name, 0, sizeof(name)); + m_rules.SetName("RuleSet::rules"); +} + +RuleSet::RuleSet(RuleSet *in_rule_set) { + assert(in_rule_set); + + map > * in_rules = in_rule_set->GetRules(); + map >::iterator itr; + map::iterator itr2; + Rule * rule; + + m_rules.SetName("RuleSet::rules"); + id = in_rule_set->GetID(); + strncpy(name, in_rule_set->GetName(), sizeof(name)); + for (itr = in_rules->begin(); itr != in_rules->end(); itr++) { + for (itr2 = itr->second.begin(); itr2 != itr->second.end(); itr2++) { + rule = itr2->second; + rules[rule->GetCategory()][rule->GetType()] = new Rule(rule); + } + } +} + +RuleSet::~RuleSet() { + ClearRules(); +} + +void RuleSet::CopyRulesInto(RuleSet *in_rule_set) { + assert(in_rule_set); + + map > * in_rules = in_rule_set->GetRules(); + map >::iterator itr; + map::iterator itr2; + Rule * rule; + + ClearRules(); + m_rules.writelock(__FUNCTION__, __LINE__); + for (itr = in_rules->begin(); itr != in_rules->end(); itr++) { + for (itr2 = itr->second.begin(); itr2 != itr->second.end(); itr2++) { + rule = itr2->second; + rules[rule->GetCategory()][rule->GetType()] = new Rule(rule); + } + } + m_rules.releasewritelock(__FUNCTION__, __LINE__); +} + +void RuleSet::AddRule(Rule *rule) { + int32 category, type; + + assert(rule); + + category = rule->GetCategory(); + type = rule->GetType(); + m_rules.writelock(__FUNCTION__, __LINE__); + if (rules[category].count(type) == 0) + rules[category][type] = rule; + else + rules[category][type]->SetValue(rule->GetValue()); + m_rules.releasewritelock(__FUNCTION__, __LINE__); +} + +Rule * RuleSet::GetRule(int32 category, int32 type) { + Rule *ret = 0; + + m_rules.readlock(__FUNCTION__, __LINE__); + if (rules[category].count(type) > 0) + ret = rules[category][type]; + m_rules.releasereadlock(__FUNCTION__, __LINE__); + + if (!ret) + ret = rule_manager.GetBlankRule(); + + LogWrite(RULESYS__DEBUG, 5, "Rules", "Rule: %s, Value: %s", ret->GetCombined(), ret->GetValue()); + return ret; +} + +Rule * RuleSet::GetRule(const char *category, const char *type) { + map >::iterator itr; + map::iterator itr2; + char combined[256]; + Rule *ret = 0; + + snprintf(combined, sizeof(combined), "%s:%s", category, type); + // Zero terminate ([max - 1] = 0) to prevent a warning/error + combined[255] = 0; + + m_rules.readlock(__FUNCTION__, __LINE__); + for (itr = rules.begin(); itr != rules.end(); itr++) { + for (itr2 = itr->second.begin(); itr2 != itr->second.end(); itr2++) { + if (!strcmp(itr2->second->GetCombined(), combined)) { + ret = itr2->second; + break; + } + } + } + m_rules.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +void RuleSet::ClearRules() { + map >::iterator itr; + map::iterator itr2; + + m_rules.writelock(__FUNCTION__, __LINE__); + for (itr = rules.begin(); itr != rules.end(); itr++) { + for (itr2 = itr->second.begin(); itr2 != itr->second.end(); itr2++) + safe_delete(itr2->second); + } + rules.clear(); + m_rules.releasewritelock(__FUNCTION__, __LINE__); +} + +RuleManager::RuleManager() { + m_rule_sets.SetName("RuleManager::rule_sets"); + m_global_rule_set.SetName("RuleManager::global_rule_set"); + m_zone_rule_sets.SetName("RuleManager::zone_rule_sets"); + + Init(); +} + +RuleManager::~RuleManager() { + Flush(); +} + +void RuleManager::Init() +{ +#define RULE_INIT(category, type, value) rules[category][type] = new Rule(category, type, value, #category ":" #type) + + /* CLIENT */ + RULE_INIT(R_Client, ShowWelcomeScreen, "0"); + RULE_INIT(R_Client, GroupSpellsTimer, "1000"); + RULE_INIT(R_Client, QuestQueueTimer, "50"); // in milliseconds + + /* FACTION */ + RULE_INIT(R_Faction, AllowFactionBasedCombat, "1"); + + /* GUILD */ + RULE_INIT(R_Guild, MaxLevel, "50"); + RULE_INIT(R_Guild, MaxPlayers, "-1"); + + /* PLAYER */ + RULE_INIT(R_Player, MaxLevel, "50"); + RULE_INIT(R_Player, MaxLevelOverrideStatus, "100"); + RULE_INIT(R_Player, VitalityAmount, ".5"); + RULE_INIT(R_Player, VitalityFrequency, "3600"); + RULE_INIT(R_Player, XPMultiplier, "1.0"); + RULE_INIT(R_Player, TSXPMultiplier, "1.0"); + RULE_INIT(R_Player, MaxAA, "320"); + RULE_INIT(R_Player, MaxClassAA, "100"); + RULE_INIT(R_Player, MaxSubclassAA, "100"); + RULE_INIT(R_Player, MaxShadowsAA, "70"); + RULE_INIT(R_Player, MaxHeroicAA, "50"); + RULE_INIT(R_Player, MaxTradeskillAA, "40"); + RULE_INIT(R_Player, MaxPrestigeAA, "25"); + RULE_INIT(R_Player, MaxTradeskillPrestigeAA, "25"); + RULE_INIT(R_Player, MinLastNameLevel, "20"); + RULE_INIT(R_Player, MaxLastNameLength, "20"); + RULE_INIT(R_Player, MinLastNameLength, "4"); + RULE_INIT(R_Player, DisableHouseAlignmentRequirement, "1"); + RULE_INIT(R_Player, MentorItemDecayRate, ".05"); // 5% per level lost when mentoring + RULE_INIT(R_Player, TemporaryItemLogoutTime, "1800.0"); // time in seconds (double) for temporary item to decay after being logged out for a period of time, 30 min is the default + RULE_INIT(R_Player, HeirloomItemShareExpiration, "172800.0"); // 2 days ('48 hours') in seconds + RULE_INIT(R_Player, SwimmingSkillMinSpeed, "20"); + RULE_INIT(R_Player, SwimmingSkillMaxSpeed, "200"); + RULE_INIT(R_Player, SwimmingSkillMinBreathLength, "30"); + RULE_INIT(R_Player, SwimmingSkillMaxBreathLength, "1000"); + RULE_INIT(R_Player, AutoSkillUpBaseSkills, "0"); // when set to 1 we auto skill to max value on levelling up for armor,shield,class,weapon skills + RULE_INIT(R_Player, MaxWeightStrengthMultiplier, "2.0"); // multiplier for strength to add to max weight, eg 25 str * 2.0 = 50 max weight + base weight + RULE_INIT(R_Player, BaseWeight, "50"); // base weight per class, added to max weight with the strength multiplier + RULE_INIT(R_Player, WeightPercentImpact, "0.01"); // overweight in stone speed impact (.01 = 1% per 1 stone) + RULE_INIT(R_Player, WeightPercentCap, "0.95"); // cap total impact for being overweight (.95 = 95%) + RULE_INIT(R_Player, CoinWeightPerStone, "40.0"); // coin weight per stone, 40.0 = 40 coins per 1 stone (per DoF client hover over) + RULE_INIT(R_Player, WeightInflictsSpeed, "1"); // whether weight will inflict speed, 1 = on, 0 = off + RULE_INIT(R_Player, LevelMasterySkillMultiplier, "5"); // multiplier for adventure level / recommended level when applying mastery damage to determine if they are in mastery range + RULE_INIT(R_Player, TraitTieringSelection, "1"); // when set to true limited to single trait per group, otherwise you can freely select from any group + RULE_INIT(R_Player, ClassicTraitLevelTable, "1"); // uses built in table based on Prima Guide, see Traits.cpp for more, otherwise uses the levels below + RULE_INIT(R_Player, TraitFocusSelectLevel, "9"); // x levels to receive new trait of focus, eg level/rule, level 10, rule value 5, 10/5 = 2 focus traits available at level 10 + RULE_INIT(R_Player, TraitTrainingSelectLevel, "10"); // x levels to receive new trait of focus + RULE_INIT(R_Player, TraitRaceSelectLevel, "10"); // x levels to receive new trait of focus + RULE_INIT(R_Player, TraitCharacterSelectLevel, "10"); // x levels to receive new trait of focus + RULE_INIT(R_Player, StartHPBase, "40"); + RULE_INIT(R_Player, StartPowerBase, "45"); + RULE_INIT(R_Player, StartHPLevelMod, "2.0"); + RULE_INIT(R_Player, StartPowerLevelMod, "2.1"); + RULE_INIT(R_Player, AllowPlayerEquipCombat, "1"); + RULE_INIT(R_Player, MaxTargetCommandDistance, "50.0"); // max distance allowed for /target command when target name is not in group + RULE_INIT(R_Player, HarvestSkillUpMultiplier, "1.5"); /* multiplier for node to take the "min skill" max and use a multiplier to offset the max skill allowed to skill up on node. + ** Eg. 50 min skill on node, 50*1.5=75, no one with higher than 75 skill gets a skill up + */ + /* PVP */ + RULE_INIT(R_PVP, AllowPVP, "0"); + RULE_INIT(R_PVP, LevelRange, "4"); + RULE_INIT(R_PVP, InvisPlayerDiscoveryRange, "20"); // value > 0 sets radius inner to see, = 0 means always seen, -1 = never seen + RULE_INIT(R_PVP, PVPMitigationModByLevel, "25"); // gives a bonus to mitigation for PVP combat to offset the percentage level * mod (default 25) + + /* COMBAT */ + RULE_INIT(R_Combat, MaxCombatRange, "4.0"); + RULE_INIT(R_Combat, DeathExperienceDebt, "50.00"); // divide by 100, 50/100 = .5% debt per pve death + RULE_INIT(R_Combat, PVPDeathExperienceDebt, "25.00"); // divide by 100, 25/100 = .25% debt per pvp death + RULE_INIT(R_Combat, GroupExperienceDebt, "0"); // set to 1 means we will share debt between the group + RULE_INIT(R_Combat, ExperienceToDebt, "50.00"); // percentage of xp earned to debt vs obtained xp 50/100 = 50% to debt + RULE_INIT(R_Combat, ExperienceDebtRecoveryPercent, "5.00"); // recovery percentage per period of time, 5/100 = 5% recovered (so if .5% debt, .5*.05 = .025, .5-.025=.475% debt left) + RULE_INIT(R_Combat, ExperienceDebtRecoveryPeriod, "600"); // every 10 minutes (x*60 seconds) recover ExperienceDebtRecoveryPercent + RULE_INIT(R_Combat, EnableSpiritShards, "1"); + RULE_INIT(R_Combat, SpiritShardSpawnScript, "SpawnScripts/Generic/SpiritShard.lua"); + RULE_INIT(R_Combat, ShardDebtRecoveryPercent, "25.00"); // recovered percentage of debt upon obtainig shard, 25/100 means 25%. If there is .5 DeathExperienceDebt, .5*25% = .125, .5 - .125 = .375 + RULE_INIT(R_Combat, ShardRecoveryByRadius, "1"); // allow shards to auto pick up by radius, not requiring to click/right click the shard + RULE_INIT(R_Combat, ShardLifetime, "86400"); // default: 86400 seconds (one day) + RULE_INIT(R_Combat, EffectiveMitigationCapLevel, "80"); // level multiplier for max effective cap, level * 80 (default) + RULE_INIT(R_Combat, CalculatedMitigationCapLevel, "100"); // The cap to calculate your mitigation from is [level*100]. + RULE_INIT(R_Combat, MitigationLevelEffectivenessMax, "1.5"); // ratio victim level / attacker level for max effectiveness, when victim is higher level cap can reach 1.5 + RULE_INIT(R_Combat, MitigationLevelEffectivenessMin, ".5"); // ratio victim level / attacker level for min effectiveness + RULE_INIT(R_Combat, MaxMitigationAllowed, ".75"); // percentage max mitigation allowed, eg. 75% of damage can be mitigated max in PVE + RULE_INIT(R_Combat, MaxMitigationAllowedPVP, ".75"); // percentage max mitigation allowed, eg. 75% of damage can be mitigated max in PVP + RULE_INIT(R_Combat, StrengthNPC, "10"); // divider for strength NPC only str/x = additional dmg to low/high dmg + RULE_INIT(R_Combat, StrengthOther, "25"); // divider for strength other than NPC str/x = additional dmg to low/high dmg + RULE_INIT(R_Combat, MaxSkillBonusByLevel, "1.5"); // Level * 1.5 = max bonus skill allowed + RULE_INIT(R_Combat, LockedEncounterNoAttack, "1"); // when set to 1, players/group members not part of the encounter cannot attack until /yell + + /* SPAWN */ + RULE_INIT(R_Spawn, SpeedMultiplier, "300"); // note: this value was 1280 until 6/1/2009, then was 600 til Sep 2009, when it became 300...? + RULE_INIT(R_Spawn, ClassicRegen, "0"); + RULE_INIT(R_Spawn, HailMovementPause, "5000"); // time in milliseconds the spawn is paused on hail + RULE_INIT(R_Spawn, HailDistance, "5"); // max distance to hail a spawn/npc + RULE_INIT(R_Spawn, UseHardCodeWaterModelType, "1"); // uses alternate method of setting water type by model type (hardcoded) versus relying on just DB + RULE_INIT(R_Spawn, UseHardCodeFlyingModelType, "1"); // uses alternate method of setting flying type by model type (hardcoded) versus relying on just DB + + /* TIMER */ + + /* UI */ + RULE_INIT(R_UI, MaxWhoResults, "20"); + RULE_INIT(R_UI, MaxWhoOverrideStatus, "200"); + + /* WORLD */ + RULE_INIT(R_World, DefaultStartingZoneID, "1"); + RULE_INIT(R_World, EnablePOIDiscovery, "0"); + RULE_INIT(R_World, GamblingTokenItemID, "2"); + RULE_INIT(R_World, GuildAutoJoin, "0"); + RULE_INIT(R_World, GuildAutoJoinID, "1"); + RULE_INIT(R_World, GuildAutoJoinDefaultRankID, "7"); + RULE_INIT(R_World, MaxPlayers, "-1"); + RULE_INIT(R_World, MaxPlayersOverrideStatus, "100"); + RULE_INIT(R_World, ServerLocked, "0"); + RULE_INIT(R_World, ServerLockedOverrideStatus, "10"); + RULE_INIT(R_World, SyncZonesWithLogin, "1"); + RULE_INIT(R_World, SyncEquipWithLogin, "1"); + RULE_INIT(R_World, UseBannedIPsTable, "0"); + RULE_INIT(R_World, LinkDeadTimer, "120000"); // default: 2 minutes + RULE_INIT(R_World, RemoveDisconnectedClientsTimer, "30000"); // default: 30 seconds + RULE_INIT(R_World, PlayerCampTimer, "20"); // default: 20 seconds + RULE_INIT(R_World, GMCampTimer, "1"); // default: 1 second + RULE_INIT(R_World, AutoAdminPlayers, "0"); // default: No + RULE_INIT(R_World, AutoAdminGMs, "0"); // default: No + RULE_INIT(R_World, AutoAdminStatusValue, "10"); // default: 10 (CSR) + RULE_INIT(R_World, DuskTime, "20:00"); // default: 8pm + RULE_INIT(R_World, DawnTime, "8:00"); // default: 8am + RULE_INIT(R_World, ThreadedLoad, "0"); // default: no threaded loading + RULE_INIT(R_World, TradeskillSuccessChance, "87.0"); // default: 87% chance of success while crafting + RULE_INIT(R_World, TradeskillCritSuccessChance, "2.0"); // default: 2% chance of critical success while crafting + RULE_INIT(R_World, TradeskillFailChance, "10.0"); // default: 10% chance of failure while crafting + RULE_INIT(R_World, TradeskillCritFailChance, "1.0"); // default: 1% chance of critical failure while crafting + RULE_INIT(R_World, TradeskillEventChance, "15.0"); // default: 15% chance of a tradeskill event while crafting + RULE_INIT(R_World, EditorURL, "www.eq2emulator.net"); // default: www.eq2emulator.net + RULE_INIT(R_World, EditorIncludeID, "0"); // default: 0 (0 = disabled, 1 = enabled) + RULE_INIT(R_World, EditorOfficialServer, "0"); // default: 0 (0 = disabled, 1 = enabled) + RULE_INIT(R_World, SavePaperdollImage, "1"); // default: true + RULE_INIT(R_World, SaveHeadshotImage, "1"); // default: true + RULE_INIT(R_World, SendPaperdollImagesToLogin, "1"); // default: true + RULE_INIT(R_World, TreasureChestDisabled, "0"); // default: false + RULE_INIT(R_World, StartingZoneLanguages, "0"); // default: 0 (0 = Live Like, 1 = Starting City Based) + RULE_INIT(R_World, StartingZoneRuleFlag, "0"); // default: 0 - match any options available, just based on version/other fields (will not force qc/outpost) + // 1 - force split zones on alignment/deity despite client selection (queens colony/overlord outpost) + // 4 - send to 'new' starting zones, won't support old clients + // 8 - (isle of refuge) + RULE_INIT(R_World, EnforceRacialAlignment, "1"); + RULE_INIT(R_World, MemoryCacheZoneMaps, "0"); // 0 disables caching the zone maps in memory, too many individual/unique zones entered may cause a lot of memory build up + RULE_INIT(R_World, AutoLockEncounter, "0"); // When set to 0 we require player to attack to lock the encounter, otherwise if 1 then npc can auto lock encounter + RULE_INIT(R_World, DisplayItemTiers, "1"); // Display item tiers when set to 1, otherwise do not + RULE_INIT(R_World, LoreAndLegendAccept, "0"); // default: 0 - L&L quests accepted only through starter books. 1 - L&L quests can be started by examining bodyparts. + + //INSERT INTO `ruleset_details`(`id`, `ruleset_id`, `rule_category`, `rule_type`, `rule_value`, `description`) VALUES (NULL, '1', 'R_World', '', '', '') + + /* ZONE */ + RULE_INIT(R_Zone, MaxPlayers, "100"); + RULE_INIT(R_Zone, MinZoneLevelOverrideStatus, "1"); + RULE_INIT(R_Zone, MinZoneAccessOverrideStatus, "100"); + RULE_INIT(R_Zone, WeatherEnabled, "1"); // default: 1 (0 = disabled, 1 = enabled) + RULE_INIT(R_Zone, WeatherType, "0"); // default: 1 (0 = normal, 1 = dynamic, 2 = random, 3 = chaotic) + RULE_INIT(R_Zone, MinWeatherSeverity, "0.0"); // default: 0.0 or no weather + RULE_INIT(R_Zone, MaxWeatherSeverity, "1.0"); // default: 1.0 or hard rain (range 0.0 - 1.0, rain starts at 0.75) + RULE_INIT(R_Zone, WeatherChangeFrequency, "300"); // default: 5 minutes + RULE_INIT(R_Zone, WeatherChangePerInterval, "0.02"); // default: 0.02 (slight changes) + RULE_INIT(R_Zone, WeatherChangeChance, "20"); // default: 20% (in whole percents) + RULE_INIT(R_Zone, WeatherDynamicMaxOffset, "0.08"); // default: 0.08 - dynamic weather changes can only change this max amount + RULE_INIT(R_Zone, SpawnUpdateTimer, "50"); // default: 50ms - how often to check for spawn update sends + RULE_INIT(R_Zone, CheckAttackNPC, "2000"); // default: 2 seconds, how often to for NPCs to attack eachother + RULE_INIT(R_Zone, CheckAttackPlayer, "2000"); // default: 2 seconds, how often to check for NPCs to attack players + RULE_INIT(R_Zone, HOTime, "10.0"); // default: 10 seconds, time to complete the HO wheel before it expires + + /* ZONE TIMERS */ + RULE_INIT(R_Zone, RegenTimer, "6000"); + RULE_INIT(R_Zone, ClientSaveTimer, "60000"); + RULE_INIT(R_Zone, ShutdownDelayTimer, "120000"); + RULE_INIT(R_Zone, WeatherTimer, "60000"); // default: 1 minute + RULE_INIT(R_Zone, SpawnDeleteTimer, "30000"); // default: 30 seconds, how long a spawn pointer is held onto after being removed from the world before deleting it + RULE_INIT(R_Zone, UseMapUnderworldCoords, "1"); // use maps lowest Y coordinate to establish underworld markers + RULE_INIT(R_Zone, MapUnderworldCoordOffset, "-200.0"); // adds (or in the case of negative value subtracts) so that the underworld marker is lower when map is using its lowest Y coordinate + + RULE_INIT(R_Loot, LootRadius, "5.0"); + RULE_INIT(R_Loot, AutoDisarmChest, "1"); + RULE_INIT(R_Loot, ChestTriggerRadiusGroup, "10.0"); // radius at which chest will trigger against group members + RULE_INIT(R_Loot, ChestUnlockedTimeDrop, "1200"); // time in seconds, 20 minutes by default, triggers only if AllowChestUnlockByDropTime is 1 + RULE_INIT(R_Loot, AllowChestUnlockByDropTime, "1"); // when set to 1 we will start a countdown timer to allow anyone to loot once ChestUnlockedTimeDrop elapsed + RULE_INIT(R_Loot, ChestUnlockedTimeTrap, "600"); // time in seconds, 10 minutes by default + RULE_INIT(R_Loot, AllowChestUnlockByTrapTime, "1"); // when set to 1 we will allow unlocking the chest to all players after the trap is triggered (or chest is open) and period ChestUnlockedTimeTrap elapsed + RULE_INIT(R_Loot, SkipLootGrayMob, "1"); + RULE_INIT(R_Loot, LootDistributionTime, "120"); // time in seconds that we allow the group to determine their loot decision (lotto/need/greed/decline). + + RULE_INIT(R_Spells, NoInterruptBaseChance, "50"); + RULE_INIT(R_Spells, EnableFizzleSpells, "1"); // enables/disables the 'fizzling' of spells based on can_fizzle in the spells table. This also enables increasing specialized skills for classes based on spells/abilities. + RULE_INIT(R_Spells, DefaultFizzleChance, "10.0"); // default percentage x / 100, eg 10% is 10.0 + RULE_INIT(R_Spells, FizzleMaxSkill, "1.2"); // 1.0 is 100%, 1.2 is 120%, so you get 120% your max skill against a spell, no fizzle + RULE_INIT(R_Spells, FizzleDefaultSkill, ".2"); // offset against MaxSkill to average out to 100%, default of .2f so we don't go over the threshold if no skill + RULE_INIT(R_Spells, EnableCrossZoneGroupBuffs, "0"); // enables/disables allowing cross zone group buffs + RULE_INIT(R_Spells, EnableCrossZoneTargetBuffs, "0"); // enables/disables allowing cross zone target buffs + RULE_INIT(R_Spells, PlayerSpellSaveStateWaitInterval, "100"); // time in milliseconds we wait before performing a save when the spell save trigger is activated, allows additional actions to take place until the cap is hit + RULE_INIT(R_Spells, PlayerSpellSaveStateCap, "1000"); // sets a maximum wait time before we queue a spell state save to the DB, given a lot can go on in a short period with players especially in combat, maybe good to have this at a higher interval. + RULE_INIT(R_Spells, RequirePreviousTierScribe, "0"); // requires step up apprentice -> apprentice (handcrafted?) -> journeyman (handcrafted?) -> adept -> expert -> master + RULE_INIT(R_Spells, CureSpellID, "110003"); // Base Cure spell that was used after they removed cure types + RULE_INIT(R_Spells, CureCurseSpellID, "110004"); // Curse Spell ID in the spells database + RULE_INIT(R_Spells, CureNoxiousSpellID, "110005"); // Noxious/Poison Spell ID in the spells database + RULE_INIT(R_Spells, CureMagicSpellID, "210006"); // Magic/Elemental Spell ID in the spells database + RULE_INIT(R_Spells, CureTraumaSpellID, "0"); // Trauma/Mental Spell ID in the spells database + RULE_INIT(R_Spells, CureArcaneSpellID, "0"); // Arcane/Heat Spell ID in the spells database + RULE_INIT(R_Spells, MinistrationSkillID, "366253016"); // ministration skill id used to map power reduction rule MinistrationPowerReductionMax + RULE_INIT(R_Spells, MinistrationPowerReductionMax, "15.0"); // max percentage of power reduction for spells with ministration mastery skill (default is 15.0 for 15%) + RULE_INIT(R_Spells, MinistrationPowerReductionSkill, "25"); // divides by integer value to establish how much skill req for higher power reduction + RULE_INIT(R_Spells, MasterSkillReduceSpellResist, "25"); // divides by integer value to establish how much skill bonus for reducing spell resistance on target + + RULE_INIT(R_Expansion, GlobalExpansionFlag, "0"); + RULE_INIT(R_Expansion, GlobalHolidayFlag, "0"); + + RULE_INIT(R_World, DatabaseVersion, "0"); + + //devn00b + RULE_INIT(R_Discord, DiscordEnabled, "0"); //Enable/Disable built in discord bot. + RULE_INIT(R_Discord, DiscordWebhookURL, "None"); //Webhook url used for server -> discord messages. + RULE_INIT(R_Discord, DiscordBotToken, "None"); //Bot token used to connect to discord and provides discord -> server messages. + RULE_INIT(R_Discord, DiscordChannel, "Discord"); // in-game channel used for server -> discord messages. + RULE_INIT(R_Discord, DiscordListenChan, "0"); // Discord ChannelID used for discord->server messages. +#undef RULE_INIT +} + +void RuleManager::Flush(bool reinit) +{ + map >::iterator itr; + map::iterator itr2; + + for (itr = rules.begin(); itr != rules.end(); itr++) { + for (itr2 = itr->second.begin(); itr2 != itr->second.end(); itr2++) + safe_delete(itr2->second); + } + + rules.clear(); + + ClearRuleSets(); + ClearZoneRuleSets(); + + if (reinit) + Init(); +} + +void RuleManager::LoadCodedDefaultsIntoRuleSet(RuleSet *rule_set) { + map >::iterator itr; + map::iterator itr2; + + assert(rule_set); + + for (itr = rules.begin(); itr != rules.end(); itr++) { + for (itr2 = itr->second.begin(); itr2 != itr->second.end(); itr2++) + rule_set->AddRule(new Rule(itr2->second)); + } +} + +bool RuleManager::AddRuleSet(RuleSet *rule_set) { + bool ret = false; + int32 id; + + assert(rule_set); + + id = rule_set->GetID(); + m_rule_sets.writelock(__FUNCTION__, __LINE__); + if (rule_sets.count(id) == 0) { + rule_sets[id] = rule_set; + ret = true; + } + m_rule_sets.releasewritelock(__FUNCTION__, __LINE__); + + return ret; +} + +int32 RuleManager::GetNumRuleSets() { + int32 ret; + + m_rule_sets.readlock(__FUNCTION__, __LINE__); + ret = rule_sets.size(); + m_rule_sets.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +void RuleManager::ClearRuleSets() { + map::iterator itr; + + m_rule_sets.writelock(__FUNCTION__, __LINE__); + for (itr = rule_sets.begin(); itr != rule_sets.end(); itr++) + safe_delete(itr->second); + rule_sets.clear(); + m_rule_sets.releasewritelock(__FUNCTION__, __LINE__); +} + +bool RuleManager::SetGlobalRuleSet(int32 rule_set_id) { + if (rule_sets.count(rule_set_id) == 0) + return false; + + global_rule_set.CopyRulesInto(rule_sets[rule_set_id]); + return true; +} + +Rule * RuleManager::GetGlobalRule(int32 category, int32 type) { + return global_rule_set.GetRule(category, type); +} + +Rule * RuleManager::GetGlobalRule(const char* category, const char* type) { + return global_rule_set.GetRule(category, type); +} + +bool RuleManager::SetZoneRuleSet(int32 zone_id, int32 rule_set_id) { + bool ret = true; + RuleSet *rule_set; + + m_rule_sets.readlock(__FUNCTION__, __LINE__); + if (rule_sets.count(rule_set_id) == 0) + ret = false; + + rule_set = rule_sets[rule_set_id]; + if (ret) { + m_zone_rule_sets.writelock(__FUNCTION__, __LINE__); + zone_rule_sets[zone_id] = rule_set; + m_zone_rule_sets.releasewritelock(__FUNCTION__, __LINE__); + } + m_rule_sets.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +Rule * RuleManager::GetZoneRule(int32 zone_id, int32 category, int32 type) { + Rule *ret = 0; + + /* we never want to return null so MAKE SURE the rule exists. if this assertion fails then the server admin must fix the problem */ + assert(rules.count(category) > 0); + assert(rules[category].count(type) > 0); + + /* first try to get the zone rule */ + m_zone_rule_sets.readlock(__FUNCTION__, __LINE__); + if (zone_rule_sets.count(zone_id) > 0) + ret = zone_rule_sets[zone_id]->GetRule(category, type); + m_zone_rule_sets.releasereadlock(__FUNCTION__, __LINE__); + + return ret ? ret : rules[category][type]; +} + +void RuleManager::ClearZoneRuleSets() { + m_zone_rule_sets.writelock(__FUNCTION__, __LINE__); + zone_rule_sets.clear(); + m_zone_rule_sets.releasewritelock(__FUNCTION__, __LINE__); +} \ No newline at end of file diff --git a/source/WorldServer/Rules/Rules.h b/source/WorldServer/Rules/Rules.h new file mode 100644 index 0000000..6fc4542 --- /dev/null +++ b/source/WorldServer/Rules/Rules.h @@ -0,0 +1,363 @@ +/* + 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 . +*/ +#ifndef RULES_H_ +#define RULES_H_ + +#include +#include +#include "../../common/Mutex.h" +#include "../../common/types.h" + +using namespace std; + +enum RuleCategory { + R_Client, + R_Faction, + R_Guild, + R_Player, + R_PVP, + R_Combat, + R_Spawn, + R_UI, + R_World, + R_Zone, + R_Loot, + R_Spells, + R_Expansion, + R_Discord +}; + +enum RuleType { + /* CLIENT */ + ShowWelcomeScreen, + + /* FACTION */ + AllowFactionBasedCombat, + + /* GUILD */ + /* PLAYER */ + MaxLevel, + MaxLevelOverrideStatus, + MaxPlayers, + MaxPlayersOverrideStatus, + VitalityAmount, + VitalityFrequency, + MaxAA, + MaxClassAA, + MaxSubclassAA, + MaxShadowsAA, + MaxHeroicAA, + MaxTradeskillAA, + MaxPrestigeAA, + MaxTradeskillPrestigeAA, + MaxDragonAA, + MinLastNameLevel, + MaxLastNameLength, + MinLastNameLength, + DisableHouseAlignmentRequirement, + MentorItemDecayRate, + TemporaryItemLogoutTime, + HeirloomItemShareExpiration, + SwimmingSkillMinSpeed, + SwimmingSkillMaxSpeed, + SwimmingSkillMinBreathLength, + SwimmingSkillMaxBreathLength, + AutoSkillUpBaseSkills, + MaxWeightStrengthMultiplier, + BaseWeight, + WeightPercentImpact, + WeightPercentCap, + CoinWeightPerStone, + WeightInflictsSpeed, + LevelMasterySkillMultiplier, + TraitTieringSelection, + ClassicTraitLevelTable, + TraitFocusSelectLevel, + TraitTrainingSelectLevel, + TraitRaceSelectLevel, + TraitCharacterSelectLevel, + StartHPBase, + StartPowerBase, + StartHPLevelMod, + StartPowerLevelMod, + AllowPlayerEquipCombat, + MaxTargetCommandDistance, + HarvestSkillUpMultiplier, + + /* PVP */ + AllowPVP, + LevelRange, + InvisPlayerDiscoveryRange, + PVPMitigationModByLevel, + + /* COMBAT */ + MaxCombatRange, + DeathExperienceDebt, + GroupExperienceDebt, + PVPDeathExperienceDebt, + ExperienceToDebt, + ExperienceDebtRecoveryPercent, + ExperienceDebtRecoveryPeriod, + EnableSpiritShards, + SpiritShardSpawnScript, + ShardDebtRecoveryPercent, + ShardRecoveryByRadius, + ShardLifetime, + EffectiveMitigationCapLevel, + CalculatedMitigationCapLevel, + MitigationLevelEffectivenessMax, + MitigationLevelEffectivenessMin, + MaxMitigationAllowed, + MaxMitigationAllowedPVP, + StrengthNPC, + StrengthOther, + MaxSkillBonusByLevel, + LockedEncounterNoAttack, + + /* SPAWN */ + SpeedMultiplier, + ClassicRegen, + HailMovementPause, + HailDistance, + UseHardCodeWaterModelType, + UseHardCodeFlyingModelType, + //SpeedRatio, + + /* UI */ + MaxWhoResults, + MaxWhoOverrideStatus, + + /* WORLD */ + DefaultStartingZoneID, + EnablePOIDiscovery, + GamblingTokenItemID, + GuildAutoJoin, + GuildAutoJoinID, + GuildAutoJoinDefaultRankID, + ServerLocked, + ServerLockedOverrideStatus, + SyncZonesWithLogin, + SyncEquipWithLogin, + UseBannedIPsTable, + LinkDeadTimer, + RemoveDisconnectedClientsTimer, + PlayerCampTimer, + GMCampTimer, + AutoAdminPlayers, + AutoAdminGMs, + AutoAdminStatusValue, + DuskTime, + DawnTime, + ThreadedLoad, + TradeskillSuccessChance, + TradeskillCritSuccessChance, + TradeskillFailChance, + TradeskillCritFailChance, + TradeskillEventChance, + EditorURL, + EditorIncludeID, + EditorOfficialServer, + GroupSpellsTimer, + QuestQueueTimer, + SavePaperdollImage, + SaveHeadshotImage, + SendPaperdollImagesToLogin, + TreasureChestDisabled, + StartingZoneLanguages, + StartingZoneRuleFlag, + EnforceRacialAlignment, + MemoryCacheZoneMaps, + AutoLockEncounter, + DisplayItemTiers, + LoreAndLegendAccept, + + /* ZONE */ + MinZoneLevelOverrideStatus, + MinZoneAccessOverrideStatus, + XPMultiplier, + TSXPMultiplier, + WeatherEnabled, + WeatherType, + MinWeatherSeverity, + MaxWeatherSeverity, + WeatherChangeFrequency, + WeatherChangePerInterval, + WeatherDynamicMaxOffset, + WeatherChangeChance, + SpawnUpdateTimer, + CheckAttackPlayer, + CheckAttackNPC, + HOTime, + UseMapUnderworldCoords, + MapUnderworldCoordOffset, + + /* LOOT */ + LootRadius, + AutoDisarmChest, // if enabled disarm only works if you right click and disarm, clicking and opening chest won't attempt auto disarm + ChestTriggerRadiusGroup, + ChestUnlockedTimeDrop, + AllowChestUnlockByDropTime, + ChestUnlockedTimeTrap, + AllowChestUnlockByTrapTime, + + /* SPELLS */ + NoInterruptBaseChance, + EnableFizzleSpells, + DefaultFizzleChance, + FizzleMaxSkill, + FizzleDefaultSkill, + EnableCrossZoneGroupBuffs, + EnableCrossZoneTargetBuffs, + PlayerSpellSaveStateWaitInterval, + PlayerSpellSaveStateCap, + RequirePreviousTierScribe, + CureSpellID, + CureCurseSpellID, + CureNoxiousSpellID, + CureMagicSpellID, + CureTraumaSpellID, + CureArcaneSpellID, + MinistrationSkillID, + MinistrationPowerReductionMax, + MinistrationPowerReductionSkill, + MasterSkillReduceSpellResist, + + /* ZONE TIMERS */ + RegenTimer, + ClientSaveTimer, + ShutdownDelayTimer, + WeatherTimer, + SpawnDeleteTimer, + + GlobalExpansionFlag, + GlobalHolidayFlag, + + DatabaseVersion, + + SkipLootGrayMob, + LootDistributionTime, + DiscordEnabled, + DiscordWebhookURL, + DiscordBotToken, + DiscordChannel, + DiscordListenChan +}; + +class Rule { +public: + Rule(); + Rule(int32 category, int32 type, const char *value, const char *combined); + Rule (Rule *rule_in); + virtual ~Rule(); + + void SetValue(const char *value) {strncpy(this->value, value, sizeof(this->value));} + + int32 GetCategory() {return category;} + int32 GetType() {return type;} + const char * GetValue() {return value;} + const char * GetCombined() {return combined;} + + int8 GetInt8() {return (int8)atoul(value);} + int16 GetInt16() {return (int16)atoul(value);} + int32 GetInt32() {return (int32)atoul(value);} + int64 GetInt64() {return (int64)atoi64(value);} + sint8 GetSInt8() {return (sint8)atoi(value);} + sint16 GetSInt16() {return (sint16)atoi(value);} + sint32 GetSInt32() {return (sint32)atoi(value);} + sint64 GetSInt64() {return (sint64)atoi64(value);} + bool GetBool() {return atoul(value) > 0 ? true : false;} + float GetFloat() {return atof(value);} + char GetChar() {return value[0];} + const char * GetString() {return value;} + +private: + int32 category; + int32 type; + char value[1024]; + char combined[2048]; +}; + +class RuleSet { +public: + RuleSet(); + RuleSet(RuleSet *in_rule_set); + virtual ~RuleSet(); + + void CopyRulesInto(RuleSet *in_rule_set); + + void SetID(int32 id) {this->id = id;} + void SetName(const char *name) {strncpy(this->name, name, sizeof(this->name));} + + int32 GetID() {return id;} + const char *GetName() {return name;} + + void AddRule(Rule *rule); + Rule * GetRule(int32 category, int32 type); + Rule * GetRule(const char *category, const char *type); + void ClearRules(); + + map > * GetRules() {return &rules;} + +private: + int32 id; + char name[64]; + Mutex m_rules; + map > rules; +}; + +class RuleManager { +public: + RuleManager(); + virtual ~RuleManager(); + + void Init(); + void Flush(bool reinit=false); + + void LoadCodedDefaultsIntoRuleSet(RuleSet *rule_set); + + bool AddRuleSet(RuleSet *rule_set); + int32 GetNumRuleSets(); + void ClearRuleSets(); + + Rule * GetBlankRule() {return &blank_rule;} + + bool SetGlobalRuleSet(int32 rule_set_id); + Rule * GetGlobalRule(int32 category, int32 type); + Rule * GetGlobalRule(const char* category, const char* type); + + bool SetZoneRuleSet(int32 zone_id, int32 rule_set_id); + Rule * GetZoneRule(int32 zone_id, int32 category, int32 type); + void ClearZoneRuleSets(); + + RuleSet * GetGlobalRuleSet() {return &global_rule_set;} + map > * GetRules() {return &rules;} + +private: + Mutex m_rule_sets; + Mutex m_global_rule_set; + Mutex m_zone_rule_sets; + Rule blank_rule; /* READ ONLY */ + map > rules; /* all of the rules loaded with their defaults (FROM THE CODE). map> */ + map rule_sets; /* all of the possible rule sets from the database. map */ + RuleSet global_rule_set; /* the global rule set, first fill it the defaults from the code, then over ride from the database */ + map zone_rule_sets; /* references to a zone's rule set. map */ +}; + +#endif \ No newline at end of file diff --git a/source/WorldServer/Rules/RulesDB.cpp b/source/WorldServer/Rules/RulesDB.cpp new file mode 100644 index 0000000..c5c57f7 --- /dev/null +++ b/source/WorldServer/Rules/RulesDB.cpp @@ -0,0 +1,109 @@ +/* + 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 . +*/ + +#include +#include "../../common/Log.h" +#include "../WorldDatabase.h" + +extern RuleManager rule_manager; + +void WorldDatabase::LoadGlobalRuleSet() { + Query query; + MYSQL_ROW row; + MYSQL_RES *res; + int32 rule_set_id = 0; + + res = query.RunQuery2(Q_SELECT, "SELECT `variable_value`\n" + "FROM `variables`\n" + "WHERE `variable_name`='default_ruleset_id'"); + if (res && (row = mysql_fetch_row(res))) + { + rule_set_id = atoul(row[0]); + LogWrite(RULESYS__DEBUG, 5, "Rules", "\t\tLoading Global Ruleset id %i", rule_set_id); + } + + if (rule_set_id > 0 && !rule_manager.SetGlobalRuleSet(rule_set_id)) + LogWrite(RULESYS__ERROR, 0, "Rules", "Error loading global rule set. A rule set with ID %u does not exist.", rule_set_id); + else if(rule_set_id == 0) + LogWrite(RULESYS__ERROR, 0, "Rules", "Variables table is missing default_ruleset_id variable name, this means the global rules will be code-default, database entries not used. Use query such as \"insert into variables set variable_name='default_ruleset_id',variable_value='1',comment='Default ruleset';\" to resolve."); + +} + +void WorldDatabase::LoadRuleSets(bool reload) { + RuleSet *rule_set; + Query query; + MYSQL_ROW row; + MYSQL_RES *res; + + if (reload) + rule_manager.Flush(true); + + /* first load the coded defaults in */ + rule_manager.LoadCodedDefaultsIntoRuleSet(rule_manager.GetGlobalRuleSet()); + + res = query.RunQuery2(Q_SELECT, "SELECT `ruleset_id`,`ruleset_name`\n" + "FROM `rulesets`\n" + "WHERE `ruleset_active`>0"); + if (res) { + while ((row = mysql_fetch_row(res))) { + rule_set = new RuleSet(); + rule_set->SetID(atoul(row[0])); + rule_set->SetName(row[1]); + if (rule_manager.AddRuleSet(rule_set)) + { + LogWrite(RULESYS__DEBUG, 5, "Rules", "\t\tLoading rule set '%s' (%u)", rule_set->GetName(), rule_set->GetID()); + LoadRuleSetDetails(rule_set); + } + else { + LogWrite(RULESYS__ERROR, 0, "Rules", "Unable to add rule set '%s'. A ruleset with ID %u already exists.", rule_set->GetName(), rule_set->GetID()); + safe_delete(rule_set); + } + } + } + + LogWrite(RULESYS__DEBUG, 3, "Rules", "--Loaded %u Rule Sets", rule_manager.GetNumRuleSets()); + + LoadGlobalRuleSet(); +} + +void WorldDatabase::LoadRuleSetDetails(RuleSet *rule_set) { + Rule *rule; + Query query; + MYSQL_ROW row; + MYSQL_RES *res; + + assert(rule_set); + + rule_set->CopyRulesInto(rule_manager.GetGlobalRuleSet()); + res = query.RunQuery2(Q_SELECT, "SELECT `rule_category`,`rule_type`,`rule_value`\n" + "FROM `ruleset_details`\n" + "WHERE `ruleset_id`=%u", + rule_set->GetID()); + if (res) { + while ((row = mysql_fetch_row(res))) { + if (!(rule = rule_set->GetRule(row[0], row[1]))) { + LogWrite(RULESYS__WARNING, 0, "Rules", "Unknown rule with category '%s' and type '%s'", row[0], row[1]); + continue; + } + LogWrite(RULESYS__DEBUG, 5, "Rules", "---Setting rule category '%s', type '%s' to value: %s", row[0], row[1], row[2]); + rule->SetValue(row[2]); + } + } +} diff --git a/source/WorldServer/Sign.cpp b/source/WorldServer/Sign.cpp new file mode 100644 index 0000000..66b7ec1 --- /dev/null +++ b/source/WorldServer/Sign.cpp @@ -0,0 +1,319 @@ +/* +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 . +*/ +#include "Sign.h" + +#include "../common/ConfigReader.h" +#include "WorldDatabase.h" +#include "World.h" +#include "../common/Log.h" + +extern World world; +extern ConfigReader configReader; +extern WorldDatabase database; +extern ZoneList zone_list; +extern MasterSpellList master_spell_list; + +Sign::Sign(){ + widget_id = 0; + widget_x = 0; + widget_y = 0; + widget_z = 0; + appearance.pos.state = 1; + appearance.difficulty = 0; + spawn_type = 2; + appearance.activity_status = 64; + sign_type = 0; + zone_x = 0; + zone_y = 0; + zone_z = 0; + zone_heading = 0; + sign_distance = 0; + include_location = false; + include_heading = false; + zone_id = 0; + language = 0; +} + +Sign::~Sign(){ + +} + +int32 Sign::GetWidgetID(){ + return widget_id; +} + +EQ2Packet* Sign::serialize(Player* player, int16 version){ + return spawn_serialize(player, version); +} + +void Sign::SetWidgetID(int32 val){ + widget_id = val; +} + +void Sign::SetWidgetX(float val){ + widget_x = val; +} + +float Sign::GetWidgetX(){ + return widget_x; +} + +void Sign::SetWidgetY(float val){ + widget_y = val; +} + +float Sign::GetWidgetY(){ + return widget_y; +} + +void Sign::SetWidgetZ(float val){ + widget_z = val; +} + +float Sign::GetWidgetZ(){ + return widget_z; +} + +void Sign::SetSignIcon(int8 val){ + appearance.icon = val; +} + +void Sign::SetIncludeLocation(bool val){ + include_location = val; +} +bool Sign::GetIncludeLocation(){ + return include_location; +} +void Sign::SetIncludeHeading(bool val){ + include_heading = val; +} +bool Sign::GetIncludeHeading(){ + return include_heading; +} + +Sign* Sign::Copy(){ + Sign* new_spawn = new Sign(); + if(GetSizeOffset() > 0){ + int8 offset = GetSizeOffset()+1; + sint32 tmp_size = size + (rand()%offset - rand()%offset); + if(tmp_size < 0) + tmp_size = 1; + else if(tmp_size >= 0xFFFF) + tmp_size = 0xFFFF; + new_spawn->size = (int16)tmp_size; + } + else + new_spawn->size = size; + new_spawn->SetCollector(IsCollector()); + new_spawn->SetMerchantID(merchant_id); + new_spawn->SetMerchantType(merchant_type); + new_spawn->SetMerchantLevelRange(GetMerchantMinLevel(), GetMerchantMaxLevel()); + new_spawn->SetPrimaryCommands(&primary_command_list); + new_spawn->primary_command_list_id = primary_command_list_id; + new_spawn->SetSecondaryCommands(&secondary_command_list); + new_spawn->secondary_command_list_id = secondary_command_list_id; + new_spawn->database_id = database_id; + memcpy(&new_spawn->appearance, &appearance, sizeof(AppearanceData)); + new_spawn->SetWidgetID(widget_id); + new_spawn->SetWidgetX(widget_x); + new_spawn->SetWidgetY(widget_y); + new_spawn->SetWidgetZ(widget_z); + new_spawn->SetSignType(sign_type); + new_spawn->SetSignZoneX(zone_x); + new_spawn->SetSignZoneY(zone_y); + new_spawn->SetSignZoneZ(zone_z); + new_spawn->SetSignZoneHeading(zone_heading); + new_spawn->SetSignZoneID(GetSignZoneID()); + new_spawn->SetSignTitle(GetSignTitle()); + new_spawn->SetSignDescription(GetSignDescription()); + new_spawn->SetSignDistance(sign_distance); + new_spawn->SetIncludeHeading(include_heading); + new_spawn->SetIncludeLocation(include_location); + new_spawn->SetTransporterID(GetTransporterID()); + new_spawn->SetSoundsDisabled(IsSoundsDisabled()); + new_spawn->SetOmittedByDBFlag(IsOmittedByDBFlag()); + new_spawn->SetLootTier(GetLootTier()); + new_spawn->SetLootDropType(GetLootDropType()); + new_spawn->SetLanguage(GetLanguage()); + return new_spawn; +} + +int32 Sign::GetSignZoneID(){ + return zone_id; +} + +void Sign::SetSignZoneID(int32 val){ + zone_id = val; +} + +const char* Sign::GetSignTitle(){ + if(title.length() > 0) + return title.c_str(); + else + return 0; +} + +void Sign::SetSignTitle(const char* val){ + if(val) + title = string(val); +} + +const char* Sign::GetSignDescription(){ + if(description.length() > 0) + return description.c_str(); + else + return 0; +} + +void Sign::SetSignDescription(const char* val){ + if(val) + description = string(val); +} + +int8 Sign::GetSignType(){ + return sign_type; +} + +void Sign::SetSignType(int8 val){ + sign_type = val; +} + +float Sign::GetSignZoneX(){ + return zone_x; +} +void Sign::SetSignZoneX(float val){ + zone_x = val; +} +float Sign::GetSignZoneY(){ + return zone_y; +} +void Sign::SetSignZoneY(float val){ + zone_y = val; +} +float Sign::GetSignZoneZ(){ + return zone_z; +} +void Sign::SetSignZoneZ(float val){ + zone_z = val; +} +float Sign::GetSignZoneHeading(){ + return zone_heading; +} +void Sign::SetSignZoneHeading(float val){ + zone_heading = val; +} +float Sign::GetSignDistance(){ + return sign_distance; +} +void Sign::SetSignDistance(float val){ + sign_distance = val; +} +void Sign::HandleUse(Client* client, string command) +{ + vector destinations; + + //The following check disables the use of doors and other widgets if the player does not meet the quest requirements + //If this is from a script ignore this check (client will be null) + if (client) { + bool meets_quest_reqs = MeetsSpawnAccessRequirements(client->GetPlayer()); + if (!meets_quest_reqs && (GetQuestsRequiredOverride() & 2) == 0) + return; + else if (meets_quest_reqs && appearance.show_command_icon != 1) + return; + } + + if( GetTransporterID() > 0 ) + GetZone()->GetTransporters(&destinations, client, GetTransporterID()); + + if( destinations.size() ) + { + client->SetTemporaryTransportID(0); + client->ProcessTeleport(this, &destinations, GetTransporterID()); + } + else if( sign_type == SIGN_TYPE_ZONE && GetSignZoneID() > 0 ) + { + if( GetSignDistance() == 0 || client->GetPlayer()->GetDistance(this) <= GetSignDistance() ) + { + string name = database.GetZoneName(GetSignZoneID()); + if( name.length() >0 ) + { + if( !client->CheckZoneAccess(name.c_str()) ) + return; + + // determine if the coordinates should be set (returns false if they should) + // clearer, if the sign has x,y,z,heading coordinates, use them otherwise I assume we use the zones safe coords(?) + bool zone_coords_invalid = ( zone_x == 0 && zone_y == 0 && zone_z == 0 && zone_heading == 0 ); + + // I really hate double-negatives. Seriously? + if ( !zone_coords_invalid ) + { + LogWrite(SIGN__DEBUG, 0, "Sign", "Sign has valid zone-to coordinates (%.2f, %.2f, %.2f, %.2f)", zone_x, zone_y, zone_z, zone_heading); + client->GetPlayer()->SetX(zone_x); + client->GetPlayer()->SetY(zone_y); + client->GetPlayer()->SetZ(zone_z); + client->GetPlayer()->SetHeading(zone_heading); + } + else // alert client we couldnt set the coordinates + { + LogWrite(SIGN__WARNING, 0, "Sign", "Sign has no zone-to coordinates set, using zones safe coords."); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Invalid zone in coords, taking you to a safe point."); + } + + // Test if where we're going is an Instanced zone + if ( !client->TryZoneInstance(GetSignZoneID(), zone_coords_invalid) ) + { + LogWrite(SIGN__DEBUG, 0, "Sign", "Sending client to instance of zone_id: %u", GetSignZoneID()); + client->Zone(name.c_str(), zone_coords_invalid); + } + } + else + { + LogWrite(SIGN__WARNING, 0, "Sign", "Unable to find zone with ID: %u", GetSignZoneID()); + client->Message(CHANNEL_COLOR_YELLOW, "Unable to find zone with ID: %u", GetSignZoneID()); + } + } + else + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You are too far away!"); + } + } + + else if (client && command.length() > 0) + { + EntityCommand* entity_command = FindEntityCommand(command); + + + //devn00b: Add support for marking objects + if (entity_command && strcmp(entity_command->command.c_str(), "mark") == 0) { + LogWrite(SIGN__DEBUG, 0, "Sign", "ActivateMarkReqested Sign - Command: '%s' (Should read mark)", entity_command->command.c_str()); + int32 char_id = client->GetCharacterID(); + database.SaveSignMark(char_id, GetWidgetID(), database.GetCharacterName(char_id), client); + return; + } + + if (entity_command) + { + LogWrite(SIGN__DEBUG, 0, "Sign", "ActivateQuestRequired Sign - Command: '%s'", entity_command->command.c_str()); + client->GetCurrentZone()->ProcessEntityCommand(entity_command, client->GetPlayer(), client->GetPlayer()->GetTarget()); + } + + } + +} diff --git a/source/WorldServer/Sign.h b/source/WorldServer/Sign.h new file mode 100644 index 0000000..d202bf5 --- /dev/null +++ b/source/WorldServer/Sign.h @@ -0,0 +1,91 @@ +/* + 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 . +*/ + +#ifndef __EQ2_SIGN__ +#define __EQ2_SIGN__ +#include "Spawn.h" + +#define SIGN_TYPE_GENERIC 0 +#define SIGN_TYPE_ZONE 1 + +using namespace std; + +class Sign : public Spawn{ +public: + Sign(); + virtual ~Sign(); + bool IsSign(){ return true; } + int32 GetWidgetID(); + void SetWidgetID(int32 val); + void SetWidgetX(float val); + float GetWidgetX(); + void SetWidgetY(float val); + float GetWidgetY(); + void SetWidgetZ(float val); + float GetWidgetZ(); + void SetSignIcon(int8 val); + Sign* Copy(); + EQ2Packet* serialize(Player *player, int16 version); + void HandleUse(Client* client, string command); + int8 GetSignType(); + void SetSignType(int8 val); + float GetSignZoneX(); + void SetSignZoneX(float val); + float GetSignZoneY(); + void SetSignZoneY(float val); + float GetSignZoneZ(); + void SetSignZoneZ(float val); + float GetSignZoneHeading(); + void SetSignZoneHeading(float val); + float GetSignDistance(); + void SetSignDistance(float val); + int32 GetSignZoneID(); + void SetSignZoneID(int32 val); + const char* GetSignTitle(); + void SetSignTitle(const char* val); + const char* GetSignDescription(); + void SetSignDescription(const char* val); + void SetIncludeLocation(bool val); + bool GetIncludeLocation(); + void SetIncludeHeading(bool val); + bool GetIncludeHeading(); + void SetLanguage(int8 in_language) { language = in_language; } + int8 GetLanguage() { return language; } + +private: + string description; + string title; + int8 sign_type; + float widget_x; + float widget_y; + float widget_z; + int32 widget_id; + float zone_x; + float zone_y; + float zone_z; + float zone_heading; + int32 zone_id; + float sign_distance; + bool include_location; + bool include_heading; + int8 language; +}; + +#endif diff --git a/source/WorldServer/Skills.cpp b/source/WorldServer/Skills.cpp new file mode 100644 index 0000000..7bffcf9 --- /dev/null +++ b/source/WorldServer/Skills.cpp @@ -0,0 +1,591 @@ +/* + 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 . +*/ +#include "Skills.h" +#include "Spawn.h" +#include "LuaInterface.h" +#include "../common/Log.h" + +extern ConfigReader configReader; +extern LuaInterface* lua_interface; + +MasterSkillList::MasterSkillList(){ +} + +MasterSkillList::~MasterSkillList(){ + map::iterator itr; + for(itr = skills.begin(); itr != skills.end(); itr++){ + safe_delete(itr->second); + } + map::iterator itr2; + for(itr2 = populate_packets.begin(); itr2 != populate_packets.end(); itr2++){ + safe_delete(itr2->second); + } +} + +Skill::Skill(){ + skill_id = 0; + current_val = 0; + previous_val = 0; + max_val = 0; + skill_type = 0; + display = 0; + save_needed = false; + active_skill = true; +} + +Skill::Skill(Skill* skill){ + skill_id = skill->skill_id; + current_val = skill->current_val; + previous_val = skill->current_val; + max_val = skill->max_val; + skill_type = skill->skill_type; + display = skill->display; + short_name = skill->short_name; + name = skill->name; + description = skill->description; + save_needed = false; + active_skill = true; +} + +map* MasterSkillList::GetAllSkills(){ + return &skills; +} + +Skill* MasterSkillList::GetSkill(int32 skill_id){ + if(skills.count(skill_id) > 0) + return skills[skill_id]; + else + return 0; +} + +Skill* MasterSkillList::GetSkillByName(const char* skill_name) { + Skill* skill = 0; + map::iterator itr; + for (itr = skills.begin(); itr != skills.end(); itr++) { + Skill* current_skill = itr->second; + if (::ToLower(string(current_skill->name.data.c_str())) == ::ToLower(string(skill_name))) { + skill = current_skill; + break; + } + } + return skill; +} + +int16 MasterSkillList::GetSkillCount(){ + return skills.size(); +} + +void MasterSkillList::AddSkill(Skill* skill){ + if(skill) + skills[skill->skill_id] = skill; +} + +EQ2Packet* MasterSkillList::GetPopulateSkillsPacket(int16 version){ + EQ2Packet* ret = 0; + int16 packet_version = configReader.GetStructVersion("WS_SkillMap", version); + if(populate_packets.count(packet_version) > 0) + ret = populate_packets[packet_version]; + else{ + PacketStruct* packet = configReader.getStruct("WS_SkillMap", packet_version); + int32 count = skills.size(); + Skill* skill = 0; + int32 i = 0; + packet->setArrayLengthByName("skill_count", count); + map::iterator itr; + for(itr = skills.begin(); itr != skills.end(); itr++, i++){ + skill = itr->second; + packet->setArrayDataByName("skill_id", skill->skill_id, i); + packet->setArrayDataByName("short_name", &skill->short_name, i); + packet->setArrayDataByName("name", &skill->name, i); + packet->setArrayDataByName("description", &skill->description, i); + } + populate_packets[packet_version] = packet->serialize(); + safe_delete(packet); + ret = populate_packets[packet_version]; + } + if(ret) + return ret->Copy(); //need to return a copy as the packet is deleted after the client confirms it and we want to keep the packet to prevent constant generation of the same data + else + return ret; +} + +PlayerSkillList::PlayerSkillList(){ + xor_packet = 0; + orig_packet = 0; + packet_count = 0; + has_updates = false; + MSkillUpdates.SetName("PlayerSkillList::MSkillUpdates"); +} + +PlayerSkillList::~PlayerSkillList(){ + map::iterator itr; + for(itr = skills.begin(); itr != skills.end(); itr++){ + safe_delete(itr->second); + } + MutexMap::iterator sb_itr = skill_bonus_list.begin(); + while (sb_itr.Next()) + RemoveSkillBonus(sb_itr.first); + safe_delete_array(xor_packet); + safe_delete_array(orig_packet); +} + +void PlayerSkillList::AddSkill(Skill* new_skill){ + std::unique_lock lock(MPlayerSkills); + Skill* tmpSkill = nullptr; + if(skills.count(new_skill->skill_id)) { + tmpSkill = skills[new_skill->skill_id]; + } + skills[new_skill->skill_id] = new_skill; + if(tmpSkill) { + lua_interface->SetLuaUserDataStale(tmpSkill); + safe_delete(tmpSkill); + } + name_skill_map.clear(); +} + +void PlayerSkillList::RemoveSkill(Skill* skill) { + std::unique_lock lock(MPlayerSkills); + if (skill) { + lua_interface->SetLuaUserDataStale(skill); + skill->active_skill = false; + name_skill_map.clear(); + } +} + +map* PlayerSkillList::GetAllSkills(){ + return &skills; +} + +void PlayerSkillList::SetSkillValuesByType(int8 type, int16 value, bool send_update) { + map::iterator itr; + for (itr = skills.begin(); itr != skills.end(); itr++) { + if (itr->second && itr->second->skill_type == type) + SetSkill(itr->second, value, send_update); + } +} + +void PlayerSkillList::SetSkillCapsByType(int8 type, int16 value){ + map::iterator itr; + for(itr = skills.begin(); itr != skills.end(); itr++){ + if(itr->second && itr->second->skill_type == type) + SetSkillCap(itr->second, value); + } +} + +void PlayerSkillList::IncreaseSkillCapsByType(int8 type, int16 value){ + map::iterator itr; + for(itr = skills.begin(); itr != skills.end(); itr++){ + if(itr->second && itr->second->skill_type == type) + IncreaseSkillCap(itr->second, value); + } +} + +void PlayerSkillList::IncreaseAllSkillCaps(int16 value){ + map::iterator itr; + for(itr = skills.begin(); itr != skills.end(); itr++){ + IncreaseSkillCap(itr->second, value); + } +} + +bool PlayerSkillList::HasSkill(int32 skill_id){ + std::shared_lock lock(MPlayerSkills); + return (skills.count(skill_id) > 0 && skills[skill_id]->active_skill); +} + +Skill* PlayerSkillList::GetSkill(int32 skill_id){ + std::shared_lock lock(MPlayerSkills); + if(skills.count(skill_id) > 0 && skills[skill_id]->active_skill) + return skills[skill_id]; + else + return 0; +} + +void PlayerSkillList::IncreaseSkill(Skill* skill, int16 amount){ + if(skill){ + skill->previous_val = skill->current_val; + skill->current_val += amount; + if(skill->current_val > skill->max_val) + skill->max_val = skill->current_val; + AddSkillUpdateNeeded(skill); + skill->save_needed = true; + } +} + +void PlayerSkillList::IncreaseSkill(int32 skill_id, int16 amount){ + IncreaseSkill(GetSkill(skill_id), amount); +} + +void PlayerSkillList::DecreaseSkill(Skill* skill, int16 amount){ + if(skill){ + skill->previous_val = skill->current_val; + if((skill->current_val - amount) < 0) + skill->current_val = 0; + else + skill->current_val -= amount; + skill->save_needed = true; + AddSkillUpdateNeeded(skill); + } +} + +void PlayerSkillList::DecreaseSkill(int32 skill_id, int16 amount){ + DecreaseSkill(GetSkill(skill_id), amount); +} + +void PlayerSkillList::SetSkill(Skill* skill, int16 value, bool send_update){ + if(skill){ + skill->previous_val = skill->current_val; + skill->current_val = value; + if(skill->current_val > skill->max_val) + skill->max_val = skill->current_val; + skill->save_needed = true; + if(send_update) + AddSkillUpdateNeeded(skill); + } +} + +void PlayerSkillList::SetSkill(int32 skill_id, int16 value, bool send_update){ + SetSkill(GetSkill(skill_id), value, send_update); +} + +void PlayerSkillList::IncreaseSkillCap(Skill* skill, int16 amount){ + if(skill){ + skill->max_val += amount; + skill->save_needed = true; + } +} + +void PlayerSkillList::IncreaseSkillCap(int32 skill_id, int16 amount){ + IncreaseSkillCap(GetSkill(skill_id), amount); +} + +void PlayerSkillList::DecreaseSkillCap(Skill* skill, int16 amount){ + if(skill){ + if((skill->max_val - amount) < 0) + skill->max_val = 0; + else + skill->max_val -= amount; + if(skill->current_val > skill->max_val){ + skill->previous_val = skill->current_val; + skill->current_val = skill->max_val; + } + AddSkillUpdateNeeded(skill); + skill->save_needed = true; + } +} + +void PlayerSkillList::DecreaseSkillCap(int32 skill_id, int16 amount){ + DecreaseSkillCap(GetSkill(skill_id), amount); +} + +void PlayerSkillList::SetSkillCap(Skill* skill, int16 value){ + if(skill){ + skill->max_val = value; + if(skill->current_val > skill->max_val){ + skill->previous_val = skill->current_val; + skill->current_val = skill->max_val; + } + AddSkillUpdateNeeded(skill); + skill->save_needed = true; + } +} + +void PlayerSkillList::SetSkillCap(int32 skill_id, int16 value){ + SetSkillCap(GetSkill(skill_id), value); +} + +int16 PlayerSkillList::CalculateSkillValue(int32 skill_id, int16 current_val){ + if (current_val > 5) { + int16 new_val = current_val; + MutexMap::iterator itr = skill_bonus_list.begin(); + while (itr.Next()) { + SkillBonus* sb = itr.second; + map::iterator sbv_itr; + for (sbv_itr = sb->skills.begin(); sbv_itr != sb->skills.end(); sbv_itr++) { + SkillBonusValue* sbv = sbv_itr->second; + if (sbv->skill_id == skill_id) + new_val += (int16)sbv->value; + } + } + return new_val; + } + + return current_val; +} + +int16 PlayerSkillList::CalculateSkillMaxValue(int32 skill_id, int16 max_val) { + int16 new_val = max_val; + MutexMap::iterator itr = skill_bonus_list.begin(); + while (itr.Next()) { + SkillBonus* sb = itr->second; + map::iterator sbv_itr; + for (sbv_itr = sb->skills.begin(); sbv_itr != sb->skills.end(); sbv_itr++) { + SkillBonusValue* sbv = sbv_itr->second; + if (sbv->skill_id == skill_id) + new_val += (int16)sbv->value; + } + } + return new_val; +} + +EQ2Packet* PlayerSkillList::GetSkillPacket(int16 version){ + std::unique_lock lock(MPlayerSkills); + PacketStruct* packet = configReader.getStruct("WS_UpdateSkillBook", version); + if(packet){ + int16 skill_count = 0; + map::iterator itr; + for (itr = skills.begin(); itr != skills.end(); itr++) { + if (itr->second && itr->second->active_skill) + skill_count++; + } + int16 size = 0; + if (version > 561) { + size = 21 * skill_count + 8; + } + else if (version < 373) { + size = 12 * skill_count + 6; + } + else if (version <= 373) { + size = 15 * skill_count + 6; + } + else if (version <= 561) { + size = 21 * skill_count + 7; + } + + if (skill_count > packet_count) { + uchar* tmp = 0; + if (orig_packet) { + tmp = new uchar[size]; + memset(tmp, 0, size); + memcpy(tmp, orig_packet, orig_packet_size); + safe_delete_array(orig_packet); + safe_delete_array(xor_packet); + orig_packet = tmp; + } + else { + orig_packet = new uchar[size]; + memset(orig_packet, 0, size); + } + xor_packet = new uchar[size]; + memset(xor_packet, 0, size); + } + packet_count = skill_count; + orig_packet_size = size; + packet->setArrayLengthByName("skill_count", skill_count); + Skill* skill = 0; + int32 i=0; + for(itr = skills.begin(); itr != skills.end(); itr++){ + skill = itr->second; + if(skill && skill->active_skill){ + int16 skill_max_with_bonuses = CalculateSkillMaxValue(skill->skill_id, skill->max_val); + int16 skill_with_bonuses = int(CalculateSkillValue(skill->skill_id, skill->current_val)); + packet->setArrayDataByName("skill_id", skill->skill_id, i); + if (version <= 561 && skill->skill_type >= SKILL_TYPE_GENERAL) { //covert it to DOF types + packet->setArrayDataByName("type", skill->skill_type-2, i); + } + else if(version >= 60085 && skill->skill_type >= 12) { + packet->setArrayDataByName("type", skill->skill_type-1, i); + } + else { + packet->setArrayDataByName("type", skill->skill_type, i); + } + + int16 current_val = skill->current_val; + + if(skill->skill_type == SKILL_TYPE_LANGUAGE) { // 13 is language in the DB?? 14 is the skill type though + packet->setArrayDataByName("language_unknown", skill->skill_id, i); + packet->setArrayDataByName("display_maxval", 1, i); + packet->setArrayDataByName("max_val", 1, i); + } + else { + packet->setArrayDataByName("max_val", skill->max_val, i); + packet->setArrayDataByName("display_minval", skill->display, i); + packet->setArrayDataByName("display_maxval", skill->display, i); + packet->setArrayDataByName("skill_delta", 0, i);// skill_with_bonuses- skill->current_val + packet->setArrayDataByName("skill_delta2", skill_max_with_bonuses - skill->max_val, i);// skill_max_with_bonuses - skill->max_val, i); + } + + packet->setArrayDataByName("current_val", current_val, i); + packet->setArrayDataByName("base_val", current_val, i); + i++; + } + } + int8 offset = 1; + if (version <= 373) + offset = 0; + EQ2Packet* ret = packet->serializeCountPacket(version, offset, orig_packet, xor_packet); + //packet->PrintPacket(); + //DumpPacket(orig_packet, orig_packet_size); + //DumpPacket(ret); + safe_delete(packet); + return ret; + } + return 0; +} + +bool PlayerSkillList::CheckSkillIncrease(Skill* skill){ + if(!skill || skill->current_val >= skill->max_val) + return false; + // Assuming that skills will be used more at higher levels, increase chances are: + // skill val of 1 ~ 20% chance, value of 100 ~ 10%, value of 400 ~ 4% + int8 percent = (int8)(((float)((float)100/(float)(50 + skill->current_val)))*10); + if(rand()%100 < percent){ // skill increase + IncreaseSkill(skill, 1); + return true; + } + else + return false; +} + +Skill* PlayerSkillList::GetSkillByName(const char* name){ + std::shared_lock lock(MPlayerSkills); + if(name_skill_map.size() == 0){ + map::iterator itr; + Skill* skill = 0; + for(itr = skills.begin(); itr != skills.end(); itr++){ + skill = itr->second; + name_skill_map[skill->name.data] = skill; + } + } + if(name_skill_map.count(name) > 0) + return name_skill_map[name]; + else + return 0; +} + +vector* PlayerSkillList::GetSaveNeededSkills(){ + std::shared_lock lock(MPlayerSkills); + vector* ret = new vector; + map::iterator itr; + for(itr = skills.begin(); itr != skills.end(); itr++){ + if(itr->second->save_needed){ + ret->push_back(itr->second); + itr->second->save_needed = false; + } + } + return ret; +} + +void PlayerSkillList::AddSkillUpdateNeeded(Skill* skill){ + MSkillUpdates.writelock(__FUNCTION__, __LINE__); + skill_updates.push_back(skill); + has_updates = true; + MSkillUpdates.releasewritelock(__FUNCTION__, __LINE__); +} + +vector* PlayerSkillList::GetSkillUpdates(){ + vector* ret = 0; + vector::iterator itr; + MSkillUpdates.writelock(__FUNCTION__, __LINE__); + if(skill_updates.size() > 0){ + ret = new vector(); + ret->insert(ret->begin(), skill_updates.begin(), skill_updates.end()); + skill_updates.clear(); + } + has_updates = false; + MSkillUpdates.releasewritelock(__FUNCTION__, __LINE__); + return ret; +} + +bool PlayerSkillList::HasSkillUpdates(){ + return has_updates; +} + +void PlayerSkillList::AddSkillBonus(int32 spell_id, int32 skill_id, float value) { + if (value != 0) { + SkillBonus* sb; + if (skill_bonus_list.count(spell_id) == 0) { + sb = new SkillBonus; + sb->spell_id = spell_id; + skill_bonus_list.Put(spell_id, sb); + } + else + sb = skill_bonus_list.Get(spell_id); + + if (sb->skills[skill_id] == nullptr) { + SkillBonusValue* sbv = new SkillBonusValue; + sbv->skill_id = skill_id; + sbv->value = value; + sb->skills[skill_id] = sbv; + } + } +} + +void PlayerSkillList::ResetPackets() { + std::unique_lock lock(MPlayerSkills); + safe_delete_array(orig_packet); + safe_delete_array(xor_packet); + orig_packet_size = 0; + orig_packet = 0; + xor_packet = 0; + packet_count = 0; +} + +SkillBonus* PlayerSkillList::GetSkillBonus(int32 spell_id) { + SkillBonus *ret = 0; + + if (skill_bonus_list.count(spell_id) > 0) + ret = skill_bonus_list.Get(spell_id); + + return ret; +} + +void PlayerSkillList::RemoveSkillBonus(int32 spell_id) { + if (skill_bonus_list.count(spell_id) > 0) { + SkillBonus* sb = skill_bonus_list.Get(spell_id); + skill_bonus_list.erase(spell_id); + map::iterator itr; + for (itr = sb->skills.begin(); itr != sb->skills.end(); itr++) + safe_delete(itr->second); + safe_delete(sb); + } +} + +int Skill::CheckDisarmSkill(int16 targetLevel, int8 chest_difficulty) +{ + if (chest_difficulty < 2) // no triggers on this chest type + return 1; + + if (targetLevel < 1) + targetLevel = 1; + + int chest_diff_result = targetLevel * chest_difficulty; + float base_difficulty = 15.0f; + float fail_threshold = 10.0f; + + + float chance = ((100.0f - base_difficulty) * ((float)current_val / (float)chest_diff_result)); + + if (chance > (100.0f - base_difficulty)) + { + chance = 100.0f - base_difficulty; + } + + float d100 = (float)MakeRandomFloat(0, 100); + + if (d100 <= chance) + return 1; + else + { + if (d100 > (chance + fail_threshold)) + return -1; + } + + return 0; +} \ No newline at end of file diff --git a/source/WorldServer/Skills.h b/source/WorldServer/Skills.h new file mode 100644 index 0000000..7d59f1f --- /dev/null +++ b/source/WorldServer/Skills.h @@ -0,0 +1,180 @@ +/* + 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 . +*/ +#ifndef __EQ2_SKILLS_H__ +#define __EQ2_SKILLS_H__ + +#include +#include +#include + +#include "../common/ConfigReader.h" +#include "../common/types.h" +#include "MutexMap.h" + +#define SKILL_TYPE_WEAPONRY 1 +#define SKILL_TYPE_SPELLCASTING 2 +#define SKILL_TYPE_AVOIDANCE 3 +#define SKILL_TYPE_ARMOR 4 +#define SKILL_TYPE_SHIELD 5 +#define SKILL_TYPE_HARVESTING 6 +#define SKILL_TYPE_ARTISAN 7 +#define SKILL_TYPE_CRAFTSMAN 8 +#define SKILL_TYPE_OUTFITTER 9 +#define SKILL_TYPE_SCHOLAR 10 +#define SKILL_TYPE_GENERAL 13 +#define SKILL_TYPE_LANGUAGE 14 +#define SKILL_TYPE_CLASS 15 +#define SKILL_TYPE_COMBAT 16 +#define SKILL_TYPE_WEAPON 17 +#define SKILL_TYPE_TSKNOWLEDGE 18 + +#define SKILL_TYPE_GENERAL_DOF 11 +#define SKILL_TYPE_LANGUAGE_DOF 12 +#define SKILL_TYPE_CLASS_DOF 13 +#define SKILL_TYPE_COMBAT_DOF 14 +#define SKILL_TYPE_WEAPON_DOF 15 +#define SKILL_TYPE_TSKNOWLEDGE_DOF 16 + +#define SKILL_ID_SCULPTING 1039865549 +#define SKILL_ID_ARTISTRY 3881305672 +#define SKILL_ID_FLETCHING 3076004370 +#define SKILL_ID_METALWORKING 4032608519 +#define SKILL_ID_METALSHAPING 3108933728 +#define SKILL_ID_TAILORING 2082133324 +#define SKILL_ID_CHEMISTRY 2557647574 +#define SKILL_ID_ARTIFICING 3330500131 +#define SKILL_ID_SCRIBING 773137566 + +//the following update the current_value to the max_value as soon as the max_value is updated +#define SKILL_ID_DUALWIELD 1852383242 +#define SKILL_ID_FISTS 3177806075 +#define SKILL_ID_DESTROYING 3429135390 +#define SKILL_ID_MAGIC_AFFINITY 2072844078 + +#define SKILL_ID_GREATSWORD 2292577688 // aka 2h slashing +#define SKILL_ID_GREATSPEAR 2380184628 // aka 2h piercing +#define SKILL_ID_STAFF 3180399725 // aka 2h crushing + +/* Each SkillBonus is comprised of multiple possible skill bonus values. This is so one spell can modify + more than one skill */ +struct SkillBonusValue { + int32 skill_id; + float value; +}; + +struct SkillBonus { + int32 spell_id; + map skills; +}; + + +class Skill{ +public: + Skill(); + Skill(Skill* skill); + int32 skill_id; + int16 current_val; + int16 previous_val; + int16 max_val; + int32 skill_type; + int8 display; + EQ2_16BitString short_name; + EQ2_16BitString name; + EQ2_16BitString description; + bool save_needed; + bool active_skill; + int CheckDisarmSkill(int16 targetLevel, int8 chest_difficulty=0); +}; + +class MasterSkillList{ +public: + MasterSkillList(); + ~MasterSkillList(); + void AddSkill(Skill* skill); + int16 GetSkillCount(); + EQ2Packet* GetPopulateSkillsPacket(int16 version); + map* GetAllSkills(); + Skill* GetSkill(int32 skill_id); + Skill* GetSkillByName(const char* skill_name); + +private: + map skills; + map populate_packets; +}; + +class PlayerSkillList{ +public: + PlayerSkillList(); + ~PlayerSkillList(); + void RemoveSkill(Skill* skill); + void AddSkill(Skill* new_skill); + bool CheckSkillIncrease(Skill* skill); + Skill* GetSkillByName(const char* name); + bool HasSkill(int32 skill_id); + Skill* GetSkill(int32 skill_id); + + void IncreaseSkill(Skill* skill, int16 amount); + void IncreaseSkill(int32 skill_id, int16 amount); + void DecreaseSkill(Skill* skill, int16 amount); + void DecreaseSkill(int32 skill_id, int16 amount); + void SetSkill(Skill* skill, int16 value, bool send_update = true); + void SetSkill(int32 skill_id, int16 value, bool send_update = true); + + void IncreaseSkillCap(Skill* skill, int16 amount); + void IncreaseSkillCap(int32 skill_id, int16 amount); + void DecreaseSkillCap(Skill* skill, int16 amount); + void DecreaseSkillCap(int32 skill_id, int16 amount); + void SetSkillCap(Skill* skill, int16 value); + void SetSkillCap(int32 skill_id, int16 value); + void IncreaseAllSkillCaps(int16 value); + void IncreaseSkillCapsByType(int8 type, int16 value); + void SetSkillCapsByType(int8 type, int16 value); + void SetSkillValuesByType(int8 type, int16 value, bool send_update = true); + void AddSkillUpdateNeeded(Skill* skill); + + void AddSkillBonus(int32 spell_id, int32 skill_id, float value); + SkillBonus* GetSkillBonus(int32 spell_id); + void RemoveSkillBonus(int32 spell_id); + + int16 CalculateSkillValue(int32 skill_id, int16 current_val); + int16 CalculateSkillMaxValue(int32 skill_id, int16 max_val); + EQ2Packet* GetSkillPacket(int16 version); + vector* GetSaveNeededSkills(); + vector* GetSkillUpdates(); + map* GetAllSkills(); + bool HasSkillUpdates(); + + void ResetPackets(); +private: + volatile bool has_updates; + mutable std::shared_mutex MPlayerSkills; + Mutex MSkillUpdates; + int16 packet_count; + uchar* orig_packet; + int16 orig_packet_size; + uchar* xor_packet; + map skills; + map name_skill_map; + vector skill_updates; + MutexMap skill_bonus_list; +}; + +#endif + diff --git a/source/WorldServer/Spawn.cpp b/source/WorldServer/Spawn.cpp new file mode 100644 index 0000000..4499b3e --- /dev/null +++ b/source/WorldServer/Spawn.cpp @@ -0,0 +1,5316 @@ +/* + 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 . +*/ +#include "Spawn.h" +#include +#include "../common/timer.h" +#include +#include +#include "Entity.h" +#include "Widget.h" +#include "Sign.h" +#include "../common/MiscFunctions.h" +#include "../common/Log.h" +#include "Rules/Rules.h" +#include "World.h" +#include "LuaInterface.h" +#include "Bots/Bot.h" +#include "Zone/raycast_mesh.h" +#include "RaceTypes/RaceTypes.h" +#include "VisualStates.h" + +extern ConfigReader configReader; +extern RuleManager rule_manager; +extern World world; +extern ZoneList zone_list; +extern MasterRaceTypeList race_types_list; +extern LuaInterface* lua_interface; +extern VisualStates visual_states; + +Spawn::Spawn(){ + loot_coins = 0; + trap_triggered = false; + trap_state = 0; + chest_drop_time = 0; + trap_opened_time = 0; + group_id = 0; + size_offset = 0; + merchant_id = 0; + merchant_type = 0; + merchant_min_level = 0; + merchant_max_level = 0; + memset(&appearance, 0, sizeof(AppearanceData)); + memset(&basic_info, 0, sizeof(BasicInfoStruct)); + appearance.pos.state = 0x4080; + appearance.difficulty = 6; + size = 32; + appearance.pos.collision_radius = 32; + id = Spawn::NextID(); + oversized_packet = 0xFF; + zone = 0; + spawn_location_id = 0; + spawn_entry_id = 0; + spawn_location_spawns_id = 0; + respawn = 0; + expire_time = 0; + expire_offset = 0; + x_offset = 0; + y_offset = 0; + z_offset = 0; + database_id = 0; + packet_num = 1; + changed = false; + vis_changed = false; + position_changed = false; + send_spawn_changes = true; + info_changed = false; + appearance.pos.Speed1 = 0; + last_attacker = 0; + faction_id = 0; + running_to = 0; + tmp_visual_state = -1; + tmp_action_state = -1; + transporter_id = 0; + invulnerable = false; + spawn_group_list = 0; + MSpawnGroup = 0; + movement_locations = 0; + target = 0; + primary_command_list_id = 0; + secondary_command_list_id = 0; + is_pet = false; + m_followTarget = 0; + following = false; + req_quests_continued_access = false; + req_quests_override = 0; + req_quests_private = false; + m_illusionModel = 0; + Cell_Info.CurrentCell = nullptr; + Cell_Info.CellListIndex = -1; + m_addedToWorldTimestamp = 0; + m_spawnAnim = 0; + m_spawnAnimLeeway = 0; + m_Update.SetName("Spawn::m_Update"); + m_requiredHistory.SetName("Spawn::m_requiredHistory"); + m_requiredQuests.SetName("Spawn::m_requiredQuests"); + last_heading_angle = 0.0; + last_grid_update = 0; + last_location_update = 0.0; + last_movement_update = Timer::GetCurrentTime2(); + forceMapCheck = false; + m_followDistance = 0; + MCommandMutex.SetName("Entity::MCommandMutex"); + has_spawn_proximities = false; + pickup_item_id = 0; + pickup_unique_item_id = 0; + disable_sounds = false; + has_quests_required = false; + has_history_required = false; + is_flying_creature = false; + is_water_creature = false; + region_map = nullptr; + current_map = nullptr; + RegionMutex.SetName("Spawn::RegionMutex"); + pause_timer.Disable(); + m_SpawnMutex.SetName("Spawn::SpawnMutex"); + appearance_equipment_list.SetAppearanceType(1); + is_transport_spawn = false; + rail_id = 0; + is_omitted_by_db_flag = false; + loot_tier = 0; + loot_drop_type = 0; + deleted_spawn = false; + is_collector = false; + trigger_widget_id = 0; + scared_by_strong_players = false; + is_alive = true; + SetLockedNoLoot(ENCOUNTER_STATE_AVAILABLE); + loot_method = GroupLootMethod::METHOD_FFA; + loot_rarity = 0; + loot_group_id = 0; + looter_spawn_id = 0; + is_loot_complete = false; + is_loot_dispensed = false; + reset_movement = false; + ResetKnockedBack(); +} + +Spawn::~Spawn(){ + is_running = false; + + vector::iterator itr; + for (itr = loot_items.begin(); itr != loot_items.end(); itr++) + safe_delete(*itr); + loot_items.clear(); + + RemovePrimaryCommands(); + + for(int32 i=0;isize()){ + safe_delete(movement_locations->front()); + movement_locations->pop_front(); + } + safe_delete(movement_locations); + } + MMovementLocations.unlock(); + + MMovementLoop.lock(); + for (int32 i = 0; i < movement_loop.size(); i++) + safe_delete(movement_loop.at(i)); + movement_loop.clear(); + MMovementLoop.unlock(); + + m_requiredHistory.writelock(__FUNCTION__, __LINE__); + map::iterator lua_itr; + for (lua_itr = required_history.begin(); lua_itr != required_history.end(); lua_itr++) { + safe_delete(lua_itr->second); + } + required_history.clear(); + m_requiredHistory.releasewritelock(__FUNCTION__, __LINE__); + + m_requiredQuests.writelock(__FUNCTION__, __LINE__); + map* >::iterator rq_itr; + for (rq_itr = required_quests.begin(); rq_itr != required_quests.end(); rq_itr++){ + safe_delete(rq_itr->second); + } + required_quests.clear(); + m_requiredQuests.releasewritelock(__FUNCTION__, __LINE__); + + // just in case to make sure data is destroyed + RemoveSpawnProximities(); + + Regions.clear(); +} + +void Spawn::RemovePrimaryCommands() +{ + for (int32 i = 0; i < primary_command_list.size(); i++) { + safe_delete(primary_command_list[i]); + } + primary_command_list.clear(); +} + +void Spawn::InitializeHeaderPacketData(Player* player, PacketStruct* header, int16 index) { + header->setDataByName("index", index); + + if (GetSpawnAnim() > 0 && Timer::GetCurrentTime2() < (GetAddedToWorldTimestamp() + GetSpawnAnimLeeway())) { + if (header->GetVersion() >= 57080) + header->setDataByName("spawn_anim", GetSpawnAnim()); + else + header->setDataByName("spawn_anim", (int16)GetSpawnAnim()); + } + else { + if (header->GetVersion() >= 57080) + header->setDataByName("spawn_anim", 0xFFFFFFFF); + else + header->setDataByName("spawn_anim", 0xFFFF); + } + + if (primary_command_list.size() > 0){ + if (primary_command_list.size() > 1) { + header->setArrayLengthByName("command_list", primary_command_list.size()); + for (int32 i = 0; i < primary_command_list.size(); i++) { + header->setArrayDataByName("command_list_name", primary_command_list[i]->name.c_str(), i); + header->setArrayDataByName("command_list_max_distance", primary_command_list[i]->distance, i); + header->setArrayDataByName("command_list_error", primary_command_list[i]->error_text.c_str(), i); + header->setArrayDataByName("command_list_command", primary_command_list[i]->command.c_str(), i); + } + } + if (header->GetVersion() <= 561) { + header->setMediumStringByName("default_command", primary_command_list[0]->name.c_str()); + } + else + header->setMediumStringByName("default_command", primary_command_list[0]->command.c_str()); + header->setDataByName("max_distance", primary_command_list[0]->distance); + } + if (spawn_group_list && MSpawnGroup){ + MSpawnGroup->readlock(__FUNCTION__, __LINE__); + header->setArrayLengthByName("group_size", spawn_group_list->size()); + vector::iterator itr; + int i = 0; + for (itr = spawn_group_list->begin(); itr != spawn_group_list->end(); itr++, i++){ + int32 idx = 0; + idx = player->GetIDWithPlayerSpawn((*itr)); + header->setArrayDataByName("group_spawn_id", idx, i); + } + MSpawnGroup->releasereadlock(__FUNCTION__, __LINE__); + } + + header->setDataByName("spawn_id", player->GetIDWithPlayerSpawn(this)); + header->setDataByName("crc", 1); + header->setDataByName("time_stamp", Timer::GetCurrentTime2()); +} + +void Spawn::InitializeVisPacketData(Player* player, PacketStruct* vis_packet) { + int16 version = vis_packet->GetVersion(); + +//why? + /*if (IsPlayer()) { + appearance.pos.grid_id = 0xFFFFFFFF; + }*/ + + int8 tag_icon = 0; + + int32 tmp_id = 0; + if(faction_id && (tag_icon = player->MatchGMVisualFilter(GMTagFilterType::GMFILTERTYPE_FACTION, faction_id, "", true)) > 0); + else if(IsGroundSpawn() && (tag_icon = player->MatchGMVisualFilter(GMTagFilterType::GMFILTERTYPE_GROUNDSPAWN, 1, "", true)) > 0); + else if((this->GetSpawnGroupID() && (tag_icon = player->MatchGMVisualFilter(GMTagFilterType::GMFILTERTYPE_SPAWNGROUP, 1, "", true)) > 0) || + (!this->GetSpawnGroupID() && (tag_icon = player->MatchGMVisualFilter(GMTagFilterType::GMFILTERTYPE_SPAWNGROUP, 0, "", true)) > 0)); + else if((this->GetRace() && (tag_icon = player->MatchGMVisualFilter(GMTagFilterType::GMFILTERTYPE_RACE, GetRace(), "", true)) > 0)); + else if(((tmp_id = race_types_list.GetRaceType(GetModelType()) > 0) && (tag_icon = player->MatchGMVisualFilter(GMTagFilterType::GMFILTERTYPE_RACE, tmp_id, "", true)) > 0)); + else if(((tmp_id = race_types_list.GetRaceBaseType(GetModelType()) > 0) && (tag_icon = player->MatchGMVisualFilter(GMTagFilterType::GMFILTERTYPE_RACE, tmp_id, "", true)) > 0)); + else if(IsEntity() && (tag_icon = ((Entity*)this)->GetInfoStruct()->get_tag1()) > 0); + + vis_packet->setDataByName("tag1", tag_icon); + + if (IsPlayer()) + vis_packet->setDataByName("player", 1); + if (version <= 561) { + vis_packet->setDataByName("targetable", appearance.targetable); + vis_packet->setDataByName("show_name", appearance.display_name); + vis_packet->setDataByName("attackable", appearance.attackable); + if(appearance.attackable == 1) + vis_packet->setDataByName("attackable_icon", 1); + if (IsPlayer()) { + if (((Player*)this)->IsGroupMember(player)) + vis_packet->setDataByName("group_member", 1); + } + + } + if (appearance.targetable == 1 || appearance.show_level == 1 || appearance.display_name == 1) { + if (!IsGroundSpawn()) { + int8 arrow_color = ARROW_COLOR_WHITE; + sint8 npc_con = player->GetFactions()->GetCon(faction_id); + + if (IsPlayer() && !((Player*)this)->CanSeeInvis(player)) + npc_con = 0; + else if (!IsPlayer() && IsEntity() && !((Entity*)this)->CanSeeInvis(player)) + npc_con = 0; + + if (appearance.attackable == 1) + arrow_color = player->GetArrowColor(GetLevel()); + if (version <= 373) { + if (GetMerchantID() > 0) + arrow_color += 7; + else { + if (primary_command_list.size() > 0) { + int16 len = strlen(primary_command_list[0]->command.c_str()); + if(len >= 4 && strncmp(primary_command_list[0]->command.c_str(), "bank", 4) == 0) + arrow_color += 14; + else if (len >= 4 && strncmp(primary_command_list[0]->command.c_str(), "hail", 4) == 0) + arrow_color += 21; + else if (len >= 6 && strncmp(primary_command_list[0]->command.c_str(), "attack", 6) == 0) { + if (arrow_color > 5) + arrow_color = 34; + else + arrow_color += 29; + } + } + } + } + if(IsNPC() && (((Entity*)this)->GetLockedNoLoot() == ENCOUNTER_STATE_BROKEN || + (((Entity*)this)->GetLockedNoLoot() == ENCOUNTER_STATE_OVERMATCHED) || + ((Entity*)this)->GetLockedNoLoot() == ENCOUNTER_STATE_LOCKED && !((NPC*)this)->Brain()->IsEntityInEncounter(player->GetID()))) { + vis_packet->setDataByName("arrow_color", ARROW_COLOR_GRAY); + } + else { + vis_packet->setDataByName("arrow_color", arrow_color); + } + if (appearance.attackable == 0 || IsPlayer() || IsBot() || (IsEntity() && ((Entity*)this)->GetOwner() && + (((Entity*)this)->GetOwner()->IsPlayer() || ((Entity*)this)->GetOwner()->IsBot()))) { + vis_packet->setDataByName("locked_no_loot", 1); + } + else { + vis_packet->setDataByName("locked_no_loot", appearance.locked_no_loot); + } + if (player->GetArrowColor(GetLevel()) == ARROW_COLOR_GRAY) + if (npc_con == -4) + npc_con = -3; + vis_packet->setDataByName("npc_con", npc_con); + if (appearance.attackable == 1 && IsNPC() && (player->GetFactions()->GetCon(faction_id) <= -4 || ((NPC*)this)->Brain()->GetHate(player) > 1)) { + vis_packet->setDataByName("npc_hate", ((NPC*)this)->Brain()->GetHatePercentage(player)); + vis_packet->setDataByName("show_difficulty_arrows", 1); + } + int8 quest_flag = player->CheckQuestFlag(this); + if (version < 1188 && quest_flag >= 16) + quest_flag = 1; + vis_packet->setDataByName("quest_flag", quest_flag); + if (player->HasQuestUpdateRequirement(this)) { + vis_packet->setDataByName("name_quest_icon", 1); + } + } + } + + int8 vis_flags = 0; + if (MeetsSpawnAccessRequirements(player)) { + if (appearance.attackable == 1) + vis_flags += 64; //attackable icon + if (appearance.show_level == 1) + vis_flags += 32; + if (appearance.display_name == 1) + vis_flags += 16; + if (IsPlayer() || appearance.targetable == 1) + vis_flags += 4; + if (appearance.show_command_icon == 1) + vis_flags += 2; + if (this == player) { + //if (version <= 283) { + // vis_flags = 1; + //} + //else + vis_flags += 1; + } + } + else if (req_quests_override > 0) + { + //Check to see if there's an override value set + vis_flags = req_quests_override & 0xFF; + } + + if (player->HasGMVision()) + { + if ((vis_flags & 16) == 0 && appearance.display_name == 0) + vis_flags += 16; + if ((vis_flags & 4) == 0) + vis_flags += 4; + } + + if (version <= 546 && (vis_flags > 1 || appearance.display_hand_icon > 0)) //interactable + vis_flags = 1; + vis_packet->setDataByName("vis_flags", vis_flags); + + + if (MeetsSpawnAccessRequirements(player)) { + vis_packet->setDataByName("hand_flag", appearance.display_hand_icon); + } + else { + if ((req_quests_override & 256) > 0) + vis_packet->setDataByName("hand_flag", 1); + } + if ((version == 546 || version == 561) && GetMerchantID() > 0) { + vis_packet->setDataByName("guild", ""); + } +} + +void Spawn::InitializeFooterPacketData(Player* player, PacketStruct* footer) { + if (IsWidget()){ + Widget* widget = (Widget*)this; + if (widget->GetMultiFloorLift()) { + footer->setDataByName("widget_x", widget->GetX()); + footer->setDataByName("widget_y", widget->GetY()); + footer->setDataByName("widget_z", widget->GetZ()); + } + else { + footer->setDataByName("widget_x", widget->GetWidgetX()); + footer->setDataByName("widget_y", widget->GetWidgetY()); + footer->setDataByName("widget_z", widget->GetWidgetZ()); + } + footer->setDataByName("widget_id", widget->GetWidgetID()); + } + else if (IsSign()){ + Sign* sign = (Sign*)this; + footer->setDataByName("widget_id", sign->GetWidgetID()); + footer->setDataByName("widget_x", sign->GetWidgetX()); + footer->setDataByName("widget_y", sign->GetWidgetY()); + footer->setDataByName("widget_z", sign->GetWidgetZ()); + + int8 showSignText = 1; + if((HasQuestsRequired() || HasHistoryRequired()) && !MeetsSpawnAccessRequirements(player)) { + showSignText = 0; + } + + if (sign->GetSignTitle()) + footer->setMediumStringByName("title", sign->GetSignTitle()); + if (sign->GetSignDescription()) + footer->setMediumStringByName("description", sign->GetSignDescription()); + footer->setDataByName("sign_distance", sign->GetSignDistance()); + footer->setDataByName("show", showSignText); + // in live we see that the language is set when the player does not have it, otherwise its left as 00's. + if(!player->HasLanguage(sign->GetLanguage())) { + footer->setDataByName("language", sign->GetLanguage()); + } + } + + if ( IsPlayer()) + footer->setDataByName("is_player", 1); + + + if (strlen(appearance.name) < 1) + strncpy(appearance.name,to_string(GetID()).c_str(),128); + + footer->setMediumStringByName("name", appearance.name); + footer->setMediumStringByName("guild", appearance.sub_title); + footer->setMediumStringByName("prefix", appearance.prefix_title); + footer->setMediumStringByName("suffix", appearance.suffix_title); + footer->setMediumStringByName("last_name", appearance.last_name); + if (appearance.attackable == 0 && GetLevel() > 0) + footer->setDataByName("spawn_type", 1); + else if (appearance.attackable == 0) + footer->setDataByName("spawn_type", 6); + else + footer->setDataByName("spawn_type", 3); +} + +EQ2Packet* Spawn::spawn_serialize(Player* player, int16 version, int16 offset, int32 value, int16 offset2, int16 offset3, int16 offset4, int32 value2) { + // If spawn is NPC AND is pet && owner is a player && owner is the player passed to this function && player's char sheet pet id is 0 + m_Update.writelock(__FUNCTION__, __LINE__); + + int16 index = 0; + if ((index = player->GetIndexForSpawn(this)) > 0) { + player->SetSpawnMapIndex(this, index); + } + else { + index = player->SetSpawnMapAndIndex(this); + } + + // Jabantiz - [Bug] Client Crash on Revive + if (player->GetIDWithPlayerSpawn(this) == 0) { + player->SetSpawnMap(this); + } + + PacketStruct* header = player->GetSpawnHeaderStruct(); + header->ResetData(); + InitializeHeaderPacketData(player, header, index); + + PacketStruct* footer = 0; + if (IsWidget()) + footer = player->GetWidgetFooterStruct(); + else if (IsSign()) + footer = player->GetSignFooterStruct(); + else if (version > 561) + footer = player->GetSpawnFooterStruct(); + if (footer) { + footer->ResetData(); + InitializeFooterPacketData(player, footer); + } + PacketStruct* vis_struct = player->GetSpawnVisStruct(); + PacketStruct* info_struct = player->GetSpawnInfoStruct(); + PacketStruct* pos_struct = player->GetSpawnPosStruct(); + + player->info_mutex.writelock(__FUNCTION__, __LINE__); + player->vis_mutex.writelock(__FUNCTION__, __LINE__); + player->pos_mutex.writelock(__FUNCTION__, __LINE__); + + info_struct->ResetData(); + InitializeInfoPacketData(player, info_struct); + + vis_struct->ResetData(); + InitializeVisPacketData(player, vis_struct); + + pos_struct->ResetData(); + InitializePosPacketData(player, pos_struct); + if (version <= 283) { + if (offset == 777) { + info_struct->setDataByName("name", "This is a really long name\n"); + info_struct->setDataByName("last_name", "This is a really long LAST name\n"); + info_struct->setDataByName("name_suffix", "This is a really long SUFFIX\n"); + info_struct->setDataByName("name_prefix", "This is a really long PREFIX\n"); + info_struct->setDataByName("unknown", "This is a really long UNKNOWN\n"); + info_struct->setDataByName("second_suffix", "This is a really long 2nd SUFFIX\n"); + } + //info_struct->setDataByName("unknown2", 3, 0); // level + //info_struct->setDataByName("unknown2", 1, 1); //unknown, two down arrows + //info_struct->setDataByName("unknown2", 1, 2); //unknown + //info_struct->setDataByName("unknown2", 1, 3); //unknown + //info_struct->setDataByName("unknown2", 1, 4); //solo fight + //info_struct->setDataByName("unknown2", 1, 5); //unknown + //info_struct->setDataByName("unknown2", 1, 6); //unknown + //info_struct->setDataByName("unknown2", 1, 7); //merchant + //info_struct->setDataByName("unknown2", 1, 8); //unknown + //info_struct->setDataByName("unknown2", 1, 9); //unknown + //info_struct->setDataByName("unknown2", 1, 10); + //112: 00 00 00 01 02 03 04 05 - 06 07 08 09 0A 00 00 00 | ................ merchant, x4 + //112: 00 00 00 01 02 03 04 05 - 00 00 00 00 00 00 00 00 | ................ x4, epic, indifferent + //info_struct->setDataByName("body_size", 42); + //for(int i=0;i<8;i++) + // info_struct->setDataByName("persistent_spell_visuals", 329, i); + //info_struct->setDataByName("persistent_spell_levels", 20); + } + + string* vis_data = vis_struct->serializeString(); + string* pos_data = pos_struct->serializeString(); + string* info_data = info_struct->serializeString(); + + int16 part2_size = pos_data->length() + vis_data->length() + info_data->length(); + uchar* part2 = new uchar[part2_size]; + + player->AddSpawnPosPacketForXOR(id, (uchar*)pos_data->c_str(), pos_data->length()); + player->AddSpawnVisPacketForXOR(id, (uchar*)vis_data->c_str(), vis_data->length()); + player->AddSpawnInfoPacketForXOR(id, (uchar*)info_data->c_str(), info_data->length()); + + int32 vislength = vis_data->length(); + int32 poslength = pos_data->length(); + int32 infolength = info_data->length(); + + uchar* ptr = part2; + memcpy(ptr, pos_data->c_str(), pos_data->length()); + ptr += pos_data->length(); + memcpy(ptr, vis_data->c_str(), vis_data->length()); + ptr += vis_data->length(); + memcpy(ptr, info_data->c_str(), info_data->length()); + player->pos_mutex.releasewritelock(__FUNCTION__, __LINE__); + player->info_mutex.releasewritelock(__FUNCTION__, __LINE__); + player->vis_mutex.releasewritelock(__FUNCTION__, __LINE__); + + string* part1 = header->serializeString(); + string* part3 = 0; + if (footer) + part3 = footer->serializeString(); + + //uchar blah7[] = {0x01,0x01,0x00,0x00,0x01,0x01,0x00,0x01,0x01,0x00 }; + //uchar blah7[] = { 0x03,0x01,0x00,0x01,0x01,0x01,0x00,0x01,0x01,0x00 }; base + //uchar blah7[] = { 0x03,0x00,0x00,0x01,0x01,0x01,0x00,0x01,0x01,0x00 }; //no change + //uchar blah7[] = { 0x03,0x00,0x00,0x00,0x01,0x01,0x00,0x01,0x01,0x00 }; //blue instead of green + //uchar blah7[] = { 0x03,0x00,0x00,0x00,0x00,0x01,0x00,0x01,0x01,0x00 }; //no change + //uchar blah7[] = { 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x00 }; //not selectable + //uchar blah7[] = { 0x03,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x01,0x00 }; //no change + //uchar blah7[] = { 0x03,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00 }; //no longer have the two down arrows + //uchar blah7[] = { 0x01,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00 }; //arrow color green instead of white/gray + //memcpy(part2 + 77, blah7, sizeof(blah7)); + + + + + + //DumpPacket(part2, 885); + if (offset > 0) { + if (offset4 > 0 && offset4 >= offset3) { + uchar* ptr2 = (uchar*)part2; + ptr2 += offset3; + while (offset4 >= offset3) { + int8 jumpsize = 1; + if (value2 > 0xFFFF) { + memcpy(ptr2, (uchar*)&value2, 4); + jumpsize = 4; + } + else if (value2 > 0xFF) { + memcpy(ptr2, (uchar*)&value2, 2); + jumpsize = 2; + } + else { + memcpy(ptr2, (uchar*)&value2, 1); + jumpsize = 1; + } + ptr2 += jumpsize; + offset4 -= jumpsize; + } + } + if (offset2 > 0 && offset2 >= offset) { + uchar* ptr2 = (uchar*)part2; + ptr2 += offset; + while (offset2 >= offset) { + int8 jumpsize = 1; + if (value > 0xFFFF) { + memcpy(ptr2, (uchar*)&value, 4); + jumpsize = 4; + } + else if (value > 0xFF) { + memcpy(ptr2, (uchar*)&value, 2); + jumpsize = 2; + } + else + memcpy(ptr2, (uchar*)&value, 1); + ptr2 += jumpsize; + offset2 -= jumpsize; + } + } + else { + uchar* ptr2 = (uchar*)part2; + ptr2 += offset; + if (value > 0xFFFF) + memcpy(ptr2, (uchar*)&value, 4); + else if (value > 0xFF) + memcpy(ptr2, (uchar*)&value, 2); + else + memcpy(ptr2, (uchar*)&value, 1); + } + cout << "setting offset: " << offset << " to: " << value << endl; + } + //if (offset > 0) + // DumpPacket(part2, part2_size); + + uchar tmp[4000]; + bool reverse = (version > 373); + part2_size = Pack(tmp, part2, part2_size, 4000, version, reverse); + int32 total_size = part1->length() + part2_size + 3; + if (part3) + total_size += part3->length(); + int32 final_packet_size = total_size + 1; + + if (version > 373) + final_packet_size += 3; + else { + if (final_packet_size >= 255) { + final_packet_size += 2; + } + } + uchar* final_packet = new uchar[final_packet_size]; + ptr = final_packet; + if (version <= 373) { + if ((final_packet_size - total_size) > 1) { + memcpy(ptr, &oversized_packet, sizeof(oversized_packet)); + ptr += sizeof(oversized_packet); + memcpy(ptr, &total_size, 2); + ptr += 2; + } + else { + memcpy(ptr, &total_size, 1); + ptr += 1; + } + } + else { + memcpy(ptr, &total_size, sizeof(total_size)); + ptr += sizeof(total_size); + } + + memcpy(ptr, &oversized_packet, sizeof(oversized_packet)); + ptr += sizeof(oversized_packet); + + int16 opcode = 0; + + if (EQOpcodeManager.count(GetOpcodeVersion(version)) != 0) { + if(IsWidget()) + opcode = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqCreateWidgetCmd); + else if(IsSign()) + opcode = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqCreateSignWidgetCmd); + else + opcode = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqCreateGhostCmd); + } + memcpy(ptr, &opcode, sizeof(opcode)); + ptr += sizeof(opcode); + + memcpy(ptr, part1->c_str(), part1->length()); + ptr += part1->length(); + + + memcpy(ptr, tmp, part2_size); + ptr += part2_size; + + if (part3) + memcpy(ptr, part3->c_str(), part3->length()); + delete[] part2; + + //printf("SpawnPacket %s (id: %u, index: %u) to %s: p1: %i, p2: %i, p3: %i, ts: %i. poslength: %u, infolength: %u, vislength: %u\n", GetName(), GetID(), index, player->GetName(), part1->length(), part2_size, (part3 != nullptr) ? part3->length() : -1, total_size, poslength, infolength, vislength); + EQ2Packet* ret = new EQ2Packet(OP_ClientCmdMsg, final_packet, final_packet_size); + delete[] final_packet; + + m_Update.releasewritelock(__FUNCTION__, __LINE__); + + return ret; +} + +uchar* Spawn::spawn_info_changes(Player* player, int16 version, int16* info_packet_size){ + int16 index = player->GetIndexForSpawn(this); + + PacketStruct* packet = player->GetSpawnInfoStruct(); + + player->info_mutex.writelock(__FUNCTION__, __LINE__); + packet->ResetData(); + InitializeInfoPacketData(player, packet); + string* data = packet->serializeString(); + int32 size = data->length(); + uchar* xor_info_packet = player->GetTempInfoPacketForXOR(); + if (!xor_info_packet || size != player->GetTempInfoXorSize()) + { + LogWrite(ZONE__DEBUG, 0, "Zone", "InstantiateInfoPacket: %i, %i", size, player->GetTempInfoXorSize()); + safe_delete(xor_info_packet); + xor_info_packet = player->SetTempInfoPacketForXOR(size); + } + + uchar* orig_packet = player->GetSpawnInfoPacketForXOR(id); + if(orig_packet){ + memcpy(xor_info_packet, (uchar*)data->c_str(), size); + Encode(xor_info_packet, orig_packet, size); + } + + + bool changed = false; + for (int i = 0; i < size; ++i) { + if (xor_info_packet[i]) { + changed = true; + break; + } + } + + if (!changed) { + player->info_mutex.releasewritelock(__FUNCTION__, __LINE__); + return nullptr; + } + + uchar* tmp = new uchar[size + 1000]; + size = Pack(tmp, xor_info_packet, size, size+1000, version); + player->info_mutex.releasewritelock(__FUNCTION__, __LINE__); + + int32 orig_size = size; + size-=sizeof(int32); + size+=CheckOverLoadSize(index); + *info_packet_size = size + CheckOverLoadSize(size); + + uchar* tmp2 = new uchar[*info_packet_size]; + uchar* ptr = tmp2; + ptr += DoOverLoad(size, ptr); + ptr += DoOverLoad(index, ptr); + memcpy(ptr, tmp+sizeof(int32), orig_size - sizeof(int32)); + delete[] tmp; + return tmp2; +} + +uchar* Spawn::spawn_vis_changes(Player* player, int16 version, int16* vis_packet_size){ + PacketStruct* vis_struct = player->GetSpawnVisStruct(); + int16 index = player->GetIndexForSpawn(this); + + player->vis_mutex.writelock(__FUNCTION__, __LINE__); + uchar* orig_packet = player->GetSpawnVisPacketForXOR(id); + vis_struct->ResetData(); + InitializeVisPacketData(player, vis_struct); + string* data = vis_struct->serializeString(); + int32 size = data->length(); + uchar* xor_vis_packet = player->GetTempVisPacketForXOR(); + if (!xor_vis_packet || size != player->GetTempVisXorSize()) + { + LogWrite(ZONE__DEBUG, 0, "Zone", "InstantiateVisPacket: %i, %i", size, player->GetTempVisXorSize()); + safe_delete(xor_vis_packet); + xor_vis_packet = player->SetTempVisPacketForXOR(size); + } + if(orig_packet){ + memcpy(xor_vis_packet, (uchar*)data->c_str(), size); + Encode(xor_vis_packet, orig_packet, size); + } + + bool changed = false; + for (int i = 0; i < size; ++i) { + if (xor_vis_packet[i]) { + changed = true; + break; + } + } + + if (!changed) { + player->vis_mutex.releasewritelock(__FUNCTION__, __LINE__); + return nullptr; + } + + uchar* tmp = new uchar[size + 1000]; + size = Pack(tmp, xor_vis_packet, size, size+1000, version); + player->vis_mutex.releasewritelock(__FUNCTION__, __LINE__); + + int32 orig_size = size; + size-=sizeof(int32); + size+=CheckOverLoadSize(index); + *vis_packet_size = size + CheckOverLoadSize(size); + uchar* tmp2 = new uchar[*vis_packet_size]; + uchar* ptr = tmp2; + ptr += DoOverLoad(size, ptr); + ptr += DoOverLoad(index, ptr); + memcpy(ptr, tmp+sizeof(int32), orig_size - sizeof(int32)); + delete[] tmp; + return tmp2; +} + +uchar* Spawn::spawn_pos_changes(Player* player, int16 version, int16* pos_packet_size, bool override_) { + int16 index = player->GetIndexForSpawn(this); + + PacketStruct* packet = player->GetSpawnPosStruct(); + + player->pos_mutex.writelock(__FUNCTION__, __LINE__); + uchar* orig_packet = player->GetSpawnPosPacketForXOR(id); + packet->ResetData(); + InitializePosPacketData(player, packet, true); + string* data = packet->serializeString(); + int32 size = data->length(); + uchar* xor_pos_packet = player->GetTempPosPacketForXOR(); + if (!xor_pos_packet || size != player->GetTempPosXorSize()) + { + LogWrite(ZONE__DEBUG, 0, "Zone", "InstantiatePosPacket: %i, %i", size, player->GetTempPosXorSize()); + safe_delete(xor_pos_packet); + xor_pos_packet = player->SetTempPosPacketForXOR(size); + } + if(orig_packet){ + memcpy(xor_pos_packet, (uchar*)data->c_str(), size); + Encode(xor_pos_packet, orig_packet, size); + } + + bool changed = false; + for (int i = 0; i < size; ++i) { + if (xor_pos_packet[i]) { + changed = true; + break; + } + } + + if (!changed && !override_) { + player->pos_mutex.releasewritelock(__FUNCTION__, __LINE__); + return nullptr; + } + + int16 newSize = size + 1000; + uchar* tmp = new uchar[newSize]; + size = Pack(tmp, xor_pos_packet, size, newSize, version); + player->pos_mutex.releasewritelock(__FUNCTION__, __LINE__); + + int32 orig_size = size; + // Needed for CoE+ clients + if (version >= 1188) + size += 1; + + if(IsPlayer() && version > 561) + size += 4; + size-=sizeof(int32); + size+=CheckOverLoadSize(index); + + *pos_packet_size = size + CheckOverLoadSize(size); + uchar* tmp2 = new uchar[*pos_packet_size]; + uchar* ptr = tmp2; + ptr += DoOverLoad(size, ptr); + ptr += DoOverLoad(index, ptr); + + // extra byte in coe+ clients, 0 for NPC's 1 for Players + int8 x = 0; + if (IsPlayer() && version > 561) { + if (version >= 1188) { + // set x to 1 and add it to the packet + x = 1; + memcpy(ptr, &x, sizeof(int8)); + ptr += sizeof(int8); + } + int32 now = Timer::GetCurrentTime2(); + memcpy(ptr, &now, sizeof(int32)); + ptr += sizeof(int32); + } + else if (version >= 1188) { + // add x to packet + memcpy(ptr, &x, sizeof(int8)); + ptr += sizeof(int8); + } + memcpy(ptr, tmp+sizeof(int32), orig_size - sizeof(int32)); + delete[] tmp; + return tmp2; +} + +EQ2Packet* Spawn::player_position_update_packet(Player* player, int16 version, bool override_){ + if(!player || player->IsPlayer() == false){ + LogWrite(SPAWN__ERROR, 0, "Spawn", "Error: Called player_position_update_packet without player!"); + return 0; + } + else if(IsPlayer() == false){ + LogWrite(SPAWN__ERROR, 0, "Spawn", "Error: Called player_position_update_packet from spawn!"); + return 0; + } + + static const int8 info_size = 1; + static const int8 vis_size = 1; + int16 pos_packet_size = 0; + m_Update.writelock(__FUNCTION__, __LINE__); + uchar* pos_changes = spawn_pos_changes(player, version, &pos_packet_size, override_); + if (pos_changes == NULL) + { + m_Update.releasewritelock(__FUNCTION__, __LINE__); + return NULL; + } + + int32 size = info_size + pos_packet_size + vis_size + 8; + if (version >= 374) + size += 3; + else if (version <= 373 && size >= 255) {//1 byte to 3 for overloaded val + size += 2; + } + static const int8 oversized = 255; + + + int16 opcode_val = 0; + + if (EQOpcodeManager.count(GetOpcodeVersion(version)) != 0) { + opcode_val = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqUpdateGhostCmd); + } + uchar* tmp = new uchar[size]; + memset(tmp, 0, size); + uchar* ptr = tmp; + if (version >= 374) { + size -= 4; + memcpy(ptr, &size, sizeof(int32)); + size += 4; + ptr += sizeof(int32); + } + else { + if (size >= 255) { + memcpy(ptr, &oversized, sizeof(int8)); + ptr += sizeof(int8); + size -= 3; + memcpy(ptr, &size, sizeof(int16)); + size += 3; + ptr += 3; + } + else { + size -= 1; + memcpy(ptr, &size, sizeof(int8)); + ptr += sizeof(int8); + size += 1; + ptr += 1; + } + } + memcpy(ptr, &oversized, sizeof(int8)); + ptr += sizeof(int8); + memcpy(ptr, &opcode_val, sizeof(int16)); + ptr += sizeof(int16); + if (version <= 373) { + int32 timestamp = Timer::GetCurrentTime2(); + memcpy(ptr, ×tamp, sizeof(int32)); + } + ptr += sizeof(int32); + ptr += info_size; + memcpy(ptr, pos_changes, pos_packet_size); + delete[] pos_changes; + m_Update.releasewritelock(__FUNCTION__, __LINE__); + EQ2Packet* ret_packet = new EQ2Packet(OP_ClientCmdMsg, tmp, size); +// DumpPacket(ret_packet); + delete[] tmp; + return ret_packet; +} + +EQ2Packet* Spawn::spawn_update_packet(Player* player, int16 version, bool override_changes, bool override_vis_changes){ + if(!player || player->IsPlayer() == false){ + LogWrite(SPAWN__ERROR, 0, "Spawn", "Error: Called spawn_update_packet without player!"); + return 0; + } + else if((IsPlayer() && info_changed == false && vis_changed == false) || (info_changed == false && vis_changed == false && position_changed == false)){ + if(!override_changes && !override_vis_changes) + return 0; + } + + uchar* info_changes = 0; + uchar* pos_changes = 0; + uchar* vis_changes = 0; + static const int8 oversized = 255; + int16 opcode_val = 0; + + if (EQOpcodeManager.count(GetOpcodeVersion(version)) != 0) { + opcode_val = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqUpdateGhostCmd); + } + + //We need to lock these variables up to make this thread safe + m_Update.writelock(__FUNCTION__, __LINE__); + //These variables are set in the spawn_info_changes, pos and vis changes functions + int16 info_packet_size = 1; + int16 pos_packet_size = 1; + int16 vis_packet_size = 1; + + if (info_changed || override_changes) + info_changes = spawn_info_changes(player, version, &info_packet_size); + if ((position_changed || override_changes) && IsPlayer() == false) + pos_changes = spawn_pos_changes(player, version, &pos_packet_size); + if (vis_changed || override_changes || override_vis_changes) + vis_changes = spawn_vis_changes(player, version, &vis_packet_size); + + int32 size = info_packet_size + pos_packet_size + vis_packet_size + 8; + if (version >= 374) + size += 3; + else if (version <= 373 && size >= 255) {//1 byte to 3 for overloaded val + size += 2; + } + uchar* tmp = new uchar[size]; + memset(tmp, 0, size); + uchar* ptr = tmp; + if (version >= 374) { + size -= 4; + memcpy(ptr, &size, sizeof(int32)); + size += 4; + ptr += sizeof(int32); + } + else { + if (size >= 255) { + memcpy(ptr, &oversized, sizeof(int8)); + ptr += sizeof(int8); + size -= 3; + memcpy(ptr, &size, sizeof(int16)); + size += 3; + ptr += 3; + } + else { + size -= 1; + memcpy(ptr, &size, sizeof(int8)); + ptr += sizeof(int8); + size += 1; + } + } + memcpy(ptr, &oversized, sizeof(int8)); + ptr += sizeof(int8); + memcpy(ptr, &opcode_val, sizeof(int16)); + ptr += sizeof(int16); + if (IsPlayer() == false || version <= 546) { //this isnt sent for player updates, it is sent on position update + //int32 time = Timer::GetCurrentTime2(); + packet_num = Timer::GetCurrentTime2(); + memcpy(ptr, &packet_num, sizeof(int32)); + } + ptr += sizeof(int32); + if(info_changes) + memcpy(ptr, info_changes, info_packet_size); + + ptr += info_packet_size; + + if(pos_changes) + memcpy(ptr, pos_changes, pos_packet_size); + + ptr += pos_packet_size; + + if(vis_changes) + memcpy(ptr, vis_changes, vis_packet_size); + + EQ2Packet* ret_packet = 0; + if(info_packet_size + pos_packet_size + vis_packet_size > 0) + ret_packet = new EQ2Packet(OP_ClientCmdMsg, tmp, size); + delete[] tmp; + safe_delete_array(info_changes); + safe_delete_array(vis_changes); + safe_delete_array(pos_changes); + m_Update.releasewritelock(__FUNCTION__, __LINE__); + + return ret_packet; +} + +uchar* Spawn::spawn_info_changes_ex(Player* player, int16 version, int16* info_packet_size) { + int16 index = player->GetIndexForSpawn(this); + + PacketStruct* packet = player->GetSpawnInfoStruct(); + + player->info_mutex.writelock(__FUNCTION__, __LINE__); + + packet->ResetData(); + InitializeInfoPacketData(player, packet); + + string* data = packet->serializeString(); + int32 size = data->length(); + uchar* xor_info_packet = player->GetTempInfoPacketForXOR(); + + if (!xor_info_packet || size != player->GetTempInfoXorSize()) { + LogWrite(ZONE__DEBUG, 0, "Zone", "InstantiateInfoExPacket: %i, %i", size, player->GetTempInfoXorSize()); + safe_delete(xor_info_packet); + xor_info_packet = player->SetTempInfoPacketForXOR(size); + } + + uchar* orig_packet = player->GetSpawnInfoPacketForXOR(id); + + if (orig_packet) { + //if (!IsPlayer() && this->EngagedInCombat()) + //packet->PrintPacket(); + memcpy(xor_info_packet, (uchar*)data->c_str(), size); + Encode(xor_info_packet, orig_packet, size); + } + + bool changed = false; + for (int i = 0; i < size; ++i) { + if (xor_info_packet[i]) { + changed = true; + break; + } + } + + if (!changed) { + player->info_mutex.releasewritelock(__FUNCTION__, __LINE__); + return nullptr; + } + + uchar* tmp = new uchar[size + 1000]; + size = Pack(tmp, xor_info_packet, size, size+1000, version); + player->info_mutex.releasewritelock(__FUNCTION__, __LINE__); + + int32 orig_size = size; + + size -= sizeof(int32); + size += CheckOverLoadSize(index); + *info_packet_size = size; + + uchar* tmp2 = new uchar[size]; + uchar* ptr = tmp2; + + ptr += DoOverLoad(index, ptr); + + memcpy(ptr, tmp + sizeof(int32), orig_size - sizeof(int32)); + + delete[] tmp; + + return move(tmp2); +} + +uchar* Spawn::spawn_vis_changes_ex(Player* player, int16 version, int16* vis_packet_size) { + PacketStruct* vis_struct = player->GetSpawnVisStruct(); + int16 index = player->GetIndexForSpawn(this); + + player->vis_mutex.writelock(__FUNCTION__, __LINE__); + + uchar* orig_packet = player->GetSpawnVisPacketForXOR(id); + + vis_struct->ResetData(); + InitializeVisPacketData(player, vis_struct); + + string* data = vis_struct->serializeString(); + int32 size = data->length(); + uchar* xor_vis_packet = player->GetTempVisPacketForXOR(); + + if (!xor_vis_packet || size != player->GetTempVisXorSize()) { + LogWrite(ZONE__DEBUG, 0, "Zone", "InstantiateVisExPacket: %i, %i", size, player->GetTempVisXorSize()); + safe_delete(xor_vis_packet); + xor_vis_packet = player->SetTempVisPacketForXOR(size); + } + + if (orig_packet) { + //if (!IsPlayer() && this->EngagedInCombat()) + // vis_struct->PrintPacket(); + memcpy(xor_vis_packet, (uchar*)data->c_str(), size); + Encode(xor_vis_packet, orig_packet, size); + } + + bool changed = false; + for (int i = 0; i < size; ++i) { + if (xor_vis_packet[i]) { + changed = true; + break; + } + } + + if (!changed) { + player->vis_mutex.releasewritelock(__FUNCTION__, __LINE__); + return nullptr; + } + + uchar* tmp = new uchar[size + 1000]; + size = Pack(tmp, xor_vis_packet, size, size+1000, version); + + player->vis_mutex.releasewritelock(__FUNCTION__, __LINE__); + + int32 orig_size = size; + + size -= sizeof(int32); + size += CheckOverLoadSize(index); + *vis_packet_size = size; + + uchar* tmp2 = new uchar[size]; + uchar* ptr = tmp2; + + ptr += DoOverLoad(index, ptr); + + memcpy(ptr, tmp + sizeof(int32), orig_size - sizeof(int32)); + + delete[] tmp; + + return move(tmp2); +} + +uchar* Spawn::spawn_pos_changes_ex(Player* player, int16 version, int16* pos_packet_size) { + int16 index = player->GetIndexForSpawn(this); + + PacketStruct* packet = player->GetSpawnPosStruct(); + + player->pos_mutex.writelock(__FUNCTION__, __LINE__); + + uchar* orig_packet = player->GetSpawnPosPacketForXOR(id); + + packet->ResetData(); + InitializePosPacketData(player, packet); + + string* data = packet->serializeString(); + int32 size = data->length(); + uchar* xor_pos_packet = player->GetTempPosPacketForXOR(); + + if (!xor_pos_packet || size != player->GetTempPosXorSize()) { + LogWrite(ZONE__DEBUG, 0, "Zone", "InstantiatePosExPacket: %i, %i", size, player->GetTempPosXorSize()); + safe_delete(xor_pos_packet); + xor_pos_packet = player->SetTempPosPacketForXOR(size); + } + + if (orig_packet) { + //if (!IsPlayer() && this->EngagedInCombat()) + // packet->PrintPacket(); + memcpy(xor_pos_packet, (uchar*)data->c_str(), size); + Encode(xor_pos_packet, orig_packet, size); + } + + bool changed = false; + for (int i = 0; i < size; ++i) { + if (xor_pos_packet[i]) { + changed = true; + break; + } + } + + if (!changed && version > 561) { + player->pos_mutex.releasewritelock(__FUNCTION__, __LINE__); + return nullptr; + } + + int16 newSize = size + 1000; + uchar* tmp = new uchar[newSize]; + size = Pack(tmp, xor_pos_packet, size, newSize, version); + player->pos_mutex.releasewritelock(__FUNCTION__, __LINE__); + + int32 orig_size = size; + + if (version >= 1188) { + size += 1; + } + + if (IsPlayer() && version > 561) { + size += 4; + } + + size -= sizeof(int32); + size += CheckOverLoadSize(index); + *pos_packet_size = size; + + uchar* tmp2 = new uchar[size]; + uchar* ptr = tmp2; + + ptr += DoOverLoad(index, ptr); + + // extra byte in coe+ clients, 0 for NPC's 1 for Players + int8 x = 0; + + if (version > 561) { + if (IsPlayer()) { + if (version >= 1188) { + x = 1; + memcpy(ptr, &x, sizeof(int8)); + ptr += sizeof(int8); + } + int32 now = Timer::GetCurrentTime2(); + memcpy(ptr, &now, sizeof(int32)); + ptr += sizeof(int32); + } + else if (version >= 1188) { + memcpy(ptr, &x, sizeof(int8)); + ptr += sizeof(int8); + } + } + + memcpy(ptr, tmp + sizeof(int32), orig_size - sizeof(int32)); + + delete[] tmp; + + return move(tmp2); +} + + +EQ2Packet* Spawn::serialize(Player* player, int16 version){ + return 0; +} + +Spawn* Spawn::GetTarget(){ + Spawn* ret = 0; + + // only attempt to get a spawn if we had a target stored + if (target != 0) + { + ret = GetZone()->GetSpawnByID(target); + + if (!ret) + target = 0; + } + + return ret; +} + +void Spawn::SetTarget(Spawn* spawn){ + SetInfo(&target, spawn ? spawn->GetID() : 0); +} + +Spawn* Spawn::GetLastAttacker() { + Spawn* ret = 0; + ret = GetZone()->GetSpawnByID(last_attacker); + if (!ret) + last_attacker = 0; + return ret; +} + +void Spawn::SetLastAttacker(Spawn* spawn){ + last_attacker = spawn->GetID(); +} + +void Spawn::SetInvulnerable(bool val){ + invulnerable = val; +} + +bool Spawn::GetInvulnerable(){ + return invulnerable; +} + +bool Spawn::TakeDamage(int32 damage){ + if(invulnerable) + return false; + if (IsEntity()) { + if (((Entity*)this)->IsMezzed()) + ((Entity*)this)->RemoveAllMezSpells(); + + if (damage == 0) + return true; + } + + int32 hp = GetHP(); + if(damage >= hp) { + SetHP(0); + if (IsPlayer()) { + ((Player*)this)->InCombat(false); + ((Player*)this)->SetRangeAttack(false); + GetZone()->TriggerCharSheetTimer(); // force char sheet updates now + } + } + else { + SetHP(hp - damage); + // if player flag the char sheet as changed so the ui updates properly + if (IsPlayer()) + ((Player*)this)->SetCharSheetChanged(true); + } + return true; +} +ZoneServer* Spawn::GetZone(){ + return zone; +} + +void Spawn::SetZone(ZoneServer* in_zone, int32 version){ + zone = in_zone; + + if(in_zone) + { + region_map = world.GetRegionMap(std::string(in_zone->GetZoneFile()), version); + current_map = world.GetMap(std::string(in_zone->GetZoneFile()), version); + } + else + { + region_map = nullptr; + current_map = nullptr; + } +} + + +/*** HIT POINT ***/ +void Spawn::SetHP(sint32 new_val, bool setUpdateFlags){ + if(new_val == 0){ + ClearRunningLocations(); + CalculateRunningLocation(true); + + if(IsEntity()) { + is_alive = false; + } + } + if(IsNPC() && new_val > 0 && !is_alive) { + is_alive = true; + } + else if(IsPlayer() && new_val > 0 && !is_alive) { + LogWrite(SPAWN__ERROR, 0, "Spawn", "Cannot change player HP > 0 while dead (%s)! Player must revive.", GetName()); + return; + } + if(new_val > basic_info.max_hp) + SetInfo(&basic_info.max_hp, new_val, setUpdateFlags); + SetInfo(&basic_info.cur_hp, new_val, setUpdateFlags); + if(/*IsPlayer() &&*/ GetZone() && basic_info.cur_hp > 0 && basic_info.cur_hp < basic_info.max_hp) + GetZone()->AddDamagedSpawn(this); + + SendGroupUpdate(); + + if ( IsPlayer() && new_val == 0 ) // fixes on death not showing hp update for players + ((Player*)this)->SetCharSheetChanged(true); + + if (IsNPC() && ((NPC*)this)->IsPet() && ((NPC*)this)->GetOwner() && ((NPC*)this)->GetOwner()->IsPlayer()) { + Player* player = (Player*)((NPC*)this)->GetOwner(); + if (player->GetPet() && player->GetCharmedPet()) { + if (this == player->GetPet()) { + player->GetInfoStruct()->set_pet_health_pct((float)basic_info.cur_hp / (float)basic_info.max_hp); + player->SetCharSheetChanged(true); + } + } + else { + player->GetInfoStruct()->set_pet_health_pct((float)basic_info.cur_hp / (float)basic_info.max_hp); + player->SetCharSheetChanged(true); + } + } +} +void Spawn::SetTotalHP(sint32 new_val){ + if(basic_info.hp_base == 0) { + SetTotalHPBase(new_val); + SetTotalHPBaseInstance(new_val); + } + SetInfo(&basic_info.max_hp, new_val); + + if(GetZone() && basic_info.cur_hp > 0 && basic_info.cur_hp < basic_info.max_hp) + GetZone()->AddDamagedSpawn(this); + + SendGroupUpdate(); + + if (IsNPC() && ((NPC*)this)->IsPet() && ((NPC*)this)->GetOwner() != nullptr && ((NPC*)this)->GetOwner()->IsPlayer()) { + Player* player = (Player*)((NPC*)this)->GetOwner(); + if (basic_info.max_hp && player->GetPet() && player->GetCharmedPet()) { + if (this == player->GetPet()) { + player->GetInfoStruct()->set_pet_health_pct((float)basic_info.cur_hp / (float)basic_info.max_hp); + player->SetCharSheetChanged(true); + } + } + else if(basic_info.max_hp) { + player->GetInfoStruct()->set_pet_health_pct((float)basic_info.cur_hp / (float)basic_info.max_hp); + player->SetCharSheetChanged(true); + } + } +} +void Spawn::SetTotalHPBase(sint32 new_val) +{ + SetInfo(&basic_info.hp_base, new_val); + + if(GetZone() && basic_info.cur_hp > 0 && basic_info.cur_hp < basic_info.max_hp) + GetZone()->AddDamagedSpawn(this); + + SendGroupUpdate(); +} + +void Spawn::SetTotalHPBaseInstance(sint32 new_val) +{ + SetInfo(&basic_info.hp_base_instance, new_val); +} + +sint32 Spawn::GetHP() +{ + return basic_info.cur_hp; +} +sint32 Spawn::GetTotalHP() +{ + return basic_info.max_hp; +} +sint32 Spawn::GetTotalHPBase() +{ + return basic_info.hp_base; +} +sint32 Spawn::GetTotalHPBaseInstance() +{ + return basic_info.hp_base_instance; +} +sint32 Spawn::GetTotalPowerBaseInstance() +{ + return basic_info.power_base_instance; +} + + +/*** POWER ***/ +void Spawn::SetPower(sint32 power, bool setUpdateFlags){ + if(power > basic_info.max_power) + SetInfo(&basic_info.max_power, power, setUpdateFlags); + SetInfo(&basic_info.cur_power, power, setUpdateFlags); + if(/*IsPlayer() &&*/ GetZone() && basic_info.cur_power < basic_info.max_power) + GetZone()->AddDamagedSpawn(this); + + SendGroupUpdate(); + + if (IsNPC() && ((NPC*)this)->IsPet() && ((NPC*)this)->GetOwner() != nullptr && ((NPC*)this)->GetOwner()->IsPlayer()) { + Player* player = (Player*)((NPC*)this)->GetOwner(); + if (player->GetPet() && player->GetCharmedPet()) { + if (this == player->GetPet()) { + player->GetInfoStruct()->set_pet_power_pct((float)basic_info.cur_power / (float)basic_info.max_power); + player->SetCharSheetChanged(true); + } + } + else { + player->GetInfoStruct()->set_pet_power_pct((float)basic_info.cur_power / (float)basic_info.max_power); + player->SetCharSheetChanged(true); + } + } +} +void Spawn::SetTotalPower(sint32 new_val) +{ + if(basic_info.power_base == 0) { + SetTotalPowerBase(new_val); + SetTotalPowerBaseInstance(new_val); + } + SetInfo(&basic_info.max_power, new_val); + + if(GetZone() && basic_info.cur_power < basic_info.max_power) + GetZone()->AddDamagedSpawn(this); + + SendGroupUpdate(); + + if (IsNPC() && ((NPC*)this)->IsPet() && ((NPC*)this)->GetOwner() != nullptr && ((NPC*)this)->GetOwner()->IsPlayer()) { + Player* player = (Player*)((NPC*)this)->GetOwner(); + if (player->GetPet() && player->GetCharmedPet()) { + if (this == player->GetPet()) { + player->GetInfoStruct()->set_pet_power_pct((float)basic_info.cur_power / (float)basic_info.max_power); + player->SetCharSheetChanged(true); + } + } + else { + player->GetInfoStruct()->set_pet_power_pct((float)basic_info.cur_power / (float)basic_info.max_power); + player->SetCharSheetChanged(true); + } + } +} +void Spawn::SetTotalPowerBase(sint32 new_val) +{ + SetInfo(&basic_info.power_base, new_val); + + if(GetZone() && basic_info.cur_power < basic_info.max_power) + GetZone()->AddDamagedSpawn(this); + + SendGroupUpdate(); +} + +void Spawn::SetTotalPowerBaseInstance(sint32 new_val) +{ + SetInfo(&basic_info.power_base_instance, new_val); +} + +sint32 Spawn::GetPower() +{ + return basic_info.cur_power; +} +sint32 Spawn::GetTotalPower(){ + return basic_info.max_power; +} +sint32 Spawn::GetTotalPowerBase() +{ + return basic_info.power_base; +} + + +/*** SAVAGERY ***/ +void Spawn::SetSavagery(sint32 savagery, bool setUpdateFlags) +{ + /* JA: extremely limited functionality until we better understand Savagery */ + if(savagery > basic_info.max_savagery) + SetInfo(&basic_info.max_savagery, savagery, setUpdateFlags); + + SetInfo(&basic_info.cur_savagery, savagery, setUpdateFlags); +} +void Spawn::SetTotalSavagery(sint32 new_val) +{ + /* JA: extremely limited functionality until we better understand Savagery */ + if(basic_info.savagery_base == 0) + SetTotalSavageryBase(new_val); + + SetInfo(&basic_info.max_savagery, new_val); +} +void Spawn::SetTotalSavageryBase(sint32 new_val){ + SetInfo(&basic_info.savagery_base, new_val); + + SendGroupUpdate(); +} +sint32 Spawn::GetTotalSavagery() +{ + return basic_info.max_savagery; +} +sint32 Spawn::GetSavagery() +{ + return basic_info.cur_savagery; +} + + +/*** DISSONANCE ***/ +void Spawn::SetDissonance(sint32 dissonance, bool setUpdateFlags) +{ + /* JA: extremely limited functionality until we better understand Dissonance */ + if(dissonance > basic_info.max_dissonance) + SetInfo(&basic_info.max_dissonance, dissonance, setUpdateFlags); + + SetInfo(&basic_info.cur_dissonance, dissonance, setUpdateFlags); +} +void Spawn::SetTotalDissonance(sint32 new_val) +{ + /* JA: extremely limited functionality until we better understand Dissonance */ + if(basic_info.dissonance_base == 0) + SetTotalDissonanceBase(new_val); + + SetInfo(&basic_info.max_dissonance, new_val); + +} +void Spawn::SetTotalDissonanceBase(sint32 new_val) +{ + SetInfo(&basic_info.dissonance_base, new_val); + + SendGroupUpdate(); +} +sint32 Spawn::GetTotalDissonance() +{ + return basic_info.max_dissonance; +} +sint32 Spawn::GetDissonance() +{ + return basic_info.cur_dissonance; +} + +/* --< Alternate Advancement Points >-- */ +void Spawn::SetAssignedAA(sint16 new_val) +{ + SetInfo(&basic_info.assigned_aa, new_val); +} + +void Spawn::SetUnassignedAA(sint16 new_val) +{ + SetInfo(&basic_info.unassigned_aa, new_val); +} + +void Spawn::SetTradeskillAA(sint16 new_val) +{ + SetInfo(&basic_info.tradeskill_aa, new_val); +} + +void Spawn::SetUnassignedTradeskillAA(sint16 new_val) +{ + SetInfo(&basic_info.unassigned_tradeskill_aa, new_val); +} + +void Spawn::SetPrestigeAA(sint16 new_val) +{ + SetInfo(&basic_info.prestige_aa, new_val); +} + +void Spawn::SetUnassignedPrestigeAA(sint16 new_val) +{ + SetInfo(&basic_info.unassigned_prestige_aa, new_val); +} + +void Spawn::SetTradeskillPrestigeAA(sint16 new_val) +{ + SetInfo(&basic_info.tradeskill_prestige_aa, new_val); +} + +void Spawn::SetUnassignedTradeskillPrestigeAA(sint16 new_val) +{ + SetInfo(&basic_info.unassigned_tradeskill_prestige_aa, new_val); +} + +void Spawn::SetAAXPRewards(int32 value) +{ + SetInfo(&basic_info.aaxp_rewards, value, false); +} + +sint16 Spawn::GetAssignedAA() +{ + return basic_info.assigned_aa; +} + +sint16 Spawn::GetUnassignedAA() +{ + return basic_info.unassigned_aa; +} + +sint16 Spawn::GetTradeskillAA() +{ + return basic_info.tradeskill_aa; +} + +sint16 Spawn::GetUnassignedTradeskillAA() +{ + return basic_info.unassigned_tradeskill_aa; +} + +sint16 Spawn::GetPrestigeAA() +{ + return basic_info.prestige_aa; +} + +sint16 Spawn::GetUnassignedPretigeAA() +{ + return basic_info.unassigned_prestige_aa; +} + +sint16 Spawn::GetTradeskillPrestigeAA() +{ + return basic_info.tradeskill_prestige_aa; +} + +sint16 Spawn::GetUnassignedTradeskillPrestigeAA() +{ + return basic_info.unassigned_tradeskill_prestige_aa; +} + +int32 Spawn::GetAAXPRewards() +{ + return basic_info.aaxp_rewards; +} + +float Spawn::GetDistance(float x1, float y1, float z1, float x2, float y2, float z2){ + x1 = x1 - x2; + y1 = y1 - y2; + z1 = z1 - z2; + return sqrt(x1*x1 + y1*y1 + z1*z1); +} + +float Spawn::GetDistance(float x, float y, float z, float radius, bool ignore_y) { + if (ignore_y) + return GetDistance(x, y, z, GetX(), y, GetZ()) - radius; + else + return GetDistance(x, y, z, GetX(), GetY(), GetZ()) - radius; +} + +float Spawn::GetDistance(float x, float y, float z, bool ignore_y) { + return GetDistance(x, y, z, 0.0f, ignore_y); +} +float Spawn::GetDistance(Spawn* spawn, bool ignore_y, bool includeRadius){ + float ret = 0; + + if (spawn) + { + float radius = 0.0f; + if (includeRadius) + radius = CalculateRadius(spawn); + ret = GetDistance(spawn->GetX(), spawn->GetY(), spawn->GetZ(), radius, ignore_y); + } + + // maybe distance against ourselves, in that case we want to nullify the radius check + if (ret < 0) + ret = 0.0f; + + return ret; +} + +float Spawn::GetDistance(Spawn* spawn, float x1, float y1, float z1, bool includeRadius) { + float ret = 0; + + if (spawn) + { + float radius = 0.0f; + if (includeRadius) + radius = CalculateRadius(spawn); + ret = GetDistance(x1, y1, z1, spawn->GetX(), spawn->GetY(), spawn->GetZ()) - radius; + } + + // maybe distance against ourselves, in that case we want to nullify the radius check + if (ret < 0) + ret = 0.0f; + + return ret; +} + +float Spawn::CalculateRadius(Spawn* target) +{ + float srcRadius = short_to_float(appearance.pos.collision_radius); + if (target) + { + float targRadius = short_to_float(target->appearance.pos.collision_radius); + return (targRadius / 32.0f) + (srcRadius / 32.0f); + } + else + return (srcRadius / 32.0f); +} + +int32 Spawn::GetRespawnTime(){ + return respawn; +} + +void Spawn::SetRespawnTime(int32 time){ + respawn = time; +} + +int32 Spawn::GetExpireOffsetTime(){ + return expire_offset; +} + +void Spawn::SetExpireOffsetTime(int32 time){ + expire_offset = time; +} + +int32 Spawn::GetSpawnLocationID(){ + return spawn_location_id; +} + +void Spawn::SetSpawnLocationID(int32 id){ + spawn_location_id = id; +} + +int32 Spawn::GetSpawnEntryID(){ + return spawn_entry_id; +} + +void Spawn::SetSpawnEntryID(int32 id){ + spawn_entry_id = id; +} + +int32 Spawn::GetSpawnLocationPlacementID(){ + return spawn_location_spawns_id; +} + +void Spawn::SetSpawnLocationPlacementID(int32 id){ + spawn_location_spawns_id = id; +} + +const char* Spawn::GetSpawnScript(){ + if(spawn_script.length() > 0) + return spawn_script.c_str(); + else + return 0; +} + +void Spawn::SetSpawnScript(string name){ + spawn_script = name; +} + +void Spawn::SetPrimaryCommand(const char* name, const char* command, float distance){ + EntityCommand* entity_command = CreateEntityCommand(name, distance, command, "", 0, 0); + if(primary_command_list.size() > 0 && primary_command_list[0]){ + safe_delete(primary_command_list[0]); + primary_command_list[0] = entity_command; + } + else + primary_command_list.push_back(entity_command); +} + +void Spawn::SetSecondaryCommands(vector* commands){ + if(commands && commands->size() > 0){ + vector::iterator itr; + if(secondary_command_list.size() > 0){ + for(itr = secondary_command_list.begin(); itr != secondary_command_list.end(); itr++){ + safe_delete(*itr); + } + secondary_command_list.clear(); + } + EntityCommand* command = 0; + for(itr = commands->begin(); itr != commands->end(); itr++){ + command = CreateEntityCommand(*itr); + secondary_command_list.push_back(command); + } + } +} + +void Spawn::SetPrimaryCommands(vector* commands){ + if(commands && commands->size() > 0){ + vector::iterator itr; + if(primary_command_list.size() > 0){ + for(itr = primary_command_list.begin(); itr != primary_command_list.end(); itr++){ + safe_delete(*itr); + } + primary_command_list.clear(); + } + EntityCommand* command = 0; + for(itr = commands->begin(); itr != commands->end(); itr++){ + command = CreateEntityCommand(*itr); + primary_command_list.push_back(command); + } + } +} + +EntityCommand* Spawn::FindEntityCommand(string command, bool primaryOnly) { + EntityCommand* entity_command = 0; + if (primary_command_list.size() > 0) { + vector::iterator itr; + for (itr = primary_command_list.begin(); itr != primary_command_list.end(); itr++) { + if ((*itr)->command.compare(command) == 0) { + entity_command = *itr; + break; + } + } + } + + if (primaryOnly) + return entity_command; + + if (!entity_command && secondary_command_list.size() > 0) { + vector::iterator itr; + for (itr = secondary_command_list.begin(); itr != secondary_command_list.end(); itr++) { + if ((*itr)->command == command) { + entity_command = *itr; + break; + } + } + } + return entity_command; +} + +void Spawn::SetSizeOffset(int8 offset){ + size_offset = offset; +} + +int8 Spawn::GetSizeOffset(){ + return size_offset; +} + +void Spawn::SetMerchantID(int32 val){ + merchant_id = val; +} + +int32 Spawn::GetMerchantID(){ + return merchant_id; +} + +void Spawn::SetMerchantType(int8 val) { + merchant_type = val; +} + +int8 Spawn::GetMerchantType() { + return merchant_type; +} + +void Spawn::SetMerchantLevelRange(int32 minLvl, int32 maxLvl) { + merchant_min_level = minLvl; + merchant_max_level = maxLvl; +} + +int32 Spawn::GetMerchantMinLevel() { + return merchant_min_level; +} + +int32 Spawn::GetMerchantMaxLevel() { + return merchant_max_level; +} + +bool Spawn::IsClientInMerchantLevelRange(Client* client, bool sendMessageIfDenied) +{ + if (!client) + return false; + + if (GetMerchantMinLevel() && client->GetPlayer()->GetLevel() < GetMerchantMinLevel()) + { + client->Message(CHANNEL_COLOR_RED, "You are unable to interact with this merchant due to a minimum level %u allowed.", GetMerchantMinLevel()); + return false; + } + else if (GetMerchantMaxLevel() && client->GetPlayer()->GetLevel() > GetMerchantMaxLevel()) + { + client->Message(CHANNEL_COLOR_RED, "You are unable to interact with this merchant due to a maximum level %u allowed.", GetMerchantMaxLevel()); + return false; + } + + return true; +} + +void Spawn::SetQuestsRequired(Spawn* new_spawn){ + m_requiredQuests.writelock(__FUNCTION__, __LINE__); + if(required_quests.size() > 0){ + map* >::iterator itr; + for(itr = required_quests.begin(); itr != required_quests.end(); itr++){ + vector* quest_steps = itr->second; + for (int32 i = 0; i < quest_steps->size(); i++) + new_spawn->SetQuestsRequired(itr->first, quest_steps->at(i)); + } + } + m_requiredQuests.releasewritelock(__FUNCTION__, __LINE__); +} + +void Spawn::SetQuestsRequired(int32 quest_id, int16 quest_step){ + m_requiredQuests.writelock(__FUNCTION__, __LINE__); + if (required_quests.count(quest_id) == 0) + required_quests.insert(make_pair(quest_id, new vector)); + else{ + for (int32 i = 0; i < required_quests[quest_id]->size(); i++){ + if (required_quests[quest_id]->at(i) == quest_step){ + m_requiredQuests.releasewritelock(__FUNCTION__, __LINE__); + return; + } + } + } + + has_quests_required = true; + required_quests[quest_id]->push_back(quest_step); + m_requiredQuests.releasewritelock(__FUNCTION__, __LINE__); +} + +bool Spawn::HasQuestsRequired(){ + return has_quests_required; +} + +bool Spawn::HasHistoryRequired(){ + return has_history_required; +} + +void Spawn::SetRequiredHistory(int32 event_id, int32 value1, int32 value2){ + LUAHistory* set_value = new LUAHistory(); + set_value->Value = value1; + set_value->Value2 = value2; + set_value->SaveNeeded = false; + m_requiredHistory.writelock(__FUNCTION__, __LINE__); + if (required_history.count(event_id) == 0) + required_history.insert(make_pair(event_id, set_value)); + else + { + LUAHistory* tmp_value = required_history[event_id]; + required_history[event_id] = set_value; + safe_delete(tmp_value); + } + has_history_required = true; + m_requiredHistory.releasewritelock(__FUNCTION__, __LINE__); +} + +void Spawn::SetTransporterID(int32 id){ + transporter_id = id; +} + +int32 Spawn::GetTransporterID(){ + return transporter_id; +} + +void Spawn::InitializePosPacketData(Player* player, PacketStruct* packet, bool bSpawnUpdate) { + int16 version = packet->GetVersion(); + + int32 new_grid_id = 0; + int32 new_widget_id = 0; + float new_y = 0.0f; + float ground_diff = 0.0f; + if(player->GetMap() != nullptr && player->GetMap()->IsMapLoaded()) + { + m_GridMutex.writelock(__FUNCTION__, __LINE__); + std::map::iterator itr = established_grid_id.find(version); + if ( itr == established_grid_id.end() || itr->second.timestamp <= (Timer::GetCurrentTime2())) + { + if(itr != established_grid_id.end() && itr->second.x == GetX() && itr->second.z == GetZ() && !itr->second.npc_save) { + itr->second.timestamp = Timer::GetCurrentTime2()+100; + itr->second.npc_save = false; + new_grid_id = itr->second.grid_id; + new_widget_id = itr->second.widget_id; + new_y = itr->second.offset_y; + + if(player->GetMap() != GetMap()) { + ground_diff = sqrtf((GetY() - itr->second.zone_ground_y) * (GetY() - itr->second.zone_ground_y)); + } + } + else { + auto loc = glm::vec3(GetX(), GetZ(), GetY()); + new_y = player->FindBestZ(loc, nullptr, &new_grid_id, &new_widget_id); + float zone_ground_y = new_y; + if(player->GetMap() != GetMap()) { + zone_ground_y = FindBestZ(loc, nullptr, &new_grid_id, &new_widget_id); + } + TimedGridData data; + data.grid_id = new_grid_id; + data.widget_id = new_widget_id; + data.x = GetX(); + data.y = GetY(); + data.z = GetZ(); + data.offset_y = new_y; + data.zone_ground_y = zone_ground_y; + ground_diff = sqrtf((GetY() - zone_ground_y) * (GetY() - zone_ground_y)); + data.npc_save = false; + data.timestamp = Timer::GetCurrentTime2()+100; + established_grid_id.insert(make_pair(packet->GetVersion(), data)); + } + } + else { + new_grid_id = itr->second.grid_id; + new_widget_id = itr->second.widget_id; + new_y = itr->second.offset_y; + ground_diff = sqrtf((GetY() - itr->second.zone_ground_y) * (GetY() - itr->second.zone_ground_y)); + } + m_GridMutex.releasewritelock(__FUNCTION__, __LINE__); + } + + if(IsKnockedBack()) { + packet->setDataByName("pos_grid_id", 0); + } + else { + packet->setDataByName("pos_grid_id", new_grid_id != 0 ? new_grid_id : GetLocation()); + } + + bool include_heading = true; + if (IsWidget() && ((Widget*)this)->GetIncludeHeading() == false) + include_heading = false; + else if (IsSign() && ((Sign*)this)->GetIncludeHeading() == false) + include_heading = false; + + if (include_heading){ + packet->setDataByName("pos_heading1", appearance.pos.Dir1); + packet->setDataByName("pos_heading2", appearance.pos.Dir2); + } + + if (version <= 910) { + packet->setDataByName("pos_collision_radius", appearance.pos.collision_radius > 0 ? appearance.pos.collision_radius : 32); + packet->setDataByName("pos_size", size > 0 ? size : 32); + packet->setDataByName("pos_size_multiplier", 32); //32 is normal + } + else { + if (size == 0) + size = 32; + + packet->setDataByName("pos_collision_radius", appearance.pos.collision_radius > 0 ? appearance.pos.collision_radius : 32); + + packet->setDataByName("pos_size", 1.0f); + + if (!IsPlayer()) + packet->setDataByName("pos_size", size > 0 ? (((float)size) / 32.0f) : 1.0f); // float not an integer + else + packet->setDataByName("pos_size", 1.0f); + + // please do not remove! This makes it so NPCs for example do not resize large/small when you are in combat with them! + packet->setDataByName("pos_size_ratio", 1.0f); + } + packet->setDataByName("pos_state", appearance.pos.state); + + bool include_location = true; + if (IsWidget() && ((Widget*)this)->GetIncludeLocation() == false) + include_location = false; + else if (IsSign() && ((Sign*)this)->GetIncludeLocation() == false) + include_location = false; + + if (include_location){ + if (IsWidget() && ((Widget*)this)->GetMultiFloorLift()) { + Widget* widget = (Widget*)this; + float x = appearance.pos.X - widget->GetWidgetX(); + float y = appearance.pos.Y - widget->GetWidgetY(); + float z = appearance.pos.Z - widget->GetWidgetZ(); + + packet->setDataByName("pos_x", x); + packet->setDataByName("pos_y", y); + packet->setDataByName("pos_z", z); + } + else { + packet->setDataByName("pos_x", appearance.pos.X); + float result_y = appearance.pos.Y; + if(!IsWidget() && !IsSign() && !(IsFlyingCreature() || IsWaterCreature() || InWater())) { + result_y = new_y; + } + if(GetMap() != player->GetMap()) { + result_y = new_y; + if(IsFlyingCreature() || IsWaterCreature() || InWater()) { + result_y += ground_diff; + } + } + packet->setDataByName("pos_y", result_y); + packet->setDataByName("pos_z", appearance.pos.Z); + } + if (IsSign()) + packet->setDataByName("pos_unknown6", 3, 2); + } + + if (IsPlayer()) { + Skill* skill = ((Player*)this)->GetSkillByName("Swimming", false); + sint16 swim_modifier = rule_manager.GetGlobalRule(R_Player, SwimmingSkillMinSpeed)->GetSInt16(); + if(skill) { + sint16 max_val = 450; + if(skill->max_val > 0) + max_val = skill->max_val; + sint16 max_swim_mod = rule_manager.GetGlobalRule(R_Player, SwimmingSkillMaxSpeed)->GetSInt16(); + float diff = (float)(skill->current_val + ((Player*)this)->GetStat(ITEM_STAT_SWIMMING)) / (float)max_val; + sint16 diff_mod = (sint16)(float)max_swim_mod * diff; + if(diff_mod > max_swim_mod) + swim_modifier = max_swim_mod; + else if(diff_mod > swim_modifier) + swim_modifier = diff_mod; + } + packet->setDataByName("pos_swim_speed_modifier", swim_modifier); + packet->setDataByName("pos_x_velocity", TransformFromFloat(GetSpeedX(), 5)); + packet->setDataByName("pos_y_velocity", TransformFromFloat(GetSpeedY(), 5)); + packet->setDataByName("pos_z_velocity", TransformFromFloat(GetSpeedZ(), 5)); + } + if (appearance.pos.X2 == 0 && appearance.pos.Y2 == 0 && appearance.pos.Z2 && (appearance.pos.X != 0 || appearance.pos.Y != 0 || appearance.pos.Z != 0)) { + appearance.pos.X2 = appearance.pos.X; + appearance.pos.Y2 = appearance.pos.Y; + appearance.pos.Z2 = appearance.pos.Z; + } + if (appearance.pos.X3 == 0 && appearance.pos.Y3 == 0 && appearance.pos.Z3 && (appearance.pos.X != 0 || appearance.pos.Y != 0 || appearance.pos.Z != 0)) { + appearance.pos.X3 = appearance.pos.X; + appearance.pos.Y3 = appearance.pos.Y; + appearance.pos.Z3 = appearance.pos.Z; + } + //Transform To/From Float bits (original client) + //pos_loc_offset[3]: 5 + //pos_x_velocity: 5 + //pos_y_velocity: 5 + //pos_z_velocity: 5 + //pos_heading1: 6 + //pos_heading2: 6 + //pos_speed: 8 + //pos_dest_loc_offset[3]: 5 + //pos_dest_loc_offset2[3]: 5 + //pos_heading_speed: 5 + //pos_move_type: 5 (speed_modifier) + //pos_swim_speed_modifier: 5 + //pos_side_speed: 8 + //pos_vert_speed: 8 + //pos_requested_pitch: 6 + //pos_requested_pitch_speed: 5 + //pos_pitch: 6 + //pos_collision_radius: 5 + //pos_size: 5 + //actor_stop_range: 5 + //this is for original box client, destinations used to be offsets + if(version <= 373 || version > 561 || !IsPlayer()) { + if (appearance.pos.X2 != 0 || appearance.pos.Y2 != 0 || appearance.pos.Z2 != 0) { + packet->setDataByName("pos_dest_loc_offset", TransformFromFloat(appearance.pos.X2 - appearance.pos.X, 5)); + packet->setDataByName("pos_dest_loc_offset", TransformFromFloat(appearance.pos.Y2 - appearance.pos.Y, 5), 1); + packet->setDataByName("pos_dest_loc_offset", TransformFromFloat(appearance.pos.Z2 - appearance.pos.Z, 5), 2); + } + if (appearance.pos.X3 != 0 || appearance.pos.Y3 != 0 || appearance.pos.Z3 != 0) { + packet->setDataByName("pos_dest_loc_offset2", TransformFromFloat(appearance.pos.X3 - appearance.pos.X, 5)); + packet->setDataByName("pos_dest_loc_offset2", TransformFromFloat(appearance.pos.Y3 - appearance.pos.Y, 5), 1); + packet->setDataByName("pos_dest_loc_offset2", TransformFromFloat(appearance.pos.Z3 - appearance.pos.Z, 5), 2); + } + } + + bool bSendSpeed = true; + + // fixes lifts dropping back to the floor in zones like northfreeport when set as TransportSpawn (Some zones do not support multifloor lifts) + if (IsWidget() && (((Widget*)this)->GetMultiFloorLift() || (IsTransportSpawn()))) { + Widget* widget = (Widget*)this; + + float x; + float y; + float z; + + bool setCoords = false; + if(!widget->GetMultiFloorLift() && version >= 561) { + packet->setDataByName("pos_next_x", appearance.pos.X2); + packet->setDataByName("pos_next_y", appearance.pos.Y2); + packet->setDataByName("pos_next_z", appearance.pos.Z2); + + packet->setDataByName("pos_x3", appearance.pos.X); + packet->setDataByName("pos_y3", appearance.pos.Y); + packet->setDataByName("pos_z3", appearance.pos.Z); + setCoords = true; + } + else if (IsRunning()){ + x = appearance.pos.X2 - widget->GetWidgetX(); + y = appearance.pos.Y2 - widget->GetWidgetY(); + z = appearance.pos.Z2 - widget->GetWidgetZ(); + } + else { + x = appearance.pos.X - widget->GetWidgetX(); + y = appearance.pos.Y - widget->GetWidgetY(); + z = appearance.pos.Z - widget->GetWidgetZ(); + } + + if(!setCoords) { + packet->setDataByName("pos_next_x", x); + packet->setDataByName("pos_next_y", y); + packet->setDataByName("pos_next_z", z); + + packet->setDataByName("pos_x3", x); + packet->setDataByName("pos_y3", y); + packet->setDataByName("pos_z3", z); + } + } + else if(IsFlyingCreature() || IsWaterCreature() || InWater()) { + packet->setDataByName("pos_next_x", appearance.pos.X2); + packet->setDataByName("pos_next_y", (GetMap() != player->GetMap()) ? (ground_diff + new_y) : appearance.pos.Y2); + packet->setDataByName("pos_next_z", appearance.pos.Z2); + + packet->setDataByName("pos_x3", appearance.pos.X); + packet->setDataByName("pos_y3", (GetMap() != player->GetMap()) ? (ground_diff + new_y) : appearance.pos.Y); + packet->setDataByName("pos_z3", appearance.pos.Z); + } + //If this is a spawn update or this spawn is currently moving we can send these values, otherwise set speed and next_xyz to 0 + //This fixes the bug where spawns with movement scripts face south when initially spawning if they are at their target location. + else if (bSpawnUpdate || memcmp(&appearance.pos.X, &appearance.pos.X2, sizeof(float) * 3) != 0) { + packet->setDataByName("pos_next_x", appearance.pos.X2); + packet->setDataByName("pos_next_y", appearance.pos.Y2); + packet->setDataByName("pos_next_z", appearance.pos.Z2); + packet->setDataByName("pos_x3", appearance.pos.X3); + packet->setDataByName("pos_y3", appearance.pos.Y3); + packet->setDataByName("pos_z3", appearance.pos.Z3); + } + else + { + bSendSpeed = false; + } + //packet->setDataByName("pos_unknown2", 4, 2); + + int16 speed_multiplier = rule_manager.GetGlobalRule(R_Spawn, SpeedMultiplier)->GetInt16(); // was 1280, 600 and now 300... investigating why + int8 movement_mode = 0; + if (IsPlayer()) { + Player* player = static_cast(this); + sint16 pos_packet_speed = player->GetPosPacketSpeed() * speed_multiplier; + sint16 side_speed = player->GetSideSpeed() * speed_multiplier; + packet->setDataByName("pos_speed", pos_packet_speed); + packet->setDataByName("pos_side_speed", side_speed); + } + else if (bSendSpeed && (!IsNPC() || Alive())) { + sint16 side_speed = GetSpeed() * speed_multiplier; + packet->setDataByName("pos_speed", side_speed); + if(side_speed != 0 && ((IsWidget() && ((Widget*)this)->GetMultiFloorLift()) || IsTransportSpawn())) { + movement_mode = 2; + } + } + else if(((IsWidget() && ((Widget*)this)->GetMultiFloorLift()) || (!IsNPC() && version <= 561 && !IsRunning()))) { + movement_mode = 2; + } + if(IsFlyingCreature() || IsWaterCreature() || InWater()) { + movement_mode = 2; + } + + if (IsNPC() || IsPlayer()) { + packet->setDataByName("pos_move_type", 25); + } + else if (IsWidget() || IsSign()) { + packet->setDataByName("pos_move_type", 11); + } + else if(IsGroundSpawn()) { + packet->setDataByName("pos_move_type", 16); + } + + if (!IsPlayer()) { // has to be 2 or NPC's warp around when moving + packet->setDataByName("pos_movement_mode", movement_mode); + } + + packet->setDataByName("face_actor_id", 0xFFFFFFFF); + + packet->setDataByName("pos_pitch1", appearance.pos.Pitch1); + packet->setDataByName("pos_pitch2", appearance.pos.Pitch2); + packet->setDataByName("pos_roll", appearance.pos.Roll); +} + +void Spawn::InitializeInfoPacketData(Player* spawn, PacketStruct* packet) { + int16 version = packet->GetVersion(); + + bool spawnHiddenFromClient = false; + + int8 classicFlags = 0; + // radius of 0 is always seen, -1 is never seen (unless items/spells override), larger than 0 is a defined radius to restrict visibility + sint32 radius = rule_manager.GetGlobalRule(R_PVP, InvisPlayerDiscoveryRange)->GetSInt32(); + if (radius != 0 && (Spawn*)spawn != this && this->IsPlayer() && !spawn->CanSeeInvis((Entity*)this)) + spawnHiddenFromClient = true; + + if (!spawnHiddenFromClient && (appearance.targetable == 1 || appearance.show_level == 1 || appearance.display_name == 1)) { + if (!IsObject() && !IsGroundSpawn() && !IsWidget() && !IsSign()) { + int8 percent = 0; + if (GetHP() > 0) + percent = (int8)(((float)GetHP() / GetTotalHP()) * 100); + + if (version >= 373) { + if (percent < 100) { + packet->setDataByName("hp_remaining", 100 ^ percent); + } + else + packet->setDataByName("hp_remaining", 0); + } + else { + if (percent > 100) + percent = 100; + packet->setDataByName("hp_remaining", percent); + } + if (GetTotalPower() > 0) { + percent = (int8)(((float)GetPower() / GetTotalPower()) * 100); + if (percent > 0) + packet->setDataByName("power_percent", percent); + else + packet->setDataByName("power_percent", 0); + } + } + } + + if (version <= 561) { + packet->setDataByName("name", appearance.name); + for (int8 i = 0; i < 8; i++) + packet->setDataByName("unknown1", 0xFF, i); + if (appearance.show_level == 0) + packet->setDataByName("hide_health", 1); + } + if (GetHP() <= 0 && IsEntity()) { + packet->setDataByName("corpse", 1); + if(HasLoot()) + packet->setDataByName("loot_icon", 1); + } + if (!IsPlayer()) + packet->setDataByName("npc", 1); + if (GetMerchantID() > 0) + packet->setDataByName("merchant", 1); + + packet->setDataByName("effective_level", IsEntity() && ((Entity*)this)->GetInfoStruct()->get_effective_level() != 0 ? (int8)((Entity*)this)->GetInfoStruct()->get_effective_level() : (int8)GetLevel()); + packet->setDataByName("level", (int8)GetLevel()); + packet->setDataByName("unknown4", (int8)GetLevel()); + packet->setDataByName("difficulty", GetDifficulty()); //6); + packet->setDataByName("unknown6", 1); + packet->setDataByName("heroic_flag", appearance.heroic_flag); + packet->setDataByName("class", appearance.adventure_class); + + int16 model_type = appearance.model_type; + if (GetIllusionModel() != 0) { + if (IsPlayer()) { + if (((Player*)this)->get_character_flag(CF_SHOW_ILLUSION)) { + model_type = GetIllusionModel(); + } + } + else + model_type = GetIllusionModel(); + } + + int16 sogaModelType = appearance.soga_model_type; + if (spawnHiddenFromClient) + { + model_type = 0; + sogaModelType = 0; + } + + if(version <= 373 && (model_type == 5864 || model_type == 5865 || model_type == 4015)) { + model_type = 4034; + } + else if(version <= 561 && model_type == 7039) { // goblin + + model_type = 145; + } + packet->setDataByName("model_type", model_type); + if (appearance.soga_model_type == 0) + packet->setDataByName("soga_model_type", model_type); + else + packet->setDataByName("soga_model_type", sogaModelType); + + int16 action_state = appearance.action_state; + if(IsEntity()) { + std::string actionState = ""; + if (GetTempActionState() >= 0) { + action_state = GetTempActionState(); + actionState = ((Entity*)this)->GetInfoStruct()->get_combat_action_state(); + } + else { + actionState = ((Entity*)this)->GetInfoStruct()->get_action_state(); + } + + Client* client = spawn->GetClient(); + if(IsEntity() && client) { + if(actionState.size() > 0) { + Emote* emote = visual_states.FindEmote(actionState, client->GetVersion()); + if(emote != NULL) + action_state = emote->GetVisualState(); + } + } + } + packet->setDataByName("action_state", action_state); + + bool scaredOfPlayer = false; + + if(IsCollector() && spawn->GetCollectionList()->HasCollectionsToHandIn()) + packet->setDataByName("visual_state", VISUAL_STATE_COLLECTION_TURN_IN); + else if(!IsRunning() && IsNPC() && IsScaredByStrongPlayers() && spawn->GetArrowColor(GetLevel()) == ARROW_COLOR_GRAY && + (GetDistance(spawn)) <= ((NPC*)this)->GetAggroRadius() && CheckLoS(spawn)) { + packet->setDataByName("visual_state", VISUAL_STATE_IDLE_AFRAID); + scaredOfPlayer = true; + } + else if (GetTempVisualState() >= 0) + packet->setDataByName("visual_state", GetTempVisualState()); + else + packet->setDataByName("visual_state", appearance.visual_state); + + if (IsNPC() && !IsPet() && !scaredOfPlayer) + { + if(((Entity*)this)->GetInfoStruct()->get_interaction_flag()) { + if(((Entity*)this)->GetInfoStruct()->get_interaction_flag() == 255) { + packet->setDataByName("interaction_flag", 0); + classicFlags += INFO_CLASSIC_FLAG_NOLOOK; + } + else { + packet->setDataByName("interaction_flag", ((Entity*)this)->GetInfoStruct()->get_interaction_flag()); //this makes NPCs head turn to look at you (12) + } + } + else { + packet->setDataByName("interaction_flag", 12); //turn head since no other value is set + } + } + packet->setDataByName("emote_state", appearance.emote_state); + packet->setDataByName("mood_state", appearance.mood_state); + packet->setDataByName("gender", appearance.gender); + packet->setDataByName("race", appearance.race); + if (IsEntity()) { + Entity* entity = ((Entity*)this); + packet->setDataByName("combat_voice", entity->GetCombatVoice()); + packet->setDataByName("emote_voice", entity->GetEmoteVoice()); + for (int i = 0; i < 25; i++) { + if (i == 2) { //don't send helm if hidden flag + if (IsPlayer()) { + if (((Player*)this)->get_character_flag(CF_HIDE_HELM)) { + packet->setDataByName("equipment_types", 0, i); + packet->setColorByName("equipment_colors", 0, i); + packet->setColorByName("equipment_highlights", 0, i); + continue; + } + } + if (IsBot()) { + if (!((Bot*)this)->ShowHelm) { + packet->setDataByName("equipment_types", 0, i); + packet->setColorByName("equipment_colors", 0, i); + packet->setColorByName("equipment_highlights", 0, i); + continue; + } + } + } + else if (i == 19) { //don't send cloak if hidden + if (IsPlayer()) { + if (!((Player*)this)->get_character_flag(CF_SHOW_CLOAK)) { + packet->setDataByName("equipment_types", 0, i); + packet->setColorByName("equipment_colors", 0, i); + packet->setColorByName("equipment_highlights", 0, i); + continue; + } + } + if (IsBot()) { + if (!((Bot*)this)->ShowCloak) { + packet->setDataByName("equipment_types", 0, i); + packet->setColorByName("equipment_colors", 0, i); + packet->setColorByName("equipment_highlights", 0, i); + continue; + } + } + } + entity->MEquipment.lock(); + packet->setDataByName("equipment_types", entity->equipment.equip_id[i], i); + packet->setColorByName("equipment_colors", entity->equipment.color[i], i); + packet->setColorByName("equipment_highlights", entity->equipment.highlight[i], i); + entity->MEquipment.unlock(); + + } + packet->setDataByName("mount_type", entity->GetMount()); + + // find the visual flags + int8 vis_flag = 0; + //Invis + crouch flag check + if (entity->IsStealthed()) { + vis_flag += (INFO_VIS_FLAG_INVIS + INFO_VIS_FLAG_CROUCH); + classicFlags += INFO_VIS_FLAG_INVIS + INFO_VIS_FLAG_CROUCH; + } + //Invis flag check + else if (entity->IsInvis()) { + vis_flag += INFO_VIS_FLAG_INVIS; + classicFlags += INFO_VIS_FLAG_INVIS; + } + + //Mount flag check + if (entity->GetMount() > 0) { + vis_flag += INFO_VIS_FLAG_MOUNTED; + } + + //Hide hood check + bool vis_hide_hood = false; + if (IsPlayer() && ((Player*)this)->get_character_flag(CF_HIDE_HOOD)) { + if(version > 561) { + vis_flag += INFO_VIS_FLAG_HIDE_HOOD; + } + vis_hide_hood = true; + } + else if(IsPlayer()) { + classicFlags += INFO_CLASSIC_FLAG_SHOW_HOOD; + } + + if(!vis_hide_hood && appearance.hide_hood && version > 561) { + vis_flag += INFO_VIS_FLAG_HIDE_HOOD; + } + + if(version <= 561) { + packet->setDataByName("flags", classicFlags); + } + packet->setDataByName("visual_flag", vis_flag); + packet->setColorByName("mount_saddle_color", entity->GetMountSaddleColor()); + packet->setColorByName("mount_color", entity->GetMountColor()); + packet->setDataByName("hair_type_id", entity->features.hair_type); + packet->setDataByName("chest_type_id", entity->features.chest_type); + packet->setDataByName("wing_type_id", entity->features.wing_type); + packet->setDataByName("legs_type_id", entity->features.legs_type); + packet->setDataByName("soga_hair_type_id", entity->features.soga_hair_type); + packet->setDataByName("facial_hair_type_id", entity->features.hair_face_type); + packet->setDataByName("soga_facial_hair_type_id", entity->features.soga_hair_face_type); + for (int i = 0; i < 3; i++) { + packet->setDataByName("eye_type", entity->features.eye_type[i], i); + packet->setDataByName("ear_type", entity->features.ear_type[i], i); + packet->setDataByName("eye_brow_type", entity->features.eye_brow_type[i], i); + packet->setDataByName("cheek_type", entity->features.cheek_type[i], i); + packet->setDataByName("lip_type", entity->features.lip_type[i], i); + packet->setDataByName("chin_type", entity->features.chin_type[i], i); + packet->setDataByName("nose_type", entity->features.nose_type[i], i); + packet->setDataByName("soga_eye_type", entity->features.soga_eye_type[i], i); + packet->setDataByName("soga_ear_type", entity->features.soga_ear_type[i], i); + packet->setDataByName("soga_eye_brow_type", entity->features.soga_eye_brow_type[i], i); + packet->setDataByName("soga_cheek_type", entity->features.soga_cheek_type[i], i); + packet->setDataByName("soga_lip_type", entity->features.soga_lip_type[i], i); + packet->setDataByName("soga_chin_type", entity->features.soga_chin_type[i], i); + packet->setDataByName("soga_nose_type", entity->features.soga_nose_type[i], i); + } + + packet->setColorByName("skin_color", entity->features.skin_color); + packet->setColorByName("model_color", entity->features.model_color); + packet->setColorByName("eye_color", entity->features.eye_color); + packet->setColorByName("hair_type_color", entity->features.hair_type_color); + packet->setColorByName("hair_type_highlight_color", entity->features.hair_type_highlight_color); + packet->setColorByName("hair_face_color", entity->features.hair_face_color); + packet->setColorByName("hair_face_highlight_color", entity->features.hair_face_highlight_color); + packet->setColorByName("hair_highlight", entity->features.hair_highlight_color); + packet->setColorByName("wing_color1", entity->features.wing_color1); + packet->setColorByName("wing_color2", entity->features.wing_color2); + packet->setColorByName("hair_color1", entity->features.hair_color1); + packet->setColorByName("hair_color2", entity->features.hair_color2); + packet->setColorByName("soga_skin_color", entity->features.soga_skin_color); + packet->setColorByName("soga_model_color", entity->features.soga_model_color); + packet->setColorByName("soga_eye_color", entity->features.soga_eye_color); + packet->setColorByName("soga_hair_color1", entity->features.soga_hair_color1); + packet->setColorByName("soga_hair_color2", entity->features.soga_hair_color2); + packet->setColorByName("soga_hair_type_color", entity->features.soga_hair_type_color); + packet->setColorByName("soga_hair_type_highlight_color", entity->features.soga_hair_type_highlight_color); + packet->setColorByName("soga_hair_face_color", entity->features.soga_hair_face_color); + packet->setColorByName("soga_hair_face_highlight_color", entity->features.soga_hair_face_highlight_color); + packet->setColorByName("soga_hair_highlight", entity->features.soga_hair_highlight_color); + + packet->setDataByName("body_size", entity->features.body_size); + packet->setDataByName("body_age", entity->features.body_age); + + packet->setDataByName("soga_body_size", entity->features.soga_body_size); + packet->setDataByName("soga_body_age", entity->features.soga_body_age); + } + else { + EQ2_Color empty; + empty.red = 255; + empty.blue = 255; + empty.green = 255; + packet->setColorByName("skin_color", empty); + packet->setColorByName("model_color", empty); + packet->setColorByName("eye_color", empty); + packet->setColorByName("soga_skin_color", empty); + packet->setColorByName("soga_model_color", empty); + packet->setColorByName("soga_eye_color", empty); + } + if (appearance.icon == 0) { + if (appearance.attackable == 1) + appearance.icon = 0; + else if (GetDifficulty() > 0) + appearance.icon = 4; + else + appearance.icon = 6; + } + + // If Coe+ clients modify the values before we send + // if not then just send the value we have. + int8 temp_icon = appearance.icon; + + //Check if we need to add the hand icon.. + if ((temp_icon & 6) != 6 && appearance.display_hand_icon) { + temp_icon |= 6; + } + + //Icon value 28 for boats, set this without modifying the value + if (version >= 1188 && temp_icon != 28) { + if ((temp_icon & 64) > 0) { + temp_icon -= 64; // remove the DoV value; + temp_icon += 128; // add the CoE value + } + if ((temp_icon & 32) > 0) { + temp_icon -= 32; // remove the DoV value; + temp_icon += 64; // add the CoE value + } + if ((temp_icon & 4) > 0) { + temp_icon -= 4; // remove DoV value + temp_icon += 8; // add the CoE icon + } + if ((temp_icon & 6) > 0) { + temp_icon -= 10; // remove DoV value + temp_icon += 12; // add the CoE icon + } + } + packet->setDataByName("icon", temp_icon);//appearance.icon); + + int32 temp_activity_status = 0; + + if (!Alive() && GetTotalHP() > 0 && !IsObject() && !IsGroundSpawn()) + temp_activity_status = 1; + + if(version >= 1188 && GetChestDropTime()) { + temp_activity_status = 0; + } + temp_activity_status += (IsNPC() || IsObject() || IsGroundSpawn()) ? 1 << 1 : 0; + if (version > 561) { + // Fix widget or sign having 'Play Legends of Norrath' or 'Tell' options in right click (client hard-coded entity commands) + if(IsWidget() || IsSign()) + temp_activity_status = 2; + + if (IsGroundSpawn() || GetShowHandIcon()) + temp_activity_status += ACTIVITY_STATUS_INTERACTABLE_1188; + + if ((appearance.activity_status & ACTIVITY_STATUS_ROLEPLAYING) > 0) + temp_activity_status += ACTIVITY_STATUS_ROLEPLAYING_1188; + + if ((appearance.activity_status & ACTIVITY_STATUS_ANONYMOUS) > 0) + temp_activity_status += ACTIVITY_STATUS_ANONYMOUS_1188; + + if ((appearance.activity_status & ACTIVITY_STATUS_LINKDEAD) > 0) + temp_activity_status += ACTIVITY_STATUS_LINKDEAD_1188; + + if ((appearance.activity_status & ACTIVITY_STATUS_CAMPING) > 0) + temp_activity_status += ACTIVITY_STATUS_CAMPING_1188; + + if ((appearance.activity_status & ACTIVITY_STATUS_LFG) > 0) + temp_activity_status += ACTIVITY_STATUS_LFG_1188; + + if ((appearance.activity_status & ACTIVITY_STATUS_LFW) > 0) + temp_activity_status += ACTIVITY_STATUS_LFW_1188; + + if ((appearance.activity_status & ACTIVITY_STATUS_SOLID) > 0) + temp_activity_status += ACTIVITY_STATUS_SOLID_1188; + + if ((appearance.activity_status & ACTIVITY_STATUS_IMMUNITY_GAINED) > 0) + temp_activity_status += ACTIVITY_STATUS_IMMUNITY_GAINED_1188; + + if ((appearance.activity_status & ACTIVITY_STATUS_IMMUNITY_REMAINING) > 0) + temp_activity_status += ACTIVITY_STATUS_IMMUNITY_REMAINING_1188; + + if ((appearance.activity_status & ACTIVITY_STATUS_AFK) > 0) + temp_activity_status += ACTIVITY_STATUS_AFK_1188; + + if (EngagedInCombat()) + temp_activity_status += ACTIVITY_STATUS_INCOMBAT_1188; + + // if this is either a boat or lift let the client be manipulated by the object + // doesn't work for DoF client version 546 + if (appearance.icon == 28 || appearance.icon == 12 || IsTransportSpawn()) + { + // there is some other flags that setting with a transport breaks their solidity/ability to properly transport + // thus we just consider the following flags for now as all necessary + temp_activity_status = ACTIVITY_STATUS_SOLID_1188; + temp_activity_status += ACTIVITY_STATUS_ISTRANSPORT_1188; + } + } + else if (version == 561) { + // Fix widget or sign having 'Play Legends of Norrath' or 'Tell' options in right click (client hard-coded entity commands) + if(IsWidget() || IsSign()) + temp_activity_status = 2; + + if (IsGroundSpawn() || GetShowHandIcon()) + temp_activity_status += ACTIVITY_STATUS_INTERACTABLE_561; + + if ((appearance.activity_status & ACTIVITY_STATUS_ROLEPLAYING) > 0) + temp_activity_status += ACTIVITY_STATUS_ROLEPLAYING_561; + + if ((appearance.activity_status & ACTIVITY_STATUS_ANONYMOUS) > 0) + temp_activity_status += ACTIVITY_STATUS_ANONYMOUS_561; + + if ((appearance.activity_status & ACTIVITY_STATUS_LINKDEAD) > 0) + temp_activity_status += ACTIVITY_STATUS_LINKDEAD_561; + + if ((appearance.activity_status & ACTIVITY_STATUS_CAMPING) > 0) + temp_activity_status += ACTIVITY_STATUS_CAMPING_561; + + if ((appearance.activity_status & ACTIVITY_STATUS_LFG) > 0) + temp_activity_status += ACTIVITY_STATUS_LFG_561; + + if ((appearance.activity_status & ACTIVITY_STATUS_LFW) > 0) + temp_activity_status += ACTIVITY_STATUS_LFW_561; + + if ((appearance.activity_status & ACTIVITY_STATUS_SOLID) > 0) + temp_activity_status += ACTIVITY_STATUS_SOLID_561; + + if ((appearance.activity_status & ACTIVITY_STATUS_IMMUNITY_GAINED) > 0) + temp_activity_status += ACTIVITY_STATUS_IMMUNITY_GAINED_561; + + if ((appearance.activity_status & ACTIVITY_STATUS_IMMUNITY_REMAINING) > 0) + temp_activity_status += ACTIVITY_STATUS_IMMUNITY_REMAINING_561; + + if ((appearance.activity_status & ACTIVITY_STATUS_AFK) > 0) + temp_activity_status += ACTIVITY_STATUS_AFK_561; + + if (EngagedInCombat()) + temp_activity_status += ACTIVITY_STATUS_INCOMBAT_561; + + // if this is either a boat or lift let the client be manipulated by the object + // doesn't work for DoF client version 546 + if (appearance.icon == 28 || appearance.icon == 12 || IsTransportSpawn()) + { + // there is some other flags that setting with a transport breaks their solidity/ability to properly transport + // thus we just consider the following flags for now as all necessary + temp_activity_status = ACTIVITY_STATUS_SOLID_561; + temp_activity_status += ACTIVITY_STATUS_ISTRANSPORT_561; + } + } + else + { + temp_activity_status = appearance.activity_status; + if(IsNPC()) + temp_activity_status = 0xFF; + + // this only partially fixes lifts in classic 283 client if you move just as the lift starts to move + if (appearance.icon == 28 || appearance.icon == 12) + packet->setDataByName("is_transport", 1); + + if (MeetsSpawnAccessRequirements(spawn)) + packet->setDataByName("hand_icon", appearance.display_hand_icon); + else { + if ((req_quests_override & 256) > 0) + packet->setDataByName("hand_icon", 1); + } + + if (IsPlayer()) { + if (((Player*)this)->get_character_flag(CF_AFK)) + packet->setDataByName("afk", 1); + if ((appearance.activity_status & ACTIVITY_STATUS_ROLEPLAYING) > 0) + packet->setDataByName("roleplaying", 1); + if ((appearance.activity_status & ACTIVITY_STATUS_ANONYMOUS) > 0) + packet->setDataByName("anonymous", 1); + if ((appearance.activity_status & ACTIVITY_STATUS_LINKDEAD) > 0) + packet->setDataByName("linkdead", 1); + if ((appearance.activity_status & ACTIVITY_STATUS_CAMPING) > 0) + packet->setDataByName("camping", 1); + if ((appearance.activity_status & ACTIVITY_STATUS_LFG) > 0) + packet->setDataByName("lfg", 1); + } + + if (EngagedInCombat()) { + packet->setDataByName("auto_attack", 1); + } + + if ((appearance.activity_status & ACTIVITY_STATUS_SOLID) > 0) + packet->setDataByName("solid_object", 1); + } + + packet->setDataByName("activity_status", temp_activity_status); //appearance.activity_status); + // If player and player has a follow target + /* Jan 2021 Note!! Setting follow_target 0xFFFFFFFF has the result in causing strange behavior in swimming. Targetting a mob makes you focus down to its swim level, unable to swim above it. + ** in the same respect the player will drop like a rock to the bottom of the ocean (seems to be when self set to that flag?) + ** for now disabling this, if DoF needs it enabled for whatever reason then we need a version check added. + */ + if (IsPlayer()) { + if (((Player*)this)->GetFollowTarget()) + packet->setDataByName("follow_target", version <= 561 ? (((Player*)this)->GetIDWithPlayerSpawn(((Player*)this)->GetFollowTarget())) : ((((Player*)this)->GetIDWithPlayerSpawn(((Player*)this)->GetFollowTarget()) * -1) - 1)); + else if(version <= 561) { + packet->setDataByName("follow_target", 0xFFFFFFFF); + } + //else + // packet->setDataByName("follow_target", 0xFFFFFFFF); + } + //else if (!IsPet()) { + // packet->setDataByName("follow_target", 0xFFFFFFFF); + //} + + // i think this is used in DoF as a way to make a client say they are in combat with this target and cannot camp, it forces you to stand up if self spawn sends this data + if ((version > 561 || spawn != this) && GetTarget() && GetTarget()->GetTargetable()) + packet->setDataByName("target_id", ((spawn->GetIDWithPlayerSpawn(GetTarget()) * -1) - 1)); + else + packet->setDataByName("target_id", 0xFFFFFFFF); + + //Send spell effects for target window + if(IsEntity()){ + InfoStruct* info = ((Entity*)this)->GetInfoStruct(); + int8 i = 0; + int16 backdrop = 0; + int16 spell_icon = 0; + int32 spell_id = 0; + LuaSpell* spell = 0; + ((Entity*)this)->GetSpellEffectMutex()->readlock(__FUNCTION__, __LINE__); + while(i < 30){ + //Change value of spell id for this packet if spell exists + spell_id = info->spell_effects[i].spell_id; + if(spell_id > 0) + spell_id = 0xFFFFFFFF - spell_id; + else + spell_id = 0; + packet->setSubstructDataByName("spell_effects", "spell_id", spell_id, i); + + //Change value of spell icon for this packet if spell exists + spell_icon = info->spell_effects[i].icon; + if(spell_icon > 0){ + if(!(spell_icon == 0xFFFF)) + spell_icon = 0xFFFF - spell_icon; + } + else + spell_icon = 0; + packet->setSubstructDataByName("spell_effects", "spell_icon", spell_icon, i); + + //Change backdrop values to match values in this packet + backdrop = info->spell_effects[i].icon_backdrop; + switch(backdrop){ + case 312: + backdrop = 33080; + break; + case 313: + backdrop = 33081; + break; + case 314: + backdrop = 33082; + break; + case 315: + backdrop = 33083; + break; + case 316: + backdrop = 33084; + break; + case 317: + backdrop = 33085; + break; + case (318 || 319): + backdrop = 33086; + break; + default: + break; + } + + packet->setSubstructDataByName("spell_effects", "spell_icon_backdrop", backdrop, i); + spell = info->spell_effects[i].spell; + if (spell) + packet->setSubstructDataByName("spell_effects", "spell_triggercount", spell->num_triggers, i); + i++; + } + ((Entity*)this)->GetSpellEffectMutex()->releasereadlock(__FUNCTION__, __LINE__); + } +} + +void Spawn::MoveToLocation(Spawn* spawn, float distance, bool immediate, bool mapped){ + if(!spawn) + return; + SetRunningTo(spawn); + FaceTarget(spawn, false); + + if (!IsPlayer() && distance > 0.0f) + { + if ((IsFlyingCreature() || IsWaterCreature() || InWater()) && CheckLoS(spawn)) + { + if (immediate) + ClearRunningLocations(); + + AddRunningLocation(spawn->GetX(), spawn->GetY(), spawn->GetZ(), GetSpeed(), distance, true, true, "", true); + } + else if (/*!mapped && */GetZone()) + { + GetZone()->movementMgr->NavigateTo((Entity*)this, spawn->GetX(), spawn->GetY(), spawn->GetZ()); + last_grid_update = Timer::GetCurrentTime2(); + } + else + { + if (immediate) + ClearRunningLocations(); + + AddRunningLocation(spawn->GetX(), spawn->GetY(), spawn->GetZ(), GetSpeed(), distance, true, true, "", mapped); + } + } +} + +void Spawn::ProcessMovement(bool isSpawnListLocked){ + CheckProximities(); + + if(IsBot() && ((Bot*)this)->IsCamping()) { + ((Bot*)this)->Begin_Camp(); + } + if(IsPlayer()){ + //Check if player is riding a boat, if so update pos (boat's current location + XYZ offsets) + Player* player = ((Player*)this); + int32 boat_id = player->GetBoatSpawn(); + Spawn* boat = 0; + if(boat_id > 0) + boat = GetZone()->GetSpawnByID(boat_id, isSpawnListLocked); + + //TODO: MAYBE do this for real boats, not lifts... GetWidgetTypeNameByTypeID + /*if(boat){ + SetX(boat->GetX() + player->GetBoatX()); + SetY(boat->GetY() + player->GetBoatY()); + SetZ(boat->GetZ() + player->GetBoatZ()); + }*/ + return; + } + + if(IsKnockedBack()) { + if(CalculateSpawnProjectilePosition(GetX(), GetY(), GetZ())) + return; // being launched! + } + + if(reset_movement) { + ResetMovement(); + reset_movement = false; + } + + if (forceMapCheck && GetZone() != nullptr && GetMap() != nullptr && GetMap()->IsMapLoaded()) + { + FixZ(true); + forceMapCheck = false; + } + + if (GetHP() <= 0 && !IsWidget()) + return; + + if (EngagedInCombat()) + { + if(IsEntity() && (((Entity*)this)->IsMezzedOrStunned() || ((Entity*)this)->IsRooted())) { + SetAppearancePosition(GetX(),GetY(),GetZ()); + if ( IsEntity() ) + ((Entity*)this)->SetSpeed(0.0f); + + SetSpeed(0.0f); + position_changed = true; + changed = true; + GetZone()->AddChangedSpawn(this); + StopMovement(); + return; + } + int locations = 0; + + MMovementLocations.lock_shared(); + if (movement_locations) { + locations = movement_locations->size(); + } + MMovementLocations.unlock_shared(); + + if (locations < 1 && GetZone() && ((Entity*)this)->IsFeared()) + { + CalculateNewFearpoint(); + ValidateRunning(true, true); + } + } + + Spawn* followTarget = GetZone()->GetSpawnByID(m_followTarget, isSpawnListLocked); + if (!followTarget && m_followTarget > 0) + m_followTarget = 0; + if (following && !IsPauseMovementTimerActive() && followTarget && !((Entity*)this)->IsFeared()) { + + // Need to clear m_followTarget before the zoneserver deletes it + if (followTarget->GetHP() <= 0) { + followTarget = 0; + return; + } + + if (!IsEntity() || (!((Entity*)this)->IsCasting() && !((Entity*)this)->IsMezzedOrStunned() && !((Entity*)this)->IsRooted())) { + if (GetBaseSpeed() > 0) { + CalculateRunningLocation(); + } + else { + float speed = 4.0f; + if (IsEntity()) + speed = ((Entity*)this)->GetMaxSpeed(); + if (IsEntity()) + ((Entity*)this)->SetSpeed(speed); + + SetSpeed(speed); + } + MovementLocation tmpLoc; + MovementLocation* loc = 0; + MMovementLocations.lock_shared(); + if(movement_locations && movement_locations->size() > 0){ + loc = movement_locations->front(); + if(loc) { + tmpLoc.attackable = loc->attackable; + tmpLoc.gridid = loc->gridid; + tmpLoc.lua_function = string(loc->lua_function); + tmpLoc.mapped = loc->mapped; + tmpLoc.reset_hp_on_runback = loc->reset_hp_on_runback; + tmpLoc.speed = loc->speed; + tmpLoc.stage = loc->stage; + tmpLoc.x = loc->x; + tmpLoc.y = loc->y; + tmpLoc.z = loc->z; + loc = &tmpLoc; + } + } + MMovementLocations.unlock_shared(); + + float dist = GetDistance(followTarget, true); + if ((!EngagedInCombat() && m_followDistance > 0 && dist <= m_followDistance) || + (dist <= rule_manager.GetGlobalRule(R_Combat, MaxCombatRange)->GetFloat())) { + ClearRunningLocations(); + CalculateRunningLocation(true); + } + else if (loc) { + float distance = GetDistance(followTarget, loc->x, loc->y, loc->z); + if ( (!EngagedInCombat() && m_followDistance > 0 && distance > m_followDistance) || + ( EngagedInCombat() && distance > rule_manager.GetGlobalRule(R_Combat, MaxCombatRange)->GetFloat())) { + MoveToLocation(followTarget, rule_manager.GetGlobalRule(R_Combat, MaxCombatRange)->GetFloat(), true, loc->mapped); + CalculateRunningLocation(); + } + } + else { + MoveToLocation(followTarget, rule_manager.GetGlobalRule(R_Combat, MaxCombatRange)->GetFloat(), false); + CalculateRunningLocation(); + } + } + } + + bool movementCase = false; + // Movement loop is only for scripted paths + if(!EngagedInCombat() && !IsPauseMovementTimerActive() && !NeedsToResumeMovement() && (!IsNPC() || !((NPC*)this)->m_runningBack)){ + MMovementLoop.writelock(); + if(movement_loop.size() > 0 && movement_index < movement_loop.size()) + { + movementCase = true; + // Get the target location + MovementData* data = movement_loop[movement_index]; + // need to resume our movement + if(resume_movement){ + MMovementLocations.lock(); + if (movement_locations){ + while (movement_locations->size()){ + safe_delete(movement_locations->front()); + movement_locations->pop_front(); + } + movement_locations->clear(); + } + MMovementLocations.unlock(); + + data = movement_loop[movement_index]; + + if(data) + { + if(IsEntity()) { + ((Entity*)this)->SetSpeed(data->speed); + } + SetSpeed(data->speed); + if(data->use_movement_location_heading) + SetHeading(data->heading); + else if(!IsWidget()) + FaceTarget(data->x, data->z); + // 0 delay at target location, need to set multiple locations + if(data->delay == 0 && movement_loop.size() > 0) { + int16 tmp_index = movement_index+1; + MovementData* data2 = 0; + if(tmp_index < movement_loop.size()) + data2 = movement_loop[tmp_index]; + else + data2 = movement_loop[0]; + AddRunningLocation(data->x, data->y, data->z, data->speed, 0, true, false, "", true); + AddRunningLocation(data2->x, data2->y, data2->z, data2->speed, 0, true, true, "", true); + } + // delay at target location, only need to set 1 location + else + AddRunningLocation(data->x, data->y, data->z, data->speed); + } + movement_start_time = 0; + resume_movement = false; + } + // If we are not moving or we have arrived at our destination + else if(!IsRunning() || (data && data->x == GetX() && data->y == GetY() && data->z == GetZ())){ + // If we were moving remove the last running location (the point we just arrived at) + if(IsRunning()) { + RemoveRunningLocation(); + } + + // If this waypoint has a delay and we just arrived here (movement_start_time == 0) + if(data && data->delay > 0 && movement_start_time == 0){ + // Set the current time + movement_start_time = Timer::GetCurrentTime2(); + // If this waypoint had a lua function then call it + if(data->lua_function.length() > 0) + GetZone()->CallSpawnScript(this, SPAWN_SCRIPT_CUSTOM, 0, data->lua_function.c_str()); + + int16 nextMove; + if ((int16)(movement_index + 1) < movement_loop.size()) + nextMove = movement_index + 1; + else + nextMove = 0; + // Get the next target location + data = movement_loop[nextMove]; + + //Go ahead and face the next location + if(data) { + FaceTarget(data->x, data->z); + } + } + // If this waypoint has no delay or we have waited the required time (current time >= delay + movement_start_time) + else if(data && data->delay == 0 || (data && data->delay > 0 && Timer::GetCurrentTime2() >= (data->delay+movement_start_time))) { + // if no delay at this waypoint but a lua function for it then call the function + if(data->delay == 0 && data->lua_function.length() > 0) + GetZone()->CallSpawnScript(this, SPAWN_SCRIPT_CUSTOM, 0, data->lua_function.c_str()); + // since we ran a lua function make sure the movement loop is still alive and accurate + if(movement_loop.size() > 0) + { + // Advance the current movement loop index + if((int16)(movement_index+1) < movement_loop.size()) + movement_index++; + else + movement_index = 0; + // Get the next target location + data = movement_loop[movement_index]; + + // set the speed for that location + SetSpeed(data->speed); + + if(!IsWidget()) + // turn towards the location + FaceTarget(data->x, data->z); + + // If 0 delay at location get and set data for the point after it + if(data->delay == 0 && movement_loop.size() > 0){ + MMovementLocations.lock(); + if(movement_locations) + { + while (movement_locations->size()){ + safe_delete(movement_locations->front()); + movement_locations->pop_front(); + } + // clear current target locations + movement_locations->clear(); + } + MMovementLocations.unlock(); + // get the data for the location after out new location + int16 tmp_index = movement_index+1; + MovementData* data2 = 0; + if(tmp_index < movement_loop.size()) + data2 = movement_loop[tmp_index]; + else + data2 = movement_loop[0]; + // set the first location (adds it to movement_locations that we just cleared) + AddRunningLocation(data->x, data->y, data->z, data->speed, 0, true, false, "", true); + // set the location after that + AddRunningLocation(data2->x, data2->y, data2->z, data2->speed, 0, true, true, "", true); + } + // there is a delay at the next location so we only need to set it + else { + AddRunningLocation(data->x, data->y, data->z, data->speed, 0, true, true, "", true); + } + + // reset this timer to 0 now that we are moving again + movement_start_time = 0; + } + } + } + // moving and not at target location yet + else if(GetBaseSpeed() > 0) { + CalculateRunningLocation(); + } + // not moving, have a target location but not at it yet + else if (data) { + SetSpeed(data->speed); + AddRunningLocation(data->x, data->y, data->z, data->speed); + } + } + MMovementLoop.releasewritelock(); + } + + if (!movementCase && IsRunning() && !IsPauseMovementTimerActive()) { + CalculateRunningLocation(); + //last_movement_update = Timer::GetCurrentTime2(); + } + else if(movementCase) + { + //last_movement_update = Timer::GetCurrentTime2(); + } + /*else if (IsNPC() && !IsRunning() && !EngagedInCombat() && ((NPC*)this)->GetRunbackLocation()) { + // Is an npc that is not moving and not engaged in combat but has a run back location set then clear the runback location + LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "Clear runback location for %s", GetName()); + ((NPC*)this)->ClearRunback(); + resume_movement = true; + NeedsToResumeMovement(false); + }*/ +} + +void Spawn::ResetMovement(){ + MMovementLoop.writelock(); + + vector::iterator itr; + for(itr = movement_loop.begin(); itr != movement_loop.end(); itr++){ + safe_delete(*itr); + } + movement_loop.clear(); + movement_index = 0; + resume_movement = true; + ClearRunningLocations(); + + MMovementLoop.releasewritelock(); + + ValidateRunning(true, true); +} + +void Spawn::AddMovementLocation(float x, float y, float z, float speed, int16 delay, const char* lua_function, float heading, bool include_heading){ + + LogWrite(LUA__DEBUG, 5, "LUA", "AddMovementLocation: x: %.2f, y: %.2f, z: %.2f, speed: %.2f, delay: %i, lua: %s", + x, y, z, speed, delay, string(lua_function).c_str()); + + MovementData* data = new MovementData; + data->x = x; + data->y = y; + data->z = z; + data->speed = speed; + data->delay = delay*1000; + if(lua_function) + data->lua_function = string(lua_function); + + data->heading = heading; + data->use_movement_location_heading = include_heading; + MMovementLoop.lock(); + movement_loop.push_back(data); + MMovementLoop.unlock(); +} + +bool Spawn::ValidateRunning(bool lockMovementLocation, bool lockMovementLoop) { + bool movement = false; + + if(lockMovementLocation) { + MMovementLocations.lock_shared(); + } + + if(movement_locations) { + movement = movement_locations->size() > 0; + } + + if(lockMovementLocation) { + MMovementLocations.unlock_shared(); + } + + if(IsPauseMovementTimerActive() || (IsEntity() && (((Entity*)this)->IsMezzedOrStunned() || ((Entity*)this)->IsRooted()))) { + is_running = false; + return false; + } + + if(movement) { + is_running = true; + return true; + } + + if(lockMovementLoop) { + MMovementLoop.lock(); + } + + movement = movement_loop.size() > 0; + + if(movement) { + is_running = true; + } + else { + is_running = false; + } + + if(lockMovementLoop) { + MMovementLoop.unlock(); + } + return movement; +} +bool Spawn::IsRunning(){ + return is_running; +} + +void Spawn::RunToLocation(float x, float y, float z, float following_x, float following_y, float following_z){ + if(IsPauseMovementTimerActive() || (IsEntity() && (((Entity*)this)->IsMezzedOrStunned() || ((Entity*)this)->IsRooted()))) { + is_running = false; + return; + } + + if(!IsWidget() && (!EngagedInCombat() || GetDistance(GetTarget()) > rule_manager.GetGlobalRule(R_Combat, MaxCombatRange)->GetFloat())) + FaceTarget(x, z); + SetPos(&appearance.pos.X2, x, false); + SetPos(&appearance.pos.Z2, z, false); + SetPos(&appearance.pos.Y2, y, false); + if(following_x == 0 && following_y == 0 && following_z == 0){ + SetPos(&appearance.pos.X3, x, false); + SetPos(&appearance.pos.Z3, z, false); + SetPos(&appearance.pos.Y3, y, false); + } + else{ + SetPos(&appearance.pos.X3, following_x, false); + SetPos(&appearance.pos.Y3, following_y, false); + SetPos(&appearance.pos.Z3, following_z, false); + } + + is_running = true; + position_changed = true; + changed = true; + GetZone()->AddChangedSpawn(this); +} + +MovementLocation* Spawn::GetCurrentRunningLocation(){ + MovementLocation* ret = 0; + MMovementLocations.lock_shared(); + if(movement_locations && movement_locations->size() > 0){ + ret = movement_locations->front(); + } + MMovementLocations.unlock_shared(); + return ret; +} + +MovementLocation* Spawn::GetLastRunningLocation(){ + MovementLocation* ret = 0; + MMovementLocations.lock_shared(); + if(movement_locations && movement_locations->size() > 0){ + ret = movement_locations->back(); + } + MMovementLocations.unlock_shared(); + return ret; +} + +void Spawn::AddRunningLocation(float x, float y, float z, float speed, float distance_away, bool attackable, bool finished_adding_locations, string lua_function, bool isMapped){ + if(speed == 0) + return; + + if ( IsEntity() ) + ((Entity*)this)->SetSpeed(speed); + else + this->SetSpeed(speed); + + MovementLocation* current_location = 0; + + float distance = GetDistance(x, y, z, distance_away != 0); + if(distance_away != 0){ + distance -= distance_away; + + x = x - (GetX() - x)*distance_away/distance; + z = z - (GetZ() - z)*distance_away/distance; + } + + MMovementLocations.lock(); + if(!movement_locations){ + movement_locations = new deque(); + } + MMovementLocations.unlock(); + + MovementLocation* data = new MovementLocation; + data->mapped = isMapped; + data->x = x; + data->y = y; + data->z = z; + data->speed = speed; + data->attackable = attackable; + data->lua_function = lua_function; + data->gridid = 0; // used for runback defaults + data->reset_hp_on_runback = false; + + MMovementLocations.lock_shared(); + if(movement_locations->size() > 0) + current_location = movement_locations->back(); + MMovementLocations.unlock_shared(); + + if(!current_location){ + SetSpawnOrigX(GetX()); + SetSpawnOrigY(GetY()); + SetSpawnOrigZ(GetZ()); + SetSpawnOrigHeading(GetHeading()); + } + is_running = true; + + MMovementLocations.lock(); + movement_locations->push_back(data); + MMovementLocations.unlock(); + if(!IsPauseMovementTimerActive() && finished_adding_locations){ + MMovementLocations.lock(); + current_location = movement_locations->front(); + SetSpeed(current_location->speed); + if(movement_locations->size() > 1){ + data = movement_locations->at(1); + RunToLocation(current_location->x, current_location->y, current_location->z, data->x, data->y, data->z); + } + else + RunToLocation(current_location->x, current_location->y, current_location->z, 0, 0, 0); + MMovementLocations.unlock(); + } +} + +bool Spawn::RemoveRunningLocation(){ + bool ret = false; + MMovementLocations.lock(); + if(movement_locations && movement_locations->size() > 0){ + delete movement_locations->front(); + movement_locations->pop_front(); + ret = true; + } + MMovementLocations.unlock(); + + ValidateRunning(true, false); + return ret; +} + +void Spawn::ClearRunningLocations(){ + while(RemoveRunningLocation()){} +} + +void Spawn::NewWaypointChange(MovementLocation* data){ + if(data){ + if(NeedsToResumeMovement()){ + resume_movement = true; + NeedsToResumeMovement(false); + } + if(!data->attackable) + SetHeading(GetSpawnOrigHeading()); + } + + if (data && data->lua_function.length() > 0) + GetZone()->CallSpawnScript(this, SPAWN_SCRIPT_CUSTOM, 0, data->lua_function.c_str()); + + RemoveRunningLocation(); +} + +bool Spawn::CalculateChange(){ + bool remove_needed = false; + MovementLocation* data = 0; + MovementLocation tmpLoc; + MMovementLocations.lock_shared(); + if(movement_locations){ + if(movement_locations->size() > 0){ + // Target location + data = movement_locations->front(); + if(data) { + tmpLoc.attackable = data->attackable; + tmpLoc.gridid = data->gridid; + tmpLoc.lua_function = string(data->lua_function); + tmpLoc.mapped = data->mapped; + tmpLoc.reset_hp_on_runback = data->reset_hp_on_runback; + tmpLoc.speed = data->speed; + tmpLoc.stage = data->stage; + tmpLoc.x = data->x; + tmpLoc.y = data->y; + tmpLoc.z = data->z; + data = &tmpLoc; + } + // If no target or we are at the target location need to remove this point + if(!data || (data->x == GetX() && data->y == GetY() && data->z == GetZ())) + remove_needed = true; + } + } + MMovementLocations.unlock_shared(); + + if(remove_needed){ + NewWaypointChange(data); + } + else if(data){ + // Speed is per second so we need a time_step (amount of time since the last update) to modify movement by + float time_step = (Timer::GetCurrentTime2() - last_movement_update) * 0.001; // * 0.001 is the same as / 1000, float muliplications is suppose to be faster though + + // Get current location + float nx = GetX(); + float ny = GetY(); + float nz = GetZ(); + + // Get Forward vecotr + float tar_vx = data->x - nx; + float tar_vy = data->y - ny; + float tar_vz = data->z - nz; + + // Multiply speed by the time_step to get how much should have changed over the last tick + float speed = GetSpeed() * time_step; + + // Normalize the forward vector and multiply by speed, this gives us our change in coords, just need to add them to our current coords + float len = sqrtf(tar_vx * tar_vx + tar_vy * tar_vy + tar_vz * tar_vz); + tar_vx = (tar_vx / len) * speed; + tar_vy = (tar_vy / len) * speed; + tar_vz = (tar_vz / len) * speed; + + // Distance less then 0.5 just set the npc to the target location + if (GetDistance(data->x, data->y, data->z, IsWidget() ? false : true) <= speed) { + SetX(data->x, false); + SetZ(data->z, false); + SetY(data->y, false, true); + remove_needed = true; + NewWaypointChange(data); + } + else { + SetX(nx + tar_vx, false); + SetZ(nz + tar_vz, false); + if ( IsWidget() ) + SetY(ny + tar_vy, false, true); + else + SetY(ny + tar_vy, false); + } + + int32 newGrid = GetLocation(); + if (GetMap() != nullptr) { + m_GridMutex.writelock(__FUNCTION__, __LINE__); + std::map::iterator itr = established_grid_id.begin(); + if ( itr == established_grid_id.end() || itr->second.timestamp <= (Timer::GetCurrentTime2())) + { + if(itr != established_grid_id.end() && itr->second.x == GetX() && itr->second.z == GetZ()) { + itr->second.timestamp = Timer::GetCurrentTime2()+1000; + itr->second.npc_save = true; + newGrid = itr->second.grid_id; + } + else { + auto loc = glm::vec3(GetX(), GetZ(), GetY()); + float new_z = FindBestZ(loc, nullptr, &newGrid); + TimedGridData data; + data.grid_id = newGrid; + data.x = GetX(); + data.y = GetY(); + data.z = GetZ(); + data.npc_save = true; + data.zone_ground_y = new_z; + data.offset_y = new_z; + data.timestamp = Timer::GetCurrentTime2()+1000; + established_grid_id.insert(make_pair(0, data)); + } + } + else + newGrid = itr->second.grid_id; + m_GridMutex.releasewritelock(__FUNCTION__, __LINE__); + } + + if ((!IsFlyingCreature() || IsTransportSpawn()) && newGrid != 0 && newGrid != GetLocation()) + SetLocation(newGrid); + } + return remove_needed; +} + +void Spawn::CalculateRunningLocation(bool stop){ + + bool pauseTimerEnabled = IsPauseMovementTimerActive(); + + if (!pauseTimerEnabled && !stop && (last_location_update + 100) > Timer::GetCurrentTime2()) + return; + else if (!pauseTimerEnabled && !stop) + last_location_update = Timer::GetCurrentTime2(); + bool continueElseIf = true; + bool removed = CalculateChange(); + if (stop || pauseTimerEnabled) { + //following = false; + SetPos(&appearance.pos.X2, GetX(), false); + SetPos(&appearance.pos.Y2, GetY(), false); + SetPos(&appearance.pos.Z2, GetZ(), false); + SetPos(&appearance.pos.X3, GetX(), false); + SetPos(&appearance.pos.Y3, GetY(), false); + SetPos(&appearance.pos.Z3, GetZ(), false); + continueElseIf = false; + } + else if (removed) { + MMovementLocations.lock_shared(); + if(movement_locations) { + if(movement_locations->size() > 0) { + MovementLocation* current_location = movement_locations->at(0); + if (movement_locations->size() > 1) { + MovementLocation* data = movement_locations->at(1); + RunToLocation(current_location->x, current_location->y, current_location->z, data->x, data->y, data->z); + } + else + RunToLocation(current_location->x, current_location->y, current_location->z, 0, 0, 0); + + continueElseIf = false; + } + } + MMovementLocations.unlock_shared(); + } + + if (continueElseIf && GetZone() && GetTarget() != NULL && EngagedInCombat()) + { + if (GetDistance(GetTarget()) > rule_manager.GetGlobalRule(R_Combat, MaxCombatRange)->GetFloat()) + { + if ((IsFlyingCreature() || IsWaterCreature() || InWater()) && CheckLoS(GetTarget())) + AddRunningLocation(GetTarget()->GetX(), GetTarget()->GetY(), GetTarget()->GetZ(), GetSpeed(), 0, false); + else + GetZone()->movementMgr->NavigateTo((Entity*)this, GetTarget()->GetX(), GetTarget()->GetY(), GetTarget()->GetZ()); + } + else + ((Entity*)this)->HaltMovement(); + } + else if (continueElseIf && !following) + { + position_changed = true; + changed = true; + GetZone()->AddChangedSpawn(this); + } +} +float Spawn::GetFaceTarget(float x, float z) { + float angle; + + double diff_x = x - GetX(); + double diff_z = z - GetZ(); + + if (diff_z == 0) { + if (diff_x > 0) + angle = 90; + else + angle = 270; + } + else + angle = ((atan(diff_x / diff_z)) * 180) / 3.14159265358979323846; + + if(angle < 0) + angle = angle + 360; + else if(angle > -0.0000001 && angle < 0.0000001) + angle = 0; + else + angle = angle + 180; + + if ((diff_x < 0 && diff_z != 0.0) || (diff_x == 0 && diff_z > 0.0)) + angle = angle + 180; + + if(angle > 360) + angle = angle - 360.0; + + return angle; +} + +void Spawn::FaceTarget(float x, float z){ + + float angle; + + double diff_x = x - GetX(); + double diff_z = z - GetZ(); + + if (diff_z == 0) { + if (diff_x > 0) + angle = 90; + else + angle = 270; + } + else + angle = ((atan(diff_x / diff_z)) * 180) / 3.14159265358979323846; + + if(angle < 0) + angle = angle + 360; + else if(angle > -0.0000001 && angle < 0.0000001) + angle = 0; + else + angle = angle + 180; + + if ((diff_x < 0 && diff_z != 0.0) || (diff_x == 0 && diff_z > 0.0)) + angle = angle + 180; + + if(angle > 360) + angle = angle - 360.0; + + SetHeading(angle); +} + +void Spawn::FaceTarget(Spawn* target, bool disable_action_state){ + if(!target) + return; + if(GetHP() > 0 && target->IsPlayer() && !EngagedInCombat()){ + if(!IsPet() && disable_action_state) { + if(IsNPC()) { + ((NPC*)this)->StartRunback(); + ((NPC*)this)->PauseMovement(30000); + } + SetTempActionState(0); + } + } + FaceTarget(target->GetX(), target->GetZ()); +} + +bool Spawn::MeetsSpawnAccessRequirements(Player* player){ + bool ret = false; + Quest* quest = 0; + //Check if we meet all quest requirements first.. + m_requiredQuests.readlock(__FUNCTION__, __LINE__); + if (player && required_quests.size() > 0) { + map* >::iterator itr; + for (itr = required_quests.begin(); itr != required_quests.end(); itr++) { + player->AddQuestRequiredSpawn(this, itr->first); + vector* quest_steps = itr->second; + for (int32 i = 0; i < quest_steps->size(); i++) { + quest = player->GetQuest(itr->first); + if (req_quests_continued_access) { + if (quest) { + if (quest->GetQuestStepCompleted(quest_steps->at(i))) { + ret = true; + break; + } + } + else if (player->HasQuestBeenCompleted(itr->first)) { + ret = true; + break; + } + } + if (quest && quest->QuestStepIsActive(quest_steps->at(i))) { + ret = true; + break; + } + } + } + } + else + ret = true; + m_requiredQuests.releasereadlock(__FUNCTION__, __LINE__); + if (!ret) + return ret; + + //Now check if the player meets all history requirements + m_requiredHistory.readlock(__FUNCTION__, __LINE__); + if (required_history.size() > 0){ + map::iterator itr; + for (itr = required_history.begin(); itr != required_history.end(); itr++){ + player->AddHistoryRequiredSpawn(this, itr->first); + LUAHistory* player_history = player->GetLUAHistory(itr->first); + if (player_history){ + if (player_history->Value != itr->second->Value || player_history->Value2 != itr->second->Value2) + ret = false; + } + else + ret = false; + if (!ret) + break; + } + } + m_requiredHistory.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +vector* Spawn::GetSpawnGroup(){ + vector* ret_list = 0; + if(spawn_group_list){ + ret_list = new vector(); + if(MSpawnGroup) + MSpawnGroup->readlock(__FUNCTION__, __LINE__); + ret_list->insert(ret_list->begin(), spawn_group_list->begin(), spawn_group_list->end()); + if(MSpawnGroup) + MSpawnGroup->releasereadlock(__FUNCTION__, __LINE__); + } + return ret_list; +} + +bool Spawn::HasSpawnGroup() { + return spawn_group_list && spawn_group_list->size() > 0; +} + +bool Spawn::IsInSpawnGroup(Spawn* spawn) { + bool ret = false; + if (HasSpawnGroup() && spawn) { + vector::iterator itr; + for (itr = spawn_group_list->begin(); itr != spawn_group_list->end(); itr++) { + if ((*itr) == spawn) { + ret = true; + break; + } + } + } + return ret; +} + +Spawn* Spawn::IsSpawnGroupMembersAlive(Spawn* ignore_spawn, bool npc_only) { + Spawn* ret = nullptr; + if (MSpawnGroup && HasSpawnGroup()) { + + MSpawnGroup->readlock(__FUNCTION__, __LINE__); + vector::iterator itr; + for (itr = spawn_group_list->begin(); itr != spawn_group_list->end(); itr++) { + if ((*itr) != ignore_spawn && (*itr)->Alive() && (!npc_only || (npc_only && (*itr)->IsNPC()))) { + ret = (*itr); + break; + } + } + MSpawnGroup->releasereadlock(__FUNCTION__, __LINE__); + } + return ret; +} + +void Spawn::UpdateEncounterState(int8 new_state) { + if (MSpawnGroup && HasSpawnGroup()) { + MSpawnGroup->readlock(__FUNCTION__, __LINE__); + vector::iterator itr; + for (itr = spawn_group_list->begin(); itr != spawn_group_list->end(); itr++) { + if ((*itr)->Alive() && (*itr)->IsNPC()) { + NPC* npc = (NPC*)(*itr); + (*itr)->SetLockedNoLoot(new_state); + if(new_state == ENCOUNTER_STATE_BROKEN && npc->Brain()) { + npc->Brain()->ClearEncounter(); + } + } + } + MSpawnGroup->releasereadlock(__FUNCTION__, __LINE__); + } +} + +void Spawn::CheckEncounterState(Entity* victim, bool test_auto_lock) { + if (!IsEntity() || !victim->IsNPC()) + return; + + Entity* ent = ((Entity*)this); + if (victim->GetLockedNoLoot() == ENCOUNTER_STATE_AVAILABLE) { + + Entity* attacker = nullptr; + if (ent->GetOwner()) + attacker = ent->GetOwner(); + else + attacker = ent; + + bool matchedAutoLock = false; + if (attacker->IsEntity() && ((Entity*)attacker)->GetGroupMemberInfo()) { + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + GroupMemberInfo* gmi = ((Entity*)attacker)->GetGroupMemberInfo(); + if (gmi && gmi->group_id) + { + PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id); + if (group && ((group->GetGroupOptions()->group_lock_method && group->GetGroupOptions()->group_autolock == 1) || attacker->GetGroupMemberInfo()->leader)) + { + matchedAutoLock = true; + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + + for (int8 i = 0; i < members->size(); i++) { + Entity* member = members->at(i)->member; + if (!member || member->GetZone() != attacker->GetZone()) + continue; + + if (member->IsEntity()) { + if (!member->GetInfoStruct()->get_engaged_encounter()) { + member->GetInfoStruct()->set_engaged_encounter(1); + } + if (((NPC*)victim)->Brain()) { + ((NPC*)victim)->Brain()->AddHate(member, 0); + ((NPC*)victim)->Brain()->AddToEncounter(member); + victim->AddTargetToEncounter(member); + } + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + } + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } + else if (attacker->GetInfoStruct()->get_group_solo_autolock()) { + matchedAutoLock = true; + if (((NPC*)victim)->Brain()) { + ((NPC*)victim)->Brain()->AddHate(attacker, 0); + ((NPC*)victim)->Brain()->AddToEncounter(attacker); + victim->AddTargetToEncounter(attacker); + } + } + + if (test_auto_lock && !matchedAutoLock) { + return; + } + + if (!ent->GetInfoStruct()->get_engaged_encounter()) { + ent->GetInfoStruct()->set_engaged_encounter(1); + } + + if (!attacker->GetInfoStruct()->get_engaged_encounter()) { + attacker->GetInfoStruct()->set_engaged_encounter(1); + } + + int8 skip_loot_gray_mob_flag = rule_manager.GetGlobalRule(R_Loot, SkipLootGrayMob)->GetInt8(); + + int8 difficulty = attacker->GetArrowColor(victim->GetLevel()); + + int8 new_enc_state = ENCOUNTER_STATE_AVAILABLE; + if (skip_loot_gray_mob_flag && difficulty == ARROW_COLOR_GRAY) { + if (!attacker->IsPlayer() && !attacker->IsBot()) { + new_enc_state = ENCOUNTER_STATE_BROKEN; + } + else { + new_enc_state = ENCOUNTER_STATE_OVERMATCHED; + } + } + else { + if (attacker->IsPlayer() || attacker->IsBot()) { + new_enc_state = ENCOUNTER_STATE_LOCKED; + } + else { + new_enc_state = ENCOUNTER_STATE_BROKEN; + } + } + + victim->SetLockedNoLoot(new_enc_state); + victim->UpdateEncounterState(new_enc_state); + } +} + +void Spawn::AddTargetToEncounter(Entity* entity) { + if (MSpawnGroup && HasSpawnGroup()) { + MSpawnGroup->readlock(__FUNCTION__, __LINE__); + vector::iterator itr; + for (itr = spawn_group_list->begin(); itr != spawn_group_list->end(); itr++) { + if ((*itr) != this && (*itr)->Alive() && (*itr)->IsNPC()) { + ((NPC*)(*itr))->Brain()->AddToEncounter(entity); + } + } + MSpawnGroup->releasereadlock(__FUNCTION__, __LINE__); + } +} + +void Spawn::AddSpawnToGroup(Spawn* spawn){ + if(!spawn) + return; + if(!spawn_group_list){ + spawn_group_list = new vector(); + spawn_group_list->push_back(this); + safe_delete(MSpawnGroup); + MSpawnGroup = new Mutex(); + MSpawnGroup->SetName("Spawn::MSpawnGroup"); + } + vector::iterator itr; + MSpawnGroup->writelock(__FUNCTION__, __LINE__); + for(itr = spawn_group_list->begin(); itr != spawn_group_list->end(); itr++){ + if((*itr) == spawn){ + MSpawnGroup->releasewritelock(__FUNCTION__, __LINE__); + return; + } + } + spawn_group_list->push_back(spawn); + spawn->SetSpawnGroupList(spawn_group_list, MSpawnGroup); + MSpawnGroup->releasewritelock(__FUNCTION__, __LINE__); +} + + +void Spawn::SetSpawnGroupList(vector* list, Mutex* mutex){ + spawn_group_list = list; + MSpawnGroup = mutex; +} + +void Spawn::RemoveSpawnFromGroup(bool erase_all){ + SetSpawnGroupID(0); + bool del = false; + if(MSpawnGroup){ + MSpawnGroup->writelock(__FUNCTION__, __LINE__); + if(spawn_group_list){ + vector::iterator itr; + Spawn* spawn = 0; + if(spawn_group_list->size() == 1) + erase_all = true; + for(itr = spawn_group_list->begin(); itr != spawn_group_list->end(); itr++){ + spawn = *itr; + if (spawn) { + if(!erase_all){ + if(spawn == this){ + spawn_group_list->erase(itr); + MSpawnGroup->releasewritelock(__FUNCTION__, __LINE__); + spawn_group_list = 0; + MSpawnGroup = 0; + return; + } + } + else{ + if (spawn != this) + spawn->SetSpawnGroupList(0, 0); + } + } + } + if (erase_all) + spawn_group_list->clear(); + del = (spawn_group_list->size() == 0); + } + MSpawnGroup->releasewritelock(__FUNCTION__, __LINE__); + if (del){ + safe_delete(MSpawnGroup); + safe_delete(spawn_group_list); + } + } +} + +void Spawn::SetSpawnGroupID(int32 id){ + m_SpawnMutex.writelock(); + group_id = id; + m_SpawnMutex.releasewritelock(); +} + +int32 Spawn::GetSpawnGroupID(){ + int32 groupid = 0; + m_SpawnMutex.readlock(); + groupid = group_id; + m_SpawnMutex.releasereadlock(); + return groupid; +} + +void Spawn::AddChangedZoneSpawn(){ + if(send_spawn_changes && GetZone()) + GetZone()->AddChangedSpawn(this); +} + +void Spawn::RemoveSpawnAccess(Spawn* spawn) { + if (allowed_access.count(spawn->GetID()) > 0) { + allowed_access.erase(spawn->GetID()); + GetZone()->HidePrivateSpawn(this); + } +} + +void Spawn::SetFollowTarget(Spawn* spawn, int32 follow_distance) { + if (spawn && spawn != this) { + m_followTarget = spawn->GetID(); + m_followDistance = follow_distance; + } + else { + m_followTarget = 0; + if (following) + following = false; + m_followDistance = 0; + } +} + +void Spawn::AddTempVariable(string var, string val) { + m_tempVariableTypes[var] = 5; + m_tempVariables[var] = val; +} + +void Spawn::AddTempVariable(string var, Spawn* val) { + m_tempVariableTypes[var] = 1; + m_tempVariableSpawn[var] = val->GetID(); +} + +void Spawn::AddTempVariable(string var, ZoneServer* val) { + m_tempVariableTypes[var] = 2; + m_tempVariableZone[var] = val; +} + +void Spawn::AddTempVariable(string var, Item* val) { + m_tempVariableTypes[var] = 3; + m_tempVariableItem[var] = val; +} + +void Spawn::AddTempVariable(string var, Quest* val) { + m_tempVariableTypes[var] = 4; + m_tempVariableQuest[var] = val; +} + +string Spawn::GetTempVariable(string var) { + string ret = ""; + + if (m_tempVariables.count(var) > 0) + ret = m_tempVariables[var]; + + return ret; +} + +Spawn* Spawn::GetTempVariableSpawn(string var) { + Spawn* ret = 0; + + if (m_tempVariableSpawn.count(var) > 0) + ret = GetZone()->GetSpawnByID(m_tempVariableSpawn[var]); + + return ret; +} + +ZoneServer* Spawn::GetTempVariableZone(string var) { + ZoneServer* ret = 0; + + if (m_tempVariableZone.count(var) > 0) + ret = m_tempVariableZone[var]; + + return ret; +} + +Item* Spawn::GetTempVariableItem(string var) { + Item* ret = 0; + + if (m_tempVariableItem.count(var) > 0) + ret = m_tempVariableItem[var]; + + return ret; +} + +Quest* Spawn::GetTempVariableQuest(string var) { + Quest* ret = 0; + + if (m_tempVariableQuest.count(var) > 0) + ret = m_tempVariableQuest[var]; + + return ret; +} + +int8 Spawn::GetTempVariableType(string var) { + int8 ret = 0; + + if (m_tempVariableTypes.count(var) > 0) + ret = m_tempVariableTypes[var]; + + return ret; +} + +void Spawn::DeleteTempVariable(string var) { + int8 type = GetTempVariableType(var); + + switch (type) { + case 1: + m_tempVariableSpawn.erase(var); + break; + case 2: + m_tempVariableZone.erase(var); + break; + case 3: + m_tempVariableItem.erase(var); + break; + case 4: + m_tempVariableQuest.erase(var); + break; + case 5: + m_tempVariables.erase(var); + break; + } + + m_tempVariableTypes.erase(var); +} + +Spawn* Spawn::GetRunningTo() { + return GetZone()->GetSpawnByID(running_to); +} + +Spawn* Spawn::GetFollowTarget() { + return GetZone()->GetSpawnByID(m_followTarget); +} + +void Spawn::CopySpawnAppearance(Spawn* spawn){ + if (!spawn) + return; + + //This function copies the appearace of the provided spawn to this one + if (spawn->IsEntity() && IsEntity()){ + memcpy(&((Entity*)this)->features, &((Entity*)spawn)->features, sizeof(CharFeatures)); + memcpy(&((Entity*)this)->equipment, &((Entity*)spawn)->equipment, sizeof(EQ2_Equipment)); + } + + SetSize(spawn->GetSize()); + SetModelType(spawn->GetModelType()); +} + +void Spawn::SetY(float y, bool updateFlags, bool disableYMapCheck) +{ + SetPos(&appearance.pos.Y, y, updateFlags); + if (!disableYMapCheck) + FixZ(); +} + +float Spawn::FindDestGroundZ(glm::vec3 dest, float z_offset) +{ + float best_z = BEST_Z_INVALID; + if (GetZone() != nullptr && GetMap() != nullptr) + { + dest.z += z_offset; + best_z = FindBestZ(dest, nullptr); + } + return best_z; +} + +float Spawn::FindBestZ(glm::vec3 loc, glm::vec3* result, int32* new_grid_id, int32* new_widget_id) { + std::shared_lock lock(MIgnoredWidgets); + + if(!GetMap()) + return BEST_Z_INVALID; + + float new_z = GetMap()->FindBestZ(loc, nullptr, &ignored_widgets, new_grid_id, new_widget_id); + return new_z; +} + +float Spawn::GetFixedZ(const glm::vec3& destination, int32 z_find_offset) { + BenchTimer timer; + timer.reset(); + + float new_z = destination.z; + + if (GetZone() != nullptr && GetMap() != nullptr) { + +/* if (flymode == GravityBehavior::Flying) + return new_z; + */ +/* if (zone->HasWaterMap() && zone->watermap->InLiquid(glm::vec3(m_Position))) + return new_z; + */ + /* + * Any more than 5 in the offset makes NPC's hop/snap to ceiling in small corridors + */ + new_z = this->FindDestGroundZ(destination, z_find_offset); + if (new_z != BEST_Z_INVALID) { + if (new_z < -2000) { + new_z = GetY(); + } + } + + auto duration = timer.elapsed(); + LogWrite(MAP__DEBUG, 0, "Map", "Mob::GetFixedZ() ([{%s}]) returned [{%f}] at [{%f}], [{%f}], [{%f}] - Took [{%f}]", + this->GetName(), + new_z, + destination.x, + destination.y, + destination.z, + duration); + } + + return new_z; +} + + +void Spawn::FixZ(bool forceUpdate) { + if (!GetZone()) { + return; + } + /* + if (flymode == GravityBehavior::Flying) { + return; + }*/ + /* + if (zone->watermap && zone->watermap->InLiquid(m_Position)) { + return; + }*/ + + // we do the inwater check here manually to avoid double calling for a Z coordinate + glm::vec3 current_loc(GetX(), GetZ(), GetY()); + + uint32 GridID = 0; + uint32 WidgetID = 0; + float new_z = GetY(); + if(GetMap() != nullptr) { + new_z = FindBestZ(current_loc, nullptr, &GridID, &WidgetID); + + if ((IsTransportSpawn() || !IsFlyingCreature()) && GridID != 0 && GridID != GetLocation()) { + LogWrite(PLAYER__DEBUG, 0, "Player", "%s left grid %u and entered grid %u", appearance.name, GetLocation(), GridID); + + const char* zone_script = world.GetZoneScript(GetZone()->GetZoneID()); + + if (zone_script && lua_interface) { + lua_interface->RunZoneScript(zone_script, "leave_location", GetZone(), this, GetLocation()); + } + + SetLocation(GridID); + + if (zone_script && lua_interface) { + lua_interface->RunZoneScript(zone_script, "enter_location", GetZone(), this, GridID); + } + } + trigger_widget_id = WidgetID; + } + + // no need to go any further for players, flying creatures or objects, just needed the grid id set + if (IsPlayer() || IsFlyingCreature() || IsObject()) { + return; + } + + if ( region_map != nullptr ) + { + glm::vec3 targPos(GetX(), GetY(), GetZ()); + + if(region_map->InWater(targPos, GetLocation())) + return; + } + + if (new_z == GetY()) + return; + + if ((new_z > -2000) && new_z != BEST_Z_INVALID) { + SetY(new_z, forceUpdate, true); + } + else { + LogWrite(MAP__DEBUG, 0, "Map", "[{%s}] is failing to find Z [{%f}]", this->GetName(), std::abs(GetY() - new_z)); + } +} + +bool Spawn::CheckLoS(Spawn* target) +{ + float radiusSrc = 2.0f; + float radiusTarg = 2.0f; + + glm::vec3 targpos(target->GetX(), target->GetZ(), target->GetY()+radiusTarg); + glm::vec3 pos(GetX(), GetZ(), GetY()+radiusSrc); + return CheckLoS(pos, targpos); +} + +bool Spawn::CheckLoS(glm::vec3 myloc, glm::vec3 oloc) +{ + bool res = false; + ZoneServer* zone = GetZone(); + if (zone == NULL || GetMap() == NULL || !GetMap()->IsMapLoaded()) + return true; + else { + MIgnoredWidgets.lock_shared(); + res = GetMap()->CheckLoS(myloc, oloc, &ignored_widgets); + MIgnoredWidgets.unlock_shared(); + } + + return res; +} + +void Spawn::CalculateNewFearpoint() +{ + if (GetZone() && GetZone()->pathing) { + auto Node = zone->pathing->GetRandomLocation(glm::vec3(GetX(), GetZ(), GetY())); + if (Node.x != 0.0f || Node.y != 0.0f || Node.z != 0.0f) { + AddRunningLocation(Node.x, Node.y, Node.z, GetSpeed(), 0, true, true, "", true); + } + } +} + +Item* Spawn::LootItem(int32 id) { + Item* ret = 0; + vector::iterator itr; + MLootItems.lock(); + for (itr = loot_items.begin(); itr != loot_items.end(); itr++) { + if ((*itr)->details.item_id == id) { + ret = *itr; + loot_items.erase(itr); + break; + } + } + MLootItems.unlock(); + return ret; +} + +void Spawn::TransferLoot(Spawn* spawn) { + if(spawn == this || spawn == nullptr) + return; // mmm no + + vector::iterator itr; + MLootItems.lock(); + for (itr = loot_items.begin(); itr != loot_items.end();) { + if (!(*itr)->IsBodyDrop()) { + spawn->AddLootItem(*itr); + itr = loot_items.erase(itr); + } + else { + itr++; + } + } + MLootItems.unlock(); +} + +int32 Spawn::GetLootItemID() { + int32 ret = 0; + vector::iterator itr; + MLootItems.lock(); + for (itr = loot_items.begin(); itr != loot_items.end(); itr++) { + ret = (*itr)->details.item_id; + break; + } + MLootItems.unlock(); + return ret; +} + +void Spawn::GetLootItemsList(std::vector* out_entries) { + if(!out_entries) + return; + + vector::iterator itr; + for (itr = loot_items.begin(); itr != loot_items.end(); itr++) { + out_entries->push_back((*itr)->details.item_id); + } +} + +bool Spawn::HasLootItemID(int32 id) { + bool ret = false; + + vector::iterator itr; + MLootItems.readlock(__FUNCTION__, __LINE__); + for (itr = loot_items.begin(); itr != loot_items.end(); itr++) { + if ((*itr)->details.item_id == id) { + ret = true; + break; + } + } + MLootItems.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +void Spawn::CheckProximities() +{ + if (!has_spawn_proximities) + return; + + if (spawn_proximities.size() > 0) + { + MutexList::iterator itr = spawn_proximities.begin(); + while (itr.Next()) { + SpawnProximity* prox = itr.value; + map::iterator spawnsItr; + for (spawnsItr = prox->spawns_in_proximity.begin(); spawnsItr != prox->spawns_in_proximity.end(); spawnsItr++) { + Spawn* tmpSpawn = 0; + if (spawnsItr->first && + ((prox->spawn_type == SPAWNPROXIMITY_DATABASE_ID && (tmpSpawn = GetZone()->GetSpawnByDatabaseID(spawnsItr->first)) != 0) || + (prox->spawn_type == SPAWNPROXIMITY_LOCATION_ID && (tmpSpawn = GetZone()->GetSpawnByLocationID(spawnsItr->first)) != 0))) + { + if (!spawnsItr->second && tmpSpawn->GetDistance(this) <= prox->distance) + { + if (prox->in_range_lua_function.size() > 0) + GetZone()->CallSpawnScript(this, SPAWN_SCRIPT_CUSTOM, tmpSpawn, prox->in_range_lua_function.c_str()); + spawnsItr->second = true; + } + else if (spawnsItr->second && tmpSpawn->GetDistance(this) > prox->distance) + { + if (prox->leaving_range_lua_function.size() > 0) + GetZone()->CallSpawnScript(this, SPAWN_SCRIPT_CUSTOM, tmpSpawn, prox->leaving_range_lua_function.c_str()); + spawnsItr->second = false; + } + } + } + } + } +} + +void Spawn::AddSpawnToProximity(int32 spawnValue, SpawnProximityType type) +{ + if (!has_spawn_proximities) + return; + + if (spawn_proximities.size() > 0) + { + MutexList::iterator itr = spawn_proximities.begin(); + while (itr.Next()) { + SpawnProximity* prox = itr->value; + if (prox->spawn_value == spawnValue && prox->spawn_type == type) + prox->spawns_in_proximity.insert(make_pair(spawnValue, false)); + } + } +} + +void Spawn::RemoveSpawnFromProximity(int32 spawnValue, SpawnProximityType type) +{ + if (!has_spawn_proximities) + return; + + if (spawn_proximities.size() > 0) + { + MutexList::iterator itr = spawn_proximities.begin(); + while (itr.Next()) { + SpawnProximity* prox = itr->value; + if (prox->spawn_value == spawnValue && prox->spawn_type == type && + prox->spawns_in_proximity.count(spawnValue) > 0) + prox->spawns_in_proximity.erase(spawnValue); + } + } +} + +void Spawn::AddPrimaryEntityCommand(const char* name, float distance, const char* command, const char* error_text, int16 cast_time, int32 spell_visual, bool defaultDenyList, Player* player) { + + EntityCommand* cmd = FindEntityCommand(string(command), true); + + bool newCommand = false; + if (!cmd) + { + newCommand = true; + cmd = CreateEntityCommand(name, distance, command, error_text, cast_time, spell_visual, !defaultDenyList); + } + + if (defaultDenyList) + SetPermissionToEntityCommand(cmd, player, true); + + if (newCommand) + primary_command_list.push_back(cmd); +} + +void Spawn::RemovePrimaryEntityCommand(const char* command) { + vector::iterator itr; + string tmpStr(command); + for (itr = primary_command_list.begin(); itr != primary_command_list.end(); itr++) { + EntityCommand* cmd = *itr; + if (cmd->command.compare(tmpStr) == 0) + { + primary_command_list.erase(itr); + delete cmd; + break; + } + } +} + +bool Spawn::SetPermissionToEntityCommand(EntityCommand* command, Player* player, bool permissionValue) +{ + if(!player) + return false; + + return SetPermissionToEntityCommandByCharID(command, player->GetCharacterID(), permissionValue); +} + +bool Spawn::SetPermissionToEntityCommandByCharID(EntityCommand* command, int32 charID, bool permissionValue) +{ + map::iterator itr = command->allow_or_deny.find(charID); + if (itr == command->allow_or_deny.end()) + command->allow_or_deny.insert(make_pair(charID, permissionValue)); + else if (itr->second != permissionValue) + itr->second = permissionValue; + + return true; +} + +void Spawn::RemoveSpawnFromPlayer(Player* player) +{ + m_Update.writelock(__FUNCTION__, __LINE__); + player->RemoveSpawn(this); // sets it as removed + m_Update.releasewritelock(__FUNCTION__, __LINE__); +} + +bool Spawn::InWater() +{ + bool inWater = false; + + if ( region_map != nullptr ) + { + glm::vec3 targPos(GetX(), GetY(), GetZ()); + if ( IsGroundSpawn() ) + targPos.y -= .5f; + + if(region_map->InWater(targPos, GetLocation())) + inWater = true; + } + + return inWater; +} + +bool Spawn::InLava() +{ + bool inLava = false; + + if ( region_map != nullptr ) + { + glm::vec3 targPos(GetX(), GetY(), GetZ()); + if ( IsGroundSpawn() ) + targPos.y -= .5f; + + if(region_map->InLava(targPos, GetLocation())) + inLava = true; + } + + return inLava; +} + +void Spawn::DeleteRegion(Region_Node* inNode, ZBSP_Node* rootNode) +{ + map, Region_Status>::iterator testitr; + for (testitr = Regions.begin(); testitr != Regions.end(); testitr++) + { + map::const_iterator actualItr = testitr->first.begin(); + Region_Node* node = actualItr->first; + ZBSP_Node* BSP_Root = actualItr->second; + if(inNode == node && rootNode == BSP_Root ) + { + testitr = Regions.erase(testitr); + break; + } + } +} + +bool Spawn::InRegion(Region_Node* inNode, ZBSP_Node* rootNode) +{ + map, Region_Status>::iterator testitr; + for (testitr = Regions.begin(); testitr != Regions.end(); testitr++) + { + map::const_iterator actualItr = testitr->first.begin(); + Region_Node* node = actualItr->first; + ZBSP_Node* BSP_Root = actualItr->second; + if(inNode == node && rootNode == BSP_Root ) + { + return testitr->second.inRegion; + } + } + + return false; +} + +int32 Spawn::GetRegionType(Region_Node* inNode, ZBSP_Node* rootNode) +{ + map, Region_Status>::iterator testitr; + for (testitr = Regions.begin(); testitr != Regions.end(); testitr++) + { + map::const_iterator actualItr = testitr->first.begin(); + Region_Node* node = actualItr->first; + ZBSP_Node* BSP_Root = actualItr->second; + if(inNode == node && rootNode == BSP_Root ) + { + return testitr->second.regionType; + } + } + + return false; +} + +float Spawn::SpawnAngle(Spawn* target, float selfx, float selfz) +{ + if (!target || target == this) + return 0.0f; + + float angle, lengthb, vectorx, vectorz, dotp; + float spx = (target->GetX()); // mob xloc (inverse because eq) + float spz = -(target->GetZ()); // mob yloc + float heading = target->GetHeading(); // mob heading + if (heading < 270) + heading += 90; + else + heading -= 270; + + heading = heading * 3.1415f / 180.0f; // convert to radians + vectorx = spx + (10.0f * std::cos(heading)); // create a vector based on heading + vectorz = spz + (10.0f * std::sin(heading)); // of spawn length 10 + + // length of spawn to player vector + lengthb = (float) std::sqrt(((selfx - spx) * (selfx - spx)) + ((-selfz - spz) * (-selfz - spz))); + + // calculate dot product to get angle + // Handle acos domain errors due to floating point rounding errors + dotp = ((vectorx - spx) * (selfx - spx) + + (vectorz - spz) * (-selfz - spz)) / (10.0f * lengthb); + + if (dotp > 1) + return 0.0f; + else if (dotp < -1) + return 180.0f; + + angle = std::acos(dotp); + angle = angle * 180.0f / 3.1415f; + + return angle; +} + +void Spawn::StopMovement() +{ + reset_movement = true; +} + +bool Spawn::PauseMovement(int32 period_of_time_ms) +{ + if(period_of_time_ms < 1) + period_of_time_ms = 1; + + RunToLocation(GetX(),GetY(),GetZ()); + pause_timer.Start(period_of_time_ms, true); + + return true; +} + +bool Spawn::IsPauseMovementTimerActive() +{ + if(pause_timer.Check()) + pause_timer.Disable(); + + return pause_timer.Enabled(); +} + +bool Spawn::IsFlyingCreature() +{ + if(!IsEntity()) + return false; + + return ((Entity*)this)->GetInfoStruct()->get_flying_type(); +} + +bool Spawn::IsWaterCreature() +{ + if(!IsEntity()) + return false; + + return ((Entity*)this)->GetInfoStruct()->get_water_type(); +} + + +void Spawn::SetFlyingCreature() { + if(!IsEntity() || !rule_manager.GetGlobalRule(R_Spawn, UseHardCodeFlyingModelType)->GetInt8()) + return; + + if(((Entity*)this)->GetInfoStruct()->get_flying_type() > 0) // DB spawn npc flag already set + return; + + switch (GetModelType()) + { + case 260: + case 295: + ((Entity*)this)->GetInfoStruct()->set_flying_type(1); + is_flying_creature = true; + break; + default: + ((Entity*)this)->GetInfoStruct()->set_flying_type(0); + break; + } +} + +void Spawn::SetWaterCreature() { + if(!IsEntity() || !rule_manager.GetGlobalRule(R_Spawn, UseHardCodeWaterModelType)->GetInt8()) + return; + + if(((Entity*)this)->GetInfoStruct()->get_water_type() > 0) // DB spawn npc flag already set + return; + + switch (GetModelType()) + { + case 194: + case 204: + case 210: + case 241: + case 242: + case 254: + case 10668: + case 20828: + ((Entity*)this)->GetInfoStruct()->set_water_type(1); + break; + default: + ((Entity*)this)->GetInfoStruct()->set_water_type(0); + break; + } +} + +void Spawn::AddRailPassenger(int32 char_id) +{ + std::lock_guard lk(m_RailMutex); + rail_passengers.insert(make_pair(char_id,true)); +} + +void Spawn::RemoveRailPassenger(int32 char_id) +{ + std::lock_guard lk(m_RailMutex); + std::map::iterator itr = rail_passengers.find(char_id); + if(itr != rail_passengers.end()) + rail_passengers.erase(itr); +} + +vector Spawn::GetPassengersOnRail() { + vector tmp_list; + Spawn* spawn; + m_RailMutex.lock(); + std::map::iterator itr = rail_passengers.begin(); + while(itr != rail_passengers.end()){ + Client* client = zone_list.GetClientByCharID(itr->first); + if(!client || !client->GetPlayer()) + continue; + + tmp_list.push_back(client->GetPlayer()); + itr++; + } + m_RailMutex.unlock(); + return tmp_list; +} + +void Spawn::SetAppearancePosition(float x, float y, float z) { + appearance.pos.X = x; + appearance.pos.Y = y; + appearance.pos.Z = z; + appearance.pos.X2 = appearance.pos.X; + appearance.pos.Y2 = appearance.pos.Y; + appearance.pos.Z2 = appearance.pos.Z; + appearance.pos.X3 = appearance.pos.X; + appearance.pos.Y3 = appearance.pos.Y; + appearance.pos.Z3 = appearance.pos.Z; + + SetSpeedX(0); + SetSpeedY(0); + SetSpeedZ(0); + if(IsPlayer()) { + ((Player*)this)->SetSideSpeed(0); + ((Player*)this)->pos_packet_speed = 0; + } +} + + +int32 Spawn::InsertRegionToSpawn(Region_Node* node, ZBSP_Node* bsp_root, WaterRegionType regionType, bool in_region) { + std::map newMap; + newMap.insert(make_pair(node, bsp_root)); + Region_Status status; + status.inRegion = in_region; + status.regionType = regionType; + int32 returnValue = 0; + if(in_region) { + lua_interface->RunRegionScript(node->regionScriptName, "EnterRegion", GetZone(), this, regionType, &returnValue); + } + status.timerTic = returnValue; + status.lastTimerTic = returnValue ? Timer::GetCurrentTime2() : 0; + Regions.insert(make_pair(newMap, status)); + return returnValue; +} + + +bool Spawn::HasRegionTracked(Region_Node* node, ZBSP_Node* bsp_root, bool in_region) { + map, Region_Status>::iterator testitr; + for (testitr = Regions.begin(); testitr != Regions.end(); testitr++) + { + map::const_iterator actualItr = testitr->first.begin(); + Region_Node *node = actualItr->first; + ZBSP_Node *BSP_Root = actualItr->second; + if(node == actualItr->first && BSP_Root == actualItr->second) { + if(testitr->second.inRegion == in_region) + return true; + else + break; + } + } + + return false; +} + + +void Spawn::SetLocation(int32 id, bool setUpdateFlags) +{ + if(GetZone()) { + GetZone()->RemoveSpawnFromGrid(this, GetLocation()); + SetPos(&appearance.pos.grid_id, id, setUpdateFlags); + GetZone()->AddSpawnToGrid(this, id); + } + else { + SetPos(&appearance.pos.grid_id, id, setUpdateFlags); + } +} + +int8 Spawn::GetArrowColor(int8 spawn_level){ + int8 color = 0; + sint16 diff = spawn_level - GetLevel(); + if(GetLevel() < 10) + diff *= 3; + else if(GetLevel() <= 20) + diff *= 2; + if(diff >= 9) + color = ARROW_COLOR_RED; + else if(diff >= 5) + color = ARROW_COLOR_ORANGE; + else if(diff >= 1) + color = ARROW_COLOR_YELLOW; + else if(diff == 0) + color = ARROW_COLOR_WHITE; + else if(diff <= -11) + color = ARROW_COLOR_GRAY; + else if(diff <= -6) + color = ARROW_COLOR_GREEN; + else //if(diff < 0) + color = ARROW_COLOR_BLUE; + return color; +} + +void Spawn::AddIgnoredWidget(int32 id) { + std::unique_lock lock(MIgnoredWidgets); + if(ignored_widgets.find(id) == ignored_widgets.end()) { + ignored_widgets.insert(make_pair(id,true)); + } +} + +void Spawn::SendGroupUpdate() { + if (IsEntity() && ((Entity*)this)->GetGroupMemberInfo()) { + ((Entity*)this)->UpdateGroupMemberInfo(); + if (IsPlayer()) { + Client* client = ((Player*)this)->GetClient(); + if(client) { + world.GetGroupManager()->SendGroupUpdate(((Entity*)this)->GetGroupMemberInfo()->group_id, client); + } + } + else + world.GetGroupManager()->SendGroupUpdate(((Entity*)this)->GetGroupMemberInfo()->group_id); + } +} + +bool Spawn::AddNeedGreedItemRequest(int32 item_id, int32 spawn_id, bool need_item) { + LogWrite(LOOT__INFO, 0, "Loot", "%s: AddNeedGreedItemRequest Item ID: %u, Spawn ID: %u, Need Item: %u", GetName(), item_id, spawn_id, need_item); + if (HasSpawnNeedGreedEntry(item_id, spawn_id)) { + return false; + } + + need_greed_items.insert(make_pair(item_id, std::make_pair(spawn_id, need_item))); + + AddSpawnLootWindowCompleted(spawn_id, false); + return true; +} + +bool Spawn::AddLottoItemRequest(int32 item_id, int32 spawn_id) { + LogWrite(LOOT__INFO, 0, "Loot", "%s: AddLottoItemRequest Item ID: %u, Spawn ID: %u", GetName(), item_id, spawn_id); + if (HasSpawnLottoEntry(item_id, spawn_id)) { + return false; + } + + lotto_items.insert(make_pair(item_id, spawn_id)); + + AddSpawnLootWindowCompleted(spawn_id, false); + return true; +} + +void Spawn::AddSpawnLootWindowCompleted(int32 spawn_id, bool status_) { + if (loot_complete.find(spawn_id) == loot_complete.end()) { + loot_complete.insert(make_pair(spawn_id, status_)); + } + + is_loot_complete = HasLootWindowCompleted(); +} + +bool Spawn::SetSpawnLootWindowCompleted(int32 spawn_id) { + std::map::iterator itr = loot_complete.find(spawn_id); + if (itr != loot_complete.end()) { + itr->second = true; + is_loot_complete = HasLootWindowCompleted(); + return true; + } + return false; +} + +bool Spawn::HasSpawnLootWindowCompleted(int32 spawn_id) { + std::map::iterator itr = loot_complete.find(spawn_id); + if (itr != loot_complete.end() && itr->second) { + return true; + } + return false; +} + +bool Spawn::HasSpawnNeedGreedEntry(int32 item_id, int32 spawn_id) { + for (auto [itr, rangeEnd] = need_greed_items.equal_range(item_id); itr != rangeEnd; itr++) { + LogWrite(LOOT__DEBUG, 8, "Loot", "%s: HasSpawnNeedGreedEntry Item ID: %u, Spawn ID: %u", GetName(), itr->first, itr->second.first); + if (spawn_id == itr->second.first) { + return true; + } + } + return false; +} + +bool Spawn::HasSpawnLottoEntry(int32 item_id, int32 spawn_id) { + for (auto [itr, rangeEnd] = lotto_items.equal_range(item_id); itr != rangeEnd; itr++) { + LogWrite(LOOT__DEBUG, 8, "Loot", "%s: HasSpawnLottoEntry Item ID: %u, Spawn ID: %u", GetName(), itr->first, itr->second); + if (spawn_id == itr->second) { + return true; + } + } + return false; +} + +void Spawn::GetSpawnLottoEntries(int32 item_id, std::map* out_entries) { + if (!out_entries) + return; + + std::map spawn_matches; + for (auto [itr, endrange] = lotto_items.equal_range(item_id); itr != endrange; itr++) { + out_entries->insert(std::make_pair(itr->second, (int32)MakeRandomInt(0, 100))); + spawn_matches[itr->second] = true; + } + + // 0xFFFFFFFF represents selecting "All" on the lotto screen + for (auto [itr, endrange] = lotto_items.equal_range(0xFFFFFFFF); itr != endrange; itr++) { + if (spawn_matches.find(itr->second) == spawn_matches.end()) { + out_entries->insert(std::make_pair(itr->second, (int32)MakeRandomInt(0, 100))); + } + } +} + +void Spawn::GetSpawnNeedGreedEntries(int32 item_id, bool need_item, std::map* out_entries) { + if (!out_entries) + return; + + for (auto [itr, rangeEnd] = need_greed_items.equal_range(item_id); itr != rangeEnd; itr++) { + out_entries->insert(std::make_pair(itr->second.first, (int32)MakeRandomInt(0, 100))); + } +} + +bool Spawn::HasLootWindowCompleted() { + std::map::iterator itr; + for (itr = loot_complete.begin(); itr != loot_complete.end(); itr++) { + if (!itr->second) + return false; + } + + return true; +} + +void Spawn::StartLootTimer(Spawn* looter) { + if (!IsLootTimerRunning()) { + int32 loot_timer_time = rule_manager.GetGlobalRule(R_Loot, LootDistributionTime)->GetInt32() * 1000; + if(rule_manager.GetGlobalRule(R_Loot, AllowChestUnlockByDropTime)->GetBool() && loot_timer_time > rule_manager.GetGlobalRule(R_Loot, ChestUnlockedTimeDrop)->GetInt32()*1000) { + loot_timer_time = (rule_manager.GetGlobalRule(R_Loot, ChestUnlockedTimeDrop)->GetInt32()*1000) / 2; + } + + if(rule_manager.GetGlobalRule(R_Loot, AllowChestUnlockByTrapTime)->GetBool() && loot_timer_time > rule_manager.GetGlobalRule(R_Loot, ChestUnlockedTimeTrap)->GetInt32()*1000) { + loot_timer_time = (rule_manager.GetGlobalRule(R_Loot, ChestUnlockedTimeTrap)->GetInt32()*1000) / 2; + } + + if(loot_timer_time < 1000) { + loot_timer_time = 60000; // hardcode assure they aren't setting some really ridiculous low number + } + + loot_timer.Start(loot_timer_time, true); + } + if (looter) { + looter_spawn_id = looter->GetID(); + } +} + +void Spawn::CloseLoot(Spawn* sender) { + if (sender) { + SetSpawnLootWindowCompleted(sender->GetID()); + } + if (sender && looter_spawn_id > 0 && sender->GetID() != looter_spawn_id) { + LogWrite(LOOT__ERROR, 0, "Loot", "%s: CloseLoot Looter Spawn ID: %u does not match sender %u.", GetName(), looter_spawn_id, sender->GetID()); + return; + } + if (!IsLootTimerRunning() && GetLootMethod() != GroupLootMethod::METHOD_LOTTO && GetLootMethod() != GroupLootMethod::METHOD_NEED_BEFORE_GREED) { + loot_timer.Disable(); + } + looter_spawn_id = 0; +} + +void Spawn::SetLootMethod(GroupLootMethod method, int8 item_rarity, int32 group_id) { + LogWrite(LOOT__INFO, 0, "Loot", "%s: Set Loot Method : %u, group id : %u", GetName(), (int32)method, group_id); + loot_group_id = group_id; + loot_method = method; + loot_rarity = item_rarity; + if (loot_name.size() < 1) { + loot_name = std::string(GetName()); + } +} + +bool Spawn::IsItemInLootTier(Item* item) { + if (!item) + return true; + + bool skipItem = true; + switch (GetLootRarity()) { + case LootTier::ITEMS_TREASURED_PLUS: { + if (item->details.tier >= ITEM_TAG_TREASURED) { + skipItem = false; + } + break; + } + case LootTier::ITEMS_LEGENDARY_PLUS: { + if (item->details.tier >= ITEM_TAG_LEGENDARY) { + skipItem = false; + } + break; + } + case LootTier::ITEMS_FABLED_PLUS: { + if (item->details.tier >= ITEM_TAG_FABLED) { + skipItem = false; + } + break; + } + default: { + skipItem = false; + break; + } + } + + return skipItem; +} + +void Spawn::DistributeGroupLoot_RoundRobin(std::vector* item_list, bool roundRobinTrashLoot) { + + std::vector::iterator item_itr; + + for (item_itr = item_list->begin(); item_itr != item_list->end(); item_itr++) { + int32 item_id = *item_itr; + Item* tmpItem = master_item_list.GetItem(item_id); + Spawn* looter = nullptr; + + bool skipItem = IsItemInLootTier(tmpItem); + + if ((skipItem && !roundRobinTrashLoot) || (!skipItem && roundRobinTrashLoot)) + continue; + + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + PlayerGroup* group = world.GetGroupManager()->GetGroup(GetLootGroupID()); + if (group) { + group->MGroupMembers.writelock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + + int8 index = group->GetLastLooterIndex(); + if (index >= members->size()) { + index = 0; + } + + GroupMemberInfo* gmi = members->at(index); + if (gmi) { + looter = gmi->member; + } + bool loopAttempted = false; + while (looter) { + if (!looter->IsPlayer()) { + index++; + if (index >= members->size()) { + if (loopAttempted) { + looter = nullptr; + break; + } + loopAttempted = true; + index = 0; + } + gmi = members->at(index); + if (gmi) { + looter = gmi->member; + } + continue; + } + else { + break; + } + } + index += 1; + group->SetNextLooterIndex(index); + group->MGroupMembers.releasewritelock(__FUNCTION__, __LINE__); + } + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + + if (looter) { + if (looter->IsPlayer()) { + Item* item = LootItem(item_id); + bool success = false; + success = ((Player*)looter)->GetClient()->HandleLootItem(this, item, ((Player*)looter), roundRobinTrashLoot); + + if (!success) + AddLootItem(item); + } + else { + Item* item = LootItem(item_id); + safe_delete(item); + } + } + } +} + +const double g = 9.81; // acceleration due to gravity (m/s^2) + +void Spawn::CalculateInitialVelocity(float heading, float distanceHorizontal, float distanceVertical, float distanceDepth, float duration) { + float vx = distanceHorizontal / duration; + float vy = (distanceVertical + 0.5 * g * duration * duration) / duration; + float vz = distanceDepth / duration; + + // Convert heading angle to radians + knocked_velocity.x = vx * cos(heading); + knocked_velocity.y = vy; + knocked_velocity.z = vz * sin(heading); +} + +// Function to calculate the projectile position at a given time +glm::vec3 Spawn::CalculateProjectilePosition(glm::vec3 initialVelocity, float time) { + glm::vec3 position; + + position.x = knocked_back_start_x + initialVelocity.x * time; + position.y = knocked_back_start_y + initialVelocity.y * time - 0.5 * g * time * time; + position.z = knocked_back_start_z + initialVelocity.z * time; + + auto loc = glm::vec3(position.x, position.z, position.y); + float new_z = FindBestZ(loc, nullptr); + if(new_z > position.y) + position.y = new_z; + + return position; +} + +bool Spawn::CalculateSpawnProjectilePosition(float x, float y, float z) { + float currentTimeOffset = (Timer::GetCurrentTime2() - last_movement_update) * 0.001; // * 0.001 is the same as / 1000, float muliplications is suppose to be faster though + float stepAheadOne = currentTimeOffset+currentTimeOffset; + + knocked_back_time_step += currentTimeOffset; + if(Timer::GetCurrentTime2() >= knocked_back_end_time) { + ResetKnockedBack(); + FixZ(true); + return false; + } + glm::vec3 position = CalculateProjectilePosition(knocked_velocity, knocked_back_time_step); + glm::vec3 position_two = position; + + if(Timer::GetCurrentTime2() <= knocked_back_end_time+stepAheadOne) { + position_two = CalculateProjectilePosition(knocked_velocity, knocked_back_time_step+stepAheadOne); + } + + if(GetMap()) { + glm::vec3 loc(GetX(), GetZ(), GetY() + .5f); + glm::vec3 dest_loc(position_two.x, position_two.z, position_two.y); + MIgnoredWidgets.lock_shared(); + glm::vec3 outNorm; + float dist = 0.0f; + bool collide_ = GetMap()->DoCollisionCheck(loc, dest_loc, &ignored_widgets, outNorm, dist); + if(collide_) { + LogWrite(SPAWN__ERROR, 0, "Spawn", "Collision Hit: cur loc x,y,z: %f %f %f. to loc %f %f %f. TimeOffset: %f Total Time: %f Duration: %f", GetX(),GetY(),GetZ(),position_two.x,position_two.y,position_two.z, currentTimeOffset, knocked_back_time_step, knocked_back_duration); + MIgnoredWidgets.unlock_shared(); + ResetKnockedBack(); + FixZ(true); + return false; + } + MIgnoredWidgets.unlock_shared(); + } + + LogWrite(SPAWN__ERROR, 0, "Spawn", "x,y,z: %f %f %f. Final %f %f %f. TimeOffset: %f Total Time: %f Duration: %f", GetX(),GetY(),GetZ(),position.x,position.y,position.z, currentTimeOffset, knocked_back_time_step, knocked_back_duration); + + SetX(position.x, false); + SetZ(position.z, false); + SetY(position.y, false, true); + + SetPos(&appearance.pos.X2, position_two.x, false); + SetPos(&appearance.pos.Z2, position_two.z, false); + SetPos(&appearance.pos.Y2, position_two.y, false); + SetPos(&appearance.pos.X3, position_two.x, false); + SetPos(&appearance.pos.Z3, position_two.z, false); + SetPos(&appearance.pos.Y3, position_two.y, false); + + position_changed = true; + changed = true; + GetZone()->AddChangedSpawn(this); + return true; +} + +void Spawn::SetKnockback(Spawn* target, int32 duration, float vertical, float horizontal) { + if(knocked_back) { + return; // already being knocked back + } + + // Calculate the direction vector from source to destination + glm::vec3 direction = {GetX() - target->GetX(), GetZ() - target->GetZ(), GetY() - target->GetY()}; + + // Calculate the heading angle in radians + double headingRad = atan2(direction.y, sqrt(direction.x * direction.x + direction.z * direction.z)); + + knocked_angle = headingRad; + knocked_back_start_x = GetX(); + knocked_back_start_y = GetY(); + knocked_back_start_z = GetZ(); + knocked_back_h_distance = horizontal / 10.0f; + knocked_back_v_distance = vertical / 10.0f; + knocked_back_duration = static_cast(duration) / 1000.0f; + knocked_back_end_time = Timer::GetCurrentTime2() + duration; + CalculateInitialVelocity(knocked_angle, knocked_back_h_distance, knocked_back_h_distance, knocked_back_h_distance, knocked_back_duration); + knocked_back = true; +} + +void Spawn::ResetKnockedBack() { + knocked_back = false; + knocked_back_time_step = 0.0f; + knocked_back_h_distance = 0.0f; + knocked_back_v_distance = 0.0f; + knocked_back_duration = 0.0f; + knocked_back_end_time = 0; + knocked_back_start_x = 0.0f; + knocked_back_start_y = 0.0f; + knocked_back_start_z = 0.0f; + knocked_angle = 0.0f; + knocked_velocity.x = 0.0f; + knocked_velocity.y = 0.0f; + knocked_velocity.z = 0.0f; +} \ No newline at end of file diff --git a/source/WorldServer/Spawn.h b/source/WorldServer/Spawn.h new file mode 100644 index 0000000..373f265 --- /dev/null +++ b/source/WorldServer/Spawn.h @@ -0,0 +1,1568 @@ +/* + 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 . +*/ +#ifndef __EQ2_SPAWN__ +#define __EQ2_SPAWN__ + +#include + +#include "../common/types.h" +#include "../common/EQPacket.h" +#include "../common/EQ2_Common_Structs.h" +#include "../common/MiscFunctions.h" +#include "../common/opcodemgr.h" +#include "../common/timer.h" +#include "Commands/Commands.h" +#include "Zone/position.h" +#include "SpawnLists.h" +#include +#include "../common/ConfigReader.h" +#include "Items/Items.h" +#include "Zone/map.h" +#include "Zone/region_map.h" +#include "Zone/region_map_v1.h" +#include "../common/Mutex.h" +#include "MutexList.h" +#include +#include // needed for LS to compile properly on linux +#include +#include + +#define DAMAGE_PACKET_TYPE_SIPHON_SPELL 0x41 +#define DAMAGE_PACKET_TYPE_SIPHON_SPELL2 0x49 +#define DAMAGE_PACKET_TYPE_MULTIPLE_DAMAGE 0x80 +#define DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE 0xC0 +#define DAMAGE_PACKET_TYPE_SPELL_DAMAGE 0xC1 +#define DAMAGE_PACKET_TYPE_SIMPLE_CRIT_DMG 0xC4 +#define DAMAGE_PACKET_TYPE_SPELL_CRIT_DMG 0xC5 +#define DAMAGE_PACKET_TYPE_SPELL_DAMAGE2 0xC8 +#define DAMAGE_PACKET_TYPE_SPELL_DAMAGE3 0xC9 +#define DAMAGE_PACKET_TYPE_RANGE_DAMAGE 0xE2 +#define DAMAGE_PACKET_TYPE_RANGE_SPELL_DMG 0xE3 +#define DAMAGE_PACKET_TYPE_RANGE_SPELL_DMG2 0xEA + +#define DAMAGE_PACKET_RESULT_NO_DAMAGE 0 +#define DAMAGE_PACKET_RESULT_SUCCESSFUL 1 +#define DAMAGE_PACKET_RESULT_MISS 4 +#define DAMAGE_PACKET_RESULT_DODGE 8 +#define DAMAGE_PACKET_RESULT_PARRY 12 +#define DAMAGE_PACKET_RESULT_RIPOSTE 16 +#define DAMAGE_PACKET_RESULT_BLOCK 20 +#define DAMAGE_PACKET_RESULT_DEATH_BLOW 24 +#define DAMAGE_PACKET_RESULT_INVULNERABLE 28 +#define DAMAGE_PACKET_RESULT_RESIST 36 +#define DAMAGE_PACKET_RESULT_REFLECT 40 +#define DAMAGE_PACKET_RESULT_IMMUNE 44 +#define DAMAGE_PACKET_RESULT_DEFLECT 48 +#define DAMAGE_PACKET_RESULT_COUNTER 52 +#define DAMAGE_PACKET_RESULT_FOCUS 56 // focus damage +#define DAMAGE_PACKET_RESULT_COUNTER_STRIKE 60 +#define DAMAGE_PACKET_RESULT_BASH 64 + +#define DAMAGE_PACKET_DAMAGE_TYPE_SLASH 0 +#define DAMAGE_PACKET_DAMAGE_TYPE_CRUSH 1 +#define DAMAGE_PACKET_DAMAGE_TYPE_PIERCE 2 +#define DAMAGE_PACKET_DAMAGE_TYPE_HEAT 3 +#define DAMAGE_PACKET_DAMAGE_TYPE_COLD 4 +#define DAMAGE_PACKET_DAMAGE_TYPE_MAGIC 5 +#define DAMAGE_PACKET_DAMAGE_TYPE_MENTAL 6 +#define DAMAGE_PACKET_DAMAGE_TYPE_DIVINE 7 +#define DAMAGE_PACKET_DAMAGE_TYPE_DISEASE 8 +#define DAMAGE_PACKET_DAMAGE_TYPE_POISON 9 +#define DAMAGE_PACKET_DAMAGE_TYPE_DROWN 10 +#define DAMAGE_PACKET_DAMAGE_TYPE_FALLING 11 +#define DAMAGE_PACKET_DAMAGE_TYPE_PAIN 12 +#define DAMAGE_PACKET_DAMAGE_TYPE_HIT 13 +#define DAMAGE_PACKET_DAMAGE_TYPE_FOCUS 14 // used as a placeholder to translate over to focus from LUA functions and weapons + + +#define HEAL_PACKET_TYPE_SIMPLE_HEAL 0 +#define HEAL_PACKET_TYPE_CRIT_HEAL 1 +#define HEAL_PACKET_TYPE_ABSORB 2 +#define HEAL_PACKET_TYPE_REGEN_ABSORB 4 +#define HEAL_PACKET_TYPE_SIMPLE_MANA 8 +#define HEAL_PACKET_TYPE_CRIT_MANA 9 +#define HEAL_PACKET_TYPE_SAVAGERY 16 +#define HEAL_PACKET_TYPE_CRIT_SAVAGERY 17 +#define HEAL_PACKET_TYPE_REPAIR 64 +#define HEAL_PACKET_TYPE_CRIT_REPAIR 65 + +#define ARROW_COLOR_GRAY 0 // 3 +#define ARROW_COLOR_GREEN 1 // 1 +#define ARROW_COLOR_BLUE 2 +#define ARROW_COLOR_WHITE 3 // 3 +#define ARROW_COLOR_YELLOW 4 // 4 +#define ARROW_COLOR_ORANGE 5 // 5 +#define ARROW_COLOR_RED 6 + +#define ACTIVITY_STATUS_ROLEPLAYING 1 +#define ACTIVITY_STATUS_ANONYMOUS 2 +#define ACTIVITY_STATUS_LINKDEAD 4 +#define ACTIVITY_STATUS_CAMPING 8 +#define ACTIVITY_STATUS_LFG 16 +#define ACTIVITY_STATUS_LFW 32 +#define ACTIVITY_STATUS_SOLID 64 //used by zone objects to remain solid +#define ACTIVITY_STATUS_IMMUNITY_GAINED 8192 +#define ACTIVITY_STATUS_IMMUNITY_REMAINING 16384 +// WE ARE UNSURE OF THESE OLD CLIENT VALUES USED AS TEMP PLACEHOLDERS FOR NEWER CLIENTS +#define ACTIVITY_STATUS_AFK 32768 // whats the real one? + +#define ACTIVITY_STATUS_CORPSE_561 1 +#define ACTIVITY_STATUS_NPC_561 1<<1 +#define ACTIVITY_STATUS_STATICOBJECT_561 1<<2 +#define ACTIVITY_STATUS_MERCHANT_561 1<<4 +#define ACTIVITY_STATUS_HIDEICON_561 1<<8 +#define ACTIVITY_STATUS_INTERACTABLE_561 1<<9 +#define ACTIVITY_STATUS_NOTARGET_561 1<<10 +#define ACTIVITY_STATUS_ISTRANSPORT_561 1<<11 +#define ACTIVITY_STATUS_SHOWHOUSEICON_561 1<<12 +#define ACTIVITY_STATUS_LOOTABLE_561 1<<13 +#define ACTIVITY_STATUS_INCOMBAT_561 1<<14 +#define ACTIVITY_STATUS_AFK_561 1<<15 +#define ACTIVITY_STATUS_ROLEPLAYING_561 1<<16 +#define ACTIVITY_STATUS_ANONYMOUS_561 1<<17 +#define ACTIVITY_STATUS_LINKDEAD_561 1<<18 +#define ACTIVITY_STATUS_CAMPING_561 1<<19 +#define ACTIVITY_STATUS_LFG_561 1<<20 +#define ACTIVITY_STATUS_LFW_561 1<<21 + +#define ACTIVITY_STATUS_SOLID_561 1<<22 //used by zone objects to remain solid +#define ACTIVITY_STATUS_MENTORING_561 1<<28 +#define ACTIVITY_STATUS_IMMUNITY_GAINED_561 1<<30 +#define ACTIVITY_STATUS_IMMUNITY_REMAINING_561 1<<31 + +#define ACTIVITY_STATUS_MERCENARY_1188 1<<2 +#define ACTIVITY_STATUS_STATICOBJECT_1188 1<<3 +#define ACTIVITY_STATUS_MERCHANT_1188 1<<4 +#define ACTIVITY_STATUS_HIDEICON_1188 1<<9 +#define ACTIVITY_STATUS_INTERACTABLE_1188 1<<10 +#define ACTIVITY_STATUS_NOTARGET_1188 1<<11 +#define ACTIVITY_STATUS_ISTRANSPORT_1188 1<<12 +#define ACTIVITY_STATUS_SHOWHOUSEICON_1188 1<<13 +#define ACTIVITY_STATUS_LOOTABLE_1188 1<<14 +#define ACTIVITY_STATUS_INCOMBAT_1188 1<<15 +#define ACTIVITY_STATUS_AFK_1188 1<<16 +#define ACTIVITY_STATUS_ROLEPLAYING_1188 1<<17 +#define ACTIVITY_STATUS_ANONYMOUS_1188 1<<18 +#define ACTIVITY_STATUS_LINKDEAD_1188 1<<19 +#define ACTIVITY_STATUS_CAMPING_1188 1<<20 +#define ACTIVITY_STATUS_LFG_1188 1<<21 +#define ACTIVITY_STATUS_LFW_1188 1<<22 +#define ACTIVITY_STATUS_SOLID_1188 1<<23 //used by zone objects to remain solid +#define ACTIVITY_STATUS_MENTORING_1188 1<<28 +#define ACTIVITY_STATUS_IMMUNITY_GAINED_1188 1<<30 +#define ACTIVITY_STATUS_IMMUNITY_REMAINING_1188 1<<31 + +#define POS_STATE_KNEELING 64 +#define POS_STATE_SOLID 128 //used by most mobs to remaind solid (cant walk through them) +#define POS_STATE_NOTARGET_CURSOR 256 //cant target and no cursor is displayed +#define POS_STATE_CROUCHING 512 + +#define MERCHANT_TYPE_NO_BUY 1 +#define MERCHANT_TYPE_NO_BUY_BACK 2 +#define MERCHANT_TYPE_SPELLS 4 +#define MERCHANT_TYPE_CRAFTING 8 +#define MERCHANT_TYPE_REPAIR 16 +#define MERCHANT_TYPE_LOTTO 32 +#define MERCHANT_TYPE_CITYMERCHANT 64 + +#define INFO_VIS_FLAG_INVIS 1 +#define INFO_VIS_FLAG_HIDE_HOOD 2 +#define INFO_VIS_FLAG_MOUNTED 4 +#define INFO_VIS_FLAG_CROUCH 8 + +#define ENCOUNTER_STATE_NONE 0 +#define ENCOUNTER_STATE_AVAILABLE 1 +#define ENCOUNTER_STATE_BROKEN 2 +#define ENCOUNTER_STATE_LOCKED 3 +#define ENCOUNTER_STATE_OVERMATCHED 4 +#define ENCOUNTER_STATE_NO_REWARD 5 + +#define VISUAL_STATE_COLLECTION_TURN_IN 6674 +#define VISUAL_STATE_IDLE_AFRAID 17953 + +#define INFO_CLASSIC_FLAG_INVIS 1 +#define INFO_CLASSIC_FLAG_SHOW_HOOD 2 +#define INFO_CLASSIC_FLAG_NOLOOK 4 +#define INFO_CLASSIC_FLAG_CROUCH 8 + +using namespace std; +class Spell; +class ZoneServer; +class Quest; +struct LUAHistory; +struct Cell; + +struct CellInfo { + Cell* CurrentCell; + int CellListIndex; +}; + +struct MovementData{ + float x; + float y; + float z; + float speed; + int32 delay; + string lua_function; + float heading; + bool use_movement_location_heading; +}; + +struct BasicInfoStruct{ + sint32 cur_hp; + sint32 max_hp; + sint32 hp_base; + sint32 hp_base_instance; + sint32 cur_power; + sint32 max_power; + sint32 power_base; + sint32 power_base_instance; + sint32 cur_savagery; + sint32 max_savagery; + sint32 savagery_base; + sint32 cur_dissonance; + sint32 max_dissonance; + sint32 dissonance_base; + sint16 assigned_aa; + sint16 unassigned_aa; + sint16 tradeskill_aa; + sint16 unassigned_tradeskill_aa; + sint16 prestige_aa; + sint16 unassigned_prestige_aa; + sint16 tradeskill_prestige_aa; + sint16 unassigned_tradeskill_prestige_aa; + int32 aaxp_rewards; +}; + +struct MovementLocation{ + float x; + float y; + float z; + float speed; + //int32 start_time; + //int32 end_time; + bool attackable; + string lua_function; + bool mapped; + int32 gridid; + int8 stage; + bool reset_hp_on_runback; +}; + +struct SpawnUpdate { + int32 spawn_id; + bool info_changed; + bool vis_changed; + bool pos_changed; + shared_ptr client; +}; + +struct SpawnData { + Spawn* spawn; + uchar* data; + int32 size; +}; + +struct TimedGridData { + int32 timestamp; + int32 grid_id; + float x; + float y; + float z; + float offset_y; + float zone_ground_y; + bool npc_save; + int32 widget_id; +}; + +enum GroupLootMethod { + METHOD_LEADER=0, + METHOD_FFA=1, + METHOD_LOTTO=2, + METHOD_NEED_BEFORE_GREED=3, + METHOD_ROUND_ROBIN=4 +}; + +enum AutoLootMode { + METHOD_DISABLED=0, + METHOD_ACCEPT=1, + METHOD_DECLINE=2 +}; + +enum LootTier { + ITEMS_ALL=0, + ITEMS_TREASURED_PLUS=1, + ITEMS_LEGENDARY_PLUS=2, + ITEMS_FABLED_PLUS=3 +}; + + +class Spawn { +public: + Spawn(); + virtual ~Spawn(); + + template void Set(Field* field, Value value, bool setUpdateFlags = true){ + if (setUpdateFlags) { + changed = true; + AddChangedZoneSpawn(); + } + *field = value; + } + template void Set(Field* field, const char* value, bool setUpdateFlags = true){ + if (setUpdateFlags) { + changed = true; + AddChangedZoneSpawn(); + } + strcpy(field, value); + } + template void SetPos(Field* field, Value value, bool setUpdateFlags = true){ + if(setUpdateFlags){ + position_changed = true; + } + Set(field, value, setUpdateFlags); + } + template void SetInfo(Field* field, Value value, bool setUpdateFlags = true){ + if(setUpdateFlags){ + info_changed = true; + } + Set(field, value); + } + template void SetVis(Field* field, Value value, bool setUpdateFlags = true){ + if(setUpdateFlags) + vis_changed = true; + Set(field, value); + } + template void SetPos(Field* field, char* value, bool setUpdateFlags = true){ + if(setUpdateFlags){ + position_changed = true; + } + Set(field, value, setUpdateFlags); + } + template void SetInfo(Field* field, char* value, bool setUpdateFlags = true){ + if(setUpdateFlags){ + info_changed = true; + } + Set(field, value); + } + EntityCommand* CreateEntityCommand(EntityCommand* old_command){ + EntityCommand* entity_command = new EntityCommand; + entity_command->name = old_command->name; + entity_command->distance = old_command->distance; + entity_command->command = old_command->command; + entity_command->error_text = old_command->error_text; + entity_command->cast_time = old_command->cast_time; + entity_command->spell_visual = old_command->spell_visual; + entity_command->default_allow_list = old_command->default_allow_list; + return entity_command; + } + EntityCommand* CreateEntityCommand(const char* name, float distance, const char* command, const char* error_text, int16 cast_time, int32 spell_visual, bool default_allow_list=true){ + EntityCommand* entity_command = new EntityCommand; + entity_command->name = name; + entity_command->distance = distance; + entity_command->command = command; + entity_command->error_text = error_text; + entity_command->cast_time = cast_time; + entity_command->spell_visual = spell_visual; + entity_command->default_allow_list = default_allow_list; + return entity_command; + } + virtual Client* GetClient() { return 0; } + void AddChangedZoneSpawn(); + void AddPrimaryEntityCommand(const char* name, float distance, const char* command, const char* error_text, int16 cast_time, int32 spell_visual, bool defaultDenyList = false, Player* player = NULL); + void RemovePrimaryEntityCommand(const char* command); + bool SetPermissionToEntityCommand(EntityCommand* command, Player* player, bool permissionValue); + bool SetPermissionToEntityCommandByCharID(EntityCommand* command, int32 charID, bool permissionValue); + + void RemoveSpawnFromPlayer(Player* player); + + void AddSecondaryEntityCommand(const char* name, float distance, const char* command, const char* error_text, int16 cast_time, int32 spell_visual){ + secondary_command_list.push_back(CreateEntityCommand(name, distance, command, error_text, cast_time, spell_visual)); + } + int8 GetLockedNoLoot(){ + return appearance.locked_no_loot; + } + int16 GetEmoteState(){ + return appearance.emote_state; + } + int8 GetHideHood(){ + return appearance.hide_hood; + } + void SetLockedNoLoot(int8 new_val, bool updateFlags = true){ + SetVis(&appearance.locked_no_loot, new_val, updateFlags); + } + void SetHandFlag(int8 new_val, bool updateFlags = true){ + SetVis(&appearance.display_hand_icon, new_val, updateFlags); + } + void SetHideHood(int8 new_val, bool updateFlags = true){ + SetInfo(&appearance.hide_hood, new_val, updateFlags); + } + void SetEmoteState(int8 new_val, bool updateFlags = true){ + SetInfo(&appearance.emote_state, new_val, updateFlags); + } + void SetName(const char* new_name, bool updateFlags = true){ + SetInfo(appearance.name, new_name, updateFlags); + } + void SetPrefixTitle(const char* new_prefix_title, bool updateFlags = true) { + SetInfo(appearance.prefix_title, new_prefix_title, updateFlags); + } + void SetSuffixTitle(const char* new_suffix_title, bool updateFlags = true) { + SetInfo(appearance.suffix_title, new_suffix_title, updateFlags); + } + void SetSubTitle(const char* new_sub_title, bool updateFlags = true) { + SetInfo(appearance.sub_title, new_sub_title, updateFlags); + } + void SetLastName(const char* new_last_name, bool updateFlags = true) { + SetInfo(appearance.last_name, new_last_name, updateFlags); + } + void SetAdventureClass(int8 new_class, bool updateFlags = true) { + SetInfo(&appearance.adventure_class, new_class, updateFlags); + } + void SetTradeskillClass(int8 new_class, bool updateFlags = true) { + SetInfo(&appearance.tradeskill_class, new_class, updateFlags); + } + void SetSize(int16 new_size, bool updateFlags = true) { + SetPos(&size, new_size, updateFlags); + } + void SetSpeedX(float speed_x, bool updateFlags = true) { + SetPos(&appearance.pos.SpeedX, speed_x, updateFlags); + } + void SetSpeedY(float speed_y, bool updateFlags = true) { + SetPos(&appearance.pos.SpeedY, speed_y, updateFlags); + } + void SetSpeedZ(float speed_z, bool updateFlags = true) { + SetPos(&appearance.pos.SpeedZ, speed_z, updateFlags); + } + void SetX(float x, bool updateFlags = true){ + SetPos(&appearance.pos.X, x, updateFlags); + } + void SetY(float y, bool updateFlags = true, bool disableYMapFix = false); + void SetZ(float z, bool updateFlags = true){ + SetPos(&appearance.pos.Z, z, updateFlags); + } + void SetHeading(sint16 dir1, sint16 dir2, bool updateFlags = true){ + SetPos(&appearance.pos.Dir1, dir1, updateFlags); + SetPos(&appearance.pos.Dir2, dir2, updateFlags); + } + void SetHeading(float heading, bool updateFlags = true){ + last_heading_angle = heading; + if (heading != 180) + heading = (heading - 180) * 64; + SetHeading((sint16)heading, (sint16)heading, updateFlags); + } + void SetPitch(sint16 pitch1, sint16 pitch2, bool updateFlags = true){ + SetPos(&appearance.pos.Pitch1, (sint16)pitch1, updateFlags); + SetPos(&appearance.pos.Pitch2, (sint16)pitch2, updateFlags); + } + void SetPitch(float pitch, bool updateFlags = true){ + if (pitch == 0){ + SetPos(&appearance.pos.Pitch1, (sint16)0, updateFlags); + SetPos(&appearance.pos.Pitch2, (sint16)0, updateFlags); + return; + } + if (pitch != 180) + pitch = (pitch - 180) * 64; + SetPos(&appearance.pos.Pitch1, (sint16)pitch, updateFlags); + SetPos(&appearance.pos.Pitch2, (sint16)pitch, updateFlags); + } + void SetRoll(float roll, bool updateFlags = true){ + if (roll == 0){ + SetPos(&appearance.pos.Roll, (sint16)0, updateFlags); + return; + } + else if (roll != 180) + roll = (roll - 180) * 64; + SetPos(&appearance.pos.Roll, (sint16)roll, updateFlags); + } + void SetVisualState(int16 state, bool updateFlags = true){ + SetInfo(&appearance.visual_state, state, updateFlags); + } + void SetActionState(int16 state, bool updateFlags = true){ + SetInfo(&appearance.action_state, state, updateFlags); + } + void SetMoodState(int16 state, bool updateFlags = true){ + SetInfo(&appearance.mood_state, state, updateFlags); + } + void SetInitialState(int16 state, bool updateFlags = true){ + SetPos(&appearance.pos.state, state, updateFlags); + } + void SetActivityStatus(int16 state, bool updateFlags = true){ + SetInfo(&appearance.activity_status, state, updateFlags); + } + void SetCollisionRadius(int32 radius, bool updateFlags = true){ + SetPos(&appearance.pos.collision_radius, radius, updateFlags); + } + int16 GetCollisionRadius(){ + return appearance.pos.collision_radius; + } + int16 GetVisualState(){ + return appearance.visual_state; + } + int16 GetActionState(){ + return appearance.action_state; + } + int16 GetMoodState(){ + return appearance.mood_state; + } + int16 GetInitialState(){ + return appearance.pos.state; + } + int16 GetActivityStatus(){ + return appearance.activity_status; + } + int32 GetPrimaryCommandListID(){ + return primary_command_list_id; + } + int32 GetSecondaryCommandListID(){ + return secondary_command_list_id; + } + void SetID(int32 in_id){ + Set(&id, in_id); + } + void SetDifficulty(int8 difficulty, bool setUpdateFlags = true){ + SetInfo(&appearance.difficulty, difficulty, setUpdateFlags); + } + virtual void SetLevel(int16 level, bool setUpdateFlags = true){ + SetInfo(&appearance.level, level, setUpdateFlags); + } + void SetTSLevel(int16 tradeskill_level, bool setUpdateFlags = true){ + SetInfo(&appearance.tradeskill_level, tradeskill_level, setUpdateFlags); + } + void SetGender(int8 gender, bool setUpdateFlags = true){ + SetInfo(&appearance.gender, gender, setUpdateFlags); + } + void SetShowName(int8 new_val, bool setUpdateFlags = true){ + SetVis(&appearance.display_name, new_val, setUpdateFlags); + } + void SetShowLevel(int8 new_val, bool setUpdateFlags = true){ + SetVis(&appearance.show_level, new_val, setUpdateFlags); + } + void SetHeroic(int8 new_val, bool setUpdateFlags = true){ + SetInfo(&appearance.heroic_flag, new_val, setUpdateFlags); + } + void SetTargetable(int8 new_val, bool setUpdateFlags = true){ + SetVis(&appearance.targetable, new_val, setUpdateFlags); + } + void SetShowCommandIcon(int8 new_val, bool setUpdateFlags = true){ + SetVis(&appearance.show_command_icon, new_val, setUpdateFlags); + } + void SetShowHandIcon(int8 new_val, bool setUpdateFlags = true){ + SetVis(&appearance.display_hand_icon, new_val, setUpdateFlags); + } + void SetAttackable(int8 new_val, bool setUpdateFlags = true){ + SetInfo(&appearance.attackable, new_val, setUpdateFlags); + } + void SetLocation(int32 id, bool setUpdateFlags = true); + void SetRace(int8 race, bool setUpdateFlags = true){ + SetInfo(&appearance.race, race, setUpdateFlags); + } + void SetIcon(int8 icon, bool setUpdateFlags = true){ + SetInfo(&appearance.icon, icon, setUpdateFlags); + } + void AddIconValue(int8 val){ + if(!(appearance.icon & val)) + SetIcon(appearance.icon+val); + } + void RemoveIconValue(int8 val){ + if((appearance.icon & val)) + SetIcon(appearance.icon-val); + } + int8 GetIconValue(){ + return appearance.icon; + } + virtual void SetSpeed(float speed){ + SetPos(&appearance.pos.Speed1, (int8)speed); + } + virtual float GetSpeed(){ + return (float)appearance.pos.Speed1; + } + virtual float GetBaseSpeed(){ + return (float)appearance.pos.Speed1; + } + void SetSpawnType(int8 new_type){ + SetInfo(&spawn_type, new_type); + } + int8 GetSpawnType(){ + return spawn_type; + } + void SetDatabaseID(int32 new_id){ + database_id = new_id; + } + int32 GetDatabaseID(){ + return database_id; + } + int8 GetShowHandIcon(){ + return appearance.display_hand_icon; + } + int32 GetLocation(){ + return appearance.pos.grid_id; + } + int8 GetAttackable(){ + return appearance.attackable; + } + int8 GetShowName(){ + return appearance.display_name; + } + int8 GetShowLevel(){ + return appearance.show_level; + } + int8 GetHeroic(){ + return appearance.heroic_flag; + } + int8 GetTargetable(){ + return appearance.targetable; + } + int8 GetShowCommandIcon(){ + return appearance.show_command_icon; + } + char* GetName(){ + return appearance.name; + } + char* GetPrefixTitle(){ + return appearance.prefix_title; + } + char* GetSuffixTitle(){ + return appearance.suffix_title; + } + char* GetSubTitle() { + return appearance.sub_title; + } + char* GetLastName() { + return appearance.last_name; + } + int8 GetAdventureClass() { + return appearance.adventure_class; + } + int8 GetTradeskillClass() { + return appearance.tradeskill_class; + } + float GetDestinationX(){ + return appearance.pos.X2; + } + float GetX() { + return appearance.pos.X; + } + float GetSpeedX() { + return appearance.pos.SpeedX; + } + float GetSpeedY() { + return appearance.pos.SpeedY; + } + float GetSpeedZ() { + return appearance.pos.SpeedZ; + } + float GetDestinationY(){ + return appearance.pos.Y2; + } + float GetY(){ + return appearance.pos.Y; + } + float GetDestinationZ(){ + return appearance.pos.Z2; + } + float GetZ(){ + return appearance.pos.Z; + } + float GetHeading(){ + float heading = 0; + if(appearance.pos.Dir1 != 0){ + heading = ((float)appearance.pos.Dir1)/((float)64); + if(heading >= 180) + heading -= 180; + else + heading += 180; + } + return heading; + } + float GetPitch(){ + float pitch = 0; + if(appearance.pos.Pitch1 != 0){ + pitch = ((float)appearance.pos.Pitch1)/((float)64); + if(pitch >= 180) + pitch -= 180; + else + pitch += 180; + } + return pitch; + } + float GetRoll(){ + float roll = 0; + if(appearance.pos.Roll != 0){ + roll = ((float)appearance.pos.Roll)/((float)64); + if(roll >= 180) + roll -= 180; + else + roll += 180; + } + return roll; + } + int32 GetID(){ + return id; + } + float GetDistance(float x1, float y1, float z1, float x2, float y2, float z2); + float GetDistance(float x, float y, float z, float radius, bool ignore_y = false); + float GetDistance(float x, float y, float z, bool ignore_y = false); + float GetDistance(Spawn* spawn, bool ignore_y = false, bool includeRadius=true); + float GetDistance(Spawn* spawn, float x1, float y1, float z1, bool includeRadius=true); + float CalculateRadius(Spawn* target); + + int8 GetDifficulty(){ + return appearance.difficulty; + } + sint32 GetTotalPower(); + sint32 GetPower(); + sint32 GetTotalHP(); + sint32 GetHP(); + sint32 GetTotalHPBase(); + sint32 GetTotalHPBaseInstance(); + sint32 GetTotalPowerBase(); + sint32 GetTotalPowerBaseInstance(); + float GetHPRatio() { return GetHP() == 0 || GetTotalHP() == 0 ? 0 : ((float) GetHP() / GetTotalHP() * 100); } + int GetIntHPRatio() { return GetTotalHP() == 0 ? 0 : static_cast(GetHPRatio()); } + + sint32 GetTotalSavagery(); + sint32 GetSavagery(); + sint32 GetTotalDissonance(); + sint32 GetDissonance(); + sint32 GetTotalSavageryBase(); + sint32 GetTotalDissonanceBase(); + sint16 GetAssignedAA(); + sint16 GetUnassignedAA(); + sint16 GetTradeskillAA(); + sint16 GetUnassignedTradeskillAA(); + sint16 GetPrestigeAA(); + sint16 GetUnassignedPretigeAA(); + sint16 GetTradeskillPrestigeAA(); + sint16 GetUnassignedTradeskillPrestigeAA(); + int32 GetAAXPRewards(); + + void SetTotalPower(sint32 new_val); + void SetTotalHP(sint32 new_val); + void SetTotalSavagery(sint32 new_val); + void SetTotalDissonance(sint32 new_val); + void SetTotalPowerBase(sint32 new_val); + void SetTotalPowerBaseInstance(sint32 new_val); + void SetTotalHPBase(sint32 new_val); + void SetTotalHPBaseInstance(sint32 new_val); + void SetTotalSavageryBase(sint32 new_val); + void SetTotalDissonanceBase(sint32 new_val); + void SetPower(sint32 power, bool setUpdateFlags = true); + void SetHP(sint32 new_val, bool setUpdateFlags = true); + void SetSavagery(sint32 savagery, bool setUpdateFlags = true); + void SetDissonance(sint32 dissonance, bool setUpdateFlags = true); + void SetAssignedAA(sint16 new_val); + void SetUnassignedAA(sint16 new_val); + void SetTradeskillAA(sint16 new_val); + void SetUnassignedTradeskillAA(sint16 new_val); + void SetPrestigeAA(sint16 new_val); + void SetUnassignedPrestigeAA(sint16 new_val); + void SetTradeskillPrestigeAA(sint16 new_val); + void SetUnassignedTradeskillPrestigeAA(sint16 new_val); + void SetAAXPRewards(int32 amount); + void SetPrivateQuestSpawn(bool val) {req_quests_private = val;} + void SetQuestsRequiredOverride(int16 val) {req_quests_override = val;} + void SetQuestsRequiredContinuedAccess(bool val) {req_quests_continued_access = val;} + bool GetPrivateQuestSpawn() {return req_quests_private;} + int16 GetQuestsRequiredOverride() {return req_quests_override;} + bool GetQuestsRequiredContinuedAccess() {return req_quests_continued_access;} + + bool Alive(){ return is_alive; } + void SetAlive(bool val) { is_alive = val; } + + int16 GetLevel(){ + return appearance.level; + } + int16 GetTSLevel(){ + return appearance.tradeskill_level; + } + int8 GetGender(){ + return appearance.gender; + } + int8 GetRace(){ + return appearance.race; + } + int32 GetSize(){ + return size; + } + int32 GetDeviation(){ + return deviation; + } + void SetDeviation(int32 in_dev){ + deviation = in_dev; + } + float GetSpawnOrigHeading(){ + return appearance.pos.SpawnOrigHeading; + } + void SetSpawnOrigHeading(float val){ + appearance.pos.SpawnOrigHeading = val; + } + float GetSpawnOrigX(){ + return appearance.pos.SpawnOrigX; + } + float GetSpawnOrigY(){ + return appearance.pos.SpawnOrigY; + } + float GetSpawnOrigZ(){ + return appearance.pos.SpawnOrigZ; + } + float GetSpawnOrigPitch(){ + return appearance.pos.SpawnOrigPitch; + } + float GetSpawnOrigRoll(){ + return appearance.pos.SpawnOrigRoll; + } + void SetSpawnOrigX(float val){ + appearance.pos.SpawnOrigX = val; + } + void SetSpawnOrigY(float val){ + appearance.pos.SpawnOrigY = val; + } + void SetSpawnOrigZ(float val){ + appearance.pos.SpawnOrigZ = val; + } + void SetSpawnOrigRoll(float val){ + appearance.pos.SpawnOrigRoll = val; + } + void SetSpawnOrigPitch(float val){ + appearance.pos.SpawnOrigPitch = val; + } + void SetSogaModelType(int16 new_val, bool setUpdateFlags = true){ + SetInfo(&appearance.soga_model_type, new_val, setUpdateFlags); + } + void SetModelType(int16 model_type, bool setUpdateFlags = true){ + SetInfo(&appearance.model_type, model_type, setUpdateFlags); + SetInfo(&appearance.soga_model_type, model_type, setUpdateFlags); + SetFlyingCreature(); + SetWaterCreature(); + } + int16 GetSogaModelType(){ + return appearance.soga_model_type; + } + int16 GetModelType(){ + return appearance.model_type; + } + + bool IsFlyingCreature(); + bool IsWaterCreature(); + bool InWater(); + bool InLava(); + + void SetFlyingCreature(); + void SetWaterCreature(); + + void SetPrimaryCommand(const char* name, const char* command, float distance = 10); + void SetPrimaryCommands(vector* commands); + void SetSecondaryCommands(vector* commands); + vector* GetPrimaryCommands() {return &primary_command_list;} + vector* GetSecondaryCommands() {return &secondary_command_list;} + EntityCommand* FindEntityCommand(string command, bool primaryOnly=false); + virtual EQ2Packet* serialize(Player* player, int16 version); + EQ2Packet* spawn_serialize(Player* player, int16 version, int16 offset = 0, int32 value = 0, int16 offset2 = 0, int16 offset3 = 0, int16 offset4 = 0, int32 value2 = 0); + EQ2Packet* spawn_update_packet(Player* player, int16 version, bool override_changes = false, bool override_vis_changes = false); + EQ2Packet* player_position_update_packet(Player* player, int16 version, bool override_ = false); + uchar* spawn_info_changes(Player* spawn, int16 version, int16* info_packet_size); + uchar* spawn_pos_changes(Player* spawn, int16 version, int16* pos_packet_size, bool override_ = false); + uchar* spawn_vis_changes(Player* spawn, int16 version, int16* vis_packet_size); + + uchar* spawn_info_changes_ex(Player* spawn, int16 version, int16* info_packet_size); + uchar* spawn_pos_changes_ex(Player* spawn, int16 version, int16* pos_packet_size); + uchar* spawn_vis_changes_ex(Player* spawn, int16 version, int16* vis_packet_size); + + virtual bool EngagedInCombat(){ return false; } + virtual bool IsObject(){ return false; } + virtual bool IsGroundSpawn(){ return false; } + virtual bool IsNPC(){ return false; } + virtual bool IsEntity(){ return false; } + virtual bool IsPlayer(){ return false; } + virtual bool IsWidget(){ return false; } + virtual bool IsSign(){ return false; } + virtual bool IsBot() { return false; } + + bool HasInfoChanged(){ return info_changed; } + bool HasPositionChanged(){ return position_changed; } + bool HasTarget(){ return target ? true : false; } + + int32 GetRespawnTime(); + void SetRespawnTime(int32 time); + int32 GetExpireTime() { return expire_time; } + void SetExpireTime(int32 new_expire_time) { expire_time = new_expire_time; } + int32 GetExpireOffsetTime(); + void SetExpireOffsetTime(int32 time); + int32 GetSpawnLocationID(); + void SetSpawnLocationID(int32 id); + int32 GetSpawnEntryID(); + void SetSpawnEntryID(int32 id); + int32 GetSpawnLocationPlacementID(); + void SetSpawnLocationPlacementID(int32 id); + float GetXOffset() { return x_offset; } + void SetXOffset(float new_x_offset) { x_offset = new_x_offset; } + float GetYOffset() { return y_offset; } + void SetYOffset(float new_y_offset) { y_offset = new_y_offset; } + float GetZOffset() { return z_offset; } + void SetZOffset(float new_z_offset) { z_offset = new_z_offset; } + + bool HasTrapTriggered() { + return trap_triggered; + } + int32 GetTrapState() { + return trap_state; + } + void SetChestDropTime() { + chest_drop_time = Timer::GetCurrentTime2(); + trap_opened_time = 0; + } + void SetTrapTriggered(bool triggered, int32 state) { + if(!trap_triggered && triggered) + trap_opened_time = Timer::GetCurrentTime2(); + + trap_triggered = triggered; + trap_state = state; + } + int32 GetChestDropTime() { + return chest_drop_time; + } + int32 GetTrapOpenedTime() { + return trap_opened_time; + } + void AddLootItem(int32 id, int16 charges = 1) { + Item* master_item = master_item_list.GetItem(id); + if (master_item) { + Item* item = new Item(master_item); + item->details.count = charges; + LockLoot(); + loot_items.push_back(item); + UnlockLoot(); + } + } + void AddLootItem(Item* item) { + if(item) { + LockLoot(); + loot_items.push_back(item); + UnlockLoot(); + } + } + bool HasLoot() { + LockLoot(); + if (loot_items.size() == 0 && loot_coins == 0) { + UnlockLoot(); + return false; + } + UnlockLoot(); + return true; + } + void TransferLoot(Spawn* spawn); + bool HasLootItemID(int32 id); + int32 GetLootItemID(); + Item* LootItem(int32 id); + vector* GetLootItems() { + return &loot_items; + } + void LockLoot() { + MLootItems.lock(); + } + void UnlockLoot() { + MLootItems.unlock(); + } + void ClearLoot() { + + MLootItems.lock(); + vector::iterator itr; + for (itr = loot_items.begin(); itr != loot_items.end();) { + Item* itm = *itr; + itr++; + safe_delete(itm); + } + + loot_items.clear(); + + MLootItems.unlock(); + } + + int32 GetLootCount() { + int32 loot_item_count = 0; + MLootItems.lock(); + loot_item_count = loot_items.size(); + MLootItems.unlock(); + + return loot_item_count; + } + + void ClearNonBodyLoot() { + + MLootItems.lock(); + vector::iterator itr; + for (itr = loot_items.begin(); itr != loot_items.end();) { + Item* itm = *itr; + if(!itm->IsBodyDrop()) + { + itr = loot_items.erase(itr); + safe_delete(itm); + } + else + itr++; + } + MLootItems.unlock(); + } + + int32 GetLootCoins() { + LockLoot(); + int32 coins = loot_coins; + UnlockLoot(); + return coins; + } + void SetLootCoins(int32 val, bool lockloot = true) { + if(lockloot) + LockLoot(); + + loot_coins = val; + + if(lockloot) + UnlockLoot(); + } + void AddLootCoins(int32 coins) { + LockLoot(); + loot_coins += coins; + UnlockLoot(); + } + Spawn* GetTarget(); + void SetTarget(Spawn* spawn); + Spawn* GetLastAttacker(); + void SetLastAttacker(Spawn* spawn); + bool TakeDamage(int32 damage); + ZoneServer* GetZone(); + virtual void SetZone(ZoneServer* in_zone, int32 version=0); + void SetFactionID(int32 val) { faction_id = val; } + int32 GetFactionID(){ + return faction_id; + } + static int32 NextID() { + static CriticalSection id_lock; + id_lock.lock(); + int32 ret = ++next_id; + if (next_id == 0xFFFFFFFE) + next_id = 1; + else if ((next_id - 255) % 256 == 0) { //we dont want it to end in 255, it will confuse/crash the client + id_lock.unlock(); + return NextID(); + } + + id_lock.unlock(); + return ret; + } + void AddProvidedQuest(int32 val){ + quest_ids.push_back(val); + } + vector* GetProvidedQuests(){ + return &quest_ids; + } + bool HasProvidedQuests(){ + return (quest_ids.size() > 0); + } + void SetSpawnScript(string name); + const char* GetSpawnScript(); + + vector* GetSpawnGroup(); + bool HasSpawnGroup(); + bool IsInSpawnGroup(Spawn* spawn); + Spawn* IsSpawnGroupMembersAlive(Spawn* ignore_spawn=nullptr, bool npc_only = true); + void UpdateEncounterState(int8 new_state); + void CheckEncounterState(Entity* victim, bool test_auto_lock = false); + void AddTargetToEncounter(Entity* entity); + + void SendSpawnChanges(bool val){ send_spawn_changes = val; } + void SetSpawnGroupID(int32 id); + int32 GetSpawnGroupID(); + void AddSpawnToGroup(Spawn* spawn); + void SetSpawnGroupList(vector* list, Mutex* mutex); + void RemoveSpawnFromGroup(bool erase_all = false); + + void SetRunningTo(Spawn* spawn){ running_to = spawn->GetID(); } + Spawn* GetRunningTo(); + void SetTempVisualState(int val, bool update = true) { SetInfo(&tmp_visual_state, val, update); } + int GetTempVisualState(){ return tmp_visual_state; } + void SetTempActionState(int val, bool update = true) { SetInfo(&tmp_action_state, val, update); } + int GetTempActionState(){ return tmp_action_state; } + void AddAllowAccessSpawn(Spawn* spawn){ allowed_access[spawn->GetID()] = 1; } + void RemoveSpawnAccess(Spawn* spawn); + bool IsPrivateSpawn(){ return allowed_access.size() > 0 ;} + bool AllowedAccess(Spawn* spawn){ return allowed_access.count(spawn->GetID()) > 0; } + void MakeSpawnPublic() { allowed_access.clear(); } + void SetSizeOffset(int8 offset); + int8 GetSizeOffset(); + void SetMerchantID(int32 val); + int32 GetMerchantID(); + void SetMerchantType(int8 val); + int8 GetMerchantType(); + void SetCollector(bool is_it) { is_collector = is_it; } + bool IsCollector() { return is_collector; } + void SetMerchantLevelRange(int32 minLvl = 0, int32 maxLvl = 0); + bool IsClientInMerchantLevelRange(Client* ent, bool sendMessageIfDenied = true); + int32 GetMerchantMinLevel(); + int32 GetMerchantMaxLevel(); + void SetQuestsRequired(Spawn* new_spawn); + void SetQuestsRequired(int32 quest_id, int16 quest_step); + bool HasQuestsRequired(); + bool HasHistoryRequired(); + void SetRequiredHistory(int32 event_id, int32 value1, int32 value2); + void SetTransporterID(int32 id); + int32 GetTransporterID(); + bool MeetsSpawnAccessRequirements(Player* player); + + void RemovePrimaryCommands(); + + void InitializePosPacketData(Player* player, PacketStruct* packet, bool bSpawnUpdate = false); + void InitializeInfoPacketData(Player* player, PacketStruct* packet); + void InitializeVisPacketData(Player* player, PacketStruct* packet); + void InitializeHeaderPacketData(Player* player, PacketStruct* packet, int16 index); + void InitializeFooterPacketData(Player* player, PacketStruct* packet); + + void MoveToLocation(Spawn* spawn, float distance, bool immediate = true, bool isMappedLocation = false); + void AddMovementLocation(float x, float y, float z, float speed, int16 delay, const char* lua_function, float heading, bool include_heading = false); + void ProcessMovement(bool isSpawnListLocked=false); + void ResetMovement(); + bool ValidateRunning(bool lockMovementLocation, bool lockMovementLoop); + bool IsRunning(); + void CalculateRunningLocation(bool stop = false); + void RunToLocation(float x, float y, float z, float following_x = 0, float following_y = 0, float following_z = 0); + + MovementLocation* GetCurrentRunningLocation(); + MovementLocation* GetLastRunningLocation(); + void NewWaypointChange(MovementLocation* data); + bool CalculateChange(); + void AddRunningLocation(float x, float y, float z, float speed, float distance_away = 0, bool attackable = true, bool finished_adding_locations = true, string lua_function = "", bool isMapped=false); + bool RemoveRunningLocation(); + void ClearRunningLocations(); + + void CopySpawnAppearance(Spawn* spawn); + + bool MovementInterrupted(){ return movement_interrupted; } + void MovementInterrupted(bool val) { movement_interrupted = val; } + bool NeedsToResumeMovement(){ return attack_resume_needed; } + void NeedsToResumeMovement(bool val) { attack_resume_needed = val; } + bool HasMovementLoop(){ return movement_loop.size() > 0; } + bool HasMovementLocations() { + bool hasLocations = false; + MMovementLocations.lock_shared(); + hasLocations = movement_locations ? movement_locations->size() > 0 : false; + MMovementLocations.unlock_shared(); + return hasLocations; + } + + Timer* GetRunningTimer(); + float GetFaceTarget(float x, float z); + void FaceTarget(float x, float z); + void FaceTarget(Spawn* target, bool disable_action_state = true); + void SetInvulnerable(bool val); + bool GetInvulnerable(); + void SetScaredByStrongPlayers(bool val) { scared_by_strong_players = val; } + bool IsScaredByStrongPlayers() { return scared_by_strong_players; } + std::atomic changed; + std::atomic position_changed; + std::atomic info_changed; + std::atomic vis_changed; + std::atomic is_running; + int16 size; + int32 faction_id; + int8 oversized_packet; //0xff + int32 id; + int8 unknown1; + int32 unknown2; + int32 primary_command_list_id; + int32 secondary_command_list_id; + vector primary_command_list; + vector secondary_command_list; + int32 group_id; + int8 group_len; + vector* spawn_group_list; + AppearanceData appearance; + int32 last_movement_update; + int32 last_location_update; + bool forceMapCheck; + bool is_water_creature; + bool is_flying_creature; + int32 trigger_widget_id; + + std::atomic following; + bool IsPet() { return is_pet; } + void SetPet(bool val) { is_pet = val; } + Mutex m_requiredQuests; + Mutex m_requiredHistory; + + void SetFollowTarget(Spawn* spawn, int32 followDistance=0); + Spawn* GetFollowTarget(); + + /// Sets a user defined variable + /// Variable we are setting + /// Value to set the variable to + void AddTempVariable(string var, string val); + + void AddTempVariable(string var, Spawn* val); + void AddTempVariable(string var, ZoneServer* val); + void AddTempVariable(string var, Quest* val); + void AddTempVariable(string var, Item* val); + /// Gets the value for the given variable + /// Variable to check + /// The value for the given variable, "" if variable was not set + string GetTempVariable(string var); + + Spawn* GetTempVariableSpawn(string var); + ZoneServer* GetTempVariableZone(string var); + Item* GetTempVariableItem(string var); + Quest* GetTempVariableQuest(string var); + int8 GetTempVariableType(string var); + void DeleteTempVariable(string var); + + void SetIllusionModel(int16 val, bool setUpdateFlags = true) { + SetInfo(&m_illusionModel, val, setUpdateFlags); + } + int16 GetIllusionModel() { return m_illusionModel; } + + CellInfo Cell_Info; + + + int32 GetSpawnAnim() { return m_spawnAnim; } + void SetSpawnAnim(int32 value) { m_spawnAnim = value; } + int32 GetAddedToWorldTimestamp() { return m_addedToWorldTimestamp; } + void SetAddedToWorldTimestamp(int32 value) { m_addedToWorldTimestamp = value; } + int16 GetSpawnAnimLeeway() { return m_spawnAnimLeeway; } + void SetSpawnAnimLeeway(int16 value) { m_spawnAnimLeeway = value; } + + float FindDestGroundZ(glm::vec3 dest, float z_offset); + float FindBestZ(glm::vec3 loc, glm::vec3* result=nullptr, int32* new_grid_id=nullptr, int32* new_widget_id=nullptr); + float GetFixedZ(const glm::vec3& destination, int32 z_find_offset = 1); + void FixZ(bool forceUpdate=false); + bool CheckLoS(Spawn* target); + bool CheckLoS(glm::vec3 myloc, glm::vec3 oloc); + + void CalculateNewFearpoint(); + + enum SpawnProximityType { + SPAWNPROXIMITY_DATABASE_ID = 0, + SPAWNPROXIMITY_LOCATION_ID = 1 + }; + + struct SpawnProximity { + float x; + float y; + float z; + int32 spawn_value; + int8 spawn_type; + float distance; + string in_range_lua_function; + string leaving_range_lua_function; + map spawns_in_proximity; + }; + + void AddSpawnToProximity(int32 spawnValue, SpawnProximityType type); + void RemoveSpawnFromProximity(int32 spawnValue, SpawnProximityType type); + + SpawnProximity* AddLUASpawnProximity(int32 spawnValue, SpawnProximityType type, float distance, string in_range_function, string leaving_range_function) { + SpawnProximity* prox = new SpawnProximity; + prox->spawn_value = spawnValue; + prox->spawn_type = type; + prox->distance = distance; + prox->in_range_lua_function = in_range_function; + prox->leaving_range_lua_function = leaving_range_function; + spawn_proximities.Add(prox); + has_spawn_proximities = true; + return prox; + } + + void RemoveSpawnProximities() { + MutexList::iterator itr = spawn_proximities.begin(); + while (itr.Next()) { + safe_delete(itr->value); + } + spawn_proximities.clear(); + has_spawn_proximities = false; + } + + Mutex MCommandMutex; + bool has_spawn_proximities; + + void SetPickupItemID(int32 itemid) + { + pickup_item_id = itemid; + } + + void SetPickupUniqueItemID(int32 uniqueid) + { + pickup_unique_item_id = uniqueid; + } + + int32 GetPickupItemID() { return pickup_item_id; } + int32 GetPickupUniqueItemID() { return pickup_unique_item_id; } + + bool IsSoundsDisabled() { return disable_sounds; } + void SetSoundsDisabled(bool val) { disable_sounds = val; } + + RegionMap* GetRegionMap() { return region_map; } + Map* GetMap() { return current_map; } + std::map established_grid_id; + + void DeleteRegion(Region_Node* inNode, ZBSP_Node* rootNode); + bool InRegion(Region_Node* inNode, ZBSP_Node* rootNode); + int32 GetRegionType(Region_Node* inNode, ZBSP_Node* rootNode); + + float SpawnAngle(Spawn* target, float selfx, float selfz); + bool BehindSpawn(Spawn *target, float selfx, float selfz) + { return (!target || target == this) ? false : SpawnAngle(target, selfx, selfz) > 90.0f; } + bool InFrontSpawn(Spawn *target, float selfx, float selfz) + { return (!target || target == this) ? false : SpawnAngle(target, selfx, selfz) < 63.0f; } + bool IsFlankingSpawn(Spawn *target, float selfx, float selfz) + { return (!target || target == this) ? false : SpawnAngle(target, selfx, selfz) > 63.0f; } + + std::map, Region_Status> Regions; + Mutex RegionMutex; + + virtual void StopMovement(); + virtual bool PauseMovement(int32 period_of_time_ms); + virtual bool IsPauseMovementTimerActive(); + bool IsTransportSpawn() { return is_transport_spawn; } + void SetTransportSpawn(bool val) { is_transport_spawn = val; } + + sint64 GetRailID() { return rail_id; } + void SetRailID(sint64 val) { rail_id = val; } + void AddRailPassenger(int32 char_id); + void RemoveRailPassenger(int32 char_id); + vector GetPassengersOnRail(); + + void SetAppearancePosition(float x, float y, float z); + + void SetOmittedByDBFlag(bool val) { is_omitted_by_db_flag = val; } + bool IsOmittedByDBFlag() { return is_omitted_by_db_flag; } + + int32 GetLootTier() { return loot_tier; } + void SetLootTier(int32 tier) { loot_tier = tier; } + + int32 GetLootDropType() { return loot_drop_type; } + void SetLootDropType(int32 type) { loot_drop_type = type; } + + void SetDeletedSpawn(bool val) { deleted_spawn = val; } + bool IsDeletedSpawn() { return deleted_spawn; } + + + int32 InsertRegionToSpawn(Region_Node* node, ZBSP_Node* bsp_root, WaterRegionType regionType, bool in_region = true); + bool HasRegionTracked(Region_Node* node, ZBSP_Node* bsp_root, bool in_region); + + int8 GetArrowColor(int8 spawn_level); + + void AddIgnoredWidget(int32 id); + + void SendGroupUpdate(); + + void OverrideLootMethod(GroupLootMethod newMethod) { loot_method = newMethod; } + void SetLootMethod(GroupLootMethod method, int8 item_rarity = 0, int32 group_id = 0); + int32 GetLootGroupID() { return loot_group_id; } + GroupLootMethod GetLootMethod() { return loot_method; } + int8 GetLootRarity() { return loot_rarity; } + int32 GetLootTimeRemaining() { return loot_timer.GetRemainingTime(); } + bool IsLootTimerRunning() { return loot_timer.Enabled(); } + bool CheckLootTimer() { return loot_timer.Check(); } + void DisableLootTimer() { return loot_timer.Disable(); } + int32 GetLooterSpawnID() { return looter_spawn_id; } + void SetLooterSpawnID(int32 id) { looter_spawn_id = id; } + bool AddNeedGreedItemRequest(int32 item_id, int32 spawn_id, bool need_item); + bool AddLottoItemRequest(int32 item_id, int32 spawn_id); + void AddSpawnLootWindowCompleted(int32 spawn_id, bool status_); + bool SetSpawnLootWindowCompleted(int32 spawn_id); + bool HasSpawnLootWindowCompleted(int32 spawn_id); + bool HasSpawnNeedGreedEntry(int32 item_id, int32 spawn_id); + bool HasSpawnLottoEntry(int32 item_id, int32 spawn_id); + void GetSpawnLottoEntries(int32 item_id, std::map* out_entries); + void GetLootItemsList(std::vector* out_entries); + void GetSpawnNeedGreedEntries(int32 item_id, bool need_item, std::map* out_entries); + + bool HasLootWindowCompleted(); + bool IsLootWindowComplete() { return is_loot_complete; } + void SetLootDispensed() { is_loot_dispensed = true; } + bool IsLootDispensed() { return is_loot_dispensed; } + std::map* GetLootWindowList() { return &loot_complete; } + void StartLootTimer(Spawn* looter); + void CloseLoot(Spawn* sender); + + void SetLootName(char* name) { + if(name != nullptr) { + loot_name = std::string(name); + } + } + + const char* GetLootName() { return loot_name.c_str(); } + + bool IsItemInLootTier(Item* item); + void DistributeGroupLoot_RoundRobin(std::vector* item_list, bool roundRobinTrashLoot = false); // trash loot is what falls under the item tier requirement by group options + + void CalculateInitialVelocity(float heading, float distanceHorizontal, float distanceVertical, float distanceDepth, float duration); + glm::vec3 CalculateProjectilePosition(glm::vec3 initialVelocity, float time); + bool CalculateSpawnProjectilePosition(float x, float y, float z); + void SetKnockback(Spawn* target, int32 duration, float vertical, float horizontal); + void ResetKnockedBack(); + + mutable std::shared_mutex MIgnoredWidgets; + std::map ignored_widgets; + + EquipmentItemList equipment_list; + EquipmentItemList appearance_equipment_list; + +protected: + + bool has_quests_required; + bool has_history_required; + bool send_spawn_changes; + bool invulnerable; + bool attack_resume_needed; + bool resume_movement; + bool movement_interrupted; + int32 running_timer_begin; + int32 running_timer_end; + vector movement_loop; + bool running_timer_updated; + int16 movement_index; + int32 last_grid_update; + int32 movement_start_time; + Mutex MMovementLoop; + map allowed_access; + vector quest_ids; + int32 database_id; + int32 packet_num; + int32 target; + int8 spawn_type; + int32 last_attacker; + int32 merchant_id; + int8 merchant_type; + int32 merchant_min_level; + int32 merchant_max_level; + + int32 transporter_id; + int32 pickup_item_id; + int32 pickup_unique_item_id; + map* > required_quests; + map required_history; + + MutexList spawn_proximities; + + void CheckProximities(); + Timer pause_timer; + + bool IsKnockedBack() { return knocked_back; } +private: + int32 loot_group_id; + GroupLootMethod loot_method; + int8 loot_rarity; + Timer loot_timer; + int32 looter_spawn_id; + vector loot_items; + int32 loot_coins; + std::multimap lotto_items; + std::multimap> need_greed_items; + + std::map loot_complete; + bool is_loot_complete; + bool is_loot_dispensed; + std::string loot_name; + + bool trap_triggered; + int32 trap_state; + int32 chest_drop_time; + int32 trap_opened_time; + deque* movement_locations; + Mutex MLootItems; + mutable std::shared_mutex MMovementLocations; + Mutex* MSpawnGroup; + int8 size_offset; + int tmp_visual_state; + int tmp_action_state; + int32 running_to; + string spawn_script; + static int32 next_id; + ZoneServer* zone; + int32 spawn_location_id; + int32 spawn_entry_id; + int32 spawn_location_spawns_id; + int32 respawn; + int32 expire_time; + int32 expire_offset; + float x_offset; + float y_offset; + float z_offset; + int32 deviation; + BasicInfoStruct basic_info; + //string data; + bool is_pet; + // m_followTarget = spawn to follow around + int32 m_followTarget; + int32 m_followDistance; + bool req_quests_private; + int16 req_quests_override; + bool req_quests_continued_access; + float last_heading_angle; + + map m_tempVariableTypes; + map m_tempVariableSpawn; + map m_tempVariableZone; + map m_tempVariableItem; + map m_tempVariableQuest; + // m_tempVariables = stores user defined variables from lua, will not persist through a zone + map m_tempVariables; + + int16 m_illusionModel; + + int32 m_spawnAnim; + int32 m_addedToWorldTimestamp; + int16 m_spawnAnimLeeway; + + Mutex m_Update; + Mutex m_SpawnMutex; + bool disable_sounds; + + RegionMap* region_map; + Map* current_map; + + bool is_transport_spawn; + sint64 rail_id; + map rail_passengers; + mutex m_RailMutex; + bool is_omitted_by_db_flag; // this particular spawn is omitted by an expansion or holiday flag + + int32 loot_tier; + int32 loot_drop_type; + + std::atomic deleted_spawn; + std::atomic reset_movement; + Mutex m_GridMutex; + bool is_collector; + bool scared_by_strong_players; + std::atomic is_alive; + std::atomic knocked_back; + float knocked_back_time_step; + float knocked_back_h_distance; + float knocked_back_v_distance; + float knocked_back_duration; + float knocked_back_start_x; + float knocked_back_start_y; + float knocked_back_start_z; + int32 knocked_back_end_time; + float knocked_angle; + glm::vec3 knocked_velocity; + +}; + +#endif + diff --git a/source/WorldServer/SpawnLists.h b/source/WorldServer/SpawnLists.h new file mode 100644 index 0000000..2e134dd --- /dev/null +++ b/source/WorldServer/SpawnLists.h @@ -0,0 +1,108 @@ +/* + 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 . +*/ +#ifndef EQ2_SPAWN_LISTS +#define EQ2_SPAWN_LISTS +#include "../common/types.h" +#include +#include + +#define SPAWN_ENTRY_TYPE_NPC 0 +#define SPAWN_ENTRY_TYPE_OBJECT 1 +#define SPAWN_ENTRY_TYPE_WIDGET 2 +#define SPAWN_ENTRY_TYPE_SIGN 3 +#define SPAWN_ENTRY_TYPE_GROUNDSPAWN 4 + +struct EntityCommand{ + string name; + float distance; + string error_text; + string command; + int16 cast_time; + int32 spell_visual; + map allow_or_deny; // this is a map of player IDs and whether they are allowed on the command or denied + bool default_allow_list; // if set to false then its a defaultDenyList +}; +struct SpawnEntry{ + int32 spawn_entry_id; + int32 spawn_location_id; + int8 spawn_type; + int32 spawn_id; + float spawn_percentage; + int32 respawn; + int32 expire_time; + int32 expire_offset; + //devn00b: added spawn location overrides, added these to accomodate. + int32 lvl_override; + int32 hp_override; + int32 mp_override; + int32 str_override; + int32 sta_override; + int32 wis_override; + int32 int_override; + int32 agi_override; + int32 heat_override; + int32 cold_override; + int32 magic_override; + int32 mental_override; + int32 divine_override; + int32 disease_override; + int32 poison_override; + int32 difficulty_override; //aka EncounterLevel +}; +class SpawnLocation{ +public: + SpawnLocation(){ + x = 0; + y = 0; + z = 0; + heading = 0; + total_percentage = 0; + x_offset = 0; + y_offset = 0; + z_offset = 0; + placement_id = 0; + pitch = 0; + roll = 0; + grid_id = 0; + conditional = 0; + } + ~SpawnLocation(){ + for(int32 i=0;i entities; + float x; + float y; + float z; + float heading; + float x_offset; + float y_offset; + float z_offset; + int32 placement_id; + float pitch; + float roll; + float total_percentage; + int32 grid_id; + string script; + int8 conditional; +}; +#endif + diff --git a/source/WorldServer/SpellProcess.cpp b/source/WorldServer/SpellProcess.cpp new file mode 100644 index 0000000..3ee9ca0 --- /dev/null +++ b/source/WorldServer/SpellProcess.cpp @@ -0,0 +1,3039 @@ +/* + 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 . +*/ +#include "SpellProcess.h" +#include "../common/Log.h" +#include "Tradeskills/Tradeskills.h" +#include "ClientPacketFunctions.h" +#include "Rules/Rules.h" + +extern MasterSpellList master_spell_list; +extern MasterSkillList master_skill_list; +extern ConfigReader configReader; +extern LuaInterface* lua_interface; +extern Commands commands; +extern World world; +extern RuleManager rule_manager; + +SpellProcess::SpellProcess(){ + last_checked_time = 0; + MRemoveTargetList.SetName("SpellProcess::MRemoveTargetList"); + MSoloHO.SetName("SpellProcess::m_soloHO"); + MGroupHO.SetName("SpellProcess:m_groupHO"); + MSpellCancelList.SetName("SpellProcess::SpellCancelList"); +} + +SpellProcess::~SpellProcess(){ + RemoveAllSpells(); +} + +void SpellProcess::RemoveCaster(Spawn* caster){ + MutexList::iterator active_spells_itr = active_spells.begin(); + while(active_spells_itr.Next()){ + LuaSpell* spell = active_spells_itr->value; + spell->MSpellTargets.writelock(__FUNCTION__, __LINE__); + if(spell->caster == caster) { + spell->caster = nullptr; + } + spell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__); + } +} + +void SpellProcess::RemoveAllSpells(bool reload_spells){ + ClearSpellScriptTimerList(); + + MutexList::iterator active_spells_itr = active_spells.begin(); + while(active_spells_itr.Next()){ + DeleteCasterSpell(active_spells_itr->value, "", true, 0, !reload_spells); + } + + MSpellProcess.lock(); + + active_spells_itr = active_spells.begin(); + while(active_spells_itr.Next()){ + active_spells.Remove(active_spells_itr->value, true); + } + active_spells.clear(); + + InterruptStruct* interrupt = 0; + MutexList::iterator interrupt_list_itr = interrupt_list.begin(); + while(interrupt_list_itr.Next()){ + interrupt = interrupt_list_itr->value; + CheckInterrupt(interrupt); + interrupt_list.Remove(interrupt_list_itr->value, true); + } + interrupt_list.clear(); + + MutexList::iterator cast_timers_itr = cast_timers.begin(); + while(cast_timers_itr.Next()){ + safe_delete(cast_timers_itr->value->timer); + safe_delete(cast_timers_itr->value->spell); + cast_timers.Remove(cast_timers_itr->value, true); + } + cast_timers.clear(); + + MutexList::iterator recast_timers_itr = recast_timers.begin(); + while(recast_timers_itr.Next()){ + recast_timers.Remove(recast_timers_itr->value, true); + } + + map*>::iterator remove_itr; + MRemoveTargetList.writelock(__FUNCTION__, __LINE__); + for (remove_itr = remove_target_list.begin(); remove_itr != remove_target_list.end(); remove_itr++){ + safe_delete(remove_itr->second); + } + remove_target_list.clear(); + MRemoveTargetList.releasewritelock(__FUNCTION__, __LINE__); + + map::iterator solo_ho_itr; + MSoloHO.writelock(__FUNCTION__, __LINE__); + for (solo_ho_itr = m_soloHO.begin(); solo_ho_itr != m_soloHO.end(); solo_ho_itr++) + safe_delete(solo_ho_itr->second); + m_soloHO.clear(); + MSoloHO.releasewritelock(__FUNCTION__, __LINE__); + + map::iterator group_ho_itr; + MGroupHO.writelock(__FUNCTION__, __LINE__); + for (group_ho_itr = m_groupHO.begin(); group_ho_itr != m_groupHO.end(); group_ho_itr++) + safe_delete(group_ho_itr->second); + m_groupHO.clear(); + MGroupHO.releasewritelock(__FUNCTION__, __LINE__); + + recast_timers.clear(); + spell_que.clear(); + + MSpellCancelList.writelock(__FUNCTION__, __LINE__); + SpellCancelList.clear(); + MSpellCancelList.releasewritelock(__FUNCTION__, __LINE__); + + MSpellProcess.unlock(); +} + +void SpellProcess::Process(){ + if(last_checked_time > Timer::GetCurrentTime2()) + return; + last_checked_time = Timer::GetCurrentTime2() + 50; + MSpellProcess.lock_shared(); + CheckSpellScriptTimers(); + if(active_spells.size(true) > 0){ + LuaSpell* spell = 0; + MutexList::iterator itr = active_spells.begin(); + map >::iterator remove_itr; + vector::iterator target_itr; + vector::iterator remove_target_itr; + while(itr.Next()){ + spell = itr->value; + if (!spell->spell->GetSpellData()->duration_until_cancel && spell->timer.Check()){ + spell->num_calls++; + // ProcessSpell(spell, flase) will atempt to call the tick() function in the lua script + // if there is no tick function it will return false, this will cause the server to crash in the event + // of a spell that has a duration but is not a "until canceled" spell or a spell with a tick (tradeskill buffs) + // to counter this check to see if the spell has a call_frequency > 0 before we call ProcessSpell() + if (spell->spell->GetSpellData()->call_frequency > 0 && !ProcessSpell(spell, false)) + active_spells.Remove(spell, true, 2000); + else if (((spell->timer.GetDuration() * spell->num_calls) >= spell->spell->GetSpellData()->duration1 * 100) || + (spell->restored && (spell->timer.GetSetAtTrigger() * spell->num_calls) >= spell->spell->GetSpellData()->duration1 * 100)) + DeleteCasterSpell(spell, "expired"); + } + else + CheckRemoveTargetFromSpell(spell); + } + } + if (SpellCancelList.size() > 0){ + std::vector tmpList; + MSpellCancelList.writelock(__FUNCTION__, __LINE__); + vector::iterator itr = SpellCancelList.begin(); + while (itr != SpellCancelList.end()){ + tmpList.push_back(*itr); + itr++; + } + SpellCancelList.clear(); + MSpellCancelList.releasewritelock(__FUNCTION__, __LINE__); + + itr = tmpList.begin(); + while (itr != tmpList.end()) { + DeleteCasterSpell(*itr, "canceled"); + itr++; + } + } + if(interrupt_list.size(true) > 0){ + InterruptStruct* interrupt = 0; + MutexList::iterator itr = interrupt_list.begin(); + while(itr.Next()){ + interrupt = itr->value; + CheckInterrupt(interrupt); + safe_delete(interrupt); + } + interrupt_list.clear(); + } + if(cast_timers.size(true) > 0){ + CastTimer* cast_timer = 0; + MutexList::iterator itr = cast_timers.begin(); + while (itr.Next()) { + cast_timer = itr->value; + if (cast_timer) { + if (cast_timer->timer->Check(false)) { + if (cast_timer->spell) { + if (cast_timer->spell->caster && cast_timer->spell->caster->IsPlayer()) { + + Client* client = ((Player*)cast_timer->spell->caster)->GetClient(); + if(client) { + PacketStruct* packet = configReader.getStruct("WS_FinishCastSpell", client->GetVersion()); + if (packet) { + packet->setMediumStringByName("spell_name", cast_timer->spell->spell->GetSpellData()->name.data.c_str()); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + } + if (cast_timer->spell && cast_timer->spell->caster) + cast_timer->spell->caster->IsCasting(false); + cast_timer->delete_timer = true; + CastProcessedSpell(cast_timer->spell, false, cast_timer->in_heroic_opp); + } + else if (cast_timer->entity_command) { + if (cast_timer->timer->Check(false)) { + cast_timer->delete_timer = true; + Spawn* target = cast_timer->zone->GetSpawnByID(cast_timer->target_id); + CastProcessedEntityCommand(cast_timer->entity_command, cast_timer->caster, target, cast_timer->in_heroic_opp); + } + } + } + if (cast_timer->delete_timer) { + safe_delete(cast_timer->timer); + cast_timers.Remove(cast_timer, true); + } + } + } + } + if (recast_timers.size(true) > 0) { + RecastTimer* recast_timer = 0; + MutexList::iterator itr = recast_timers.begin(); + vector::iterator remove_recast_itr; + while(itr.Next()){ + recast_timer = itr->value; + if(recast_timer->timer->Check(false)){ + MaintainedEffects* effect = 0; + if(recast_timer->caster && (!(effect = recast_timer->caster->GetMaintainedSpell(recast_timer->spell_id)) || !effect->spell->spell->GetSpellData()->duration_until_cancel)) + UnlockSpell(recast_timer->client, recast_timer->spell); + safe_delete(recast_timer->timer); + recast_timers.Remove(recast_timer, true); + } + } + } + if(spell_que.size(true) > 0){ + MutexMap::iterator itr = spell_que.begin(); + while(itr.Next()){ + if(itr->first->IsCasting() == false && IsReady(itr->second, itr->first)){ + RemoveSpellFromQueue(itr->second, itr->first); + ProcessSpell(itr->first->GetZone(), itr->second, itr->first, itr->first->GetTarget()); + } + } + } + + // Check solo HO timers + MSoloHO.writelock(__FUNCTION__, __LINE__); + if (m_soloHO.size() > 0) { + map::iterator itr = m_soloHO.begin(); + while (itr != m_soloHO.end()) { + if (itr->second->GetWheel() && Timer::GetCurrentTime2() >= (itr->second->GetStartTime() + (itr->second->GetTotalTime() * 1000))) { + itr->second->SetComplete(1); + ClientPacketFunctions::SendHeroicOPUpdate(itr->first, itr->second); + safe_delete(itr->second); + itr = m_soloHO.erase(itr); + continue; + } + else + itr++; + } + } + MSoloHO.releasewritelock(__FUNCTION__, __LINE__); + + // Check group HO timers + MGroupHO.writelock(__FUNCTION__, __LINE__); + if (m_groupHO.size() > 0) { + map::iterator itr = m_groupHO.begin(); + while (itr != m_groupHO.end()) { + if (itr->second->GetWheel() && Timer::GetCurrentTime2() >= (itr->second->GetStartTime() + (itr->second->GetTotalTime() * 1000))) { + itr->second->SetComplete(1); + + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + deque::iterator itr2; + PlayerGroup* group = world.GetGroupManager()->GetGroup(itr->first); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + for (itr2 = members->begin(); itr2 != members->end(); itr2++) { + if ((*itr2)->client) + ClientPacketFunctions::SendHeroicOPUpdate((*itr2)->client, itr->second); + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + + safe_delete(itr->second); + itr = m_groupHO.erase(itr); + continue; + } + else + itr++; + } + } + MGroupHO.releasewritelock(__FUNCTION__, __LINE__); + + MSpellProcess.unlock_shared(); +} +bool SpellProcess::IsReady(Spell* spell, Entity* caster){ + if(caster->IsCasting()) { + return false; + } + bool ret = true; + RecastTimer* recast_timer = 0; + MutexList::iterator itr = recast_timers.begin(); + while(itr.Next()){ + recast_timer = itr->value; + if((recast_timer->spell == spell || recast_timer->spell_id == spell->GetSpellID()) && + recast_timer->caster == caster){ + ret = false; + break; + } + } + return ret; +} +void SpellProcess::CheckRecast(Spell* spell, Entity* caster, float timer_override, bool check_linked_timers) { + if(spell && caster && spell->GetSpellData()->recast > 0.0f){ + RecastTimer* timer = new RecastTimer; + timer->caster = caster; + if(caster->IsPlayer()) + timer->client = ((Player*)caster)->GetClient(); + else + timer->client = 0; + timer->spell = spell; + int32 recast_time = static_cast(spell->GetSpellData()->recast * 1000.0f); + + if(timer_override == 0.0f) { + recast_time = spell->CalculateRecastTimer(caster); + timer->timer = new Timer(recast_time); + } + else { + recast_time = spell->CalculateRecastTimer(caster, timer_override); + timer->timer = new Timer(recast_time); + } + + if(recast_time < 1) { + safe_delete(timer->timer); + safe_delete(timer); + } + else { + timer->type_group_spell_id = spell->GetSpellData()->type_group_spell_id; + timer->linked_timer = spell->GetSpellData()->linked_timer; + timer->spell_id = spell->GetSpellID(); + + recast_timers.Add(timer); + } + if(caster->IsPlayer()){ + if(recast_time < 1) { + ((Player*)caster)->UnlockSpell(spell); + } + else { + ((Player*)caster)->LockSpell(spell, (int16)(recast_time)); + } + if (check_linked_timers && spell->GetSpellData()->linked_timer > 0) { + vector linkedSpells = ((Player*)caster)->GetSpellBookSpellsByTimer(spell, spell->GetSpellData()->linked_timer); + for (int8 i = 0; i < linkedSpells.size(); i++) { + Spell* spell2 = linkedSpells.at(i); + if (spell2) + CheckRecast(spell2, caster, static_cast(recast_time), false); + } + } + } + } +} +void SpellProcess::CheckInterrupt(InterruptStruct* interrupt){ + if(!interrupt || !interrupt->interrupted || !interrupt->interrupted->IsEntity()) + return; + Entity* entity = (Entity*)interrupt->interrupted; + Client* client = nullptr; + + if(entity->IsPlayer()) { + client = ((Player*)entity)->GetClient(); + } + + if(entity->IsPlayer() && client) + SendFinishedCast(GetLuaSpell(entity), client); + RemoveSpellTimersFromSpawn(entity, false); + entity->IsCasting(false); + entity->GetZone()->SendInterruptPacket(entity, interrupt->spell); + if(interrupt->error_code > 0 && client) + entity->GetZone()->SendSpellFailedPacket(client, interrupt->error_code); + if(entity->IsPlayer()) + { + ((Player*)entity)->UnlockSpell(interrupt->spell->spell); + if(client) { + SendSpellBookUpdate(client); + } + } +} + +bool SpellProcess::DeleteCasterSpell(Spawn* caster, Spell* spell, string reason){ + + bool ret = false; + // need to use size(true) to get pending updates to the list as well + if (caster && spell && active_spells.size() > 0) { + LuaSpell* lua_spell = 0; + MutexList::iterator itr = active_spells.begin(); + while (itr.Next()){ + lua_spell = itr->value; + if (lua_spell->spell == spell && lua_spell->caster == caster) { + ret = DeleteCasterSpell(lua_spell, reason); + break; + } + } + } + return ret; +} + +bool SpellProcess::DeleteCasterSpell(LuaSpell* spell, string reason, bool removing_all_spells, Spawn* remove_target, bool zone_shutting_down, bool shared_lock_spell){ + if(shared_lock_spell && !removing_all_spells) { + MSpellProcess.lock_shared(); + } + + bool ret = false; + Spawn* target = 0; + bool target_valid = false; + if(spell) { + spell->MSpellTargets.writelock(__FUNCTION__, __LINE__); + if(remove_target && spell->targets.size() > 1) { + for (int32 i = 0; i < spell->targets.size(); i++) { + if(remove_target->GetID() == spell->targets.at(i)) { + if(remove_target->IsEntity()){ + spell->removed_targets.push_back(remove_target->GetID()); + ((Entity*)remove_target)->RemoveProc(0, spell); + ((Entity*)remove_target)->RemoveSpellEffect(spell); + ((Entity*)remove_target)->RemoveSpellBonus(spell); + if(spell->spell->GetSpellData()->det_type > 0 && (spell->spell->GetSpellDuration() > 0 || spell->spell->GetSpellData()->duration_until_cancel)) + ((Entity*)remove_target)->RemoveDetrimentalSpell(spell); + } + spell->targets.erase(spell->targets.begin()+i, spell->targets.begin()+i+1); + target_valid = true; + break; + } + } + spell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__); + if(shared_lock_spell && !removing_all_spells) { + MSpellProcess.unlock_shared(); + } + return target_valid; + } + spell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__); + + if (active_spells.count(spell) > 0) + active_spells.Remove(spell); + if (!zone_shutting_down && spell->caster) { // spell->caster ptr cannot be trusted during zone shutdown + if(spell->caster->GetThreatTransfer() && spell->caster->GetThreatTransfer()->Spell == spell) { + spell->caster->SetThreatTransfer(nullptr); + } + if (spell->spell->GetSpellData()->cast_type == SPELL_CAST_TYPE_TOGGLE){ + + int8 actual_concentration = spell->spell->GetSpellData()->req_concentration; + + if (actual_concentration > 0) + { + int8 curConcentration = spell->caster->GetInfoStruct()->get_cur_concentration(); + if(curConcentration >= actual_concentration) + { + spell->caster->GetInfoStruct()->set_cur_concentration(curConcentration - actual_concentration); + if (spell->caster->IsPlayer()&& spell->caster->GetZone()) + spell->caster->GetZone()->TriggerCharSheetTimer(); + } + } + if(!spell->spell->GetSpellData()->duration_until_cancel) + { + CheckRecast(spell->spell, spell->caster); + if (spell->caster && spell->caster->IsPlayer()) + SendSpellBookUpdate(((Player*)spell->caster)->GetClient()); + } + } + if(IsReady(spell->spell, spell->caster) && spell->caster->IsPlayer()) { + ((Player*)spell->caster)->UnlockSpell(spell->spell); + SendSpellBookUpdate(((Player*)spell->caster)->GetClient()); + } + + spell->caster->RemoveProc(0, spell); + spell->caster->RemoveMaintainedSpell(spell); + CheckRemoveTargetFromSpell(spell, removing_all_spells, removing_all_spells); + ZoneServer* zone = spell->caster->GetZone(); + if(zone) { + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < spell->targets.size(); i++) { + target = zone->GetSpawnByID(spell->targets.at(i)); + if(target && target->IsEntity()){ + spell->removed_targets.push_back(target->GetID()); + ((Entity*)target)->RemoveProc(0, spell); + ((Entity*)target)->RemoveSpellEffect(spell); + ((Entity*)target)->RemoveSpellBonus(spell); + if(spell->spell->GetSpellData()->det_type > 0 && (spell->spell->GetSpellDuration() > 0 || spell->spell->GetSpellData()->duration_until_cancel)) + ((Entity*)target)->RemoveDetrimentalSpell(spell); + } + else{ + spell->caster->RemoveSpellEffect(spell); + spell->caster->RemoveSpellBonus(spell); + if(spell->spell->GetSpellData()->det_type > 0 && (spell->spell->GetSpellDuration() > 0 || spell->spell->GetSpellData()->duration_until_cancel)) + spell->caster->RemoveDetrimentalSpell(spell); + } + if(target && target->IsPlayer() && spell->spell->GetSpellData()->fade_message.length() > 0){ + Client* client = ((Player*)target)->GetClient(); + if(client){ + bool send_to_sender = true; + string fade_message = spell->spell->GetSpellData()->fade_message; + if(fade_message.find("%t") != string::npos) + fade_message.replace(fade_message.find("%t"), 2, target->GetName()); + client->Message(CHANNEL_SPELLS_OTHER, fade_message.c_str()); + } + } + if (target && target->IsPlayer() && spell->spell->GetSpellData()->fade_message.length() > 0) { + Client* client = ((Player*)target)->GetClient(); + if (client) { + bool send_to_sender = true; + string fade_message_others = spell->spell->GetSpellData()->fade_message_others; + if (fade_message_others.find("%t") != string::npos) + fade_message_others.replace(fade_message_others.find("%t"), 2, target->GetName()); + if (fade_message_others.find("%c") != string::npos) + fade_message_others.replace(fade_message_others.find("%c"), 2, spell->caster->GetName()); + if (fade_message_others.find("%T") != string::npos) { + fade_message_others.replace(fade_message_others.find("%T"), 2, target->GetName()); + send_to_sender = false; + } + if (fade_message_others.find("%C") != string::npos) { + fade_message_others.replace(fade_message_others.find("%C"), 2, spell->caster->GetName()); + send_to_sender = false; + } + if(spell->caster->GetZone()) { + spell->caster->GetZone()->SimpleMessage(CHANNEL_SPELLS_OTHER, fade_message_others.c_str(), target, 50, send_to_sender); + } + } + } + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + ret = true; + } + if(lua_interface && !zone_shutting_down) + lua_interface->RemoveSpell(spell, true, SpellScriptTimersHasSpell(spell), reason, removing_all_spells); + } + + if(shared_lock_spell && !removing_all_spells) { + MSpellProcess.unlock_shared(); + } + return ret; +} + +bool SpellProcess::ProcessSpell(LuaSpell* spell, bool first_cast, const char* function, SpellScriptTimer* timer, bool all_targets) { + bool ret = false; + if(!spell->state) + { + LogWrite(SPELL__ERROR, 0, "Spell", "Error: State is NULL! SpellProcess::ProcessSpell for Spell '%s'", (spell->spell != nullptr) ? spell->spell->GetName() : "Unknown"); + } + else if(lua_interface && !spell->interrupted){ + if(all_targets) + { + for(int t=0;ttargets.size();t++) + { + if(spell->caster->GetZone()) { + Spawn* altSpawn = spell->caster->GetZone()->GetSpawnByID(spell->targets[t]); + if(altSpawn) + { + std::string functionCall = ApplyLuaFunction(spell, first_cast, function, timer, altSpawn); + if(functionCall.length() < 1) + ret = false; + else + ret = lua_interface->CallSpellProcess(spell, 2 + spell->spell->GetLUAData()->size(), functionCall); + } + } + } + return true; + } + std::string functionCall = ApplyLuaFunction(spell, first_cast, function, timer); + if(functionCall.length() < 1) + ret = false; + else + ret = lua_interface->CallSpellProcess(spell, 2 + spell->spell->GetLUAData()->size(), functionCall); + } + return ret; +} + +std::string SpellProcess::ApplyLuaFunction(LuaSpell* spell, bool first_cast, const char* function, SpellScriptTimer* timer, Spawn* altTarget) +{ + std::string functionCall = lua_interface->AddSpawnPointers(spell, first_cast, false, function, timer, false, altTarget); + vector* data = spell->spell->GetLUAData(); + for(int32 i=0;isize();i++){ + switch(data->at(i)->type){ + case 0:{ + lua_interface->SetSInt32Value(spell->state, data->at(i)->int_value); + break; + } + case 1:{ + lua_interface->SetFloatValue(spell->state, data->at(i)->float_value); + break; + } + case 2:{ + lua_interface->SetBooleanValue(spell->state, data->at(i)->bool_value); + break; + } + case 3:{ + lua_interface->SetStringValue(spell->state, data->at(i)->string_value.c_str()); + break; + } + default:{ + LogWrite(SPELL__ERROR, 0, "Spell", "Error: Unknown LUA Type '%i' in SpellProcess::ProcessSpell for Spell '%s'", (int)data->at(i)->type, spell->spell->GetName()); + return string(""); + } + } + } + return functionCall; +} + +bool SpellProcess::CastPassives(Spell* spell, Entity* caster, bool remove) { + CastInstant(spell, caster, caster, remove, true); + return true; +} + +bool SpellProcess::CastInstant(Spell* spell, Entity* caster, Entity* target, bool remove, bool passive) { + LuaSpell* lua_spell = 0; + + if(lua_interface) + lua_spell = lua_interface->GetSpell(spell->GetSpellData()->lua_script.c_str()); + + if(!lua_spell) + { + string lua_script = spell->GetSpellData()->lua_script; + lua_script.append(".lua"); + lua_spell = 0; + + if(lua_interface) + lua_spell = lua_interface->GetSpell(lua_script.c_str()); + + if(!lua_spell) { + LogWrite(SPELL__ERROR, 0, "Spell", "Failed to get a LuaSpell for %s (%u)", spell->GetName(), spell->GetSpellID()); + return false; + } + else + spell->GetSpellData()->lua_script = lua_script; + } + + lua_spell->caster = caster; + lua_spell->initial_caster_char_id = (caster && caster->IsPlayer()) ? ((Player*)caster)->GetCharacterID() : 0; + lua_spell->spell = spell; + lua_spell->initial_target = target->GetID(); + lua_spell->initial_target_char_id = (target && target->IsPlayer()) ? ((Player*)target)->GetCharacterID() : 0; + GetSpellTargets(lua_spell); + + if (!lua_spell->spell->IsCopiedSpell()) + { + lua_getglobal(lua_spell->state, "customspell"); + if (lua_isfunction(lua_spell->state, lua_gettop(lua_spell->state))) { + lua_pop(lua_spell->state, 1); + Spell* tmpSpell = lua_spell->spell; + lua_spell->spell = new Spell(lua_spell->spell); + lua_interface->AddCustomSpell(lua_spell); + std::string outCall = lua_interface->AddSpawnPointers(lua_spell, false, false, "customspell",0,true); + if (outCall.length() > 0 && lua_pcall(lua_spell->state, 3, 3, 0) != 0) { + lua_interface->RemoveCustomSpell(lua_spell->spell->GetSpellID()); + lua_interface->ResetFunctionStack(lua_spell->state); + safe_delete(lua_spell->spell); + lua_spell->spell = tmpSpell; + } + else + lua_interface->ResetFunctionStack(lua_spell->state); + } + else + lua_interface->ResetFunctionStack(lua_spell->state); + } + + bool result = CastProcessedSpell(lua_spell, passive); + + caster->GetZone()->SendCastSpellPacket(lua_spell, caster); + + if(!result) { + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return result; + } + + if(!remove) + return result; + + lua_interface->RemoveSpell(lua_spell, true, SpellScriptTimersHasSpell(lua_spell)); + return true; +} + +void SpellProcess::SendStartCast(LuaSpell* spell, Client* client){ + if(client){ + PacketStruct* packet = configReader.getStruct("WS_StartCastSpell", client->GetVersion()); + if(packet){ + packet->setDataByName("cast_time", spell->spell->GetSpellData()->cast_time*.01); + packet->setMediumStringByName("spell_name", spell->spell->GetSpellData()->name.data.c_str()); + EQ2Packet* outapp = packet->serialize(); + //DumpPacket(outapp); + client->QueuePacket(outapp); + safe_delete(packet); + } + } +} + +void SpellProcess::SendFinishedCast(LuaSpell* spell, Client* client){ + if(client && spell && spell->spell){ + if (spell->spell->GetSpellData()->cast_type == SPELL_CAST_TYPE_TOGGLE) + UnlockAllSpells(client, spell->spell); + else + UnlockAllSpells(client); + + if(spell->resisted && spell->spell->GetSpellData()->recast > 0) + CheckRecast(spell->spell, client->GetPlayer(), 0.5f); // half sec recast on resisted spells + else if (!spell->interrupted) + CheckRecast(spell->spell, client->GetPlayer()); + else if(spell->caster && spell->caster->IsPlayer()) + { + ((Player*)spell->caster)->LockSpell(spell->spell, (int16)(spell->spell->CalculateRecastTimer(spell->caster))); + } + PacketStruct* packet = configReader.getStruct("WS_FinishCastSpell", client->GetVersion()); + if(packet){ + packet->setMediumStringByName("spell_name", spell->spell->GetSpellData()->name.data.c_str()); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + SendSpellBookUpdate(client); + } + if (spell) { + if (!spell->interrupted) { + TakePower(spell); + TakeHP(spell); + TakeSavagery(spell); + AddDissonance(spell); + AddConcentration(spell); + if (client && spell->spell && !spell->spell->GetSpellData()->friendly_spell) { + if(!client->GetPlayer()->EngagedInCombat()) { + client->GetPlayer()->InCombat(true, false); + client->GetPlayer()->SetRangeAttack(false); + } + else if(client->GetPlayer()->EngagedInCombat() && + client->GetPlayer()->GetRangeAttack() && spell->spell->GetSpellData()->type == SPELL_BOOK_TYPE_COMBAT_ART) { + Spawn* target = client->GetPlayer()->GetZone()->GetSpawnByID(spell->initial_target); + if(target) { + float distance = client->GetPlayer()->GetDistance(target); + if(distance <= rule_manager.GetGlobalRule(R_Combat, MaxCombatRange)->GetFloat()) { + client->GetPlayer()->InCombat(true, false); + client->GetPlayer()->SetRangeAttack(false); + } + } + } + } + if(client && spell->caster) + client->CheckPlayerQuestsSpellUpdate(spell->spell); + } + } +} + +void SpellProcess::LockAllSpells(Client* client){ + if(client){ + client->GetPlayer()->LockAllSpells(); + SendSpellBookUpdate(client); + } +} + +void SpellProcess::UnlockAllSpells(Client* client, Spell* exception){ + if(client) + client->GetPlayer()->UnlockAllSpells(false, exception); +} + +void SpellProcess::UnlockSpell(Client* client, Spell* spell){ + if(client && client->GetPlayer() && spell) { + client->GetPlayer()->UnlockSpell(spell); + SendSpellBookUpdate(client); + } +} + +bool SpellProcess::CheckPower(LuaSpell* spell){ + int16 req = 0; + if(spell->caster){ + req = spell->spell->GetPowerRequired(spell->caster); + if(spell->caster->GetPower() >= req) + return true; + } + return false; +} + +bool SpellProcess::TakePower(LuaSpell* spell, int32 custom_power_req){ + int16 req = 0; + if(spell->caster){ + + if(custom_power_req) + req = custom_power_req; + else + req = spell->spell->GetPowerRequired(spell->caster); + + if(spell->caster->GetPower() >= req){ + spell->caster->SetPower(spell->caster->GetPower() - req); + if(spell->caster->IsPlayer() && spell->caster->GetZone()) + spell->caster->GetZone()->TriggerCharSheetTimer(); + return true; + } + } + return false; +} + +bool SpellProcess::CheckHP(LuaSpell* spell) { + int16 req = 0; + if(spell->caster){ + req = spell->spell->GetHPRequired(spell->caster); + if(spell->caster->GetHP() >= req) + return true; + } + return false; +} + +bool SpellProcess::TakeHP(LuaSpell* spell, int32 custom_hp_req) { + int16 req = 0; + if(spell->caster){ + if(custom_hp_req) + req = custom_hp_req; + else + req = spell->spell->GetHPRequired(spell->caster); + if(spell->caster->GetHP() >= req){ + spell->caster->SetHP(spell->caster->GetHP() - req); + if(spell->caster->IsPlayer() && spell->caster->GetZone()) + spell->caster->GetZone()->TriggerCharSheetTimer(); + return true; + } + } + return false; +} + +bool SpellProcess::CheckConcentration(LuaSpell* spell) { + if (spell && spell->caster) { + int8 req = spell->spell->GetSpellData()->req_concentration; + if(spell->caster->GetConcentrationCurrent() >= spell->caster->GetConcentrationMax()) { + if(req) { + return false; // req needed, no concentration or beyond our concentration + } + else { + return true; // no req set + } + } + int8 current_avail = spell->caster->GetConcentrationMax() - spell->caster->GetConcentrationCurrent(); + if (current_avail >= req) + return true; + } + return false; +} + +bool SpellProcess::AddConcentration(LuaSpell* spell) { + if (spell && spell->caster) { + int8 req = spell->spell->GetSpellData()->req_concentration; + + if(spell->caster->GetConcentrationCurrent() >= spell->caster->GetConcentrationMax()) { + if(req) { + return false; // req needed, no concentration or beyond our concentration + } + else { + return true; // no req set + } + } + int8 current_avail = spell->caster->GetConcentrationMax() - spell->caster->GetConcentrationCurrent(); + if (current_avail >= req) { + spell->caster->GetInfoStruct()->set_cur_concentration(spell->caster->GetConcentrationCurrent() + req); + if (spell->caster->IsPlayer() && spell->caster->GetZone()) + spell->caster->GetZone()->TriggerCharSheetTimer(); + LogWrite(SPELL__DEBUG, 0, "Spell", "Concentration is now %u on %s (Spell %s)", spell->caster->GetInfoStruct()->get_cur_concentration(), spell->caster->GetName(), spell->spell->GetName()); + return true; + } + } + return false; +} + +bool SpellProcess::CheckSavagery(LuaSpell* spell) { + if (spell && spell->caster) { + int16 req = spell->spell->GetSavageryRequired(spell->caster); + if(spell->caster->GetSavagery() >= req) + return true; + } + return false; +} + +bool SpellProcess::TakeSavagery(LuaSpell* spell) { + int16 req = 0; + if(spell && spell->caster && spell->caster->IsPlayer()){ + req = spell->spell->GetSavageryRequired(spell->caster); + if(spell->caster->GetSavagery() >= req){ + spell->caster->SetSavagery(spell->caster->GetSavagery() - req); + if(spell->caster->IsPlayer() && spell->caster->GetZone()) + spell->caster->GetZone()->TriggerCharSheetTimer(); + return true; + } + } + return false; +} + +bool SpellProcess::CheckDissonance(LuaSpell* spell) { + if (spell && spell->caster) { + int16 req = spell->spell->GetDissonanceRequired(spell->caster); + if(spell->caster->GetDissonance() <= req) + return true; + } + return false; +} + +bool SpellProcess::AddDissonance(LuaSpell* spell) { + int16 req = 0; + if(spell && spell->caster && spell->caster->IsPlayer()){ + req = spell->spell->GetDissonanceRequired(spell->caster); + if(spell->caster->GetDissonance() >= req){ + spell->caster->SetDissonance(spell->caster->GetDissonance() - req); + if(spell->caster->IsPlayer() && spell->caster->GetZone()) + spell->caster->GetZone()->TriggerCharSheetTimer(); + return true; + } + } + return false; +} + +void SpellProcess::AddSpellToQueue(Spell* spell, Entity* caster){ + if(caster && caster->IsPlayer() && spell){ + LogWrite(SPELL__DEBUG, 1, "Spell", "%s AddSpellToQueue casting %s.", caster->GetName(), spell->GetName()); + spell_que.Put(caster, spell); + ((Player*)caster)->QueueSpell(spell); + Client* client = ((Player*)caster)->GetClient(); + if(client) + SendSpellBookUpdate(client); + } +} + +void SpellProcess::RemoveSpellFromQueue(Spell* spell, Entity* caster, bool send_update){ + if(caster && caster->IsPlayer() && spell){ + LogWrite(SPELL__DEBUG, 1, "Spell", "%s RemoveSpellFromQueue casting %s.", caster->GetName(), spell->GetName()); + spell_que.erase(caster); + ((Player*)caster)->UnQueueSpell(spell); + Client* client = ((Player*)caster)->GetClient(); + if(client && send_update) + SendSpellBookUpdate(client); + } +} + +void SpellProcess::RemoveSpellFromQueue(Entity* caster, bool hostile_only) { + if (caster && spell_que.count(caster) > 0) { + Spell* spell = spell_que.Get(caster); + if (spell) { + bool remove = true; + if (hostile_only && spell->GetSpellData()->target_type != SPELL_TARGET_ENEMY) + remove = false; + if (remove) { + + LogWrite(SPELL__DEBUG, 1, "Spell", "%s RemoveSpellFromQueue::Secondary casting %s.", caster->GetName(), spell->GetName()); + spell_que.erase(caster); + ((Player*)caster)->UnQueueSpell(spell); + Client* client = ((Player*)caster)->GetClient(); + if (client) + SendSpellBookUpdate(client); + } + } + } +} + +bool SpellProcess::CheckSpellQueue(Spell* spell, Entity* caster){ + if(caster->IsPlayer()){ + bool add = true; + bool remove = false; + Spell* prevSpell = 0; + if(spell_que.count(caster) > 0){ + prevSpell = spell_que.Get(caster); + remove = true; + if(prevSpell == spell) { + add = false; + } + } + if(remove) + RemoveSpellFromQueue(prevSpell, caster, !add); + if(add) + { + AddSpellToQueue(spell, caster); + return true; + } + } + return false; +} + +void SpellProcess::SendSpellBookUpdate(Client* client){ + if(client && client->IsReadyForSpawns()){ + EQ2Packet* app = client->GetPlayer()->GetSpellBookUpdatePacket(client->GetVersion()); + if(app) + client->QueuePacket(app); + } +} + +LuaSpell* SpellProcess::GetLuaSpell(Entity* caster){ + LuaSpell* spell = 0; + if(caster && cast_timers.size() > 0){ + CastTimer* cast_timer = 0; + MutexList::iterator itr = cast_timers.begin(); + while(itr.Next()){ + cast_timer = itr->value; + if(cast_timer && cast_timer->spell && cast_timer->spell->caster == caster){ + spell = cast_timer->spell; + break; + } + } + } + return spell; +} + +Spell* SpellProcess::GetSpell(Entity* caster){ + Spell* spell = 0; + if(cast_timers.size() > 0){ + CastTimer* cast_timer = 0; + MutexList::iterator itr = cast_timers.begin(); + while(itr.Next()){ + cast_timer = itr->value; + if(cast_timer && cast_timer->spell && cast_timer->spell->caster == caster){ + spell = cast_timer->spell->spell; + break; + } + } + } + return spell; +} + +void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster, Spawn* target, bool lock, bool harvest_spell, LuaSpell* customSpell, int16 custom_cast_time, bool in_heroic_opp) +{ + if((customSpell != 0 || spell != 0) && caster) + { + Client* client = 0; + //int16 version = 0; + + LuaSpell* lua_spell = 0; + + if (customSpell) + { + lua_spell = customSpell; + spell = lua_spell->spell; + } + else if(lua_interface) + lua_spell = lua_interface->GetSpell(spell->GetSpellData()->lua_script.c_str()); + + // this will only hit if customSpell is null and we go through the lua_interface + if(!lua_spell) + { + string lua_script = spell->GetSpellData()->lua_script; + lua_script.append(".lua"); + + if(lua_interface) + lua_spell = lua_interface->GetSpell(lua_script.c_str()); + + if(!lua_spell) + return; + else + spell->GetSpellData()->lua_script = lua_script; + } + + if (!target) + target = caster; + + int8 target_type = spell->GetSpellData()->target_type; + int8 spell_type = spell->GetSpellData()->spell_type; + + lua_spell->caster = caster; + lua_spell->initial_caster_char_id = (caster && caster->IsPlayer()) ? ((Player*)caster)->GetCharacterID() : 0; + lua_spell->spell = spell; + + int32 target_id = target->GetID(); + lua_spell->initial_target = target_id; + lua_spell->initial_target_char_id = (target && target->IsPlayer()) ? ((Player*)target)->GetCharacterID() : 0; + + if (!harvest_spell) + GetSpellTargets(lua_spell); + else{ + AddLuaSpellTarget(lua_spell, target_id); + } + + + if (target_id == lua_spell->initial_target) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s is casting %s on %s.", caster->GetName(), spell->GetName(), ( target ) ? target->GetName() : "No Target" ); + } + else + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Casting on %s through %s as target.", spell->GetName(), caster->GetZone()->GetSpawnByID(lua_spell->initial_target)->GetName(), target->GetName()); + } + + target = lua_spell->caster->GetZone()->GetSpawnByID(lua_spell->initial_target); + + if(caster->IsPlayer() && zone) + { + client = ((Player*)caster)->GetClient(); + //version = client->GetVersion(); + } + + if (!customSpell && !lua_spell->spell->IsCopiedSpell()) + { + lua_getglobal(lua_spell->state, "customspell"); + if (lua_isfunction(lua_spell->state, lua_gettop(lua_spell->state))) { + lua_pop(lua_spell->state, 1); + Spell* tmpSpell = lua_spell->spell; + lua_spell->spell = new Spell(lua_spell->spell); + lua_interface->AddCustomSpell(lua_spell); + std::string outCall = lua_interface->AddSpawnPointers(lua_spell, false, false, "customspell", 0, true); + if (outCall.length() > 0 && lua_pcall(lua_spell->state, 3, 3, 0) != 0) { + lua_interface->RemoveCustomSpell(lua_spell->spell->GetSpellID()); + lua_interface->ResetFunctionStack(lua_spell->state); + safe_delete(lua_spell->spell); + lua_spell->spell = tmpSpell; + } + else + lua_interface->ResetFunctionStack(lua_spell->state); + } + else + lua_interface->ResetFunctionStack(lua_spell->state); + } + + //If this spell is the toggle cast type and is being toggled off, do this now + if (spell->GetSpellData()->cast_type == SPELL_CAST_TYPE_TOGGLE) + { + bool ret_val = DeleteCasterSpell(caster, spell, "purged"); + + if (ret_val) + { + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + } + + // If a player is casting the spell AND spell is a tradeskill spell + if (client && spell->GetSpellData()->spell_book_type == SPELL_BOOK_TYPE_TRADESKILL) + { + // If the player is currently crafting + if (client->IsCrafting()) + { + Tradeskill* tradeskill = 0; + zone->GetTradeskillMgr()->ReadLock(__FUNCTION__, __LINE__); + tradeskill = zone->GetTradeskillMgr()->GetTradeskill(client); + + // Can not cast a crafting spell that doesn't match the current crafting technique + if (spell->GetSpellData()->mastery_skill != tradeskill->recipe->GetTechnique()) + { + // send a message to the client, used chat_relationship to match other tradeskill messages, not sure if it is accurate though + client->Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "You are not using %s on this recipe.", master_skill_list.GetSkill(spell->GetSpellData()->mastery_skill)->name.data.c_str()); + + // Write a spell debug message on why we couldn't cast + LogWrite(SPELL__DEBUG, 1, "Spell", "%s could not cast tradeskill spell (%s), skills did not match. spell mastery skill = %u, tradeskill technique = %u", caster->GetName(), spell->GetName(), spell->GetSpellData()->mastery_skill, tradeskill->recipe->GetTechnique()); + + // make sure to release the lock before we return out + zone->GetTradeskillMgr()->ReleaseReadLock(__FUNCTION__, __LINE__); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + // need to make sure the lock is released if the if passed + zone->GetTradeskillMgr()->ReleaseReadLock(__FUNCTION__, __LINE__); + } + else // If the player is not currently crafting + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s cannot cast a tradeskill spell (%s) while not crafting.", caster->GetName(), spell->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_ONLY_WHEN_CRAFTING); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + } + + if (caster != target && target != NULL && !caster->CheckLoS(target)) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s cannot see target %s.", caster->GetName(), target->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_CANT_SEE_TARGET); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + + LuaSpell* conflictSpell = caster->HasLinkedTimerID(lua_spell, target, (spell_type == SPELL_TYPE_DD || spell_type == SPELL_TYPE_DOT)); + if(conflictSpell) + { + if(conflictSpell->spell->GetSpellData()->min_class_skill_req > 0 && lua_spell->spell->GetSpellData()->min_class_skill_req > 0) + { + if(conflictSpell->spell->GetSpellData()->min_class_skill_req <= lua_spell->spell->GetSpellData()->min_class_skill_req) + { + if(lua_spell->spell->GetSpellData()->duration_until_cancel && !lua_spell->num_triggers) + { + if(spell->GetSpellData()->friendly_spell) + { + ZoneServer* zone = caster->GetZone(); + Spawn* tmpTarget = zone->GetSpawnByID(conflictSpell->initial_target); + if(tmpTarget && tmpTarget->IsEntity()) + { + zone->RemoveTargetFromSpell(conflictSpell, tmpTarget); + CheckRemoveTargetFromSpell(conflictSpell); + ((Entity*)tmpTarget)->RemoveSpellEffect(conflictSpell); + if(client && IsReady(conflictSpell->spell, client->GetPlayer())) + UnlockSpell(client, conflictSpell->spell); + } + DeleteSpell(lua_spell); + return; + } + else if(lua_spell->spell->GetSpellData()->spell_type == SPELL_TYPE_DEBUFF) + { + SpellCannotStack(zone, client, lua_spell->caster, lua_spell, conflictSpell); + return; + } + } + } + else + { + SpellCannotStack(zone, client, lua_spell->caster, lua_spell, conflictSpell); + return; + } + } + } + + if ((caster->IsMezzed() && !spell->CastWhileMezzed()) || (caster->IsStunned() && !spell->CastWhileStunned())) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s cannot cast (mezzed or stunned).", caster->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_CANNOT_CAST_STUNNED); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + + if (caster->IsStifled() && !spell->CastWhileStifled()) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s cannot cast (stifled).", caster->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_CANNOT_CAST_STIFFLED); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + + if (caster->IsFeared() && !spell->CastWhileFeared()) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s cannot cast (feared).", caster->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_CANNOT_CAST_FEARED); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + + if(caster->IsPlayer() && !IsReady(spell, caster)) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "Queuing spell for %s.", caster->GetName()); + bool queueSpell = CheckSpellQueue(spell, caster); + if(!queueSpell) + { + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + } + return; + } + + if (target_type != SPELL_TARGET_SELF && target_type != SPELL_TARGET_GROUP_AE && spell_type != SPELL_TYPE_ALLGROUPTARGETS && target_type != SPELL_TARGET_NONE && spell->GetSpellData()->max_aoe_targets == 0) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Not Self, Not Group AE, Not None, Max Targets = 0", spell->GetName()); + + if (!target) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: No target.", spell->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_NO_ELIGIBLE_TARGET); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + + float tmpRange = spell->GetSpellData()->range; + + if (client && target != caster && !spell->GetSpellData()->range) + { + bool match = false; + int tmpTier = 0; + Spell* tmpSpell = 0; + for (tmpTier = 0; tmpTier < spell->GetSpellTier(); tmpTier++) + { + tmpSpell = master_spell_list.GetSpell(spell->GetSpellData()->id, tmpTier); + if (tmpSpell && tmpSpell->GetSpellData()->range) + { + match = true; + break; + } + } + if (tmpSpell) + tmpRange = tmpSpell->GetSpellData()->range; + + if (!match) + tmpTier = -1; + + char msg[512]; + snprintf(msg, 512, "SpellCasted without proper spell range set %s ID %i Tier %i Range obtained from tier %i range %f", spell->GetName(), spell->GetSpellID(), spell->GetSpellTier(), tmpTier, tmpRange); + if (!world.CheckTempBugCRC(msg)) + commands.Command_ReportBug(client, new Seperator(msg)); + } + + if(caster->GetDistance(target) > tmpRange) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Too far.", spell->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_TOO_FAR_AWAY); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + + if (caster->GetDistance(target) < spell->GetSpellData()->min_range) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Too close.", spell->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_TOO_CLOSE); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + } + + if(target_type == SPELL_TARGET_SELF && spell->GetSpellData()->max_aoe_targets == 0) + { + if (harvest_spell) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Harvest, Target Self, Max Targets = 0", spell->GetName()); + + if (!target || !target->IsGroundSpawn()) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: No target or not groundspawn.", spell->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_NO_ELIGIBLE_TARGET); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + } + else + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Target '%s' is Caster '%s'.", spell->GetName(), ( target ) ? target->GetName() : "None", caster->GetName()); + target = caster; + } + } + + // Is enemy spell AND direct-damage (no AE) + if (target_type == SPELL_TARGET_ENEMY && spell->GetSpellData()->max_aoe_targets == 0) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Target Enemy (%s) and Max AE Targets = 0.", spell->GetName(), target->GetName()); + + + if((spell->GetSpellData()->friendly_spell && caster != target && (caster->IsPlayer() || caster->IsBot()) && (target->IsPlayer() || target->IsBot()) && + ((Entity*)target)->GetInfoStruct()->get_engaged_encounter()) && + ((!((Entity*)caster)->GetGroupMemberInfo() || !((Entity*)target)->GetGroupMemberInfo()) || + (((Entity*)caster)->GetGroupMemberInfo()->group_id != ((Entity*)target)->GetGroupMemberInfo()->group_id))) { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Target (%s) is engaged in combat and cannot be assisted.", spell->GetName(), target->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_A_FRIEND); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + else if (spell->GetSpellData()->friendly_spell && target->IsPlayer() ) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Is Friendly, Target is Player (%s).", spell->GetName(), target->GetName()); + + if (!target->IsEntity()) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: No target.", spell->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_NO_ELIGIBLE_TARGET); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + /*if (target->appearance.attackable) { + zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_A_FRIEND); + return; + }*/ + } + else if (spell->GetSpellData()->friendly_spell && target->IsNPC() && target->appearance.attackable) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Is Not Friendly (friendly and NPC)", spell->GetName()); + + if (target->IsPet()) + { + if (((NPC*)target)->GetOwner() && ((NPC*)target)->GetOwner()->IsNPC()) + { + zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_A_FRIEND); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + } + else if(((Entity*)target)->GetInfoStruct()->get_friendly_target_npc()) { + // it's a bypass! + } + else if (target->IsBot() && (caster->IsPlayer() || caster->IsBot())) { + // Needed so bots or player can cast friendly spells on bots + } + else if (caster->IsNPC()) { + // npcs can cast on other npcs + } + else + { + zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_A_FRIEND); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + } + else if (spell->GetSpellData()->friendly_spell && target->IsNPC() && caster->IsNPC()) { + // TODO: faction checks? some other checks to prevent an npc casting a friendly spell on another npc + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: NPC Cast", spell->GetName()); + } + else + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Is Not Friendly (catch all)", spell->GetName()); + + if (!target->IsEntity()) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: No target.", spell->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_NO_ELIGIBLE_TARGET); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + + if (caster == target || (!target->IsPlayer() && !target->appearance.attackable)) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Not an Enemy (Target: %s).", spell->GetName(), target->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_AN_ENEMY); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + + if (!target->Alive()) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Target is not alive (Target: %s).", spell->GetName(), target->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_ALIVE); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + + if (target->GetInvulnerable()) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Target is invulnerable (Target: %s).", spell->GetName(), target->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_TARGET_INVULNERABLE); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + + bool attackAllowed = (Entity*)caster->AttackAllowed((Entity*)target, 0); + if (!attackAllowed) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Target (%s) is player and not attackable.", spell->GetName(), target->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_AN_ENEMY); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + + if (target->IsPet() && ((NPC*)target)->GetOwner() && ((NPC*)target)->GetOwner() == caster) { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Target (%s) is casters pet and not attackable by caster.", spell->GetName(), target->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_AN_ENEMY); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + } + } + + if (lua_spell->targets.size() == 0 && spell->GetSpellData()->max_aoe_targets == 0) + { + LogWrite(SPELL__ERROR, 0, "Spell", "SpellProcess::ProcessSpell Unable to find any spell targets for spell '%s', spell type: %u, target type %u.", + spell->GetName(), spell->GetSpellData()->spell_type, spell->GetSpellData()->target_type); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + + if(target_type == SPELL_TARGET_ENEMY_CORPSE || target_type == SPELL_TARGET_GROUP_CORPSE) + { + if(target->Alive()) + { + zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_DEAD); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + if(target->IsPlayer() && ((Player*)target)->GetClient() && ((Player*)target)->GetClient()->GetCurrentRez()->active){ + zone->SendSpellFailedPacket(client, SPELL_ERROR_ALREADY_CAST); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + } + + if(!CheckPower(lua_spell)) + { + LogWrite(SPELL__WARNING, 1, "Spell", "%s: Caster Lacked Power to cast (%s).", spell->GetName(), caster->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_ENOUGH_POWER); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + + if (!CheckHP(lua_spell)) + { + LogWrite(SPELL__WARNING, 1, "Spell", "%s: Caster Lacked Health to cast (%s).", spell->GetName(), caster->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_ENOUGH_HEALTH); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + + if (!CheckSavagery(lua_spell)) + { + LogWrite(SPELL__WARNING, 1, "Spell", "%s: Caster Lacked Savagery to cast (%s).", spell->GetName(), caster->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_ENOUGH_SAVAGERY); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + + if (!CheckDissonance(lua_spell)) + { + LogWrite(SPELL__WARNING, 1, "Spell", "%s: Caster Lacked Dissonance to cast (%s).", spell->GetName(), caster->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_ENOUGH_DISSONANCE); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + + if (!CheckConcentration(lua_spell)) + { + LogWrite(SPELL__WARNING, 1, "Spell", "%s: Caster Lacked Concentration to cast (%s).", spell->GetName(), caster->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_ENOUGH_CONC); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + + // Precast in lua + if (lua_interface) { + bool result = false; + std::string outCall = lua_interface->AddSpawnPointers(lua_spell, false, true); + if (outCall.length() > 0 && lua_pcall(lua_spell->state, 2, LUA_MULTRET, 0) == 0) { + int8 error = SPELL_ERROR_CANNOT_PREPARE; + if(lua_istable(lua_spell->state, -1)) { + lua_rawgeti(lua_spell->state, -1, 1); + if(lua_isboolean(lua_spell->state, -1)) { + result = lua_toboolean(lua_spell->state,-1); + } + lua_pop(lua_spell->state, 1); + + lua_rawgeti(lua_spell->state, -1, 2); + if(lua_isnumber(lua_spell->state, -1)) { + error = lua_tonumber(lua_spell->state,-1); + } + lua_pop(lua_spell->state, 1); + } + else if (lua_toboolean(lua_spell->state, -1)) + { + result = lua_toboolean(lua_spell->state, -1); + lua_pop(lua_spell->state, 1); + } + lua_interface->ResetFunctionStack(lua_spell->state); + + // need to add back support for error to be modified + if (!result) { + zone->SendSpellFailedPacket(client, error); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + } + else { + LogWrite(SPELL__DEBUG, 1, "Spell", "No precast function found for %s", lua_spell->spell->GetName()); + lua_interface->ResetFunctionStack(lua_spell->state); + } + } + else + LogWrite(SPELL__DEBUG, 1, "Spell", "Unable to do precast check as there was no lua_interface"); + + if (custom_cast_time > 0) { + spell->GetSpellData()->orig_cast_time = custom_cast_time; + spell->GetSpellData()->cast_time = custom_cast_time; + } + + //Apply casting speed mod + spell->ModifyCastTime(caster); + + //cancel stealth effects on cast + if(caster->IsStealthed() || caster->IsInvis()) + caster->CancelAllStealth(); + + if(caster && caster->IsEntity() && caster->CheckFizzleSpell(lua_spell)) + { + caster->GetZone()->SendCastSpellPacket(0, target ? target : caster, caster); + caster->GetZone()->SendInterruptPacket(caster, lua_spell, true); + caster->IsCasting(false); + + if(caster->IsPlayer()) + { + ((Player*)caster)->UnlockSpell(spell); + SendSpellBookUpdate(((Player*)caster)->GetClient()); + } + + // fizzle takes half + int16 power_req = spell->GetPowerRequired(caster) / 2; + TakePower(lua_spell, power_req); + + int16 hp_req = spell->GetHPRequired(caster) / 2; + TakeHP(lua_spell, hp_req); + + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + + SendStartCast(lua_spell, client); + + LockAllSpells(client); + + if(spell->GetSpellData()->cast_time > 0) + { + CastTimer* cast_timer = new CastTimer; + cast_timer->entity_command = 0; + cast_timer->target_id = target ? target->GetID() : 0; + cast_timer->spell = lua_spell; + cast_timer->spell->caster = caster; + cast_timer->delete_timer = false; + cast_timer->timer = new Timer(spell->GetSpellData()->cast_time * 10); + cast_timer->zone = zone; + cast_timer->in_heroic_opp = in_heroic_opp; + cast_timers.Add(cast_timer); + if(caster) + caster->IsCasting(true); + } + else + { + if(!CastProcessedSpell(lua_spell, false, in_heroic_opp)) + { + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + } + + if(caster) + caster->GetZone()->SendCastSpellPacket(lua_spell, caster); + + } +} + +void SpellProcess::ProcessEntityCommand(ZoneServer* zone, EntityCommand* entity_command, Entity* caster, Spawn* target, bool lock, bool in_heroic_opp) { + if (zone && entity_command && caster && target && !target->IsPlayer()) { + Client* client = ((Player*)caster)->GetClient(); + + if(!client) { + return; + } + if (caster->GetDistance(target) > entity_command->distance) { + zone->SendSpellFailedPacket(client, SPELL_ERROR_TOO_FAR_AWAY); + return; + } + if (entity_command->cast_time > 0) { + PacketStruct* packet = configReader.getStruct("WS_StartCastSpell", client->GetVersion()); + if (packet) { + LockAllSpells(client); + packet->setDataByName("cast_time", entity_command->cast_time * 0.01); + packet->setMediumStringByName("spell_name", entity_command->name.c_str()); + EQ2Packet* outapp = packet->serialize(); + client->QueuePacket(outapp); + safe_delete(packet); + + CastTimer* cast_timer = new CastTimer; + cast_timer->caster = client; + cast_timer->target_id = target ? target->GetID() : 0; + cast_timer->entity_command = entity_command; + cast_timer->spell = 0; + cast_timer->delete_timer = false; + cast_timer->timer = new Timer(entity_command->cast_time * 10); + cast_timer->zone = zone; + cast_timer->in_heroic_opp = in_heroic_opp; + cast_timers.Add(cast_timer); + caster->IsCasting(true); + } + } + else if (!CastProcessedEntityCommand(entity_command, client, target, in_heroic_opp)) + return; + if (entity_command && entity_command->spell_visual > 0) + caster->GetZone()->SendCastEntityCommandPacket(entity_command, caster->GetID(), target->GetID()); + } +} + +bool SpellProcess::CastProcessedSpell(LuaSpell* spell, bool passive, bool in_heroic_opp){ + if(!spell || !spell->caster || !spell->spell || spell->interrupted) + return false; + Client* client = 0; + if(spell->caster && spell->caster->IsPlayer()) + client = ((Player*)spell->caster)->GetClient(); + if (spell->spell->GetSpellData()->max_aoe_targets > 0 && spell->targets.size() == 0) { + GetSpellTargetsTrueAOE(spell); + if (spell->targets.size() == 0) { + if(client) + { + client->GetPlayer()->UnlockAllSpells(true); + SendSpellBookUpdate(client); + } + spell->caster->GetZone()->SendSpellFailedPacket(client, SPELL_ERROR_NO_TARGETS_IN_RANGE); + return false; + } + } + + if(client && client->GetCurrentZone() && + !spell->spell->GetSpellData()->friendly_spell) + { + ZoneServer* zone = client->GetCurrentZone(); + Spawn* tmpTarget = zone->GetSpawnByID(spell->initial_target); + int8 spell_type = spell->spell->GetSpellData()->spell_type; + LuaSpell* conflictSpell = spell->caster->HasLinkedTimerID(spell, tmpTarget, (spell_type == SPELL_TYPE_DD || spell_type == SPELL_TYPE_DOT)); + if(conflictSpell && tmpTarget && tmpTarget->IsEntity()) + { + ((Entity*)tmpTarget)->RemoveSpellEffect(conflictSpell); + zone->RemoveTargetFromSpell(conflictSpell, tmpTarget); + } + } + + MutexList::iterator itr = active_spells.begin(); + bool processedSpell = false; + + bool allTargets = (spell->spell->GetSpellData()->spell_type == SPELL_TYPE_ALLGROUPTARGETS); + if (!processedSpell) + processedSpell = ProcessSpell(spell, true, 0, 0, allTargets); + + // Quick hack to prevent a crash on spells that zones the caster (Gate) + if (!spell->caster) + return true; + + Skill* skill = spell->caster->GetSkillByID(spell->spell->GetSpellData()->mastery_skill, false); + // trigger potential skill increase if we succeed in casting a mastery skill and it still has room to grow (against this spell) + if(skill && skill->current_val < spell->spell->GetSpellData()->min_class_skill_req) + spell->caster->GetSkillByID(spell->spell->GetSpellData()->mastery_skill, true); + + ZoneServer* zone = spell->caster->GetZone(); + Spawn* target = 0; + if(processedSpell){ + for (int32 i = 0; i < spell->targets.size(); i++) { + target = zone->GetSpawnByID(spell->targets[i]); + if (!target) + continue; + + if (client && client->IsZoning()) + continue; + + // TODO: Establish actual hate per spell + if (!spell->spell->GetSpellData()->friendly_spell && target->IsNPC()) + ((NPC*)target)->AddHate((Entity*)spell->caster, 50); + + if(spell->spell->GetSpellData()->success_message.length() > 0){ + if(client){ + string success_message = spell->spell->GetSpellData()->success_message; + if(success_message.find("%t") != string::npos) + success_message.replace(success_message.find("%t"), 2, spell->caster->GetName()); + client->Message(CHANNEL_SPELLS, success_message.c_str()); + } + } + if(spell->spell->GetSpellData()->effect_message.length() > 0){ + string effect_message = spell->spell->GetSpellData()->effect_message; + bool send_to_sender = true; + if(effect_message.find("%t") != string::npos) + effect_message.replace(effect_message.find("%t"), 2, target->GetName()); + if (effect_message.find("%c") != string::npos) + effect_message.replace(effect_message.find("%c"), 2, spell->caster->GetName()); + if (effect_message.find("%T") != string::npos) { + effect_message.replace(effect_message.find("%T"), 2, target->GetName()); + send_to_sender = false; + } + if (effect_message.find("%C") != string::npos) { + effect_message.replace(effect_message.find("%C"), 2, spell->caster->GetName()); + send_to_sender = false; + } + + if(spell->caster && spell->caster->GetZone()) { + spell->caster->GetZone()->SimpleMessage(CHANNEL_SPELLS_OTHER, effect_message.c_str(), target, 50, send_to_sender); + } + } + if(target->GetZone()) { + target->GetZone()->CallSpawnScript(target, SPAWN_SCRIPT_CASTED_ON, spell->caster, spell->spell->GetName()); + } + } + } + else{ + if (!passive) + SendFinishedCast(spell, client); + return false; + } + if(!spell->resisted && (spell->spell->GetSpellDuration() > 0 || spell->spell->GetSpellData()->duration_until_cancel || spell->spell->GetSpellData()->spell_book_type == SPELL_BOOK_TYPE_NOT_SHOWN)) { + for (int32 i = 0; i < spell->targets.size(); i++) { + + //LogWrite(SPELL__ERROR, 0, "Spell", "No precast function found for %s", ((Entity*)target)->GetName()); + target = zone->GetSpawnByID(spell->targets.at(i)); + if (!target && spell->targets.at(i) == spell->caster->GetID()) { + target = spell->caster; + } + if (!target) { + if(client) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Zone has not finished loading process yet. Try again later."); + } + continue; + } + if (i == 0 && !spell->spell->GetSpellData()->not_maintained) { + spell->caster->AddMaintainedSpell(spell); + } + + SpellEffects* effect = ((Entity*)target)->GetSpellEffect(spell->spell->GetSpellID()); + if (effect && effect->tier > spell->spell->GetSpellTier()) { + if(client) { + spell->caster->GetZone()->SendSpellFailedPacket(client, SPELL_ERROR_TAKE_EFFECT_MOREPOWERFUL); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "The spell did not take hold as the target already has a better spell of this type."); + } + } + else{ + ((Entity*)target)->AddSpellEffect(spell); + if(spell->spell->GetSpellData()->det_type > 0) + ((Entity*)target)->AddDetrimentalSpell(spell); + } + } + + active_spells.Add(spell); + + if (spell->num_triggers > 0) + ClientPacketFunctions::SendMaintainedExamineUpdate(client, spell->slot_pos, spell->num_triggers, 0); + if (spell->damage_remaining > 0) + ClientPacketFunctions::SendMaintainedExamineUpdate(client, spell->slot_pos, spell->damage_remaining, 1); + + spell->caster->GetZone()->TriggerCharSheetTimer(); + } + spell->num_calls = 1; + if(!spell->resisted && spell->spell->GetSpellData()->duration1 > 0){ + spell->timer.Start(); + if(spell->spell->GetSpellData()->call_frequency > 0) + spell->timer.SetTimer(spell->spell->GetSpellData()->call_frequency*100); + else + spell->timer.SetTimer(spell->spell->GetSpellData()->duration1*100); + if (active_spells.count(spell) < 1) { + active_spells.Add(spell); + } + } + + // if the caster is a player and the spell is a tradeskill spell check for a tradeskill event + if (client && spell->spell->GetSpellData()->spell_book_type == SPELL_BOOK_TYPE_TRADESKILL) { + spell->resisted = (spell->caster->DetermineHit(target ? target : spell->caster, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, 255, 0, true, spell) == DAMAGE_PACKET_RESULT_RESIST); + client->GetCurrentZone()->GetTradeskillMgr()->CheckTradeskillEvent(client, spell->resisted ? 0 : spell->spell->GetSpellData()->icon); + } + + + if (spell->spell->GetSpellData()->friendly_spell && zone->GetSpawnByID(spell->initial_target)) + spell->caster->CheckProcs(PROC_TYPE_BENEFICIAL, zone->GetSpawnByID(spell->initial_target)); + // Check HO's + if (client && !in_heroic_opp) { + HeroicOP* ho = nullptr; + Spell* ho_spell = nullptr; + int32 ho_target = 0; + + MSoloHO.writelock(__FUNCTION__, __LINE__); + MGroupHO.writelock(__FUNCTION__, __LINE__); + map::iterator soloItr = m_soloHO.find(client); + if (soloItr != m_soloHO.end()) { + ho = soloItr->second; + bool match = false; + LogWrite(SPELL__DEBUG, 0, "HO", "target = %u", ho->GetTarget()); + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int8 i = 0; i < spell->targets.size(); i++) { + LogWrite(SPELL__DEBUG, 0, "HO", "Target ID: %u", spell->targets.at(i)); + if (spell->targets.at(i) == ho->GetTarget() || spell->spell->GetSpellData()->friendly_spell) { + match = true; + LogWrite(SPELL__DEBUG, 0, "HO", "match found"); + break; + } + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + + if(match && !spell->spell) + LogWrite(SPELL__ERROR, 0, "HO", "%s: spell->spell is nullptr", client->GetPlayer()->GetName()); + else if (match && spell->spell && ho->UpdateHeroicOP(spell->spell->GetSpellIconHeroicOp())) { + ClientPacketFunctions::SendHeroicOPUpdate(client, ho); + if (ho->GetComplete() > 0) { + if(!ho->GetWheel()) + LogWrite(SPELL__ERROR, 0, "HO", "%s: Wheel is inactive (nullptr) cannot check for invalid spell", client->GetPlayer()->GetName()); + else + { + ho_spell = master_spell_list.GetSpell(ho->GetWheel()->spell_id, 1); + ho_target = ho->GetTarget(); + if (!ho_spell) + LogWrite(SPELL__ERROR, 0, "HO", "%s: Invalid spell for a HO, spell id = %u", client->GetPlayer()->GetName(), ho->GetWheel()->spell_id); + + safe_delete(ho); + m_soloHO.erase(soloItr); + } + } + } + } + else if (client->GetPlayer()->GetGroupMemberInfo()) { + map::iterator groupItr = m_groupHO.find(client->GetPlayer()->GetGroupMemberInfo()->group_id); + if (groupItr != m_groupHO.end()) { + ho = groupItr->second; + int32 group_id = client->GetPlayer()->GetGroupMemberInfo()->group_id; + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + if (((spell->targets.size() > 0 && spell->targets.at(0) == ho->GetTarget()) || spell->spell->GetSpellData()->friendly_spell) + && ho->UpdateHeroicOP(spell->spell->GetSpellIconHeroicOp())) { + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + deque::iterator itr; + PlayerGroup* group = world.GetGroupManager()->GetGroup(group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + for (itr = members->begin(); itr != members->end(); itr++) { + if ((*itr)->client) + ClientPacketFunctions::SendHeroicOPUpdate((*itr)->client, ho); + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + + if (ho->GetComplete() > 0) { + ho_spell = master_spell_list.GetSpell(ho->GetWheel()->spell_id, 1); + ho_target = ho->GetTarget(); + if (!ho_spell) + LogWrite(SPELL__ERROR, 0, "HO", "Invalid spell for a HO, spell id = %u", ho->GetWheel()->spell_id); + + safe_delete(ho); + m_groupHO.erase(groupItr); + } + } + else + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + } + MGroupHO.releasewritelock(__FUNCTION__, __LINE__); + MSoloHO.releasewritelock(__FUNCTION__, __LINE__); + + if (ho_spell && ho_target != 0) + client->GetCurrentZone()->ProcessSpell(ho_spell, client->GetPlayer(), spell->caster->GetZone()->GetSpawnByID(ho_target)); + + } + + if (spell->spell->GetSpellData()->friendly_spell == 1 && spell->initial_target) + spell->caster->CheckProcs(PROC_TYPE_BENEFICIAL, spell->caster->GetZone()->GetSpawnByID(spell->initial_target)); + + if (!passive) + SendFinishedCast(spell, client); + + return true; +} + +bool SpellProcess::CastProcessedEntityCommand(EntityCommand* entity_command, Client* client, Spawn* target, bool in_heroic_opp) { + bool ret = false; + if (entity_command && client) { + UnlockAllSpells(client); + client->GetPlayer()->IsCasting(false); + if (entity_command->cast_time == 0) { + client->GetPlayer()->GetZone()->CallSpawnScript(target, SPAWN_SCRIPT_CASTED_ON, client->GetPlayer(), entity_command->command.c_str()); + ret = true; + } + if (!ret) { + PacketStruct* packet = configReader.getStruct("WS_FinishCastSpell", client->GetVersion()); + if (packet) { + packet->setMediumStringByName("spell_name", entity_command->name.c_str()); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + SendSpellBookUpdate(client); + client->GetPlayer()->GetZone()->CallSpawnScript(target, SPAWN_SCRIPT_CASTED_ON, client->GetPlayer(), entity_command->command.c_str()); + ret = true; + } + } + if (ret) { + EQ2_16BitString command; + command.data = entity_command->command; + command.size = entity_command->command.length(); + int32 handler = commands.GetCommandHandler(command.data.c_str()); + if (handler != 0xFFFFFFFF && handler < 999) + commands.Process(handler, &command, client, target); + } + } + return ret; +} + +void SpellProcess::Interrupted(Entity* caster, Spawn* interruptor, int16 error_code, bool cancel, bool from_movement) +{ + if(caster) + { + LogWrite(SPELL__DEBUG, 0, "Spell", "'%s' is Interrupting spell of '%s'...", interruptor ? interruptor->GetName() : "unknown", caster->GetName()); + LuaSpell* spell = GetLuaSpell(caster); + + if (spell && ((from_movement && !spell->spell->GetSpellData()->cast_while_moving) || (!from_movement && spell->spell->GetSpellData()->interruptable) || + cancel)) + { + InterruptStruct* interrupt = new InterruptStruct; + interrupt->interrupted = caster; + interrupt->spell = spell; + interrupt->error_code = error_code; + spell->interrupted = true; + interrupt_list.Add(interrupt); + + Client* client = 0; + if(interruptor && interruptor->IsPlayer()) + { + client = ((Player*)interruptor)->GetClient(); + if(client) { + client->Message(CHANNEL_SPELLS_OTHER, "You interrupt %s's ability to cast!", caster->GetName()); + } + } + + } + } +} + +void SpellProcess::RemoveSpellTimersFromSpawn(Spawn* spawn, bool remove_all, bool delete_recast, bool call_expire_function, bool lock_spell_process){ + if(lock_spell_process) + MSpellProcess.lock_shared(); + + int32 i = 0; + if(cast_timers.size() > 0){ + CastTimer* cast_timer = 0; + MutexList::iterator itr = cast_timers.begin(); + while(itr.Next()){ + cast_timer = itr->value; + if(cast_timer && cast_timer->spell && cast_timer->spell->caster == spawn){ + cast_timer->spell->caster = 0; + cast_timer->delete_timer = true; + cast_timer->spell = nullptr; + } + } + } + if(remove_all){ + LuaSpell* spell = 0; + MutexList::iterator itr = active_spells.begin(); + while(itr.Next()){ + spell = itr->value; + if (!spell) + continue; + if(spell->caster == spawn && call_expire_function){ + DeleteCasterSpell(spell, "expired", remove_all, nullptr, false, lock_spell_process); + continue; + } + if (spell->spell->GetSpellData()->persist_through_death) + continue; + + bool foundMatch = false; + + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (i = 0; i < spell->targets.size(); i++){ + if (spawn->GetID() == spell->targets.at(i)){ + foundMatch = true; + break; + } + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + if(foundMatch) { + if (spawn->IsEntity()) + ((Entity*)spawn)->RemoveSpellEffect(spell); + RemoveTargetFromSpell(spell, spawn, remove_all); + } + } + if(recast_timers.size() > 0 && delete_recast){ + RecastTimer* recast_timer = 0; + MutexList::iterator itr = recast_timers.begin(); + while(itr.Next()){ + recast_timer = itr->value; + if(recast_timer && recast_timer->caster == spawn){ + safe_delete(recast_timer->timer); + recast_timers.Remove(recast_timer, true); + } + } + } + if(spell_que.size() > 0 && spawn->IsEntity()){ + spell_que.erase((Entity*)spawn); + } + if(interrupt_list.size() > 0){ + InterruptStruct* interrupt = 0; + MutexList::iterator itr = interrupt_list.begin(); + while(itr.Next()){ + interrupt = itr->value; + if(interrupt && interrupt->interrupted == spawn){ + interrupt_list.Remove(interrupt, true); + } + } + } + } + + if(lock_spell_process) + MSpellProcess.unlock_shared(); +} + +void SpellProcess::GetSpellTargets(LuaSpell* luaspell) +{ + if (luaspell && luaspell->spell && luaspell->caster && luaspell->spell->GetSpellData()->max_aoe_targets == 0) + { + int8 target_type = luaspell->spell->GetSpellData()->target_type; + int8 spell_type = luaspell->spell->GetSpellData()->spell_type; + Spawn* caster = luaspell->caster; + Spawn* target = caster->GetZone()->GetSpawnByID(luaspell->initial_target); + SpellData* data = luaspell->spell->GetSpellData(); + bool implied = false; + Spawn* secondary_target = nullptr; + + //implied target check -- only use this for players + if (target && (target_type == SPELL_TARGET_ENEMY || target_type == SPELL_TARGET_ENEMY_CORPSE || target_type == SPELL_TARGET_GROUP_CORPSE || target_type == SPELL_TARGET_OTHER_GROUP_AE)) + { + if (caster->IsPlayer() && target->HasTarget()) + { + secondary_target = target->GetTarget(); + // check if spell is friendly + if (data->friendly_spell) { + //if target is NPC (and not a bot) on friendly spell, check to see if target is friendly + if (target->IsNPC() && !((Entity*)target)->GetInfoStruct()->get_friendly_target_npc() && !target->IsBot()) { + if (!target->IsPet() || (target->IsPet() && ((NPC*)target)->GetOwner() && ((NPC*)target)->GetOwner()->IsNPC())) { + if (secondary_target && secondary_target->IsPlayer()) { + target = secondary_target; + luaspell->initial_target = target->GetID(); + luaspell->initial_target_char_id = (target && target->IsPlayer()) ? ((Player*)target)->GetCharacterID() : 0; + AddLuaSpellTarget(luaspell, target->GetID()); + } + else if (secondary_target && secondary_target->IsPet() && ((NPC*)secondary_target)->GetOwner() && ((NPC*)secondary_target)->GetOwner()->IsPlayer()) + implied = true; + } + else if (target->IsPet() && ((Entity*)target)->GetOwner()->IsPlayer()) + { + luaspell->initial_target = target->GetID(); + luaspell->initial_target_char_id = (target && target->IsPlayer()) ? ((Player*)target)->GetCharacterID() : 0; + AddLuaSpellTarget(luaspell, target->GetID()); + } + } + } // if spell is not friendly + else { // check if there is an implied target for this non-friendly spell + if (target->IsPlayer() || (target->IsPet() && ((NPC*)target)->GetOwner() && ((NPC*)target)->GetOwner()->IsPlayer())) { + if (secondary_target && secondary_target->IsNPC()) { + if (!secondary_target->IsPet() || (secondary_target->IsPet() && ((NPC*)secondary_target)->GetOwner() && ((NPC*)secondary_target)->GetOwner()->IsNPC())) { + implied = true; + } + } + else if (target->IsPlayer() && ((Entity*)caster)->AttackAllowed((Entity*)target)) + { + secondary_target = target; + implied = true; + luaspell->initial_target = target->GetID(); + luaspell->initial_target_char_id = (target && target->IsPlayer()) ? ((Player*)target)->GetCharacterID() : 0; + AddLuaSpellTarget(luaspell, target->GetID()); + GetPlayerGroupTargets((Player*)target, caster, luaspell); + + } + else if (secondary_target && target->IsPlayer() && ((Entity*)caster)->AttackAllowed((Entity*)secondary_target)) + { + implied = true; + luaspell->initial_target = secondary_target->GetID(); + luaspell->initial_target_char_id = (secondary_target && secondary_target->IsPlayer()) ? ((Player*)secondary_target)->GetCharacterID() : 0; + AddLuaSpellTarget(luaspell, secondary_target->GetID()); + GetPlayerGroupTargets((Player*)secondary_target, caster, luaspell); + } + } + } // end friendly spell check + } + else if (caster->IsPlayer()) { + if (data->friendly_spell) { + if (target->IsNPC() && !target->IsBot()) { + if (!((Entity*)target)->GetInfoStruct()->get_friendly_target_npc() && (!target->IsPet() || (target->IsPet() && ((NPC*)target)->GetOwner() && ((NPC*)target)->GetOwner()->IsNPC()))) { + target = caster; + luaspell->initial_target = caster->GetID(); + luaspell->initial_target_char_id = (caster && caster->IsPlayer()) ? ((Player*)caster)->GetCharacterID() : 0; + } + } + } + else + { + if (target->IsPlayer() && ((Entity*)caster)->AttackAllowed((Entity*)target)) { + luaspell->initial_target = target->GetID(); + luaspell->initial_target_char_id = (target && target->IsPlayer()) ? ((Player*)target)->GetCharacterID() : 0; + AddLuaSpellTarget(luaspell, target->GetID()); + } + } + } + } + else if (target_type == SPELL_TARGET_GROUP_AE || spell_type == SPELL_TYPE_ALLGROUPTARGETS || target_type == SPELL_TARGET_RAID_AE) { + + // player handling + if (target) + { + if (data->icon_backdrop == 316 || data->icon_backdrop == 312) // using TARGET backdrop icon + { + // PLAYER LOGIC: + if ((data->friendly_spell && (target->IsPlayer() && luaspell->caster->IsPlayer() && target != luaspell->caster && ((Player*)target)->GetGroupMemberInfo() != NULL && ((Player*)luaspell->caster)->GetGroupMemberInfo() != NULL + && ((Player*)target)->GetGroupMemberInfo()->group_id == ((Player*)luaspell->caster)->GetGroupMemberInfo()->group_id)) + || (!data->friendly_spell && (target->IsPlayer() && luaspell->caster->IsPlayer() && target != luaspell->caster && ((Player*)target)->GetGroupMemberInfo() != NULL))) + { + GetPlayerGroupTargets((Player*)target, caster, luaspell, true, false); + } + //TODO: NEED RAID SUPPORT + + // NPC LOGIC: + else if (target->IsNPC()) + { + // Check to see if the npc is a spawn group by getting the group and checikng if valid + vector* group = ((NPC*)target)->GetSpawnGroup(); + if (group) + { + vector::iterator itr; + + // iterate through spawn group members + for (itr = group->begin(); itr != group->end(); itr++) + { + Spawn* group_member = *itr; + + // if NPC group member is (still) an NPC (wtf?) and is alive, send the NPC group member back as a successful target of non-friendly spell group_member->Alive() + if (group_member->GetZone() == caster->GetZone() && + group_member->IsNPC() && (data->friendly_spell || ((Entity*)caster)->AttackAllowed((Entity*)group_member)) && group_member->Alive() && !((Entity*)group_member)->IsAOEImmune() && (!((Entity*)group_member)->IsMezzed() || group_member == target)) + AddLuaSpellTarget(luaspell, group_member->GetID()); + + // note: this should generate some hate towards the caster + } + } // end is spawngroup + else if(data->friendly_spell || ((Entity*)caster)->AttackAllowed((Entity*)target)) + AddLuaSpellTarget(luaspell, target->GetID()); // return single target NPC for non-friendly spell + } + else if(data->friendly_spell) + { + // add self + target = NULL; + AddSelfAndPet(luaspell, caster); + + luaspell->initial_target = caster->GetID(); + luaspell->initial_target_char_id = (caster && caster->IsPlayer()) ? ((Player*)caster)->GetCharacterID() : 0; + } + else if(target && target->IsPlayer()) + { + if(!GetPlayerGroupTargets((Player*)target, caster, luaspell, true, false) && ((Entity*)caster)->AttackAllowed((Entity*)target)) + AddSelfAndPet(luaspell, target); + } + } + else // default self cast for group/raid AE + { + target = caster; + luaspell->initial_target = caster->GetID(); + luaspell->initial_target_char_id = (caster && caster->IsPlayer()) ? ((Player*)caster)->GetCharacterID() : 0; + } + // spell target versus self cast + } + else if (data->icon_backdrop != 316) // default self cast for group/raid AE and not using TARGET backdrop icon + { + target = caster; + luaspell->initial_target = caster->GetID(); + luaspell->initial_target_char_id = (caster && caster->IsPlayer()) ? ((Player*)caster)->GetCharacterID() : 0; + } + } + else if (target_type == SPELL_TARGET_SELF){ + target = caster; + luaspell->initial_target = caster->GetID(); + luaspell->initial_target_char_id = (caster && caster->IsPlayer()) ? ((Player*)caster)->GetCharacterID() : 0; + } + + //if using implied target, target = the implied target + if (implied) + { + target = secondary_target; + luaspell->initial_target = secondary_target->GetID(); + luaspell->initial_target_char_id = (secondary_target && secondary_target->IsPlayer()) ? ((Player*)secondary_target)->GetCharacterID() : 0; + } + + luaspell->MSpellTargets.writelock(__FUNCTION__, __LINE__); + // Group AE type NOTE: Add support for RAID AE to affect raid members once raids have been completed + if (target_type == SPELL_TARGET_GROUP_AE || spell_type == SPELL_TYPE_ALLGROUPTARGETS || target_type == SPELL_TARGET_RAID_AE) + { + if (data->icon_backdrop == 316) // single target in a group/raid + { + if (target) + AddLuaSpellTarget(luaspell, target->GetID(), false); + } + // is friendly + else if (data->friendly_spell) + { + // caster is an Entity + if (luaspell->caster->IsEntity()) + { + // entity caster is in a player group + if (((Entity*)caster)->GetGroupMemberInfo()) + { + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + + // get group members + PlayerGroup* group = world.GetGroupManager()->GetGroup(((Entity*)caster)->GetGroupMemberInfo()->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + deque::iterator itr; + + // iterate through list of group members + for (itr = members->begin(); itr != members->end(); itr++) + { + + // get group member player info + Entity* group_member = (*itr)->member; + + if(!group_member){ + continue; + } + + LogWrite(SPELL__DEBUG, 0, "Player", "%s is group member for spell %s", group_member->GetName(), luaspell->spell->GetName()); + // if the group member is in the casters zone, and is alive + + if( group_member->Alive()) + { + if(group_member->GetZone() != caster->GetZone()) + { + SpellProcess::AddSelfAndPetToCharTargets(luaspell, group_member); + } + else if (group_member->GetZone() == luaspell->caster->GetZone()) { + AddLuaSpellTarget(luaspell, group_member->GetID(), false); + if (group_member->HasPet()) { + Entity* pet = group_member->GetPet(); + if (!pet) + pet = group_member->GetCharmedPet(); + if (pet) + AddLuaSpellTarget(luaspell, pet->GetID(), false); + + LogWrite(SPELL__DEBUG, 0, "Player", "%s added a pet %s (%u) for spell %s", group_member->GetName(), pet ? pet->GetName() : "", pet ? pet->GetID() : 0, luaspell->spell->GetName()); + } + } + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } + else + AddSelfAndPet(luaspell, caster); // else caster is not in a group, thus alone + } + else if (caster->IsNPC()) // caster is NOT a player + { + // caster is NPC and in a spawn group with other NPCs + vector* group = ((NPC*)caster)->GetSpawnGroup(); + if (group) + { + vector::iterator itr; + + for (itr = group->begin(); itr != group->end(); itr++) + { + Spawn* group_member = *itr; + + if (group_member->IsNPC() && group_member->Alive() && ((Entity*)caster)->AttackAllowed(((Entity*)group_member))){ + AddLuaSpellTarget(luaspell, group_member->GetID(), false); + if (((Entity*)group_member)->HasPet()){ + Entity* pet = ((Entity*)group_member)->GetPet(); + if (pet) + AddLuaSpellTarget(luaspell, pet->GetID(), false); + pet = ((Entity*)group_member)->GetCharmedPet(); + if (pet) + AddLuaSpellTarget(luaspell, pet->GetID(), false); + } + } + } + } + else + AddSelfAndPet(luaspell, caster); + + safe_delete(group); + } // end is player + } // end is friendly + } // end is Group AE + + else if (target_type == SPELL_TARGET_SELF && caster) + AddLuaSpellTarget(luaspell, caster->GetID(), false); // if spell is SELF, return caster + + else if (target_type == SPELL_TARGET_CASTER_PET && caster && caster->IsEntity() && ((Entity*)caster)->HasPet()) { + AddSelfAndPet(luaspell, caster, true); + } + + else if (target_type == SPELL_TARGET_ENEMY && target && target->Alive()) // if target is enemy, and is alive + { + // if friendly spell + if (data->friendly_spell > 0) + { + // if caster is a player + if (caster->IsPlayer()) + { + // if spell can affect raid, only group members or is a group spell + if (data->can_effect_raid > 0 || data->affect_only_group_members > 0 || data->group_spell > 0) + { + // if caster is in a group, and target is a player and targeted player is a group member + if (((Player*)caster)->GetGroupMemberInfo() && (target->IsPlayer() || target->IsBot() || target->IsPet()) && ((Player*)caster)->IsGroupMember((Entity*)target)) + AddLuaSpellTarget(luaspell, target->GetID(), false); // return the target + else + AddLuaSpellTarget(luaspell, caster->GetID(), false); // else return the caster + } + else if (target->IsPlayer() || target->IsBot() || (target->IsNPC() && ((Entity*)target)->GetInfoStruct()->get_friendly_target_npc())) // else it is not raid, group only or group spell + AddLuaSpellTarget(luaspell, target->GetID(), false); // return target for single spell + else if ((luaspell->targets.size() < 1) || (!target->IsPet() || (((Entity*)target)->GetOwner() && !((Entity*)target)->GetOwner()->IsPlayer()))) + AddLuaSpellTarget(luaspell, caster->GetID(), false); // and if no target, cast on self + } + else if (caster->IsNPC()) // caster is an NPC + { + // if NPC's spell can affect raid, only group members or is a group spell + if (data->can_effect_raid > 0 || data->affect_only_group_members > 0 || data->group_spell > 0) + { + if (caster->IsBot() && (target->IsBot() || target->IsPlayer())) { + GroupMemberInfo* gmi = ((Entity*)caster)->GetGroupMemberInfo(); + if (gmi && target->IsEntity() && world.GetGroupManager()->IsInGroup(gmi->group_id, (Entity*)target)) { + AddLuaSpellTarget(luaspell, target->GetID(), false); // return the target + } + else + AddSelfAndPet(luaspell, caster); + } + // if NPC caster is in a group, and target is a player and targeted player is a group member + else if (((NPC*)caster)->HasSpawnGroup() && target->IsNPC() && ((NPC*)caster)->IsInSpawnGroup((NPC*)target)) + AddLuaSpellTarget(luaspell, target->GetID(), false); // return the target + else + AddSelfAndPet(luaspell, caster); + } + else if (target->IsNPC()) + AddLuaSpellTarget(luaspell, target->GetID(), false); // return target for single spell + else { + if (caster->IsBot() && (target->IsBot() || target->IsPlayer())) + AddLuaSpellTarget(luaspell, target->GetID(), false); + else + AddLuaSpellTarget(luaspell, caster->GetID(), false); // and if no target, cast on self + } + } // end is player + } // end is friendly + + else if (target && (data->group_spell > 0 || data->icon_backdrop == 312)) // is not friendly, but is a group spell, icon_backdrop 312 is green (encounter AE) + { + // target is non-player + if (target->IsNPC()) + { + // Check to see if the npc is a spawn group by getting the group and checikng if valid + vector* group = ((NPC*)target)->GetSpawnGroup(); + if (group) + { + vector::iterator itr; + + // iterate through spawn group members + for (itr = group->begin(); itr != group->end(); itr++) + { + Spawn* group_member = *itr; + + // if NPC group member is (still) an NPC (wtf?) and is alive, send the NPC group member back as a successful target of non-friendly spell group_member->Alive() + if (group_member->GetZone() == caster->GetZone() && + group_member->IsNPC() && group_member->Alive() && ((Entity*)caster)->AttackAllowed((Entity*)group_member) && !((Entity*)group_member)->IsAOEImmune() && (!((Entity*)group_member)->IsMezzed() || group_member == target)) + AddLuaSpellTarget(luaspell, group_member->GetID(), false); + + // note: this should generate some hate towards the caster + } + } // end is spawngroup + else + AddLuaSpellTarget(luaspell, target->GetID(), false); // return single target NPC for non-friendly spell + + safe_delete(group); + } // end is NPC + + else if (target->IsPlayer() && caster->IsNPC()) // the NPC is casting on a player + { + // player is in a group + if (((Player*)target)->GetGroupMemberInfo()) + { + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + + deque::iterator itr; + PlayerGroup* group = world.GetGroupManager()->GetGroup(((Player*)target)->GetGroupMemberInfo()->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + + // iterate through players group members + for (itr = members->begin(); itr != members->end(); itr++) + { + Entity* group_member = (*itr)->member; + + // if the group member is in the same zone as caster, and group member is alive, and group member is within distance + if (group_member && group_member->GetZone() == caster->GetZone() && group_member->Alive() && caster->GetDistance(group_member) <= data->range + && (group_member == target || !group_member->IsAOEImmune())) + AddLuaSpellTarget(luaspell, group_member->GetID(), false); // add as target + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } + else + AddLuaSpellTarget(luaspell, target->GetID(), false); // player not in group + } // end is caster player or npc + } + else if (target) + AddLuaSpellTarget(luaspell, target->GetID(), false); // is not friendly nor a group spell + } + //Rez spells + else if(target && target_type == SPELL_TARGET_ENEMY_CORPSE){ + //is friendly + if(data->friendly_spell){ + //target is player + if(target->IsPlayer()){ + AddLuaSpellTarget(luaspell, target->GetID(), false); + } + } + } + else if(target_type == SPELL_TARGET_GROUP_CORPSE){ + //is friendly + if(data->friendly_spell){ + //target is player + if(target && target->IsPlayer()){ + //if target has group + if(((Player*)target)->GetGroupMemberInfo()) { + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + + deque::iterator itr; + PlayerGroup* group = world.GetGroupManager()->GetGroup(((Player*)target)->GetGroupMemberInfo()->group_id); + + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + + Entity* group_member = 0; + for (itr = members->begin(); itr != members->end(); itr++) { + group_member = (*itr)->member; + //Check if group member is in the same zone in range of the spell and dead + if (group_member && group_member->GetZone() == target->GetZone() && !group_member->Alive() && target->GetDistance(group_member) <= data->radius) { + AddLuaSpellTarget(luaspell, group_member->GetID(), false); + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } + else + AddLuaSpellTarget(luaspell, target->GetID(), false); + } + } + } + luaspell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__); + } + + if (luaspell && luaspell->targets.size() > 20) + LogWrite(SPELL__WARNING, 0, "Spell", "Warning in %s: Size of targets array is %u", __FUNCTION__, luaspell->targets.size()); +} + +bool SpellProcess::GetPlayerGroupTargets(Player* target, Spawn* caster, LuaSpell* luaspell, bool bypassSpellChecks, bool bypassRangeChecks) +{ + if (bypassSpellChecks || luaspell->spell->GetSpellData()->group_spell > 0 || luaspell->spell->GetSpellData()->icon_backdrop == 312) + { + if (((Player*)target)->GetGroupMemberInfo()) + { + PlayerGroup* group = world.GetGroupManager()->GetGroup(((Player*)target)->GetGroupMemberInfo()->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + deque::iterator itr; + GroupMemberInfo* info = 0; + + for (itr = members->begin(); itr != members->end(); itr++) { + info = *itr; + if (info == ((Player*)target)->GetGroupMemberInfo()) + continue; + else if (info && info->client && + info->client->GetPlayer()->GetZone() == ((Player*)target)->GetZone() && info->client->GetPlayer()->Alive() + && (bypassRangeChecks || caster->GetDistance((Entity*)info->client->GetPlayer()) <= luaspell->spell->GetSpellData()->range)) + { + AddSelfAndPet(luaspell, info->client->GetPlayer()); + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + + return true; + } + } + } + + return false; +} + +void SpellProcess::GetSpellTargetsTrueAOE(LuaSpell* luaspell) { + if (luaspell && luaspell->caster && luaspell->spell && luaspell->spell->GetSpellData()->max_aoe_targets > 0) { + if (luaspell->caster->HasTarget() && luaspell->caster->GetTarget() != luaspell->caster){ + //Check if the caster has an implied target + if (luaspell->caster->GetDistance(luaspell->caster->GetTarget()) <= luaspell->spell->GetSpellData()->radius) + { + luaspell->initial_target = luaspell->caster->GetTarget()->GetID(); + luaspell->initial_target_char_id = (luaspell->caster->GetTarget() && luaspell->caster->GetTarget()->IsPlayer()) ? ((Player*)luaspell->caster->GetTarget())->GetCharacterID() : 0; + } + } + int32 ignore_target = 0; + std::vector> spawns = luaspell->caster->GetZone()->GetAttackableSpawnsByDistance(luaspell->caster, luaspell->spell->GetSpellData()->radius); + luaspell->MSpellTargets.writelock(__FUNCTION__, __LINE__); + int32 i = 0; + for (const auto& pair : spawns) { + if (i == 0){ + Spawn* spawn = luaspell->caster->GetZone()->GetSpawnByID(luaspell->initial_target); + if (spawn && luaspell->initial_target && luaspell->caster->GetID() != luaspell->initial_target && luaspell->caster->AttackAllowed((Entity*)spawn)){ + //this is the "Direct" target and aoe can't be avoided + AddLuaSpellTarget(luaspell, luaspell->initial_target, false); + ignore_target = luaspell->initial_target; + } + } + + i++; + + if (luaspell->targets.size() >= luaspell->spell->GetSpellData()->max_aoe_targets) + break; + + int32 target_id = pair.first; + Spawn* spawn = luaspell->caster->GetZone()->GetSpawnByID(target_id); + if(!spawn) { + LogWrite(SPELL__ERROR, 0, "Spell", "Error: Spell target is NULL! SpellProcess::ProcessSpell for Spell '%s' target id %u", (luaspell->spell != nullptr) ? luaspell->spell->GetName() : "Unknown", target_id); + } + //If we have already added this spawn, check the next spawn in the list + if (spawn && spawn->GetID() == ignore_target || (spawn->IsEntity() && !luaspell->caster->AttackAllowed((Entity*)spawn))){ + continue; + } + if (spawn){ + //If this spawn is immune to aoe, continue + if (((Entity*)spawn)->IsAOEImmune() || ((Entity*)spawn)->IsMezzed()) + continue; + AddLuaSpellTarget(luaspell, spawn->GetID(), false); + } + + if (luaspell->targets.size() >= luaspell->spell->GetSpellData()->max_aoe_targets) + break; + } + luaspell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__); + } + if (luaspell->targets.size() > 20) + LogWrite(SPELL__DEBUG, 0, "Spell", "Warning in SpellProcess::GetSpellTargetsTrueAOE Size of targets array is %u", luaspell->targets.size()); +} + +void SpellProcess::AddSpellScriptTimer(SpellScriptTimer* timer) { + MSpellScriptTimers.writelock(__FUNCTION__, __LINE__); + m_spellScriptList.push_back(timer); + MSpellScriptTimers.releasewritelock(__FUNCTION__, __LINE__); +} + +void SpellProcess::RemoveSpellScriptTimer(SpellScriptTimer* timer, bool locked) { + if (m_spellScriptList.size() == 0) + return; + + vector::iterator itr; + if(!locked) + MSpellScriptTimers.writelock(__FUNCTION__, __LINE__); + for (itr = m_spellScriptList.begin(); itr != m_spellScriptList.end(); itr++) { + if ((*itr) == timer) { + SpellScriptTimer* timer = *itr; + if ((*itr) && (*itr)->deleteWhenDone && lua_interface) { + lua_interface->AddPendingSpellDelete(timer->spell); + } + m_spellScriptList.erase(itr); + safe_delete(timer); + break; + } + } + + if(!locked) + MSpellScriptTimers.releasewritelock(__FUNCTION__, __LINE__); +} + +void SpellProcess::RemoveSpellScriptTimerBySpell(LuaSpell* spell, bool clearPendingDeletes) { + vector::iterator itr; + MSpellScriptTimers.writelock(__FUNCTION__, __LINE__); + if (lua_interface && clearPendingDeletes) + lua_interface->DeletePendingSpell(spell); + + if (m_spellScriptList.size() == 0) + { + MSpellScriptTimers.releasewritelock(__FUNCTION__, __LINE__); + return; + } + + for (itr = m_spellScriptList.begin(); itr != m_spellScriptList.end(); ) { + if ((*itr)->spell == spell) + { + vector::iterator cur = itr; + SpellScriptTimer* timer = *itr; + m_spellScriptList.erase(cur); + safe_delete(timer); + break; + } + else + itr++; + } + MSpellScriptTimers.releasewritelock(__FUNCTION__, __LINE__); +} + +void SpellProcess::CheckSpellScriptTimers() { + vector::iterator itr; + vector temp_list; + + MSpellScriptTimers.writelock(__FUNCTION__, __LINE__); + for (itr = m_spellScriptList.begin(); itr != m_spellScriptList.end(); itr++) { + if (Timer::GetCurrentTime2() >= (*itr)->time) { + temp_list.push_back((*itr)); + ProcessSpell((*itr)->spell, false, (*itr)->customFunction.c_str(), (*itr)); + } + } + + for (itr = temp_list.begin(); itr != temp_list.end(); itr++) { + RemoveSpellScriptTimer(*itr, true); + } + MSpellScriptTimers.releasewritelock(__FUNCTION__, __LINE__); +} + +bool SpellProcess::SpellScriptTimersHasSpell(LuaSpell* spell) { + bool ret = false; + vector::iterator itr; + + MSpellScriptTimers.readlock(__FUNCTION__, __LINE__); + for (itr = m_spellScriptList.begin(); itr != m_spellScriptList.end(); itr++) { + SpellScriptTimer* timer = *itr; + if (timer && timer->spell == spell) { + ret = true; + break; + } + } + MSpellScriptTimers.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +std::string SpellProcess::SpellScriptTimerCustomFunction(LuaSpell* spell) { + bool ret = false; + std::string val = string(""); + vector::iterator itr; + + MSpellScriptTimers.readlock(__FUNCTION__, __LINE__); + for (itr = m_spellScriptList.begin(); itr != m_spellScriptList.end(); itr++) { + SpellScriptTimer* timer = *itr; + if (timer && timer->spell == spell) { + val = string(timer->customFunction); + break; + } + } + MSpellScriptTimers.releasereadlock(__FUNCTION__, __LINE__); + + return val; +} + +void SpellProcess::ClearSpellScriptTimerList() { + vector::iterator itr; + MSpellScriptTimers.writelock(__FUNCTION__, __LINE__); + + for(itr = m_spellScriptList.begin(); itr != m_spellScriptList.end(); itr++) { + if ((*itr) && (*itr)->deleteWhenDone && lua_interface) + lua_interface->AddPendingSpellDelete((*itr)->spell); + + safe_delete((*itr)); + } + + m_spellScriptList.clear(); + MSpellScriptTimers.releasewritelock(__FUNCTION__, __LINE__); +} + +void SpellProcess::RemoveTargetFromSpell(LuaSpell* spell, Spawn* target, bool removeCaster){ + if (!spell || !target) + return; + + LogWrite(SPELL__DEBUG, 0, "Spell", "%s RemoveTargetFromSpell %s (%u).", spell->spell->GetName(), target->GetName(), target->GetID()); + MRemoveTargetList.writelock(__FUNCTION__, __LINE__); + + if(removeCaster && spell->caster && spell->caster == target) + spell->caster = nullptr; + + if (!remove_target_list[spell]) + remove_target_list[spell] = new vector; + remove_target_list[spell]->push_back(target->GetID()); + + MRemoveTargetList.releasewritelock(__FUNCTION__, __LINE__); +} + +void SpellProcess::CheckRemoveTargetFromSpell(LuaSpell* spell, bool allow_delete, bool removing_all_spells){ + if (!spell) + return; + + MRemoveTargetList.writelock(__FUNCTION__, __LINE__); + if (remove_target_list.size() > 0){ + map*>::iterator remove_itr; + vector::iterator remove_target_itr; + vector::iterator target_itr; + vector* targets; + vector* remove_targets = 0; + Spawn* remove_spawn = 0; + bool should_delete = false; + + for (remove_itr = remove_target_list.begin(); remove_itr != remove_target_list.end(); remove_itr++){ + if (remove_itr->first == spell){ + targets = &spell->targets; + remove_targets = remove_itr->second; + if (remove_targets && targets){ + spell->MSpellTargets.writelock(__FUNCTION__, __LINE__); + for (remove_target_itr = remove_targets->begin(); remove_target_itr != remove_targets->end(); remove_target_itr++){ + if(!spell->caster || !spell->caster->GetZone()) + continue; + + remove_spawn = spell->caster->GetZone()->GetSpawnByID((*remove_target_itr)); + if (remove_spawn) { + if(remove_spawn && remove_spawn->IsPlayer()) + { + multimap::iterator entries; + while((entries = spell->char_id_targets.find(((Player*)remove_spawn)->GetCharacterID())) != spell->char_id_targets.end()) + { + spell->char_id_targets.erase(entries); + } + } + for (target_itr = targets->begin(); target_itr != targets->end(); target_itr++) { + if (remove_spawn->GetID() == (*target_itr)) { + ((Entity*)remove_spawn)->RemoveProc(0, spell); + ((Entity*)remove_spawn)->RemoveMaintainedSpell(spell); + LogWrite(SPELL__DEBUG, 0, "Spell", "%s CheckRemoveTargetFromSpell %s (%u).", spell->spell->GetName(), remove_spawn->GetName(), remove_spawn->GetID()); + targets->erase(target_itr); + if (remove_spawn->IsEntity()) + { + if(!removing_all_spells) + { + if(remove_spawn->IsPlayer()) + spell->char_id_targets.insert(make_pair(((Player*)remove_spawn)->GetCharacterID(),0)); + else if(remove_spawn->IsPet() && ((Entity*)remove_spawn)->GetOwner() && ((Entity*)remove_spawn)->GetOwner()->IsPlayer()) + { + Entity* pet = (Entity*)remove_spawn; + Player* ownerPlayer = (Player*)pet->GetOwner(); + spell->char_id_targets.insert(make_pair(ownerPlayer->GetCharacterID(),pet->GetPetType())); + } + } + ((Entity*)remove_spawn)->RemoveEffectsFromLuaSpell(spell); + } + break; + } + } + if (targets->size() == 0 && spell->char_id_targets.size() == 0 && allow_delete) { + spell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__); + should_delete = true; + break; + } + } + } + spell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__); + } + break; + } + } + remove_target_list.erase(spell); + if (remove_targets) + remove_targets->clear(); + safe_delete(remove_targets); + MRemoveTargetList.releasewritelock(__FUNCTION__, __LINE__); + if (should_delete) + DeleteCasterSpell(spell, "purged"); + } + else { + MRemoveTargetList.releasewritelock(__FUNCTION__, __LINE__); + } +} + +bool SpellProcess::AddHO(Client* client, HeroicOP* ho) { + bool ret = true; + + if (client && ho) { + MSoloHO.writelock(__FUNCTION__, __LINE__); + if (m_soloHO.count(client) > 0) { + if (m_soloHO[client]->GetWheel()) { + ret = false; + } + else { + safe_delete(m_soloHO[client]); + m_soloHO[client] = ho; + } + } + else + m_soloHO[client] = ho; + MSoloHO.releasewritelock(__FUNCTION__, __LINE__); + } + + return ret; +} + +bool SpellProcess::AddHO(int32 group_id, HeroicOP* ho) { + bool ret = true; + + if (group_id > 0 && ho) { + MGroupHO.writelock(__FUNCTION__, __LINE__); + if (m_groupHO.count(group_id) > 0) { + if (m_groupHO[group_id]->GetWheel()) { + ret = false; + } + else { + safe_delete(m_groupHO[group_id]); + m_groupHO[group_id] = ho; + } + } + else + m_groupHO[group_id] = ho; + MGroupHO.releasewritelock(__FUNCTION__, __LINE__); + } + + return ret; +} + +void SpellProcess::KillHOBySpawnID(int32 spawn_id) { + // Check solo HO's + MSoloHO.writelock(__FUNCTION__, __LINE__); + map::iterator itr = m_soloHO.begin(); + map::iterator delete_itr; + while (itr != m_soloHO.end()) { + if (itr->second->GetTarget() == spawn_id) { + itr->second->SetComplete(1); + ClientPacketFunctions::SendHeroicOPUpdate(itr->first, itr->second); + delete_itr = itr; + safe_delete(itr->second); + itr++; + m_soloHO.erase(delete_itr); + } + else + itr++; + } + MSoloHO.releasewritelock(__FUNCTION__, __LINE__); + + // Check Group HO's + MGroupHO.writelock(__FUNCTION__, __LINE__); + map::iterator itr2 = m_groupHO.begin(); + map::iterator delete_itr2; + while (itr2 != m_groupHO.end()) { + if (itr2->second->GetTarget() == spawn_id) { + itr2->second->SetComplete(1); + + world.GetGroupManager()->GroupLock(__FUNCTION__ , __LINE__); + deque::iterator itr3; + PlayerGroup* group = world.GetGroupManager()->GetGroup(itr2->first); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + for (itr3 = members->begin(); itr3 != members->end(); itr3++) { + if ((*itr3)->client) + ClientPacketFunctions::SendHeroicOPUpdate((*itr3)->client, itr2->second); + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + + delete_itr2 = itr2; + safe_delete(itr2->second); + itr2++; + m_groupHO.erase(delete_itr2); + } + else + itr2++; + } + MGroupHO.releasewritelock(__FUNCTION__, __LINE__); +} + +void SpellProcess::AddSpellCancel(LuaSpell* spell){ + MSpellCancelList.writelock(__FUNCTION__, __LINE__); + SpellCancelList.push_back(spell); + MSpellCancelList.releasewritelock(__FUNCTION__, __LINE__); +} + +void SpellProcess::DeleteSpell(LuaSpell* spell) +{ + RemoveSpellFromQueue(spell->spell, spell->caster); + + if (spell->spell->IsCopiedSpell()) + { + lua_interface->RemoveCustomSpell(spell->spell->GetSpellID()); + safe_delete(spell->spell); + } + + lua_interface->SetLuaUserDataStale(spell); + lua_interface->RemoveCurrentSpell(spell->state, true); + + DeleteActiveSpell(spell); +} + +void SpellProcess::SpellCannotStack(ZoneServer* zone, Client* client, Entity* caster, LuaSpell* lua_spell, LuaSpell* conflictSpell) +{ + LogWrite(SPELL__DEBUG, 1, "Spell", "%s cannot stack spell %s, conflicts with %s.", caster->GetName(), lua_spell->spell->GetName(), conflictSpell->spell->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_TAKE_EFFECT_MOREPOWERFUL); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); +} + +void SpellProcess::AddActiveSpell(LuaSpell* spell) +{ + if(!active_spells.count(spell)) + active_spells.Add(spell); +} + +void SpellProcess::AddSelfAndPet(LuaSpell* spell, Spawn* self, bool onlyPet) +{ + if(!onlyPet) + AddLuaSpellTarget(spell, self->GetID(), false); + + if(self->IsEntity() && ((Entity*)self)->HasPet() && ((Entity*)self)->GetPet()) + AddLuaSpellTarget(spell, ((Entity*)self)->GetPet()->GetID(), false); + if(self->IsEntity() && ((Entity*)self)->HasPet() && ((Entity*)self)->GetCharmedPet()) + AddLuaSpellTarget(spell, ((Entity*)self)->GetCharmedPet()->GetID(), false); + if(!onlyPet && self->IsEntity() && ((Entity*)self)->IsPet() && ((Entity*)self)->GetOwner()) + AddLuaSpellTarget(spell, ((Entity*)self)->GetOwner()->GetID(), false); +} + +void SpellProcess::AddSelfAndPetToCharTargets(LuaSpell* spell, Spawn* caster, bool onlyPet) +{ + if(!caster->IsPlayer()) + return; + + Player* player = ((Player*)caster); + int32 charID = player->GetCharacterID(); + + if(player->HasPet() && player->GetPet()) + spell->char_id_targets.insert(make_pair(charID, player->GetPet()->GetPetType())); + if(player->HasPet() && player->GetCharmedPet()) + spell->char_id_targets.insert(make_pair(charID, player->GetPet()->GetPetType())); + if(!onlyPet) + spell->char_id_targets.insert(make_pair(charID, 0x00)); +} + +void SpellProcess::DeleteActiveSpell(LuaSpell* spell) { + active_spells.Remove(spell, true); +} + +bool SpellProcess::AddLuaSpellTarget(LuaSpell* lua_spell, int32 id, bool lock_spell_targets) { + bool ret = false; + if(lock_spell_targets) + lua_spell->MSpellTargets.writelock(__FUNCTION__, __LINE__); + + if(std::find(lua_spell->targets.begin(), lua_spell->targets.end(), id) != lua_spell->targets.end()) { + ret = true; + } + else if(std::find(lua_spell->removed_targets.begin(), lua_spell->removed_targets.end(), id) == lua_spell->removed_targets.end()) { + lua_spell->targets.push_back(id); + ret = true; + } + + if(lock_spell_targets) + lua_spell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__); + + return ret; +} \ No newline at end of file diff --git a/source/WorldServer/SpellProcess.h b/source/WorldServer/SpellProcess.h new file mode 100644 index 0000000..cb398be --- /dev/null +++ b/source/WorldServer/SpellProcess.h @@ -0,0 +1,423 @@ +/* + 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 . +*/ +#ifndef __EQ2_SPELL_PROCESS__ +#define __EQ2_SPELL_PROCESS__ +#include +#include + +#include "client.h" +#include "Spells.h" +#include "zoneserver.h" +#include "LuaInterface.h" +#include "MutexMap.h" +#include "MutexList.h" +#include "World.h" +#include "HeroicOp/HeroicOp.h" + +#define MODIFY_HEALTH 1 +#define MODIFY_FOCUS 2 +#define MODIFY_DEFENSE 3 +#define MODIFY_POWER 4 +#define MODIFY_SPEED 5 +#define MODIFY_INT 6 +#define MODIFY_WIS 7 +#define MODIFY_STR 8 +#define MODIFY_AGI 9 +#define MODIFY_STA 10 +#define MODIFY_COLD_RESIST 11 +#define MODIFY_HEAT_RESIST 12 +#define MODIFY_DISEASE_RESIST 13 +#define MODIFY_POISON_RESIST 14 +#define MODIFY_MAGIC_RESIST 15 +#define MODIFY_MENTAL_RESIST 16 +#define MODIFY_DIVINE_RESIST 17 +#define MODIFY_ATTACK 18 +#define MODIFY_MITIGATION 19 +#define MODIFY_AVOIDANCE 20 +#define MODIFY_CONCENTRATION 21 +#define MODIFY_EXP 22 +#define MODIFY_FACTION 23 +#define CHANGE_SIZE 24 +#define CHANGE_RACE 25 +#define CHANGE_LOCATION 26 +#define CHANGE_ZONE 27 +#define CHANGE_PREFIX_TITLE 28 +#define CHANGE_DEITY 29 +#define CHANGE_LAST_NAME 30 +#define MODIFY_HASTE 31 +#define MODIFY_SKILL 32 +#define CHANGE_TARGET 33 +#define CHANGE_LEVEL 34 +#define MODIFY_SPELL_CAST_TIME 35 +#define MODIFY_SPELL_POWER_REQ 36 +#define MODIFY_SPELL_HEALTH_REQ 37 +#define MODIFY_SPELL_RECOVERY 38 +#define MODIFY_SPELL_RECAST_TIME 39 +#define MODIFY_SPELL_RADIUS 40 +#define MODIFY_SPELL_AOE_TARGETS 41 +#define MODIFY_SPELL_RANGE 42 +#define MODIFY_SPELL_DURATION 43 +#define MODIFY_SPELL_RESISTIBILITY 44 +#define MODIFY_DAMAGE 45 +#define MODIFY_DELAY 46 +#define MODIFY_TRADESKILL_EXP 47 +#define ADD_MOUNT 48 +#define REMOVE_MOUNT 49 +#define MODIFY_SPELL_CRIT_CHANCE 50 +#define MODIFY_CRIT_CHANCE 51 +#define SUMMON_ITEM 52 +#define MODIFY_JUMP 53 +#define MODIFY_FALL_SPEED 54 +#define INFLICT_DAMAGE 55 +#define ADD_DOT 56 +#define REMOVE_DOT 57 +#define HEAL_TARGET 58 +#define HEAL_AOE 59 +#define INFLICT_AOE_DAMAGE 60 +#define HEAL_GROUP_AOE 61 +#define ADD_AOE_DOT 62 +#define REMOVE_AOE_DOT 63 +#define ADD_HOT 64 +#define REMOVE_HOT 65 +#define MODIFY_AGGRO_RANGE 66 +#define BLIND_TARGET 67 +#define UNBLIND_TARGET 68 +#define KILL_TARGET 69 +#define RESURRECT_TARGET 70 +#define CHANGE_SUFFIX_TITLE 71 +#define SUMMON_PET 72 +#define MODIFY_HATE 73 +#define ADD_REACTIVE_HEAL 74 +#define MODIFY_POWER_REGEN 75 +#define MODIFY_HP_REGEN 76 +#define FEIGN_DEATH 77 +#define MODIFY_VISION 78 +#define INVISIBILITY 79 +#define CHARM_TARGET 80 +#define MODIFY_TRADESKILL_DURABILITY 81 +#define MODIFY_TRADESKILL_PROGRESS 82 + + +#define ACTIVE_SPELL_NORMAL 0 +#define ACTIVE_SPELL_ADD 1 +#define ACTIVE_SPELL_REMOVE 2 + +#define GET_VALUE_BAD_VALUE 0xFFFFFFFF +struct InterruptStruct{ + Spawn* interrupted; + LuaSpell* spell; + int16 error_code; +}; +struct CastTimer{ + Client* caster; + int32 target_id; + EntityCommand* entity_command; + LuaSpell* spell; + Timer* timer; + ZoneServer* zone; + bool delete_timer; + bool in_heroic_opp; +}; +struct CastSpell{ + Entity* caster; + Spawn* target; + int32 spell_id; + ZoneServer* zone; +}; +struct RecastTimer{ + Entity* caster; + Client* client; + Spell* spell; + Timer* timer; + int32 spell_id; + int32 linked_timer; + int32 type_group_spell_id; +}; + +/// Handles all spell casts for a zone, only 1 SpellProcess per zone +class SpellProcess{ +public: + SpellProcess(); + ~SpellProcess(); + + /// Remove dead pointers for casters when the Spawn is deconstructed + void RemoveCaster(Spawn* caster); + + /// Remove all spells from the SpellProcess + void RemoveAllSpells(bool reload_spells = false); + + /// Main loop, handles everything (interupts, cast time, recast, ...) + void Process(); + + /// Interrupts the caster (creates the InterruptStruct and adds it to a list) + /// Entity being interrupted + /// Spawn that interrupted the caster + /// The error code + /// Bool if the spell was cancelled not interrupted + void Interrupted(Entity* caster, Spawn* interruptor, int16 error_code, bool cancel = false, bool from_movement = false); + + /// Does all the checks and actually casts the spell + /// The current ZoneServer + /// The Spell to cast + /// The Entity casting the spell + /// The target(Spawn) of the spell + /// ??? not currently used + /// Is this a harvest spell? + void ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster, Spawn* target = 0, bool lock = true, bool harvest_spell = false, LuaSpell* customSpell = 0, int16 custom_cast_time = 0, bool in_heroic_opp = false); + + /// Cast an EntityCommand (right click menu) + /// The current ZoneServer + /// the EntityCommand to cast + /// The Entity casting the EntityCommand + /// The target(Spawn*) of the EntityCommand + /// ??? not currently used + void ProcessEntityCommand(ZoneServer* zone, EntityCommand* entity_command, Entity* caster, Spawn* target, bool lock = true, bool in_heroic_opp = false); + + /// Checks to see if the caster has enough power and takes it + /// LuaSpell to check and take power for (LuaSpell contains the caster) + /// True if caster had enough power + bool TakePower(LuaSpell* spell, int32 custom_power_req = 0); + + /// Check to see if the caster has enough power to cast the spell + /// LuaSpell to check (LuaSpell contains the caster) + /// True if the caster has enough power + bool CheckPower(LuaSpell* spell); + + /// Check to see if the caster has enough hp and take it + /// LuaSpell to check and take hp for (LuaSpell contains the caster) + /// True if the caster had enough hp + bool TakeHP(LuaSpell* spell, int32 custom_hp_req = 0); + + /// Check to see if the caster has enough hp to cast the spell + /// LuaSpell to check (LuaSpell contains the caster) + /// True if the caster had enough hp + bool CheckHP(LuaSpell* spell); + + /// Check to see if the caster has enough concentration available to cast the spell + /// LuaSpell to check (LuaSpell contains the caster) + /// True if the caster has enough concentration + bool CheckConcentration(LuaSpell* spell); + + bool CheckSavagery(LuaSpell* spell); + bool TakeSavagery(LuaSpell* spell); + bool CheckDissonance(LuaSpell* spell); + bool AddDissonance(LuaSpell* spell); + + /// Check to see if the caster has enough concentration available and add to the casters concentration + /// LuaSpell to check (LuaSpell contains the caster) + /// True of the caster had enough concentration + bool AddConcentration(LuaSpell* spell); + + /// Cast the spell, calls ProcessSpell for the given LuaSpell, as well as sends the messages for the spells and calls the casted on function in the targets spawn script + /// LuaSpell to cast + /// Is this a passive spell being cast? + /// True if the spell was casted + bool CastProcessedSpell(LuaSpell* spell, bool passive = false, bool in_heroic_opp = false); + + /// Cast the EntityCommand, calls ProcessEntityCommand for the given EntityCommand, as well as sends the messages for the command and calls the casted on function in the targets spawn script + /// EntityCommand to cast + /// Client casting the entity command + /// True if the spell was casted + bool CastProcessedEntityCommand(EntityCommand* entity_command, Client* client, Spawn* target, bool in_heroic_opp = false); + + /// Sends the start cast packet for the given client + /// LuaSpell being cast + /// The client casting the spell + void SendStartCast(LuaSpell* spell, Client* client); + + /// Send finish cast packet and take power/hp or add conc, also checks for quest updates + /// LuaSpell that just finished casting + /// Client that just finished casting, null if not a player + void SendFinishedCast(LuaSpell* spell, Client* client); + + /// Locks all the spells for the given client (shades them all gray) + /// Client to lock the spells for + void LockAllSpells(Client* client); + + /// Unlock all the spells for the given client + /// Client to unlock the spells for + void UnlockAllSpells(Client* client, Spell* exception = 0); + + /// Unlock a single spell for the given client + /// The client to unlock the spell for + /// The spell to unlock + void UnlockSpell(Client* client, Spell* spell); + + /// Remove the given spell for the given caster from the SpellProcess + /// The spawn to remove the spell for + /// The spell to remove + bool DeleteCasterSpell(Spawn* caster, Spell* spell, string reason = ""); + + /// Remove the given spell from the ZpellProcess + /// LuaSpell to remove + bool DeleteCasterSpell(LuaSpell* spell, string reason="", bool removing_all_spells = false, Spawn* remove_target = nullptr, bool zone_shutting_down = false, bool shared_lock_spell = true); + + /// Interrupt the spell + /// InterruptStruct that contains all the info + void CheckInterrupt(InterruptStruct* interrupt); + + /// Removes the timers for the given spawn + /// Spawn to remove the timers for + /// Remove all timers (cast, recast, active, queue, interrupted)? If false only cast timers are removed + void RemoveSpellTimersFromSpawn(Spawn* spawn, bool remove_all = false, bool delete_recast = false, bool call_expire_function = true, bool lock_spell_process = false); + + /// Sets the recast timer for the spell + /// The spell to set the recast for + /// The entity to set the recast for + /// Change the recast timer of the spell + /// Set the recast on all other spells the player has with the same timer + void CheckRecast(Spell* spell, Entity* caster, float timer_override = 0, bool check_linked_timers = true); + + /// Add a spell to the queue for the player + /// Spell to add + /// Entity's queue to add the spell to, if not a player function does nothing + void AddSpellToQueue(Spell* spell, Entity* caster); + + /// Removes a spell from the queue for the player + /// Spell to remove from the queue + /// Entity's queue to remove the spell from, if not a player function does nothing + void RemoveSpellFromQueue(Spell* spell, Entity* caster, bool send_update = true); + + /// Clear the queue, or clear only hostile spells from the queue + /// Entity to clear the queue for, if not player function does nothing + /// Set to true to only remove hostile spells, default is false + void RemoveSpellFromQueue(Entity* caster, bool hostile_only = false); + + /// Check the given enities queue for the spell, if found remove, if not found add + /// Spell to check for + /// Entity's queue to check, if not player function does nothing + bool CheckSpellQueue(Spell* spell, Entity* caster); + + /// Checks to see if the entity can cast the spell + /// The spell being cast + /// The entity casting the spell + bool IsReady(Spell* spell, Entity* caster); + + /// Send the spell book update packet to the given client + /// Client to send the packet to + void SendSpellBookUpdate(Client* client); + + /// Gets the target of the currently casting spell for the given entity + /// Entity whos spell we are checking + /// Spawn* - the spells target + Spawn* GetSpellTarget(Entity* caster); + + /// Gets the currently casting spell for the given entity + /// Entity to get the spell for + /// Spell* for the currently casting spell + Spell* GetSpell(Entity* caster); + + /// Gets the currently casting LuaSpell for the given entity + /// Entity to get the LuaSpell for + /// LuaSpell* for the currently casting spell + LuaSpell* GetLuaSpell(Entity* caster); + + /// Gets the targets for the spell and adds them to the LuaSpell targets array + /// LuaSpell to get the targets for + static void GetSpellTargets(LuaSpell* luaspell); + + static bool GetPlayerGroupTargets(Player* target, Spawn* caster, LuaSpell* luaspell, bool bypassSpellChecks=false, bool bypassRangeChecks=false); + + /// Gets targets for a true aoe spell (not an encounter ae) and adds them to the LuaSpell targets array + /// LuaSpell to get the targets for + static void GetSpellTargetsTrueAOE(LuaSpell* luaspell); + + /// Applies or removes passive spells, bypasses the spell queue and treats the spell as an insta cast spell + /// The passive spell to apply or remove + /// The Entity to apply or remove the passive spell to + /// Tells the function to remove the spell effects of this passive, default is false + bool CastPassives(Spell* spell, Entity* caster, bool remove = false); + bool CastInstant(Spell* spell, Entity* caster, Entity* target, bool remove = false, bool passive=false); + + /// Adds a spell script timer to the list + /// Timer to add + void AddSpellScriptTimer(SpellScriptTimer* timer); + + /// Removes a spell script timer from the list + /// Timer to remove + void RemoveSpellScriptTimer(SpellScriptTimer* timer, bool locked=false); + void RemoveSpellScriptTimerBySpell(LuaSpell* spell, bool clearPendingDeletes=true); + + /// Checks the spell script timers + void CheckSpellScriptTimers(); + + /// Checks to see if the list has the spell + bool SpellScriptTimersHasSpell(LuaSpell* spell); + std::string SpellScriptTimerCustomFunction(LuaSpell* spell); + + void ClearSpellScriptTimerList(); + + MutexList* GetActiveSpells() { return &active_spells; } + + void RemoveTargetFromSpell(LuaSpell* spell, Spawn* target, bool remove_caster = false); + void CheckRemoveTargetFromSpell(LuaSpell* spell, bool allow_delete = true, bool removing_all_spells = false); + void RemoveTargetList(LuaSpell* spell); + + /// Adds a solo HO to the SpellProcess + /// The client who is starting the HO + /// The HO that is being started + bool AddHO(Client* client, HeroicOP* ho); + + /// Adds a group HO to the SpellProcess + /// ID of the group that is starting the HO + /// The HO that is being started + bool AddHO(int32 group_id, HeroicOP* ho); + + /// Stops the HO that targets the given spawn + /// ID of the spawn targeted by the HO we want to stop + void KillHOBySpawnID(int32 spawn_id); + + void AddSpellCancel(LuaSpell* spell); + + void DeleteSpell(LuaSpell* spell); + + void SpellCannotStack(ZoneServer* zone, Client* client, Entity* caster, LuaSpell* lua_spell, LuaSpell* conflictSpell); + + bool ProcessSpell(LuaSpell* spell, bool first_cast = true, const char* function = 0, SpellScriptTimer* timer = 0, bool all_targets = false); + std::string ApplyLuaFunction(LuaSpell* spell, bool first_cast, const char* function, SpellScriptTimer* timer, Spawn* altTarget = 0); + + void AddActiveSpell(LuaSpell* spell); + static void AddSelfAndPet(LuaSpell* spell, Spawn* self, bool onlyPet=false); + static void AddSelfAndPetToCharTargets(LuaSpell* spell, Spawn* caster, bool onlyPet=false); + void DeleteActiveSpell(LuaSpell* spell); + static bool AddLuaSpellTarget(LuaSpell* lua_spell, int32 id, bool lock_spell_targets = true); + mutable std::shared_mutex MSpellProcess; +private: + MutexMap spell_que; + MutexList active_spells; + MutexList cast_timers; + MutexListinterrupt_list; + MutexList recast_timers; + int32 last_checked_time; + vector m_spellScriptList; + Mutex MSpellScriptTimers; + map*> remove_target_list; + Mutex MRemoveTargetList; + vector SpellCancelList; + Mutex MSpellCancelList; + + Mutex MSoloHO; + Mutex MGroupHO; + map m_soloHO; + map m_groupHO; +}; + +#endif + diff --git a/source/WorldServer/Spells.cpp b/source/WorldServer/Spells.cpp new file mode 100644 index 0000000..3b4cbc1 --- /dev/null +++ b/source/WorldServer/Spells.cpp @@ -0,0 +1,2453 @@ +/* + 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 . +*/ +#include "Spells.h" +#include "../common/ConfigReader.h" +#include "WorldDatabase.h" +#include "../common/Log.h" +#include "Traits/Traits.h" +#include "AltAdvancement/AltAdvancement.h" +#include +#include "LuaInterface.h" +#include "Rules/Rules.h" + +#include + +extern ConfigReader configReader; +extern WorldDatabase database; +extern MasterTraitList master_trait_list; +extern MasterAAList master_aa_list; +extern MasterSpellList master_spell_list; +extern LuaInterface* lua_interface; +extern MasterSkillList master_skill_list; +extern RuleManager rule_manager; + +Spell::Spell(){ + spell = new SpellData; + heal_spell = false; + buff_spell = false; + damage_spell = false; + control_spell = false; + offense_spell = false; + copied_spell = false; +} + +Spell::Spell(Spell* host_spell, bool unique_spell) +{ + std::shared_lock lock(host_spell->MSpellInfo); + copied_spell = true; + + spell = new SpellData; + + if (host_spell->GetSpellData()) + { + if(!unique_spell) { + spell->id = host_spell->GetSpellData()->id; + } + else { + // try inheriting an existing custom spell id, otherwise obtain the new highest number on the spell list + int32 tmpid = lua_interface->GetFreeCustomSpellID(); + if (tmpid) + spell->id = tmpid; + else + { + spell->id = master_spell_list.GetNewMaxSpellID(); + } + } + + spell->inherited_spell_id = host_spell->GetSpellData()->inherited_spell_id; + + spell->affect_only_group_members = host_spell->GetSpellData()->affect_only_group_members; + spell->call_frequency = host_spell->GetSpellData()->call_frequency; + spell->can_effect_raid = host_spell->GetSpellData()->can_effect_raid; + spell->casting_flags = host_spell->GetSpellData()->casting_flags; + spell->cast_time = host_spell->GetSpellData()->cast_time; + spell->orig_cast_time = host_spell->GetSpellData()->orig_cast_time; + spell->cast_type = host_spell->GetSpellData()->cast_type; + spell->cast_while_moving = host_spell->GetSpellData()->cast_while_moving; + spell->class_skill = host_spell->GetSpellData()->class_skill; + spell->min_class_skill_req = host_spell->GetSpellData()->min_class_skill_req; + spell->control_effect_type = host_spell->GetSpellData()->control_effect_type; + spell->description = EQ2_16BitString(host_spell->GetSpellData()->description); + spell->det_type = host_spell->GetSpellData()->det_type; + spell->display_spell_tier = host_spell->GetSpellData()->display_spell_tier; + + spell->dissonance_req = host_spell->GetSpellData()->dissonance_req; + spell->dissonance_req_percent = host_spell->GetSpellData()->dissonance_req_percent; + spell->dissonance_upkeep = host_spell->GetSpellData()->dissonance_upkeep; + spell->duration1 = host_spell->GetSpellData()->duration1; + spell->duration2 = host_spell->GetSpellData()->duration2; + spell->duration_until_cancel = host_spell->GetSpellData()->duration_until_cancel; + spell->effect_message = string(host_spell->GetSpellData()->effect_message); + spell->fade_message = string(host_spell->GetSpellData()->fade_message); + spell->fade_message_others = string(host_spell->GetSpellData()->fade_message_others); + + spell->friendly_spell = host_spell->GetSpellData()->friendly_spell; + spell->group_spell = host_spell->GetSpellData()->group_spell; + + spell->hit_bonus = host_spell->GetSpellData()->hit_bonus; + + spell->hp_req = host_spell->GetSpellData()->hp_req; + spell->hp_req_percent = host_spell->GetSpellData()->hp_req_percent; + spell->hp_upkeep = host_spell->GetSpellData()->hp_upkeep; + + spell->icon = host_spell->GetSpellData()->icon; + spell->icon_backdrop = host_spell->GetSpellData()->icon_backdrop; + + spell->icon_heroic_op = host_spell->GetSpellData()->icon_heroic_op; + + spell->incurable = host_spell->GetSpellData()->incurable; + spell->interruptable = host_spell->GetSpellData()->interruptable; + spell->is_aa = host_spell->GetSpellData()->is_aa; + + spell->is_active = host_spell->GetSpellData()->is_active; + spell->linked_timer = host_spell->GetSpellData()->linked_timer; + spell->lua_script = string(host_spell->GetSpellData()->lua_script); + + spell->mastery_skill = host_spell->GetSpellData()->mastery_skill; + spell->max_aoe_targets = host_spell->GetSpellData()->max_aoe_targets; + + spell->min_range = host_spell->GetSpellData()->min_range; + spell->name = EQ2_8BitString(host_spell->GetSpellData()->name); + spell->not_maintained = host_spell->GetSpellData()->not_maintained; + spell->num_levels = host_spell->GetSpellData()->num_levels; + spell->persist_through_death = host_spell->GetSpellData()->persist_through_death; + spell->power_by_level = host_spell->GetSpellData()->power_by_level; + spell->power_req = host_spell->GetSpellData()->power_req; + spell->power_req_percent = host_spell->GetSpellData()->power_req_percent; + spell->power_upkeep = host_spell->GetSpellData()->power_upkeep; + spell->radius = host_spell->GetSpellData()->radius; + spell->range = host_spell->GetSpellData()->range; + spell->recast = host_spell->GetSpellData()->recast; + spell->recovery = host_spell->GetSpellData()->recovery; + spell->req_concentration = host_spell->GetSpellData()->req_concentration; + spell->resistibility = host_spell->GetSpellData()->resistibility; + spell->savagery_req = host_spell->GetSpellData()->savagery_req; + spell->savagery_req_percent = host_spell->GetSpellData()->savagery_req_percent; + spell->savagery_upkeep = host_spell->GetSpellData()->savagery_upkeep; + spell->savage_bar = host_spell->GetSpellData()->savage_bar; + spell->savage_bar_slot = host_spell->GetSpellData()->savage_bar_slot; + spell->soe_spell_crc = host_spell->GetSpellData()->soe_spell_crc; + spell->spell_book_type = host_spell->GetSpellData()->spell_book_type; + spell->spell_name_crc = host_spell->GetSpellData()->spell_name_crc; + spell->spell_type = host_spell->GetSpellData()->spell_type; + spell->spell_visual = host_spell->GetSpellData()->spell_visual; + spell->success_message = string(host_spell->GetSpellData()->success_message); + spell->target_type = host_spell->GetSpellData()->target_type; + spell->tier = host_spell->GetSpellData()->tier; + spell->ts_loc_index = host_spell->GetSpellData()->ts_loc_index; + spell->type = host_spell->GetSpellData()->type; + spell->type_group_spell_id = host_spell->GetSpellData()->type_group_spell_id; + spell->can_fizzle = host_spell->GetSpellData()->can_fizzle; + } + + heal_spell = host_spell->IsHealSpell(); + buff_spell = host_spell->IsBuffSpell(); + damage_spell = host_spell->IsDamageSpell();; + control_spell = host_spell->IsControlSpell(); + offense_spell = host_spell->IsOffenseSpell(); + + std::vector::iterator itr; + for (itr = host_spell->levels.begin(); itr != host_spell->levels.end(); itr++) + { + LevelArray* lvlArray = *itr; + AddSpellLevel(lvlArray->adventure_class, lvlArray->tradeskill_class, lvlArray->spell_level); + } + + std::vector::iterator sdeitr; + for (sdeitr = host_spell->effects.begin(); sdeitr != host_spell->effects.end(); sdeitr++) + { + SpellDisplayEffect* sde = *sdeitr; + AddSpellEffect(sde->percentage, sde->subbullet, sde->description); + } + + vector::iterator luaitr; + for (luaitr = host_spell->lua_data.begin(); luaitr != host_spell->lua_data.end(); luaitr++) { + LUAData* data = *luaitr; + AddSpellLuaData(data->type, data->int_value, data->int_value2, data->float_value, data->float_value2, data->bool_value, string(data->string_value), string(data->string_value2), string(data->string_helper)); + } +} + +Spell::Spell(SpellData* in_spell){ + spell = in_spell; + heal_spell = false; + buff_spell = false; + damage_spell = false; + control_spell = false; + offense_spell = false; + copied_spell = false; +} + +Spell::~Spell(){ + vector::iterator itr1; + for(itr1=levels.begin();itr1!=levels.end();itr1++) { + safe_delete(*itr1); + } + vector::iterator itr2; + for(itr2=effects.begin();itr2!=effects.end();itr2++) { + safe_delete(*itr2); + } + vector::iterator itr3; + for(itr3=lua_data.begin();itr3!=lua_data.end();itr3++) { + safe_delete(*itr3); + } + safe_delete(spell); +} + +void Spell::AddSpellLuaData(int8 type, int int_value, int int_value2, float float_value, float float_value2, bool bool_value, string string_value, string string_value2, string helper){ + std::unique_lock lock(MSpellInfo); + LUAData* data = new LUAData; + data->type = type; + data->int_value = int_value; + data->int_value2 = int_value2; + data->float_value = float_value; + data->float_value2 = float_value2; + data->bool_value = bool_value; + data->string_value = string_value; + data->string_value2 = string_value2; + data->string_helper = helper; + + lua_data.push_back(data); +} + +void Spell::AddSpellLuaDataInt(int value, int value2, string helper) { + std::unique_lock lock(MSpellInfo); + LUAData *data = new LUAData; + + data->type = 0; + data->int_value = value; + data->int_value2 = value2; + data->float_value = 0; + data->float_value2 = 0; + data->bool_value = false; + data->string_helper = helper; + + lua_data.push_back(data); +} + +void Spell::AddSpellLuaDataFloat(float value, float value2, string helper) { + std::unique_lock lock(MSpellInfo); + LUAData *data = new LUAData; + + data->type = 1; + data->int_value = 0; + data->int_value2 = 0; + data->float_value = value; + data->float_value2 = value2; + data->bool_value = false; + data->string_helper = helper; + + lua_data.push_back(data); +} + +void Spell::AddSpellLuaDataBool(bool value, string helper) { + std::unique_lock lock(MSpellInfo); + LUAData *data = new LUAData; + + data->type = 2; + data->int_value = 0; + data->float_value = 0; + data->bool_value = value; + data->string_helper = helper; + + lua_data.push_back(data); +} + +void Spell::AddSpellLuaDataString(string value, string value2,string helper) { + std::unique_lock lock(MSpellInfo); + LUAData *data = new LUAData; + + data->type = 3; + data->int_value = 0; + data->int_value2 = 0; + data->float_value = 0; + data->float_value2 = 0; + data->bool_value = false; + data->string_value = value; + data->string_value2 = value2; + data->string_helper = helper; + + lua_data.push_back(data); +} + +int16 Spell::GetLevelRequired(Player* player){ + int16 ret = 0xFFFF; + if(!player) + return ret; + LevelArray* level = 0; + vector::iterator itr; + for(itr = levels.begin(); itr != levels.end(); itr++){ + level = *itr; + if(level && level->adventure_class == player->GetAdventureClass()){ + ret = level->spell_level/10; + break; + } + } + return ret; +} + +void Spell::SetAAPacketInformation(PacketStruct* packet, AltAdvanceData* data, Client* client, bool display_tier) { + int8 current_tier = client->GetPlayer()->GetSpellTier(spell->id); + Spell* next_spell; + SpellData* spell2; + if (data->maxRank > current_tier) { + next_spell = master_spell_list.GetSpell(spell->id, current_tier + 1); + spell2 = next_spell->GetSpellData(); + } + SpellDisplayEffect* effect2; + + //next_spell->effects[1]->description; + + + int xxx = 0; + + int16 hp_req = 0; + int16 power_req = 0; + + if (current_tier > 0) { + packet->setSubstructDataByName("spell_info", "current_id", spell->id); + packet->setSubstructDataByName("spell_info", "current_icon", spell->icon); + packet->setSubstructDataByName("spell_info", "current_icon2", spell->icon_heroic_op); // fix struct element name eventually + packet->setSubstructDataByName("spell_info", "current_icontype", spell->icon_backdrop); // fix struct element name eventually + + if (packet->GetVersion() >= 63119) { + packet->setSubstructDataByName("spell_info", "current_version", 0x04); + packet->setSubstructDataByName("spell_info", "current_sub_version", 0x24); + } + else if (packet->GetVersion() >= 58617) { + packet->setSubstructDataByName("spell_info", "current_version", 0x03); + packet->setSubstructDataByName("spell_info", "current_sub_version", 0x131A); + } + else { + packet->setSubstructDataByName("spell_info", "current_version", 0x00); + packet->setSubstructDataByName("spell_info", "current_sub_version", 0xD9); + } + + packet->setSubstructDataByName("spell_info", "current_type", spell->type); + packet->setSubstructDataByName("spell_info", "unknown_MJ1d", 1); //63119 test + packet->setSubstructDataByName("spell_info", "current_class_skill", spell->class_skill); + packet->setSubstructDataByName("spell_info", "current_mastery_skill", spell->mastery_skill); + packet->setSubstructDataByName("spell_info", "duration_flag", spell->duration_until_cancel); + + + if (client && spell->type != 2) { + sint8 spell_text_color = client->GetPlayer()->GetArrowColor(GetLevelRequired(client->GetPlayer())); + if (spell_text_color != ARROW_COLOR_WHITE && spell_text_color != ARROW_COLOR_RED && spell_text_color != ARROW_COLOR_GRAY) + spell_text_color = ARROW_COLOR_WHITE; + spell_text_color -= 6; + if (spell_text_color < 0) + spell_text_color *= -1; + packet->setSubstructDataByName("spell_info", "current_spell_text_color", (xxx == 1 ? 0xFFFFFFFF : spell_text_color)); + } + else { + packet->setSubstructDataByName("spell_info", "current_spell_text_color", (xxx == 1 ? 0xFFFFFFFF : 3)); + } + packet->setSubstructDataByName("spell_info", "current_spell_text_color", (xxx == 1 ? 0xFFFFFFFF : 3)); + packet->setSubstructDataByName("spell_info", "current_tier", (spell->tier)); + + if (spell->type != 2) { + packet->setArrayLengthByName("current_num_levels", 0); + for (int32 i = 0; i < levels.size(); i++) { + // revisit when implementing AA and use AppendLevelInformation instead (this struct doesn't even exist yet for KoS) + packet->setArrayDataByName("spell_info_aa_adventure_class", levels[i]->adventure_class, i); + packet->setArrayDataByName("spell_info_aa_tradeskill_class", levels[i]->tradeskill_class, i); + packet->setArrayDataByName("spell_info_aa_spell_level", levels[i]->spell_level, i); + } + } + //packet->setSubstructDataByName("spell_info","unknown9", 20); + + if (client) { + hp_req = GetHPRequired(client->GetPlayer()); + power_req = GetPowerRequired(client->GetPlayer()); + + // might need version checks around these? + if (client->GetVersion() >= 1193) + { + int16 savagery_req = GetSavageryRequired(client->GetPlayer()); // dunno why we need to do this + packet->setSubstructDataByName("spell_info", "current_savagery_req", savagery_req); + packet->setSubstructDataByName("spell_info", "current_savagery_upkeep", spell->savagery_upkeep); + } + if (client->GetVersion() >= 57048) + { + int16 dissonance_req = GetDissonanceRequired(client->GetPlayer()); // dunno why we need to do this + packet->setSubstructDataByName("spell_info", "dissonance_req", dissonance_req); + packet->setSubstructDataByName("spell_info", "dissonance_upkeep", spell->dissonance_upkeep); + } + } + packet->setSubstructDataByName("spell_info", "current_health_req", hp_req); + packet->setSubstructDataByName("spell_info", "current_health_upkeep", spell->hp_upkeep); + packet->setSubstructDataByName("spell_info", "current_power_req", power_req); + packet->setSubstructDataByName("spell_info", "current_power_upkeep", spell->power_upkeep); + packet->setSubstructDataByName("spell_info", "current_req_concentration", spell->req_concentration); + //unknown1 savagery??? + packet->setSubstructDataByName("spell_info", "current_cast_time", spell->cast_time); + packet->setSubstructDataByName("spell_info", "current_recovery", spell->recovery); + packet->setSubstructDataByName("spell_info", "current_recast", spell->recast); + packet->setSubstructDataByName("spell_info", "current_radius", spell->radius); + packet->setSubstructDataByName("spell_info", "current_max_aoe_targets", spell->max_aoe_targets); + packet->setSubstructDataByName("spell_info", "current_friendly_spell", spell->friendly_spell); + // rumber of reagents with array + + + + + + + + + + packet->setSubstructArrayLengthByName("spell_info", "current_num_effects", (xxx == 1 ? 0 : effects.size())); + for (int32 i = 0; i < effects.size(); i++) { + packet->setArrayDataByName("current_subbulletflag", effects[i]->subbullet, i); + string effect_message; + if (effects[i]->description.length() > 0) { + effect_message = effects[i]->description; + if (effect_message.find("%LM") < 0xFFFFFFFF) { + int string_index = effect_message.find("%LM"); + int data_index = stoi(effect_message.substr(string_index + 3, 2)); + float value; + if (lua_data[data_index]->type == 1) + value = lua_data[data_index]->float_value * client->GetPlayer()->GetLevel(); + else + value = lua_data[data_index]->int_value * client->GetPlayer()->GetLevel(); + string strValue = to_string(value); + strValue.erase(strValue.find_last_not_of('0') + 1, std::string::npos); + effect_message.replace(effect_message.find("%LM"), 5, strValue); + } + // Magic damage min + if (effect_message.find("%DML") < 0xFFFFFFFF) { + int string_index = effect_message.find("%DML"); + int data_index = stoi(effect_message.substr(string_index + 4, 2)); + float value; + if (lua_data[data_index]->type == 1) + value = lua_data[data_index]->float_value * client->GetPlayer()->GetLevel(); + else + value = lua_data[data_index]->int_value * client->GetPlayer()->GetLevel(); + + value = client->GetPlayer()->CalculateDamageAmount(nullptr, value, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type); + string damage = to_string((int)round(value)); + effect_message.replace(effect_message.find("%DML"), 6, damage); + } + // Magic damage max + if (effect_message.find("%DMH") < 0xFFFFFFFF) { + int string_index = effect_message.find("%DMH"); + int data_index = stoi(effect_message.substr(string_index + 4, 2)); + float value; + if (lua_data[data_index]->type == 1) + value = lua_data[data_index]->float_value * client->GetPlayer()->GetLevel(); + else + value = lua_data[data_index]->int_value * client->GetPlayer()->GetLevel(); + + value = client->GetPlayer()->CalculateDamageAmount(nullptr, value, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type); + string damage = to_string((int)round(value)); + effect_message.replace(effect_message.find("%DMH"), 6, damage); + } + // level based Magic damage min + if (effect_message.find("%LDML") < 0xFFFFFFFF) { + int string_index = effect_message.find("%LDML"); + int data_index = stoi(effect_message.substr(string_index + 5, 2)); + float value; + if (lua_data[data_index]->type == 1) + value = lua_data[data_index]->float_value * client->GetPlayer()->GetLevel(); + else + value = lua_data[data_index]->int_value * client->GetPlayer()->GetLevel(); + + value = client->GetPlayer()->CalculateDamageAmount(nullptr, value, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type); + string damage = to_string((int)round(value)); + effect_message.replace(effect_message.find("%LDML"), 7, damage); + } + // level based Magic damage max + if (effect_message.find("%LDMH") < 0xFFFFFFFF) { + int string_index = effect_message.find("%LDMH"); + int data_index = stoi(effect_message.substr(string_index + 5, 2)); + float value; + if (lua_data[data_index]->type == 1) + value = lua_data[data_index]->float_value * client->GetPlayer()->GetLevel(); + else + value = lua_data[data_index]->int_value * client->GetPlayer()->GetLevel(); + + value = client->GetPlayer()->CalculateDamageAmount(nullptr, value, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type); + string damage = to_string((int)round(value)); + effect_message.replace(effect_message.find("%LDMH"), 7, damage); + } + //GetZone()->SimpleMessage(CHANNEL_COLOR_SPELL_EFFECT, effect_message.c_str(), victim, 50); + packet->setArrayDataByName("current_effect", effect_message.c_str(), i); + } + packet->setArrayDataByName("current_percentage", effects[i]->percentage, i); + } + if (display_tier == true) + packet->setSubstructDataByName("spell_info", "current_display_spell_tier", 1);// spell2->display_spell_tier); + else + packet->setSubstructDataByName("spell_info", "current_display_spell_tier", 1);// 0); + + packet->setSubstructDataByName("spell_info", "current_unknown_1", 1);// 0); + //unkown1_1 + packet->setSubstructDataByName("spell_info", "current_minimum_range", spell->min_range); + packet->setSubstructDataByName("spell_info", "current_range", spell->range); + packet->setSubstructDataByName("spell_info", "current_duration_1", spell->duration1); + packet->setSubstructDataByName("spell_info", "current_duration_2", spell->duration2); + + packet->setSubstructDataByName("spell_info", "current_duration_flag", spell->duration_until_cancel); + packet->setSubstructDataByName("spell_info", "current_target", spell->target_type); + + + + + + packet->setSubstructDataByName("spell_info", "current_can_effect_raid", spell->can_effect_raid); + packet->setSubstructDataByName("spell_info", "current_affect_only_group_members", spell->affect_only_group_members); + packet->setSubstructDataByName("spell_info", "current_group_spell", spell->group_spell); + packet->setSubstructDataByName("spell_info", "current_resistibility", spell->resistibility); + packet->setSubstructDataByName("spell_info", "current_name", &(spell->name)); + packet->setSubstructDataByName("spell_info", "current_description", &(spell->description)); + + } + + if (current_tier + 1 <= data->maxRank) { + packet->setSubstructDataByName("spell_info", "next_id", spell2->id); + packet->setSubstructDataByName("spell_info", "next_icon", spell2->icon); + packet->setSubstructDataByName("spell_info", "next_icon2", spell2->icon_heroic_op); // fix struct element name eventually + packet->setSubstructDataByName("spell_info", "next_icontype", spell2->icon_backdrop); // fix struct element name eventually + + if (packet->GetVersion() >= 63119) { + packet->setSubstructDataByName("spell_info", "next_aa_spell_info2", "version", 0x04); + packet->setSubstructDataByName("spell_info", "next_aa_spell_info2", "sub_version", 0x24); + } + else if (packet->GetVersion() >= 58617) { + packet->setSubstructDataByName("spell_info", "next_version", 0x03); + packet->setSubstructDataByName("spell_info", "next_sub_version", 0x131A); + } + else { + packet->setSubstructDataByName("spell_info", "next_version", 0x00); + packet->setSubstructDataByName("spell_info", "next_sub_version", 0xD9); + } + + packet->setSubstructDataByName("spell_info", "next_type", spell2->type); + packet->setSubstructDataByName("spell_info", "next_unknown_MJ1d", 1); //63119 test + packet->setSubstructDataByName("spell_info", "next_class_skill", spell2->class_skill); + packet->setSubstructDataByName("spell_info", "next_mastery_skill", spell2->mastery_skill); + packet->setSubstructDataByName("spell_info", "next_duration_flag", spell2->duration_until_cancel); + if (client && spell->type != 2) { + sint8 spell_text_color = client->GetPlayer()->GetArrowColor(GetLevelRequired(client->GetPlayer())); + if (spell_text_color != ARROW_COLOR_WHITE && spell_text_color != ARROW_COLOR_RED && spell_text_color != ARROW_COLOR_GRAY) + spell_text_color = ARROW_COLOR_WHITE; + spell_text_color -= 6; + if (spell_text_color < 0) + spell_text_color *= -1; + packet->setSubstructDataByName("spell_info", "next_spell_text_color", spell_text_color); + } + else + packet->setSubstructDataByName("spell_info", "next_spell_text_color", 3); + if (spell->type != 2) { + packet->setArrayLengthByName("num_levels", levels.size()); + for (int32 i = 0; i < levels.size(); i++) { + packet->setArrayDataByName("spell_info_aa_adventure_class2", levels[i]->adventure_class, i); + packet->setArrayDataByName("spell_info_aa_tradeskill_class2", levels[i]->tradeskill_class, i); + packet->setArrayDataByName("spell_info_aa_spell_level2", levels[i]->spell_level, i); + } + } + //packet->setSubstructDataByName("spell_info","unknown9", 20); + hp_req = 0; + power_req = 0; + if (client) { + hp_req = GetHPRequired(client->GetPlayer()); + power_req = GetPowerRequired(client->GetPlayer()); + + // might need version checks around these? + if (client->GetVersion() >= 1193) + { + int16 savagery_req = GetSavageryRequired(client->GetPlayer()); // dunno why we need to do this + packet->setSubstructDataByName("spell_info", "next_savagery_req", savagery_req); + packet->setSubstructDataByName("spell_info", "next_savagery_upkeep", spell->savagery_upkeep); + } + if (client->GetVersion() >= 57048) + { + int16 dissonance_req = GetDissonanceRequired(client->GetPlayer()); // dunno why we need to do this + packet->setSubstructDataByName("spell_info", "next_dissonance_req", dissonance_req); + packet->setSubstructDataByName("spell_info", "next_dissonance_upkeep", spell->dissonance_upkeep); + } + } + packet->setSubstructDataByName("spell_info", "next_target", spell->target_type); + packet->setSubstructDataByName("spell_info", "next_recovery", spell->recovery); + packet->setSubstructDataByName("spell_info", "next_health_upkeep", spell->hp_upkeep); + packet->setSubstructDataByName("spell_info", "next_health_req", hp_req); + packet->setSubstructDataByName("spell_info", "next_tier", spell->tier); + packet->setSubstructDataByName("spell_info", "next_power_req", power_req); + packet->setSubstructDataByName("spell_info", "next_power_upkeep", spell->power_upkeep); + + packet->setSubstructDataByName("spell_info", "next_cast_time", spell->cast_time); + packet->setSubstructDataByName("spell_info", "next_recast", spell->recast); + packet->setSubstructDataByName("spell_info", "next_radius", spell->radius); + packet->setSubstructDataByName("spell_info", "next_req_concentration", spell->req_concentration); + //packet->setSubstructDataByName("spell_info","req_concentration2", 2); + packet->setSubstructDataByName("spell_info", "next_max_aoe_targets", spell->max_aoe_targets); + packet->setSubstructDataByName("spell_info", "next_friendly_spell", spell->friendly_spell); + packet->setSubstructArrayLengthByName("spell_info", "next_num_effects", next_spell->effects.size()); + for (int32 i = 0; i < next_spell->effects.size(); i++) { + packet->setArrayDataByName("next_subbulletflag", next_spell->effects[i]->subbullet, i); + string effect_message; + if (next_spell->effects[i]->description.length() > 0) { + effect_message = next_spell->effects[i]->description; + if (effect_message.find("%LM") < 0xFFFFFFFF) { + int string_index = effect_message.find("%LM"); + int data_index = stoi(effect_message.substr(string_index + 3, 2)); + float value; + if (next_spell->lua_data[data_index]->type == 1) + value = next_spell->lua_data[data_index]->float_value * client->GetPlayer()->GetLevel(); + else + value = next_spell->lua_data[data_index]->int_value * client->GetPlayer()->GetLevel(); + string strValue = to_string(value); + strValue.erase(strValue.find_last_not_of('0') + 1, std::string::npos); + effect_message.replace(effect_message.find("%LM"), 5, strValue); + } + // Magic damage min + if (effect_message.find("%DML") < 0xFFFFFFFF) { + int string_index = effect_message.find("%DML"); + int data_index = stoi(effect_message.substr(string_index + 4, 2)); + float value; + if (next_spell->lua_data[data_index]->type == 1) + value = next_spell->lua_data[data_index]->float_value * client->GetPlayer()->GetLevel(); + else + value = next_spell->lua_data[data_index]->int_value * client->GetPlayer()->GetLevel(); + + value = client->GetPlayer()->CalculateDamageAmount(nullptr, value, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type); + string damage = to_string((int)round(value)); + damage.erase(damage.find_last_not_of('0') + 1, std::string::npos); + effect_message.replace(effect_message.find("%DML"), 6, damage); + } + // Magic damage max + if (effect_message.find("%DMH") < 0xFFFFFFFF) { + int string_index = effect_message.find("%DMH"); + int data_index = stoi(effect_message.substr(string_index + 4, 2)); + float value; + if (next_spell->lua_data[data_index]->type == 1) + value = next_spell->lua_data[data_index]->float_value * client->GetPlayer()->GetLevel(); + else + value = next_spell->lua_data[data_index]->int_value * client->GetPlayer()->GetLevel(); + + value = client->GetPlayer()->CalculateDamageAmount(nullptr, value, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type); + string damage = to_string((int)round(value)); + damage.erase(damage.find_last_not_of('0') + 1, std::string::npos); + effect_message.replace(effect_message.find("%DMH"), 6, damage); + } + // level based Magic damage min + if (effect_message.find("%LDML") < 0xFFFFFFFF) { + int string_index = effect_message.find("%LDML"); + int data_index = stoi(effect_message.substr(string_index + 5, 2)); + float value; + if (next_spell->lua_data[data_index]->type == 1) + value = next_spell->lua_data[data_index]->float_value * client->GetPlayer()->GetLevel(); + else + value = next_spell->lua_data[data_index]->int_value * client->GetPlayer()->GetLevel(); + + value = client->GetPlayer()->CalculateDamageAmount(nullptr, value, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type); + string damage = to_string((int)round(value)); + effect_message.replace(effect_message.find("%LDML"), 7, damage); + } + // level based Magic damage max + if (effect_message.find("%LDMH") < 0xFFFFFFFF) { + int string_index = effect_message.find("%LDMH"); + int data_index = stoi(effect_message.substr(string_index + 5, 2)); + float value; + if (next_spell->lua_data[data_index]->type == 1) + value = next_spell->lua_data[data_index]->float_value * client->GetPlayer()->GetLevel(); + else + value = next_spell->lua_data[data_index]->int_value * client->GetPlayer()->GetLevel(); + + value = client->GetPlayer()->CalculateDamageAmount(nullptr, value, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type); + string damage = to_string((int)round(value)); + effect_message.replace(effect_message.find("%LDMH"), 7, damage); + } + //GetZone()->SimpleMessage(CHANNEL_COLOR_SPELL_EFFECT, effect_message.c_str(), victim, 50); + packet->setArrayDataByName("next_effect", effect_message.c_str(), i); + } + + packet->setArrayDataByName("next_percentage", next_spell->effects[i]->percentage, i); + + } + if (display_tier == true) + packet->setSubstructDataByName("spell_info", "next_display_spell_tier", 1);// spell->display_spell_tier); + else + packet->setSubstructDataByName("spell_info", "next_display_spell_tier", 1);//0 + packet->setSubstructDataByName("spell_info", "next_unknown_1", 1);//0 + packet->setSubstructDataByName("spell_info", "next_range", spell2->range); + packet->setSubstructDataByName("spell_info", "next_duration_1", spell2->duration1); + packet->setSubstructDataByName("spell_info", "next_duration_2", spell2->duration2); + + packet->setSubstructDataByName("spell_info", "next_can_effect_raid", spell2->can_effect_raid); + packet->setSubstructDataByName("spell_info", "next_affect_only_group_members", spell2->affect_only_group_members); + packet->setSubstructDataByName("spell_info", "next_group_spell", spell2->group_spell); + packet->setSubstructDataByName("spell_info", "next_resistibility", spell2->resistibility); + packet->setSubstructDataByName("spell_info", "next_name", &(spell2->name)); + packet->setSubstructDataByName("spell_info", "next_description", &(spell2->description)); + + } +} + +void Spell::AppendLevelInformation(PacketStruct* packet) { + if(!packet) + return; + + vector newLevels; + vector * tmpArray = &levels; + if(packet->GetVersion() <= 561) { + for (int32 i = 0; i < tmpArray->size(); i++) { + LevelArray* levelData = tmpArray->at(i); + if((levelData->adventure_class != 255 && levelData->adventure_class > CLASSIC_MAX_ADVENTURE_CLASS) || (levelData->tradeskill_class != 255 && levelData->tradeskill_class > CLASSIC_MAX_TRADESKILL_CLASS)) + continue; + + newLevels.push_back(levelData); + } + tmpArray = &newLevels; + } + + packet->setSubstructArrayLengthByName("spell_info", "num_levels", tmpArray->size()); + for (int32 i = 0; i < tmpArray->size(); i++) { + LevelArray* levelData = tmpArray->at(i); + packet->setArrayDataByName("adventure_class", levelData->adventure_class, i); + packet->setArrayDataByName("tradeskill_class", levelData->tradeskill_class, i); + packet->setArrayDataByName("spell_level", levelData->spell_level, i); + } +} + +sint16 Spell::TranslateClientSpellIcon(int16 version) { + sint16 spell_icon = GetSpellIcon(); + if(version <= 561) { + switch(spell_icon) { + case 772: // tracking + spell_icon = 231; // ?? + break; + case 773: // mining + spell_icon = 33; // OK + break; + case 774: // gathering + spell_icon = 56; // OK + break; + case 775: // fishing + spell_icon = 55; // OK + break; + case 776: // trapping + spell_icon = 46; // OK + break; + case 777: // foresting + spell_icon = 125; // OK + break; + case 778: // collecting + spell_icon = 167; // OK + break; + } + } + return spell_icon; +} + +void Spell::SetPacketInformation(PacketStruct* packet, Client* client, bool display_tier) { + packet->setSubstructDataByName("spell_info", "id", spell->id); + packet->setSubstructDataByName("spell_info", "icon", TranslateClientSpellIcon(client->GetVersion())); + packet->setSubstructDataByName("spell_info", "icon2", spell->icon_heroic_op); // fix struct element name eventually + packet->setSubstructDataByName("spell_info", "icontype", spell->icon_backdrop); // fix struct element name eventually + + if (packet->GetVersion() >= 63119) { + packet->setSubstructDataByName("spell_info", "version", 0x04); + packet->setSubstructDataByName("spell_info", "sub_version", 0x24); + } + else if (packet->GetVersion() >= 60114) { + packet->setSubstructDataByName("spell_info", "version", 0x03); + packet->setSubstructDataByName("spell_info", "sub_version", 4890); + } + else if (packet->GetVersion() <= 561) { + packet->setSubstructDataByName("spell_info", "version", 0x10); + packet->setSubstructDataByName("spell_info", "sub_version", 0x0f); + } + else { + packet->setSubstructDataByName("spell_info", "version", 0x11); + packet->setSubstructDataByName("spell_info", "sub_version", 0x14); + } + + packet->setSubstructDataByName("spell_info", "type", spell->type); + packet->setSubstructDataByName("spell_info", "unknown_MJ1d", 1); //63119 test + packet->setSubstructDataByName("spell_info", "class_skill", spell->class_skill); + packet->setSubstructDataByName("spell_info", "min_class_skill_req", spell->min_class_skill_req); + packet->setSubstructDataByName("spell_info", "mastery_skill", spell->mastery_skill); + packet->setSubstructDataByName("spell_info", "duration_flag", spell->duration_until_cancel); + if (client && spell->type != 2) { + sint8 spell_text_color = client->GetPlayer()->GetArrowColor(GetLevelRequired(client->GetPlayer())); + if (spell_text_color != ARROW_COLOR_WHITE && spell_text_color != ARROW_COLOR_RED && spell_text_color != ARROW_COLOR_GRAY) + spell_text_color = ARROW_COLOR_WHITE; + spell_text_color -= 6; + if (spell_text_color < 0) + spell_text_color *= -1; + packet->setSubstructDataByName("spell_info", "spell_text_color", spell_text_color); + } + else + packet->setSubstructDataByName("spell_info", "spell_text_color", 3); + if (spell->type != 2) { + AppendLevelInformation(packet); + } + packet->setSubstructDataByName("spell_info", "unknown9", 20); + int16 hp_req = 0; + int16 power_req = 0; + if (client) { + hp_req = GetHPRequired(client->GetPlayer()); + power_req = GetPowerRequired(client->GetPlayer()); + + // might need version checks around these? + if (client->GetVersion() >= 1193) + { + int16 savagery_req = GetSavageryRequired(client->GetPlayer()); // dunno why we need to do this + packet->setSubstructDataByName("spell_info", "savagery_req", savagery_req); + packet->setSubstructDataByName("spell_info", "savagery_upkeep", spell->savagery_upkeep); + } + if (client->GetVersion() >= 57048) + { + int16 dissonance_req = GetDissonanceRequired(client->GetPlayer()); // dunno why we need to do this + packet->setSubstructDataByName("spell_info", "dissonance_req", dissonance_req); + packet->setSubstructDataByName("spell_info", "dissonance_upkeep", spell->dissonance_upkeep); + } + } + packet->setSubstructDataByName("spell_info", "target", spell->target_type); + packet->setSubstructDataByName("spell_info", "recovery", spell->recovery); + packet->setSubstructDataByName("spell_info", "health_upkeep", spell->hp_upkeep); + packet->setSubstructDataByName("spell_info", "health_req", hp_req); + packet->setSubstructDataByName("spell_info", "tier", spell->tier); + packet->setSubstructDataByName("spell_info", "power_req", power_req); + packet->setSubstructDataByName("spell_info", "power_upkeep", spell->power_upkeep); + if (packet->GetVersion() <= 561) {//cast times are displayed differently on new clients + packet->setSubstructDataByName("spell_info", "cast_time", spell->cast_time/10); + } + else { + packet->setSubstructDataByName("spell_info", "cast_time", spell->cast_time); + } + packet->setSubstructDataByName("spell_info", "recast", CalculateRecastTimer(client->GetPlayer())/1000); + packet->setSubstructDataByName("spell_info", "radius", spell->radius); + packet->setSubstructDataByName("spell_info", "req_concentration", spell->req_concentration); + //packet->setSubstructDataByName("spell_info","req_concentration2", 2); + packet->setSubstructDataByName("spell_info", "max_aoe_targets", spell->max_aoe_targets); + packet->setSubstructDataByName("spell_info", "friendly_spell", spell->friendly_spell); + packet->setSubstructArrayLengthByName("spell_info", "num_effects", effects.size()); + for (int32 i = 0; i < effects.size(); i++) { + + packet->setArrayDataByName("subbulletflag", effects[i]->subbullet, i); + string effect_message; + if (effects[i]->description.length() > 0) { + effect_message = effects[i]->description; + if (effect_message.find("%LM") < 0xFFFFFFFF) { + int string_index = effect_message.find("%LM"); + int data_index = stoi(effect_message.substr(string_index + 3, 2)); + float value; + if (lua_data[data_index]->type == 1) + value = lua_data[data_index]->float_value * client->GetPlayer()->GetLevel(); + else + value = lua_data[data_index]->int_value * client->GetPlayer()->GetLevel(); + string strValue = to_string(value); + strValue.erase(strValue.find_last_not_of('0') + 1, std::string::npos); + effect_message.replace(effect_message.find("%LM"), 5, strValue); + } + // Magic damage min + if (effect_message.find("%DML") < 0xFFFFFFFF) { + int string_index = effect_message.find("%DML"); + int data_index = stoi(effect_message.substr(string_index + 4, 2)); + float value; + if (lua_data[data_index]->type == 1) + value = lua_data[data_index]->float_value * client->GetPlayer()->GetLevel(); + else + value = lua_data[data_index]->int_value * client->GetPlayer()->GetLevel(); + + value = client->GetPlayer()->CalculateDamageAmount(nullptr, value, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type); + string damage = to_string((int)round(value)); + effect_message.replace(effect_message.find("%DML"), 6, damage); + } + // Magic damage max + if (effect_message.find("%DMH") < 0xFFFFFFFF) { + int string_index = effect_message.find("%DMH"); + int data_index = stoi(effect_message.substr(string_index + 4, 2)); + float value; + if (lua_data[data_index]->type == 1) + value = lua_data[data_index]->float_value * client->GetPlayer()->GetLevel(); + else + value = lua_data[data_index]->int_value * client->GetPlayer()->GetLevel(); + + value = client->GetPlayer()->CalculateDamageAmount(nullptr, value, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type); + string damage = to_string((int)round(value)); + effect_message.replace(effect_message.find("%DMH"), 6, damage); + } + // level based Magic damage min + if (effect_message.find("%LDML") < 0xFFFFFFFF) { + int string_index = effect_message.find("%LDML"); + int data_index = stoi(effect_message.substr(string_index + 5, 2)); + float value; + if (lua_data[data_index]->type == 1) + value = lua_data[data_index]->float_value * client->GetPlayer()->GetLevel(); + else + value = lua_data[data_index]->int_value * client->GetPlayer()->GetLevel(); + + value = client->GetPlayer()->CalculateDamageAmount(nullptr, value, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type); + string damage = to_string((int)round(value)); + effect_message.replace(effect_message.find("%LDML"), 7, damage); + } + // level based Magic damage max + if (effect_message.find("%LDMH") < 0xFFFFFFFF) { + int string_index = effect_message.find("%LDMH"); + int data_index = stoi(effect_message.substr(string_index + 5, 2)); + float value; + if (lua_data[data_index]->type == 1) + value = lua_data[data_index]->float_value * client->GetPlayer()->GetLevel(); + else + value = lua_data[data_index]->int_value * client->GetPlayer()->GetLevel(); + + value = client->GetPlayer()->CalculateDamageAmount(nullptr, value, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type); + string damage = to_string((int)round(value)); + effect_message.replace(effect_message.find("%LDMH"), 7, damage); + } + + boost::regex re("([0-9]{1,10})\\s+\\-\\s+([0-9]{1,10})"); + boost::match_results base_match; + if (boost::regex_search(effect_message, base_match, re, boost::match_partial)) { + boost::ssub_match match = base_match[0]; + std::size_t midPos = match.str().find(" - "); + if(midPos != std::string::npos) + { + int32 minValue = atoul(match.str().substr(0, midPos).c_str()); + int32 maxValue = atoul(match.str().substr(midPos+3).c_str()); + + int32 newMin = minValue; + + bool crit = false; + Skill* skill = master_skill_list.GetSkill(spell->mastery_skill); + std::string skillName = ""; + + if(skill) + skillName = skill->name.data; + + if(skillName == "Aggression") + newMin = (int32)client->GetPlayer()->CalculateHateAmount(nullptr, (sint32)minValue); + else if(spell->friendly_spell && skillName == "Ministration") + newMin = (int32)client->GetPlayer()->CalculateHealAmount(nullptr, (sint32)minValue, 0, &crit, true); + else + newMin = (int32)client->GetPlayer()->CalculateDamageAmount(nullptr, (sint32)minValue, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type); + + int32 newMax = maxValue; + if(skillName == "Aggression") + newMax = (int32)client->GetPlayer()->CalculateHateAmount(nullptr, (sint32)maxValue); + else if(spell->friendly_spell && skillName == "Ministration") + newMax = (int32)client->GetPlayer()->CalculateHealAmount(nullptr, (sint32)maxValue, 0, &crit, true); + else + newMax = (int32)client->GetPlayer()->CalculateDamageAmount(nullptr, (sint32)maxValue, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type); + + std::string newStr = std::to_string(newMin) + " - " + std::to_string(newMax); + + effect_message.replace(effect_message.find(match.str()), match.str().size(), newStr); + } + } + //GetZone()->SimpleMessage(CHANNEL_COLOR_SPELL_EFFECT, effect_message.c_str(), victim, 50); + } + packet->setArrayDataByName("effect", effect_message.c_str(), i); + packet->setArrayDataByName("percentage", effects[i]->percentage, i); + } + if (display_tier == true) + packet->setSubstructDataByName("spell_info", "display_spell_tier", spell->display_spell_tier); + else + packet->setSubstructDataByName("spell_info", "display_spell_tier", 0); + packet->setSubstructDataByName("spell_info", "range", spell->range); + packet->setSubstructDataByName("spell_info", "duration1", spell->duration1); + packet->setSubstructDataByName("spell_info", "duration2", spell->duration2); + + packet->setSubstructDataByName("spell_info", "can_effect_raid", spell->can_effect_raid); + packet->setSubstructDataByName("spell_info", "affect_only_group_members", spell->affect_only_group_members); + packet->setSubstructDataByName("spell_info", "group_spell", spell->group_spell); + packet->setSubstructDataByName("spell_info", "resistibility", spell->resistibility); + packet->setSubstructDataByName("spell_info", "name", &(spell->name)); + packet->setSubstructDataByName("spell_info", "description", &(spell->description)); + //packet->PrintPacket(); +} +EQ2Packet* Spell::SerializeSpecialSpell(Client* client, bool display, int8 packet_type, int8 sub_packet_type) { + if (client->GetVersion() <= 373) + return SerializeSpell(client, display, false, packet_type, 0, "WS_ExaminePartialSpellInfo"); + return SerializeSpell(client, display, false, packet_type, sub_packet_type, "WS_ExamineSpecialSpellInfo"); +} + + +EQ2Packet* Spell::SerializeAASpell(Client* client, int8 tier, AltAdvanceData* data, bool display, bool trait_display, int8 packet_type, int8 sub_packet_type, const char* struct_name) { + if (!client) + return 0; + int16 version = 1; + if (client) + version = client->GetVersion(); + if (!struct_name) + struct_name = "WS_ExamineAASpellInfo"; + PacketStruct* packet = configReader.getStruct(struct_name, version); + if (display) + packet->setSubstructDataByName("info_header", "show_name", 1);//1 + else + if (!trait_display) + packet->setSubstructDataByName("info_header", "show_popup", 1);//1 + else + packet->setSubstructDataByName("info_header", "show_popup", 0); + + if (packet_type > 0) + packet->setSubstructDataByName("info_header", "packettype", packet_type * 256 + 0xFE); + else + packet->setSubstructDataByName("info_header", "packettype", 0x4FFE);// 0x45FE GetItemPacketType(version)); + //packet->setDataByName("unknown2",5); + //packet->setDataByName("unknown7", 1); + //packet->setDataByName("unknown9", 20); + //packet->setDataByName("unknown10", 1, 2); + if (sub_packet_type == 0) + sub_packet_type = 0x83; + packet->setSubstructDataByName("info_header", "packetsubtype", 4);// sub_packet_type); + packet->setSubstructDataByName("spell_info", "aa_id", data->spellID); + packet->setSubstructDataByName("spell_info", "aa_tab_id", data->group); + packet->setSubstructDataByName("spell_info", "aa_icon", data->icon); + packet->setSubstructDataByName("spell_info", "aa_icon2", data->icon2); + packet->setSubstructDataByName("spell_info", "aa_current_rank", tier); // how to get this info to here? + packet->setSubstructDataByName("spell_info", "aa_max_rank", data->maxRank); + packet->setSubstructDataByName("spell_info", "aa_rank_cost", data->rankCost); + packet->setSubstructDataByName("spell_info", "aa_unknown_2", 20); + packet->setSubstructDataByName("spell_info", "aa_name", &(spell->name)); + packet->setSubstructDataByName("spell_info", "aa_description", &(spell->description)); + + + + + //packet->setDataByName("unknown3",2); + //packet->setDataByName("unknown7", 50); + if (sub_packet_type == 0x81) + SetAAPacketInformation(packet, data, client); + else + SetAAPacketInformation(packet, data, client, true); + packet->setSubstructDataByName("spell_info", "uses_remaining", 0xFFFF); + packet->setSubstructDataByName("spell_info", "damage_remaining", 0xFFFF); + //packet->PrintPacket(); + // This adds the second portion to the spell packet. Could be used for bonuses etc.? + string* data1 = packet->serializeString(); + uchar* data2 = (uchar*)data1->c_str(); + uchar* ptr2 = data2; + int32 size = data1->length();// *2; + ////uchar* data3 = new uchar[size]; + ////memcpy(data3, data2, data1->length()); + ////uchar* ptr = data3; + ////size -= 17; + ////memcpy(ptr, &size, sizeof(int32)); + ////size += 3; + ////ptr += data1->length(); + ////ptr2 += 14; + ////memcpy(ptr, ptr2, data1->length() - 14); + + EQ2Packet* outapp = new EQ2Packet(OP_ClientCmdMsg, data2, size); + //DumpPacket(outapp); + //safe_delete_array(data3); + safe_delete(packet); + return outapp; +} + +EQ2Packet* Spell::SerializeSpell(Client* client, bool display, bool trait_display, int8 packet_type, int8 sub_packet_type, const char* struct_name, bool send_partial_packet) { + int16 version = 1; + if (client) + version = client->GetVersion(); + if (!struct_name) + struct_name = "WS_ExamineSpellInfo"; + if (version <= 561) { + if (packet_type == 1) + struct_name = "WS_ExamineEffectInfo"; + else if (!display && (version<=373 || send_partial_packet)) + struct_name = "WS_ExaminePartialSpellInfo"; + else + struct_name = "WS_ExamineSpellInfo"; + } + PacketStruct* packet = configReader.getStruct(struct_name, version); + if (display) + packet->setSubstructDataByName("info_header", "show_name", 1); + else { + if (!trait_display) + packet->setSubstructDataByName("info_header", "show_popup", 1); + else + packet->setSubstructDataByName("info_header", "show_popup", 0); + } + if (version > 561) { + if (packet_type > 0) + packet->setSubstructDataByName("info_header", "packettype", packet_type * 256 + 0xFE); + else + packet->setSubstructDataByName("info_header", "packettype", GetItemPacketType(version)); + } + else { + if (packet_type == 3 || packet_type == 0) + packet->setSubstructDataByName("info_header", "packettype", 3); // 0: item, 1: effect, 2: recipe, 3: spell/ability + else + packet->setSubstructDataByName("info_header", "packettype", 1); + } + //packet->setDataByName("unknown2",5); + //packet->setDataByName("unknown7", 1); + //packet->setDataByName("unknown9", 20); + //packet->setDataByName("unknown10", 1, 2); + if (sub_packet_type == 0) + sub_packet_type = 0x83; + packet->setSubstructDataByName("info_header", "packetsubtype", sub_packet_type); + //packet->setDataByName("unknown3",2); + //packet->setDataByName("unknown7", 50); + if (sub_packet_type == 0x81) + SetPacketInformation(packet, client); + else + SetPacketInformation(packet, client, true); + packet->setSubstructDataByName("spell_info", "uses_remaining", 0xFFFF); + packet->setSubstructDataByName("spell_info", "damage_remaining", 0xFFFF); + //packet->PrintPacket(); + // This adds the second portion to the spell packet. Could be used for bonuses etc.? + int8 offset = 0; + if (packet->GetVersion() == 60114) { + offset = 28; + } + else { + offset = 14; + } + EQ2Packet* outapp = 0; + if (version > 561) { + string* data1 = packet->serializeString(); + uchar* data2 = (uchar*)data1->c_str(); + uchar* ptr2 = data2; + int32 size = data1->length() * 2; + uchar* data3 = new uchar[size]; + memcpy(data3, data2, data1->length()); + uchar* ptr = data3; + size -= offset + 3; + memcpy(ptr, &size, sizeof(int32)); + size += 3; + ptr += data1->length(); + ptr2 += offset; + memcpy(ptr, ptr2, data1->length() - offset); + + outapp = new EQ2Packet(OP_ClientCmdMsg, data3, size); + safe_delete_array(data3); + safe_delete(packet); + } + else + outapp = packet->serialize(); + //DumpPacket(outapp); + return outapp; +} + +void Spell::AddSpellEffect(int8 percentage, int8 subbullet, string description){ + std::unique_lock lock(MSpellInfo); + SpellDisplayEffect* effect = new SpellDisplayEffect; + effect->description = description; + effect->subbullet = subbullet; + effect->percentage = percentage; + + effects.push_back(effect); +} + +int16 Spell::GetHPRequired(Spawn* spawn){ + int16 hp_req = spell->hp_req; + if(spawn && spell->hp_req_percent > 0){ + double result = ((double)spell->hp_req_percent/100)*spawn->GetTotalHP(); + if(result >= (((int16)result) + .5)) + result++; + hp_req = (int16)result; + } + return hp_req; +} + +int16 Spell::GetPowerRequired(Spawn* spawn){ + int16 power_req; + if (spell->power_by_level == true) { + power_req =round( (spell->power_req) * spawn->GetLevel()); + } + else { + power_req = round(spell->power_req); + } + if(spawn && spell->power_req_percent > 0){ + double result = ((double)spell->power_req_percent/100)*spawn->GetTotalPower(); + if(result >= (((int16)result) + .5)) + result++; + power_req = (int16)result; + } + if(spawn && spawn->IsPlayer()) { + int32 ministry_skill_id = rule_manager.GetGlobalRule(R_Spells, MinistrationSkillID)->GetInt32(); + if(spell->mastery_skill == ministry_skill_id) { // ministration offers a power reduction + Skill* skill = ((Player*)spawn)->GetSkillByID(spell->mastery_skill, false); + if(skill) { + float ministry_reduction_percent = rule_manager.GetGlobalRule(R_Spells, MinistrationPowerReductionMax)->GetFloat(); + if(ministry_reduction_percent <= 0.0f) + ministry_reduction_percent = 15.0f; + + int32 ministration_skill_reduce = rule_manager.GetGlobalRule(R_Spells, MinistrationPowerReductionSkill)->GetInt32(); + if(ministration_skill_reduce < 1) + ministration_skill_reduce = 25; + + float item_stat_bonus = 0.0f; + int32 item_stat = master_item_list.GetItemStatIDByName(::ToLower(skill->name.data)); + if(item_stat != 0xFFFFFFFF) { + item_stat_bonus = ((Entity*)spawn)->GetStat(item_stat); + } + + float reduction = (skill->current_val + item_stat_bonus) / ministration_skill_reduce; + + if(reduction > ministry_reduction_percent) + reduction = ministry_reduction_percent; + int16 power_to_reduce = (int16)(float)(power_req * (ministry_reduction_percent/100.0f)) * (reduction / ministry_reduction_percent); + if(power_to_reduce > power_req) { + power_req = 0; + } + else { + power_req = power_req - power_to_reduce; + } + } + } + + float power_reduction = ((Entity*)spawn)->GetStat(ITEM_STAT_POWER_COST_REDUCTION); + if(power_reduction > 0.0f) { + int16 power_to_reduce = (int16)(float)(power_req * (power_reduction/100.0f)); + if(power_to_reduce > power_req) { + power_req = 0; + } + else { + power_req = power_req - power_to_reduce; + } + } + } + return power_req; +} + +int16 Spell::GetSavageryRequired(Spawn* spawn){ + int16 savagery_req = spell->savagery_req; + if(spawn && spell->savagery_req_percent > 0){ + double result = ((double)spell->savagery_req_percent/100)*spawn->GetTotalSavagery(); + if(result >= (((int16)result) + .5)) + result++; + savagery_req = (int16)result; + } + return savagery_req; +} + +int16 Spell::GetDissonanceRequired(Spawn* spawn){ + int16 dissonance_req = spell->dissonance_req; + if(spawn && spell->dissonance_req_percent > 0){ + double result = ((double)spell->dissonance_req_percent/100)*spawn->GetTotalDissonance(); + if(result >= (((int16)result) + .5)) + result++; + dissonance_req = (int16)result; + } + return dissonance_req; +} + +int32 Spell::GetSpellDuration(){ + if(spell->duration1 == spell->duration2) + return spell->duration1; + + int32 difference = 0; + int32 lower = 0; + if(spell->duration2 > spell->duration1){ + difference = spell->duration2 - spell->duration1; + lower = spell->duration1; + } + else{ + difference = spell->duration1 - spell->duration2; + lower = spell->duration2; + } + int32 duration = (rand()%difference) + lower; + return duration; +} + +const char* Spell::GetName(){ + return spell->name.data.c_str(); +} + +const char* Spell::GetDescription(){ + return spell->description.data.c_str(); +} + +void Spell::AddSpellLevel(int8 adventure_class, int8 tradeskill_class, int16 level){ + std::unique_lock lock(MSpellInfo); + LevelArray* lvl = new LevelArray; + lvl->adventure_class = adventure_class; + lvl->tradeskill_class = tradeskill_class; + lvl->spell_level = level; + + levels.push_back(lvl); +} + +int32 Spell::GetSpellID(){ + if (spell) + return spell->id; + return 0; +} + +int8 Spell::GetSpellTier(){ + if (spell) + return spell->tier; + return 0; +} + +vector* Spell::GetLUAData(){ + return &lua_data; +} +SpellData* Spell::GetSpellData(){ + return spell; +} + +bool Spell::GetSpellData(lua_State* state, std::string field) +{ + if (!lua_interface) + return false; + + bool valSet = false; + + if (field == "spell_book_type") + { + lua_interface->SetInt32Value(state, GetSpellData()->spell_book_type); + valSet = true; + } + else if (field == "icon") + { + lua_interface->SetSInt32Value(state, GetSpellData()->icon); + valSet = true; + } + else if (field == "icon_heroic_op") + { + lua_interface->SetInt32Value(state, GetSpellData()->icon_heroic_op); + valSet = true; + } + else if (field == "icon_backdrop") + { + lua_interface->SetInt32Value(state, GetSpellData()->icon_backdrop); + valSet = true; + } + else if (field == "type") + { + lua_interface->SetInt32Value(state, GetSpellData()->type); + valSet = true; + } + else if (field == "class_skill") + { + lua_interface->SetInt32Value(state, GetSpellData()->class_skill); + valSet = true; + } + else if (field == "min_class_skill_req") + { + lua_interface->SetInt32Value(state, GetSpellData()->min_class_skill_req); + valSet = true; + } + else if (field == "mastery_skill") + { + lua_interface->SetInt32Value(state, GetSpellData()->mastery_skill); + valSet = true; + } + else if (field == "ts_loc_index") + { + lua_interface->SetSInt32Value(state, GetSpellData()->ts_loc_index); + valSet = true; + } + else if (field == "num_levels") + { + lua_interface->SetSInt32Value(state, GetSpellData()->num_levels); + valSet = true; + } + else if (field == "tier") + { + lua_interface->SetSInt32Value(state, GetSpellData()->tier); + valSet = true; + } + else if (field == "hp_req") + { + lua_interface->SetSInt32Value(state, GetSpellData()->hp_req); + valSet = true; + } + else if (field == "hp_upkeep") + { + lua_interface->SetSInt32Value(state, GetSpellData()->hp_upkeep); + valSet = true; + } + else if (field == "power_req") + { + lua_interface->SetFloatValue(state, GetSpellData()->power_req); + valSet = true; + } + else if (field == "power_by_level") + { + lua_interface->SetBooleanValue(state, GetSpellData()->power_by_level); + valSet = true; + } + else if (field == "power_upkeep") + { + lua_interface->SetSInt32Value(state, GetSpellData()->power_upkeep); + valSet = true; + } + else if (field == "savagery_req") + { + lua_interface->SetSInt32Value(state, GetSpellData()->savagery_req); + valSet = true; + } + else if (field == "savagery_upkeep") + { + lua_interface->SetSInt32Value(state, GetSpellData()->savagery_upkeep); + valSet = true; + } + else if (field == "dissonance_req") + { + lua_interface->SetSInt32Value(state, GetSpellData()->dissonance_req); + valSet = true; + } + else if (field == "dissonance_upkeep") + { + lua_interface->SetSInt32Value(state, GetSpellData()->dissonance_upkeep); + valSet = true; + } + else if (field == "target_type") + { + lua_interface->SetSInt32Value(state, GetSpellData()->target_type); + valSet = true; + } + else if (field == "cast_time") + { + lua_interface->SetSInt32Value(state, GetSpellData()->cast_time); + valSet = true; + } + else if (field == "recovery") + { + lua_interface->SetFloatValue(state, GetSpellData()->recovery); + valSet = true; + } + else if (field == "recast") + { + lua_interface->SetFloatValue(state, GetSpellData()->recast); + valSet = true; + } + else if (field == "linked_timer") + { + lua_interface->SetSInt32Value(state, GetSpellData()->linked_timer); + valSet = true; + } + else if (field == "radius") + { + lua_interface->SetFloatValue(state, GetSpellData()->radius); + valSet = true; + } + else if (field == "max_aoe_targets") + { + lua_interface->SetSInt32Value(state, GetSpellData()->max_aoe_targets); + valSet = true; + } + else if (field == "friendly_spell") + { + lua_interface->SetSInt32Value(state, GetSpellData()->friendly_spell); + valSet = true; + } + else if (field == "req_concentration") + { + lua_interface->SetSInt32Value(state, GetSpellData()->req_concentration); + valSet = true; + } + else if (field == "range") + { + lua_interface->SetFloatValue(state, GetSpellData()->range); + valSet = true; + } + else if (field == "duration1") + { + lua_interface->SetSInt32Value(state, GetSpellData()->duration1); + valSet = true; + } + else if (field == "duration2") + { + lua_interface->SetSInt32Value(state, GetSpellData()->duration2); + valSet = true; + } + else if (field == "resistibility") + { + lua_interface->SetFloatValue(state, GetSpellData()->resistibility); + valSet = true; + } + else if (field == "duration_until_cancel") + { + lua_interface->SetBooleanValue(state, GetSpellData()->duration_until_cancel); + valSet = true; + } + else if (field == "power_req_percent") + { + lua_interface->SetSInt32Value(state, GetSpellData()->power_req_percent); + valSet = true; + } + else if (field == "hp_req_percent") + { + lua_interface->SetSInt32Value(state, GetSpellData()->hp_req_percent); + valSet = true; + } + else if (field == "savagery_req_percent") + { + lua_interface->SetSInt32Value(state, GetSpellData()->savagery_req_percent); + valSet = true; + } + else if (field == "dissonance_req_percent") + { + lua_interface->SetSInt32Value(state, GetSpellData()->dissonance_req_percent); + valSet = true; + } + else if (field == "name") + { + lua_interface->SetStringValue(state, GetSpellData()->name.data.c_str()); + valSet = true; + } + else if (field == "description") + { + lua_interface->SetStringValue(state, GetSpellData()->description.data.c_str()); + valSet = true; + } + else if (field == "success_message") + { + lua_interface->SetStringValue(state, GetSpellData()->success_message.c_str()); + valSet = true; + } + else if (field == "fade_message") + { + lua_interface->SetStringValue(state, GetSpellData()->fade_message.c_str()); + valSet = true; + } + else if (field == "fade_message_others") + { + lua_interface->SetStringValue(state, GetSpellData()->fade_message_others.c_str()); + valSet = true; + } + else if (field == "cast_type") + { + lua_interface->SetSInt32Value(state, GetSpellData()->cast_type); + valSet = true; + } + else if (field == "lua_script") + { + lua_interface->SetStringValue(state, GetSpellData()->lua_script.c_str()); + valSet = true; + } + else if (field == "interruptable") + { + lua_interface->SetBooleanValue(state, GetSpellData()->interruptable); + valSet = true; + } + else if (field == "spell_visual") + { + lua_interface->SetSInt32Value(state, GetSpellData()->spell_visual); + valSet = true; + } + else if (field == "effect_message") + { + lua_interface->SetStringValue(state, GetSpellData()->effect_message.c_str()); + valSet = true; + } + else if (field == "min_range") + { + lua_interface->SetFloatValue(state, GetSpellData()->min_range); + valSet = true; + } + else if (field == "can_effect_raid") + { + lua_interface->SetSInt32Value(state, GetSpellData()->can_effect_raid); + valSet = true; + } + else if (field == "affect_only_group_members") + { + lua_interface->SetSInt32Value(state, GetSpellData()->affect_only_group_members); + valSet = true; + } + else if (field == "group_spell") + { + lua_interface->SetSInt32Value(state, GetSpellData()->group_spell); + valSet = true; + } + else if (field == "hit_bonus") + { + lua_interface->SetFloatValue(state, GetSpellData()->hit_bonus); + valSet = true; + } + else if (field == "display_spell_tier") + { + lua_interface->SetSInt32Value(state, GetSpellData()->display_spell_tier); + valSet = true; + } + else if (field == "is_active") + { + lua_interface->SetSInt32Value(state, GetSpellData()->is_active); + valSet = true; + } + else if (field == "det_type") + { + lua_interface->SetSInt32Value(state, GetSpellData()->det_type); + valSet = true; + } + else if (field == "incurable") + { + lua_interface->SetBooleanValue(state, GetSpellData()->incurable); + valSet = true; + } + else if (field == "control_effect_type") + { + lua_interface->SetSInt32Value(state, GetSpellData()->control_effect_type); + valSet = true; + } + else if (field == "casting_flags") + { + lua_interface->SetSInt32Value(state, GetSpellData()->casting_flags); + valSet = true; + } + else if (field == "cast_while_moving") + { + lua_interface->SetBooleanValue(state, GetSpellData()->cast_while_moving); + valSet = true; + } + else if (field == "persist_through_death") + { + lua_interface->SetBooleanValue(state, GetSpellData()->persist_through_death); + valSet = true; + } + else if (field == "not_maintained") + { + lua_interface->SetBooleanValue(state, GetSpellData()->not_maintained); + valSet = true; + } + else if (field == "is_aa") + { + lua_interface->SetBooleanValue(state, GetSpellData()->is_aa); + valSet = true; + } + else if (field == "savage_bar") + { + lua_interface->SetSInt32Value(state, GetSpellData()->savage_bar); + valSet = true; + } + else if (field == "savage_bar_slot") + { + lua_interface->SetSInt32Value(state, GetSpellData()->savage_bar_slot); + valSet = true; + } + else if (field == "soe_spell_crc") + { + lua_interface->SetSInt32Value(state, GetSpellData()->soe_spell_crc); + valSet = true; + } + else if (field == "spell_type") + { + lua_interface->SetSInt32Value(state, GetSpellData()->spell_type); + valSet = true; + } + else if (field == "spell_name_crc") + { + lua_interface->SetSInt32Value(state, GetSpellData()->spell_name_crc); + valSet = true; + } + else if (field == "type_group_spell_id") + { + lua_interface->SetSInt32Value(state, GetSpellData()->type_group_spell_id); + valSet = true; + } + else if (field == "can_fizzle") + { + lua_interface->SetBooleanValue(state, GetSpellData()->can_fizzle); + valSet = true; + } + + return valSet; +} + +bool Spell::SetSpellData(lua_State* state, std::string field, int8 fieldArg) +{ + if (!lua_interface) + return false; + + bool valSet = false; + + if (field == "spell_book_type") + { + int32 spell_book_type = lua_interface->GetInt32Value(state, fieldArg); + GetSpellData()->spell_book_type = spell_book_type; + valSet = true; + } + else if (field == "icon") + { + sint16 icon = lua_interface->GetSInt32Value(state, fieldArg); + GetSpellData()->icon = icon; + valSet = true; + } + else if (field == "icon_heroic_op") + { + int16 icon_heroic_op = lua_interface->GetInt16Value(state, fieldArg); + GetSpellData()->icon_heroic_op = icon_heroic_op; + valSet = true; + } + else if (field == "icon_backdrop") + { + int16 icon_backdrop = lua_interface->GetInt16Value(state, fieldArg); + GetSpellData()->icon_backdrop = icon_backdrop; + valSet = true; + } + else if (field == "type") + { + int16 type = lua_interface->GetInt16Value(state, fieldArg); + GetSpellData()->type = type; + valSet = true; + } + else if (field == "class_skill") + { + int32 class_skill = lua_interface->GetInt32Value(state, fieldArg); + GetSpellData()->class_skill = class_skill; + valSet = true; + } + else if (field == "min_class_skill_req") + { + int16 min_class_skill_req = lua_interface->GetInt16Value(state, fieldArg); + GetSpellData()->min_class_skill_req = min_class_skill_req; + valSet = true; + } + else if (field == "mastery_skill") + { + int32 mastery_skill = lua_interface->GetInt32Value(state, fieldArg); + GetSpellData()->mastery_skill = mastery_skill; + valSet = true; + } + else if (field == "ts_loc_index") + { + int8 ts_loc_index = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->ts_loc_index = ts_loc_index; + valSet = true; + } + else if (field == "num_levels") + { + int8 num_levels = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->num_levels = num_levels; + valSet = true; + } + else if (field == "tier") + { + int8 tier = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->tier = tier; + valSet = true; + } + else if (field == "hp_req") + { + int16 hp_req = lua_interface->GetInt16Value(state, fieldArg); + GetSpellData()->hp_req = hp_req; + valSet = true; + } + else if (field == "hp_upkeep") + { + int16 hp_upkeep = lua_interface->GetInt16Value(state, fieldArg); + GetSpellData()->hp_upkeep = hp_upkeep; + valSet = true; + } + else if (field == "power_req") + { + float power_req = lua_interface->GetFloatValue(state, fieldArg); + GetSpellData()->power_req = power_req; + valSet = true; + } + else if (field == "power_by_level") + { + bool power_by_level = lua_interface->GetBooleanValue(state, fieldArg); + GetSpellData()->power_by_level = power_by_level; + valSet = true; + } + else if (field == "power_upkeep") + { + int16 power_upkeep = lua_interface->GetInt16Value(state, fieldArg); + GetSpellData()->power_upkeep = power_upkeep; + valSet = true; + } + else if (field == "savagery_req") + { + int16 savagery_req = lua_interface->GetInt16Value(state, fieldArg); + GetSpellData()->savagery_req = savagery_req; + valSet = true; + } + else if (field == "savagery_upkeep") + { + int16 savagery_upkeep = lua_interface->GetInt16Value(state, fieldArg); + GetSpellData()->savagery_upkeep = savagery_upkeep; + valSet = true; + } + else if (field == "dissonance_req") + { + int16 dissonance_req = lua_interface->GetInt16Value(state, fieldArg); + GetSpellData()->dissonance_req = dissonance_req; + valSet = true; + } + else if (field == "dissonance_upkeep") + { + int16 dissonance_upkeep = lua_interface->GetInt16Value(state, fieldArg); + GetSpellData()->dissonance_upkeep = dissonance_upkeep; + valSet = true; + } + else if (field == "target_type") + { + int16 target_type = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->target_type = target_type; + valSet = true; + } + else if (field == "cast_time") + { + int16 cast_time = lua_interface->GetInt16Value(state, fieldArg); + GetSpellData()->orig_cast_time = cast_time; + GetSpellData()->cast_time = cast_time; + valSet = true; + } + else if (field == "recovery") + { + float recovery = lua_interface->GetFloatValue(state, fieldArg); + GetSpellData()->recovery = recovery; + valSet = true; + } + else if (field == "recast") + { + float recast = lua_interface->GetFloatValue(state, fieldArg); + GetSpellData()->recast = recast; + valSet = true; + } + else if (field == "linked_timer") + { + int32 linked_timer = lua_interface->GetInt32Value(state, fieldArg); + GetSpellData()->linked_timer = linked_timer; + valSet = true; + } + else if (field == "radius") + { + float radius = lua_interface->GetFloatValue(state, fieldArg); + GetSpellData()->radius = radius; + valSet = true; + } + else if (field == "max_aoe_targets") + { + int16 max_aoe_targets = lua_interface->GetInt16Value(state, fieldArg); + GetSpellData()->max_aoe_targets = max_aoe_targets; + valSet = true; + } + else if (field == "friendly_spell") + { + int8 friendly_spell = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->friendly_spell = friendly_spell; + valSet = true; + } + else if (field == "req_concentration") + { + int16 req_concentration = lua_interface->GetInt16Value(state, fieldArg); + GetSpellData()->req_concentration = req_concentration; + valSet = true; + } + else if (field == "range") + { + float range = lua_interface->GetFloatValue(state, fieldArg); + GetSpellData()->range = range; + valSet = true; + } + else if (field == "duration1") + { + sint32 duration = lua_interface->GetSInt32Value(state, fieldArg); + GetSpellData()->duration1 = duration; + valSet = true; + } + else if (field == "duration2") + { + sint32 duration = lua_interface->GetSInt32Value(state, fieldArg); + GetSpellData()->duration2 = duration; + valSet = true; + } + else if (field == "resistibility") + { + float resistibility = lua_interface->GetFloatValue(state, fieldArg); + GetSpellData()->resistibility = resistibility; + valSet = true; + } + else if (field == "duration_until_cancel") + { + bool duration_until_cancel = lua_interface->GetBooleanValue(state, fieldArg); + GetSpellData()->duration_until_cancel = duration_until_cancel; + valSet = true; + } + else if (field == "power_req_percent") + { + int8 power_req_percent = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->power_req_percent = power_req_percent; + valSet = true; + } + else if (field == "hp_req_percent") + { + int8 hp_req_percent = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->hp_req_percent = hp_req_percent; + valSet = true; + } + else if (field == "savagery_req_percent") + { + int8 savagery_req_percent = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->savagery_req_percent = savagery_req_percent; + valSet = true; + } + else if (field == "dissonance_req_percent") + { + int8 dissonance_req_percent = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->dissonance_req_percent = dissonance_req_percent; + valSet = true; + } + else if (field == "name") + { + string name = lua_interface->GetStringValue(state, fieldArg); + GetSpellData()->name.data = name; + valSet = true; + } + else if (field == "description") + { + string description = lua_interface->GetStringValue(state, fieldArg); + GetSpellData()->description.data = description; + valSet = true; + } + else if (field == "success_message") + { + string success_message = lua_interface->GetStringValue(state, fieldArg); + GetSpellData()->success_message = success_message; + valSet = true; + } + else if (field == "fade_message") + { + string fade_message = lua_interface->GetStringValue(state, fieldArg); + GetSpellData()->fade_message = fade_message; + valSet = true; + } + else if (field == "fade_message_others") + { + string fade_message_others = lua_interface->GetStringValue(state, fieldArg); + GetSpellData()->fade_message_others = fade_message_others; + valSet = true; + } + else if (field == "cast_type") + { + int8 cast_type = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->cast_type = cast_type; + valSet = true; + } + else if (field == "cast_type") + { + int32 call_frequency = lua_interface->GetInt32Value(state, fieldArg); + GetSpellData()->call_frequency = call_frequency; + valSet = true; + } + else if (field == "interruptable") + { + bool interruptable = lua_interface->GetBooleanValue(state, fieldArg); + GetSpellData()->interruptable = interruptable; + valSet = true; + } + else if (field == "spell_visual") + { + int32 spell_visual = lua_interface->GetInt32Value(state, fieldArg); + GetSpellData()->spell_visual = spell_visual; + valSet = true; + } + else if (field == "effect_message") + { + string effect_message = lua_interface->GetStringValue(state, fieldArg); + GetSpellData()->effect_message = effect_message; + valSet = true; + } + else if (field == "min_range") + { + float min_range = lua_interface->GetFloatValue(state, fieldArg); + GetSpellData()->min_range = min_range; + valSet = true; + } + else if (field == "can_effect_raid") + { + int8 can_effect_raid = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->can_effect_raid = can_effect_raid; + valSet = true; + } + else if (field == "affect_only_group_members") + { + int8 affect_only_group_members = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->affect_only_group_members = affect_only_group_members; + valSet = true; + } + else if (field == "group_spell") + { + int8 group_spell = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->group_spell = group_spell; + valSet = true; + } + else if (field == "hit_bonus") + { + float hit_bonus = lua_interface->GetFloatValue(state, fieldArg); + GetSpellData()->hit_bonus = hit_bonus; + valSet = true; + } + else if (field == "display_spell_tier") + { + int8 display_spell_tier = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->display_spell_tier = display_spell_tier; + valSet = true; + } + else if (field == "is_active") + { + int8 is_active = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->is_active = is_active; + valSet = true; + } + else if (field == "det_type") + { + int8 det_type = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->det_type = det_type; + valSet = true; + } + else if (field == "incurable") + { + bool incurable = lua_interface->GetBooleanValue(state, fieldArg); + GetSpellData()->incurable = incurable; + valSet = true; + } + else if (field == "control_effect_type") + { + int8 control_effect_type = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->control_effect_type = control_effect_type; + valSet = true; + } + else if (field == "casting_flags") + { + int32 casting_flags = lua_interface->GetInt32Value(state, fieldArg); + GetSpellData()->casting_flags = casting_flags; + valSet = true; + } + else if (field == "cast_while_moving") + { + bool cast_while_moving = lua_interface->GetBooleanValue(state, fieldArg); + GetSpellData()->cast_while_moving = cast_while_moving; + valSet = true; + } + else if (field == "persist_through_death") + { + bool persist_through_death = lua_interface->GetBooleanValue(state, fieldArg); + GetSpellData()->persist_through_death = persist_through_death; + valSet = true; + } + else if (field == "not_maintained") + { + bool not_maintained = lua_interface->GetBooleanValue(state, fieldArg); + GetSpellData()->not_maintained = not_maintained; + valSet = true; + } + else if (field == "is_aa") + { + bool is_aa = lua_interface->GetBooleanValue(state, fieldArg); + GetSpellData()->is_aa = is_aa; + valSet = true; + } + else if (field == "savage_bar") + { + int8 savage_bar = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->savage_bar = savage_bar; + valSet = true; + } + else if (field == "spell_type") + { + int8 spell_type = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->spell_type = spell_type; + valSet = true; + } + else if (field == "type_group_spell_id") + { + sint32 type_group_spell_id = lua_interface->GetSInt32Value(state, fieldArg); + GetSpellData()->type_group_spell_id = type_group_spell_id; + valSet = true; + } + else if (field == "can_fizzle") + { + bool can_fizzle = lua_interface->GetBooleanValue(state, fieldArg); + GetSpellData()->can_fizzle = can_fizzle; + valSet = true; + } + + return valSet; +} +int16 Spell::GetSpellIcon(){ + if (spell) + return spell->icon; + return 0; +} + +int16 Spell::GetSpellIconBackdrop(){ + if (spell) + return spell->icon_backdrop; + return 0; +} + +int16 Spell::GetSpellIconHeroicOp(){ + if (spell) + return spell->icon_heroic_op; + return 0; +} + +bool Spell::IsHealSpell(){ + return heal_spell; +} + +bool Spell::IsBuffSpell(){ + return buff_spell; +} + +bool Spell::IsDamageSpell(){ + return damage_spell; +} +bool Spell::IsControlSpell(){ + return control_spell; +} + +bool Spell::IsOffenseSpell() { + return offense_spell; +} + +bool Spell::IsCopiedSpell() { + return copied_spell; +} + +void Spell::ModifyCastTime(Entity* caster){ + int16 cast_time = spell->orig_cast_time; + spell->cast_time = cast_time; + float cast_speed = caster->GetInfoStruct()->get_casting_speed(); + if (cast_speed > 0.0f){ + bool modifiedSpeed = false; + if (cast_speed > 0.0f && (modifiedSpeed = true)) // casting speed can only reduce up to half a cast time + spell->cast_time *= max(0.5f, (float) (1 / (1 + (cast_speed * .01)))); + else if (cast_speed < 0.0f && (modifiedSpeed = true)) // not sure if casting speed debuff is capped on live or not, capping at 1.5 * the normal rate for now + spell->cast_time *= min(1.5f, (float) (1 + (1 - (1 / (1 + (cast_speed * -.01)))))); + + if(modifiedSpeed) { + LogWrite(SPELL__DEBUG, 9, "Spells", "%s: spell %s cast time %u to %u based on cast_speed %f", GetName(), caster->GetName(), cast_time, spell->cast_time, cast_speed); + } + } +} + +int32 Spell::CalculateRecastTimer(Entity* caster, float override_timer) { + int32 original_recast = static_cast(GetSpellData()->recast * 1000.0f); + + if(override_timer > 0.0f) { + original_recast = static_cast(override_timer); + } + + int32 recast_time = original_recast; + float cast_speed = caster->GetInfoStruct()->get_spell_reuse_speed(); + if (cast_speed > 0.0f){ + bool modifiedSpeed = false; + if (cast_speed > 0.0f && (modifiedSpeed = true)) // casting speed can only reduce up to half a cast time + recast_time *= max(0.5f, (float) (1 / (1 + (cast_speed * .01)))); + else if (cast_speed < 0.0f && (modifiedSpeed = true)) // not sure if casting speed debuff is capped on live or not, capping at 1.5 * the normal rate for now + recast_time *= min(1.5f, (float) (1 + (1 - (1 / (1 + (cast_speed * -.01)))))); + + if(modifiedSpeed) { + LogWrite(SPELL__DEBUG, 9, "Spells", "%s: spell %s recast time %u to %u based on spell_reuse_time %f", GetName(), caster->GetName(), original_recast, recast_time, cast_speed); + } + } + return recast_time; +} + +vector * Spell::GetSpellEffects(){ + std::shared_lock lock(MSpellInfo); + vector * ret = &effects; + return ret; +} + +vector * Spell::GetSpellLevels(){ + std::shared_lock lock(MSpellInfo); + vector * ret = &levels; + return ret; +} + +bool Spell::ScribeAllowed(Player* player){ + std::shared_lock lock(MSpellInfo); + bool ret = false; + if(player){ + for(int32 i=0;!ret && iGetLevel(); + int16 spelllevels = levels[i]->spell_level; + bool advlev = player->GetAdventureClass() == levels[i]->adventure_class; + bool tslev = player->GetTradeskillClass() == levels[i]->tradeskill_class; + bool levelmatch = player->GetLevel() >= levels[i]->spell_level; + if((player->GetAdventureClass() == levels[i]->adventure_class || player->GetTradeskillClass() == levels[i]->tradeskill_class) && player->GetLevel() >= levels[i]->spell_level/10) + ret = true; + } + } + return ret; +} + +MasterSpellList::MasterSpellList(){ + max_spell_id = 0; + MMasterSpellList.SetName("MasterSpellList::MMasterSpellList"); +} + +MasterSpellList::~MasterSpellList(){ + DestroySpells(); +} +void MasterSpellList::DestroySpells(){ + + spell_errors.clear(); + + MMasterSpellList.lock(); + map >::iterator iter; + map::iterator iter2; + for(iter = spell_list.begin();iter != spell_list.end(); iter++){ + for(iter2 = iter->second.begin();iter2 != iter->second.end(); iter2++){ + safe_delete(iter2->second); + } + } + spell_list.clear(); + for(int i=0;i* levels = spell->GetSpellLevels(); + LevelArray* level = 0; + vector::iterator level_itr; + for(level_itr = levels->begin(); level_itr != levels->end(); level_itr++){ + level = *level_itr; + if(level->adventure_class && level->adventure_class < MAX_CLASSES){ + class_spell_list[level->adventure_class][id][tier] = spell; + } + if(level->tradeskill_class && level->tradeskill_class < MAX_CLASSES) { + class_spell_list[level->tradeskill_class][id][tier] = spell; + } + } + + spell_name_map[spell->GetName()] = spell; + spell_soecrc_map[spell->GetSpellData()->soe_spell_crc] = spell; + + if (id > max_spell_id) + max_spell_id = id; + + MMasterSpellList.unlock(); +} + +Spell* MasterSpellList::GetSpell(int32 id, int8 tier){ + if (spell_list.count(id) > 0 && spell_list[id].count(tier) > 0) + return spell_list[id][tier]; + else if (spell_list.count(id) > 0 && tier == 0 && spell_list[id].count(1) > 0) + return spell_list[id][1]; + return 0; +} + +Spell* MasterSpellList::GetSpellByName(const char* name){ + if(spell_name_map.count(name) > 0) + return spell_name_map[name]; + return 0; +} + +Spell* MasterSpellList::GetSpellByCRC(int32 spell_crc){ + if(spell_soecrc_map.count(spell_crc) > 0) + return spell_soecrc_map[spell_crc]; + return 0; +} + +EQ2Packet* MasterSpellList::GetSpellPacket(int32 id, int8 tier, Client* client, bool display, int8 packet_type){ + Spell* spell = GetSpell(id, tier); + + // if we can't find it on the master spell list, see if it is a custom spell + if (!spell) + { + lua_interface->FindCustomSpellLock(); + LuaSpell* tmpSpell = lua_interface->FindCustomSpell(id); + EQ2Packet* pack = 0; + if (tmpSpell) + { + spell = tmpSpell->spell; + pack = spell->SerializeSpell(client, display, packet_type); + } + + lua_interface->FindCustomSpellUnlock(); + return pack; + } + + if(spell) + return spell->SerializeSpell(client, display, packet_type); + return 0; +} +EQ2Packet* MasterSpellList::GetAASpellPacket(int32 id, int8 tier, Client* client, bool display, int8 packet_type) { + Spell* spell = GetSpell(id, (tier == 0 ? 1 : tier)); + + // if we can't find it on the master spell list, see if it is a custom spell + if (!spell) + { + lua_interface->FindCustomSpellLock(); + LuaSpell* tmpSpell = lua_interface->FindCustomSpell(id); + EQ2Packet* pack = 0; + if (tmpSpell) + { + spell = tmpSpell->spell; + // TODO: this isn't a tested thing yet... need to add custom spells to alt advancement? + AltAdvanceData* data = master_aa_list.GetAltAdvancement(id); + + if(data) + pack = spell->SerializeAASpell(client, tier, data, display, false, packet_type); + } + + lua_interface->FindCustomSpellUnlock(); + return pack; + } + + //Spell* spell2= GetSpell(id, (tier +1)); + AltAdvanceData* data = master_aa_list.GetAltAdvancement(id); + if (spell) + return spell->SerializeAASpell(client,tier, data, display,false, packet_type); + return 0; +} + +EQ2Packet* MasterSpellList::GetSpecialSpellPacket(int32 id, int8 tier, Client* client, bool display, int8 packet_type){ + Spell* spell = GetSpell(id, tier); + + // if we can't find it on the master spell list, see if it is a custom spell + if (!spell) + { + lua_interface->FindCustomSpellLock(); + LuaSpell* tmpSpell = lua_interface->FindCustomSpell(id); + EQ2Packet* pack = 0; + if (tmpSpell) + { + spell = tmpSpell->spell; + pack = spell->SerializeSpecialSpell(client, display, packet_type, 0x81); + } + + lua_interface->FindCustomSpellUnlock(); + return pack; + } + + if(spell) + return spell->SerializeSpecialSpell(client, display, packet_type, 0x81); + return 0; +} + +vector* MasterSpellList::GetSpellListByAdventureClass(int8 class_id, int16 max_level, int8 max_tier){ + vector* ret = new vector; + if(class_id >= MAX_CLASSES) { + return ret; + } + + Spell* spell = 0; + vector* levels = 0; + LevelArray* level = 0; + vector::iterator level_itr; + MMasterSpellList.lock(); + map >::iterator iter; + map::iterator iter2; + max_level *= 10; //convert to client level format, which is 10 times higher + for(iter = class_spell_list[class_id].begin();iter != class_spell_list[class_id].end(); iter++){ + for(iter2 = iter->second.begin();iter2 != iter->second.end(); iter2++){ + spell = iter2->second; + if(iter2->first <= max_tier && spell && spell->GetSpellData()->given_by_type != GivenByType::GivenBy_SpellScroll && + spell->GetSpellData()->given_by_type != GivenByType::GivenBy_TradeskillClass){ + levels = spell->GetSpellLevels(); + for(level_itr = levels->begin(); level_itr != levels->end(); level_itr++){ + level = *level_itr; + if(level->spell_level <= max_level && level->adventure_class == class_id){ + ret->push_back(spell); + break; + } + } + } + } + } + MMasterSpellList.unlock(); + return ret; +} + +vector* MasterSpellList::GetSpellListByTradeskillClass(int8 class_id, int16 max_level, int8 max_tier){ + vector* ret = new vector; + if(class_id >= MAX_CLASSES) { + return ret; + } + + Spell* spell = 0; + vector* levels = 0; + LevelArray* level = 0; + vector::iterator level_itr; + MMasterSpellList.lock(); + map >::iterator iter; + map::iterator iter2; + for(iter = class_spell_list[class_id].begin();iter != class_spell_list[class_id].end(); iter++){ + for(iter2 = iter->second.begin();iter2 != iter->second.end(); iter2++){ + spell = iter2->second; + if(iter2->first <= max_tier && spell && spell->GetSpellData()->given_by_type == GivenByType::GivenBy_TradeskillClass){ + levels = spell->GetSpellLevels(); + for(level_itr = levels->begin(); level_itr != levels->end(); level_itr++){ + level = *level_itr; + if(level->spell_level <= max_level && level->tradeskill_class == class_id){ + ret->push_back(spell); + break; + } + } + } + } + } + MMasterSpellList.unlock(); + return ret; +} + +void MasterSpellList::Reload(){ + master_trait_list.DestroyTraits(); + DestroySpells(); + database.LoadSpells(); + database.LoadSpellErrors(); + database.LoadTraits(); +} + +int16 MasterSpellList::GetSpellErrorValue(int16 version, int8 error_index) { + version = GetClosestVersion(version); + + if (spell_errors[version].count(error_index) == 0) { + LogWrite(SPELL__ERROR, 0, "Spells", "No spell error entry. (version = %i, error_index = %i)", version, error_index); + // 1 will give the client a pop up message of "Cannot cast" and a chat message of "[BUG] Cannot cast. Unknown failure casting spell." + return 1; + } + return spell_errors[version][error_index]; +} + +void MasterSpellList::AddSpellError(int16 version, int8 error_index, int16 error_value) { + if (spell_errors[version].count(error_index) == 0) + spell_errors[version][error_index] = error_value; +} + +int16 MasterSpellList::GetClosestVersion(int16 version) { + int16 ret = 0; + map >::iterator itr; + // Get the closest version in the list that is less then or equal to the given version + for (itr = spell_errors.begin(); itr != spell_errors.end(); itr++) { + if (itr->first <= version) { + if (itr->first > ret) + ret = itr->first; + } + } + + return ret; +} + +bool Spell::CastWhileStunned(){ + return (spell->casting_flags & CASTING_FLAG_STUNNED) == CASTING_FLAG_STUNNED; +} + +bool Spell::CastWhileMezzed(){ + return (spell->casting_flags & CASTING_FLAG_MEZZED) == CASTING_FLAG_MEZZED; +} + +bool Spell::CastWhileStifled(){ + return (spell->casting_flags & CASTING_FLAG_STIFLED) == CASTING_FLAG_STIFLED; +} + +bool Spell::CastWhileFeared(){ + return (spell->casting_flags & CASTING_FLAG_FEARED) == CASTING_FLAG_FEARED; +} diff --git a/source/WorldServer/Spells.h b/source/WorldServer/Spells.h new file mode 100644 index 0000000..5272219 --- /dev/null +++ b/source/WorldServer/Spells.h @@ -0,0 +1,446 @@ +/* + 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 . +*/ +#ifndef __EQ2_SPELLS__ +#define __EQ2_SPELLS__ +#include +#include +#include +#include +#include "../common/types.h" +#include "../common/EQPacket.h" +#include "../common/MiscFunctions.h" +#include "client.h" +#include "classes.h" +#include "../common/Mutex.h" +#include "AltAdvancement/AltAdvancement.h" + +#include "../LUA/lua.hpp" + +#define SPELL_TARGET_SELF 0 +#define SPELL_TARGET_ENEMY 1 +#define SPELL_TARGET_GROUP_AE 2 +#define SPELL_TARGET_CASTER_PET 3 +#define SPELL_TARGET_ENEMY_PET 4 +#define SPELL_TARGET_ENEMY_CORPSE 5 +#define SPELL_TARGET_GROUP_CORPSE 6 +#define SPELL_TARGET_NONE 7 +#define SPELL_TARGET_RAID_AE 8 +#define SPELL_TARGET_OTHER_GROUP_AE 9 + + +#define SPELL_BOOK_TYPE_SPELL 0 +#define SPELL_BOOK_TYPE_COMBAT_ART 1 +#define SPELL_BOOK_TYPE_ABILITY 2 +#define SPELL_BOOK_TYPE_TRADESKILL 3 +#define SPELL_BOOK_TYPE_NOT_SHOWN 4 + +#define SPELL_CAST_TYPE_NORMAL 0 +#define SPELL_CAST_TYPE_TOGGLE 1 + + +#define SPELL_ERROR_NOT_ENOUGH_KNOWLEDGE 1 +#define SPELL_ERROR_INTERRUPTED 2 +#define SPELL_ERROR_TAKE_EFFECT_MOREPOWERFUL 3 +#define SPELL_ERROR_TAKE_EFFECT_SAMESPELL 4 +#define SPELL_ERROR_CANNOT_CAST_DEAD 5 +#define SPELL_ERROR_NOT_ALIVE 6 +#define SPELL_ERROR_NOT_DEAD 7 +#define SPELL_ERROR_CANNOT_CAST_SITTING 8 +#define SPELL_ERROR_CANNOT_CAST_UNCON 9 +#define SPELL_ERROR_ALREADY_CASTING 10 +#define SPELL_ERROR_RECOVERING 11 +#define SPELL_ERROR_NON_COMBAT_ONLY 12 +#define SPELL_ERROR_CANNOT_CAST_STUNNED 13 +#define SPELL_ERROR_CANNOT_CAST_STIFFLED 14 +#define SPELL_ERROR_CANNOT_CAST_CHARMED 15 +#define SPELL_ERROR_NOT_WHILE_MOUNTED 16 +#define SPELL_ERROR_NOT_WHILE_FLYING 17 +#define SPELL_ERROR_NOT_WHILE_CLIMBING 18 +#define SPELL_ERROR_NOT_READY 19 +#define SPELL_ERROR_CANT_SEE_TARGET 20 +#define SPELL_ERROR_INCORRECT_STANCE 21 +#define SPELL_ERROR_CANNOT_CAST_FEIGNDEATH 22 +#define SPELL_ERROR_INVENTORY_FULL 23 +#define SPELL_ERROR_NOT_ENOUGH_COIN 24 +#define SPELL_ERROR_NOT_ALLOWED_HERE 25 +#define SPELL_ERROR_NOT_WHILE_CRAFTING 26 +#define SPELL_ERROR_ONLY_WHEN_CRAFTING 27 +#define SPELL_ERROR_ITEM_NOT_ATTUNED 28 +#define SPELL_ERROR_ITEM_WORN_OUT 29 +#define SPELL_ERROR_MUST_EQUIP_WEAPON 30 +#define SPELL_ERROR_WEAPON_BROKEN 31 +#define SPELL_ERROR_CANNOT_CAST_FEARED 32 +#define SPELL_ERROR_TARGET_IMMUNE_HOSTILE 33 +#define SPELL_ERROR_TARGET_IMMUNE_BENEFICIAL 34 +#define SPELL_ERROR_NO_TAUNT_SPELLS 35 +#define SPELL_ERROR_CANNOT_USE_IN_BATTLEGROUNDS 36 +#define SPELL_ERROR_CANNOT_PREPARE 37 +#define SPELL_ERROR_NO_ELIGIBLE_TARGET 38 +#define SPELL_ERROR_NO_TARGETS_IN_RANGE 39 +#define SPELL_ERROR_TOO_CLOSE 40 +#define SPELL_ERROR_TOO_FAR_AWAY 41 +#define SPELL_ERROR_TARGET_TOO_WEAK 42 +#define SPELL_ERROR_TARGET_TOO_POWERFUL 43 +#define SPELL_ERROR_WONT_WORK_ON_TARGET 44 +#define SPELL_ERROR_TARGET_INVULNERABLE 45 +#define SPELL_ERROR_TARGET_IMMUNE 46 +#define SPELL_ERROR_TARGET_ENGAGED 47 +#define SPELL_ERROR_TARGET_NOT_GROUPED 48 +#define SPELL_ERROR_TARGET_IN_USE 49 +#define SPELL_ERROR_TARGET_GROUP_HAS_SPELL 50 +#define SPELL_ERROR_TARGET_ALREADY_ENGAGED 51 +#define SPELL_ERROR_CANNOT_ENGAGE 52 +#define SPELL_ERROR_NOT_A_FRIEND 53 +#define SPELL_ERROR_NOT_AN_ENEMY 54 +#define SPELL_ERROR_TARGET_INVENTORY_FULL 55 +#define SPELL_ERROR_FINISH_DUELING_FIRST 56 +#define SPELL_ERROR_ILLEGAL_TARGET_ATTACK 57 +#define SPELL_ERROR_NOT_WHILE_MENTORING_PVP 58 +#define SPELL_ERROR_NOT_WHILE_MENTORING_BENEFICIAL 59 +#define SPELL_ERROR_ILLEGAL_TARGET_HEAL_OUTSIDE_LEVEL_RANGE 60 +#define SPELL_ERROR_NOTHING_TO_CURE 61 +#define SPELL_ERROR_NOT_ENOUGH_POWER 62 +#define SPELL_ERROR_NOT_ENOUGH_HEALTH 63 +#define SPELL_ERROR_NOT_ENOUGH_CONC 64 +#define SPELL_ERROR_MISSING_COMPONENT 65 +#define SPELL_ERROR_OUT_OF_CHARGES 66 +#define SPELL_ERROR_LACK_AMMO 67 +#define SPELL_ERROR_NO_RANGED_EQUIPPED 68 +#define SPELL_ERROR_RANGED_NEEDS_REPAIR 69 +#define SPELL_ERROR_LACK_WEAPON_TYPE 70 +#define SPELL_ERROR_NOT_ENOUGH_SAVAGERY 71 +#define SPELL_ERROR_ALREADY_PREPARED 72 +#define SPELL_ERROR_ALREADY_HAVE_SPELL 73 +#define SPELL_ERROR_NOT_SMART_ENOUGH 74 // "You lack the intellectual capacity to prepare another spell." +#define SPELL_ERROR_NO_HOSTILE_SPELLS 75 +#define SPELL_ERROR_NO_BENEFICIAL_SPELLS 76 +#define SPELL_ERROR_CANNOT_MOUNT_NOW_SITTING 77 +#define SPELL_ERROR_CANNOT_MOUNT_NOW_DEAD 78 +#define SPELL_ERROR_CANNOT_MOUNT_NOW_CLIMBING 79 +#define SPELL_ERROR_CANNOT_MOUNT_NOW_FORM 80 +#define SPELL_ERROR_CANNOT_MOUNT_NOW_WATER_TO_DEEP 81 +#define SPELL_ERROR_ALREADY_CAST 82 +#define SPELL_ERROR_LOTTERY_IN_PROGRESS 83 +#define SPELL_ERROR_NOT_IN_PVP 84 +#define SPELL_ERROR_NOT_ENOUGH_DISSONANCE 85 +#define SPELL_ERROR_NOT_CANNOT_CAST_BUG_UNKNOWN_FAILURE 86 +#define SPELL_ERROR_NOT_CANNOT_CAST_BUG_SPELL_TEMPLATE 87 +#define SPELL_ERROR_NOT_PREPARED_BUG 88 +#define SPELL_ERROR_NOT_CANNOT_CAST_BUG_NO_GAME_WORLD 89 +#define SPELL_ERROR_NOT_CANNOT_CAST_BUG_NO_OWNER 90 +#define SPELL_ERROR_NOT_CANNOT_CAST_BUG_OWNER_TYPE 91 +#define SPELL_ERROR_NOT_CANNOT_CAST_BUG_NO_CASTER 92 +#define SPELL_ERROR_NO_RESPONSE_10 93 +#define SPELL_ERROR_BUG_PARTIAL_INTERUPT 94 +#define SPELL_ERROR_NO_RESPONSE_15 95 +#define SPELL_ERROR_BUG_TARGET_RESISTED 96 +#define SPELL_ERROR_BUG_TARGET_REFLECTED 97 +#define SPELL_ERROR_NO_RESPONSE_18 98 +#define SPELL_ERROR_NO_RESPONSE_35 99 +#define SPELL_ERROR_BUG_UNKNOWN_43 100 +#define SPELL_ERROR_BUG_UNKNOWN_44 101 +#define SPELL_ERROR_BUG_UNKNOWN_47 102 +#define SPELL_ERROR_TARGET_IMMUNE_HEALED_WITH_REPAIRS 103 +#define SPELL_ERROR_NOT_WHILE_MENTORING 104 +#define SPELL_ERROR_BUG_NO_EFFECTS_LANDED 105 +#define SPELL_ERROR_TOO_MUCH_DISSONANCE 106 +#define SPELL_ERROR_BUG_INVALID_SPELL_INDEX 107 +#define SPELL_ERROR_CANNOT_CAST_NOT_FOUND_95 108 +#define SPELL_ERROR_BUG_CONTAINMENT_TYPE 109 +#define SPELL_ERROR_BUG_SLOT_FULL 110 +#define SPELL_ERROR_CANNOT_CAST_NO_SPELL_101 111 +#define SPELL_ERROR_RECOVERING_ITEM_ABILITY 112 +#define SPELL_ERROR_NO_RESPONSE_110 113 +#define SPELL_ERROR_ALREADY_CAST_ON_TARGET 114 + +#define CASTING_FLAG_MEZZED 1 +#define CASTING_FLAG_STIFLED 2 +#define CASTING_FLAG_STUNNED 4 +#define CASTING_FLAG_FEARED 8 + +// Spell type is for AI so code knows what a spell is +#define SPELL_TYPE_UNSET 1 +#define SPELL_TYPE_DD 2 +#define SPELL_TYPE_DOT 3 +#define SPELL_TYPE_HEAL 4 +#define SPELL_TYPE_HOT_WARD 5 +#define SPELL_TYPE_DEBUFF 6 +#define SPELL_TYPE_BUFF 7 +#define SPELL_TYPE_COMBATBUFF 8 +#define SPELL_TYPE_TAUNT 9 +#define SPELL_TYPE_DETAUNT 10 +#define SPELL_TYPE_REZ 11 +#define SPELL_TYPE_CURE 12 +#define SPELL_TYPE_FOOD 13 +#define SPELL_TYPE_DRINK 14 +#define SPELL_TYPE_ROOT 15 +#define SPELL_TYPE_SNARE 16 +#define SPELL_TYPE_ALLGROUPTARGETS 17 + + +struct LUAData{ + int8 type; + sint32 int_value; + bool bool_value; + float float_value; + string string_value; + string string_value2; + sint32 int_value2; + float float_value2; + string string_helper; +}; +struct SpellScriptTimer { + LuaSpell* spell; + string customFunction; + int32 time; + int32 caster; + int32 target; + bool deleteWhenDone; +}; +struct LevelArray{ + int8 adventure_class; + int8 tradeskill_class; + int16 spell_level; +}; +struct SpellDisplayEffect{ + int8 percentage; + int8 subbullet; + string description; +}; + +enum GivenByType { + GivenBy_Unset = 0, + GivenBy_TradeskillClass = 1, + GivenBy_SpellScroll = 2, + GivenBy_AltAdvancement = 3, + GivenBy_Race = 4, + GivenBy_RacialInnate = 5, + GivenBy_RacialTradition = 6, + GivenBy_Class = 7, + GivenBy_CharacterTrait = 8, + GivenBy_FocusAbility = 9, + GivenBy_ClassTraining = 10, + GivenBy_WarderSpell = 11 +}; + +struct SpellData{ + int32 spell_book_type; + int32 id; + int32 inherited_spell_id; + sint16 icon; + int16 icon_heroic_op; + int16 icon_backdrop; + int16 type; + int32 class_skill; + int16 min_class_skill_req; + int32 mastery_skill; + int8 ts_loc_index; + int8 num_levels; + int8 tier; + int16 hp_req; + int16 hp_upkeep; + float power_req; + bool power_by_level; + int16 power_upkeep; + int16 savagery_req; + int16 savagery_upkeep; + int16 dissonance_req; + int16 dissonance_upkeep; + int8 target_type; + int16 cast_time; + int16 orig_cast_time; + float recovery; + float recast; + int32 linked_timer; + float radius; + int16 max_aoe_targets; + int8 friendly_spell; + int16 req_concentration; + float range; + int32 duration1; + int32 duration2; + float resistibility; + bool duration_until_cancel; + int8 power_req_percent; + int8 hp_req_percent; + int8 savagery_req_percent; + int8 dissonance_req_percent; + EQ2_8BitString name; + EQ2_16BitString description; + string success_message; + string fade_message; + string fade_message_others; + int8 cast_type; + string lua_script; + int32 call_frequency; + bool interruptable; + int32 spell_visual; + string effect_message; + float min_range; + int8 can_effect_raid; + int8 affect_only_group_members; + int8 group_spell; + float hit_bonus; + int8 display_spell_tier; + int8 is_active; + int8 det_type; + bool incurable; + int8 control_effect_type; + int32 casting_flags; + bool cast_while_moving; + bool persist_through_death; + bool not_maintained; + bool is_aa; + int8 savage_bar; + int8 savage_bar_slot; + int32 soe_spell_crc; + int8 spell_type; + int32 spell_name_crc; + sint32 type_group_spell_id; + bool can_fizzle; + EQ2_8BitString given_by; + GivenByType given_by_type; +}; + +class Spell{ +public: + ~Spell(); + Spell(); + Spell(SpellData* in_spell); + Spell(Spell* host_spell, bool unique_spell = true); + EQ2Packet* SerializeSpell(Client* client, bool display, bool trait_display = false, int8 packet_type = 0, int8 sub_packet_type = 0, const char* struct_name = 0, bool send_partial_packet = false); + EQ2Packet* SerializeSpecialSpell(Client* client, bool display, int8 packet_type = 0, int8 sub_packet_type = 0); + EQ2Packet* SerializeAASpell(Client* client,int8 tier, AltAdvanceData* data, bool display, bool trait_display = false, int8 packet_type = 0, int8 sub_packet_type = 0, const char* struct_name = 0); + void AddSpellLevel(int8 adventure_class, int8 tradeskill_class, int16 level); + void AddSpellEffect(int8 percentage, int8 subbullet, string description); + void AddSpellLuaData(int8 type, int int_value, int int_value2, float float_value, float float_value2, bool bool_value, string string_value,string string_value2, string helper); + void AddSpellLuaDataInt(int value, int value2, string helper); + void AddSpellLuaDataFloat(float value, float value2, string helper); + void AddSpellLuaDataBool(bool value, string helper); + void AddSpellLuaDataString(string value, string value2, string helper); + int32 GetSpellID(); + sint16 TranslateClientSpellIcon(int16 version); + void SetPacketInformation(PacketStruct* packet, Client* client = 0, bool display_tier = false); + void SetAAPacketInformation(PacketStruct* packet, AltAdvanceData* data, Client* client = 0, bool display_tier = false); + void AppendLevelInformation(PacketStruct* packet); + int8 GetSpellTier(); + int32 GetSpellDuration(); + int16 GetSpellIcon(); + int16 GetSpellIconBackdrop(); + int16 GetSpellIconHeroicOp(); + int16 GetLevelRequired(Player* player); + int16 GetHPRequired(Spawn* spawn); + int16 GetPowerRequired(Spawn* spawn); + int16 GetSavageryRequired(Spawn* spawn); + int16 GetDissonanceRequired(Spawn* spawn); + SpellData* GetSpellData(); + bool GetSpellData(lua_State* state, std::string field); + bool SetSpellData(lua_State* state, std::string field, int8 fieldArg); + bool ScribeAllowed(Player* player); + vector* GetLUAData(); + vector * GetSpellLevels(); + vector * GetSpellEffects(); + const char* GetName(); + const char* GetDescription(); + bool IsHealSpell(); + bool IsBuffSpell(); + bool IsDamageSpell(); + bool IsControlSpell(); + bool IsOffenseSpell(); + bool IsCopiedSpell(); + void ModifyCastTime(Entity* caster); + int32 CalculateRecastTimer(Entity* caster, float override_timer = 0.0f); + bool CastWhileStunned(); + bool CastWhileMezzed(); + bool CastWhileStifled(); + bool CastWhileFeared(); + bool GetStayLocked() { return stay_locked; } + void StayLocked(bool val) { stay_locked = val; } + + vector effects; + vector lua_data; + + mutable std::shared_mutex MSpellInfo; +private: + bool stay_locked = false; + bool heal_spell; + bool buff_spell; + bool damage_spell; + bool control_spell; + bool offense_spell; + bool copied_spell; + + SpellData* spell; + + //vector effects; + vector levels; +}; +class MasterSpellList{ +public: + MasterSpellList(); + ~MasterSpellList(); + void DestroySpells(); + map spell_name_map; + map > spell_list; + map > class_spell_list[MAX_CLASSES]; + map spell_soecrc_map; + Spell* GetSpell(int32 id, int8 tier); + vector* GetSpellListByAdventureClass(int8 class_id, int16 max_level, int8 max_tier); + vector* GetSpellListByTradeskillClass(int8 class_id, int16 max_level, int8 max_tier); + Spell* GetSpellByName(const char* name); + Spell* GetSpellByCRC(int32 spell_crc); + void Reload(); + EQ2Packet* GetSpellPacket(int32 id, int8 tier, Client* client = 0, bool display = false, int8 packet_type = 0); + EQ2Packet* GetAASpellPacket(int32 id, int8 group, Client* client, bool display, int8 packet_type); + EQ2Packet* GetSpecialSpellPacket(int32 id, int8 tier, Client* client = 0, bool display = false, int8 packet_type = 0); + void AddSpell(int32 id, int8 tier, Spell* spell); + Mutex MMasterSpellList; + + /// Gets the correct spell error value for the given version + /// Client version + /// ID of the error + /// The int16 value for the given error and version + int16 GetSpellErrorValue(int16 version, int8 error_index); + + /// Adds a spell error to the list + /// Client version for the error + /// ID for the error + /// Value for the error + void AddSpellError(int16 version, int8 error_index, int16 error_value); + + int32 GetNewMaxSpellID() { + int32 id = 0; + MMasterSpellList.lock(); + max_spell_id++; + id = max_spell_id; + MMasterSpellList.unlock(); + return id; + } +private: + /// Helper function that gets the closest version in the spell_errors map that is less then or equal to the given version + /// Client version + /// int16 version that is closest to the given version + int16 GetClosestVersion(int16 version); + // map > + map > spell_errors; + int32 max_spell_id; +}; +#endif + diff --git a/source/WorldServer/Titles.cpp b/source/WorldServer/Titles.cpp new file mode 100644 index 0000000..8c0e39c --- /dev/null +++ b/source/WorldServer/Titles.cpp @@ -0,0 +1,162 @@ +/* + 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 . +*/ + +#include +#include +#include "Titles.h" +#include "../common/MiscFunctions.h" + +Title::Title(){ + id = 0; + memset(name, 0, sizeof(name)); + prefix = 0; + save_needed = false; +} + +Title::Title(Title* title){ + id = title->id; + strncpy(name, title->GetName(), sizeof(name)); + prefix = title->prefix; + save_needed = title->save_needed; +} + +Title::~Title(){ +} + +MasterTitlesList::MasterTitlesList(){ + MMasterTitleMutex.SetName("MasterTitlesList::MMasterTitleMutex"); +} + +MasterTitlesList::~MasterTitlesList(){ + Clear(); +} + +void MasterTitlesList::Clear(){ + MMasterTitleMutex.writelock(); + map::iterator itr; + for(itr = titles_list.begin(); itr != titles_list.end(); itr++) + safe_delete(itr->second); + titles_list.clear(); + MMasterTitleMutex.releasewritelock(); +} + +void MasterTitlesList::AddTitle(Title* title){ + assert(title); + MMasterTitleMutex.writelock(); + if(titles_list.count(title->GetID()) == 0) + titles_list[title->GetID()] = title; + MMasterTitleMutex.releasewritelock(); +} + +int32 MasterTitlesList::Size(){ + int32 size = 0; + MMasterTitleMutex.readlock(); + size = titles_list.size(); + MMasterTitleMutex.releasereadlock(); + + return size; +} + +Title* MasterTitlesList::GetTitle(sint32 id){ + Title* title = 0; + + MMasterTitleMutex.readlock(); + if(titles_list.count(id) > 0) + title = titles_list[id]; + MMasterTitleMutex.releasereadlock(); + + return title; +} + +Title* MasterTitlesList::GetTitleByName(const char* title_name){ + Title* title = 0; + map::iterator itr; + MMasterTitleMutex.readlock(); + for(itr = titles_list.begin(); itr != titles_list.end(); itr++){ + Title* current_title = itr->second; + if(::ToLower(string(current_title->GetName())) == ::ToLower(string(title_name))){ + title = current_title; + break; + } + } + MMasterTitleMutex.releasereadlock(); + return title; +} + +PlayerTitlesList::PlayerTitlesList(){ + MPlayerTitleMutex.SetName("PlayerTitlesList::MPlayerTitleMutex"); +} + +PlayerTitlesList::~PlayerTitlesList(){ + MPlayerTitleMutex.writelock(); + vector::iterator itr; + for (itr = player_titles_list.begin(); itr != player_titles_list.end(); itr++) + safe_delete(*itr); + + player_titles_list.clear(); + MPlayerTitleMutex.releasewritelock(); +} + +Title* PlayerTitlesList::GetTitle(sint32 index){ + MPlayerTitleMutex.readlock(); + Title* title = 0; + Title* ret = 0; + if ( index < player_titles_list.size() ) + ret = player_titles_list[index]; + + MPlayerTitleMutex.releasereadlock(); + return ret; +} + +Title* PlayerTitlesList::GetTitleByName(const char* title_name){ + Title* resTitle = 0; + vector::iterator itr; + MPlayerTitleMutex.readlock(); + for(itr = player_titles_list.begin(); itr != player_titles_list.end(); itr++){ + Title* title = *itr; + if(::ToLower(string(title->GetName())) == ::ToLower(string(title_name))){ + resTitle = title; + break; + } + } + MPlayerTitleMutex.releasereadlock(); + return resTitle; +} + + +vector* PlayerTitlesList::GetAllTitles(){ + MPlayerTitleMutex.readlock(); + return &player_titles_list; +} + +void PlayerTitlesList::Add(Title* title){ + MPlayerTitleMutex.writelock(); + player_titles_list.push_back(title); + MPlayerTitleMutex.releasewritelock(); +} + +int32 PlayerTitlesList::Size(){ + int32 size = 0; + MPlayerTitleMutex.readlock(); + size = player_titles_list.size(); + MPlayerTitleMutex.releasereadlock(); + + return size; +} \ No newline at end of file diff --git a/source/WorldServer/Titles.h b/source/WorldServer/Titles.h new file mode 100644 index 0000000..78e6f80 --- /dev/null +++ b/source/WorldServer/Titles.h @@ -0,0 +1,83 @@ +/* + 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 . +*/ +#ifndef TITLES_H_ +#define TITLES_H_ + +#include +#include +#include +#include "../common/Mutex.h" +#include "../common/types.h" + +using namespace std; + +class Title { +public: + Title(); + Title(Title* title); + ~Title(); + void SetID(int32 id) {this->id = id;} + void SetName(const char *name) {strncpy(this->name, name, sizeof(this->name));} + void SetPrefix(int8 prefix) {this->prefix = prefix;} + void SetSaveNeeded(bool save_needed) {this->save_needed = save_needed;} + + sint32 GetID() {return id;} + const char* GetName() {return name;} + int8 GetPrefix() {return prefix;} + bool GetSaveNeeded() {return save_needed;} + +private: + sint32 id; + int8 prefix; + char name[256]; + bool save_needed; +}; + +class MasterTitlesList { +public: + MasterTitlesList(); + ~MasterTitlesList(); + void Clear(); + int32 Size(); + void AddTitle(Title* title); + Title* GetTitle(sint32 id); + Title* GetTitleByName(const char* title_name); + +private: + map titles_list; + Mutex MMasterTitleMutex; +}; + +class PlayerTitlesList { +public: + PlayerTitlesList(); + ~PlayerTitlesList(); + Title* GetTitle(sint32 index); + Title* GetTitleByName(const char* title_name); + vector* GetAllTitles(); + void Add(Title* title); + + int32 Size(); + void ReleaseReadLock() { MPlayerTitleMutex.releasereadlock(); } +private: + vector player_titles_list; + Mutex MPlayerTitleMutex; +}; +#endif \ No newline at end of file diff --git a/source/WorldServer/Trade.cpp b/source/WorldServer/Trade.cpp new file mode 100644 index 0000000..da9159e --- /dev/null +++ b/source/WorldServer/Trade.cpp @@ -0,0 +1,604 @@ +#include "Trade.h" +#include "Items/Items.h" +#include "Entity.h" +#include "Bots/Bot.h" +#include "../common/Log.h" +#include "Rules/Rules.h" + +extern ConfigReader configReader; +extern MasterItemList master_item_list; +extern RuleManager rule_manager; + +Trade::Trade(Entity* trader1, Entity* trader2) { + this->trader1 = trader1; + this->trader2 = trader2; + + trade_max_slots = 12; + + if(trader1->IsPlayer()) { + if(((Player*)trader1)->GetClient() && ((Player*)trader1)->GetClient()->GetVersion() <= 561) { + trade_max_slots = 6; + } + } + + if(trader2->IsPlayer()) { + if(((Player*)trader2)->GetClient() && ((Player*)trader2)->GetClient()->GetVersion() <= 561) { + trade_max_slots = 6; + } + } + trader1_accepted = false; + trader2_accepted = false; + + trader1_coins = 0; + trader2_coins = 0; + + + OpenTradeWindow(); +} + +Trade::~Trade() { + +} + +int8 Trade::AddItemToTrade(Entity* character, Item* item, int8 quantity, int8 slot) { + LogWrite(PLAYER__ERROR, 0, "Trade", "Player (%s) adding item (%u) to slot %u of the trade window", character->GetName(), item->details.item_id, slot); + if (slot == 255) + slot = GetNextFreeSlot(character); + + if (slot < 0) { + LogWrite(PLAYER__ERROR, 0, "Trade", "Player (%s) tried to add an item to an invalid trade slot (%u)", character->GetName(), slot); + return 255; + } + else if(slot >= trade_max_slots) { + return 254; + } + + Entity* other = GetTradee(character); + int8 result = CheckItem(character, item, other); + + if (result == 0) { + if (character == trader1) { + Trader1ItemAdd(item, quantity, slot); + // Only trader2 can be a bot so only + // need to do the bot check here + if (trader2->IsBot()) { + ((Bot*)trader2)->TradeItemAdded(item); + } + } + else if (character == trader2) + Trader2ItemAdd(item, quantity, slot); + else { + LogWrite(PLAYER__ERROR, 0, "Trade", "Player (%s) tried to add an item to a trade but was neither trader1 or trader2", character->GetName()); + return 255; + } + } + + SendTradePacket(); + return result; +} + +int8 Trade::CheckItem(Entity* trader, Item* item, Entity* other) { + int8 ret = 0; + map* list = 0; + map::iterator itr; + + bool other_is_bot = false; + + if(other) + other_is_bot = other->IsBot(); + + if (trader == trader1) + list = &trader1_items; + else if (trader == trader2) + list = &trader2_items; + + if (list) { + if (trader->IsPlayer()) { + // Check to see if the item is already in the trade + for (itr = list->begin(); itr != list->end(); itr++) { + if (itr->second.item->details.unique_id == item->details.unique_id) { + ret = 1; + break; + } + } + + // Only allow heirloom and no-trade items to be traded with a bot + if (!other_is_bot) { + if (item->CheckFlag(NO_TRADE)) + ret = 2; + if (item->CheckFlag2(HEIRLOOM)) { + if(other->IsPlayer() && item->grouped_char_ids.find(((Player*)other)->GetCharacterID()) != item->grouped_char_ids.end()) { + double diffInSeconds = 0.0; std::difftime(std::time(nullptr), item->created); + if(item->CheckFlag(ATTUNED) || ((diffInSeconds = std::difftime(std::time(nullptr), item->created)) && diffInSeconds >= rule_manager.GetGlobalRule(R_Player, HeirloomItemShareExpiration)->GetFloat())) { + ret = 3; // denied heirloom cannot be transferred to outside of group after 48 hours (by rule setting) or if already attuned + } + } + else { + ret = 3; // not part of the group/raid + } + } + } + } + } + + return ret; +} + +void Trade::RemoveItemFromTrade(Entity* character, int8 slot) { + map* list = 0; + + if (character == trader1) + list = &trader1_items; + else if (character == trader2) + list = &trader2_items; + + if (list) { + if (list->count(slot) > 0) { + list->erase(slot); + SendTradePacket(); + } + else + LogWrite(PLAYER__ERROR, 0, "Trade", "Player (%s) tried to remove an item from a trade slot that was empty ", character->GetName()); + } +} + +void Trade::AddCoinToTrade(Entity* character, int64 amount) { + if (!character->IsPlayer()) + return; + + if (character == trader1) { + trader1_coins += amount; + if (!((Player*)character)->HasCoins(trader1_coins)) { + trader1_coins -= amount; + LogWrite(PLAYER__ERROR, 0, "Trade", "Player (%s) tried to add more coins then they had ", character->GetName()); + } + } + else if (character == trader2) { + trader2_coins += amount; + if (!((Player*)character)->HasCoins(trader2_coins)) { + trader2_coins -= amount; + LogWrite(PLAYER__ERROR, 0, "Trade", "Player (%s) tried to add more coins then they had ", character->GetName()); + } + } + + SendTradePacket(); +} + +void Trade::RemoveCoinFromTrade(Entity* character, int64 amount) { + if (character == trader1) + trader1_coins = (amount >= trader1_coins) ? 0 : trader1_coins - amount; + else if (character == trader2) + trader2_coins = (amount >= trader2_coins) ? 0 : trader2_coins - amount; + + SendTradePacket(); +} + +Entity* Trade::GetTradee(Entity* character) { + if (character == trader1) + return trader2; + else if (character == trader2) + return trader1; + + return 0; +} + +bool Trade::SetTradeAccepted(Entity* character) { + if (character == trader1) + trader1_accepted = true; + else if (character == trader2) + trader2_accepted = true; + else + return false; + + Entity* other = GetTradee(character); + if (other) { + if (other->IsPlayer()) { + Client* client = ((Player*)other)->GetClient(); + if(client) { + PacketStruct* packet = configReader.getStruct("WS_PlayerTrade", client->GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(character)); + packet->setDataByName("type", 16); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + } + else if (other->IsBot()) { + Client* client = ((Player*)character)->GetClient(); + if (trader1_coins > 0) { + CancelTrade(other); + if (client) + client->SimpleMessage(CHANNEL_ERROR, "Bots can't take coins so the trade was canceled."); + + return true; + } + else { + if (!((Bot*)other)->CheckTradeItems(&trader1_items)) { + CancelTrade(other); + if (client) + client->SimpleMessage(CHANNEL_ERROR, "There was an item the bot could not equip so the trade was canceled."); + + return true; + } + else + trader2_accepted = true; + } + } + + if (HasAcceptedTrade(other)) { + CompleteTrade(); + return true; + } + } + + return false; +} + +bool Trade::HasAcceptedTrade(Entity* character) { + if (character == trader1) + return trader1_accepted; + else if (character == trader2) + return trader2_accepted; + + return false; +} + +void Trade::CancelTrade(Entity* character) { + Entity* other = GetTradee(character); + if (other){ + if (other->IsPlayer()) { + Client* client = ((Player*)other)->GetClient(); + if(client) { + PacketStruct* packet = configReader.getStruct("WS_PlayerTrade", client->GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(character)); + packet->setDataByName("type", 2); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + } + else if (other->IsBot()) + ((Bot*)other)->FinishTrade(); + } + + trader1->trade = 0; + trader2->trade = 0; +} + +void Trade::Trader1ItemAdd(Item* item, int8 quantity, int8 slot) { + trader1_items[slot].item = item; + trader1_items[slot].quantity = quantity; +} + +void Trade::Trader2ItemAdd(Item* item, int8 quantity, int8 slot) { + trader2_items[slot].item = item; + trader2_items[slot].quantity = quantity; +} + +Item* Trade::GetTraderSlot(Entity* trader, int8 slot) { + if(trader == GetTrader1()) { + map::iterator itr = trader1_items.find(slot); + if(itr != trader1_items.end()) { + return itr->second.item; + } + } + else if(trader == GetTrader2()) { + map::iterator itr = trader2_items.find(slot); + if(itr != trader2_items.end()) { + return itr->second.item; + } + } + return nullptr; +} + +void Trade::CompleteTrade() { + map::iterator itr; + vector trader1_item_pass; + vector::iterator itr2; + string log_string = "TradeComplete:\n"; + + if (trader1->IsPlayer()) { + Player* player = (Player*)trader1; + Client* client = ((Player*)player)->GetClient(); + if (client) { + log_string += "Trader1 = "; + log_string += trader1->GetName(); + log_string += "(" + to_string(client->GetCharacterID()) + ")\n"; + log_string += "Coins: " + to_string(trader1_coins) + "\n"; + log_string += "Items:\n"; + player->RemoveCoins(trader1_coins); + for (itr = trader1_items.begin(); itr != trader1_items.end(); itr++) { + // client->RemoveItem can delete the item so we need to store the item id's and quantity to give to trader2 + Item* newitem = new Item(itr->second.item); + newitem->details.count = itr->second.quantity; + trader1_item_pass.push_back(newitem); + + log_string += itr->second.item->name + " (" + to_string(itr->second.item->details.item_id) + ") x" + to_string(itr->second.quantity) + "\n"; + client->RemoveItem(itr->second.item, itr->second.quantity); + } + + player->AddCoins(trader2_coins); + for (itr = trader2_items.begin(); itr != trader2_items.end(); itr++) { + Item* newitem = new Item(itr->second.item); + newitem->details.count = itr->second.quantity; + client->AddItem(newitem, nullptr); + } + + PacketStruct* packet = configReader.getStruct("WS_PlayerTrade", client->GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(trader2)); + packet->setDataByName("type", 24); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + } + // trader1 is the player who starts the trade, will never be a bot, if trader1 is not a player something went horribly wrong. + + + if (trader2->IsPlayer()) { + Player* player = (Player*)trader2; + Client* client = ((Player*)player)->GetClient(); + if (client) { + log_string += "Trader2 = "; + log_string += trader2->GetName(); + log_string += "(" + to_string(client->GetCharacterID()) + ")\n"; + log_string += "Coins: " + to_string(trader2_coins) + "\n"; + log_string += "Items:\n"; + player->RemoveCoins(trader2_coins); + for (itr = trader2_items.begin(); itr != trader2_items.end(); itr++) { + log_string += itr->second.item->name + " (" + to_string(itr->second.item->details.item_id) + ") x" + to_string(itr->second.quantity) + "\n"; + client->RemoveItem(itr->second.item, itr->second.quantity); + } + + player->AddCoins(trader1_coins); + for (itr2 = trader1_item_pass.begin(); itr2 != trader1_item_pass.end(); itr2++) { + client->AddItem(*itr2, nullptr); + } + + PacketStruct* packet = configReader.getStruct("WS_PlayerTrade", client->GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(trader1)); + packet->setDataByName("type", 24); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + } + else if (trader2->IsBot()) { + Bot* bot = (Bot*)trader2; + log_string += "Trader2 is a bot"; + for (itr = trader2_items.begin(); itr != trader2_items.end(); itr++) { + bot->RemoveItem(itr->second.item); + } + + for (itr2 = trader1_item_pass.begin(); itr2 != trader1_item_pass.end(); itr2++) { + bot->GiveItem(*itr2); + } + bot->FinishTrade(); + } + + LogWrite(PLAYER__INFO, 0, "Trade", log_string.c_str()); + + trader1->trade = 0; + trader2->trade = 0; +} + +void Trade::OpenTradeWindow() { + if (trader1->IsPlayer()) { + Client* client = ((Player*)trader1)->GetClient(); + if(client) { + PacketStruct* packet = configReader.getStruct("WS_PlayerTrade", client->GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(trader2)); + packet->setDataByName("type", 1); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + } + + if (trader2->IsPlayer()) { + Client* client = ((Player*)trader2)->GetClient(); + if(client) { + PacketStruct* packet = configReader.getStruct("WS_PlayerTrade", client->GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(trader1)); + packet->setDataByName("type", 1); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + } +} + +void Trade::SendTradePacket() { + if (trader1->IsPlayer()) { + Client* client = ((Player*)trader1)->GetClient(); + if(!client) { + return; + } + + PacketStruct* packet = configReader.getStruct("WS_PlayerTrade", client->GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(trader2)); + packet->setDataByName("type", 1); + + int8 size = (int8)(trader1_items.size()); + int8 i = 0; + map::iterator itr; + + packet->setArrayLengthByName("your_item_count", size); + for (itr = trader1_items.begin(); itr != trader1_items.end(); itr++) { + packet->setArrayDataByName("your_item_unknown1", 1, i); + packet->setArrayDataByName("your_item_unknown2", 1, i); + packet->setArrayDataByName("your_item_slot", itr->first, i); + packet->setArrayDataByName("your_item_id", itr->second.item->details.item_id, i); + packet->setArrayDataByName("your_item_name", itr->second.item->name.c_str(), i); + packet->setArrayDataByName("your_item_quantity", itr->second.quantity, i); + packet->setArrayDataByName("your_item_icon", itr->second.item->GetIcon(client->GetVersion()), i); + packet->setArrayDataByName("your_item_background", 0, i); // No clue on this value yet + i++; + } + int32 plat = 0; + int32 gold = 0; + int32 silver = 0; + int32 copper = 0; + + CalculateCoins(trader1_coins, plat, gold, silver, copper); + packet->setDataByName("your_copper", copper); + packet->setDataByName("your_silver", silver); + packet->setDataByName("your_gold", gold); + packet->setDataByName("your_plat", plat); + + + size = (int8)(trader2_items.size()); + i = 0; + + packet->setArrayLengthByName("their_item_count", size); + for (itr = trader2_items.begin(); itr != trader2_items.end(); itr++) { + packet->setArrayDataByName("their_item_unknown1", 1, i); + packet->setArrayDataByName("their_item_unknown2", 1, i); + packet->setArrayDataByName("their_item_slot", itr->first, i); + packet->setArrayDataByName("their_item_id", itr->second.item->details.item_id, i); + packet->setArrayDataByName("their_item_name", itr->second.item->name.c_str(), i); + packet->setArrayDataByName("their_item_quantity", itr->second.quantity, i); + packet->setArrayDataByName("their_item_icon", itr->second.item->GetIcon(client->GetVersion()), i); + packet->setArrayDataByName("their_item_background", 0, i); // No clue on this value yet + i++; + } + + plat = 0; + gold = 0; + silver = 0; + copper = 0; + + CalculateCoins(trader2_coins, plat, gold, silver, copper); + packet->setDataByName("their_copper", copper); + packet->setDataByName("their_silver", silver); + packet->setDataByName("their_gold", gold); + packet->setDataByName("their_plat", plat); + + LogWrite(PLAYER__ERROR, 0, "Trade", "packet sent"); + packet->PrintPacket(); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + + if (trader2->IsPlayer()) { + Client* client = ((Player*)trader2)->GetClient(); + if(!client) { + return; + } + PacketStruct* packet2 = configReader.getStruct("WS_PlayerTrade", client->GetVersion()); + if (packet2) { + packet2->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(trader1)); + packet2->setDataByName("type", 1); + + int8 size = (int8)(trader2_items.size()); + int8 i = 0; + map::iterator itr; + + packet2->setArrayLengthByName("your_item_count", size); + for (itr = trader2_items.begin(); itr != trader2_items.end(); itr++) { + packet2->setArrayDataByName("your_item_unknown1", 1, i); + packet2->setArrayDataByName("your_item_unknown2", 1, i); + packet2->setArrayDataByName("your_item_slot", itr->first, i); + packet2->setArrayDataByName("your_item_id", itr->second.item->details.item_id, i); + packet2->setArrayDataByName("your_item_name", itr->second.item->name.c_str(), i); + packet2->setArrayDataByName("your_item_quantity", itr->second.quantity, i); + packet2->setArrayDataByName("your_item_icon", itr->second.item->GetIcon(client->GetVersion()), i); + packet2->setArrayDataByName("your_item_background", 0, i); // No clue on this value yet + i++; + } + int32 plat = 0; + int32 gold = 0; + int32 silver = 0; + int32 copper = 0; + + CalculateCoins(trader2_coins, plat, gold, silver, copper); + packet2->setDataByName("your_copper", copper); + packet2->setDataByName("your_silver", silver); + packet2->setDataByName("your_gold", gold); + packet2->setDataByName("your_plat", plat); + + size = (int8)(trader1_items.size()); + i = 0; + + packet2->setArrayLengthByName("their_item_count", size); + for (itr = trader1_items.begin(); itr != trader1_items.end(); itr++) { + packet2->setArrayDataByName("their_item_unknown1", 1, i); + packet2->setArrayDataByName("their_item_unknown2", 1, i); + packet2->setArrayDataByName("their_item_slot", itr->first, i); + packet2->setArrayDataByName("their_item_id", itr->second.item->details.item_id, i); + packet2->setArrayDataByName("their_item_name", itr->second.item->name.c_str(), i); + packet2->setArrayDataByName("their_item_quantity", itr->second.quantity, i); + packet2->setArrayDataByName("their_item_icon", itr->second.item->GetIcon(client->GetVersion()), i); + packet2->setArrayDataByName("their_item_background", 0, i); // No clue on this value yet + i++; + } + + plat = 0; + gold = 0; + silver = 0; + copper = 0; + + CalculateCoins(trader1_coins, plat, gold, silver, copper); + packet2->setDataByName("their_copper", copper); + packet2->setDataByName("their_silver", silver); + packet2->setDataByName("their_gold", gold); + packet2->setDataByName("their_plat", plat); + + LogWrite(PLAYER__ERROR, 0, "Trade", "packet sent #2"); + packet2->PrintPacket(); + client->QueuePacket(packet2->serialize()); + safe_delete(packet2); + } + } +} + +void Trade::CalculateCoins(int64 val, int32& plat, int32& gold, int32& silver, int32& copper) { + int32 tmp = 0; + if (val >= 1000000) { + tmp = val / 1000000; + val -= tmp * 1000000; + plat = tmp; + } + if (val >= 10000) { + tmp = val / 10000; + val -= tmp * 10000; + gold = tmp; + } + if (val >= 100) { + tmp = val / 100; + val -= tmp * 100; + silver = tmp; + } + if (val > 0) { + copper = val; + } +} + +int8 Trade::GetNextFreeSlot(Entity* character) { + map* list = 0; + + if (character == trader1) + list = &trader1_items; + else if (character == trader2) + list = &trader2_items; + else + return 255; + + int8 ret = 255; + for (int8 index = 0; index < 12; index++) { + if (list->count(index) == 0) { + ret = index; + break; + } + } + + return ret; +} \ No newline at end of file diff --git a/source/WorldServer/Trade.h b/source/WorldServer/Trade.h new file mode 100644 index 0000000..2292a4f --- /dev/null +++ b/source/WorldServer/Trade.h @@ -0,0 +1,57 @@ +#pragma once + +#include "../common/types.h" +#include + +class Item; +class Entity; + +struct TradeItemInfo { + Item* item; + int32 quantity; +}; + +class Trade { +public: + Trade(Entity* trader1, Entity* trader2); + ~Trade(); + + int8 AddItemToTrade(Entity* character, Item* item, int8 quantity, int8 slot); + void RemoveItemFromTrade(Entity* character, int8 slot); + void AddCoinToTrade(Entity* character, int64 amount); + void RemoveCoinFromTrade(Entity* character, int64 amount); + Entity* GetTradee(Entity* character); + + bool SetTradeAccepted(Entity* character); + bool HasAcceptedTrade(Entity* character); + void CancelTrade(Entity* character); + + int8 CheckItem(Entity* trader, Item* item, Entity* other); + + Item* GetTraderSlot(Entity* trader, int8 slot); + Entity* GetTrader1() { return trader1; } + Entity* GetTrader2() { return trader2; } + + int8 MaxSlots() { return trade_max_slots; } +private: + + + void Trader1ItemAdd(Item* item, int8 quantity, int8 slot); + void Trader2ItemAdd(Item* item, int8 quantity, int8 slot); + void CompleteTrade(); + void OpenTradeWindow(); + void SendTradePacket(); + void CalculateCoins(int64 val, int32& plat, int32& gold, int32& silver, int32& copper); + int8 GetNextFreeSlot(Entity* character); + + Entity* trader1; + map trader1_items; + int64 trader1_coins; + bool trader1_accepted; + + Entity* trader2; + map trader2_items; + int64 trader2_coins; + bool trader2_accepted; + int32 trade_max_slots; +}; \ No newline at end of file diff --git a/source/WorldServer/Tradeskills/Tradeskills.cpp b/source/WorldServer/Tradeskills/Tradeskills.cpp new file mode 100644 index 0000000..19cac6b --- /dev/null +++ b/source/WorldServer/Tradeskills/Tradeskills.cpp @@ -0,0 +1,869 @@ +/* + 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 . +*/ +#include +#include + +#include "Tradeskills.h" +#include "../client.h" +#include "../../common/ConfigReader.h" +#include "../classes.h" +//#include "../../common/debug.h" +#include "../../common/Log.h" +//#include "../zoneserver.h" +//#include "../Skills.h" +//#include "../classes.h" +#include "../World.h" +//#include "../LuaInterface.h" +#include "../ClientPacketFunctions.h" +#include "../WorldDatabase.h" +#include "../Rules/Rules.h" + +extern Classes classes; +extern ConfigReader configReader; +extern MasterSkillList master_skill_list; +extern MasterRecipeList master_recipe_list; +extern MasterTradeskillEventsList master_tradeskillevent_list; +extern WorldDatabase database; +extern RuleManager rule_manager; + +TradeskillMgr::TradeskillMgr() { + m_tradeskills.SetName("TradeskillMgr::tradeskillsList"); + // % chance for each was made up by me (Jabantiz) and may need some tweaking + // 2% for crit fail + m_success = rule_manager.GetGlobalRule(R_World, TradeskillSuccessChance)->GetFloat(); + m_critSuccess = rule_manager.GetGlobalRule(R_World, TradeskillCritSuccessChance)->GetFloat(); + m_fail = rule_manager.GetGlobalRule(R_World, TradeskillFailChance)->GetFloat(); + m_critFail = rule_manager.GetGlobalRule(R_World, TradeskillCritFailChance)->GetFloat(); + m_eventChance = rule_manager.GetGlobalRule(R_World, TradeskillEventChance)->GetFloat(); + + if ((m_success + m_critSuccess + m_fail + m_critFail) != 100.0f) { + LogWrite(TRADESKILL__ERROR, 0, "Tradeskills", "Success, crit success, fail, and crit fail MUST add up to 100, reverting to defaults..."); + m_success = 87.0f; + m_critSuccess = 2.0f; + m_fail = 10.0f; + m_critFail = 1.0f; + } +} + +TradeskillMgr::~TradeskillMgr() { + m_tradeskills.writelock(__FUNCTION__, __LINE__); + + map::iterator itr; + for (itr = tradeskillList.begin(); itr != tradeskillList.end(); itr++) + safe_delete(itr->second); + + tradeskillList.clear(); + + m_tradeskills.releasewritelock(__FUNCTION__, __LINE__); +} + +void TradeskillMgr::Process() { + m_tradeskills.writelock(__FUNCTION__, __LINE__); + map::iterator itr = tradeskillList.begin(); + while (itr != tradeskillList.end()) { + Tradeskill* tradeskill = 0; + tradeskill = itr->second; + if (!tradeskill) + continue; + if (Timer::GetCurrentTime2() >= tradeskill->nextUpdateTime) { + Client* client = itr->first; + if(!client->GetPlayer()) { + continue; + } + SetClientIdleVisualState(client, tradeskill); + + sint32 progress = 0; + sint32 durability = 0; + /* + Following was grabbed from + http://eq2.stratics.com/content/guides/padasher_crafting_2.php + old but the base fail/succes should still be the same + + -100 Durability / -50 Progress (Critical Failure) + -50 Durability / 0 Progress (Failure) + -10 Durability / +50 Progress (Standard tick) + +10 Durability / + 100 Progress (Critical Success) + */ + float roll = MakeRandomFloat(0, 100); + int8 effect = 0; //1 is critical success, 2 is success, 3 is failure, and 4 is critical failure. + + float success = m_success; + float crit_success = m_critSuccess; + float fail = m_fail; + float crit_fail = m_critFail; + + // Modify the % chance for success based off of stats + client->GetPlayer()->MStats.lock(); + fail -= client->GetPlayer()->stats[ITEM_STAT_SUCCESS_MOD]; + success += client->GetPlayer()->stats[ITEM_STAT_SUCCESS_MOD]; + client->GetPlayer()->MStats.unlock(); + + // add values together for the if + crit_success += crit_fail; + fail += crit_success; + success += fail; + + // Crit fail + if (roll <= crit_fail) { + progress = -50; + durability = -100; + effect = 4; + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Critical failure!"); + } + // Crit success + else if (roll > crit_fail && roll <= crit_success) { + progress = 100; + durability = 10; + effect = 1; + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Critical success!"); + } + // Fail + else if (roll > crit_success && roll <= fail) { + progress = 0; + durability = -50; + effect = 3; + } + // Success + else if (roll > fail && roll <= success) { + progress = 50; + durability = -10; + effect = 2; + } + else { + // Just a debug, should never end up in this, if we do write out a log but treat as a success for the player + LogWrite(TRADESKILL__ERROR, 0, "Tradeskills", "Process roll was not within valid range. roll = %f, crit fail = %f, crit success = %f, fail = %f, success = %f", roll, crit_fail, crit_success, fail, success); + progress = 50; + durability = -10; + effect = 2; + } + + // Check to see if there was an event, if there was give out the rewards/penalties for it + if (tradeskill->CurrentEvent) { + if (tradeskill->eventCountered) { + progress += tradeskill->CurrentEvent->SuccessProgress; + durability += tradeskill->CurrentEvent->SuccessDurability; + } + else { + progress += tradeskill->CurrentEvent->FailProgress; + durability += tradeskill->CurrentEvent->FailDurability; + } + } + + // Modify the progress/durability by the players stats + client->GetPlayer()->MStats.lock(); + progress += client->GetPlayer()->stats[ITEM_STAT_PROGRESS_ADD]; + durability += client->GetPlayer()->stats[ITEM_STAT_DURABILITY_ADD]; + client->GetPlayer()->MStats.unlock(); + + tradeskill->currentDurability += durability; + tradeskill->currentProgress += progress; + + PacketStruct* packet = configReader.getStruct("WS_UpdateCreateItem", client->GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(tradeskill->table)); + packet->setDataByName("effect", effect); + packet->setDataByName("total_durability", tradeskill->currentDurability); + packet->setDataByName("total_progress", tradeskill->currentProgress); + packet->setDataByName("durability_change", durability); + packet->setDataByName("progress_change", progress); + + if (tradeskill->currentProgress >= 1000) + packet->setDataByName("progress_level", 4); + else if (tradeskill->currentProgress >= 800) + packet->setDataByName("progress_level", 3); + else if (tradeskill->currentProgress >= 600) + packet->setDataByName("progress_level", 2); + else if (tradeskill->currentProgress >= 400) + packet->setDataByName("progress_level", 1); + else + packet->setDataByName("progress_level", 0); + + // Reset the tradeskill event + tradeskill->CurrentEvent = 0; + tradeskill->eventChecked = false; + tradeskill->eventCountered = false; + + // 15% chance for an event (change this to a rule probably) + + int eventRoll = MakeRandomFloat(0, 100); + if (eventRoll <= m_eventChance) { + // Get a vector of all possible events for this crafting technique + vector* events = master_tradeskillevent_list.GetEventByTechnique(tradeskill->recipe->GetTechnique()); + if (events) { + // Get the size of the vector + int size = events->size(); + // Get a random number from 0 to size - 1 to use as an index + int index = MakeRandomInt(0, size - 1); + // use the index to get an event + TradeskillEvent* TSEvent = events->at(index); + if (TSEvent) { + // Now that we got a random event set it in the packet + packet->setDataByName("reaction_icon", TSEvent->Icon); + packet->setDataByName("reaction_name", TSEvent->Name); + + // Set the current tradeskill event + tradeskill->CurrentEvent = TSEvent; + } + } + } + EQ2Packet* pack = packet->serialize(); + //packet->PrintPacket(); + client->QueuePacket(pack); + safe_delete(packet); + } + + if (tradeskill->currentProgress >= 1000) { + itr++; + StopCrafting(client, false); + continue; + } + else + tradeskill->nextUpdateTime = Timer::GetCurrentTime2() + 4000; + } + itr++; + } + m_tradeskills.releasewritelock(__FUNCTION__, __LINE__); +} + +void TradeskillMgr::BeginCrafting(Client* client, vector> components) { + Recipe* recipe = master_recipe_list.GetRecipe(client->GetPlayer()->GetCurrentRecipe()); + + if (!recipe) { + LogWrite(TRADESKILL__ERROR, 0, "Recipe", "Recipe (%u) not found in TradeskillMgr::BeginCrafting()", client->GetPlayer()->GetCurrentRecipe()); + ClientPacketFunctions::StopCrafting(client); + return; + } + + // TODO: use the vecotr to lock inventory slots + vector>::iterator itr; + bool missingItem = false; + int32 itemid = 0; + vector tmpItems; + for (itr = components.begin(); itr != components.end(); itr++) { + itemid = itr->first; + Item* item = client->GetPlayer()->item_list.GetItemFromUniqueID(itemid); + int8 qty_req = 0; + if(!item) + { + missingItem = true; + break; + } + + item->details.item_locked = true; + tmpItems.push_back(item); + } + + if(!recipe->ProvidedAllRequiredComponents(client, &tmpItems, &components)) { + LogWrite(TRADESKILL__ERROR, 0, "Recipe", "Recipe (%u) quantity of items incorrect or component missing.", recipe->GetID()); + missingItem = true; + } + + if (missingItem) { + LogWrite(TRADESKILL__ERROR, 0, "Recipe", "Recipe (%u) player missing item when attempting to create recipe.", recipe->GetID()); + vector::iterator itemitr; + for (itemitr = tmpItems.begin(); itemitr != tmpItems.end(); itemitr++) { + Item* tmpItem = *itemitr; + tmpItem->details.item_locked = false; + } + ClientPacketFunctions::StopCrafting(client); + return; + } + + ClientPacketFunctions::SendItemCreationUI(client, recipe); + Tradeskill* tradeskill = new Tradeskill; + tradeskill->player = client->GetPlayer(); + tradeskill->table = client->GetPlayer()->GetTarget(); + tradeskill->recipe = recipe; + tradeskill->currentDurability = 1000; + tradeskill->currentProgress = 0; + tradeskill->nextUpdateTime = Timer::GetCurrentTime2() + 500; + tradeskill->usedComponents = components; + tradeskill->CurrentEvent = 0; + tradeskill->eventChecked = false; + tradeskill->eventCountered = false; + m_tradeskills.writelock(__FUNCTION__, __LINE__); + tradeskillList.insert(make_pair(client, tradeskill)); + + SetClientIdleVisualState(client, tradeskill); + m_tradeskills.releasewritelock(__FUNCTION__, __LINE__); + + // Unlock TS Spells and lock all others + client->GetPlayer()->UnlockTSSpells(); + + client->ClearSentItemDetails(); + EQ2Packet* outapp = client->GetPlayer()->SendInventoryUpdate(client->GetVersion()); + if (outapp) + client->QueuePacket(outapp); +} + +void TradeskillMgr::StopCrafting(Client* client, bool lock) { + + if (lock) + m_tradeskills.writelock(__FUNCTION__, __LINE__); + + if (tradeskillList.count(client) == 0) { + if (lock) + m_tradeskills.releasewritelock(__FUNCTION__, __LINE__); + return; + } + + Tradeskill* tradeskill = 0; + tradeskill = tradeskillList[client]; + + //TODO: unlock inventory slots, give the product to the player, give tradeskill xp + ClientPacketFunctions::StopCrafting(client); + + int32 dur = tradeskill->currentDurability; + int32 progress = tradeskill->currentProgress; + Recipe* recipe = tradeskill->recipe; + vector>::iterator itr; + Item* item = 0; + int32 item_id = 0; + int8 i = 0; + int8 qty = 0; + + Recipe* playerRecipe = client->GetPlayer()->GetRecipeList()->GetRecipe(recipe->GetID()); + + if(!playerRecipe) + { + LogWrite(TRADESKILL__ERROR, 0, "Tradeskills", "%s: TradeskillMgr::StopCrafting Error finding player recipe in their recipe book for recipe id %u", client->GetPlayer()->GetName(), recipe->GetID()); + client->Message(CHANNEL_COLOR_RED, "%s: StopCrafting Error finding player recipe in their recipe book for recipe id %u!", client->GetPlayer()->GetName(), recipe->GetID()); + if (lock) + m_tradeskills.releasewritelock(__FUNCTION__, __LINE__); + return; + } + bool updateInvReq = false; + // cycle through the list of used items and remove them + for (itr = tradeskill->usedComponents.begin(); itr != tradeskill->usedComponents.end(); itr++, i++) { + // Get the item in the players inventory and remove or reduce the quantity + int32 itmid = itr->first; + qty = itr->second > 0 ? itr->second : 1; + item = client->GetPlayer()->item_list.GetItemFromUniqueID(itmid); + if (item && item->details.count <= qty) + { + item->details.item_locked = false; + client->GetPlayer()->item_list.RemoveItem(item); + updateInvReq = true; + } + else if(item) { + item->details.count -= qty; + item->details.item_locked = false; + item->save_needed = true; + updateInvReq = true; + } + else + { + LogWrite(TRADESKILL__ERROR, 0, "Tradeskills", "%s: TradeskillMgr::StopCrafting Error finding item %u to remove quantity for recipe id %u", client->GetPlayer()->GetName(), itmid, recipe->GetID()); + client->Message(CHANNEL_COLOR_RED, "%s: StopCrafting Error finding item %u to remove quantity for recipe id %u!", client->GetPlayer()->GetName(), itmid, recipe->GetID()); + } + } + + if(updateInvReq) + { + EQ2Packet* outapp = client->GetPlayer()->SendInventoryUpdate(client->GetVersion()); + if (outapp) + client->QueuePacket(outapp); + } + + item = 0; + qty = recipe->GetFuelComponentQuantity(); + item_id = recipe->components[5][0]; + int32 byproduct_itemid = 0; + int16 byproduct_qty = 0; + float tsx = 0; + bool success = false; + int8 HS = playerRecipe->GetHighestStage(); + if (progress < 400) { //stage 0 + if (recipe->products.count(0) > 0) { + item_id = recipe->products[0]->product_id; + qty = recipe->products[0]->product_qty; + byproduct_itemid = recipe->products[0]->byproduct_id; + byproduct_qty = recipe->products[0]->byproduct_qty; + } + tsx = 1; + } + else if (progress >= 400 && progress < 600) { //stage 1 + if (HS & (1 << (1 - 1))) { + } + else { + playerRecipe->SetHighestStage(HS + 1 ); + database.UpdatePlayerRecipe(client->GetPlayer(), recipe->GetID(), playerRecipe->GetHighestStage()); + } + if (recipe->products.count(1) > 0) { + item_id = recipe->products[1]->product_id; + qty = recipe->products[1]->product_qty; + byproduct_itemid = recipe->products[1]->byproduct_id; + byproduct_qty = recipe->products[1]->byproduct_qty; + } + tsx = .45; + } + //else if (progress >= 600 && progress < 800) { //stage 2 + else if ((dur < 200 && progress >= 600) || (dur >= 200 && progress >= 600 && progress < 800)) { //stage 2 + if (HS & (1 << (2 - 1))) { + } + else { + playerRecipe->SetHighestStage(HS + 2); + database.UpdatePlayerRecipe(client->GetPlayer(), recipe->GetID(), playerRecipe->GetHighestStage()); + } + if (recipe->products.count(2) > 0) { + item_id = recipe->products[2]->product_id; + qty = recipe->products[2]->product_qty; + byproduct_itemid = recipe->products[2]->byproduct_id; + byproduct_qty = recipe->products[2]->byproduct_qty; + } + tsx = .30; + } + else if ((dur >= 200 && dur < 800 && progress >= 800) || (dur >= 800 && progress >= 800 && progress < 1000)) { // stage 3 + //else if (progress >= 800 && progress < 1000) { // stage 3 + if (HS & (1 << (3 - 1))) { + } + else { + playerRecipe->SetHighestStage(HS + 4); + database.UpdatePlayerRecipe(client->GetPlayer(), recipe->GetID(), playerRecipe->GetHighestStage()); + } + if (recipe->products.count(3) > 0) { + item_id = recipe->products[3]->product_id; + qty = recipe->products[3]->product_qty; + byproduct_itemid = recipe->products[3]->byproduct_id; + byproduct_qty = recipe->products[3]->byproduct_qty; + } + tsx = .15; + } + else if (dur >= 800 && progress >= 1000) { // stage 4 + //else if (progress >= 1000) { // stage 4 + if (HS & (1 << (4 - 1))) { + } + else { + playerRecipe->SetHighestStage(HS + 8); + database.UpdatePlayerRecipe(client->GetPlayer(), recipe->GetID(), playerRecipe->GetHighestStage()); + } + if (recipe->products.count(4) > 0) { + success = true; + item_id = recipe->products[4]->product_id; + qty = recipe->products[4]->product_qty; + byproduct_itemid = recipe->products[4]->byproduct_id; + byproduct_qty = recipe->products[4]->byproduct_qty; + } + } + + if(progress > 1000 && item_id < 1) { + LogWrite(TRADESKILL__INFO, 0, "Tradeskills", "%s: TradeskillMgr::StopCrafting progress over 1000, but no item id set with recipe id %u, finding override. Highest Stage: %u, progress: %u, durability %u", client->GetPlayer()->GetName(), recipe->GetID(), HS, progress, dur); + for(int i=4;i>=0;i--) { + if (recipe->products.count(i) > 0) { + item_id = recipe->products[i]->product_id; + qty = recipe->products[i]->product_qty; + byproduct_itemid = recipe->products[i]->byproduct_id; + byproduct_qty = recipe->products[i]->byproduct_qty; + break; + } + } + } + + if(item_id) { + LogWrite(TRADESKILL__INFO, 0, "Tradeskills", "%s: TradeskillMgr::StopCrafting crafted item %u with recipe id %u. Highest Stage: %u, progress: %u, durability %u", client->GetPlayer()->GetName(), item_id, recipe->GetID(), HS, progress, dur); + + item = new Item(master_item_list.GetItem(item_id)); + if (!item) { + LogWrite(TRADESKILL__ERROR, 0, "Tradeskills", "Item (%u) not found.", item_id); + } + else { + item->details.count = qty; + // use CHANNEL_COLOR_CHAT_RELATIONSHIP as that is the same value (4) as it is in a log for this message + client->Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "You created %s.", item->CreateItemLink(client->GetVersion()).c_str()); + client->AddItem(item); + if(byproduct_itemid) { + Item* byproductItem = new Item(master_item_list.GetItem(byproduct_itemid)); + byproductItem->details.count = byproduct_qty; + client->Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "You received %s as a byproduct.", byproductItem->CreateItemLink(client->GetVersion()).c_str()); + client->AddItem(byproductItem); + } + //Check for crafting quest updates + int8 update_amt = 0; + if(item->stack_count > 1) + update_amt = 1; + else + update_amt = qty; + client->GetPlayer()->CheckQuestsCraftUpdate(item, update_amt); + } + } + else { + LogWrite(TRADESKILL__WARNING, 0, "Tradeskills", "%s: TradeskillMgr::StopCrafting no item summoned for player with recipe id %u. Highest Stage: %u, progress: %u, durability %u", client->GetPlayer()->GetName(), recipe->GetID(), HS, progress, dur); + } + + float xp = client->GetPlayer()->CalculateTSXP(recipe->GetLevel()); + xp = xp - (xp * tsx); + if (xp > 0) { + int16 level = client->GetPlayer()->GetTSLevel(); + if (client->GetPlayer()->AddTSXP((int32)xp)) { + client->Message(CHANNEL_REWARD, "You gain %u Tradeskill XP!", (int32)xp); + LogWrite(PLAYER__DEBUG, 0, "Player", "Player: %s earned %u tradeskill experience.", client->GetPlayer()->GetName(), (int32)xp); + if(client->GetPlayer()->GetTSLevel() != level) + client->ChangeTSLevel(level, client->GetPlayer()->GetTSLevel()); + client->GetPlayer()->SetCharSheetChanged(true); + } + } + + if(tradeskill && tradeskill->recipe) { + if(success) { + int32 success_anim = GetTechniqueSuccessAnim(client->GetVersion(), tradeskill->recipe->GetTechnique()); + client->GetPlayer()->GetZone()->PlayAnimation(client->GetPlayer(), success_anim); + } + else { + int32 failure_anim = GetTechniqueFailureAnim(client->GetVersion(), tradeskill->recipe->GetTechnique()); + client->GetPlayer()->GetZone()->PlayAnimation(client->GetPlayer(), failure_anim); + } + } + + tradeskillList.erase(client); + safe_delete(tradeskill); + + if (lock) + m_tradeskills.releasewritelock(__FUNCTION__, __LINE__); + + // Lock TS spells and unlock all others + client->GetPlayer()->LockTSSpells(); +} + +bool TradeskillMgr::IsClientCrafting(Client* client) { + bool ret = false; + + m_tradeskills.readlock(__FUNCTION__, __LINE__); + ret = tradeskillList.count(client) > 0; + m_tradeskills.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +void TradeskillMgr::CheckTradeskillEvent(Client* client, int16 icon) { + // Check to see if the given client is crafting + if (!IsClientCrafting(client)) + return; + + m_tradeskills.writelock(__FUNCTION__, __LINE__); + // check to see if the client currently has an event and if it does if we had already tried to counter it this round + if (tradeskillList[client]->CurrentEvent == 0 || tradeskillList[client]->eventChecked) { + // No current event, or we already tried to counter it, return out + m_tradeskills.releasewritelock(__FUNCTION__, __LINE__); + return; + } + + // set the eventChecked flag so we don't try to counter it again + tradeskillList[client]->eventChecked = true; + // compare the event icon with the given spell icon to see if we countered it and store the result for the update + bool countered = (icon == tradeskillList[client]->CurrentEvent->Icon); + tradeskillList[client]->eventCountered = countered; + + // send the success or fail message to the client + client->Message(CHANNEL_NARRATIVE, "You %s %s.", countered ? "successfully countered" : "failed to counter", tradeskillList[client]->CurrentEvent->Name); + + // unlock the list and send the result packet + m_tradeskills.releasewritelock(__FUNCTION__, __LINE__); + ClientPacketFunctions::CounterReaction(client, countered); +} + +Tradeskill* TradeskillMgr::GetTradeskill(Client* client) { + if (tradeskillList.count(client) == 0) + return 0; + + return tradeskillList[client]; +} + +int32 TradeskillMgr::GetTechniqueSuccessAnim(int16 version, int32 technique) { + switch(technique) { + case SKILL_ID_SCULPTING: { + if(version <= 561) { + return 3007; // leatherworking_success + } + + return 11785; // 3005 = failure, 3006 = idle. 11783 = failure, 11784 = idle + break; + } + case SKILL_ID_ARTISTRY: { + if(version <= 561) { + return 2319; // cooking_success + } + + return 11245; // 2317 = failure, 2318 = idle. 11243 = failure, 11244 = idle + break; + } + case SKILL_ID_FLETCHING: { + if(version <= 561) { + return 2356; // woodworking_success + } + + return 13309; // 2354 = failure, 2355 = idle. 13307 = failure, 13308 = idle + break; + } + case SKILL_ID_METALWORKING: + case SKILL_ID_METALSHAPING: { + if(version <= 561) { + return 2442; // metalworking_success + } + + return 11813; // 2441 = failure, 1810 = idle. 11811 = failure, 11812 = idle + break; + } + case SKILL_ID_TAILORING: { + if(version <= 561) { + return 2352; // tailoring_success + } + + return 13040; // 2350 = failure, 2351 = idle. 13038 = failure, 13039 = idle + break; + } + case SKILL_ID_CHEMISTRY:{ + if(version <= 561) { + return 2298; // alchemy_success + } + + return 10749; // 2296 = failure, 2297 = idle. 10747 = failure, 10748 = idle + break; + } + case SKILL_ID_ARTIFICING:{ + if(version <= 561) { + return 2304; // artificing_success + } + + return 10767; // 2302 = failure, 2303 = idle. 10765 = failure, 10766 = idle + break; + } + case SKILL_ID_SCRIBING: { + if(version <= 561) { + return 0; // ??? + } + + return 0; // ??? = failure, 3131 = idle. ??? = failure, 12193 = idle + break; + } + } + return 0; +} + +int32 TradeskillMgr::GetTechniqueFailureAnim(int16 version, int32 technique) { + switch(technique) { + case SKILL_ID_SCULPTING: { + if(version <= 561) { + return 3005; // leatherworking_failure + } + + return 11783; // 3005 = failure, 3006 = idle. 11783 = failure, 11784 = idle + break; + } + case SKILL_ID_ARTISTRY: { + if(version <= 561) { + return 2317; // cooking_failure + } + + return 11243; // 2317 = failure, 2318 = idle. 11243 = failure, 11244 = idle + break; + } + case SKILL_ID_FLETCHING: { + if(version <= 561) { + return 2354; // woodworking_failure + } + + return 13307; // 2354 = failure, 2355 = idle. 13307 = failure, 13308 = idle + break; + } + case SKILL_ID_METALWORKING: + case SKILL_ID_METALSHAPING: { + if(version <= 561) { + return 2441; // metalworking_failure + } + + return 11811; // 2441 = failure, 1810 = idle. 11811 = failure, 11812 = idle + break; + } + case SKILL_ID_TAILORING: { + if(version <= 561) { + return 2350; // tailoring_failure + } + + return 13038; // 2350 = failure, 2351 = idle. 13038 = failure, 13039 = idle + break; + } + case SKILL_ID_CHEMISTRY:{ + if(version <= 561) { + return 2298; // alchemy_success + } + + return 10749; // 2296 = failure, 2297 = idle. 10747 = failure, 10748 = idle + break; + } + case SKILL_ID_ARTIFICING:{ + if(version <= 561) { + return 2302; // artificing_failure + } + + return 10765; // 2302 = failure, 2303 = idle. 10765 = failure, 10766 = idle + break; + } + case SKILL_ID_SCRIBING: { + if(version <= 561) { + return 0; // ??? + } + + return 0; // ??? = failure, 3131 = idle. ??? = failure, 12193 = idle + break; + } + } + return 0; +} + +int32 TradeskillMgr::GetTechniqueIdleAnim(int16 version, int32 technique) { + switch(technique) { + case SKILL_ID_SCULPTING: { + if(version <= 561) { + return 3006; // leatherworking_idle + } + + return 11784; // 3005 = failure, 3006 = idle. 11783 = failure, 11784 = idle + break; + } + case SKILL_ID_ARTISTRY: { + if(version <= 561) { + return 2318; // cooking_idle + } + + return 11244; // 2317 = failure, 2318 = idle. 11243 = failure, 11244 = idle + break; + } + case SKILL_ID_FLETCHING: { + if(version <= 561) { + return 2355; // woodworking_idle + } + + return 13308; // 2354 = failure, 2355 = idle. 13307 = failure, 13308 = idle + break; + } + case SKILL_ID_METALWORKING: + case SKILL_ID_METALSHAPING: { + if(version <= 561) { + return 1810; // metalworking_idle + } + + return 11812; // 2441 = failure, 1810 = idle. 11811 = failure, 11812 = idle + break; + } + case SKILL_ID_TAILORING: { + if(version <= 561) { + return 2351; // tailoring_idle + } + + return 13039; // 2350 = failure, 2351 = idle. 13038 = failure, 13039 = idle + break; + } + case SKILL_ID_CHEMISTRY:{ + if(version <= 561) { + return 2297; // alchemy_idle + } + + return 10748; // 2296 = failure, 2297 = idle. 10747 = failure, 10748 = idle + break; + } + case SKILL_ID_ARTIFICING:{ + if(version <= 561) { + return 2303; // artificing_idle + } + + return 10766; // 2302 = failure, 2303 = idle. 10765 = failure, 10766 = idle + break; + } + case SKILL_ID_SCRIBING: { + if(version <= 561) { + return 3131; // scribing_idle + } + + return 12193; // ??? = failure, 3131 = idle. ??? = failure, 12193 = idle + break; + } + } + return 0; +} + +int32 TradeskillMgr::GetMissTargetAnim(int16 version) { + if(version <= 561) { + return 1144; + } + + return 11814; // 11815 seems also possible? +} + +int32 TradeskillMgr::GetKillMissTargetAnim(int16 version) { + if(version <= 561) { + return 33912; + } + + return 44582; // 44583 seems also possible? +} + +void TradeskillMgr::SetClientIdleVisualState(Client* client, Tradeskill* ts) { + if(!client || !ts || !client->GetPlayer()) { + return; + } + + int32 idle_anim = 0; + if(ts->recipe) { + idle_anim = GetTechniqueIdleAnim(client->GetVersion(), ts->recipe->GetTechnique()); + } + if(idle_anim) { + client->GetPlayer()->SetTempVisualState(idle_anim); + } +} + +MasterTradeskillEventsList::MasterTradeskillEventsList() { + m_eventList.SetName("MasterTradeskillEventsList::eventList"); +} + +MasterTradeskillEventsList::~MasterTradeskillEventsList() { + m_eventList.writelock(__FUNCTION__, __LINE__); + map >::iterator itr; + vector::iterator ts_itr; + for (itr = eventList.begin(); itr != eventList.end(); itr++){ + for (ts_itr = itr->second.begin(); ts_itr != itr->second.end(); ts_itr++){ + safe_delete(*ts_itr); + } + } + eventList.clear(); + m_eventList.releasewritelock(__FUNCTION__, __LINE__); +} + +void MasterTradeskillEventsList::AddEvent(TradeskillEvent* tradeskillEvent) { + m_eventList.writelock(__FUNCTION__, __LINE__); + eventList[tradeskillEvent->Technique].push_back(tradeskillEvent); + m_eventList.releasewritelock(__FUNCTION__, __LINE__); +} + +vector* MasterTradeskillEventsList::GetEventByTechnique(int32 technique) { + if (eventList.count(technique) == 0) + return 0; + + return &eventList[technique]; +} + +int32 MasterTradeskillEventsList::Size() { + int32 count = 0; + m_eventList.readlock(__FUNCTION__, __LINE__); + map >::iterator itr; + for (itr = eventList.begin(); itr != eventList.end(); itr++) + count += itr->second.size(); + m_eventList.releasereadlock(__FUNCTION__, __LINE__); + return count; +} diff --git a/source/WorldServer/Tradeskills/Tradeskills.h b/source/WorldServer/Tradeskills/Tradeskills.h new file mode 100644 index 0000000..882c331 --- /dev/null +++ b/source/WorldServer/Tradeskills/Tradeskills.h @@ -0,0 +1,153 @@ +/* + 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 . +*/ +#ifndef __EQ2_TRADESKILLS__ +#define __EQ2_TRADESKILLS__ + +#include "../../common/types.h" +#include "../../common/Mutex.h" +#include "../Items/Items.h" +#include +class Player; +class Spawn; +class Recipe; +class Client; + +struct TradeskillEvent +{ + char Name[250]; + int16 Icon; + int32 Technique; + int16 SuccessProgress; + int16 SuccessDurability; + int16 SuccessHP; + int16 SuccessPower; + int32 SuccessSpellID; + int32 SuccessItemID; + sint16 FailProgress; + sint16 FailDurability; + sint16 FailHP; + sint16 FailPower; +}; + +struct Tradeskill +{ + Player* player; + Spawn* table; + Recipe* recipe; + int32 currentProgress; + int32 currentDurability; + int32 nextUpdateTime; + vector> usedComponents; + TradeskillEvent* CurrentEvent; + bool eventChecked; + bool eventCountered; +}; + +class TradeskillMgr +{ +public: + TradeskillMgr(); + ~TradeskillMgr(); + + /// Determines if an update is needed if so send one and stop crafting if finished + void Process(); + + /// Starts the actual crafting process + /// Client that is crafting + /// List of items the player is using to craft + void BeginCrafting(Client* client, vector> components); + + /// Stops the crafting process + /// Client that stopped crafting + /// Does the list need a mutex lock? default = true + void StopCrafting(Client* client, bool lock = true); + + /// Checks to see if the given client is crafting + /// The client to check + /// True if the client is crafting + bool IsClientCrafting(Client* client); + + /// Get the tradeskill struct for the given client + /// The client to get the tradeskill struct for + /// Pointer to the clients tradeskill struct, or 0 if they don't have one + Tradeskill* GetTradeskill(Client* client); + + /// Check to see if we countered the tradeskill event + /// The client to check for + /// The icon of the spell we casted + void CheckTradeskillEvent(Client* client, int16 icon); + + /// Lock the tradeskill list for reading, should never need to write to the tradeskill list outside of the TradeskillMgr class + /// Function name that called this lock + /// Line number this lock was called from + void ReadLock(const char* function = (const char*)0, int32 line = 0) { m_tradeskills.readlock(function, line); } + + /// Releases the red lock on the tradeskill list + /// Function name that is releasing the lock + /// Line number that is releasing the lock + void ReleaseReadLock(const char* function = (const char*)0, int32 line = 0) { m_tradeskills.releasereadlock(function, line); } + + int32 GetTechniqueSuccessAnim(int16 version, int32 technique); + int32 GetTechniqueFailureAnim(int16 version, int32 technique); + int32 GetTechniqueIdleAnim(int16 version, int32 technique); + int32 GetMissTargetAnim(int16 version); + int32 GetKillMissTargetAnim(int16 version); + + void SetClientIdleVisualState(Client* client, Tradeskill* ts); +private: + /// Sends the creation window + /// Client to send the window to + /// The recipe being crafted + void SendItemCreationUI(Client* client, Recipe* recipe); + map tradeskillList; + Mutex m_tradeskills; + + float m_critFail; + float m_critSuccess; + float m_fail; + float m_success; + float m_eventChance; +}; + +class MasterTradeskillEventsList +{ +public: + MasterTradeskillEventsList(); + ~MasterTradeskillEventsList(); + + /// Adds a tradeskill event to the master list + /// The event to add + void AddEvent(TradeskillEvent* tradeskillEvent); + + /// Gets a list of tradeskill events for the given technique + /// The skill id of the technique + /// Vector of TradeskillEvent* for the given technique + vector* GetEventByTechnique(int32 technique); + + /// Get the size of the event list + /// int32 containing the size of the list + int32 Size(); + +private: + Mutex m_eventList; + map > eventList; +}; + +#endif diff --git a/source/WorldServer/Tradeskills/TradeskillsDB.cpp b/source/WorldServer/Tradeskills/TradeskillsDB.cpp new file mode 100644 index 0000000..f5c3e7f --- /dev/null +++ b/source/WorldServer/Tradeskills/TradeskillsDB.cpp @@ -0,0 +1,64 @@ +/* + 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 . +*/ +#ifdef WIN32 + #include + #include +#endif +#include +#include +#include "../../common/Log.h" +#include "../WorldDatabase.h" +#include "Tradeskills.h" + +extern MasterTradeskillEventsList master_tradeskillevent_list; + +void WorldDatabase::LoadTradeskillEvents() { + TradeskillEvent* TSEvent = 0; + Query query; + MYSQL_ROW row; + MYSQL_RES *res; + + res = query.RunQuery2(Q_SELECT, "SELECT `name`,`icon`,`technique`,`success_progress`,`success_durability`,`success_hp`,`success_power`,`success_spell_id`,`success_item_id`,`fail_progress`,`fail_durability`,`fail_hp`, `fail_power`\n" + "FROM `tradeskillevents`"); + if (res) { + while ((row = mysql_fetch_row(res))) { + TSEvent = new TradeskillEvent; + + strncpy(TSEvent->Name, row[0], sizeof(TSEvent->Name)); + TSEvent->Icon = atoi(row[1]); + TSEvent->Technique = atoul(row[2]); + TSEvent->SuccessProgress = atoi(row[3]); + TSEvent->SuccessDurability = atoi(row[4]); + TSEvent->SuccessHP = atoi(row[5]); + TSEvent->SuccessPower = atoi(row[6]); + TSEvent->SuccessSpellID = atoul(row[7]); + TSEvent->SuccessItemID = atoul(row[8]); + TSEvent->FailProgress = atoi(row[9]); + TSEvent->FailDurability = atoi(row[10]); + TSEvent->FailHP = atoi(row[11]); + TSEvent->FailPower = atoi(row[12]); + + LogWrite(TRADESKILL__DEBUG, 7, "Tradeskills", "Loading tradeskill event: %s", TSEvent->Name); + master_tradeskillevent_list.AddEvent(TSEvent); + } + } + + LogWrite(TRADESKILL__DEBUG, 0, "Tradeskills", "\tLoaded %u tradeskill events", master_tradeskillevent_list.Size()); +} \ No newline at end of file diff --git a/source/WorldServer/Tradeskills/TradeskillsPackets.cpp b/source/WorldServer/Tradeskills/TradeskillsPackets.cpp new file mode 100644 index 0000000..fcfbbb6 --- /dev/null +++ b/source/WorldServer/Tradeskills/TradeskillsPackets.cpp @@ -0,0 +1,610 @@ +/* + 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 . +*/ + +#include +#include "../ClientPacketFunctions.h" +#include "../client.h" +#include "../../common/ConfigReader.h" +#include "../../common/PacketStruct.h" +#include "../Recipes/Recipe.h" +#include "../../common/Log.h" +#include "../Spells.h" +#include "../../common/MiscFunctions.h" +#include "../World.h" + +extern ConfigReader configReader; +extern MasterRecipeList master_recipe_list; +extern MasterSpellList master_spell_list; +extern World world; + +void ClientPacketFunctions::SendCreateFromRecipe(Client* client, int32 recipeID) { + + // if recipeID is 0 we are repeating the last recipe, if not set the players current recipe to the new one + if (recipeID == 0) + recipeID = client->GetPlayer()->GetCurrentRecipe(); + else + client->GetPlayer()->SetCurrentRecipe(recipeID); + + Recipe* playerRecipe = client->GetPlayer()->GetRecipeList()->GetRecipe(recipeID); + + // Get the recipe + Recipe* recipe = master_recipe_list.GetRecipe(recipeID); + + if(!playerRecipe) + { + LogWrite(TRADESKILL__ERROR, 0, "Tradeskills", "%s: ClientPacketFunctions::SendCreateFromRecipe Error finding player recipe %s in their recipe book for recipe id %u", client->GetPlayer()->GetName(), client->GetPlayer()->GetName(), recipe ? recipe->GetID() : 0); + client->Message(CHANNEL_COLOR_RED, "You do not have %s (%u) in your recipe book.", recipe ? recipe->GetName() : "Unknown", recipe ? recipe->GetID() : 0); + client->GetPlayer()->SetCurrentRecipe(0); + return; + } + + if (!recipe) { + client->GetPlayer()->SetCurrentRecipe(0); + LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Error loading recipe (%u) in ClientPacketFunctions::SendCreateFromRecipe()", recipeID); + return; + } + + // Create the packet + PacketStruct* packet = configReader.getStruct("WS_CreateFromRecipe", client->GetVersion()); + if (!packet) { + client->GetPlayer()->SetCurrentRecipe(0); + LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Error loading WS_CreateFromRecipe in ClientPacketFunctions::SendCreateFromRecipe()"); + return; + } + vector::iterator itr; + vector itemss; + int8 i = 0; + int8 k = 0; + int32 firstID = 0; + int32 IDcount = 0; + int32 primary_comp_id = 0; + Item* first_item = 0; + Item* item = 0; + Item* item_player = 0; + Spawn* target = client->GetPlayer()->GetTarget(); + int32 device_id = GetDeviceID(std::string(recipe->GetDevice())); + if (!target || !target->IsObject() || device_id == 0 || (device_id != 0xFFFFFFFF && ((Object*)target)->GetDeviceID() != device_id)) { + client->GetPlayer()->SetCurrentRecipe(0); + client->Message(CHANNEL_COLOR_YELLOW, "You must be at a %s in order to craft this recipe", recipe->GetDevice()); + LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Failed to begin crafting with recipe id %u, recipe device_id is %u (%s), target object is %u.", recipe->GetID(), device_id, recipe->GetDevice(), (target && target->IsObject()) ? ((Object*)target)->GetDeviceID() : 0); + return; + } + + // Recipe and crafting table info + packet->setDataByName("crafting_station", recipe->GetDevice()); + packet->setDataByName("recipe_name", recipe->GetName()); + packet->setDataByName("tier", recipe->GetTier()); + // Mass Production + int32 mpq = 1; + int32 mp = 5; + vector v{ 1,2,4,6,11,21 }; + // mpq will eventually be retrieved from achievement for mass production + mpq = v[mp]; + + packet->setArrayLengthByName("num_mass_production_choices",mpq); + packet->setArrayDataByName("mass_qty", 1, 0); + for (int x = 1; x < mpq; x++) { + packet->setArrayDataByName("mass_qty", x * 5, x); + } + // Product info + item = master_item_list.GetItem(recipe->GetProductID()); + packet->setDataByName("product_name", item->name.c_str()); + packet->setDataByName("icon", item->GetIcon(client->GetVersion())); + packet->setDataByName("product_qty", recipe->GetProductQuantity()); + packet->setDataByName("primary_title", recipe->GetPrimaryBuildComponentTitle()); + packet->setDataByName("primary_qty_needed", recipe->GetPrimaryComponentQuantity()); + packet->setDataByName("unknown6", 11); + packet->setDataByName("unknown3", 18); + // Reset item to 0 + item, item_player = 0; + + // Check to see if we have a primary component (slot = 0) + if (recipe->components.count(0) > 0) { + vector rc = recipe->components[0]; + vector > selected_items; + + int32 total_primary_items = 0; + for (itr = rc.begin(); itr != rc.end(); itr++) { + itemss = client->GetPlayer()->item_list.GetAllItemsFromID((*itr)); + if (itemss.size() > 0) + total_primary_items += itemss.size(); + } + packet->setArrayLengthByName("num_primary_choices", total_primary_items); + + + int16 have_qty = 0; + for (itr = rc.begin(); itr != rc.end(); itr++, i++) { + if (firstID == 0) + firstID = *itr; + + item = master_item_list.GetItem(*itr); + if (!item) + { + client->GetPlayer()->SetCurrentRecipe(0); + LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Error creating packet to client missing item %u", *itr); + client->Message(CHANNEL_COLOR_RED, "Error producing create recipe packet! Recipe is trying to find item %u, but it is missing!", *itr); + safe_delete(packet); + return; + } + item_player = 0; + item_player = client->GetPlayer()->item_list.GetItemFromID((*itr)); + + itemss = client->GetPlayer()->item_list.GetAllItemsFromID((*itr)); + if (itemss.size() > 0) { + + int16 needed_qty = recipe->GetPrimaryComponentQuantity(); + if (firstID == 0) + firstID = *itr; + for (int8 i = 0; i < itemss.size(); i++, k++) { + IDcount++; + if (have_qty < needed_qty) { + + int16 stack_count = itemss[i]->details.count; + int16 min_qty = min(stack_count, int16(needed_qty - have_qty)); + selected_items.push_back(make_pair(int32(itemss[i]->details.unique_id), min_qty)); + have_qty += min_qty; + } + packet->setArrayDataByName("primary_component", itemss[i]->name.c_str(), k); + packet->setArrayDataByName("primary_item_id", itemss[i]->details.unique_id, k); + packet->setArrayDataByName("primary_icon", itemss[i]->GetIcon(client->GetVersion()), k); + packet->setArrayDataByName("primary_total_quantity", itemss[i]->details.count, k); + //packet->setArrayDataByName("primary_supply_depot", itemss[i]->details.count, k); // future need + //packet->setArrayDataByName("primary_unknown3a",); // future need + } + packet->setDataByName("primary_id", itemss[0]->details.unique_id); + packet->setDataByName("primary_default_selected_id", itemss[0]->details.unique_id); + for (int8 i = 0; i < selected_items.size(); i++) { + + + + + + } + int16 qty = 0; + if (item) { + qty = (int8)item->details.count; + if (qty > 0 && firstID == primary_comp_id) + qty -= 1; + } + } + else + packet->setDataByName("primary_id",*itr); + } + // store the id of the primary comp + primary_comp_id = firstID; + + // Set the default item id to the first component id + packet->setArrayLengthByName("num_primary_items_selected", selected_items.size()); + for (int8 i = 0; i < selected_items.size(); i++) { + packet->setArrayDataByName("primary_selected_item_qty", selected_items[i].second,i ); + packet->setArrayDataByName("primary_selected_item_id", selected_items[i].first,i); + + } + + // Reset the variables we will reuse + i = 0; + firstID = 0; + item = 0; + k = 0; + } + else { + LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Recipe has no primary component"); + return; + } + + // Check to see if we have build components (slot = 1 - 4) + int8 total_build_components = 0; + if (recipe->components.count(1) > 0) + total_build_components++; + if (recipe->components.count(2)) + total_build_components++; + if (recipe->components.count(3)) + total_build_components++; + if (recipe->components.count(4)) + total_build_components++; + //--------------------------------------------------------------Start Build Components------------------------------------------------------------- + if (total_build_components > 0) { + packet->setArrayLengthByName("num_build_components", total_build_components); + LogWrite(TRADESKILL__INFO, 0, "Recipes", "num_build_components %u", total_build_components); + for (int8 index = 0; index < 4; index++) { + if (recipe->components.count(index + 1) == 0) + continue; + packet->setArrayDataByName("build_slot", index, index); + vector rc = recipe->components[index + 1]; + int32 total_component_items = 0; + int8 hasComp = 0; + vector > selected_items; + for (itr = rc.begin(); itr != rc.end(); itr++) { + itemss = client->GetPlayer()->item_list.GetAllItemsFromID((*itr)); + if (itemss.size() > 0) + total_component_items += itemss.size(); + } + packet->setSubArrayLengthByName("num_build_choices", total_component_items, index);// get # build choces first + hasComp = 0; + char msgbuf[200] = ""; + for (itr = rc.begin(); itr != rc.end(); itr++) {// iterates through each recipe component to find the stacks in inventory + item = master_item_list.GetItem(*itr); + itemss = client->GetPlayer()->item_list.GetAllItemsFromID((*itr)); + if (itemss.size() > 0) { + int16 needed_qty = 0; + int16 have_qty = 0; + if (index == 0) { + needed_qty = recipe->GetBuild1ComponentQuantity(); + have_qty = 0; + } + else if (index == 1) { + needed_qty = recipe->GetBuild2ComponentQuantity(); + have_qty = 0; + } + else if (index == 2) { + needed_qty = recipe->GetBuild3ComponentQuantity(); + have_qty = 0; + } + else if (index == 3) { + needed_qty = recipe->GetBuild4ComponentQuantity(); + have_qty = 0; + } + if (firstID == 0) + firstID = *itr; + if (hasComp == 0) { + hasComp = 100 + k; + } + for (int8 j = 0; j < itemss.size(); j++, k++) { // go through each stack of a compnent + if (have_qty < needed_qty) { + + int16 stack_count = itemss[j]->details.count; + int16 min_qty = min(stack_count, int16(needed_qty - have_qty)); + selected_items.push_back(make_pair(int32(itemss[j]->details.unique_id), min_qty)); + have_qty += min_qty; + } + item = master_item_list.GetItem(itemss[j]->details.item_id); + packet->setSubArrayDataByName("build_component", itemss[j]->name.c_str(), index, k); + packet->setSubArrayDataByName("build_item_id", itemss[j]->details.unique_id, index, k); + packet->setSubArrayDataByName("build_icon", itemss[j]->GetIcon(client->GetVersion()), index, k); + packet->setSubArrayDataByName("build_total_quantity", itemss[j]->details.count, index, k); + //packet->setSubArrayDataByName("build_supply_depot",); // future need + //packet->setSubArrayDataByName("build_unknown3",); // future need + + } + } + } + packet->setSubArrayLengthByName("num_build_items_selected", selected_items.size(),index ); + for (int8 i = 0; i < selected_items.size(); i++) { + packet->setSubArrayDataByName("build_selected_item_qty", selected_items[i].second,index, i); + packet->setSubArrayDataByName("build_selected_item_id", selected_items[i].first,index, i); + + } + int16 qty = 0; + if (item) { + qty = (int16)item->details.count; + if (qty > 0 && firstID == primary_comp_id) + qty -= 1; + } + if (index == 0) { + packet->setArrayDataByName("build_title", recipe->GetBuild1ComponentTitle(), index); + packet->setArrayDataByName("build_qty_needed", recipe->GetBuild1ComponentQuantity(), index); + if (item) + packet->setArrayDataByName("build_selected_item_qty_have", min(qty, recipe->GetBuild1ComponentQuantity()), index); + } + else if (index == 1) { + packet->setArrayDataByName("build_title", recipe->GetBuild2ComponentTitle(), index); + packet->setArrayDataByName("build_qty_needed", recipe->GetBuild2ComponentQuantity(), index); + if (item) + packet->setArrayDataByName("build_selected_item_qty_have", min(qty, recipe->GetBuild2ComponentQuantity()), index); + } + else if (index == 2) { + packet->setArrayDataByName("build_title", recipe->GetBuild3ComponentTitle(), index); + packet->setArrayDataByName("build_qty_needed", recipe->GetBuild3ComponentQuantity(), index); + if (item) + packet->setArrayDataByName("build_selected_item_qty_have", min(qty, recipe->GetBuild3ComponentQuantity()), index); + } + else { + packet->setArrayDataByName("build_title", recipe->GetBuild4ComponentTitle(), index); + packet->setArrayDataByName("build_qty_needed", recipe->GetBuild4ComponentQuantity(), index); + if (item) + packet->setArrayDataByName("build_selected_item_qty_have", min(qty, recipe->GetBuild4ComponentQuantity()), index); + } + + // Reset the variables we will reuse + i = 0; + firstID = 0; + item = 0; + k = 0; + } + + + int32 xxx = 0; + } + + + // Check to see if we have a fuel component (slot = 5) + if (recipe->components.count(5) > 0) { + vector rc = recipe->components[5]; + vector > selected_items; + for (itr = rc.begin(); itr != rc.end(); itr++, i++) { + if (firstID == 0) + firstID = *itr; + item = master_item_list.GetItem(*itr); + if (!item) + { + LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Error creating packet to client missing item %u", *itr); + client->Message(CHANNEL_COLOR_RED, "Error producing create recipe packet! Recipe is trying to find item %u, but it is missing!", *itr); + safe_delete(packet); + return; + } + item_player = 0; + item_player = client->GetPlayer()->item_list.GetItemFromID((*itr)); + + if(client->GetVersion() <= 561) { + packet->setDataByName("fuel_qty", item->details.count); + packet->setDataByName("fuel_icon", item->GetIcon(client->GetVersion())); + } + + itemss = client->GetPlayer()->item_list.GetAllItemsFromID((*itr)); + packet->setArrayLengthByName("num_fuel_choices", itemss.size()); + if (itemss.size() > 0) { + + int16 needed_qty = recipe->GetFuelComponentQuantity(); + int16 have_qty = 0; + if (firstID == 0) + firstID = *itr; + for (int8 i = 0; i < itemss.size(); i++) { + IDcount++; + if (have_qty < needed_qty) { + + int16 stack_count = itemss[i]->details.count; + int16 min_qty = min(stack_count, int16(needed_qty - have_qty)); + selected_items.push_back(make_pair(int32(itemss[i]->details.unique_id), min_qty)); + have_qty += min_qty; + } + packet->setArrayDataByName("fuel_component", itemss[i]->name.c_str(), i); + packet->setArrayDataByName("fuel_item_id", itemss[i]->details.unique_id, i); + packet->setArrayDataByName("fuel_icon", itemss[i]->GetIcon(client->GetVersion()), i); + packet->setArrayDataByName("fuel_total_quantity", itemss[i]->details.count, i); + //packet->setArrayDataByName("fuel_supply_depot", itemss[i]->details.count, i); // future need + //packet->setArrayDataByName("primary_unknown3a",); // future need + } + packet->setDataByName("fuel_selected_item_id", itemss[0]->details.unique_id); + int16 qty = 0; + if (item) { + qty = (int8)item->details.count; + if (qty > 0 && firstID == primary_comp_id) + qty -= 1; + } + } + else + packet->setDataByName("primary_vvv", *itr); + } + // store the id of the primary comp + primary_comp_id = firstID; + + // Set the default item id to the first component id + packet->setArrayLengthByName("num_fuel_items_selected", selected_items.size()); + for (int8 i = 0; i < selected_items.size(); i++) { + packet->setArrayDataByName("fuel_selected_item_qty", selected_items[i].second, i); + packet->setArrayDataByName("fuel_selected_item_id", selected_items[i].first, i); + } + packet->setDataByName("fuel_title", recipe->GetFuelComponentTitle()); + packet->setDataByName("fuel_qty_needed", recipe->GetFuelComponentQuantity()); + + + // Reset the variables we will reuse + i = 0; + firstID = 0; + item = 0; + } + else { + LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Recipe has no fuel component"); + return; + } + + packet->setDataByName("recipe_id", recipeID); + + packet->PrintPacket(); + EQ2Packet* outapp = packet->serialize(); + //DumpPacket(outapp); + // Send the packet + client->QueuePacket(outapp); + safe_delete(packet); +} + +void ClientPacketFunctions::SendItemCreationUI(Client* client, Recipe* recipe) { + // Check for valid recipe + if (!recipe) { + LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Recipe = null in ClientPacketFunctions::SendItemCreationUI()"); + return; + } + + // Load the packet + PacketStruct* packet = configReader.getStruct("WS_ShowItemCreation", client->GetVersion()); + if (!packet) { + LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Error loading WS_ShowItemCreation in ClientPacketFunctions::SendItemCreationUI()"); + return; + } + + int16 item_version = GetItemPacketType(packet->GetVersion()); + + Item* item = 0; + RecipeProducts* rp = 0; + + packet->setDataByName("max_possible_durability", 1000); + packet->setDataByName("max_possible_progress", 1000); + + // All the packets I have looked at these unknowns are always the same + // so hardcoding them until they are identified. + packet->setDataByName("unknown2", 1045220557, 0); + packet->setDataByName("unknown2", 1061997773, 1); + + // Highest stage the player has been able to complete + // TODO: store this for the player, for now use 0 (none known) + Recipe* playerRecipe = client->GetPlayer()->GetRecipeList()->GetRecipe(recipe->GetID()); + + if(!playerRecipe) + { + LogWrite(TRADESKILL__ERROR, 0, "Tradeskills", "%s: ClientPacketFunctions::SendItemCreationUI Error finding player recipe in their recipe book for recipe id %u", client->GetPlayer()->GetName(), recipe->GetID()); + client->Message(CHANNEL_COLOR_RED, "%s: SendItemCreationUI Error finding player recipe in their recipe book for recipe id %u!", client->GetPlayer()->GetName(), recipe->GetID()); + safe_delete(packet); + return; + } + + packet->setDataByName("progress_levels_known", playerRecipe ? playerRecipe->GetHighestStage() : 0); + + packet->setArrayLengthByName("num_process", 4); + for (int8 i = 0; i < 4; i++) { + + // Don't like this code but need to change the IfVariableNotSet value on unknown3 element + // to point to the currect progress_needed + vector dataStructs = packet->GetDataStructs(); + vector::iterator itr; + for (itr = dataStructs.begin(); itr != dataStructs.end(); itr++) { + DataStruct* data = *itr; + char tmp[20] = {0}; + sprintf(tmp,"_%i",i); + string name = "unknown3"; + name.append(tmp); + if (strcmp(data->GetName(), name.c_str()) == 0) { + name = "progress_needed"; + name.append(tmp); + data->SetIfNotSetVariable(name.c_str()); + } + } + if (i == 1) + packet->setArrayDataByName("progress_needed", 400, i); + else if (i == 2) + packet->setArrayDataByName("progress_needed", 600, i); + else if (i == 3) + packet->setArrayDataByName("progress_needed", 800, i); + + // get the product for this stage, if none found default to fuel + if (recipe->products.count(i) > 0) + rp = recipe->products[i]; + if (!rp || (rp->product_id == 0)) { + rp = new RecipeProducts; + rp->product_id = recipe->components[5].front(); + rp->product_qty = recipe->GetFuelComponentQuantity(); + rp->byproduct_id = 0; + rp->byproduct_qty = 0; + recipe->products[i] = rp; + } + item = master_item_list.GetItem(rp->product_id); + if (!item) { + LogWrite(TRADESKILL__ERROR, 0, "Recipe", "Error loading item (%u) in ClientPacketFunctions::SendItemCreationUI()", rp->product_id); + return; + } + + packet->setArrayDataByName("item_name", item->name.c_str(), i); + packet->setArrayDataByName("item_icon", item->GetIcon(client->GetVersion()), i); + + if(client->GetVersion() < 860) { + packet->setItemArrayDataByName("item", item, client->GetPlayer(), i, 0, client->GetClientItemPacketOffset()); + //packet->setItemArrayDataByName("item", item, client->GetPlayer(), i, 0, -1); + } + else if (client->GetVersion() < 1193) + packet->setItemArrayDataByName("item", item, client->GetPlayer(), i); + else + packet->setItemArrayDataByName("item", item, client->GetPlayer(), i, 0, 2); + + if (rp->byproduct_id > 0) { + item = 0; + item = master_item_list.GetItem(rp->byproduct_id); + if (item) { + packet->setArrayDataByName("item_byproduct_name", item->name.c_str(), i); + packet->setArrayDataByName("item_byproduct_icon", item->GetIcon(client->GetVersion()), i); + } + } + + packet->setArrayDataByName("packettype", item_version, i); + packet->setArrayDataByName("packetsubtype", 0xFF, i); + + item = 0; + rp = 0; + } + packet->setDataByName("product_progress_needed", 1000); + + rp = recipe->products[4]; + item = master_item_list.GetItem(rp->product_id); + + packet->setDataByName("product_item_name", item->name.c_str()); + packet->setDataByName("product_item_icon", item->GetIcon(client->GetVersion())); + + if(client->GetVersion() < 860) + packet->setItemByName("product_item", item, client->GetPlayer(), 0, client->GetClientItemPacketOffset()); + else if (client->GetVersion() < 1193) + packet->setItemByName("product_item", item, client->GetPlayer()); + else + packet->setItemByName("product_item", item, client->GetPlayer(), 0, 2); + + //packet->setItemByName("product_item", item, client->GetPlayer()); + + if (rp->byproduct_id > 0) { + item = 0; + item = master_item_list.GetItem(rp->byproduct_id); + if (item) { + packet->setDataByName("product_byproduct_name", item->name.c_str()); + packet->setDataByName("product_byproduct_icon", item->GetIcon(client->GetVersion())); + } + } + + packet->setDataByName("packettype", item_version); + packet->setDataByName("packetsubtype", 0xFF); + + // Start of basic work to get the skills to show on the tradeskill window + // not even close to accurate but skills do get put on the ui + int8 index = 0; + int8 size = 0; + vector::iterator itr; + vector spells = client->GetPlayer()->GetSpellBookSpellIDBySkill(recipe->GetTechnique()); + for (itr = spells.begin(); itr != spells.end(); itr++) { + size++; + Spell* spell = master_spell_list.GetSpell(*itr,1); + if(!spell) { + + return; + } + if (size > 6) { + // only 6 slots for skills on the ui + break; + } + packet->setDataByName("skill_id", *itr ,spell->GetSpellData()->ts_loc_index -1); + } + + + packet->PrintPacket(); + EQ2Packet* outapp = packet->serialize(); + DumpPacket(outapp); + client->QueuePacket(outapp); + safe_delete(packet); +} + +void ClientPacketFunctions::StopCrafting(Client* client) { + if(client->GetPlayer()) { + client->GetPlayer()->SetTempVisualState(-1); + } + client->QueuePacket(new EQ2Packet(OP_StopItemCreationMsg, 0, 0)); +} + +void ClientPacketFunctions::CounterReaction(Client* client, bool countered) { + PacketStruct* packet = configReader.getStruct("WS_TSEventReaction", client->GetVersion()); + if (packet) { + packet->setDataByName("counter_reaction", countered ? 1 : 0); + client->QueuePacket(packet->serialize()); + } + safe_delete(packet); +} diff --git a/source/WorldServer/Traits/Traits.cpp b/source/WorldServer/Traits/Traits.cpp new file mode 100644 index 0000000..46840a9 --- /dev/null +++ b/source/WorldServer/Traits/Traits.cpp @@ -0,0 +1,932 @@ +/* +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 . +*/ + +#include "Traits.h" +#include "../../common/ConfigReader.h" +#include "../../common/Log.h" +#include "../Spells.h" +#include "../WorldDatabase.h" +#include "../client.h" +#include "../Rules/Rules.h" +#include + +#include + +extern ConfigReader configReader; +extern MasterSpellList master_spell_list; +extern WorldDatabase database; +extern RuleManager rule_manager; + +using namespace boost::assign; + +MasterTraitList::MasterTraitList(){ + MMasterTraitList.SetName("MasterTraitList::TraitList"); +} + +MasterTraitList::~MasterTraitList(){ + DestroyTraits(); +} + +void MasterTraitList::AddTrait(TraitData* data){ + MMasterTraitList.writelock(__FUNCTION__, __LINE__); + TraitList.push_back(data); + MMasterTraitList.releasewritelock(__FUNCTION__, __LINE__); +} + +int MasterTraitList::Size(){ + return TraitList.size(); +} + +bool MasterTraitList::GenerateTraitLists(Client* client, map > >* sortedTraitList, map >* classTraining, + map >* raceTraits, map >* innateRaceTraits, map >* focusEffects, int16 max_level, int8 trait_group) +{ + if (!client) { + LogWrite(SPELL__ERROR, 0, "Traits", "GetTraitListPacket called with an invalid client"); + return false; + } + // Sort the Data + if (Size() == 0 || !sortedTraitList || !classTraining || !raceTraits || !innateRaceTraits || !focusEffects) + return false; + + map > >::iterator itr; + map >::iterator itr2; + vector::iterator itr3; + + MMasterTraitList.readlock(__FUNCTION__, __LINE__); + + for (int i=0; i < Size(); i++) { + if(max_level > 0 && TraitList[i]->level > max_level) { + continue; // skip per max level requirement + } + if(trait_group != 255 && trait_group != TraitList[i]->group) { + continue; + } + + // Sort Character Traits + if (TraitList[i]->classReq == 255 && TraitList[i]->raceReq == 255 && !TraitList[i]->isFocusEffect && TraitList[i]->isTrait) { + itr = sortedTraitList->lower_bound(TraitList[i]->group); + if (itr != sortedTraitList->end() && !(sortedTraitList->key_comp()(TraitList[i]->group, itr->first))) { + + itr2 = (itr->second).lower_bound(TraitList[i]->level); + if (itr2 != (itr->second).end() && !((itr->second).key_comp()(TraitList[i]->level, itr2->first))) { + ((itr->second)[itr2->first]).push_back(TraitList[i]); + LogWrite(SPELL__INFO, 0, "Traits", "Added Trait: %u Tier %i", TraitList[i]->spellID, TraitList[i]->tier); + } + else { + vector tempVec; + tempVec.push_back(TraitList[i]); + (itr->second).insert(make_pair(TraitList[i]->level, tempVec)); + LogWrite(SPELL__INFO, 0, "Traits", "Added Trait: %u Tier %i", TraitList[i]->spellID, TraitList[i]->tier); + } + } + else { + map > tempMap; + vector tempVec; + tempVec.push_back(TraitList[i]); + tempMap.insert(make_pair(TraitList[i]->level, tempVec)); + sortedTraitList->insert(make_pair(TraitList[i]->group, tempMap)); + LogWrite(SPELL__INFO, 0, "Traits", "Added Trait: %u Tier %i", TraitList[i]->spellID, TraitList[i]->tier); + } + } + + // Sort Class Training + if (TraitList[i]->classReq == client->GetPlayer()->GetAdventureClass() && TraitList[i]->isTraining) { + itr2 = classTraining->lower_bound(TraitList[i]->level); + if (itr2 != classTraining->end() && !(classTraining->key_comp()(TraitList[i]->level, itr2->first))) { + (itr2->second).push_back(TraitList[i]); + } + else { + vector tempVec; + tempVec.push_back(TraitList[i]); + classTraining->insert(make_pair(TraitList[i]->level, tempVec)); + } + } + + // Sort Racial Abilities + if (TraitList[i]->raceReq == client->GetPlayer()->GetRace() && !TraitList[i]->isInate && !TraitList[i]->isTraining) { + itr2 = raceTraits->lower_bound(TraitList[i]->group); + if (itr2 != raceTraits->end() && !(raceTraits->key_comp()(TraitList[i]->group, itr2->first))) { + (itr2->second).push_back(TraitList[i]); + } + else { + vector tempVec; + tempVec.push_back(TraitList[i]); + raceTraits->insert(make_pair(TraitList[i]->group, tempVec)); + } + } + + // Sort Innate Racial Abilities + if (TraitList[i]->raceReq == client->GetPlayer()->GetRace() && TraitList[i]->isInate) { + itr2 = innateRaceTraits->lower_bound(TraitList[i]->group); + if (itr2 != innateRaceTraits->end() && !(innateRaceTraits->key_comp()(TraitList[i]->group, itr2->first))) { + (itr2->second).push_back(TraitList[i]); + } + else { + vector tempVec; + tempVec.push_back(TraitList[i]); + innateRaceTraits->insert(make_pair(TraitList[i]->group, tempVec)); + } + } + + // Sort Focus Effects + if ((TraitList[i]->classReq == client->GetPlayer()->GetAdventureClass() || TraitList[i]->classReq == 255) && TraitList[i]->isFocusEffect) { + int8 j = 0; + itr2 = focusEffects->lower_bound(TraitList[i]->group); + if (itr2 != focusEffects->end() && !(focusEffects->key_comp()(TraitList[i]->group, itr2->first))) { + + (itr2->second).push_back(TraitList[i]); + //LogWrite(SPELL__INFO, 0, "Traits", "Added Focus Effect: %u Tier: %i", TraitList[i]->spellID, TraitList[i]->tier); + j++; + } + else { + vector tempVec; + tempVec.push_back(TraitList[i]); + focusEffects->insert(make_pair(TraitList[i]->group, tempVec)); + //LogWrite(SPELL__INFO, 0, "Traits", "Added Focus Effect: %u Tier %i", TraitList[i]->spellID, TraitList[i]->tier); + } + } + } + + MMasterTraitList.releasereadlock(__FUNCTION__, __LINE__); + + return true; +} + +bool MasterTraitList::IdentifyNextTrait(Client* client, map >* traitList, vector* collectTraits, vector* tieredTraits, std::map* previousMatchedSpells, bool omitFoundMatches) { + bool found_spell_match = false; + bool match = false; + bool tiered_selection = rule_manager.GetGlobalRule(R_Player, TraitTieringSelection)->GetBool(); + int8 group_to_apply = 255; + int8 count = 0; + map >::iterator itr2; + vector::iterator itr3; + + for (itr2 = traitList->begin(); itr2 != traitList->end(); itr2++) { + //LogWrite(SPELL__INFO, 0, "AA", "Character Traits Size...%i ", traits_size); + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++) { + if(tiered_selection) { + if(found_spell_match && (*itr3)->group == group_to_apply) { + continue; // skip! + } + else if((*itr3)->group != group_to_apply) { + if(group_to_apply != 255 && !found_spell_match) { + // found match + LogWrite(SPELL__INFO, 0, "Traits", "Found match to group id %u", group_to_apply); + match = true; + break; + } + else { + LogWrite(SPELL__INFO, 0, "Traits", "Try match to group... spell id %u, group id %u", (*itr3)->spellID, (*itr3)->group); + found_spell_match = false; + group_to_apply = (*itr3)->group; + count = 0; + if(!omitFoundMatches) + tieredTraits->clear(); + } + } + } + count++; + + std::map::iterator spell_itr = previousMatchedSpells->find((*itr3)->spellID); + + if(spell_itr != previousMatchedSpells->end() && (*itr3)->group > spell_itr->second) { + continue; + } + if(!IsPlayerAllowedTrait(client, (*itr3))) { + LogWrite(SPELL__INFO, 0, "Traits", "We are not allowed any more spells from this type/group... spell id %u, group id %u", (*itr3)->spellID, (*itr3)->group); + found_spell_match = true; + } + else if (client->GetPlayer()->HasSpell((*itr3)->spellID, (*itr3)->tier)) { + LogWrite(SPELL__INFO, 0, "Traits", "Found existing spell match to group... spell id %u, group id %u", (*itr3)->spellID, (*itr3)->group); + if(!omitFoundMatches) + found_spell_match = true; + previousMatchedSpells->insert(std::make_pair((*itr3)->spellID,(*itr3)->group)); + } + else { + tieredTraits->push_back((*itr3)); + collectTraits->push_back((*itr3)); + } + } + + if(match) + break; + } + + + if(!match && group_to_apply != 255 && !found_spell_match) { + // found match + match = true; + } + else if(!tiered_selection && collectTraits->size() > 0) { + match = true; + } + return match; +} + +bool MasterTraitList::ChooseNextTrait(Client* client) { + map > >* SortedTraitList = new map > >; + map > >::iterator itr; + bool tiered_selection = rule_manager.GetGlobalRule(R_Player, TraitTieringSelection)->GetBool(); + vector::iterator itr3; + + map >* ClassTraining = new map >; + map >* RaceTraits = new map >; + map >* InnateRaceTraits = new map >; + map >* FocusEffects = new map >; + vector* collectTraits = new vector; + vector* tieredTraits = new vector; + std::map* previousMatchedSpells = new std::map; + bool match = false; + if(GenerateTraitLists(client, SortedTraitList, ClassTraining, RaceTraits, InnateRaceTraits, FocusEffects, client->GetPlayer()->GetLevel())) { + + vector* endTraits; + if(!match || !tiered_selection) { + match = IdentifyNextTrait(client, ClassTraining, collectTraits, tieredTraits, previousMatchedSpells); + } + if(!match || !tiered_selection) { + match = IdentifyNextTrait(client, RaceTraits, collectTraits, tieredTraits, previousMatchedSpells, true); + + bool overrideMatch = IdentifyNextTrait(client, InnateRaceTraits, collectTraits, tieredTraits, previousMatchedSpells, true); + + if(!match && overrideMatch) + match = true; + } + if(!match || !tiered_selection) { + match = IdentifyNextTrait(client, FocusEffects, collectTraits, tieredTraits, previousMatchedSpells); + } + + if(!tiered_selection && collectTraits->size() > 0) { + endTraits = collectTraits; + } + else if (match) { + endTraits = tieredTraits; + } + if(match) { + PacketStruct* packet = configReader.getStruct("WS_QuestRewardPackMsg", client->GetVersion()); + // 0=enemy mastery, 1=specialized training,2=character trait, 3=racial tradition + int8 packet_type = 0; + int8 item_count = 0; + packet->setSubstructArrayLengthByName("reward_data", "num_select_rewards", endTraits->size()); + for (itr3 = endTraits->begin(); itr3 != endTraits->end(); itr3++) { + + if((*itr3)->item_id) { + //LogWrite(SPELL__INFO, 0, "Traits", "Item %u to be sent", (*itr3)->item_id); + Item* item = master_item_list.GetItem((*itr3)->item_id); + if(item) { + //LogWrite(SPELL__INFO, 0, "Traits", "Item found %s to be sent", item->name.c_str()); + packet->setArrayDataByName("select_reward_id", (*itr3)->item_id, item_count); + packet->setItemArrayDataByName("select_item", item, client->GetPlayer(), item_count, 0, client->GetClientItemPacketOffset()); + item_count++; + } + } + + + // Sort Character Traits + if ((*itr3)->classReq == 255 && (*itr3)->raceReq == 255 && (*itr3)->isTrait) { + packet_type = 2; + } + // Sort Class Training + else if ((*itr3)->classReq == client->GetPlayer()->GetAdventureClass() && (*itr3)->isTraining) { + packet_type = 1; + } + // Sort Racial Abilities + else if ((*itr3)->raceReq == client->GetPlayer()->GetRace() && !(*itr3)->isInate && !(*itr3)->isTraining) { + packet_type = 3; + } + // Sort Innate Racial Abilities + else if ((*itr3)->raceReq == client->GetPlayer()->GetRace() && (*itr3)->isInate) { + packet_type = 3; + } + + //LogWrite(SPELL__INFO, 0, "Traits", "Sending trait %u", (*itr3)->spellID); + } + packet->setSubstructDataByName("reward_data", "unknown1", packet_type); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + + safe_delete(SortedTraitList); + safe_delete(ClassTraining); + safe_delete(RaceTraits); + safe_delete(InnateRaceTraits); + safe_delete(FocusEffects); + + safe_delete(collectTraits); + safe_delete(tieredTraits); + safe_delete(previousMatchedSpells); + return match; +} + +int16 MasterTraitList::GetSpellCount(Client* client, map >* traits, bool onlyCharTraits) { + if(!traits) + return 0; + + int16 count = 0; + map >::iterator itr2; + vector::iterator itr3; + for (itr2 = traits->begin(); itr2 != traits->end(); itr2++) { + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++) { + if (client->GetPlayer()->HasSpell((*itr3)->spellID, (*itr3)->tier)) { + if(!onlyCharTraits || (onlyCharTraits && (*itr3)->classReq == 255 && (*itr3)->raceReq == 255 && (*itr3)->isTrait)) { + count++; + } + } + } + } + + return count; +} + +vector PersonalTraitLevelLimits = boost::assign::list_of(0)(8)(14)(22)(28)(36)(42)(46)(48); +vector TrainingTraitLevelLimits = boost::assign::list_of(0)(10)(20)(30)(40)(50); +vector RacialTraitLevelLimits = boost::assign::list_of(0)(18)(26)(34)(44); +vector CharacterTraitLevelLimits = boost::assign::list_of(0)(12)(16)(24)(32)(38); + +/*** +Every other level, beginning at Level 8, +you gain an additional advantage — a +Personal Trait, an Enemy Tactic, a Racial +Tradition or a Training ability. Each time +you reach an even-numbered level, you +can select another advantage from the +appropriate list. You don’t have to select +in order — you may take any of the avail- +able choices. +Level Advantage +8 Personal Trait (1st) +10 Training (1st) +12 Enemy Tactic (1st) +14 Personal Trait (2nd) +16 Enemy Tactic (2nd) +18 Racial Tradition (1st) +20 Training (2nd) +22 Personal Trait (3rd) +24 Enemy Tactic (3rd) +26 Racial Tradition (2nd) +28 Personal Trait (4th) +30 Training (3rd) +32 Enemy Tactic (4th) +34 Racial Tradition (3rd) +36 Personal Trait (5th) +38 Enemy Tactic (5th) +40 Training (4th) +42 Personal Trait (6th) +44 Racial Tradition (4th) +46 Personal Trait (7th) +48 Personal Trait (8th) +50 Training (5th) +***/ + +bool MasterTraitList::IsPlayerAllowedTrait(Client* client, TraitData* trait) { + std::unique_lock(client->GetPlayer()->trait_mutex); + map > >* SortedTraitList = client->GetPlayer()->SortedTraitList; + map > >::iterator itr; + map >::iterator itr2; + vector::iterator itr3; + bool use_classic_table = rule_manager.GetGlobalRule(R_Player, ClassicTraitLevelTable)->GetBool(); + + map >* ClassTraining = client->GetPlayer()->ClassTraining; + map >* RaceTraits = client->GetPlayer()->RaceTraits; + map >* InnateRaceTraits = client->GetPlayer()->InnateRaceTraits; + map >* FocusEffects = client->GetPlayer()->FocusEffects; + if(client->GetPlayer()->need_trait_update) { + SortedTraitList->clear(); + ClassTraining->clear(); + RaceTraits->clear(); + InnateRaceTraits->clear(); + FocusEffects->clear(); + } + bool allowed_trait = false; + if(!client->GetPlayer()->need_trait_update || GenerateTraitLists(client, SortedTraitList, ClassTraining, RaceTraits, InnateRaceTraits, FocusEffects, 0)) { + client->GetPlayer()->need_trait_update = false; + if(trait->isFocusEffect) { + + int32 trait_level = rule_manager.GetGlobalRule(R_Player, TraitFocusSelectLevel)->GetInt32(); + int16 num_available_selections = 0; + if(trait_level > 0) { + num_available_selections = client->GetPlayer()->GetLevel() / trait_level; + } + int16 total_used = GetSpellCount(client, FocusEffects); + + int16 classic_avail = 0; + + if(use_classic_table && PersonalTraitLevelLimits.size() > total_used+1) { + int16 classic_level_req = PersonalTraitLevelLimits.at(total_used+1); + if(client->GetPlayer()->GetLevel() >= classic_level_req) + classic_avail = num_available_selections = total_used+1; + else + num_available_selections = 0; + } + else if(use_classic_table) + num_available_selections = 0; + + LogWrite(SPELL__INFO, 9, "Traits", "%s FocusEffects used %u, available %u, classic available %u", client->GetPlayer()->GetName(), total_used, num_available_selections, classic_avail); + if(total_used < num_available_selections) { + allowed_trait = true; + } + } + else if(trait->isTraining) { + int32 trait_level = rule_manager.GetGlobalRule(R_Player, TraitTrainingSelectLevel)->GetInt32(); + int16 num_available_selections = 0; + if(trait_level > 0) { + num_available_selections = client->GetPlayer()->GetLevel() / trait_level; + } + int16 total_used = GetSpellCount(client, ClassTraining); + + int16 classic_avail = 0; + + if(use_classic_table && TrainingTraitLevelLimits.size() > total_used+1) { + int16 classic_level_req = TrainingTraitLevelLimits.at(total_used+1); + if(client->GetPlayer()->GetLevel() >= classic_level_req) + classic_avail = num_available_selections = total_used+1; + else + num_available_selections = 0; + } + else if(use_classic_table) + num_available_selections = 0; + + LogWrite(SPELL__INFO, 9, "Traits", "%s ClassTraining used %u, available %u, classic available %u", client->GetPlayer()->GetName(), total_used, num_available_selections, classic_avail); + if(total_used < num_available_selections) { + allowed_trait = true; + } + } + else { + if(trait->raceReq == client->GetPlayer()->GetRace()) { + int32 trait_level = rule_manager.GetGlobalRule(R_Player, TraitRaceSelectLevel)->GetInt32(); + int16 num_available_selections = 0; + if(trait_level > 0) { + num_available_selections = client->GetPlayer()->GetLevel() / trait_level; + } + int16 total_used = GetSpellCount(client, RaceTraits); + int16 total_used2 = GetSpellCount(client, InnateRaceTraits); + + int16 classic_avail = 0; + + if(use_classic_table && RacialTraitLevelLimits.size() > total_used+total_used2+1) { + int16 classic_level_req = RacialTraitLevelLimits.at(total_used+total_used2+1); + if(client->GetPlayer()->GetLevel() >= classic_level_req) + classic_avail = num_available_selections = total_used+total_used2+1; + else + num_available_selections = 0; + } + else if(use_classic_table) + num_available_selections = 0; + + LogWrite(SPELL__INFO, 9, "Traits", "%s RaceTraits used %u, available %u, classic available %u", client->GetPlayer()->GetName(), total_used+total_used2, num_available_selections, classic_avail); + if(total_used+total_used2 < num_available_selections) { + allowed_trait = true; + } + } + else { // character trait? + int16 num_available_selections = 0; + int32 trait_level = rule_manager.GetGlobalRule(R_Player, TraitCharacterSelectLevel)->GetInt32(); + if(trait_level > 0) { + num_available_selections = client->GetPlayer()->GetLevel() / trait_level; + } + int16 total_used = 0; + for (itr = SortedTraitList->begin(); itr != SortedTraitList->end(); itr++) { + total_used += GetSpellCount(client, &itr->second, true); + } + + int16 classic_avail = 0; + + if(use_classic_table && CharacterTraitLevelLimits.size() > total_used+1) { + int16 classic_level_req = CharacterTraitLevelLimits.at(total_used+1); + if(client->GetPlayer()->GetLevel() >= classic_level_req) + classic_avail = num_available_selections = total_used+1; + else + num_available_selections = 0; + } + else if(use_classic_table) + num_available_selections = 0; + + LogWrite(SPELL__INFO, 9, "Traits", "%s CharacterTraits used %u, available %u, classic available %u", client->GetPlayer()->GetName(), total_used, num_available_selections, classic_avail); + if(total_used < num_available_selections) { + allowed_trait = true; + } + } + } + } + + return allowed_trait; +} + +EQ2Packet* MasterTraitList::GetTraitListPacket (Client* client) +{ + std::unique_lock(client->GetPlayer()->trait_mutex); + + if (!client) { + LogWrite(SPELL__ERROR, 0, "Traits", "GetTraitListPacket called with an invalid client"); + return 0; + } + // Sort the Data + if (Size() == 0) + return NULL; + + map > >* SortedTraitList = client->GetPlayer()->SortedTraitList; + map > >::iterator itr; + map >::iterator itr2; + vector::iterator itr3; + + map >* ClassTraining = client->GetPlayer()->ClassTraining; + map >* RaceTraits = client->GetPlayer()->RaceTraits; + map >* InnateRaceTraits = client->GetPlayer()->InnateRaceTraits; + map >* FocusEffects = client->GetPlayer()->FocusEffects; + + if(client->GetPlayer()->need_trait_update) { + SortedTraitList->clear(); + ClassTraining->clear(); + RaceTraits->clear(); + InnateRaceTraits->clear(); + FocusEffects->clear(); + } + + if(client->GetPlayer()->need_trait_update && !GenerateTraitLists(client, SortedTraitList, ClassTraining, RaceTraits, InnateRaceTraits, FocusEffects)) { + return NULL; + } + + client->GetPlayer()->need_trait_update = false; + + int16 version = 1; + int8 count = 0; + int8 index = 0; + int8 num_traits = 0; + int8 traits_size = 0; + int8 num_focuseffects = 0; + char sTrait [20]; + char temp [20]; + + version = client->GetVersion(); + + // Jabantiz: Get the value for num_traits in the struct (num_traits refers to the number of rows not the total number of traits) + for (itr = SortedTraitList->begin(); itr != SortedTraitList->end(); itr++) { + num_traits += (itr->second).size(); + } + + PacketStruct* packet = configReader.getStruct("WS_TraitsList", version); + + if (packet == NULL) { + return NULL; + } + + packet->setArrayLengthByName("num_traits", num_traits); + + for (itr = SortedTraitList->begin(); itr != SortedTraitList->end(); itr++) { + + for (itr2 = itr->second.begin(); itr2 != itr->second.end(); itr2++, index++) { + traits_size += (itr2->second).size(); + count = 0; + Spell* tmp_spell = 0; + packet->setArrayDataByName("trait_level", (*itr2).first, index); + packet->setArrayDataByName("trait_line", 255, index); + //LogWrite(SPELL__INFO, 0, "AA", "Character Traits Size...%i ", traits_size); + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++, count++) { + // Jabantiz: cant have more then 5 traits per line + tmp_spell = master_spell_list.GetSpell((*itr3)->spellID, (*itr3)->tier); + if(!tmp_spell) { + LogWrite(SPELL__ERROR, 0, "Traits", "Could not find SpellID %u and Tier %i in Master Spell List (line: %i)", (*itr3)->spellID, (*itr3)->tier, __LINE__); + continue; + } + if (count > 4) + break; + + strcpy(sTrait, "trait"); + itoa(count, temp, 10); + strcat(sTrait, temp); + + strcpy(temp, sTrait); + strcat(sTrait, "_icon"); + if (tmp_spell) + packet->setArrayDataByName(sTrait, tmp_spell->GetSpellIcon(), index); + else + LogWrite(SPELL__ERROR, 0, "Traits", "Could not find SpellID %u and Tier %i in Master Spell List (line: %i)", (*itr3)->spellID, (*itr3)->tier, __LINE__); + + strcpy(sTrait, temp); + strcat(sTrait, "_icon2"); + packet->setArrayDataByName(sTrait, 65535, index); + + strcpy(sTrait, temp); + strcat(sTrait, "_id"); + packet->setArrayDataByName(sTrait, (*itr3)->spellID, index); + + strcpy(sTrait, temp); + strcat(sTrait, "_name"); + if (tmp_spell) + packet->setArrayDataByName(sTrait, tmp_spell->GetName(), index); + else + LogWrite(SPELL__ERROR, 0, "Traits", "Could not find SpellID %u and Tier %i in Master Spell List (line: %i)", (*itr3)->spellID, (*itr3)->tier, __LINE__); + + strcpy(sTrait, temp); + strcat(sTrait, "_unknown2"); + packet->setArrayDataByName(sTrait, 1, index); + + strcpy(sTrait, temp); + strcat(sTrait, "_unknown"); + packet->setArrayDataByName(sTrait, 1, index); + + if (client->GetPlayer()->HasSpell((*itr3)->spellID, (*itr3)->tier)) + packet->setArrayDataByName("trait_line", count, index); + } + // Jabantiz: If less then 5 fill the rest of the line with FF FF FF FF FF FF FF FF FF FF FF FF + while (count < 5) { + strcpy(sTrait, "trait"); + itoa(count, temp, 10); + strcat(sTrait, temp); + strcpy(temp, sTrait); + strcat(sTrait, "_icon"); + packet->setArrayDataByName(sTrait, 65535, index); // FF FF + strcpy(sTrait, temp); + strcat(sTrait, "_icon2"); + packet->setArrayDataByName(sTrait, 65535, index); // FF FF + strcpy(sTrait, temp); + strcat(sTrait, "_id"); + packet->setArrayDataByName(sTrait, 0xFFFFFFFF, index); + strcpy(sTrait, temp); + strcat(sTrait, "_unknown"); + packet->setArrayDataByName(sTrait, 0xFFFFFFFF, index); + strcpy(sTrait, temp); + strcat(sTrait, "_name"); + packet->setArrayDataByName(sTrait, "", index); + count++; + } + } + } + + // Class Training portion of the packet + packet->setArrayLengthByName("num_trainings", ClassTraining->size()); + index = 0; + for (itr2 = ClassTraining->begin(); itr2 != ClassTraining->end(); itr2++, index++) { + count = 0; + Spell* tmp_spell = 0; + packet->setArrayDataByName("training_level", itr2->first, index); + packet->setArrayDataByName("training_line", 255, index); + + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++, count++) { + // Jabantiz: cant have more then 5 traits per line + tmp_spell = master_spell_list.GetSpell((*itr3)->spellID, (*itr3)->tier); + if(!tmp_spell) { + LogWrite(SPELL__ERROR, 0, "Traits", "Could not find SpellID %u and Tier %i in Master Spell List (line: %i)", (*itr3)->spellID, (*itr3)->tier, __LINE__); + continue; + } + if (count > 4) + break; + if (client->GetPlayer()->HasSpell((*itr3)->spellID, (*itr3)->tier)) { + packet->setArrayDataByName("training_line", count, index); + } + strcpy(sTrait, "training"); + itoa(count, temp, 10); + strcat(sTrait, temp); + + strcpy(temp, sTrait); + strcat(sTrait, "_icon"); + + if (tmp_spell) + packet->setArrayDataByName(sTrait, tmp_spell->GetSpellIcon(), index); + else + LogWrite(SPELL__ERROR, 0, "Training", "Could not find SpellID %u and Tier %i in Master Spell List (line: %i)", (*itr3)->spellID, (*itr3)->tier, __LINE__); + + strcpy(sTrait, temp); + strcat(sTrait, "_icon2"); + if (tmp_spell) + packet->setArrayDataByName(sTrait, tmp_spell->GetSpellIconBackdrop(), index); + else + LogWrite(SPELL__ERROR, 0, "Training", "Could not find SpellID %u and Tier %i in Master Spell List (line: %i)", (*itr3)->spellID, (*itr3)->tier, __LINE__); + + strcpy(sTrait, temp); + strcat(sTrait, "_id"); + packet->setArrayDataByName(sTrait, (*itr3)->spellID, index); + + strcpy(sTrait, temp); + strcat(sTrait, "_unknown"); + packet->setArrayDataByName(sTrait,0xFFFFFFFF , index); + + strcpy(sTrait, temp); + strcat(sTrait, "_unknown2"); + packet->setArrayDataByName(sTrait, 1, index); + + strcpy(sTrait, temp); + strcat(sTrait, "_name"); + if (tmp_spell) + packet->setArrayDataByName(sTrait, tmp_spell->GetName(), index); + else + LogWrite(SPELL__ERROR, 0, "Training", "Could not find SpellID %u and Tier %i in Master Spell List (line: %i)", (*itr3)->spellID, (*itr3)->tier, __LINE__); + + if (client->GetPlayer()->HasSpell((*itr3)->spellID, (*itr3)->tier)) + packet->setArrayDataByName("training_line", count, index); + } + // Jabantiz: If less then 5 fill the rest of the line with FF FF FF FF FF FF FF FF FF FF FF FF + while (count < 5) { + strcpy(sTrait, "training"); + itoa(count, temp, 10); + strcat(sTrait, temp); + strcpy(temp, sTrait); + strcat(sTrait, "_icon"); + packet->setArrayDataByName(sTrait, 65535, index); // FF FF + strcpy(sTrait, temp); + strcat(sTrait, "_icon2"); + packet->setArrayDataByName(sTrait, 65535, index); // FF FF + strcpy(sTrait, temp); + strcat(sTrait, "_id"); + packet->setArrayDataByName(sTrait, 0xFFFFFFFF, index); + strcpy(sTrait, temp); + strcat(sTrait, "_unknown"); + packet->setArrayDataByName(sTrait, 0xFFFFFFFF, index); + strcpy(sTrait, temp); + strcat(sTrait, "_name"); + packet->setArrayDataByName(sTrait, "", index); + count++; + } + } + // Racial Traits + packet->setArrayLengthByName("num_sections", RaceTraits->size()); + index = 0; + string tempStr; + int8 num_selections = 0; + for (itr2 = RaceTraits->begin(); itr2 != RaceTraits->end(); itr2++, index++) { + count = 0; + Spell* tmp_spell = 0; + switch (itr2->first) + { + case TRAITS_ATTRIBUTES: + tempStr = "Attributes"; + break; + case TRAITS_COMBAT: + tempStr = "Combat"; + break; + case TRAITS_NONCOMBAT: + tempStr = "Noncombat"; + break; + case TRAITS_POOLS: + tempStr = "Pools"; + break; + case TRAITS_RESIST: + tempStr = "Resist"; + break; + case TRAITS_TRADESKILL: + tempStr = "Tradeskill"; + break; + default: + tempStr = "Unknown"; + break; + } + packet->setArrayDataByName("section_name", tempStr.c_str(), index); + packet->setSubArrayLengthByName("num_traditions", (itr2->second).size(), index); + + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++, count++) { + if (client->GetPlayer()->HasSpell((*itr3)->spellID, (*itr3)->tier)) { + num_selections++; + packet->setSubArrayDataByName("tradition_selected", 1, index, count); + } + else { + packet->setSubArrayDataByName("tradition_selected", 0, index, count); + } + tmp_spell = master_spell_list.GetSpell((*itr3)->spellID, (*itr3)->tier); + if (tmp_spell){ + packet->setSubArrayDataByName("tradition_icon", tmp_spell->GetSpellIcon(), index, count); + packet->setSubArrayDataByName("tradition_icon2", tmp_spell->GetSpellIconBackdrop(), index, count); + packet->setSubArrayDataByName("tradition_id", (*itr3)->spellID, index, count); + packet->setSubArrayDataByName("tradition_name", tmp_spell->GetName(), index, count); + packet->setSubArrayDataByName("tradition_unknown_58617_MJ1", 1, index, count); + } + else + LogWrite(SPELL__ERROR, 0, "Traits", "Could not find SpellID %u and Tier %i in Master Spell List (line: %i)", (*itr3)->spellID, (*itr3)->tier, __LINE__); + } + } + int8 num_available_selections = client->GetPlayer()->GetLevel() / 10; + if (num_selections < num_available_selections) + packet->setDataByName("allow_select", num_available_selections - num_selections); + else + packet->setDataByName("allow_select", 0); + + // Innate Racial Traits + index = 0; + + // total number of Innate traits + num_traits = 0; + for (itr2 = InnateRaceTraits->begin(); itr2 != InnateRaceTraits->end(); itr2++) { + num_traits += (itr2->second).size(); + } + packet->setArrayLengthByName("num_abilities", num_traits); + for (itr2 = InnateRaceTraits->begin(); itr2 != InnateRaceTraits->end(); itr2++) { + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++, index++) { + Spell* innate_spell = master_spell_list.GetSpell((*itr3)->spellID, (*itr3)->tier); + if (innate_spell) { + packet->setArrayDataByName("ability_icon", innate_spell->GetSpellIcon(), index); + packet->setArrayDataByName("ability_icon2", innate_spell->GetSpellIconBackdrop(), index); + packet->setArrayDataByName("ability_id", (*itr3)->spellID, index); + packet->setArrayDataByName("ability_name", innate_spell->GetName(), index); + } + else + LogWrite(SPELL__ERROR, 0, "Traits", "Could not find SpellID %u and Tier %i in Master Spell List (line: %i)", (*itr3)->spellID, (*itr3)->tier, __LINE__); + } + } + + if (client->GetVersion() >= 1188) { + // total number of Focus Effects + num_selections = 0; + num_focuseffects = 0; + index = 0; + for (itr2 = FocusEffects->begin(); itr2 != FocusEffects->end(); itr2++) { + num_focuseffects += (itr2->second).size(); + } + packet->setArrayLengthByName("num_focuseffects", num_focuseffects); + for (itr2 = FocusEffects->begin(); itr2 != FocusEffects->end(); itr2++) { + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++, index++) { + Spell* spell = master_spell_list.GetSpell((*itr3)->spellID, (*itr3)->tier); + if (client->GetPlayer()->HasSpell((*itr3)->spellID, (*itr3)->tier)) { + num_selections++; + packet->setArrayDataByName("focus_selected", 1, index); + } + else { + packet->setArrayDataByName("focus_selected", 0, index); + } + if (spell) { + packet->setArrayDataByName("focus_unknown2", 1, index); + packet->setArrayDataByName("focus_icon", spell->GetSpellIcon(), index); + packet->setArrayDataByName("focus_icon2", spell->GetSpellIconBackdrop(), index); + packet->setArrayDataByName("focus_spell_crc", (*itr3)->spellID, index); + packet->setArrayDataByName("focus_name", spell->GetName(), index); + packet->setArrayDataByName("focus_unknown_58617_MJ1", 1, index); + } + else + LogWrite(SPELL__ERROR, 0, "Traits", "Could not find SpellID %u and Tier %i in Master Spell List (line: %i)", (*itr3)->spellID, (*itr3)->tier, __LINE__); + } + } + num_available_selections = client->GetPlayer()->GetLevel() / 9; + if (num_selections < num_available_selections) + packet->setDataByName("focus_allow_select", num_available_selections - num_selections); + else + packet->setDataByName("focus_allow_select", 0); + } + LogWrite(SPELL__PACKET, 0, "Traits", "Dump/Print Packet in func: %s, line: %i", __FUNCTION__, __LINE__); +#if EQDEBUG >= 9 + packet->PrintPacket(); +#endif + EQ2Packet* data = packet->serialize(); + EQ2Packet* outapp = new EQ2Packet(OP_ClientCmdMsg, data->pBuffer, data->size); + //DumpPacket(outapp); + safe_delete(packet); + safe_delete(data); + + return outapp; +} + +// Jabantiz: Probably a better way to do this but can't think of it right now +TraitData* MasterTraitList::GetTrait(int32 spellID) { + vector::iterator itr; + TraitData* data = NULL; + + MMasterTraitList.readlock(__FUNCTION__, __LINE__); + for (itr = TraitList.begin(); itr != TraitList.end(); itr++) { + if ((*itr)->spellID == spellID) { + data = (*itr); + break; + } + } + MMasterTraitList.releasereadlock(__FUNCTION__, __LINE__); + + return data; +} + +TraitData* MasterTraitList::GetTraitByItemID(int32 itemID) { + vector::iterator itr; + TraitData* data = NULL; + + MMasterTraitList.readlock(__FUNCTION__, __LINE__); + for (itr = TraitList.begin(); itr != TraitList.end(); itr++) { + if ((*itr)->item_id == itemID) { + data = (*itr); + break; + } + } + MMasterTraitList.releasereadlock(__FUNCTION__, __LINE__); + + return data; +} + +void MasterTraitList::DestroyTraits(){ + MMasterTraitList.writelock(__FUNCTION__, __LINE__); + vector::iterator itr; + for (itr = TraitList.begin(); itr != TraitList.end(); itr++) + safe_delete((*itr)); + TraitList.clear(); + MMasterTraitList.releasewritelock(__FUNCTION__, __LINE__); +} diff --git a/source/WorldServer/Traits/Traits.h b/source/WorldServer/Traits/Traits.h new file mode 100644 index 0000000..678a59c --- /dev/null +++ b/source/WorldServer/Traits/Traits.h @@ -0,0 +1,94 @@ +/* +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 . +*/ + +#ifndef __Traits__ +#define __Traits__ + +#include +#include +#include "../../common/Mutex.h" +#include "../../common/types.h" +#include "../../common/EQPacket.h" + +class Client; + +struct TraitData +{ + int32 spellID; + int8 level; + int8 classReq; + int8 raceReq; + bool isTrait; + bool isInate; + bool isFocusEffect; + bool isTraining; + int8 tier; + int8 group; + int32 item_id; +}; + +#define TRAITS_ATTRIBUTES 0 +#define TRAITS_COMBAT 1 +#define TRAITS_NONCOMBAT 2 +#define TRAITS_POOLS 3 +#define TRAITS_RESIST 4 +#define TRAITS_TRADESKILL 5 + +class MasterTraitList +{ +public: + MasterTraitList(); + ~MasterTraitList(); + + bool IdentifyNextTrait(Client* client, map >* traitList, vector* collectTraits, vector* tieredTraits, std::map* previousMatchedSpells, bool omitFoundMatches = false); + bool ChooseNextTrait(Client* client); + int16 GetSpellCount(Client* client, map >* traits, bool onlyCharTraits = false); + bool IsPlayerAllowedTrait(Client* client, TraitData* trait); + bool GenerateTraitLists(Client* client, map > >* sortedTraitList, map >* classTraining, + map >* raceTraits, map >* innateRaceTraits, map >* focusEffects, int16 max_level = 0, int8 trait_group = 255); + + /// Sorts the traits for the given client and creats and sends the trait packet. + /// The Client calling this function + /// EQ2Packet* + EQ2Packet* GetTraitListPacket(Client* client); + + /// Add trait data to the global list. + /// The trait data to add. + void AddTrait(TraitData* data); + + /// Get the total number of traits in the global list. + int Size(); + + /// Get the trait data for the given spell. + /// Spell ID to get trait data for. + TraitData* GetTrait(int32 spellID); + + /// Get the trait data for the given item. + /// Item ID to map to the trait data. + TraitData* GetTraitByItemID(int32 itemID); + + /// Empties the master trait list + void DestroyTraits(); +private: + vector TraitList; + Mutex MMasterTraitList; +}; + +#endif \ No newline at end of file diff --git a/source/WorldServer/Transmute.cpp b/source/WorldServer/Transmute.cpp new file mode 100644 index 0000000..f6a05f0 --- /dev/null +++ b/source/WorldServer/Transmute.cpp @@ -0,0 +1,339 @@ +#include "Transmute.h" +#include "../common/MiscFunctions.h" +#include "../common/PacketStruct.h" +#include "client.h" +#include "Items/Items.h" +#include +#include +#include "zoneserver.h" +#include "SpellProcess.h" +#include "../common/Log.h" +#include "WorldDatabase.h" + +extern ConfigReader configReader; +extern MasterSpellList master_spell_list; + +using namespace std; + +int32 Transmute::CreateItemRequest(Client* client, Player* player) { + PacketStruct* p = configReader.getStruct("WS_EqTargetItemCmd", client->GetVersion()); + if (!p) return 0; + + union { + sint32 signed_request_id; + int32 request_id; + }; + + do { + signed_request_id = MakeRandomInt(-2147483648, 2147483647); + } while (signed_request_id == 0); + + map* il = player->GetItemList(); + + p->setDataByName("request_id", request_id); + p->setDataByName("request_type", 1); + p->setDataByName("unknownff", 0xff); + + vector transmutables; + + for (auto& itr : *il) { + if (!itr.second) continue; + + if (ItemIsTransmutable(itr.second)) { + transmutables.push_back(itr.first); + } + } + + p->setArrayLengthByName("item_array_size", transmutables.size()); + + for (int i = 0; i < transmutables.size(); i++) { + p->setArrayDataByName("item_id", transmutables[i], i); + } + + client->QueuePacket(p->serialize()); + + delete il; + delete p; + + client->SetTransmuteID(request_id); + + return request_id; +} + +bool Transmute::ItemIsTransmutable(Item* item) { + //Item level > 0 AND Item is not LORE_EQUP, LORE, NO_VALUE etc AND item rarity is >= 5 + //(4 is treasured but the rarity used for journeyman spells) + //I think flag 16384 is NO-TRANSMUTE but not positive + const int32 disqualifyFlags = NO_ZONE | NO_VALUE | TEMPORARY | NO_DESTROY | NO_TRANSMUTE; + const int32 disqualityFlags2 = ORNATE; + if (item->generic_info.adventure_default_level > 0 + && (item->generic_info.item_flags & disqualifyFlags) == 0 + && (item->generic_info.item_flags2 & disqualityFlags2) == 0 + && item->details.tier >= 5 + && item->stack_count <= 1) + { + return true; + } + + return false; +} + +void Transmute::HandleItemResponse(Client* client, Player* player, int32 req_id, int32 item_id) { + Item* item = player->item_list.GetItemFromUniqueID(item_id); + if (!item) { + client->SimpleMessage(CHANNEL_COLOR_RED, "Could not find the item you wish to transmute. Please try again."); + return; + } + + if (!ItemIsTransmutable(item)) { + client->Message(CHANNEL_COLOR_RED, "%s is not transmutable.", item->name.c_str()); + return; + } + + int32 item_level = item->generic_info.adventure_default_level; + Skill* skill = player->GetSkillByName("Transmuting"); + + int32 required_skill = (std::max(item_level, 5) - 5) * 5; + sint32 item_stat_bonus = player->GetStat(ITEM_STAT_TRANSMUTING); + if (!skill || (skill->current_val+item_stat_bonus) < required_skill) { + client->Message(CHANNEL_COLOR_RED, "You need at least %u Transmuting skill to transmute the %s." + " You have %u Transmuting skill.", required_skill, item->name.c_str(), skill ? (skill->current_val+item_stat_bonus) : 0); + return; + } + + client->SetTransmuteID(item_id); + SendConfirmRequest(client, req_id, item); +} + +void Transmute::SendConfirmRequest(Client* client, int32 req_id, Item* item) { + PacketStruct* p = configReader.getStruct("WS_ChoiceWindow", client->GetVersion()); + if (!p) { + client->SimpleMessage(CHANNEL_COLOR_RED, "Struct error for transmutation. Let a dev know."); + return; + } + + ostringstream ss; + ss << "Are you sure you want to transmute the " << item->name << '?'; + p->setMediumStringByName("text", ss.str().c_str()); + p->setMediumStringByName("accept_text", "OK"); + + ss.str(""); + ss << "targetitem " << req_id << ' ' << item->details.unique_id; + string cancel_command = ss.str(); + ss << " 1"; + string accept_command = ss.str(); + + p->setMediumStringByName("accept_command", accept_command.c_str()); + p->setMediumStringByName("cancel_text", "Cancel"); + p->setMediumStringByName("cancel_command", cancel_command.c_str()); + + client->QueuePacket(p->serialize()); + delete p; +} + +void Transmute::HandleConfirmResponse(Client* client, Player* player, int32 item_id) { + Item* item = player->item_list.GetItemFromUniqueID(item_id); + if (!item) { + client->SimpleMessage(CHANNEL_COLOR_RED, "Item no longer exists!"); + return; + } + + client->SetTransmuteID(item_id); + + ZoneServer* zone = player->GetZone(); + if (!zone) return; + + const int32 transmute_item_spell = 5163; + + Spell* spell = master_spell_list.GetSpell(transmute_item_spell, 1); + + if (!spell) { + LogWrite(SPELL__ERROR, 0, "Transmute", "Could not find the Transmute Item spell : %u", transmute_item_spell); + return; + } + + zone->GetSpellProcess()->ProcessSpell(zone, spell, player); +} + +void Transmute::CompleteTransmutation(Client* client, Player* player) { + int32 item_id = client->GetTransmuteID(); + Item* item = player->item_list.GetItemFromUniqueID(item_id); + if (!item) { + client->SimpleMessage(CHANNEL_COLOR_RED, "Item no longer exists!"); + return; + } + + int32 common_mat_id = 0; + int32 rare_mat_id = 0; + + //Figure out the transmutation tier for our loot roll + int32 item_level = item->generic_info.adventure_default_level; + vector& tiers = GetTransmutingTiers(); + for (auto& itr : tiers) { + if (itr.min_level <= item_level && itr.max_level >= item_level) { + //This is the correct tier + int32 tier = item->details.tier; + + if (tier >= ITEM_TAG_FABLED) { + common_mat_id = itr.infusion_id; + rare_mat_id = itr.mana_id; + } + else if (tier >= ITEM_TAG_LEGENDARY) { + common_mat_id = itr.powder_id; + rare_mat_id = itr.infusion_id; + } + else { + common_mat_id = itr.fragment_id; + rare_mat_id = itr.powder_id; + } + + break; + } + } + + if (common_mat_id == 0 || rare_mat_id == 0) { + client->SimpleMessage(CHANNEL_COLOR_RED, "Could not complete transmutation! Tell a dev!"); + return; + } + + //Do the loot roll + const int32 BOTH_ITEMS_CHANCE_PERCENT = 15; + //The common/rare roll only applies if the both items roll fails + const int32 COMMON_MAT_CHANCE_PERCENT = 75; + const int32 RARE_MAT_CHANCE_PERCENT = 25; + + Item* item1 = nullptr; + Item* item2 = nullptr; + + int32 roll = MakeRandomInt(1, 100); + if (roll <= BOTH_ITEMS_CHANCE_PERCENT) { + item1 = master_item_list.GetItem(rare_mat_id); + if (item1) item1 = new Item(item1); + + item2 = master_item_list.GetItem(common_mat_id); + if (item2) item2 = new Item(item2); + } + else if (roll <= COMMON_MAT_CHANCE_PERCENT) { + item1 = master_item_list.GetItem(common_mat_id); + if (item1) item1 = new Item(item1); + } + else { //rare mat roll + item2 = master_item_list.GetItem(rare_mat_id); + if (item2) item2 = new Item(item2); + } + + client->Message(89, "You transmute %s and create: ", item->CreateItemLink(client->GetVersion(), false).c_str()); + + player->item_list.RemoveItem(item, true); + + PacketStruct* packet = configReader.getStruct("WS_QuestComplete", client->GetVersion()); + if (packet) { + packet->setDataByName("title", "Item Transmuted!"); + } + + if (item1) { + item1->details.count = 1; + client->Message(89, " %s", item1->CreateItemLink(client->GetVersion(), false).c_str()); + bool itemDeleted = false; + client->AddItem(item1, &itemDeleted); + + if (packet && !itemDeleted) { + packet->setArrayDataByName("reward_id", item1->details.item_id, 0); + if (client->GetVersion() < 860) + packet->setItemArrayDataByName("item", item1, player, 0, 0, -1); + else if (client->GetVersion() < 1193) + packet->setItemArrayDataByName("item", item, player); + else + packet->setItemArrayDataByName("item", item1, player, 0, 0, 2); + } + } + + if (item2) { + item2->details.count = 1; + client->Message(89, " %s", item2->CreateItemLink(client->GetVersion(), false).c_str()); + bool itemDeleted = false; + client->AddItem(item2, &itemDeleted); + + if (packet && !itemDeleted) { + int32 dataIndex = 1; + if (!item1) { + packet->setArrayLengthByName("num_rewards", 1); + dataIndex = 0; + } + packet->setArrayDataByName("reward_id", item2->details.item_id, dataIndex); + if (client->GetVersion() < 860) + packet->setItemArrayDataByName("item", item2, player, dataIndex, 0, -1); + else if (client->GetVersion() < 1193) + packet->setItemArrayDataByName("item", item2, player, dataIndex); + else + packet->setItemArrayDataByName("item", item2, player, dataIndex, 0, 2); + } + } + + if (packet) { + client->QueuePacket(packet->serialize()); + delete packet; + } + + //Check if we need to apply a skill-up + Skill* skill = player->GetSkillByName("Transmuting"); + if (!skill) { + //Shouldn't happen, sanity check + LogWrite(SKILL__ERROR, 0, "Skill", "Unable to find the transmuting skill for the player %s", player->GetName()); + return; + } + + //Skill up roll + int32 max_trans_level = skill->current_val / 5 + 5; + sint32 level_dif = (sint32)max_trans_level - (sint32)item_level; + if (level_dif > 10 || skill->current_val >= skill->max_val) { + //No skill up possible + LogWrite(SKILL__DEBUG, 7, "Skill", "Transmuting skill up not possible. level_dif = %u, skill val = %u, skill max val = %u", level_dif, skill->current_val, skill->max_val); + return; + } + + //50% Base chance of a skillup at max item level, 20% overall decrease per level difference + const int32 SKILLUP_PERCENT_CHANCE_MAX = 50; + int32 required_roll = SKILLUP_PERCENT_CHANCE_MAX * (1.f - (item_level <= 5 ? 0.f : (level_dif * .2f))); + roll = MakeRandomInt(1, 100); + //LogWrite(SKILL__ERROR, 0, "Skill", "Skill up roll results, roll = %u, required_roll = %u", roll, required_roll); + if (roll <= required_roll) { + player->skill_list.IncreaseSkill(skill, 1); + } +} + +void WorldDatabase::LoadTransmuting() { + DatabaseResult result; + + if (!database_new.Select(&result, + "SELECT min_level, max_level, fragment, powder, infusion, mana FROM `transmuting`")) { + LogWrite(DATABASE__ERROR, 0, "Transmuting", "Error loading transmuting data!"); + return; + } + + Transmute::ProcessDBResult(result); +} + +vector& Transmute::GetTransmutingTiers() { + static vector gTransmutingTiers; + return gTransmutingTiers; +} + +void Transmute::ProcessDBResult(DatabaseResult& result) { + vector& tiers = GetTransmutingTiers(); + tiers.clear(); + tiers.reserve(result.GetNumRows()); + + while (result.Next()) { + tiers.emplace_back(); + TransmutingTier& t = tiers.back(); + + int32_t i = 0; + t.min_level = result.GetInt32(i++); + t.max_level = result.GetInt32(i++); + t.fragment_id = result.GetInt32(i++); + t.powder_id = result.GetInt32(i++); + t.infusion_id = result.GetInt32(i++); + t.mana_id = result.GetInt32(i++); + } +} \ No newline at end of file diff --git a/source/WorldServer/Transmute.h b/source/WorldServer/Transmute.h new file mode 100644 index 0000000..bd87aec --- /dev/null +++ b/source/WorldServer/Transmute.h @@ -0,0 +1,35 @@ +#ifndef TRANSMUTE_H +#define TRANSMUTE_H + +#include "../common/types.h" +#include + +class Client; +class Player; +class Item; +class DatabaseResult; + +class Transmute { +public: + static int32 CreateItemRequest(Client* client, Player* player); + static void HandleItemResponse(Client* client, Player* player, int32 req_id, int32 item_id); + static bool ItemIsTransmutable(Item* item); + static void SendConfirmRequest(Client* client, int32 req_id, Item* item); + static void HandleConfirmResponse(Client* client, Player* player, int32 item_id); + static void CompleteTransmutation(Client* client, Player* player); + static void ProcessDBResult(DatabaseResult& res); + +private: + struct TransmutingTier { + int32 min_level; + int32 max_level; + int32 fragment_id; + int32 powder_id; + int32 infusion_id; + int32 mana_id; + }; + + static std::vector& GetTransmutingTiers(); +}; + +#endif \ No newline at end of file diff --git a/source/WorldServer/Variables.h b/source/WorldServer/Variables.h new file mode 100644 index 0000000..45d2472 --- /dev/null +++ b/source/WorldServer/Variables.h @@ -0,0 +1,92 @@ +/* + 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 . +*/ +#ifndef EQ2_VARIABLES_H +#define EQ2_VARIABLES_H +#include +#include + +class Variable{ +public: + Variable (const char* name, const char* value, const char* comment){ + variableName = string(name); + variableValue = string(value); + if(comment) + variableComment = string(comment); + } + + const char* GetName() { return variableName.c_str(); } + const char* GetValue() { return variableValue.c_str(); } + const char* GetComment() { return variableComment.c_str(); } + string GetNameValuePair(){ return string(variableName).append(" ").append(variableValue); } + void SetValue(const char* value){ + if(value) + variableValue = string(value); + } +private: + string variableName; + string variableValue; + string variableComment; +}; + +class Variables +{ +public: + ~Variables(){ + ClearVariables(); + } + void AddVariable ( Variable* var ) + { + variables[string(var->GetName())] = var; + } + + void ClearVariables() + { + if(variables.size() == 0) + return; + + map::iterator map_list; + for( map_list = variables.begin(); map_list != variables.end(); map_list++ ) { + safe_delete(map_list->second); + } + variables.clear(); + } + + Variable* FindVariable ( string name ) + { + if(variables.count(name) > 0) + return variables[name]; + return 0; + } + + vector* GetVariables(string partial_name){ + vector* ret = new vector(); + map::iterator itr; + for(itr = variables.begin(); itr != variables.end(); itr++){ + if(itr->first.find(partial_name) < 0xFFFFFFFF) + ret->push_back(itr->second); + } + return ret; + } + +private: + map variables; + +}; +#endif diff --git a/source/WorldServer/VisualStates.h b/source/WorldServer/VisualStates.h new file mode 100644 index 0000000..02d9df5 --- /dev/null +++ b/source/WorldServer/VisualStates.h @@ -0,0 +1,282 @@ +/* + 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 . +*/ +#include "../common/Log.h" +#include "../../common/MiscFunctions.h" +#include + +using namespace std; + +// Visual States must use a hash table because of the large amount that exists and the large spacing +// between their ID's. String and character arrays could not be used for the first iterator because +// it would require the same pointer to access it from the hash table, which is obviously not possible +// since the text is from the client. + +// maximum amount of iterations it will attempt to find a entree +#define HASH_SEARCH_MAX 20 + +class VisualState +{ +public: + VisualState(int inID, char* inName){ + if(!inName) + return; + name = string(inName); + id = inID; + } + + int GetID() { return id; } + const char* GetName() { return name.c_str(); } + string GetNameString() { return name; } + +private: + int id; + string name; +}; +class Emote{ +public: + Emote(char* in_name, int in_visual_state, char* in_message, char* in_targeted_message){ + if(!in_name) + return; + name = string(in_name); + visual_state = in_visual_state; + if(in_message) + message = string(in_message); + if(in_targeted_message) + targeted_message = string(in_targeted_message); + } + int32 GetVisualState() { return visual_state; } + const char* GetName() { return name.c_str(); } + const char* GetMessage() { return message.c_str(); } + const char* GetTargetedMessage() { return targeted_message.c_str(); } + + string GetNameString() { return name; } + string GetMessageString() { return message; } + string GetTargetedMessageString() { return targeted_message; } +private: + int32 visual_state; + string name; + string message; + string targeted_message; +}; + +class EmoteVersionRange { +public: + EmoteVersionRange(char* in_name) + { + name = string(in_name); + } + + ~EmoteVersionRange() + { + map::iterator itr; + for (itr = version_map.begin(); itr != version_map.end(); itr++) + { + VersionRange* range = itr->first; + Emote* emote = itr->second; + delete range; + delete emote; + } + + version_map.clear(); + } + + void AddVersionRange(int32 min_version, int32 max_version, + char* in_name, int in_visual_state, char* in_message = nullptr, char* in_targeted_message = nullptr) + { + map::iterator itr = FindVersionRange(min_version, max_version); + if (itr != version_map.end()) + { + VersionRange* range = itr->first; + LogWrite(WORLD__ERROR, 0, "Emotes Table Error: Duplicate emote mapping of %s with range min %u max %u, Existing found with range min %u max %u\n", name.c_str(), min_version, max_version, range->GetMinVersion(), range->GetMaxVersion()); + return; + } + + version_map.insert(make_pair(new VersionRange(min_version, max_version), new Emote(in_name, in_visual_state, in_message, in_targeted_message))); + } + + map::iterator FindVersionRange(int32 min_version, int32 max_version) + { + map::iterator itr; + for (itr = version_map.begin(); itr != version_map.end(); itr++) + { + VersionRange* range = itr->first; + // if min and max version are both in range + if (range->GetMinVersion() <= min_version && max_version <= range->GetMaxVersion()) + return itr; + // if the min version is in range, but max range is 0 + else if (range->GetMinVersion() <= min_version && range->GetMaxVersion() == 0) + return itr; + // if min version is 0 and max_version has a cap + else if (range->GetMinVersion() == 0 && max_version <= range->GetMaxVersion()) + return itr; + } + + return version_map.end(); + } + + map::iterator FindEmoteVersion(int32 version) + { + map::iterator itr; + for (itr = version_map.begin(); itr != version_map.end(); itr++) + { + VersionRange* range = itr->first; + // if min and max version are both in range + if (version >= range->GetMinVersion() && (range->GetMaxVersion() == 0 || version <= range->GetMaxVersion())) + return itr; + } + + return version_map.end(); + } + + const char* GetName() { return name.c_str(); } + string GetNameString() { return name; } + + map::iterator GetRangeEnd() { return version_map.end(); } +private: + map version_map; + string name; +}; + +class VisualStates +{ +public: + ~VisualStates(){ + Reset(); + } + + void Reset(){ + ClearVisualStates(); + ClearEmotes(); + ClearSpellVisuals(); + } + + void ClearEmotes(){ + map::iterator map_list; + for(map_list = emoteMap.begin(); map_list != emoteMap.end(); map_list++ ) + safe_delete(map_list->second); + emoteMap.clear(); + } + + void ClearVisualStates(){ + map::iterator map_list; + for(map_list = visualStateMap.begin(); map_list != visualStateMap.end(); map_list++ ) + safe_delete(map_list->second); + visualStateMap.clear(); + } + + void InsertVisualState(VisualState* vs){ + visualStateMap[vs->GetNameString()] = vs; + } + + VisualState* FindVisualState(string var){ + if(visualStateMap.count(var) > 0) + return visualStateMap[var]; + return 0; + } + + void InsertEmoteRange(EmoteVersionRange* emote) { + emoteMap[emote->GetName()] = emote; + } + + EmoteVersionRange* FindEmoteRange(string var) { + if (emoteMap.count(var) > 0) + { + return emoteMap[var]; + } + return 0; + } + + Emote* FindEmote(string var, int32 version){ + if (emoteMap.count(var) > 0) + { + map::iterator itr = emoteMap[var]->FindEmoteVersion(version); + + if (itr != emoteMap[var]->GetRangeEnd()) + { + Emote* emote = itr->second; + return emote; + } + } + return 0; + } + + void InsertSpellVisualRange(EmoteVersionRange* emote, int32 spell_visual_id) { + spellMap[emote->GetName()] = emote; + spellMapID[spell_visual_id] = emote; + } + + EmoteVersionRange* FindSpellVisualRange(string var) { + if (spellMap.count(var) > 0) + { + return spellMap[var]; + } + return 0; + } + + EmoteVersionRange* FindSpellVisualRangeByID(int32 id) { + if (spellMapID.count(id) > 0) + { + return spellMapID[id]; + } + return 0; + } + + Emote* FindSpellVisual(string var, int32 version){ + if (spellMap.count(var) > 0) + { + map::iterator itr = spellMap[var]->FindEmoteVersion(version); + + if (itr != spellMap[var]->GetRangeEnd()) + { + Emote* emote = itr->second; + return emote; + } + } + return 0; + } + + Emote* FindSpellVisualByID(int32 visual_id, int32 version){ + if (spellMapID.count(visual_id) > 0) + { + map::iterator itr = spellMapID[visual_id]->FindEmoteVersion(version); + + if (itr != spellMapID[visual_id]->GetRangeEnd()) + { + Emote* emote = itr->second; + return emote; + } + } + return 0; + } + + void ClearSpellVisuals(){ + map::iterator map_list; + for(map_list = spellMap.begin(); map_list != spellMap.end(); map_list++ ) + safe_delete(map_list->second); + spellMap.clear(); + spellMapID.clear(); + } +private: + map visualStateMap; + map emoteMap; + map spellMap; + map spellMapID; +}; + diff --git a/source/WorldServer/Web/WorldWeb.cpp b/source/WorldServer/Web/WorldWeb.cpp new file mode 100644 index 0000000..c8ff5ec --- /dev/null +++ b/source/WorldServer/Web/WorldWeb.cpp @@ -0,0 +1,84 @@ +#include "../World.h" +#include "../LoginServer.h" + +#include +#include +#include + +extern ZoneList zone_list; +extern World world; +extern LoginServer loginserver; +extern sint32 numclients; + +void World::Web_worldhandle_status(const http::request& req, http::response& res) { + res.set(http::field::content_type, "application/json"); + boost::property_tree::ptree pt; + + pt.put("web_status", "online"); + bool world_online = world.world_loaded; + pt.put("world_status", world.world_loaded ? "online" : "offline"); + pt.put("world_uptime", (getCurrentTimestamp() - world.world_uptime)); + auto [days, hours, minutes, seconds] = convertTimestampDuration((getCurrentTimestamp() - world.world_uptime)); + std::string uptime_str("Days: " + std::to_string(days) + ", " + "Hours: " + std::to_string(hours) + ", " + "Minutes: " + std::to_string(minutes) + ", " + "Seconds: " + std::to_string(seconds)); + pt.put("world_uptime_string", uptime_str); + pt.put("login_connected", loginserver.Connected() ? "connected" : "disconnected"); + pt.put("player_count", zone_list.GetZonesPlayersCount()); + pt.put("client_count", numclients); + pt.put("zones_connected", zone_list.Count()); + pt.put("world_reloading", world.IsReloadingSubsystems() ? "yes" : "no"); + + std::ostringstream oss; + boost::property_tree::write_json(oss, pt); + std::string json = oss.str(); + res.body() = json; + res.prepare_payload(); +} + +void World::Web_worldhandle_clients(const http::request& req, http::response& res) { + zone_list.PopulateClientList(res); +} + +void ZoneList::PopulateClientList(http::response& res) { + res.set(http::field::content_type, "application/json"); + boost::property_tree::ptree maintree; + + std::ostringstream oss; + + MClientList.lock(); + map::iterator itr; + for(itr = client_map.begin(); itr != client_map.end(); itr++){ + if(itr->second){ + Client* cur = (Client*)itr->second; + boost::property_tree::ptree pt; + pt.put("character_id", cur->GetCharacterID()); + pt.put("character_name", cur->GetPlayer() ? cur->GetPlayer()->GetName() : "N/A"); + pt.put("class1", cur->GetPlayer() ? cur->GetPlayer()->GetInfoStruct()->get_class1() : 0); + pt.put("class2", cur->GetPlayer() ? cur->GetPlayer()->GetInfoStruct()->get_class2() : 0); + pt.put("class3", cur->GetPlayer() ? cur->GetPlayer()->GetInfoStruct()->get_class3() : 0); + pt.put("tradeskill_class1", cur->GetPlayer() ? cur->GetPlayer()->GetInfoStruct()->get_tradeskill_class1() : 0); + pt.put("tradeskill_class2", cur->GetPlayer() ? cur->GetPlayer()->GetInfoStruct()->get_tradeskill_class2() : 0); + pt.put("tradeskill_class3", cur->GetPlayer() ? cur->GetPlayer()->GetInfoStruct()->get_tradeskill_class3() : 0); + pt.put("race", cur->GetPlayer() ? cur->GetPlayer()->GetInfoStruct()->get_race() : 0); + pt.put("level", cur->GetPlayer() ? cur->GetPlayer()->GetInfoStruct()->get_level() : 0); + pt.put("effective_level", cur->GetPlayer() ? cur->GetPlayer()->GetInfoStruct()->get_effective_level() : 0); + pt.put("tradeskill_level", cur->GetPlayer() ? cur->GetPlayer()->GetInfoStruct()->get_tradeskill_level() : 0); + pt.put("account_age", cur->GetPlayer() ? cur->GetPlayer()->GetInfoStruct()->get_account_age_base() : 0); + pt.put("account_id", cur->GetAccountID()); + pt.put("version", cur->GetVersion()); + pt.put("is_zoning", (cur->IsZoning() || !cur->IsReadyForUpdates())); + + bool linkdead = cur->GetPlayer() ? (((cur->GetPlayer()->GetActivityStatus() & ACTIVITY_STATUS_LINKDEAD) > 0)) : false; + pt.put("is_linkdead", linkdead); + pt.put("in_zone", cur->IsReadyForUpdates()); + pt.put("zonename", (cur->GetPlayer() && cur->GetPlayer()->GetZone()) ? cur->GetPlayer()->GetZone()->GetZoneName() : "N/A"); + maintree.add_child("Client", pt); + } + } + MClientList.unlock(); + + boost::property_tree::write_json(oss, maintree); + std::string json = oss.str(); + res.body() = json; + res.prepare_payload(); +} + diff --git a/source/WorldServer/Widget.cpp b/source/WorldServer/Widget.cpp new file mode 100644 index 0000000..37444ca --- /dev/null +++ b/source/WorldServer/Widget.cpp @@ -0,0 +1,468 @@ +/* + 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 . +*/ + +#include +#include + +#include "Widget.h" +#include "../common/ConfigReader.h" +#include "Spells.h" +#include "World.h" +#include "../common/Log.h" +#include "ClientPacketFunctions.h" +#include "LuaInterface.h" + +extern World world; +extern ConfigReader configReader; +extern MasterSpellList master_spell_list; +extern LuaInterface* lua_interface; + +Widget::Widget(){ + widget_id = 0; + widget_x = 0; + widget_y = 0; + widget_z = 0; + action_spawn = 0; + action_spawn_id = 0; + linked_spawn = 0; + linked_spawn_id = 0; + appearance.pos.state = 1; + appearance.difficulty = 0; + spawn_type = 2; + appearance.activity_status = 64; + include_location = true; + include_heading = true; + is_open = false; + widget_type = 0; + open_heading = -1; + closed_heading = -1; + open_y = 0; + open_x = 0; + open_z = 0; + close_x = 0; + close_z = 0; + movement_index = 0; + movement_interrupted = false; + resume_movement = true; + attack_resume_needed = false; + multi_floor_lift = false; + MMovementLoop.SetName("Widget::MMovementLoop"); + last_movement_update = Timer::GetCurrentTime2(); +} +Widget::~Widget(){ + +} + +int32 Widget::GetWidgetID(){ + return widget_id; +} + +EQ2Packet* Widget::serialize(Player* player, int16 version){ + return spawn_serialize(player, version); +} + +void Widget::SetWidgetID(int32 val){ + widget_id = val; +} + +void Widget::SetWidgetX(float val){ + widget_x = val; +} + +float Widget::GetWidgetX(){ + return widget_x; +} + +void Widget::SetWidgetY(float val){ + widget_y = val; +} + +float Widget::GetWidgetY(){ + return widget_y; +} + +void Widget::SetWidgetZ(float val){ + widget_z = val; +} + +float Widget::GetWidgetZ(){ + return widget_z; +} + +void Widget::SetWidgetIcon(int8 val){ + appearance.icon = val; +} +void Widget::SetOpenDuration(int16 val){ + open_duration = val; +} +int16 Widget::GetOpenDuration(){ + return open_duration; +} + +Widget* Widget::Copy(){ + Widget* new_spawn = new Widget(); + if(GetOpenY() > 0) + appearance.pos.state = 0; + if(GetSizeOffset() > 0){ + int8 offset = GetSizeOffset()+1; + sint32 tmp_size = size + (rand()%offset - rand()%offset); + if(tmp_size < 0) + tmp_size = 1; + else if(tmp_size >= 0xFFFF) + tmp_size = 0xFFFF; + new_spawn->size = (int16)tmp_size; + } + else + new_spawn->size = size; + new_spawn->SetCollector(IsCollector()); + new_spawn->SetMerchantID(merchant_id); + new_spawn->SetMerchantType(merchant_type); + new_spawn->SetMerchantLevelRange(GetMerchantMinLevel(), GetMerchantMaxLevel()); + new_spawn->SetPrimaryCommands(&primary_command_list); + new_spawn->primary_command_list_id = primary_command_list_id; + new_spawn->SetSecondaryCommands(&secondary_command_list); + new_spawn->secondary_command_list_id = secondary_command_list_id; + new_spawn->database_id = database_id; + memcpy(&new_spawn->appearance, &appearance, sizeof(AppearanceData)); + new_spawn->SetWidgetID(widget_id); + new_spawn->SetWidgetX(widget_x); + new_spawn->SetWidgetY(widget_y); + new_spawn->SetWidgetZ(widget_z); + new_spawn->SetIncludeHeading(include_heading); + new_spawn->SetIncludeLocation(include_location); + new_spawn->SetOpenY(open_y); + new_spawn->SetCloseY(close_y); + new_spawn->SetOpenDuration(open_duration); + if(GetOpenSound()) + new_spawn->SetOpenSound(GetOpenSound()); + if(GetCloseSound()) + new_spawn->SetCloseSound(GetCloseSound()); + new_spawn->SetOpenHeading(open_heading); + new_spawn->SetClosedHeading(closed_heading); + new_spawn->SetWidgetType(widget_type); + new_spawn->SetActionSpawnID(action_spawn_id); + new_spawn->SetLinkedSpawnID(linked_spawn_id); + new_spawn->SetTransporterID(GetTransporterID()); + new_spawn->SetHouseID(GetHouseID()); + new_spawn->SetCloseX(GetCloseX()); + new_spawn->SetCloseZ(GetCloseZ()); + new_spawn->SetOpenX(GetOpenX()); + new_spawn->SetOpenZ(GetOpenZ()); + new_spawn->SetMultiFloorLift(multi_floor_lift); + new_spawn->SetSoundsDisabled(IsSoundsDisabled()); + return new_spawn; +} + +void Widget::SetIncludeLocation(bool val){ + include_location = val; +} +bool Widget::GetIncludeLocation(){ + return include_location; +} +void Widget::SetIncludeHeading(bool val){ + include_heading = val; +} +bool Widget::GetIncludeHeading(){ + return include_heading; +} +float Widget::GetOpenHeading(){ + return open_heading; +} +void Widget::SetOpenHeading(float val){ + open_heading = val; +} +float Widget::GetClosedHeading(){ + return closed_heading; +} +void Widget::SetClosedHeading(float val){ + closed_heading = val; +} +float Widget::GetOpenY(){ + return open_y; +} +void Widget::SetOpenY(float val){ + open_y = val; +} +float Widget::GetCloseY(){ + return close_y; +} +void Widget::SetCloseY(float val){ + close_y = val; +} +bool Widget::IsOpen(){ + std::lock_guard lk(MWidgetMutex); + bool widget_open = is_open; + return widget_open; +} +int8 Widget::GetWidgetType(){ + return widget_type; +} +void Widget::SetWidgetType(int8 val){ + widget_type = val; +} +int32 Widget::GetActionSpawnID(){ + return action_spawn_id; +} +void Widget::SetActionSpawnID(int32 id){ + action_spawn_id = id; +} +int32 Widget::GetLinkedSpawnID(){ + return linked_spawn_id; +} +void Widget::SetLinkedSpawnID(int32 id){ + linked_spawn_id = id; +} +const char* Widget::GetOpenSound(){ + if(open_sound.length() > 0) + return open_sound.c_str(); + else + return 0; +} +void Widget::SetOpenSound(const char* name){ + open_sound = string(name); +} +const char* Widget::GetCloseSound(){ + if(close_sound.length() > 0) + return close_sound.c_str(); + else + return 0; +} +void Widget::SetCloseSound(const char* name){ + close_sound = string(name); +} + +void Widget::HandleTimerUpdate(){ + if(widget_type == WIDGET_TYPE_LIFT) + return; //This Widget is a lift, return. + else if (widget_type == WIDGET_TYPE_DOOR && is_open) + HandleUse(nullptr, ""); +} + +void Widget::OpenDoor(){ + std::lock_guard lk(MWidgetMutex); + if(GetOpenHeading() >= 0) + SetHeading(GetOpenHeading()); + float openX = GetOpenX(); + float openY = GetOpenY(); + float openZ = GetOpenZ(); + if(openX != 0 || openY != 0 || openZ != 0 ) { + float x = GetX(); + float y = GetY(); + float z = GetZ(); + + if(openX != 0) + x = openX; + if(openY != 0) + y = openY; + if(openZ != 0) + z = openZ; + + AddRunningLocation(x, y, z, 4); + + float diff = GetDistance(GetX(), GetY(), GetZ(), x, y, z); + if(diff < 0) + diff*=-1; + GetZone()->AddWidgetTimer(this, diff / 4); + } + if (widget_type != WIDGET_TYPE_LIFT) + SetActivityStatus(0); + is_open = true; + if(open_duration > 0) + GetZone()->AddWidgetTimer(this, open_duration); + + GetZone()->SendSpawnChanges(this); +} + +void Widget::CloseDoor(){ + std::lock_guard lk(MWidgetMutex); + if(GetClosedHeading() > 0) + SetHeading(GetClosedHeading()); + else if(GetOpenHeading() >= 0) + SetHeading(GetSpawnOrigHeading()); + + if (widget_type != WIDGET_TYPE_LIFT) + SetActivityStatus(64); + + if (GetCloseX() != 0 || GetCloseY() != 0 || GetCloseZ() != 0 || GetOpenX() != 0 || GetOpenY() != 0 || GetOpenZ() != 0) { + float x = GetSpawnOrigX(); + float y = GetSpawnOrigY(); + float z = GetSpawnOrigZ(); + + if (GetCloseX() != 0) + x = GetCloseX(); + if (GetCloseY() != 0) + y = GetCloseY(); + if (GetCloseZ() != 0) + z = GetCloseZ(); + + AddRunningLocation(x, y, z, 4); + + float diff = GetDistance(GetX(), GetY(), GetZ(), x, y, z); + + if (diff < 0) + diff *= -1; + GetZone()->AddWidgetTimer(this, diff / 4); + } + + is_open = false; + + GetZone()->SendSpawnChanges(this); +} + +void Widget::ProcessUse(Spawn* caller){ + if(widget_type == WIDGET_TYPE_LIFT && GetZone()->HasWidgetTimer(this)) //this door is a lift and in use, wait until it gets to the + return; + if (GetZone()->CallSpawnScript(this, SPAWN_SCRIPT_USEDOOR, caller, "", is_open)) { + // handled in lua, nothing to do here! + } + else + { + bool wasOpen = IsOpen(); + if(wasOpen) //close + CloseDoor(); + else //open + OpenDoor(); + + bool isOpen = IsOpen(); + if(isOpen){ + if(GetOpenSound()) + GetZone()->PlaySoundFile(0, GetOpenSound(), widget_x, widget_y, widget_z); + } + else + if(GetCloseSound()) + GetZone()->PlaySoundFile(0, GetCloseSound(), widget_x, widget_y, widget_z); + } +} + +void Widget::HandleUse(Client* client, string command, int8 overrideWidgetType){ + vector destinations; + //The following check disables the use of doors and other widgets if the player does not meet the quest requirements + //If this is from a script ignore this check (client will be null) + + if (overrideWidgetType == 0xFF) + overrideWidgetType = widget_type; + + if (client) { + bool meets_quest_reqs = MeetsSpawnAccessRequirements(client->GetPlayer()); + if (!meets_quest_reqs && (GetQuestsRequiredOverride() & 2) == 0) + return; + else if (meets_quest_reqs && appearance.show_command_icon != 1) + return; + } + std::string cmdlower(command); + boost::algorithm::to_lower(cmdlower); + + if (client && GetTransporterID() > 0) + { + client->SetTemporaryTransportID(0); + GetZone()->GetTransporters(&destinations, client, GetTransporterID()); + } + bool skipHouseCommands = (cmdlower == "access" || cmdlower == "visit"); + if (!skipHouseCommands && destinations.size() && client) + client->ProcessTeleport(this, &destinations, GetTransporterID()); + else if (!skipHouseCommands && (overrideWidgetType == WIDGET_TYPE_DOOR || overrideWidgetType == WIDGET_TYPE_LIFT)){ + Widget* widget = this; + if (!action_spawn && action_spawn_id > 0){ + Spawn* spawn = GetZone()->GetSpawnByDatabaseID(action_spawn_id); + if (spawn && spawn->IsWidget()) + action_spawn = (Widget*)spawn; + } + if (!linked_spawn && linked_spawn_id > 0){ + Spawn* spawn = GetZone()->GetSpawnByDatabaseID(linked_spawn_id); + if (spawn && spawn->IsWidget()) + linked_spawn = (Widget*)spawn; + } + if (linked_spawn){ + widget = linked_spawn; + ProcessUse(client ? client->GetPlayer() : nullptr); //fire the first door, then fire the linked door below + } + else if (action_spawn) { + widget = action_spawn; + if (!widget->linked_spawn && widget->linked_spawn_id > 0) { + Spawn* spawn = GetZone()->GetSpawnByDatabaseID(widget->linked_spawn_id); + if (spawn && spawn->IsWidget()) + widget->linked_spawn = (Widget*)spawn; + } + + if (widget->linked_spawn) + widget->linked_spawn->ProcessUse(client ? client->GetPlayer() : nullptr); + } + widget->ProcessUse(client ? client->GetPlayer() : nullptr); + } + else if (client && cmdlower == "access" && GetZone()->GetInstanceID() && + (GetZone()->GetInstanceType() == PERSONAL_HOUSE_INSTANCE || GetZone()->GetInstanceType() == GUILD_HOUSE_INSTANCE)) { + // Used a door within a house + PlayerHouse* ph = world.GetPlayerHouseByInstanceID(GetZone()->GetInstanceID()); + if (ph) { + HouseZone* hz = world.GetHouseZone(ph->house_id); + if (hz) { + int32 id = client->GetPlayer()->GetIDWithPlayerSpawn(this); + ClientPacketFunctions::SendHouseVisitWindow(client, world.GetAllPlayerHousesByHouseID(hz->id)); + + ClientPacketFunctions::SendBaseHouseWindow(client, hz, ph, id); + } + } + } + else if (client && m_houseID > 0 && cmdlower == "access") { + // Used a door to enter a house + HouseZone* hz = nullptr; + PlayerHouse* ph = nullptr; + + int32 id = 0; + if(client->GetVersion() <= 561) { + id = client->GetPlayer()->GetIDWithPlayerSpawn(this); + ph = world.GetPlayerHouse(client, id, 0, &hz); + } + else { + hz = world.GetHouseZone(m_houseID); + if(hz) { + ph = world.GetPlayerHouseByHouseID(client->GetPlayer()->GetCharacterID(), hz->id); + } + id = client->GetPlayer()->GetID();// reconsider? + } + + if (ph && hz) { + // if we aren't in our own house we should get the full list of houses we can visit + if ( client->GetCurrentZone()->GetInstanceType() != Instance_Type::PERSONAL_HOUSE_INSTANCE && client->GetVersion() > 561 ) + ClientPacketFunctions::SendHouseVisitWindow(client, world.GetAllPlayerHousesByHouseID(hz->id)); + + ClientPacketFunctions::SendBaseHouseWindow(client, hz, ph, id); + client->GetCurrentZone()->SendHouseItems(client); + } + else { + if (hz) + ClientPacketFunctions::SendHousePurchase(client, hz, 0); + } + } + else if (client && m_houseID > 0 && cmdlower == "visit") { + HouseZone* hz = nullptr; + int32 id = client->GetPlayer()->GetIDWithPlayerSpawn(this); + PlayerHouse* ph = world.GetPlayerHouse(client, id, 0, &hz); + ClientPacketFunctions::SendHousingList(client); + if(hz != nullptr) { + ClientPacketFunctions::SendHouseVisitWindow(client, world.GetAllPlayerHousesByHouseID(hz->id)); + } + } + else if (client && command.length() > 0) { + EntityCommand* entity_command = FindEntityCommand(command); + if (entity_command) + client->GetCurrentZone()->ProcessEntityCommand(entity_command, client->GetPlayer(), client->GetPlayer()->GetTarget()); + } +} \ No newline at end of file diff --git a/source/WorldServer/Widget.h b/source/WorldServer/Widget.h new file mode 100644 index 0000000..c85b76b --- /dev/null +++ b/source/WorldServer/Widget.h @@ -0,0 +1,133 @@ +/* + 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 . +*/ +#ifndef __EQ2_WIDGET__ +#define __EQ2_WIDGET__ +#include "Spawn.h" +#include "client.h" +#include +#include + +using namespace std; +#define WIDGET_TYPE_GENERIC 0 +#define WIDGET_TYPE_DOOR 1 +#define WIDGET_TYPE_LIFT 2 +class Widget : public Spawn{ +public: + Widget(); + virtual ~Widget(); + bool IsWidget(){ return true; } + int32 GetWidgetID(); + void SetWidgetID(int32 val); + void SetWidgetX(float val); + float GetWidgetX(); + void SetWidgetY(float val); + float GetWidgetY(); + void SetWidgetZ(float val); + float GetWidgetZ(); + void SetIncludeLocation(bool val); + bool GetIncludeLocation(); + void SetIncludeHeading(bool val); + bool GetIncludeHeading(); + void SetWidgetIcon(int8 val); + Widget* Copy(); + EQ2Packet* serialize(Player* player, int16 version); + void HandleTimerUpdate(); + void OpenDoor(); + void CloseDoor(); + void HandleUse(Client* client, string command, int8 overrideWidgetType=0xFF); + float GetOpenHeading(); + void SetOpenHeading(float val); + float GetClosedHeading(); + void SetClosedHeading(float val); + float GetOpenY(); + void SetOpenY(float val); + float GetCloseY(); + void SetCloseY(float val); + float GetOpenX(){return open_x;} + float GetOpenZ(){return open_z;} + float GetCloseX(){return close_x;} + float GetCloseZ(){return close_z;} + void SetOpenX(float x){open_x = x;} + void SetOpenZ(float z){open_z = z;} + void SetCloseX(float x){close_x = x;} + void SetCloseZ(float z){close_z = z;} + int8 GetWidgetType(); + void SetWidgetType(int8 val); + bool IsOpen(); + int32 GetActionSpawnID(); + void SetActionSpawnID(int32 id); + int32 GetLinkedSpawnID(); + void SetLinkedSpawnID(int32 id); + const char* GetOpenSound(); + void SetOpenSound(const char* name); + const char* GetCloseSound(); + void SetCloseSound(const char* name); + void SetOpenDuration(int16 val); + int16 GetOpenDuration(); + void ProcessUse(Spawn* caller=nullptr); + void SetHouseID(int32 val) { m_houseID = val; } + int32 GetHouseID() { return m_houseID; } + + void SetMultiFloorLift(bool val) { multi_floor_lift = val; } + bool GetMultiFloorLift() { return multi_floor_lift; } + + static string GetWidgetTypeNameByTypeID(int8 type) + { + switch (type) + { + case WIDGET_TYPE_DOOR: + return string("Door"); + break; + case WIDGET_TYPE_LIFT: + return string("Lift"); + break; + } + + return string("Generic"); + } +private: + int8 widget_type; + bool include_location; + bool include_heading; + float widget_x; + float widget_y; + float widget_z; + int32 widget_id; + float open_heading; + float closed_heading; + float open_y; + float close_y; + Widget* action_spawn; + int32 action_spawn_id; + Widget* linked_spawn; + int32 linked_spawn_id; + bool is_open; + string open_sound; + string close_sound; + int16 open_duration; + int32 m_houseID; + float open_x; + float open_z; + float close_x; + float close_z; + bool multi_floor_lift; + std::mutex MWidgetMutex; +}; +#endif diff --git a/source/WorldServer/World.cpp b/source/WorldServer/World.cpp new file mode 100644 index 0000000..707638a --- /dev/null +++ b/source/WorldServer/World.cpp @@ -0,0 +1,2989 @@ +/* + 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 . +*/ +#include +#include "World.h" +#include "Items/Items.h" +#include "Items/Items_ToV.h" +#include "Items/Items_DoV.h" +#include "Spells.h" +#include "client.h" +#include "WorldDatabase.h" +#include "../common/debug.h" +#include "races.h" +#include "classes.h" +#include "VisualStates.h" +#include "Appearances.h" +#include "Skills.h" +#include "LoginServer.h" +#include "Quests.h" +#include "Factions.h" +#include "Guilds/Guild.h" +#include "Collections/Collections.h" +#include "Achievements/Achievements.h" +#include "Recipes/Recipe.h" +#include "Rules/Rules.h" +#include "../common/Log.h" +#include "Traits/Traits.h" +#include "Chat/Chat.h" +#include "Tradeskills/Tradeskills.h" +#include "AltAdvancement/AltAdvancement.h" +#include "LuaInterface.h" +#include "HeroicOp/HeroicOp.h" +#include "RaceTypes/RaceTypes.h" +#include "LuaInterface.h" +#include "../common/version.h" + +#include "Player.h" + +#include +#include +#include + +MasterQuestList master_quest_list; +MasterItemList master_item_list; +MasterSpellList master_spell_list; +MasterTraitList master_trait_list; +MasterHeroicOPList master_ho_list; +MasterSkillList master_skill_list; +MasterFactionList master_faction_list; +MasterCollectionList master_collection_list; +MasterAchievementList master_achievement_list; +MasterRecipeList master_recipe_list; +MasterRecipeBookList master_recipebook_list; +MasterTradeskillEventsList master_tradeskillevent_list; +MasterAAList master_aa_list; +MasterRaceTypeList race_types_list; +MasterAANodeList master_tree_nodes; +ClientList client_list; +ZoneList zone_list; +ZoneAuth zone_auth; +int32 Spawn::next_id = 1; +int32 WorldDatabase::next_id = 0; +Commands commands; +Variables variables; +VisualStates visual_states; +Appearances master_appearance_list; +Classes classes; +Races races; +mapEQOpcodeManager; +map EQOpcodeVersions; +WorldDatabase database; +GuildList guild_list; +Chat chat; +Player player; + +extern ConfigReader configReader; +extern LoginServer loginserver; +extern World world; +extern ZoneList zone_list; +extern RuleManager rule_manager; +extern LuaInterface* lua_interface; +extern sint32 numclients; + +World::World() : save_time_timer(300000), time_tick_timer(3000), vitality_timer(3600000), player_stats_timer(60000), server_stats_timer(60000), /*remove_grouped_player(30000),*/ guilds_timer(60000), lotto_players_timer(500), watchdog_timer(10000) { + save_time_timer.Start(); + time_tick_timer.Start(); + vitality_timer.Start(); + player_stats_timer.Start(); + server_stats_timer.Start(); + //remove_grouped_player.Start(); + guilds_timer.Start(); + lotto_players_timer.Start(); + watchdog_timer.Start(); + xp_rate = -1; + ts_xp_rate = -1; + vitality_frequency = 0xFFFFFFFF; + vitality_amount = -1; + last_checked_time = 0; + items_loaded = false; + spells_loaded = false; + achievments_loaded = false; + merchant_inventory_items.clear(); + MHouseZones.SetName("World::m_houseZones"); + MPlayerHouses.SetName("World::m_playerHouses"); + MWorldMaps.SetName("World::MWorldMaps"); + MWorldRegionMaps.SetName("World::MWorldRegionMaps"); + world_webserver = nullptr; + world_loaded = false; + world_uptime = getCurrentTimestamp(); +} + +World::~World(){ + // At this point the log system is already shut down so no calls to LogWrite are allowed in any of the functions called by this deconstructor + DeleteSpawns(); + if(database.GetStatus() == database.Connected) + WriteServerStatistics(); + RemoveServerStatistics(); + DeleteMerchantsInfo(); + MutexMap::iterator itr = lotto_players.begin(); + while (itr.Next()) + safe_delete(itr->second); + map::iterator itr2; + for (itr2 = m_houseZones.begin(); itr2 != m_houseZones.end(); itr2++) + safe_delete(itr2->second); + m_houseZones.clear(); + + tov_itemstat_conversion.clear(); + + PurgeStartingLists(); + PurgeVoiceOvers(); + + map::iterator itr3; + for (itr3 = region_maps.begin(); itr3 != region_maps.end(); itr3++) + safe_delete(itr3->second); + + map::iterator itr4; + for (itr4 = maps.begin(); itr4 != maps.end(); itr4++) + safe_delete(itr4->second); + + PurgeNPCSpells(); + + safe_delete(world_webserver); +} + +void World::init(std::string web_ipaddr, int16 web_port, std::string cert_file, std::string key_file, std::string key_password, std::string hardcode_user, std::string hardcode_password){ + WorldDatabase::next_id = database.GetMaxHotBarID(); + + LogWrite(COMMAND__DEBUG, 1, "Command", "-Loading Commands..."); + database.LoadCommandList(); + LogWrite(COMMAND__DEBUG, 1, "Command", "-Load Commands complete!"); + + LogWrite(FACTION__DEBUG, 1, "Faction", "-Loading Factions..."); + database.LoadFactionList(); + LogWrite(FACTION__DEBUG, 1, "Faction", "-Load Factions complete!..."); + + LogWrite(SKILL__DEBUG, 1, "Skill", "-Loading Skills..."); + database.LoadSkills(); + LogWrite(SKILL__DEBUG, 1, "Skill", "-Load Skills complete..."); + + LogWrite(WORLD__DEBUG, 1, "World", "-Loading `variables`..."); + database.LoadGlobalVariables(); + LogWrite(WORLD__DEBUG, 1, "World", "-Load `variables` complete!"); + + LogWrite(WORLD__DEBUG, 1, "World", "-Loading `appearances`..."); + database.LoadAppearanceMasterList(); + LogWrite(WORLD__DEBUG, 1, "World", "-Load `appearances` complete!"); + + LogWrite(WORLD__DEBUG, 1, "World", "-Loading `visual_states`..."); + database.LoadVisualStates(); + LogWrite(WORLD__DEBUG, 1, "World", "-Load `visual states` complete!"); + + LoadStartingLists(); + LoadVoiceOvers(); + + LogWrite(WORLD__DEBUG, 1, "World", "-Loading EXP Curve From DB..."); + player.InitXPTable(); + LogWrite(WORLD__DEBUG, 1, "World", "-Loading EXP Curve From DB Complete!"); + + LogWrite(WORLD__DEBUG, 1, "World", "-Setting system parameters..."); + Variable* var = variables.FindVariable("gametime"); + const char* time_string = 0; + char default_time[] = "0/0/3800 8:30"; + + if(var) + time_string = var->GetValue(); + + if(!time_string) + time_string = default_time; + int year, month, day, hour, minute; + sscanf (time_string, "%d/%d/%d %d:%d", &month, &day, &year, &hour, &minute); + LogWrite(WORLD__DEBUG, 3, "World", "--Setting World Time to %s...", time_string); + world_time.month = month; + world_time.day = day; + world_time.year = year; + world_time.hour = hour; + world_time.minute = minute; + + LogWrite(WORLD__DEBUG, 3, "World", "--Loading Vitality Information..."); + LoadVitalityInformation(); + + LogWrite(WORLD__DEBUG, 3, "World", "--Loading Server Statistics..."); + database.LoadServerStatistics(); + + LogWrite(WORLD__DEBUG, 3, "World", "--Setting Server Start Time..."); + UpdateServerStatistic(STAT_SERVER_START_TIME, Timer::GetUnixTimeStamp(), true); + + LogWrite(WORLD__DEBUG, 3, "World", "--Resetting Accepted Connections to 0..."); + UpdateServerStatistic(STAT_SERVER_ACCEPTED_CONNECTION, 0, true); + + LogWrite(WORLD__DEBUG, 3, "World", "--Resetting Active Zones to 0..."); + UpdateServerStatistic(STAT_SERVER_NUM_ACTIVE_ZONES, 0, true); + + // Clear all online players at server startup + LogWrite(WORLD__DEBUG, 3, "World", "--Resetting characters online flags..."); + database.ToggleCharacterOnline(); + LogWrite(WORLD__DEBUG, 1, "World", "-Set system parameters complete!"); + + LogWrite(RULESYS__DEBUG, 1, "Rules", "-Loading Rule Sets..."); + database.LoadRuleSets(); + LogWrite(RULESYS__DEBUG, 1, "Rules", "-Load Rule Sets complete!"); + + LoadItemBlueStats(); + //PopulateTOVStatMap(); + group_buff_updates.Start(rule_manager.GetGlobalRule(R_Client, GroupSpellsTimer)->GetInt32()); + + if(web_ipaddr.size() > 0 && web_port > 0) { + try { + world_webserver = new WebServer(web_ipaddr, web_port, cert_file, key_file, key_password, hardcode_user, hardcode_password); + + world_webserver->register_route("/status", World::Web_worldhandle_status); + world_webserver->register_route("/clients", World::Web_worldhandle_clients); + world_webserver->run(); + LogWrite(INIT__INFO, 0, "Init", "World Web Server is listening on %s:%u..", web_ipaddr.c_str(), web_port); + } + catch (const std::exception& e) { + LogWrite(INIT__ERROR, 0, "Init", "World Web Server failed to listen on %s:%u due to reason %s", web_ipaddr.c_str(), web_port, e.what()); + } + } +} + + + +PacketStruct* World::GetWorldTime(int16 version){ + MWorldTime.readlock(__FUNCTION__, __LINE__); + PacketStruct* packet = configReader.getStruct("WS_GameWorldTime", version); + if(packet){ + packet->setDataByName("year", world_time.year); + packet->setDataByName("month", world_time.month); + packet->setDataByName("day", world_time.day); + packet->setDataByName("hour", world_time.hour); + packet->setDataByName("minute", world_time.minute); + packet->setDataByName("unknown", 250); + packet->setDataByName("unix_time", Timer::GetUnixTimeStamp()); + packet->setDataByName("unknown2", 1); + } + MWorldTime.releasereadlock(__FUNCTION__, __LINE__); + return packet; +} + +float World::GetXPRate(){ + xp_rate = rule_manager.GetGlobalRule(R_Player, XPMultiplier)->GetFloat(); + LogWrite(WORLD__DEBUG, 0, "World", "Setting Global XP Rate to: %.2f", xp_rate); + return xp_rate; +} + +float World::GetTSXPRate(){ + ts_xp_rate = rule_manager.GetGlobalRule(R_Player, TSXPMultiplier)->GetFloat(); + LogWrite(WORLD__DEBUG, 0, "World", "Setting Global Tradeskill XP Rate to: %.2f", ts_xp_rate); + return ts_xp_rate; +} + +void World::Process(){ + if(last_checked_time > Timer::GetCurrentTime2()) + return; + last_checked_time = Timer::GetCurrentTime2() + 1000; + + if(save_time_timer.Check()) + { + MWorldTime.readlock(__FUNCTION__, __LINE__); + database.SaveWorldTime(&world_time); + MWorldTime.releasereadlock(__FUNCTION__, __LINE__); + } + + if(time_tick_timer.Check()) + { + MWorldTime.writelock(__FUNCTION__, __LINE__); + WorldTimeTick(); + MWorldTime.releasewritelock(__FUNCTION__, __LINE__); + } + + if(lua_interface) + lua_interface->Process(); + + if(vitality_timer.Check()) + UpdateVitality(); + if (player_stats_timer.Check()) + WritePlayerStatistics(); + if (server_stats_timer.Check()) + WriteServerStatistics(); + /*if(remove_grouped_player.Check()) + CheckRemoveGroupedPlayer();*/ + if (group_buff_updates.Check()) + GetGroupManager()->UpdateGroupBuffs(); + if (guilds_timer.Check()) + SaveGuilds(); + if (lotto_players_timer.Check()) + CheckLottoPlayers(); + if(watchdog_timer.Check()) + zone_list.WatchdogHeartbeat(); +} + +vector* World::GetClientVariables(){ + return variables.GetVariables("cl_"); +} + +void World::LoadVitalityInformation() +{ + int32 timestamp = Timer::GetUnixTimeStamp(); + int32 diff = 0; + + // fetch vitalitytimer value from `variables` table + Variable* timer_var = variables.FindVariable("vitalitytimer"); + + if(timer_var) + { + try + { + diff = timestamp - atoul(timer_var->GetValue()); + diff *= 1000; //convert seconds to milliseconds + } + catch(...) + { + LogWrite(WORLD__ERROR, 0, "World", "Error parsing vitalitytimer, value: %s", timer_var->GetValue()); + } + } + + // Now using Rules System to set vitality parameters + vitality_amount = rule_manager.GetGlobalRule(R_Player, VitalityAmount)->GetFloat(); + vitality_frequency = rule_manager.GetGlobalRule(R_Player, VitalityFrequency)->GetInt32(); + + vitality_frequency *= 1000; //convert seconds to milliseconds + + if(diff >= vitality_frequency) + UpdateVitality(); //update now + else + vitality_timer.SetTimer(vitality_frequency - diff); +} + +void World::UpdateVitality() +{ + // push new vitalitytimer to `variables` table + database.UpdateVitality(Timer::GetUnixTimeStamp(), vitality_amount); + + if(vitality_timer.GetDuration() != vitality_frequency) + vitality_timer.SetTimer(vitality_frequency); + + zone_list.UpdateVitality(vitality_amount); +} + + +void ZoneList::UpdateVitality(float amount) +{ + list::iterator zone_iter; + ZoneServer* tmp = 0; + MZoneList.readlock(__FUNCTION__, __LINE__); + + for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++) + { + tmp = *zone_iter; + if(tmp) + tmp->UpdateVitality(amount); + } + + MZoneList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneList::SendTimeUpdate() +{ + list::iterator zone_iter; + ZoneServer* tmp = 0; + MZoneList.readlock(__FUNCTION__, __LINE__); + + for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++) + { + tmp = *zone_iter; + if(tmp && !tmp->isZoneShuttingDown()) + tmp->WorldTimeUpdateTrigger(); + } + + MZoneList.releasereadlock(__FUNCTION__, __LINE__); +} + +// should already be ran inside MWorldTime +void World::WorldTimeTick(){ + world_time.minute++; + //I know it looks complicated, but the nested ifs are to avoid checking all of them every 3 seconds + if(world_time.minute >= 60){ // >= in case of bad time from db + world_time.minute = 0; + world_time.hour++; + if(world_time.hour >= 24){ + world_time.hour = 0; + world_time.day++; + if(world_time.day>=30){ + world_time.day = 0; + world_time.month++; + if(world_time.month >= 12){ + world_time.month = 0; + world_time.year++; + } + } + } + } +} + + +ZoneList::ZoneList() { + MZoneList.SetName("ZoneList::MZoneList"); +} + +ZoneList::~ZoneList() { + list::iterator zone_iter; + ZoneServer* zs = 0; + for(zone_iter=zlist.begin(); zone_iter!=zlist.end();){ + zs = *zone_iter; + zone_iter = zlist.erase(zone_iter); + safe_delete(zs); + } +} + +void ZoneList::CheckFriendList(Client* client) { + LogWrite(WORLD__DEBUG, 0, "World", "Sending FriendList..."); + MClientList.lock(); + map::iterator itr; + for(itr = client_map.begin(); itr != client_map.end(); itr++){ + if(itr->second != client && itr->second){ + if(itr->second->GetPlayer()->IsFriend(client->GetPlayer()->GetName())){ + itr->second->SendFriendList(); + itr->second->Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "Friend: %s has logged in.", client->GetPlayer()->GetName()); + } + } + } + MClientList.unlock(); +} + +void ZoneList::CheckFriendZoned(Client* client){ + MClientList.lock(); + map::iterator itr; + for(itr = client_map.begin(); itr != client_map.end(); itr++){ + if(itr->second != client && itr->second){ + if(itr->second->GetPlayer()->IsFriend(client->GetPlayer()->GetName())){ + itr->second->SendFriendList(); + } + } + } + MClientList.unlock(); +} + +bool ZoneList::HandleGlobalChatMessage(Client* from, char* to, int16 channel, const char* message, const char* channel_name, int32 current_language_id){ + if (!from) { + LogWrite(WORLD__ERROR, 0, "World", "HandleGlobalChatMessage() called with an invalid client"); + return false; + } + if(channel == CHANNEL_PRIVATE_TELL){ + Client* find_client = zone_list.GetClientByCharName(to); + if(!find_client || find_client->GetPlayer()->IsIgnored(from->GetPlayer()->GetName())) + return false; + else if(find_client == from) + { + from->Message(CHANNEL_COLOR_RED,"You must be very lonely...(ERROR: Cannot send tell to self)"); + } + else + { + const char* whoto = find_client->GetPlayer()->GetName(); + find_client->HandleTellMessage(from, message, whoto, from->GetPlayer()->GetCurrentLanguage()); + from->HandleTellMessage(from, message, whoto, from->GetPlayer()->GetCurrentLanguage()); + if (find_client->GetPlayer()->get_character_flag(CF_AFK)) { + find_client->HandleTellMessage(find_client, find_client->GetPlayer()->GetAwayMessage().c_str(),whoto, from->GetPlayer()->GetCurrentLanguage()); + from->HandleTellMessage(find_client, find_client->GetPlayer()->GetAwayMessage().c_str(),whoto, from->GetPlayer()->GetCurrentLanguage()); + } + } + } + + else if(channel == CHANNEL_GROUP_SAY) { + GroupMemberInfo* gmi = from->GetPlayer()->GetGroupMemberInfo(); + if(gmi) + world.GetGroupManager()->GroupMessage(gmi->group_id, message); + } + else{ + list::iterator zone_iter; + ZoneServer* zs = 0; + MZoneList.readlock(__FUNCTION__, __LINE__); + for(zone_iter=zlist.begin(); zone_iter!=zlist.end();zone_iter++){ + zs = *zone_iter; + if(zs) + zs->HandleChatMessage(from->GetPlayer(), to, channel, message, 0, channel_name, true, current_language_id); + } + MZoneList.releasereadlock(__FUNCTION__, __LINE__); + } + return true; +} + +void ZoneList::LoadSpellProcess(){ + list::iterator zone_iter; + ZoneServer* zone = 0; + MZoneList.readlock(__FUNCTION__, __LINE__); + for (zone_iter=zlist.begin(); zone_iter!=zlist.end();zone_iter++){ + zone = *zone_iter; + if (zone) + zone->LoadSpellProcess(); + } + MZoneList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneList::DeleteSpellProcess(){ + list::iterator zone_iter; + ZoneServer* zone = 0; + MZoneList.readlock(__FUNCTION__, __LINE__); + for (zone_iter=zlist.begin(); zone_iter!=zlist.end();zone_iter++){ + zone = *zone_iter; + if (zone) + zone->DeleteSpellProcess(); + } + MZoneList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneList::HandleGlobalBroadcast(const char* message) { + list::iterator zone_iter; + ZoneServer* zone = 0; + MZoneList.readlock(__FUNCTION__, __LINE__); + for (zone_iter=zlist.begin(); zone_iter!=zlist.end();zone_iter++){ + zone = *zone_iter; + if (zone) + zone->HandleBroadcast(message); + } + MZoneList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneList::HandleGlobalAnnouncement(const char* message) { + list::iterator zone_iter; + ZoneServer* zone = 0; + MZoneList.readlock(__FUNCTION__, __LINE__); + for (zone_iter=zlist.begin(); zone_iter!=zlist.end();zone_iter++){ + zone = *zone_iter; + if (zone) + zone->HandleAnnouncement(message); + } + MZoneList.releasereadlock(__FUNCTION__, __LINE__); +} + +int32 ZoneList::Count(){ + return zlist.size(); +} + +void ZoneList::Add(ZoneServer* zone) { + MZoneList.writelock(__FUNCTION__, __LINE__); + zlist.push_back(zone); + MZoneList.releasewritelock(__FUNCTION__, __LINE__); +} +void ZoneList::Remove(ZoneServer* zone) { + const char* zoneName = zone->GetZoneName(); + MZoneList.writelock(__FUNCTION__, __LINE__); + zlist.remove(zone); + MZoneList.releasewritelock(__FUNCTION__, __LINE__); + + ZoneServer* alternativeZone = Get(zoneName, false, false); + if(!alternativeZone && !rule_manager.GetGlobalRule(R_World, MemoryCacheZoneMaps)->GetBool()) { + world.RemoveMaps(std::string(zoneName)); + } +} +ZoneServer* ZoneList::Get(const char* zone_name, bool loadZone, bool skip_existing_zones, bool increment_zone) { + list::iterator zone_iter; + ZoneServer* tmp = 0; + ZoneServer* ret = 0; + + if(!skip_existing_zones) { + MZoneList.readlock(__FUNCTION__, __LINE__); + for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++){ + tmp = *zone_iter; + if (!tmp->isZoneShuttingDown() && !tmp->IsInstanceZone() && strlen(zone_name) == strlen(tmp->GetZoneName()) && + strncasecmp(tmp->GetZoneName(), zone_name, strlen(zone_name))==0){ + if(tmp->NumPlayers() < 30 || tmp->IsCityZone()) { + ret = tmp; + if(increment_zone) { + ret->IncrementIncomingClients(); + } + break; + } + } + } + + MZoneList.releasereadlock(__FUNCTION__, __LINE__); + } + + if(!ret ) + { + if ( loadZone ) + { + ret = new ZoneServer(zone_name); + database.LoadZoneInfo(ret); + ret->Init(); + } + } + return ret; +} + +ZoneServer* ZoneList::Get(int32 id, bool loadZone, bool skip_existing_zones, bool increment_zone) { + list::iterator zone_iter; + ZoneServer* tmp = 0; + ZoneServer* ret = 0; + if(!skip_existing_zones) { + MZoneList.readlock(__FUNCTION__, __LINE__); + for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++){ + tmp = *zone_iter; + if(!tmp->isZoneShuttingDown() && !tmp->IsInstanceZone() && tmp->GetZoneID() == id){ + if(tmp->NumPlayers() < 30 || tmp->IsCityZone()) { + ret = tmp; + if(increment_zone) { + ret->IncrementIncomingClients(); + } + break; + } + } + } + MZoneList.releasereadlock(__FUNCTION__, __LINE__); + } + + if(ret) { + tmp = ret; + } + else if (loadZone) { + string zonename = database.GetZoneName(id); + if(zonename.length() >0){ + tmp = new ZoneServer(zonename.c_str()); + database.LoadZoneInfo(tmp); + tmp->Init(); + } + } + return tmp; +} + + +void ZoneList::SendZoneList(Client* client) { + list::iterator zone_iter; + ZoneServer* tmp = 0; + MZoneList.readlock(__FUNCTION__, __LINE__); + int zonesListed = 0; + for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++){ + tmp = *zone_iter; + if ( zonesListed > 20 ) + { + client->Message(CHANNEL_COLOR_YELLOW,"Reached max zone list of 20."); + break; + } + zonesListed++; + client->Message(CHANNEL_COLOR_YELLOW,"Zone(ID): %s(%i), Instance ID: %i, Description: %s.",tmp->GetZoneName(),tmp->GetZoneID(), + tmp->GetInstanceID(),tmp->GetZoneDescription()); + } + MZoneList.releasereadlock(__FUNCTION__, __LINE__); +} + +ZoneServer* ZoneList::GetByInstanceID(int32 id, int32 zone_id, bool skip_existing_zones, bool increment_zone) { + list::iterator zone_iter; + ZoneServer* tmp = 0; + ZoneServer* ret = 0; + if(!skip_existing_zones) { + MZoneList.readlock(__FUNCTION__, __LINE__); + if ( id > 0 ) + { + for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++){ + tmp = *zone_iter; + if(!tmp->isZoneShuttingDown() && tmp->GetInstanceID() == id){ + ret = tmp; + if(increment_zone) { + ret->IncrementIncomingClients(); + } + break; + } + } + } + MZoneList.releasereadlock(__FUNCTION__, __LINE__); + } + + if(ret) { + tmp = ret; + } + else if ( zone_id > 0 ){ + string zonename = database.GetZoneName(zone_id); + if(zonename.length() > 0){ + tmp = new ZoneServer(zonename.c_str()); + + // the player is trying to preload an already existing instance but it isn't loaded + if ( id > 0 ) + tmp->SetupInstance(id); + + database.LoadZoneInfo(tmp); + tmp->Init(); + } + } + return tmp; +} + +ZoneServer* ZoneList::GetByLowestPopulation(int32 zone_id) { + ZoneServer* ret = 0; + ZoneServer* zone = 0; + int32 clients = 0; + list::iterator itr; + MZoneList.readlock(__FUNCTION__, __LINE__); + if (zone_id) { + for (itr = zlist.begin(); itr != zlist.end(); itr++) { + zone = *itr; + if (zone) { + // check the zone id's + if (zone->GetZoneID() == zone_id) { + // check this zones client count + if (clients == 0 || zone->GetClientCount() < clients) { + ret = zone; + clients = zone->GetClientCount(); + } + } + } + } + } + MZoneList.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} +int32 ZoneList::GetZonesPlayersCount() { + int32 ret = 0; + ZoneServer* zone = nullptr; + list::iterator itr; + MZoneList.readlock(__FUNCTION__, __LINE__); + for (itr = zlist.begin(); itr != zlist.end(); itr++) { + zone = *itr; + if (zone) { + ret += zone->GetClientCount(); + } + } + MZoneList.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +bool ZoneList::ClientConnected(int32 account_id){ + bool ret = false; + map::iterator itr; + MClientList.lock(); + for(itr=client_map.begin(); itr != client_map.end(); itr++){ + if(itr->second && itr->second->GetAccountID() == account_id && itr->second->getConnection() && itr->second->getConnection()->GetState() != CLOSING && itr->second->getConnection()->GetState() != CLOSED && (itr->second->GetPlayer()->GetActivityStatus() & ACTIVITY_STATUS_LINKDEAD) == 0){ + ret = true; + break; + } + else if(!itr->second){ + client_map.erase(itr); + if(client_map.size() > 0){ + itr=client_map.begin(); + if(itr == client_map.end()){ + if(itr->second && itr->second->GetAccountID() == account_id && (itr->second->GetPlayer()->GetActivityStatus() & ACTIVITY_STATUS_LINKDEAD) == 0) + ret = true; + break; + } + } + else + break; + } + } + MClientList.unlock(); + return ret; +} + +void ZoneList::RemoveClientZoneReference(ZoneServer* zone){ + map::iterator itr; + MClientList.lock(); + for(itr=client_map.begin(); itr != client_map.end(); itr++){ + if(itr->second) { + if(itr->second->GetCurrentZone() == zone) { + itr->second->SetCurrentZone(nullptr); + } + if(itr->second->GetZoningDestination() == zone) { + itr->second->SetZoningDestination(nullptr); + } + } + } + MClientList.unlock(); + + list::iterator zone_iter; + ZoneServer* tmp = 0; + MZoneList.readlock(__FUNCTION__, __LINE__); + for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++){ + tmp = *zone_iter; + if(tmp) + tmp->RemoveClientsFromZone(zone); + } + MZoneList.releasereadlock(__FUNCTION__, __LINE__); +} +void ZoneList::ReloadClientQuests(){ + list::iterator zone_iter; + ZoneServer* tmp = 0; + MZoneList.readlock(__FUNCTION__, __LINE__); + for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++){ + tmp = *zone_iter; + if(tmp) + tmp->ReloadClientQuests(); + } + MZoneList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneList::ProcessWhoQuery(vector* queries, ZoneServer* zone, vector* players, bool isGM){ + Entity* player = 0; + bool add_player = true; + bool found_match = false; + int8 lower = 0; + int8 upper = 0; + vector tmpPlayers; + vector::iterator spawn_iter; + if(!zone->isZoneShuttingDown()){ + tmpPlayers = zone->GetPlayers(); + for(spawn_iter = tmpPlayers.begin(); spawn_iter!=tmpPlayers.end(); spawn_iter++){ + player = *spawn_iter; + add_player = true; + Client* find_client = zone_list.GetClientByCharName(player->GetName()); + if (find_client == NULL) continue; + int flags = find_client->GetPlayer()->GetInfoStruct()->get_flags(); + int flags2 = find_client->GetPlayer()->GetInfoStruct()->get_flags2(); + for(int32 i=0;add_player && queries && isize();i++){ + found_match = false; + if(queries->at(i) == "ALL") + continue; + if(queries->at(i).length() > 3 && classes.GetClassID(queries->at(i).c_str()) > 0){ + if(player->GetAdventureClass() != classes.GetClassID(queries->at(i).c_str())) + add_player = false; + found_match = true; + } + else if(queries->at(i).length() > 2 && races.GetRaceID(queries->at(i).c_str()) > 0){ + if(player->GetRace() != races.GetRaceID(queries->at(i).c_str())) + add_player = false; + found_match = true; + } + if(!found_match && queries->at(i) == "GOOD"){ + if(player->GetDeity() != 1) + add_player = false; + found_match = true; + } + else if(!found_match && queries->at(i) == "EVIL"){ + if(player->GetDeity() == 1) + add_player = false; + found_match = true; + } + if((queries->at(i) == "GUIDE") && (find_client->GetAdminStatus() > 0) && ((find_client->GetAdminStatus() >> 4) < 5)) + found_match = true; + else if((queries->at(i) == "GM") && ((find_client->GetAdminStatus() >> 4) > 4)) + found_match = true; + else if((queries->at(i) == "LFG") && (flags & (1 << CF_LFG))) + found_match = true; + else if((queries->at(i) == "LFW") && (flags & (1 << CF_LFW))) + found_match = true; + else if((queries->at(i) == "ROLEPLAYING") && (flags & (1 << CF_ROLEPLAYING))) + found_match = true; + else if(strspn(queries->at(i).c_str(),"0123456789") == queries->at(i).length()){ + try{ + if(lower == 0) + lower = atoi(queries->at(i).c_str()); + else + upper = atoi(queries->at(i).c_str()); + } + catch(...){} + found_match = true; + } + if(!found_match){ + string name = string(player->GetName()); + name = ToUpper(name); + if(name.find(queries->at(i)) == name.npos) + add_player = false; + } + } + if(lower > 0 && upper > 0){ + if(player->GetLevel() < lower || player->GetLevel() > upper) + add_player = false; + } + else if(lower > 0){ + if(player->GetLevel() != lower) + add_player = false; + } + if((flags2 & (1 << (CF_GM_HIDDEN - 32))) && !isGM) { + add_player = false; + found_match = true; + } + if(add_player) + players->push_back(player); + } + } +} + +void ZoneList::ProcessWhoQuery(const char* query, Client* client){ + list::iterator zone_iter; + vector players; + vector::iterator spawn_iter; + Entity* player = 0; + //for now display all clients + bool all = false; + vector* queries = 0; + bool isGM = ((client->GetAdminStatus() >> 4) > 4); + if(query){ + string query_string = string(query); + query_string = ToUpper(query_string); + queries = SplitString(query_string, ' '); + } + if(queries && queries->size() > 0 && queries->at(0) == "ALL") + all = true; + if(all){ + MZoneList.readlock(__FUNCTION__, __LINE__); + for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++){ + ZoneServer* tmp = *zone_iter; + ProcessWhoQuery(queries, tmp, &players, isGM); + } + MZoneList.releasereadlock(__FUNCTION__, __LINE__); + } + else{ + ProcessWhoQuery(queries, client->GetCurrentZone(), &players, isGM); + } + + PacketStruct* packet = configReader.getStruct("WS_WhoQueryReply", client->GetVersion()); + if(packet){ + packet->setDataByName("account_id", client->GetAccountID()); + packet->setDataByName("unknown", 0xFFFFFFFF); + int8 num_characters = players.size(); + int8 max_who_results = 10; + int8 max_who_results_status_override = 100; + + Variable* var = variables.FindVariable("max_who_results_status_override"); + if ( var ){ + max_who_results_status_override = atoi(var->GetValue()); + } + + // AdnaeDMorte + if ( client->GetAdminStatus() >= max_who_results_status_override ){ + client->Message(CHANNEL_COLOR_RED, "** ADMIN-MODE ** "); + } + + Variable* var1 = variables.FindVariable("max_who_results"); + if ( var1 ){ + max_who_results = atoi(var1->GetValue()); + } + + if(num_characters > max_who_results && client->GetAdminStatus() < max_who_results_status_override){ + num_characters = max_who_results; + packet->setDataByName("response", 3); //response 1 = error message, 3 == capped + } + else + packet->setDataByName("response", 2); + packet->setArrayLengthByName("num_characters", num_characters); + packet->setDataByName("unknown10", 1); + int i=0; + for(spawn_iter = players.begin(); spawn_iter!=players.end(); spawn_iter++, i++){ + if(i == num_characters) + break; + player = *spawn_iter; + Client* find_client = zone_list.GetClientByCharName(player->GetName()); + int flags = find_client->GetPlayer()->GetInfoStruct()->get_flags(); + int flags2 = find_client->GetPlayer()->GetInfoStruct()->get_flags2(); + packet->setArrayDataByName("char_name", player->GetName(), i); + packet->setArrayDataByName("level", player->GetLevel(), i); + packet->setArrayDataByName("admin_level", ((flags2 & (1 << (CF_HIDE_STATUS - 32))) && !isGM)?0:(find_client->GetAdminStatus() >> 4), i); + packet->setArrayDataByName("class", player->GetAdventureClass(), i); + packet->setArrayDataByName("unknown4", 0xFF, i); //probably tradeskill class + packet->setArrayDataByName("flags", (((flags >> CF_ANONYMOUS) & 1) << 0 ) | + (((flags >> CF_LFG) & 1) << 1 ) | + (((flags >> CF_ANONYMOUS) & 1) << 2 ) | + /*(((flags >> CF_HIDDEN) & 1) << 3 ) |*/ + (((flags >> CF_ROLEPLAYING) & 1) << 4 ) | + (((flags >> CF_AFK) & 1) << 5 ) | + (((flags >> CF_LFW) & 1) << 6 ) /*| + (((flags >> CF_NOTA) & 1) << 7 )*/, i); + packet->setArrayDataByName("race", player->GetRace(), i); + if(player->GetZone() && player->GetZone()->GetZoneDescription()) + packet->setArrayDataByName("zone", player->GetZone()->GetZoneDescription(), i); + if(player->appearance.sub_title) { + int32 sub_title_length = strlen(player->appearance.sub_title); + char tmp_title[255]; + int32 index = 0; + int32 index_tmp = 0; + while (index < sub_title_length) { + if (player->appearance.sub_title[index] != '<' && player->appearance.sub_title[index] != '>') { + memcpy(tmp_title + index_tmp, player->appearance.sub_title + index, 1); + index_tmp++; + } + index++; + } + tmp_title[index_tmp] = '\0'; + packet->setArrayDataByName("guild", tmp_title, i); + } + } + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } +} + +bool ZoneList::DepopFinished(){ + list::iterator zone_iter; + MZoneList.readlock(__FUNCTION__, __LINE__); + bool finished_depop = true; + for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++){ + if(!(*zone_iter)->FinishedDepop()) + finished_depop = false; + } + MZoneList.releasereadlock(__FUNCTION__, __LINE__); + return finished_depop; +} + +void ZoneList::Depop(){ + list::iterator zone_iter; + MZoneList.readlock(__FUNCTION__, __LINE__); + for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++){ + (*zone_iter)->Depop(); + } + MZoneList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneList::Repop(){ + list::iterator zone_iter; + MZoneList.readlock(__FUNCTION__, __LINE__); + for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++){ + (*zone_iter)->Depop(false, true); + } + MZoneList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneList::ReloadSpawns() { + MZoneList.readlock(__FUNCTION__, __LINE__); + + list::iterator itr; + for (itr = zlist.begin(); itr != zlist.end(); itr++) + (*itr)->ReloadSpawns(); + + MZoneList.releasereadlock(__FUNCTION__, __LINE__); +} + +bool World::ReportBug(string data, char* player_name, int32 account_id, const char* spawn_name, int32 spawn_id, int32 zone_id){ + //loginserver + vector list; + int32 offset = 0; + int32 old_offset = 0; + while((offset = data.find(7, old_offset+1)) < 0xFFFFFFFF){ + if(old_offset > 0) + list.push_back(data.substr(old_offset+1, offset-old_offset-1)); + else + list.push_back(data.substr(old_offset, offset)); + old_offset = offset; + } + if(list.size() > 0 && list.size() < 7){ + string output = "Invalid bug list:\n"; + for(int32 i=0;ipBuffer; + + if (list.size() < 7) { + strncpy(report->category, "AutoBug", 7); + strncpy(report->subcategory, "AutoGenerate", 12); + strncpy(report->causes_crash, "N", 1); + strncpy(report->reproducible, "Y", 1); + strncpy(report->summary, data.c_str(), data.length() > 127 ? 127 : data.length()); + strncpy(report->description, data.c_str(), data.length() > 1999 ? 1999 : data.length()); + strncpy(report->version, "CUR", 3); + } + else + { + strncpy(report->category, list[0].c_str(), list[0].length() > 63 ? 63 : list[0].length()); + strncpy(report->subcategory, list[1].c_str(), list[1].length() > 63 ? 63 : list[1].length()); + strncpy(report->causes_crash, list[2].c_str(), list[2].length() > 63 ? 63 : list[2].length()); + strncpy(report->reproducible, list[3].c_str(), list[3].length() > 63 ? 63 : list[3].length()); + strncpy(report->summary, list[4].c_str(), list[4].length() > 127 ? 127 : list[4].length()); + strncpy(report->description, list[5].c_str(), list[5].length() > 1999 ? 1999 : list[5].length()); + strncpy(report->version, list[6].c_str(), list[6].length() > 31 ? 31 : list[6].length()); + } + + strncpy(report->player, player_name, strlen(player_name) > 63 ? 63 : strlen(player_name)); + strncpy(report->spawn_name, spawn_name, strlen(spawn_name) > 63 ? 63 : strlen(spawn_name)); + report->spawn_id = spawn_id; + report->account_id = account_id; + report->zone_id = zone_id; + loginserver.SendPacket(outpack); + database.SaveBugReport(report->category, report->subcategory, report->causes_crash, report->reproducible, report->summary, report->description, report->version, report->player, account_id, spawn_name, spawn_id, zone_id); + safe_delete(outpack); + return true; +} + +void ZoneList::WritePlayerStatistics() { + list::iterator zone_itr; + MZoneList.readlock(__FUNCTION__, __LINE__); + for (zone_itr = zlist.begin(); zone_itr != zlist.end(); zone_itr++) + (*zone_itr)->WritePlayerStatistics(); + MZoneList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneList::ShutDownZones(){ + LogWrite(WORLD__INFO, 0, "World", "Shutting down all zones, please wait..."); + list::iterator zone_itr; + int32 size = 0; + MZoneList.readlock(__FUNCTION__, __LINE__); + for (zone_itr = zlist.begin(); zone_itr != zlist.end(); zone_itr++){ + (*zone_itr)->Shutdown(); + } + size = zlist.size(); + MZoneList.releasereadlock(__FUNCTION__, __LINE__); + while(size > 0){ + Sleep(10); + MZoneList.readlock(__FUNCTION__, __LINE__); + size = zlist.size(); + MZoneList.releasereadlock(__FUNCTION__, __LINE__); + } + LogWrite(WORLD__INFO, 0, "World", "Zone shutdown complete"); +} + +void ZoneList::ReloadMail() { + map::iterator itr; + MClientList.writelock(__FUNCTION__, __LINE__); + for (itr = client_map.begin(); itr != client_map.end(); itr++) { + itr->second->GetPlayer()->DeleteMail(); + database.LoadPlayerMail(itr->second); + } + MClientList.releasewritelock(__FUNCTION__, __LINE__); +} + +void World::AddSpawnScript(int32 id, const char* name){ + MSpawnScripts.lock(); + if(name) + spawn_scripts[id] = string(name); + MSpawnScripts.unlock(); +} + +void World::AddSpawnEntryScript(int32 id, const char* name){ + MSpawnScripts.lock(); + if(name) + spawnentry_scripts[id] = string(name); + MSpawnScripts.unlock(); +} + +void World::AddSpawnLocationScript(int32 id, const char* name){ + MSpawnScripts.lock(); + if(name) + spawnlocation_scripts[id] = string(name); + MSpawnScripts.unlock(); +} + +void World::AddZoneScript(int32 id, const char* name) { + MZoneScripts.lock(); + if (name) + zone_scripts[id] = string(name); + MZoneScripts.unlock(); +} + +const char* World::GetSpawnScript(int32 id){ + LogWrite(SPAWN__TRACE, 1, "Spawn", "Enter %s", __FUNCTION__); + const char* ret = 0; + MSpawnScripts.lock(); + if(spawn_scripts.count(id) > 0) + ret = spawn_scripts[id].c_str(); + MSpawnScripts.unlock(); + LogWrite(SPAWN__TRACE, 1, "Spawn", "Exit %s", __FUNCTION__); + return ret; +} + +const char* World::GetSpawnEntryScript(int32 id){ + LogWrite(SPAWN__TRACE, 1, "Spawn", "Enter %s", __FUNCTION__); + const char* ret = 0; + MSpawnScripts.lock(); + if(spawnentry_scripts.count(id) > 0) + ret = spawnentry_scripts[id].c_str(); + MSpawnScripts.unlock(); + LogWrite(SPAWN__TRACE, 1, "Spawn", "Exit %s", __FUNCTION__); + return ret; +} + +const char* World::GetSpawnLocationScript(int32 id){ + LogWrite(SPAWN__TRACE, 1, "Spawn", "Enter %s", __FUNCTION__); + const char* ret = 0; + MSpawnScripts.lock(); + if(spawnlocation_scripts.count(id) > 0) + ret = spawnlocation_scripts[id].c_str(); + MSpawnScripts.unlock(); + LogWrite(SPAWN__TRACE, 1, "Spawn", "Exit %s", __FUNCTION__); + return ret; +} + +const char* World::GetZoneScript(int32 id) { + const char* ret = 0; + MZoneScripts.lock(); + if (zone_scripts.count(id) > 0) + ret = zone_scripts[id].c_str(); + MZoneScripts.unlock(); + return ret; +} + +void World::ResetSpawnScripts(){ + MSpawnScripts.lock(); + spawn_scripts.clear(); + spawnentry_scripts.clear(); + spawnlocation_scripts.clear(); + MSpawnScripts.unlock(); +} + +void World::ResetZoneScripts() { + MZoneScripts.lock(); + zone_scripts.clear(); + MZoneScripts.unlock(); +} + + + +vector* World::GetMerchantItemList(int32 merchant_id, int8 merchant_type, Player* player) +{ + vector* ret = 0; + MMerchantList.lock(); + + if(merchant_info.count(merchant_id) > 0) + { + MerchantInfo* info = merchant_info[merchant_id]; + vector::iterator itr; + int32 inventory_id = 0; + Item* item = 0; + + for(int i=info->inventory_ids.size()-1;i>=0;i--) + { + inventory_id = info->inventory_ids[i]; + + if(merchant_inventory_items.count(inventory_id) > 0) + { + for(itr = merchant_inventory_items[inventory_id].begin(); itr != merchant_inventory_items[inventory_id].end(); itr++) + { + if(!ret) + ret = new vector; + + item = master_item_list.GetItem((*itr).item_id); + + // if NOT spell merchant, OR + // skill req is any skill, OR player has the skill, AND + // skill req2 is any skill, OR player has the skill2 + if(item && ( (merchant_type & MERCHANT_TYPE_SPELLS) == 0 || ( (item->generic_info.skill_req1 == 0xFFFFFFFF || player->GetSkills()->HasSkill(item->generic_info.skill_req1)) && (item->generic_info.skill_req2 == 0xFFFFFFFF || player->GetSkills()->HasSkill(item->generic_info.skill_req2)) ) ) ) + (*ret).push_back(*itr); + } + } + } + } + MMerchantList.unlock(); + return ret; +} + +vector* World::GetMerchantList(int32 merchant_id){ + vector* ret = 0; + MMerchantList.lock(); + if(merchant_info.count(merchant_id) > 0){ + MerchantInfo* info = merchant_info[merchant_id]; + map::iterator itr; + int32 inventory_id = 0; + for(int i=info->inventory_ids.size()-1;i>=0;i--){ + inventory_id = info->inventory_ids[i]; + if(merchant_inventory_items.count(inventory_id) > 0){ + ret = &merchant_inventory_items[inventory_id]; + } + } + } + MMerchantList.unlock(); + return ret; +} + +void World::AddMerchantItem(int32 inventory_id, MerchantItemInfo ItemInfo){ + MMerchantList.lock(); + merchant_inventory_items[inventory_id].push_back(ItemInfo); + MMerchantList.unlock(); +} + +void World::DeleteMerchantItems(){ + MMerchantList.lock(); + merchant_inventory_items.clear(); + MMerchantList.unlock(); +} + +void World::RemoveMerchantItem(int32 inventory_id, int32 item_id){ + MMerchantList.lock(); + if(merchant_inventory_items.count(inventory_id) > 0) { + vector::iterator itr; + for(itr = merchant_inventory_items[inventory_id].begin(); itr != merchant_inventory_items[inventory_id].end(); itr++){ + if ((*itr).item_id == item_id) { + merchant_inventory_items[inventory_id].erase(itr); + break; + } + } + } + MMerchantList.unlock(); +} + +int16 World::GetMerchantItemQuantity(int32 merchant_id, int32 item_id){ + int16 amount = 0; + int32 inventory_id = GetInventoryID(merchant_id, item_id); + if(inventory_id > 0){ + MMerchantList.lock(); + vector::iterator itr; + for(itr = merchant_inventory_items[inventory_id].begin(); itr != merchant_inventory_items[inventory_id].end(); itr++){ + if ((*itr).item_id == item_id) + amount = (*itr).quantity; + } + MMerchantList.unlock(); + } + return amount; +} + +int32 World::GetInventoryID(int32 merchant_id, int32 item_id){ + int32 ret = 0; + MMerchantList.lock(); + if(merchant_info.count(merchant_id) > 0){ + MerchantInfo* info = merchant_info[merchant_id]; + vector::iterator itr; + int32 inventory_id = 0; + for(int i=info->inventory_ids.size()-1;i>=0;i--){ + inventory_id = info->inventory_ids[i]; + if(merchant_inventory_items.count(inventory_id) > 0){ + for(itr = merchant_inventory_items[inventory_id].begin(); itr != merchant_inventory_items[inventory_id].end(); itr++){ + if((*itr).item_id == item_id){ + ret = inventory_id; + break; + } + } + if(ret > 0) + break; + } + } + } + MMerchantList.unlock(); + return ret; +} + +void World::DecreaseMerchantQuantity(int32 merchant_id, int32 item_id, int16 amount){ + int16 total_left = GetMerchantItemQuantity(merchant_id, item_id); + if(total_left > 0 && total_left < 0xFF){ + int32 inventory_id = GetInventoryID(merchant_id, item_id); + if(inventory_id > 0){ + MMerchantList.lock(); + vector::iterator itr; + for(itr = merchant_inventory_items[inventory_id].begin(); itr != merchant_inventory_items[inventory_id].end(); itr++){ + if ((*itr).item_id == item_id) { + if(total_left <= amount) { + merchant_inventory_items[inventory_id].erase(itr); + amount = 0; + break; + } + else + (*itr).quantity -= amount; + amount = (*itr).quantity; + } + } + + MMerchantList.unlock(); + } + } +} + +MerchantInfo* World::GetMerchantInfo(int32 merchant_id){ + MerchantInfo* ret = 0; + MMerchantList.lock(); + if(merchant_info.count(merchant_id) > 0) + ret = merchant_info[merchant_id]; + MMerchantList.unlock(); + return ret; +} + +void World::AddMerchantInfo(int32 merchant_id, MerchantInfo* info){ + MMerchantList.lock(); + if(merchant_info.count(merchant_id) > 0){ + safe_delete(merchant_info[merchant_id]); + } + merchant_info[merchant_id] = info; + MMerchantList.unlock(); +} + +map* World::GetMerchantInfo() { + return &merchant_info; +} + +void World::DeleteMerchantsInfo(){ + MMerchantList.lock(); + map::iterator itr; + for(itr = merchant_info.begin(); itr != merchant_info.end(); itr++){ + safe_delete(itr->second); + } + merchant_info.clear(); + MMerchantList.unlock(); +} + + + + + +void World::DeleteSpawns(){ + //reloading = true; + //ClearLootTables(); + /* + map::iterator npc_list_iter; + for(npc_list_iter=npc_list.begin();npc_list_iter!=npc_list.end();npc_list_iter++) { + safe_delete(npc_list_iter->second); + } + npc_list.clear(); + map::iterator object_list_iter; + for(object_list_iter=object_list.begin();object_list_iter!=object_list.end();object_list_iter++) { + safe_delete(object_list_iter->second); + } + object_list.clear(); + map::iterator groundspawn_list_iter; + for(groundspawn_list_iter=groundspawn_list.begin();groundspawn_list_iter!=groundspawn_list.end();groundspawn_list_iter++) { + safe_delete(groundspawn_list_iter->second); + } + groundspawn_list.clear(); + map::iterator widget_list_iter; + for(widget_list_iter=widget_list.begin();widget_list_iter!=widget_list.end();widget_list_iter++) { + safe_delete(widget_list_iter->second); + } + widget_list.clear(); + map::iterator sign_list_iter; + for(sign_list_iter=sign_list.begin();sign_list_iter!=sign_list.end();sign_list_iter++) { + safe_delete(sign_list_iter->second); + } + sign_list.clear();*/ + map::iterator appearance_list_iter; + for(appearance_list_iter=npc_appearance_list.begin();appearance_list_iter!=npc_appearance_list.end();appearance_list_iter++) { + safe_delete(appearance_list_iter->second); + } + npc_appearance_list.clear(); + + /* + map* >::iterator command_list_iter; + for(command_list_iter=entity_command_list.begin();command_list_iter!=entity_command_list.end();command_list_iter++) { + vector* v = command_list_iter->second; + if(v){ + for(int32 i=0;isize();i++){ + safe_delete(v->at(i)); + } + safe_delete(v); + } + } + entity_command_list.clear(); + */ + + //DeleteGroundSpawnItems(); + //DeleteTransporters(); + //DeleteTransporterMaps(); +} + +void World::ReloadGuilds() { + guild_list.GetGuilds()->clear(true); + database.LoadGuilds(); +} + +int8 World::GetClassID(const char* name){ + return classes.GetClassID(name); +} + +void World::WritePlayerStatistics() { + zone_list.WritePlayerStatistics(); +} + +void World::WriteServerStatistics() { + map::iterator itr; + Statistic* stat = 0; + for (itr = server_statistics.begin(); itr != server_statistics.end(); itr++) { + stat = itr->second; + if (stat->save_needed) { + stat->save_needed = false; + database.WriteServerStatistic(stat); + } + } + database.WriteServerStatisticsNeededQueries(); +} + +void World::AddServerStatistic(int32 stat_id, sint32 stat_value, int32 stat_date) { + if (server_statistics.count(stat_id) == 0) { + Statistic* stat = new Statistic; + stat->stat_id = stat_id; + stat->stat_value = stat_value; + stat->stat_date = stat_date; + stat->save_needed = false; + server_statistics[stat_id] = stat; + } +} + +void World::UpdateServerStatistic(int32 stat_id, sint32 stat_value, bool overwrite) { + if (server_statistics.count(stat_id) == 0) + AddServerStatistic(stat_id, stat_value, 0); + Statistic* stat = server_statistics[stat_id]; + overwrite == true ? stat->stat_value = stat_value : stat->stat_value += stat_value; + stat->stat_date = Timer::GetUnixTimeStamp(); + stat->save_needed = true; +} + +sint32 World::GetServerStatisticValue(int32 stat_id) { + if (server_statistics.count(stat_id) > 0) + return server_statistics[stat_id]->stat_value; + return 0; +} + +void World::RemoveServerStatistics() { + map::iterator stat_itr; + for (stat_itr = server_statistics.begin(); stat_itr != server_statistics.end(); stat_itr++) + safe_delete(stat_itr->second); + server_statistics.clear(); +} + +void World::SendGroupQuests(PlayerGroup* group, Client* client){ + return; + /*if(!group) + return; + GroupMemberInfo* info = 0; + MGroups.readlock(__FUNCTION__, __LINE__); + deque::iterator itr; + for(itr = group->members.begin(); itr != group->members.end(); itr++){ + info = *itr; + if(info->client){ + LogWrite(PLAYER__DEBUG, 0, "Player", "Send Quest Journal..."); + info->client->SendQuestJournal(false, client); + client->SendQuestJournal(false, info->client); + } + } + MGroups.releasereadlock(__FUNCTION__, __LINE__);*/ +} + +/*void World::CheckRemoveGroupedPlayer(){ + map::iterator itr; + GroupMemberInfo* found = 0; + MGroups.readlock(__FUNCTION__, __LINE__); + for(itr = group_removal_pending.begin(); itr != group_removal_pending.end(); itr++){ + if(itr->second < Timer::GetCurrentTime2()){ + found = itr->first; + break; + } + } + MGroups.releasereadlock(__FUNCTION__, __LINE__); + if(found){ + if(!found->client || (found->client && found->client->IsConnected() == false)) + DeleteGroupMember(found); + else{ + MGroups.writelock(__FUNCTION__, __LINE__); + group_removal_pending.erase(found); + MGroups.releasewritelock(__FUNCTION__, __LINE__); + } + } +}*/ + +bool World::RejoinGroup(Client* client, int32 group_id){ + if (!group_id) // no need if no group id! + return false; + + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + PlayerGroup* group = world.GetGroupManager()->GetGroup(group_id); + deque* members = 0; + if (group) + members = group->GetMembers(); + + string name = string(client->GetPlayer()->GetName()); + if (!members) + { + // group does not exist! + + Query query; + query.AddQueryAsync(client->GetCharacterID(), &database, Q_INSERT, "UPDATE characters set group_id = 0 where id = %u", + client->GetCharacterID()); + LogWrite(PLAYER__ERROR, 0, "Player", "Group did not exist for player %s to group id %i, async query to group_id = 0.", name.c_str(), group_id); + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + return false; + } + deque::iterator itr; + GroupMemberInfo* info = 0; + + bool match = false; + group->MGroupMembers.writelock(); + for (itr = members->begin(); itr != members->end(); itr++) { + + info = *itr; + + if (info && info->name == name) + { + info->client = client; + info->member = client->GetPlayer(); + client->GetPlayer()->SetGroup(group); + client->GetPlayer()->SetGroupMemberInfo(info); + client->GetPlayer()->UpdateGroupMemberInfo(true, true); + LogWrite(PLAYER__DEBUG, 0, "Player", "Identified group match for player %s to group id %u", name.c_str(), group_id); + match = true; + break; + } + } + group->MGroupMembers.releasewritelock(); + + // must be done after cause it needs a readlock + if (match) + group->SendGroupUpdate(); + + if (!match) + LogWrite(PLAYER__ERROR, 0, "Player", "Identified group match for player %s to group id %u, however the player name was not present in the group! May be an old group id that has been re-used.", name.c_str(), group_id); + + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + + return match; +} + + +void World::AddBonuses(Item* item, ItemStatsValues* values, int16 type, sint32 value, Entity* entity){ + if(values){ + if(item && entity && entity->IsPlayer()) + { + int32 effective_level = entity->GetInfoStructUInt("effective_level"); + if(effective_level && effective_level < entity->GetLevel() && item->details.recommended_level > effective_level) + { + int32 diff = item->details.recommended_level - effective_level; + float tmpValue = (float)value; + value = (sint32)(float)(tmpValue / (1.0f + ((float)diff * .05f))); + } + } + switch(type){ + case ITEM_STAT_STR:{ + values->str += value; + break; + } + case ITEM_STAT_STA:{ + values->sta += value; + break; + } + case ITEM_STAT_AGI:{ + values->agi += value; + break; + } + case ITEM_STAT_WIS:{ + values->wis += value; + break; + } + case ITEM_STAT_INT:{ + values->int_ += value; + break; + } + case ITEM_STAT_VS_SLASH:{ + values->vs_slash += value; + break; + } + case ITEM_STAT_VS_CRUSH:{ + values->vs_crush += value; + break; + } + case ITEM_STAT_VS_PIERCE:{ + values->vs_pierce += value; + break; + } + case ITEM_STAT_VS_HEAT:{ + values->vs_heat += value; + break; + } + case ITEM_STAT_VS_COLD:{ + values->vs_cold += value; + break; + } + case ITEM_STAT_VS_MAGIC:{ + values->vs_magic += value; + break; + } + case ITEM_STAT_VS_MENTAL:{ + values->vs_mental += value; + break; + } + case ITEM_STAT_VS_DIVINE:{ + values->vs_divine += value; + break; + } + case ITEM_STAT_VS_DISEASE:{ + values->vs_disease += value; + break; + } + case ITEM_STAT_VS_POISON:{ + values->vs_poison += value; + break; + } + case ITEM_STAT_HEALTH:{ + values->health += value; + break; + } + case ITEM_STAT_POWER:{ + values->power += value; + break; + } + case ITEM_STAT_CONCENTRATION:{ + values->concentration += value; + break; + } + case ITEM_STAT_ABILITY_MODIFIER:{ + values->ability_modifier += value; + break; + } + case ITEM_STAT_CRITICALMITIGATION:{ + values->criticalmitigation += value; + break; + } + case ITEM_STAT_EXTRASHIELDBLOCKCHANCE:{ + values->extrashieldblockchance += value; + break; + } + case ITEM_STAT_BENEFICIALCRITCHANCE:{ + values->beneficialcritchance += value; + break; + } + case ITEM_STAT_CRITBONUS:{ + values->critbonus += value; + break; + } + case ITEM_STAT_POTENCY:{ + values->potency += value; + break; + } + case ITEM_STAT_HATEGAINMOD:{ + values->hategainmod += value; + break; + } + case ITEM_STAT_ABILITYREUSESPEED:{ + values->abilityreusespeed += value; + break; + } + case ITEM_STAT_ABILITYCASTINGSPEED:{ + values->abilitycastingspeed += value; + break; + } + case ITEM_STAT_ABILITYRECOVERYSPEED:{ + values->abilityrecoveryspeed += value; + break; + } + case ITEM_STAT_SPELLREUSESPEED:{ + values->spellreusespeed += value; + break; + } + case ITEM_STAT_SPELLMULTIATTACKCHANCE:{ + values->spellmultiattackchance += value; + break; + } + case ITEM_STAT_DPS:{ + values->dps += value; + break; + } + case ITEM_STAT_ATTACKSPEED:{ + values->attackspeed += value; + break; + } + case ITEM_STAT_MULTIATTACKCHANCE:{ + values->multiattackchance += value; + break; + } + case ITEM_STAT_AEAUTOATTACKCHANCE:{ + values->aeautoattackchance += value; + break; + } + case ITEM_STAT_STRIKETHROUGH:{ + values->strikethrough += value; + break; + } + case ITEM_STAT_ACCURACY:{ + values->accuracy += value; + break; + } + /*case ITEM_STAT_OFFENSIVESPEED:{ + values->offensivespeed += value; + break; + }*/ + default: { + if (entity) { + entity->MStats.lock(); + entity->stats[type] += value; + entity->MStats.unlock(); + } + break; + } + } + } +} + +void World::CreateGuild(const char* guild_name, Client* leader, int32 group_id) { + deque::iterator itr; + GroupMemberInfo* gmi; + Guild *guild; + + assert(guild_name); + + guild = new Guild(); + guild->SetName(guild_name); + guild->SetFormedDate(Timer::GetUnixTimeStamp()); + database.LoadGuildDefaultRanks(guild); + database.LoadGuildDefaultEventFilters(guild); + database.SaveGuild(guild, true); + database.SaveGuildEvents(guild); + database.SaveGuildRanks(guild); + database.SaveGuildEventFilters(guild); + database.SaveGuildRecruiting(guild); + guild_list.AddGuild(guild); + if (leader && !leader->GetPlayer()->GetGuild()) + guild->AddNewGuildMember(leader, 0, GUILD_RANK_LEADER); + database.SaveGuildMembers(guild); + if (leader && group_id > 0) { + GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + + PlayerGroup* group = world.GetGroupManager()->GetGroup(group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + for (itr = members->begin(); itr != members->end(); itr++) { + gmi = *itr; + if (gmi->client && gmi->client != leader && !gmi->client->GetPlayer()->GetGuild()) + guild->InvitePlayer(gmi->client, leader->GetPlayer()->GetName()); + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } +} + +void World::SaveGuilds() { + MutexMap* guilds = guild_list.GetGuilds(); + MutexMap::iterator itr = guilds->begin(); + while (itr.Next()) { + Guild* guild = itr.second; + if (guild->GetSaveNeeded()) + database.SaveGuild(guild); + if (guild->GetMemberSaveNeeded()) + database.SaveGuildMembers(guild); + if (guild->GetEventsSaveNeeded()) + database.SaveGuildEvents(guild); + if (guild->GetRanksSaveNeeded()) + database.SaveGuildRanks(guild); + if (guild->GetEventFiltersSaveNeeded()) + database.SaveGuildEventFilters(guild); + if (guild->GetPointsHistorySaveNeeded()) + database.SaveGuildPointsHistory(guild); + if (guild->GetRecruitingSaveNeeded()) + database.SaveGuildRecruiting(guild); + } +} + +void World::PickRandomLottoDigits(int32* digits) { + if (digits) { + for (int32 i = 0; i < 6; i++) { + bool found = true; + int32 num = 0; + while (found) { + num = ((int32)rand() % 36) + 1; + for (int32 j = 0; j < 6; j++) { + if (digits[j] == num) + break; + if (j == 5) + found = false; + } + } + digits[i] = num; + } + } +} + +void World::AddLottoPlayer(int32 character_id, int32 end_time) { + LottoPlayer* lp; + if (lotto_players.count(character_id) == 0) { + lp = new LottoPlayer; + lotto_players.Put(character_id, lp); + } + else + lp = lotto_players.Get(character_id); + lp->end_time = end_time; + lp->num_matches = 0; + lp->set = false; +} + +void World::RemoveLottoPlayer(int32 character_id) { + if (lotto_players.count(character_id) > 0) + lotto_players.erase(character_id, false, true); +} + +void World::SetLottoPlayerNumMatches(int32 character_id, int8 num_matches) { + if (lotto_players.count(character_id) > 0) { + lotto_players.Get(character_id)->num_matches = num_matches; + lotto_players.Get(character_id)->set = true; + } +} + +void World::CheckLottoPlayers() { + MutexMap::iterator itr = lotto_players.begin(); + while (itr.Next()) { + LottoPlayer* lp = itr->second; + if (Timer::GetCurrentTime2() >= lp->end_time && lp->set) { + int8 num_matches = lp->num_matches; + LogWrite(PLAYER__DEBUG, 0, "Player", "Num matches: %u", lp->num_matches); + Client* client = zone_list.GetClientByCharID(itr->first); + if (client && num_matches >= 2) { + if (num_matches == 2) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You receive 10 silver."); + client->SendPopupMessage(0, "Congratulations! You have won 10 silver!", "", 2, 0xFF, 0xFF, 0x99); + client->GetPlayer()->AddCoins(1000); + client->GetPlayer()->GetZone()->SendCastSpellPacket(869, client->GetPlayer()); + } + else if (num_matches == 3) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You receive 50 silver."); + client->SendPopupMessage(0, "Congratulations! You have won 50 silver!", "", 2, 0xFF, 0xFF, 0x99); + client->GetPlayer()->AddCoins(5000); + client->GetPlayer()->GetZone()->SendCastSpellPacket(870, client->GetPlayer()); + } + else if (num_matches == 4) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You receive 2 gold 50 silver."); + client->SendPopupMessage(0, "Congratulations! You have won 2 gold 50 silver!", "", 2, 0xFF, 0xFF, 0x99); + client->GetPlayer()->AddCoins(25000); + client->GetPlayer()->GetZone()->SendCastSpellPacket(871, client->GetPlayer()); + } + else if (num_matches == 5) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You receive 25 gold."); + client->SendPopupMessage(0, "Congratulations! You have won 25 gold!", "", 2, 0xFF, 0xFF, 0x99); + client->GetPlayer()->AddCoins(250000); + client->GetPlayer()->GetZone()->SendCastSpellPacket(872, client->GetPlayer()); + } + else if (num_matches == 6) { + Variable* var = variables.FindVariable("gambling_current_jackpot"); + if (var) { + int64 jackpot = 0; + try { + jackpot = atoul(var->GetValue()); + } + catch (...) { + jackpot = 10000; + } + char coin_message[128]; + char message[512]; + char announcement[512]; + memset(coin_message, 0, sizeof(coin_message)); + memset(message, 0, sizeof(message)); + memset(announcement, 0, sizeof(announcement)); + sprintf(coin_message, "%s", client->GetCoinMessage(jackpot).c_str()); + sprintf(message, "Congratulations! You have won %s!", coin_message); + sprintf(announcement, "%s as won the jackpot containing a total of %s!", client->GetPlayer()->GetName(), coin_message); + client->Message(CHANNEL_COLOR_YELLOW, "You receive %s.", coin_message); + client->SendPopupMessage(0, message, "", 2, 0xFF, 0xFF, 0x99); + zone_list.HandleGlobalAnnouncement(announcement); + client->GetPlayer()->AddCoins(jackpot); + client->GetPlayer()->GetZone()->SendCastSpellPacket(843, client->GetPlayer()); + client->GetPlayer()->GetZone()->SendCastSpellPacket(1413, client->GetPlayer()); + } + } + } + RemoveLottoPlayer(itr->first); + } + } +} + +void World::AddHouseZone(int32 id, string name, int64 cost_coins, int32 cost_status, int64 upkeep_coins, int32 upkeep_status, int8 vault_slots, int8 alignment, int8 guild_level, int32 zone_id, int32 exit_zone_id, float exit_x, float exit_y, float exit_z, float exit_heading) { + MHouseZones.writelock(__FUNCTION__, __LINE__); + if (m_houseZones.count(id) == 0) { + HouseZone* hz = new HouseZone; + //ZeroMemory(hz, sizeof(HouseZone)); + hz->id = id; + hz->name = name; + hz->cost_coin = cost_coins; + hz->cost_status = cost_status; + hz->upkeep_coin = upkeep_coins; + hz->upkeep_status = upkeep_status; + hz->vault_slots = vault_slots; + hz->alignment = alignment; + hz->guild_level = guild_level; + hz->zone_id = zone_id; + hz->exit_zone_id = exit_zone_id; + hz->exit_x = exit_x; + hz->exit_y = exit_y; + hz->exit_z = exit_z; + hz->exit_heading = exit_heading; + m_houseZones[id] = hz; + } + else { + LogWrite(WORLD__ERROR, 0, "Housing", "Duplicate house id (%u) for %s", id, name.c_str()); + } + MHouseZones.releasewritelock(__FUNCTION__, __LINE__); +} + +HouseZone* World::GetHouseZone(int32 id) { + HouseZone* ret = 0; + + MHouseZones.readlock(__FUNCTION__, __LINE__); + if (m_houseZones.count(id) > 0) + ret = m_houseZones[id]; + MHouseZones.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +void World::AddPlayerHouse(int32 char_id, int32 house_id, int64 unique_id, int32 instance_id, int32 upkeep_due, int64 escrow_coins, int32 escrow_status, string player_name) { + MPlayerHouses.writelock(__FUNCTION__, __LINE__); + if (m_playerHouses.count(house_id) == 0 || m_playerHouses[house_id].count(char_id) == 0) { + PlayerHouse* ph = new PlayerHouse; + ph->house_id = house_id; + ph->unique_id = unique_id; + ph->instance_id = instance_id; + ph->escrow_coins = escrow_coins; + ph->escrow_status = escrow_status; + ph->upkeep_due = upkeep_due; + ph->player_name = player_name; + ReloadHouseData(ph); + m_playerHouses[house_id][char_id] = ph; + } + MPlayerHouses.releasewritelock(__FUNCTION__, __LINE__); +} + +void World::ReloadHouseData(PlayerHouse* ph) +{ + database.LoadDeposits(ph); + database.LoadHistory(ph); +} + +PlayerHouse* World::GetPlayerHouseByHouseID(int32 char_id, int32 house_id) { + PlayerHouse* ret = 0; + + MPlayerHouses.readlock(__FUNCTION__, __LINE__); + if (m_playerHouses.count(house_id) > 0 && m_playerHouses[house_id].count(char_id) > 0) + ret = m_playerHouses[house_id][char_id]; + MPlayerHouses.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +PlayerHouse* World::GetPlayerHouseByUniqueID(int64 unique_id) { + PlayerHouse* ret = 0; + + MPlayerHouses.readlock(__FUNCTION__, __LINE__); + map >::iterator itr; + for (itr = m_playerHouses.begin(); itr != m_playerHouses.end(); itr++) { + map::iterator itr2; + for (itr2 = itr->second.begin(); itr2 != itr->second.end(); itr2++) { + if (itr2->second->unique_id == unique_id) { + ret = itr2->second; + break; + } + } + if (ret) + break; + } + MPlayerHouses.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +PlayerHouse* World::GetPlayerHouseByInstanceID(int32 instance_id) { + PlayerHouse* ret = 0; + + MPlayerHouses.readlock(__FUNCTION__, __LINE__); + map >::iterator itr; + for (itr = m_playerHouses.begin(); itr != m_playerHouses.end(); itr++) { + map::iterator itr2; + for (itr2 = itr->second.begin(); itr2 != itr->second.end(); itr2++) { + if (itr2->second->instance_id == instance_id) { + ret = itr2->second; + break; + } + } + if (ret) + break; + } + MPlayerHouses.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +vector World::GetAllPlayerHouses(int32 char_id) { + vector ret; + + MPlayerHouses.readlock(__FUNCTION__, __LINE__); + map >::iterator itr; + for (itr = m_playerHouses.begin(); itr != m_playerHouses.end(); itr++) { + if (itr->second.count(char_id) > 0) + ret.push_back(itr->second[char_id]); + } + MPlayerHouses.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +vector World::GetAllPlayerHousesByHouseID(int32 house_id) { + vector ret; + + MPlayerHouses.readlock(__FUNCTION__, __LINE__); + if (m_houseZones.count(house_id) > 0) { + map::iterator itr; + for (itr = m_playerHouses[house_id].begin(); itr != m_playerHouses[house_id].end(); itr++) + ret.push_back(itr->second); + } + MPlayerHouses.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +PlayerHouse* World::GetPlayerHouse(Client* client, int32 spawn_id, int64 unique_house_id, HouseZone** set_house_zone) { + PlayerHouse* ph = nullptr; + HouseZone* hz = nullptr; + + if(spawn_id) { + Spawn* houseWidget = client->GetPlayer()->GetSpawnByIndex(spawn_id); + if(houseWidget && houseWidget->IsWidget() && ((Widget*)houseWidget)->GetHouseID()) { + hz = world.GetHouseZone(((Widget*)houseWidget)->GetHouseID()); + if (hz) { + ph = world.GetPlayerHouseByHouseID(client->GetPlayer()->GetCharacterID(), hz->id); + } + } + } + + if(!ph && client->GetCurrentZone()->GetInstanceID()) { + ph = world.GetPlayerHouseByInstanceID(client->GetCurrentZone()->GetInstanceID()); + } + + if(!ph && unique_house_id) { + ph = world.GetPlayerHouseByUniqueID(unique_house_id); + } + + if (ph && !hz) { + hz = world.GetHouseZone(ph->house_id); + } + + if(set_house_zone) + *set_house_zone = hz; + + return ph; +} + +void World::PopulateTOVStatMap() { + //This function populates a map that converts changed CoE to ToV stats + tov_itemstat_conversion[0] = TOV_ITEM_STAT_HPREGEN; + tov_itemstat_conversion[1] = TOV_ITEM_STAT_MANAREGEN; + tov_itemstat_conversion[2] = TOV_ITEM_STAT_HPREGENPPT; + tov_itemstat_conversion[3] = TOV_ITEM_STAT_MPREGENPPT; + tov_itemstat_conversion[4] = TOV_ITEM_STAT_COMBATHPREGENPPT; + tov_itemstat_conversion[5] = TOV_ITEM_STAT_COMBATMPREGENPPT; + tov_itemstat_conversion[6] = TOV_ITEM_STAT_MAXHP; + tov_itemstat_conversion[7] = TOV_ITEM_STAT_MAXHPPERC; + tov_itemstat_conversion[8] = TOV_ITEM_STAT_MAXHPPERCFINAL; + tov_itemstat_conversion[9] = TOV_ITEM_STAT_SPEED; + tov_itemstat_conversion[10] = TOV_ITEM_STAT_SLOW; + tov_itemstat_conversion[11] = TOV_ITEM_STAT_MOUNTSPEED; + tov_itemstat_conversion[12] = TOV_ITEM_STAT_MOUNTAIRSPEED; + tov_itemstat_conversion[13] = TOV_ITEM_STAT_LEAPSPEED; + tov_itemstat_conversion[14] = TOV_ITEM_STAT_LEAPTIME; + tov_itemstat_conversion[15] = TOV_ITEM_STAT_GLIDEEFFICIENCY; + tov_itemstat_conversion[16] = TOV_ITEM_STAT_OFFENSIVESPEED; + tov_itemstat_conversion[17] = TOV_ITEM_STAT_ATTACKSPEED; + tov_itemstat_conversion[18] = 698; + tov_itemstat_conversion[19] = TOV_ITEM_STAT_MAXMANA; + tov_itemstat_conversion[20] = TOV_ITEM_STAT_MAXMANAPERC; + tov_itemstat_conversion[21] = TOV_ITEM_STAT_MAXATTPERC; + tov_itemstat_conversion[22] = TOV_ITEM_STAT_BLURVISION; + tov_itemstat_conversion[23] = TOV_ITEM_STAT_MAGICLEVELIMMUNITY; + tov_itemstat_conversion[24] = TOV_ITEM_STAT_HATEGAINMOD; + tov_itemstat_conversion[25] = TOV_ITEM_STAT_COMBATEXPMOD; + tov_itemstat_conversion[26] = TOV_ITEM_STAT_TRADESKILLEXPMOD; + tov_itemstat_conversion[27] = TOV_ITEM_STAT_ACHIEVEMENTEXPMOD; + tov_itemstat_conversion[28] = TOV_ITEM_STAT_SIZEMOD; + tov_itemstat_conversion[29] = TOV_ITEM_STAT_DPS; + tov_itemstat_conversion[30] = 698; + tov_itemstat_conversion[31] = TOV_ITEM_STAT_STEALTH; + tov_itemstat_conversion[32] = TOV_ITEM_STAT_INVIS; + tov_itemstat_conversion[33] = TOV_ITEM_STAT_SEESTEALTH; + tov_itemstat_conversion[34] = TOV_ITEM_STAT_SEEINVIS; + tov_itemstat_conversion[35] = TOV_ITEM_STAT_EFFECTIVELEVELMOD; + tov_itemstat_conversion[36] = TOV_ITEM_STAT_RIPOSTECHANCE; + tov_itemstat_conversion[37] = TOV_ITEM_STAT_PARRYCHANCE; + tov_itemstat_conversion[38] = TOV_ITEM_STAT_DODGECHANCE; + tov_itemstat_conversion[39] = TOV_ITEM_STAT_AEAUTOATTACKCHANCE; + tov_itemstat_conversion[40] = 698; + tov_itemstat_conversion[41] = TOV_ITEM_STAT_MULTIATTACKCHANCE; + tov_itemstat_conversion[42] = 698; + tov_itemstat_conversion[43] = 698; + tov_itemstat_conversion[44] = 698; + tov_itemstat_conversion[45] = TOV_ITEM_STAT_SPELLMULTIATTACKCHANCE; + tov_itemstat_conversion[46] = 698; + tov_itemstat_conversion[47] = TOV_ITEM_STAT_FLURRY; + tov_itemstat_conversion[48] = 698; + tov_itemstat_conversion[49] = TOV_ITEM_STAT_MELEEDAMAGEMULTIPLIER; + tov_itemstat_conversion[50] = TOV_ITEM_STAT_EXTRAHARVESTCHANCE; + tov_itemstat_conversion[51] = TOV_ITEM_STAT_EXTRASHIELDBLOCKCHANCE; + tov_itemstat_conversion[52] = TOV_ITEM_STAT_ITEMHPREGENPPT; + tov_itemstat_conversion[53] = TOV_ITEM_STAT_ITEMPPREGENPPT; + tov_itemstat_conversion[54] = TOV_ITEM_STAT_MELEECRITCHANCE; + tov_itemstat_conversion[55] = TOV_ITEM_STAT_CRITAVOIDANCE; + tov_itemstat_conversion[56] = TOV_ITEM_STAT_BENEFICIALCRITCHANCE; + tov_itemstat_conversion[57] = TOV_ITEM_STAT_CRITBONUS; + tov_itemstat_conversion[58] = 698; + tov_itemstat_conversion[59] = TOV_ITEM_STAT_POTENCY; + tov_itemstat_conversion[60] = 698; + tov_itemstat_conversion[61] = TOV_ITEM_STAT_UNCONSCIOUSHPMOD; + tov_itemstat_conversion[62] = TOV_ITEM_STAT_ABILITYREUSESPEED; + tov_itemstat_conversion[63] = TOV_ITEM_STAT_ABILITYRECOVERYSPEED; + tov_itemstat_conversion[64] = TOV_ITEM_STAT_ABILITYCASTINGSPEED; + tov_itemstat_conversion[65] = TOV_ITEM_STAT_SPELLREUSESPEED; + tov_itemstat_conversion[66] = TOV_ITEM_STAT_MELEEWEAPONRANGE; + tov_itemstat_conversion[67] = TOV_ITEM_STAT_RANGEDWEAPONRANGE; + tov_itemstat_conversion[68] = TOV_ITEM_STAT_FALLINGDAMAGEREDUCTION; + tov_itemstat_conversion[69] = TOV_ITEM_STAT_RIPOSTEDAMAGE; + tov_itemstat_conversion[70] = TOV_ITEM_STAT_MINIMUMDEFLECTIONCHANCE; + tov_itemstat_conversion[71] = TOV_ITEM_STAT_MOVEMENTWEAVE; + tov_itemstat_conversion[72] = TOV_ITEM_STAT_COMBATHPREGEN; + tov_itemstat_conversion[73] = TOV_ITEM_STAT_COMBATMANAREGEN; + tov_itemstat_conversion[74] = TOV_ITEM_STAT_CONTESTSPEEDBOOST; + tov_itemstat_conversion[75] = TOV_ITEM_STAT_TRACKINGAVOIDANCE; + tov_itemstat_conversion[76] = TOV_ITEM_STAT_STEALTHINVISSPEEDMOD; + tov_itemstat_conversion[77] = TOV_ITEM_STAT_LOOT_COIN; + tov_itemstat_conversion[78] = TOV_ITEM_STAT_ARMORMITIGATIONINCREASE; + tov_itemstat_conversion[79] = TOV_ITEM_STAT_AMMOCONSERVATION; + tov_itemstat_conversion[80] = TOV_ITEM_STAT_STRIKETHROUGH; + tov_itemstat_conversion[81] = TOV_ITEM_STAT_STATUSBONUS; + tov_itemstat_conversion[82] = TOV_ITEM_STAT_ACCURACY; + tov_itemstat_conversion[83] = TOV_ITEM_STAT_COUNTERSTRIKE; + tov_itemstat_conversion[84] = TOV_ITEM_STAT_SHIELDBASH; + tov_itemstat_conversion[85] = TOV_ITEM_STAT_WEAPONDAMAGEBONUS; + tov_itemstat_conversion[86] = 698; + tov_itemstat_conversion[87] = TOV_ITEM_STAT_WEAPONDAMAGEBONUSMELEEONLY; + tov_itemstat_conversion[88] = TOV_ITEM_STAT_ADDITIONALRIPOSTECHANCE; + tov_itemstat_conversion[89] = TOV_ITEM_STAT_PVPTOUGHNESS; + tov_itemstat_conversion[90] = TOV_ITEM_STAT_PVPLETHALITY; + tov_itemstat_conversion[91] = TOV_ITEM_STAT_STAMINABONUS; + tov_itemstat_conversion[92] = TOV_ITEM_STAT_WISDOMMITBONUS; + tov_itemstat_conversion[93] = TOV_ITEM_STAT_HEALRECEIVE; + tov_itemstat_conversion[94] = TOV_ITEM_STAT_HEALRECEIVEPERC; + tov_itemstat_conversion[95] = TOV_ITEM_STAT_PVPCRITICALMITIGATION; + tov_itemstat_conversion[96] = TOV_ITEM_STAT_BASEAVOIDANCEBONUS; + tov_itemstat_conversion[97] = TOV_ITEM_STAT_INCOMBATSAVAGERYREGEN; + tov_itemstat_conversion[98] = TOV_ITEM_STAT_OUTOFCOMBATSAVAGERYREGEN; + tov_itemstat_conversion[99] = TOV_ITEM_STAT_SAVAGERYREGEN; + tov_itemstat_conversion[100] = TOV_ITEM_STAT_SAVAGERYGAINMOD; + tov_itemstat_conversion[101] = TOV_ITEM_STAT_MAXSAVAGERYLEVEL; +} + +int32 World::LoadItemBlueStats() { + Query query; + MYSQL_ROW row; + int32 count = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT version_range1,version_range2,emu_stat,name,stat from itemstats"); + + if (result && mysql_num_rows(result) > 0) { + while (result && (row = mysql_fetch_row(result))) { + count++; + + if (atoi(row[0]) >= 63119) //KA + ka_itemstat_conversion[atoi(row[2])] = atoi(row[4]); + else if (atoi(row[0]) >= 57101) // ToV + tov_itemstat_conversion[atoi(row[2])] = atoi(row[4]); + else if (atoi(row[0]) >= 1193) // CoE + coe_itemstat_conversion[atoi(row[2])] = atoi(row[4]); + else if (atoi(row[0]) >= 1096) // DoV + dov_itemstat_conversion[atoi(row[2])] = atoi(row[4]); + } + } + return count; +} + +sint64 World::newValue = 0; + +sint16 World::GetItemStatAOMValue(sint16 subtype) { + sint16 tmp_subtype = subtype; + // this is ugly for now cause I didn't want to map it all out, see a better way later but a lot of these are just slightly shifted + if(subtype > 39) + { + // 88 needs to be something else (crit mitigation) + // 19 needs to be something else (ability reuse speed) which is 62 + if(subtype == 21) // ITEM_STAT_MAXATTPERC + tmp_subtype = 20; + else if(subtype == 41) // flurry + tmp_subtype = 39; + else if(subtype == 47) // flurry + tmp_subtype = 41; + else if(subtype == 49) // flurry + tmp_subtype = 42; + else if(subtype == 51) // ITEM_STAT_EXTRASHIELDBLOCKCHANCE + tmp_subtype = 44; + + //tmp_subtype = 43 is bountiful harvest + else if(subtype == 54 && subtype <= 57) // ITEM_STAT_MELEECRITCHANCE + tmp_subtype = subtype - 7; + else if(subtype == 59) // ITEM_STAT_POTENCY + tmp_subtype = 51; + else if(subtype >= 61 && subtype <= 85) // ITEM_STAT_RANGEDWEAPONRANGE + tmp_subtype = subtype - 9; // + else if(subtype >= 86 && subtype <= 101) // ITEM_STAT_WEAPONDAMAGEBONUSMELEEONLY + tmp_subtype = subtype - 8; // + else if(subtype == 102) // ITEM_STAT_SPELLWEAPONDAMAGEBONUS + tmp_subtype = 77; // + else if(subtype >= 103 && subtype <= 110) + tmp_subtype = subtype - 9; + else if(subtype == 122) // ITEM_STAT_ABILITYDOUBLECASTCHANCE + tmp_subtype = 40; // + else if(subtype == 124) // ITEM_STAT_STATUSEARNED + tmp_subtype = 27; // + else + tmp_subtype += 1; + + // 80 serves as ranged weapon range increase, but so does 58? + } + else if((subtype > 18 && subtype < 28) || subtype > 30) // max mana was 18 + tmp_subtype = subtype - 1; + else if(subtype == 5) + tmp_subtype = 46; + else if(subtype == 4) + tmp_subtype = 45; + + LogWrite(PLAYER__DEBUG, 0, "Player", "Convert type: %i -> %i", subtype, tmp_subtype); + return tmp_subtype; +} +sint16 World::GetItemStatTOVValue(sint16 subtype) { + return (tov_itemstat_conversion[subtype] - 600); +} +sint16 World::GetItemStatDOVValue(sint16 subtype) { + return (dov_itemstat_conversion[subtype] - 600); +} +sint16 World::GetItemStatCOEValue(sint16 subtype) { + return (coe_itemstat_conversion[subtype] - 600); +} +sint16 World::GetItemStatKAValue(sint16 subtype) { + return (ka_itemstat_conversion[subtype] - 600); +} + +int8 World::TranslateSlotSubTypeToClient(Client* client, int8 stat_type, sint16 sub_type) { + int8 new_subtype = (int8)sub_type; + switch(stat_type) { + case 2: { + if(client->GetVersion() <= 561) { + if(sub_type == (ITEM_STAT_VS_POISON-200)) // poison + new_subtype = 9; + else if(sub_type == (ITEM_STAT_VS_DISEASE-200)) // disease + new_subtype = 8; + else if(sub_type == (ITEM_STAT_VS_COLD-200)) // cold + new_subtype = 4; + else if(sub_type == (ITEM_STAT_VS_HEAT-200) || sub_type == (ITEM_STAT_VS_MAGIC-200)) + new_subtype += 2; + else if(sub_type == (ITEM_STAT_VS_MENTAL-200) || sub_type == (ITEM_STAT_VS_DIVINE-200)) + new_subtype -= 2; + } + else if(client->GetVersion() >= 60085) { // AoM era since its the client we support + if(sub_type == (ITEM_STAT_VS_MENTAL-200) || sub_type == (ITEM_STAT_VS_DIVINE-200) || sub_type == (ITEM_STAT_VS_COLD-200)) { + new_subtype = 255; // omit client cannot properly display + } + } + break; + } + case 6: + case 7: { + if(stat_type == 7){ + new_subtype = sub_type; + } + else if ((client->GetVersion() >= 63119) || client->GetVersion() == 61331){ //KA + new_subtype = world.GetItemStatKAValue(sub_type); + } + else if(client->GetVersion() >= 60085 ) { + new_subtype = world.GetItemStatAOMValue(sub_type); + } + else if (client->GetVersion() >= 57107){ //TOV + new_subtype = world.GetItemStatTOVValue(sub_type); + } + else if (client->GetVersion() >= 1193){ //COE + new_subtype = world.GetItemStatCOEValue(sub_type); + //tmp_subtype = stat->stat_subtype; + } + else if (client->GetVersion() >= 1096){ //DOV + new_subtype = world.GetItemStatDOVValue(sub_type); //comment out for testing + //tmp_subtype = stat->stat_subtype; //comment for normal use + } + break; + } + } + + return new_subtype; +} + +bool World::CheckTempBugCRC(char* msg) +{ + MBugReport.writelock(); + + sint32 crc = GetItemNameCrc(std::string(msg)); + + if (bug_report_crc.count(crc) > 0) + { + MBugReport.releasewritelock(); + return true; + } + else + bug_report_crc.insert(make_pair(crc, true)); + + MBugReport.releasewritelock(); + + return false; +} + + +#ifdef WIN32 +ulong World::GetCurrentThreadID(){ + return GetCurrentThreadId(); +} + +int64 World::GetThreadUsageCPUTime(){ + HANDLE handle = GetCurrentThread(); + int64 lpCreationTime; + int64 lpExitTime; + int64 lpKernelTime; + int64 lpUserTime; + if(GetThreadTimes(handle, (FILETIME*)&lpCreationTime, (FILETIME*)&lpExitTime, (FILETIME*)&lpKernelTime, (FILETIME*)&lpUserTime)) + return lpKernelTime + lpUserTime; + return 0; +} +#else + +#endif + + +void World::SyncCharacterAbilities(Client* client) +{ + MStartingLists.readlock(); + + int8 baseClass = classes.GetBaseClass(client->GetPlayer()->GetAdventureClass()); + int8 secondaryClass = classes.GetSecondaryBaseClass(client->GetPlayer()->GetAdventureClass()); + int8 actualClass = client->GetPlayer()->GetAdventureClass(); + int8 baseRace = client->GetPlayer()->GetRace(); + + multimap*>::iterator skill_itr = starting_skills.begin(); + multimap*>::iterator spell_itr = starting_spells.begin(); + bool isProcessing = false; + int8 wait_iterations = 0; // wait 5 iterations and give up if db takes too long + do + { + isProcessing = false; + if (skill_itr != starting_skills.end()) + { + isProcessing = true; + + // race = 255 is wildcard all, otherwise needs to match the race id + if (skill_itr->first == 255 || skill_itr->first == baseRace) + { + multimap::iterator child_itr; + for (child_itr = skill_itr->second->begin(); child_itr != skill_itr->second->end(); child_itr++) + { + // class = 255 is wildcard all, otherwise needs to match the class id + if (child_itr->first == 255 || + child_itr->first == baseClass || + child_itr->first == secondaryClass || + child_itr->first == actualClass) + { + if (!client->GetPlayer()->skill_list.HasSkill(child_itr->second.skill_id)) + { + Query query; + LogWrite(PLAYER__DEBUG, 0, "Player", "Adding skill %i for race: %i, class: %i for char_id: %u", child_itr->second.skill_id, baseRace, baseClass, client->GetCharacterID()); + query.AddQueryAsync(client->GetCharacterID(), &database, Q_INSERT, "INSERT IGNORE INTO character_skills (char_id, skill_id, current_val, max_val) VALUES (%u, %u, %u, %u)", + client->GetCharacterID(), child_itr->second.skill_id, child_itr->second.current_val, child_itr->second.max_val); + + client->GetPlayer()->AddSkill(child_itr->second.skill_id, child_itr->second.current_val, child_itr->second.max_val); + } + } + } + } + skill_itr++; + } + + if (spell_itr != starting_spells.end()) + { + isProcessing = true; + + // race = 255 is wildcard all, otherwise needs to match the race id + if (spell_itr->first == 255 || spell_itr->first == baseRace) + { + multimap::iterator child_itr; + for (child_itr = spell_itr->second->begin(); child_itr != spell_itr->second->end(); child_itr++) + { + // class = 255 is wildcard all, otherwise needs to match the class id + if (child_itr->first == 255 || + child_itr->first == baseClass || + child_itr->first == secondaryClass || + child_itr->first == actualClass) + { + if (!client->GetPlayer()->HasSpell(child_itr->second.spell_id, child_itr->second.tier, true)) + { + Query query; + LogWrite(PLAYER__DEBUG, 0, "Player", "Adding spell %i for race: %i, class: %i for char_id: %u", child_itr->second.spell_id, baseRace, baseClass, client->GetCharacterID()); + // knowledge_slot is a signed int in the DB + query.AddQueryAsync(client->GetCharacterID(), &database, Q_INSERT, "INSERT IGNORE INTO character_spells (char_id, spell_id, tier, knowledge_slot) VALUES (%u, %u, %u, %i)", + client->GetCharacterID(), child_itr->second.spell_id, child_itr->second.tier, child_itr->second.knowledge_slot); + + // reload spells, we don't know the spellbook or timer info + client->GetPlayer()->GetInfoStruct()->set_reload_player_spells(1); + } + } + } + } + spell_itr++; + } + } while (isProcessing); + + MStartingLists.releasereadlock(); +} + +void World::LoadStartingLists() +{ + LogWrite(WORLD__DEBUG, 1, "World", "-Loading `starting_skills`..."); + database.LoadStartingSkills(this); + + LogWrite(WORLD__DEBUG, 1, "World", "-Loading `starting_spells`..."); + database.LoadStartingSpells(this); +} + +void World::PurgeStartingLists() +{ + MStartingLists.writelock(); + + multimap*>::iterator skill_itr; + + for (skill_itr = starting_skills.begin(); skill_itr != starting_skills.end(); skill_itr++) + { + multimap* tmpMap = skill_itr->second; + safe_delete(tmpMap); + } + starting_skills.clear(); + + + multimap*>::iterator spell_itr; + + for (spell_itr = starting_spells.begin(); spell_itr != starting_spells.end(); spell_itr++) + { + multimap* tmpMap = spell_itr->second; + safe_delete(tmpMap); + } + starting_spells.clear(); + + + for(int type=0;type<3;type++) { + multimap*>::iterator vos_itr; + + for (vos_itr = voiceover_map[type].begin(); vos_itr != voiceover_map[type].end(); vos_itr++) + { + multimap* tmpMap = vos_itr->second; + safe_delete(tmpMap); + } + voiceover_map[type].clear(); + } + MStartingLists.releasewritelock(); +} + +void World::SetReloadingSubsystem(string subsystem) { + MReloadingSubsystems.lock(); + reloading_subsystems[subsystem] = Timer::GetCurrentTime2(); + MReloadingSubsystems.unlock(); +} + +void World::RemoveReloadingSubSystem(string subsystem) { + MReloadingSubsystems.lock(); + if (reloading_subsystems.count(subsystem) > 0) + reloading_subsystems.erase(subsystem); + MReloadingSubsystems.unlock(); +} + +bool World::IsReloadingSubsystems() { + bool result = false; + MReloadingSubsystems.lock(); + result = reloading_subsystems.size() > 0; + MReloadingSubsystems.unlock(); + return result; +} + +map World::GetOldestReloadingSubsystem() { + map result; + MReloadingSubsystems.lock(); + int32 current_time = Timer::GetCurrentTime2(); + map::iterator itr; + int32 oldest = current_time; + string oldestname = ""; + for (itr = reloading_subsystems.begin(); itr != reloading_subsystems.end(); itr++) { + if (itr->second < oldest) { + oldestname = itr->first; + result.clear(); + result[oldestname] = oldest; + } + } + MReloadingSubsystems.unlock(); + return result; +} + +void ZoneList::WatchdogHeartbeat() +{ + list::iterator zone_iter; + ZoneServer* tmp = 0; + MZoneList.writelock(__FUNCTION__, __LINE__); + + bool match = false; + for (zone_iter = zlist.begin(); zone_iter != zlist.end(); zone_iter++) + { + tmp = *zone_iter; + if (tmp) + { + int32 curTime = Timer::GetCurrentTime2(); + sint64 diff = (sint64)curTime - (sint64)tmp->GetWatchdogTime(); + if (diff > 120000) + { + LogWrite(WORLD__ERROR, 1, "World", "Zone %s is hung for %i milliseconds.. attempting to cancel threads...", tmp->GetZoneName(), diff); +#ifndef WIN32 + tmp->CancelThreads(); + zlist.erase(zone_iter); + safe_delete(tmp); +#endif + MZoneList.releasewritelock(__FUNCTION__, __LINE__); + match = true; + break; + } + else if (diff > 90000 && !tmp->isZoneShuttingDown()) + { + tmp->SetWatchdogTime(Timer::GetCurrentTime2()); // reset so we don't continuously flood this heartbeat + map oldest_process = world.GetOldestReloadingSubsystem(); + if (oldest_process.size() > 0) { + map::iterator itr = oldest_process.begin(); + if(itr != oldest_process.end()) + LogWrite(WORLD__ERROR, 1, "World", "Zone %s is hung for %i milliseconds.. while waiting for %s to reload...attempting shutdown", tmp->GetZoneName(), diff, itr->first); + else + LogWrite(WORLD__ERROR, 1, "World", "Zone %s is hung for %i milliseconds... attempting shutdown", tmp->GetZoneName(), diff); + + } + else + LogWrite(WORLD__ERROR, 1, "World", "Zone %s is hung for %i milliseconds.. attempting shutdown", tmp->GetZoneName(), diff); + tmp->Shutdown(); + } + else if (diff > 30000) + { + if (world.IsReloadingSubsystems()) { + if (world.GetSuppressedWarningTime() == 0) { + world.SetSuppressedWarning(); + map oldest_process = world.GetOldestReloadingSubsystem(); + if (oldest_process.size() > 0) { + map::iterator itr = oldest_process.begin(); + if(itr != oldest_process.end()) + LogWrite(WORLD__ERROR, 1, "World", "Zone %s is hung for %i milliseconds.. while waiting for %s to reload...", tmp->GetZoneName(), diff, itr->first); + else + LogWrite(WORLD__ERROR, 1, "World", "Zone %s is hung for %i milliseconds...", tmp->GetZoneName(), diff); + } + } + continue; + } + } + else if (diff > 60000 && !tmp->isZoneShuttingDown()) + { + if (world.IsReloadingSubsystems()) + continue; + LogWrite(WORLD__ERROR, 1, "World", "Zone %s is hung for %i milliseconds.. attempting shutdown", tmp->GetZoneName(), diff); + tmp->Shutdown(); + } + } + } + if(!match) + MZoneList.releasewritelock(__FUNCTION__, __LINE__); +} + +void World::LoadRegionMaps(std::string zoneFile) +{ + string zoneToLower(zoneFile); + boost::algorithm::to_lower(zoneToLower); + + MWorldRegionMaps.writelock(); + std::map::iterator itr; + itr = region_maps.find(zoneToLower); + if (itr == region_maps.end()) + { + RegionMapRange* newRange = new RegionMapRange(); + newRange->AddVersionRange(zoneFile); + + region_maps.insert(make_pair(zoneToLower, newRange)); + } + MWorldRegionMaps.releasewritelock(); +} + +RegionMap* World::GetRegionMap(std::string zoneFile, int32 client_version) +{ + string zoneToLower(zoneFile); + boost::algorithm::to_lower(zoneToLower); + + MWorldRegionMaps.readlock(); + std::map::iterator itr; + itr = region_maps.find(zoneToLower); + if ( itr != region_maps.end()) + { + std::map::iterator rmitr; + rmitr = itr->second->FindRegionByVersion(client_version); + if ( rmitr != itr->second->GetRangeEnd()) + { + MWorldRegionMaps.releasereadlock(); + return rmitr->second; + } + } + + MWorldRegionMaps.releasereadlock(); + return nullptr; +} + + +void World::LoadMaps(std::string zoneFile) +{ + string zoneToLower(zoneFile); + boost::algorithm::to_lower(zoneToLower); + + MWorldMaps.writelock(); + std::map::iterator itr; + itr = maps.find(zoneToLower); + if (itr == maps.end()) + { + MapRange* newRange = new MapRange(); + newRange->AddVersionRange(zoneFile); + + maps.insert(make_pair(zoneToLower, newRange)); + } + MWorldMaps.releasewritelock(); +} + +void World::RemoveMaps(std::string zoneFile) +{ + string zoneToLower(zoneFile); + boost::algorithm::to_lower(zoneToLower); + + MWorldMaps.writelock(); + std::map::iterator itr; + itr = maps.find(zoneToLower); + if (itr != maps.end()) + { + MapRange* range = itr->second; + maps.erase(itr); + MWorldMaps.releasewritelock(); + safe_delete(range); + } + else { + MWorldMaps.releasewritelock(); + } +} + +Map* World::GetMap(std::string zoneFile, int32 client_version) +{ + string zoneToLower(zoneFile); + boost::algorithm::to_lower(zoneToLower); + + MWorldMaps.readlock(); + std::map::iterator itr; + itr = maps.find(zoneToLower); + if ( itr != maps.end()) + { + std::map::iterator rmitr; + rmitr = itr->second->FindMapByVersion(client_version); + if ( rmitr != itr->second->GetRangeEnd()) + { + MWorldMaps.releasereadlock(); + return rmitr->second; + } + } + + MWorldMaps.releasereadlock(); + return nullptr; +} + +void World::SendTimeUpdate() +{ + zone_list.SendTimeUpdate(); +} + +void World::LoadVoiceOvers() +{ + LogWrite(WORLD__DEBUG, 1, "World", "-Loading `voiceovers`..."); + database.LoadVoiceOvers(this); +} + + +void World::PurgeVoiceOvers() +{ + MVoiceOvers.writelock(); + for(int type=0;type*>::iterator vos_itr; + + for (vos_itr = voiceover_map[type].begin(); vos_itr != voiceover_map[type].end(); vos_itr++) + { + multimap* tmpMap = vos_itr->second; + safe_delete(tmpMap); + } + voiceover_map[type].clear(); + } + MVoiceOvers.releasewritelock(); +} + + +bool World::FindVoiceOver(int8 type, int32 id, int16 index, VoiceOverStruct* struct_, bool* find_garbled, VoiceOverStruct* garble_struct_) { + // if we complete both requirements, based on struct_ and garble_struct_ being passed when required by ptr not being null + bool succeed = false; + if(type > MAX_VOICEOVER_TYPE) { + LogWrite(WORLD__ERROR, 0, "World", "Voice over %u out of range, max voiceover type is %u...", type, MAX_VOICEOVER_TYPE); + return succeed; + } + + MVoiceOvers.readlock(); + multimap*>::iterator itr = voiceover_map[type].find(id); + if(itr != voiceover_map[type].end()) { + std::pair result = itr->second->equal_range(index); + int count = std::distance(result.first, result.second); + bool tries_attempt = true; // abort out the while loop + bool non_garble_found = false; + int rand = 0; // use to randomize the voiceover selection + int pos = 0; + int tries = 0; + bool has_ungarbled = false; + bool has_garbled = false; + int8 garble_link_id = 0; // used to match ungarbled to garbled when the link id is set in the DB + while(tries_attempt) { + pos = 0; + rand = MakeRandomInt(0, count); + if ( tries == 3 || non_garble_found || (find_garbled && (*find_garbled))) + rand = 0; // override, too many tries, or we otherwise found one garbled/ungarbled lets try to link it + for (VOMapIterator it = result.first; it != result.second; it++) { + if(!it->second.is_garbled) { + has_ungarbled = true; + } + else { + has_garbled = true; + } + pos++; + + // if there is only 1 entry in the voiceover list we aren't going to bother skipping + if(count > 1 && pos < rand) { + continue; + } + if(!it->second.is_garbled && (garble_link_id == 0 || it->second.garble_link_id == garble_link_id)) { + garble_link_id = it->second.garble_link_id; + non_garble_found = true; + if(struct_) { + CopyVoiceOver(struct_, &it->second); + } + + if(!find_garbled || ((*find_garbled))) { + if(find_garbled) + *find_garbled = true; + tries_attempt = false; + succeed = true; + break; + } + } + else if(find_garbled && !(*find_garbled) && it->second.is_garbled && (garble_link_id == 0 || it->second.garble_link_id == garble_link_id)) { + *find_garbled = true; + garble_link_id = it->second.garble_link_id; + if(garble_struct_) { + CopyVoiceOver(garble_struct_, &it->second); + if(!struct_ || non_garble_found) { + tries_attempt = false; + succeed = true; + break; + } + } + } + } + tries++; + if(!tries_attempt || (tries > 0 && !has_ungarbled && (!find_garbled || *find_garbled == true || !has_garbled)) || tries > 3) + break; + } + } + MVoiceOvers.releasereadlock(); + + return succeed; +} + +void World::AddVoiceOver(int8 type, int32 id, int16 index, VoiceOverStruct* struct_) { + if(type > MAX_VOICEOVER_TYPE) { + LogWrite(WORLD__ERROR, 0, "World", "Voice over %u out of range, max voiceover type is %u...", type, MAX_VOICEOVER_TYPE); + return; + } + + VoiceOverStruct tmpStruct; + tmpStruct.mp3_string = std::string(struct_->mp3_string); + tmpStruct.text_string = std::string(struct_->text_string); + tmpStruct.emote_string = std::string(struct_->emote_string); + tmpStruct.key1 = struct_->key1; + tmpStruct.key2 = struct_->key2; + tmpStruct.is_garbled = struct_->is_garbled; + + MVoiceOvers.writelock(); + if(!voiceover_map[type].count(id)) + { + multimap* vo_struct = new multimap(); + vo_struct->insert(make_pair(index, tmpStruct)); + voiceover_map[type].insert(make_pair(id, vo_struct)); + } + else + { + multimap*>::const_iterator itr = voiceover_map[type].find(id); + itr->second->insert(make_pair(index, tmpStruct)); + } + MVoiceOvers.releasewritelock(); +} + +void World::CopyVoiceOver(VoiceOverStruct* struct1, VoiceOverStruct* struct2) { + if(!struct1 || !struct2) + return; + + struct1->mp3_string = std::string(struct2->mp3_string); + struct1->text_string = std::string(struct2->text_string); + struct1->emote_string = std::string(struct2->emote_string); + struct1->key1 = struct2->key1; + struct1->key2 = struct2->key2; + struct1->is_garbled = struct2->is_garbled; + struct1->garble_link_id = struct2->garble_link_id; +} + +void World::AddNPCSpell(int32 list_id, int32 spell_id, int8 tier, bool spawn_cast, bool aggro_cast, sint8 req_hp_ratio){ + std::unique_lock lock(MNPCSpells); + NPCSpell* npc_spell_struct = new NPCSpell; + npc_spell_struct->list_id = list_id; + npc_spell_struct->spell_id = spell_id; + npc_spell_struct->tier = tier; + npc_spell_struct->cast_on_spawn = spawn_cast; + npc_spell_struct->cast_on_initial_aggro = aggro_cast; + npc_spell_struct->required_hp_ratio = req_hp_ratio; + if(npc_spell_list.count(list_id) && npc_spell_list[list_id].count(spell_id)) { + map::iterator spell_itr = npc_spell_list[list_id].find(spell_id); + if(spell_itr != npc_spell_list[list_id].end()) { + safe_delete(spell_itr->second); + npc_spell_list[list_id].erase(spell_itr); + } + } + + npc_spell_list[list_id].insert(make_pair(spell_id, npc_spell_struct)); +} + +vector* World::GetNPCSpells(int32 primary_list, int32 secondary_list){ + std::shared_lock lock(MNPCSpells); + vector* ret = 0; + if(npc_spell_list.count(primary_list) > 0){ + ret = new vector(); + map::iterator itr; + Spell* tmpSpell = 0; + for(itr = npc_spell_list[primary_list].begin(); itr != npc_spell_list[primary_list].end(); itr++){ + tmpSpell = master_spell_list.GetSpell(itr->first, itr->second->tier); + if(tmpSpell) { + NPCSpell* addedSpell = new NPCSpell(itr->second); + ret->push_back(addedSpell); + } + } + } + if(npc_spell_list.count(secondary_list) > 0){ + if(!ret) + ret = new vector(); + map::iterator itr; + Spell* tmpSpell = 0; + for(itr = npc_spell_list[secondary_list].begin(); itr != npc_spell_list[secondary_list].end(); itr++){ + tmpSpell = master_spell_list.GetSpell(itr->first, itr->second->tier); + if(tmpSpell) { + NPCSpell* addedSpell = new NPCSpell(itr->second); + ret->push_back(addedSpell); + } + } + } + if(ret && ret->size() == 0){ + safe_delete(ret); + ret = 0; + } + return ret; +} + +void World::PurgeNPCSpells() { + std::unique_lock lock(MNPCSpells); + map >::iterator list_itr; + map::iterator spell_itr; + Spell* tmpSpell = 0; + for(list_itr = npc_spell_list.begin(); list_itr != npc_spell_list.end(); list_itr++) { + for(spell_itr = npc_spell_list[list_itr->first].begin(); spell_itr != npc_spell_list[list_itr->first].end(); spell_itr++){ + safe_delete(spell_itr->second); + } + } + + npc_spell_list.clear(); +} \ No newline at end of file diff --git a/source/WorldServer/World.h b/source/WorldServer/World.h new file mode 100644 index 0000000..b0a9527 --- /dev/null +++ b/source/WorldServer/World.h @@ -0,0 +1,729 @@ +/* + 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 . +*/ +#ifndef EQ2_WORLD_H +#define EQ2_WORLD_H + +#include +#include +#include +#include +#include +#include +#include +#include "SpawnLists.h" +#include "zoneserver.h" +#include "NPC.h" +#include "Widget.h" +#include "Object.h" +#include "GroundSpawn.h" +#include "Sign.h" +#include "Variables.h" +#include "MutexList.h" + +#include "PlayerGroups.h" +#include "../common/Web/WebServer.h" + +#include "./Zone/region_map.h" +#include "./Zone/map.h" + +using namespace std; +struct MerchantInfo{ + vector inventory_ids; + /*int32 faction_id; + sint32 faction_min; + sint32 faction_max; + float low_buy_multiplier; + float high_buy_multiplier; + float low_sell_multiplier; + float high_sell_multiplier;*/ +}; + +struct MerchantItemInfo{ + int32 item_id; + int16 quantity; + int32 price_item_id; + int32 price_item2_id; + int16 price_item_qty; + int16 price_item2_qty; + int32 price_status; + int64 price_coins; + int32 price_stationcash; +}; + +struct LootTable{ + string name; + int32 mincoin; + int32 maxcoin; + int16 maxlootitems; + float lootdrop_probability; + float coin_probability; +}; + +struct LootDrop{ + int32 item_id; + int16 item_charges; + bool equip_item; + float probability; + int32 no_drop_quest_completed_id; +}; + +struct GroundSpawnEntry { + int16 min_skill_level; + int16 min_adventure_level; + int8 bonus_table; + float harvest1; + float harvest3; + float harvest5; + float harvest_imbue; + float harvest_rare; + float harvest10; + int32 harvest_coin; +}; + +struct GroundSpawnEntryItem { + int32 item_id; + int8 is_rare; + int32 grid_id; +}; + +struct TransportDestination{ + int32 unique_id; + int8 type; + string display_name; + string message; + int32 destination_zone_id; + float destination_x; + float destination_y; + float destination_z; + float destination_heading; + int32 cost; + int8 min_level; + int8 max_level; + int32 req_quest; + int16 req_quest_step; + int32 req_quest_complete; + + int32 map_x; + int32 map_y; + int32 faction_id; + int32 faction_value; + + int32 expansion_flag; + int32 holiday_flag; + + int32 min_client_version; + int32 max_client_version; + + int32 flight_path_id; + + int16 mount_id; + int8 mount_red_color; + int8 mount_green_color; + int8 mount_blue_color; +}; + +struct LocationTransportDestination{ + int32 unique_id; + string message; + int32 destination_zone_id; + float destination_x; + float destination_y; + float destination_z; + float destination_heading; + float trigger_x; + float trigger_y; + float trigger_z; + float trigger_radius; + int32 cost; + int32 faction_id; + int32 faction_value; +}; + +struct LottoPlayer { + int32 end_time; + int8 num_matches; + bool set; +}; + +struct HouseZone { + int32 id; + string name; + int64 cost_coin; + int32 cost_status; + int64 upkeep_coin; + int32 upkeep_status; + int8 vault_slots; + int8 alignment; + int8 guild_level; + int32 zone_id; + int32 exit_zone_id; + float exit_x; + float exit_y; + float exit_z; + float exit_heading; +}; + +struct Deposit { + int32 timestamp; + int64 amount; + int64 last_amount; + int32 status; + int32 last_status; + string name; +}; + +struct HouseHistory { + HouseHistory() + { + timestamp = 0; + amount = 0; + name = string(""); + reason = string(""); + status = 0; + pos_flag = 0; + } + HouseHistory(int32 in_timestamp, int64 in_amount, string in_name, string in_reason, int32 in_status, int8 in_pos_flag) + { + timestamp = in_timestamp; + amount = in_amount; + name = in_name; + reason = in_reason; + status = in_status; + pos_flag = in_pos_flag; + } + int32 timestamp; + int64 amount; + string name; + string reason; + int32 status; + int8 pos_flag; +}; + +struct PlayerHouse { + int32 house_id; + int64 unique_id; + int32 instance_id; + int32 upkeep_due; + int64 escrow_coins; + int32 escrow_status; + string player_name; + list deposits; + map depositsMap; + list history; +}; + +// Constants for STATs counters + +// Server Utilization +#define STAT_SERVER_OS_TYPE 1 // what OS this server is running on +#define STAT_SERVER_CPU_TYPE 2 // cpu type/speed (ie., Intel P4 3.0GHz) +#define STAT_SERVER_CPU_CURRENT 3 // current CPU usage by EQ2World.exe process +#define STAT_SERVER_CPU_PEAK 4 // highest CPU usage by EQ2World.exe this session +#define STAT_SERVER_PHYSICAL_RAM_TOTAL 5 // total RAM in server +#define STAT_SERVER_PHYSICAL_RAM_CURRENT 6 // current RAM usage by EQ2World.exe +#define STAT_SERVER_PHYSICAL_RAM_PEAK 7 // highest RAM usage by EQ2World.exe this session +#define STAT_SERVER_VIRTUAL_RAM_TOTAL 8 // total vRAM in server +#define STAT_SERVER_VIRTUAL_RAM_CURRENT 9 // current vRAM usage by EQ2World.exe +#define STAT_SERVER_VIRTUAL_RAM_PEAK 10 // highest vRAM usage by EQ2World.exe this session +#define STAT_SERVER_DISK_USAGE 11 // size of /eq2emulator folder and contents +#define STAT_SERVER_THREAD_COUNT 12 // thread count of EQ2World.exe process +#define STAT_SERVER_AVG_LATENCY 13 // network latency between world and loginserver + +// Server Stats +#define STAT_SERVER_CREATED 100 // unix_timestamp of date server first came online +#define STAT_SERVER_START_TIME 101 // unix_timestamp of date/time server booted up +#define STAT_SERVER_ACCEPTED_CONNECTION 102 // successful connections since server startup +#define STAT_SERVER_MOST_CONNECTIONS 103 // most players online in history of server +#define STAT_SERVER_NUM_ACCOUNTS 104 // total number of unique accounts +#define STAT_SERVER_NUM_CHARACTERS 105 // total number of player characters +#define STAT_SERVER_AVG_CHAR_LEVEL 106 // average level of player characters +#define STAT_SERVER_NUM_ACTIVE_ZONES 107 // number of currently running/loaded zones +#define STAT_SERVER_NUM_ACTIVE_INSTANCES 108 // number of active zones that are "instances" + +// Player PvE counters +#define STAT_PLAYER_TOTAL_NPC_KILLS 1000 // total NPC kills by player +#define STAT_PLAYER_TOTAL_DEATHS 1001 // total non-PvP deaths of player +#define STAT_PLAYER_KVD_RATIO 1002 // kill-versus-death ratio of player +#define STAT_PLAYER_HIGHEST_MELEE_HIT 1003 // players highest melee hit to date +#define STAT_PLAYER_HIGHEST_MAGIC_HIT 1004 // players highest magic hit to date +#define STAT_PLAYER_HIGHEST_HO_HIT 1005 // players highest heroic opportunity hit +#define STAT_PLAYER_TOTAL_STATUS 1006 // player total status +#define STAT_PLAYER_TOTAL_WEALTH 1007 // player total wealth +#define STAT_PLAYER_QUESTS_COMPLETED 1008 // total quests completed +#define STAT_PLAYER_RECIPES_KNOWN 1009 // total recipes player knows +#define STAT_PLAYER_TOTAL_CRAFTED_ITEMS 1010 // total items crafted by player +#define STAT_PLAYER_ITEMS_DISCOVERED 1011 // total items discovered by player +#define STAT_PLAYER_RARES_HARVESTED 1012 // total rare harvests by player +#define STAT_PLAYER_ITEMS_HARVESTED 1013 // total rare harvests by player +#define STAT_PLAYER_MASTER_ABILITIES 1014 // total master abilities player has +#define STAT_PLAYER_HIGHEST_FALLING_HIT 1015 // player's highest damage amount taken from falling + +// Player PvP counters +#define STAT_PLAYER_TOTAL_PVP_KILLS 1100 // total PVP kills by player +#define STAT_PLAYER_PVP_KILL_STREAK 1101 // longest PVP kill streak of player +#define STAT_PLAYER_TOTAL_PVP_DEATHS 1102 // total PVP deaths of player +#define STAT_PLAYER_PVP_DEATH_STREAK 1103 // longest PVP death streak of player +#define STAT_PLAYER_PVP_KVD_RATIO 1104 // PVP kill-versus-death ratio of player +#define STAT_PLAYER_TOTAL_ARENA_KILLS 1105 // total Arena kills of player + +// MOST stats for players +#define STAT_PLAYER_MOST_NPC_KILLS 1200 // IPvP: Player with most NPC kills +#define STAT_PLAYER_MOST_NPC_DEATHS 1201 // IPvP: Player with most non-PVP deaths +#define STAT_PLAYER_MOST_PVP_KILLS 1202 // IPvP: Player with most PvP kills +#define STAT_PLAYER_MOST_PVP_DEATHS 1203 // IPvP: Player with most PvP deaths +#define STAT_PLAYER_MOST_ARENA_KILLS 1204 // IPvP: Player with most Arena kills +#define STAT_PLAYER_MOST_STATUS 1205 // IPvP: Player with most Status +#define STAT_PLAYER_MOST_WEALTH 1206 // IPvP: Player with most Wealth + +// HIGHEST stats for players +#define STAT_PLAYER_HIGHEST_NPC_KVD_RATIO 1300 // IPvP: Player with highest NPC kill-versus-death ratio +#define STAT_PLAYER_HIGHEST_PVP_KILL_STREAK 1301 // IPvP: Player with longest PvP kill streak +#define STAT_PLAYER_HIGHEST_PVP_DEATH_STREAK 1302 // IPvP: Player with longest PvP death streak +#define STAT_PLAYER_HIGHEST_PVP_KVD_RATIO 1303 // IPvP: Player with highest PvP kill-versus-death ratio +#define STAT_PLAYER_HIGHEST_HP 1304 // IPvP: Player with highest HP on server +#define STAT_PLAYER_HIGHEST_POWER 1305 // IPvP: Player with highest Power on server +#define STAT_PLAYER_HIGHEST_RESISTS 1306 // IPvP: Player with highest Resists on server + + +struct Statistic { + int32 stat_id; + sint32 stat_value; + int32 stat_date; + bool save_needed; +}; + + +// Player EVENT defines +// Some EVENTs are single occurrance (S), while others are cummulative throughout the life of the player (C) +#define PLAYER_EVENT_NEW_ADV_LEVEL 2000 // (C) player achieves a new adventure level +#define PLAYER_EVENT_NEW_TS_LEVEL 2001 // (C) player achieves a new tradeskill level +#define PLAYER_EVENT_NEW_AA 2002 // (C) player earns AA pt +#define PLAYER_EVENT_NEW_ACHIEVEMENT 2003 // (C) player new achievement +#define PLAYER_EVENT_LAST_DEATH 2004 // (S) player was last killed +#define PLAYER_EVENT_LAST_KILL 2005 // (S) player last killed spawn_id +#define PLAYER_EVENT_DISCOVER_POI 2006 // (C) player discovers location_id + +// These maybe should be World stat events, since it is about 1 player discovering a new item? +#define PLAYER_EVENT_DISCOVER_ITEM 2007 // (C) player discovers item_id +#define PLAYER_EVENT_DISCOVER_RECIPE 2008 // (C) player discovers recipe_id + + +struct PlayerHistory { + int32 history_zone; + int32 history_id; + sint32 history_value; + int32 history_date; + bool save_needed; +}; + +struct GlobalLoot { + int8 minLevel; + int8 maxLevel; + int32 table_id; + int32 loot_tier; +}; + +#define TRANSPORT_TYPE_LOCATION 0 +#define TRANSPORT_TYPE_ZONE 1 +#define TRANSPORT_TYPE_GENERIC 2 +#define TRANSPORT_TYPE_FLIGHT 3 + + +// structs MUST start with class_id and race_id, in that order as int8's +struct StartingStructHeader +{ + int8 class_id; + int8 race_id; +}; + +struct StartingSkill +{ + StartingStructHeader header; + int32 skill_id; + int16 current_val; + int16 max_val; + int32 progress; // what is this for..? +}; + +struct StartingSpell +{ + StartingStructHeader header; + int32 spell_id; + int8 tier; + int32 knowledge_slot; +}; + +#define MAX_VOICEOVER_TYPE 2 +struct VoiceOverStruct{ + string mp3_string; + string text_string; + string emote_string; + int32 key1; + int32 key2; + bool is_garbled; + int8 garble_link_id; +}; + +class ZoneList { + public: + ZoneList(); + ~ZoneList(); + + void Add(ZoneServer* zone); + void Remove(ZoneServer* zone); + ZoneServer* Get(int32 id, bool loadZone = true, bool skip_existing_zones = false, bool increment_zone = true); + ZoneServer* Get(const char* zone_name, bool loadZone=true, bool skip_existing_zones = false, bool increment_zone = true); + ZoneServer* GetByInstanceID(int32 id, int32 zone_id=0, bool skip_existing_zones = false, bool increment_zone = true); + + /// Get the instance for the given zone id with the lowest population + /// The id of the zone to look up + /// ZoneServer* of an active zone with the given id + ZoneServer* GetByLowestPopulation(int32 zone_id); + + int32 GetZonesPlayersCount(); + + void AddClientToMap(string name, Client* client){ + name = ToLower(name); + MClientList.lock(); + client_map[name] = client; + MClientList.unlock(); + } + void CheckFriendList(Client* client); + void CheckFriendZoned(Client* client); + + // move to Chat/Chat.h? + bool HandleGlobalChatMessage(Client* from, char* to, int16 channel, const char* message, const char* channel_name = 0, int32 current_language_id = 0); + void HandleGlobalBroadcast(const char* message); + void HandleGlobalAnnouncement(const char* message); + // + + int32 Count(); + Client* GetClientByCharName(string name){ + name = ToLower(name); + Client* ret = 0; + MClientList.lock(); + if(client_map.count(name) > 0) + ret = client_map[name]; + MClientList.unlock(); + return ret; + } + Client* GetClientByCharID(int32 id) { + Client* ret = 0; + MClientList.lock(); + map::iterator itr; + for (itr = client_map.begin(); itr != client_map.end(); itr++) { + if (itr->second->GetCharacterID() == id) { + ret = itr->second; + break; + } + } + MClientList.unlock(); + return ret; + } + Client* GetClientByEQStream(EQStream* eqs) { + Client* ret = 0; + if (eqs) { + MClientList.lock(); + map::iterator itr; + for (itr = client_map.begin(); itr != client_map.end(); itr++) { + if (itr->second->getConnection() == eqs) { + ret = itr->second; + break; + } + } + MClientList.unlock(); + } + return ret; + } + void UpdateVitality(float amount); + void RemoveClientFromMap(string name, Client* client){ + name = ToLower(name); + MClientList.lock(); + if(client_map.count(name) > 0 && client_map[name] == client) + client_map.erase(name); + MClientList.unlock(); + } + bool ClientConnected(int32 account_id); + void RemoveClientZoneReference(ZoneServer* zone); + void ReloadClientQuests(); + bool DepopFinished(); + void Depop(); + void Repop(); + void DeleteSpellProcess(); + void LoadSpellProcess(); + void ProcessWhoQuery(const char* query, Client* client); + void ProcessWhoQuery(vector* queries, ZoneServer* zone, vector* players, bool isGM); + void SendZoneList(Client* client); + void WritePlayerStatistics(); + void ShutDownZones(); + void ReloadMail(); + void ReloadSpawns(); + + void WatchdogHeartbeat(); + + void SendTimeUpdate(); + + void PopulateClientList(http::response& res); +private: + Mutex MClientList; + Mutex MZoneList; + map removed_zoneservers; + map client_map; + list zlist; +}; +class World { +public: + World(); + ~World(); + int8 GetClassID(const char* name); + void Process(); + void init(std::string web_ipaddr, int16 web_port, std::string cert_file, std::string key_file, std::string key_password, std::string hardcode_user, std::string hardcode_password); + PacketStruct* GetWorldTime(int16 version); + void WorldTimeTick(); + float GetXPRate(); + float GetTSXPRate(); + void LoadVitalityInformation(); + void UpdateVitality(); + WorldTime* GetWorldTimeStruct(){ + return &world_time; + } + ulong GetCurrentThreadID(); + int64 GetThreadUsageCPUTime(); + + // These 2 functions are never used. What was their purpose? Should they be removed? + void AddNPCAppearance(int32 id, AppearanceData* appearance){ npc_appearance_list[id] = appearance; } + AppearanceData* GetNPCAppearance(int32 id) { return npc_appearance_list[id]; } + + void ReloadGuilds(); + bool ReportBug(string data, char* player_name, int32 account_id, const char* spawn_name, int32 spawn_id, int32 zone_id); + void AddSpawnScript(int32 id, const char* name); + void AddSpawnEntryScript(int32 id, const char* name); + void AddSpawnLocationScript(int32 id, const char* name); + void AddZoneScript(int32 id, const char* name); + const char* GetSpawnScript(int32 id); + const char* GetSpawnEntryScript(int32 id); + const char* GetSpawnLocationScript(int32 id); + const char* GetZoneScript(int32 id); + void ResetSpawnScripts(); + void ResetZoneScripts(); + int16 GetMerchantItemQuantity(int32 merchant_id, int32 item_id); + void DecreaseMerchantQuantity(int32 merchant_id, int32 item_id, int16 amount); + int32 GetInventoryID(int32 merchant_id, int32 item_id); + void AddMerchantItem(int32 inventory_id, MerchantItemInfo ItemInfo); + void RemoveMerchantItem(int32 inventory_id, int32 item_id); + vector* GetMerchantList(int32 merchant_id); + vector* GetMerchantItemList(int32 merchant_id, int8 merchant_type, Player* player); + MerchantInfo* GetMerchantInfo(int32 merchant_id); + map* GetMerchantInfo(); + void AddMerchantInfo(int32 merchant_id, MerchantInfo* multiplier); + void DeleteMerchantsInfo(); + void DeleteMerchantItems(); + void DeleteSpawns(); + vector* GetClientVariables(); + void WritePlayerStatistics(); + void WriteServerStatistics(); + void AddServerStatistic(int32 stat_id, sint32 stat_value, int32 stat_date); + void UpdateServerStatistic(int32 stat_id, sint32 stat_value, bool overwrite = false); + sint32 GetServerStatisticValue(int32 stat_id); + void RemoveServerStatistics(); + + //PlayerGroup* AddGroup(Client* leader); + //void AddGroupMember(PlayerGroup* group, Client* member); + //void RemoveGroupMember(Client* member, bool immediate = false); + //void DisbandGroup(PlayerGroup* group, bool lock = true); + void SendGroupQuests(PlayerGroup* group, Client* client); + //void UpdateGroupBuffs(); + //void RemoveGroupBuffs(PlayerGroup *group, Client *client); + //void SetPendingGroup(char* name, char* leader); + //void GroupMessage(PlayerGroup* group, const char* message, ...); + //void SimpleGroupMessage(PlayerGroup* group, const char* message); + //void GroupChatMessage(PlayerGroup* group, Spawn* from, const char* message); + //const char* GetPendingGroup(string name); + //void GroupReadLock(); + //void GroupReadUnLock(); + //void CheckRemoveGroupedPlayer(); + //void SendGroupUpdate(PlayerGroup* group, Client* exclude = 0); + bool RejoinGroup(Client* client, int32 group_id); + //bool MakeLeader(Client* leader, string new_leader); + + void AddBonuses(Item* item, ItemStatsValues* values, int16 type, sint32 value, Entity* entity); + void CreateGuild(const char* guild_name, Client* leader = 0, int32 group_id = 0); + void SaveGuilds(); + void PickRandomLottoDigits(int32* digits); + void AddLottoPlayer(int32 character_id, int32 end_time); + void RemoveLottoPlayer(int32 character_id); + void SetLottoPlayerNumMatches(int32 character_id, int8 num_matches); + void CheckLottoPlayers(); + void PopulateTOVStatMap(); + int32 LoadItemBlueStats(); + sint16 GetItemStatAOMValue(sint16 subtype); + sint16 GetItemStatTOVValue(sint16 subtype); + sint16 GetItemStatDOVValue(sint16 subtype); + sint16 GetItemStatCOEValue(sint16 subtype); + sint16 GetItemStatKAValue(sint16 subtype); + sint16 GetItemStatTESTValue(sint16 subtype); + int8 TranslateSlotSubTypeToClient(Client* client, int8 stat_type, sint16 sub_type); + + vector biography; + + volatile bool items_loaded; + volatile bool spells_loaded; + volatile bool achievments_loaded; + + std::atomic world_loaded; + std::atomic world_uptime; + + void AddHouseZone(int32 id, string name, int64 cost_coins, int32 cost_status, int64 upkeep_coins, int32 upkeep_status, int8 vault_slots, int8 alignment, int8 guild_level, int32 zone_id, int32 exit_zone_id, float exit_x, float exit_y, float exit_z, float exit_heading); + HouseZone* GetHouseZone(int32 id); + + void AddPlayerHouse(int32 char_id, int32 house_id, int64 unique_id, int32 instance_id, int32 upkeep_due, int64 escrow_coins, int32 escrow_status, string player_name); + PlayerHouse* GetPlayerHouseByHouseID(int32 char_id, int32 house_id); + PlayerHouse* GetPlayerHouseByUniqueID(int64 unique_id); + PlayerHouse* GetPlayerHouseByInstanceID(int32 instance_id); + vector GetAllPlayerHouses(int32 char_id); + vector GetAllPlayerHousesByHouseID(int32 house_id); + PlayerHouse* GetPlayerHouse(Client* client, int32 spawn_id, int64 unique_house_id, HouseZone** set_house_zone); + + void ReloadHouseData(PlayerHouse* ph); + + PlayerGroupManager* GetGroupManager() { return &m_playerGroupManager; } + + bool CheckTempBugCRC(char* msg); + + void SyncCharacterAbilities(Client* client); + + void LoadStartingLists(); + void PurgeStartingLists(); + multimap*> starting_skills; + multimap*> starting_spells; + Mutex MStartingLists; + void SetReloadingSubsystem(string subsystem); + void RemoveReloadingSubSystem(string subsystem); + + bool IsReloadingSubsystems(); + int32 GetSuppressedWarningTime() { + return suppressed_warning; + } + void SetSuppressedWarning() { suppressed_warning = Timer::GetCurrentTime2(); } + map GetOldestReloadingSubsystem(); + + void LoadRegionMaps(std::string zoneFile); + RegionMap* GetRegionMap(std::string zoneFile, int32 client_version); + + void LoadMaps(std::string zoneFile); + void RemoveMaps(std::string zoneFile); + Map* GetMap(std::string zoneFile, int32 client_version); + + void SendTimeUpdate(); + // just in case we roll over a time as to not send bad times to clients (days before hours, hours before minutes as examples) + Mutex MWorldTime; + + void LoadVoiceOvers(); + void PurgeVoiceOvers(); + typedef std::multimap::iterator VOMapIterator; + bool FindVoiceOver(int8 type, int32 id, int16 index, VoiceOverStruct* struct_ = nullptr, bool* find_garbled = nullptr, VoiceOverStruct* garble_struct_ = nullptr); + void AddVoiceOver(int8 type, int32 id, int16 index, VoiceOverStruct* struct_); + void CopyVoiceOver(VoiceOverStruct* struct1, VoiceOverStruct* struct2); + + /* NPC Spells */ + void AddNPCSpell(int32 list_id, int32 spell_id, int8 tier, bool spawn_cast, bool aggro_cast, sint8 req_hp_ratio); + vector* GetNPCSpells(int32 primary_list, int32 secondary_list); + + void PurgeNPCSpells(); + + + static void Web_worldhandle_status(const http::request& req, http::response& res); + static void Web_worldhandle_clients(const http::request& req, http::response& res); + + Mutex MVoiceOvers; + + static sint64 newValue; +private: + multimap*> voiceover_map[3]; + int32 suppressed_warning = 0; + map reloading_subsystems; + //void RemovePlayerFromGroup(PlayerGroup* group, GroupMemberInfo* info, bool erase = true); + //void DeleteGroupMember(GroupMemberInfo* info); + Mutex MReloadingSubsystems; + Mutex MMerchantList; + Mutex MSpawnScripts; + Mutex MZoneScripts; + //Mutex MGroups; + + mutable std::shared_mutex MNPCSpells; + + map merchant_info; + map > merchant_inventory_items; + int32 vitality_frequency; + float vitality_amount; + float xp_rate; + float ts_xp_rate; // JA + WorldTime world_time; + + map npc_appearance_list; + + map spawn_scripts; + map spawnentry_scripts; + map spawnlocation_scripts; + map zone_scripts; + //vector player_groups; + //map group_removal_pending; + //map pending_groups; + map server_statistics; + MutexMap lotto_players; + int32 last_checked_time; + Timer save_time_timer; + Timer time_tick_timer; + Timer vitality_timer; + Timer player_stats_timer; + Timer server_stats_timer; + //Timer remove_grouped_player; + Timer guilds_timer; + Timer lotto_players_timer; + Timer group_buff_updates; + + Timer watchdog_timer; + + map m_houseZones; + // Map > + map > m_playerHouses; + Mutex MHouseZones; + Mutex MPlayerHouses; + + map tov_itemstat_conversion; + map dov_itemstat_conversion; + map coe_itemstat_conversion; + map ka_itemstat_conversion; + + PlayerGroupManager m_playerGroupManager; + + Mutex MBugReport; + map bug_report_crc; + + std::map region_maps; + std::map maps; + Mutex MWorldMaps; + Mutex MWorldRegionMaps; + + map > npc_spell_list; + + WebServer* world_webserver; +}; +#endif diff --git a/source/WorldServer/WorldDatabase.cpp b/source/WorldServer/WorldDatabase.cpp new file mode 100644 index 0000000..6641cbe --- /dev/null +++ b/source/WorldServer/WorldDatabase.cpp @@ -0,0 +1,8563 @@ +/* +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 . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "WorldDatabase.h" +#include "../common/debug.h" +#include "../common/packet_dump.h" +#include "../common/GlobalHeaders.h" +#include "Items/Items.h" +#include "Factions.h" +#include "World.h" +#include "Variables.h" +#include "VisualStates.h" +#include "Appearances.h" +#include "Skills.h" +#include "Quests.h" +#include "LuaInterface.h" +#include "classes.h" +#include "../common/Log.h" +#include "Rules/Rules.h" +#include "Titles.h" +#include "Languages.h" +#include "Traits/Traits.h" +#include "ClientPacketFunctions.h" +#include "Zone/ChestTrap.h" +#include "../common/version.h" +#include "SpellProcess.h" +#include "races.h" + + +extern Classes classes; +extern Commands commands; +extern MasterTitlesList master_titles_list; +extern MasterItemList master_item_list; +extern MasterSpellList master_spell_list; +extern MasterTraitList master_trait_list; +extern MasterFactionList master_faction_list; +extern World world; +extern Variables variables; +extern VisualStates visual_states; +extern Appearances master_appearance_list; +extern MasterSkillList master_skill_list; +extern MasterQuestList master_quest_list; +extern LuaInterface* lua_interface; +extern ZoneList zone_list; +extern GuildList guild_list; +extern MasterCollectionList master_collection_list; +extern RuleManager rule_manager; +extern MasterLanguagesList master_languages_list; +extern ChestTrapList chest_trap_list; + +//devn00b: Fix for linux builds since we dont use stricmp we use strcasecmp +#if defined(__GNUC__) +#define stricmp strcasecmp +#define strnicmp strncasecmp +#include +#endif + +WorldDatabase::WorldDatabase(){ +} + +WorldDatabase::~WorldDatabase(){ +} + +bool WorldDatabase::ConnectNewDatabase() { + /* + TESTS + + database_new.Connect(); + DatabaseResult result; + database_new.Select(&result, "select name from characters where id=1"); + if (result.Next()) { + printf("'%s'\n", result.GetStringStr("name")); + printf("'%s'\n", result.GetStringStr("nameBAD")); + printf("'%s'\n", result.GetString(3)); + } + return true; + */ + + return database_new.Connect(); +} + +void WorldDatabase::PingNewDB() +{ + database_new.PingNewDB(); +} + +void WorldDatabase::DeleteBuyBack(int32 char_id, int32 item_id, int16 quantity, int32 price) { + LogWrite(MERCHANT__DEBUG, 0, "Merchant", "Deleting Buyback - Player: %u, Item ID: %u, Qty: %i, Price: %u", char_id, item_id, quantity, price); + + Query query; + query.RunQuery2(Q_DELETE, "DELETE FROM character_buyback WHERE char_id = %u AND item_id = %u AND quantity = %i AND price = %u", char_id, item_id, quantity, price); +} + +void WorldDatabase::LoadBuyBacks(Client* client) { + LogWrite(MERCHANT__DEBUG, 0, "Merchant", "Loading Buyback - Player: %u", client->GetCharacterID()); + + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, item_id, quantity, price FROM character_buyback where char_id = %u ORDER BY id desc limit 10", client->GetCharacterID()); + int8 count = 0; + int32 last_id = 0; + if(result) + { + while(result && (row = mysql_fetch_row(result))) + { + LogWrite(MERCHANT__DEBUG, 5, "Merchant", "AddBuyBack: item: %u, qty: %i, price: %u", atoul(row[1]), atoi(row[2]), atoul(row[3])); + last_id = atoul(row[0]); + client->AddBuyBack(last_id, atoul(row[1]), atoi(row[2]), atoul(row[3]), false); + count++; + } + if(count >= 10) + { + LogWrite(MERCHANT__DEBUG, 0, "Merchant", "Deleting excess Buyback from Player: %u", client->GetCharacterID()); + Query query2; + query2.RunQuery2(Q_DELETE, "DELETE FROM character_buyback WHERE char_id = %u AND id < %u", client->GetCharacterID(), last_id); + } + } +} + +void WorldDatabase::SaveBuyBacks(Client* client) +{ + LogWrite(MERCHANT__DEBUG, 3, "Merchant", "Saving Buybacks - Player: %u", client->GetCharacterID()); + + deque* buybacks = client->GetBuyBacks(); + + if(buybacks && buybacks->size() > 0) + { + BuyBackItem* item = 0; + deque::iterator itr; + + for(itr = buybacks->begin(); itr != buybacks->end(); itr++) + { + item = *itr; + + if(item && item->save_needed) + { + LogWrite(MERCHANT__DEBUG, 5, "Merchant", "SaveBuyBack: char: %u, item: %u, qty: %i, price: %u", client->GetCharacterID(), item->item_id, item->quantity, item->price); + SaveBuyBack(client->GetCharacterID(), item->item_id, item->quantity, item->price); + item->save_needed = false; + } + } + } +} + +void WorldDatabase::SaveBuyBack(int32 char_id, int32 item_id, int16 quantity, int32 price) +{ + LogWrite(MERCHANT__DEBUG, 3, "Merchant", "Saving Buyback - Player: %u, Item ID: %u, Qty: %i, Price: %u", char_id, item_id, quantity, price); + + Query query; + string insert = string("INSERT INTO character_buyback (char_id, item_id, quantity, price) VALUES (%u, %u, %i, %u) "); + query.AddQueryAsync(char_id, this, Q_INSERT, insert.c_str(), char_id, item_id, quantity, price); +} + +int32 WorldDatabase::LoadCharacterSpells(int32 char_id, Player* player) +{ + LogWrite(SPELL__DEBUG, 0, "Spells", "Loading Character Spells for player %s...", player->GetName()); + + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT spell_id, tier, knowledge_slot, spell_book_type, linked_timer_id FROM character_spells, spells where character_spells.spell_id = spells.id and character_spells.char_id = %u ORDER BY spell_id, tier desc", char_id); + int32 old_spell_id = 0; + int32 new_spell_id = 0; + int32 count = 0; + + if(result && mysql_num_rows(result) >0) + { + while(result && (row = mysql_fetch_row(result))) + { + count++; + new_spell_id = atoul(row[0]); + + if(new_spell_id == old_spell_id) + continue; + + old_spell_id = new_spell_id; + + LogWrite(SPELL__DEBUG, 5, "Spells", "\tLoading SpellID: %u, tier: %i, slot: %i, type: %u linked_timer_id: %u", new_spell_id, atoi(row[1]), atoi(row[2]), atoul(row[3]), atoul(row[4])); + + int8 tier = atoi(row[1]); + + if (player->HasSpell(new_spell_id, tier, true)) + continue; + + player->AddSpellBookEntry(new_spell_id, tier, atoi(row[2]), atoul(row[3]), atoul(row[4])); + } + } + + return count; +} + +void WorldDatabase::SavePlayerSpells(Client* client) +{ + if(!client || client->GetCharacterID() < 1) + return; + + LogWrite(SPELL__DEBUG, 3, "Spells", "Saving Spell(s) for Player: '%s'", client->GetPlayer()->GetName()); + vector* spells = client->GetPlayer()->GetSpellsSaveNeeded(); + + if(spells) + { + vector::iterator itr; + SpellBookEntry* spell = 0; + + for(itr = spells->begin(); itr != spells->end(); itr++) + { + spell = *itr; + Query query; + LogWrite(SPELL__DEBUG, 5, "Spells", "\tSaving SpellID: %u, tier: %i, slot: %i", spell->spell_id, spell->tier, spell->slot); + query.AddQueryAsync(client->GetCharacterID(), this, Q_INSERT, "INSERT INTO character_spells (char_id, spell_id, tier) SELECT %u, %u, %i ON DUPLICATE KEY UPDATE tier = %i", + client->GetPlayer()->GetCharacterID(), spell->spell_id, spell->tier, spell->tier); + spell->save_needed = false; + } + safe_delete(spells); + } +} + +int32 WorldDatabase::LoadCharacterSkills(int32 char_id, Player* player) +{ + Query query; + MYSQL_ROW row; + int32 count = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT skill_id, current_val, max_val FROM character_skills, skills where character_skills.skill_id = skills.id and character_skills.char_id = %u", char_id); + + if(result && mysql_num_rows(result) >0) + { + while(result && (row = mysql_fetch_row(result))) + { + count++; + LogWrite(SKILL__DEBUG, 5, "Skills", "Loading SkillID: %u, cur_val: %i, max_val: %l", strtoul(row[0], NULL, 0), atoi(row[1]), atoi(row[2])); + player->AddSkill(strtoul(row[0], NULL, 0), atoi(row[1]), atoi(row[2])); + } + } + return count; +} + +void WorldDatabase::DeleteCharacterSkill(int32 char_id, Skill* skill) +{ + if (char_id > 0 && skill) + { + LogWrite(SKILL__DEBUG, 0, "Skills", "Deleting Skill '%s' (%u) from char_id: %u", skill->name.data.c_str(), skill->skill_id, char_id); + Query query; + query.RunQuery2(Q_DELETE, "DELETE FROM `character_skills` WHERE `char_id`=%u AND `skill_id`=%u", char_id, skill->skill_id); + } +} + +int32 WorldDatabase::LoadSkills() +{ + int32 total = 0; + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, short_name, name, description, skill_type, display FROM skills"); + + if(result) + { + if (mysql_num_rows(result) >0) + { + Skill* skill = 0; + + while(result && (row = mysql_fetch_row(result))) + { + skill = new Skill(); + skill->skill_id = strtoul(row[0], NULL, 0); + skill->short_name.data = string(row[1]); + skill->short_name.size = skill->short_name.data.length(); + skill->name.data = string(row[2]); + skill->name.size = skill->name.data.length(); + skill->description.data = string(row[3]); + skill->description.size = skill->description.data.length(); + skill->skill_type = strtoul(row[4], NULL, 0); + //these two need to be converted to the correct numbers + if(skill->skill_type == 13) + skill->skill_type = SKILL_TYPE_LANGUAGE; + else if(skill->skill_type == 12) + skill->skill_type = SKILL_TYPE_GENERAL; + + skill->display = atoi(row[5]); + master_skill_list.AddSkill(skill); + total++; + LogWrite(SKILL__DEBUG, 5, "Skill", "---Loading Skill: %s (%u)", skill->name.data.c_str(), skill->skill_id); + LogWrite(SKILL__DEBUG, 7, "Skill", "---short_name: %s, type: %i, display: %i", skill->short_name.data.c_str(), skill->skill_type, skill->display); + } + } + } + LogWrite(SKILL__DEBUG, 3, "Skill", "--Loaded %u Skill(s)", total); + return total; +} + +map >* WorldDatabase::LoadCharacterMacros(int32 char_id) +{ + Query query; + MYSQL_ROW row; + int32 total = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT macro_number, macro_name, macro_icon, macro_text FROM character_macros where char_id = %u ORDER BY macro_number, id", char_id); + + if(result && mysql_num_rows(result) >0) + { + map >* macros = new map >; + + while(result && (row = mysql_fetch_row(result))) + { + MacroData* data = new MacroData; + data->name = row[1]; + data->icon = atoi(row[2]); + data->text = row[3]; + (*macros)[atoi(row[0])].push_back(data); + total++; + LogWrite(PLAYER__DEBUG, 5, "Player", "\tLoading macro: %i. %s for player: %u", atoi(row[0]), row[1], char_id); + } + LogWrite(PLAYER__DEBUG, 0, "Player", "\tLoaded %u macro%s", total, total == 1 ? "" : "s"); + return macros; + } + return 0; +} + +void WorldDatabase::UpdateCharacterMacro(int32 char_id, int8 number, const char* name, int16 icon, vector* updates) +{ + LogWrite(PLAYER__DEBUG, 0, "Player", "Update player id %u macro: %i", char_id, number); + + Query query; + Query query2; + query.RunQuery2(Q_DELETE, "delete FROM character_macros where char_id = %u and macro_number = %i", char_id, number); + + if(name && updates && updates->size() > 0) + { + for(int8 i=0;isize();i++) + { + query2.RunQuery2(Q_INSERT, "insert into character_macros (char_id, macro_number, macro_name, macro_icon, macro_text) values(%u, %i, '%s', %i, '%s')", char_id, number, getSafeEscapeString(name).c_str(), icon, getSafeEscapeString(updates->at(i).c_str()).c_str()); + LogWrite(PLAYER__DEBUG, 5, "Player", "\tAdding macro: %s, %s (Player: %u)", name, updates->at(i).c_str(), char_id); + } + } +} + +//we use our timestamp just in case db is on another server, otherwise times might be off +void WorldDatabase::UpdateVitality(int32 timestamp, float amount){ + Query query, query2, query3; + + LogWrite(PLAYER__DEBUG, 3, "Player", "Reset Vitality > 100: %f", amount); + query.RunQuery2(Q_UPDATE, "update character_details set xp_vitality=100 where (xp_vitality + %f) > 100", amount); + + LogWrite(PLAYER__DEBUG, 3, "Player", "Update Vitality <= 100: %f", amount); + query2.RunQuery2(Q_UPDATE, "update character_details set xp_vitality=(xp_vitality+%f) where (xp_vitality + %f) <= 100", amount, amount); + + LogWrite(PLAYER__DEBUG, 3, "Player", "Update Vitality Timer: %u", timestamp); + query3.RunQuery2(Q_UPDATE, "update variables set variable_value=%u where variable_name='vitalitytimer'", timestamp); +} + +void WorldDatabase::SaveVariable(const char* name, const char* value, const char* comment){ + + LogWrite(WORLD__DEBUG, 0, "Variables", "Saving Variable: %s = %s", name, value); + Query query; + if(comment){ + query.RunQuery2(Q_REPLACE, "replace into variables (variable_name, variable_value, comment) values('%s', '%s', '%s')", + getSafeEscapeString(name).c_str(), getSafeEscapeString(value).c_str(), getSafeEscapeString(comment).c_str()); + } + else{ + query.RunQuery2(Q_REPLACE, "replace into variables (variable_name, variable_value) values('%s', '%s')", + getSafeEscapeString(name).c_str(), getSafeEscapeString(value).c_str()); + } +} + +void WorldDatabase::LoadGlobalVariables(){ + variables.ClearVariables ( ); + int32 total = 0; + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT variable_name, variable_value, comment FROM variables"); + + while(result && (row = mysql_fetch_row(result))) + { + Variable* newVar = new Variable(row[0], row[1], row[2]); + variables.AddVariable(newVar); + total++; + LogWrite(WORLD__DEBUG, 5, "World", "---Loading variable: '%s' = '%s'", row[0], row[1]); + } + LogWrite(WORLD__DEBUG, 3, "World", "--Loaded %u variables", total); +} + + +void WorldDatabase::LoadAppearanceMasterList() +{ + DatabaseResult result; + int32 total = 0; + int32 appearance_id; + int16 appearance_version; + + master_appearance_list.ClearAppearances(); + + database_new.Select(&result, "SELECT appearance_id, `name`, min_client_version FROM appearances ORDER BY appearance_id"); + + while( result.Next() ) + { + appearance_id = result.GetInt32Str("appearance_id"); + const char *appearance_name = result.GetStringStr("name"); + appearance_version = result.GetInt16Str("min_client_version"); + Appearance* a = new Appearance(appearance_id, appearance_name, appearance_version); + + master_appearance_list.InsertAppearance(a); + + total++; + LogWrite(WORLD__DEBUG, 5, "World", "---Loading appearances: '%s' (%i)", appearance_name, appearance_id); + } + LogWrite(WORLD__DEBUG, 3, "World", "--Loaded %u appearances", total); +} + + +void WorldDatabase::LoadVisualStates() +{ + visual_states.Reset(); + int32 total = 0; + Query query; + Query query2; + Query query3; + MYSQL_ROW row; + MYSQL_ROW row2; + MYSQL_ROW row3; + + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT visual_state_id, name FROM visual_states"); + while(result && (row = mysql_fetch_row(result))) + { + VisualState* vs = new VisualState(atoi(row[0]), row[1]); + visual_states.InsertVisualState(vs); + total++; + LogWrite(WORLD__DEBUG, 5, "World", "---Loading visual state: '%s' (%i)", row[1], atoi(row[0])); + } + LogWrite(WORLD__DEBUG, 3, "World", "--Loaded %u visual states", total); + + total = 0; + MYSQL_RES* result2 = query2.RunQuery2(Q_SELECT, "SELECT name, visual_state_id, message, targeted_message, min_version_range, max_version_range FROM emotes"); + while(result2 && (row2 = mysql_fetch_row(result2))) + { + EmoteVersionRange* range = 0; + if ((range = visual_states.FindEmoteRange(string(row2[0]))) == NULL) + { + range = new EmoteVersionRange(row2[0]); + visual_states.InsertEmoteRange(range); + } + + range->AddVersionRange(atoul(row2[4]),atoul(row2[5]), row2[0], atoul(row2[1]), row2[2], row2[3]); + total++; + LogWrite(WORLD__DEBUG, 5, "World", "---Loading emote state: '%s' (%i)", row2[0], atoul(row2[0])); + } + LogWrite(WORLD__DEBUG, 3, "World", "--Loaded %u emote state(s)", total); + + + total = 0; + MYSQL_RES* result3 = query3.RunQuery2(Q_SELECT, "SELECT name, spell_visual_id, alternate_spell_visual, min_version_range, max_version_range FROM spell_visuals"); + while(result3 && (row3 = mysql_fetch_row(result3))) + { + EmoteVersionRange* range = 0; + if ((range = visual_states.FindSpellVisualRange(string(row3[0]))) == NULL) + { + range = new EmoteVersionRange(row3[0]); + visual_states.InsertSpellVisualRange(range, atoul(row3[1])); + } + + range->AddVersionRange(atoul(row3[3]),atoul(row3[4]), row3[0], atoul(row3[1]), row3[2]); + total++; + LogWrite(WORLD__DEBUG, 5, "World", "---Loading spell visual state: '%s' (%u)", row3[1], atoul(row3[0])); + } + LogWrite(WORLD__DEBUG, 3, "World", "--Loaded %u spell visual state(s)", total); +} + +void WorldDatabase::LoadSubCommandList() +{ + int32 total = 0; + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT command, subcommand, handler, required_status FROM commands where length(subcommand) > 0 ORDER BY handler asc"); + while(result && (row = mysql_fetch_row(result))) + { + commands.GetRemoteCommands()->CheckAddSubCommand(string(row[0]), EQ2_RemoteCommandString(row[1], (int32)strtoul(row[2], NULL, 0), atoi(row[3]))); + total++; + LogWrite(COMMAND__DEBUG, 5, "Command", "---Loading Command: '%s', sub '%s', handler, %u status %i", row[0], row[1], atoul(row[2]), atoi(row[3])); + } + LogWrite(COMMAND__DEBUG, 3, "Command", "--Loaded %i Subcommand(s)", total); +} + +void WorldDatabase::LoadCommandList() +{ + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT command, handler, required_status FROM commands where length(subcommand) = 0 ORDER BY handler asc"); + int16 index = 0; + + while(result && (row = mysql_fetch_row(result))) + { + int32 handler = strtoul(row[1], NULL, 0); + while(handler>index && handler != 999) + { + LogWrite(COMMAND__DEBUG, 5, "Command", "---Loading Remote Commands: handler %u, index %u", handler, index); + commands.GetRemoteCommands()->addZero(); + index++; + } + LogWrite(COMMAND__DEBUG, 5, "Command", "---Loading Commands: handler %u, index %u", handler, index); + commands.GetRemoteCommands()->addCommand(EQ2_RemoteCommandString(row[0], handler, atoi(row[2]))); + index++; + } + LogWrite(COMMAND__DEBUG, 3, "Command", "--Loaded %i Command%s", index, index > 0 ? "s" : ""); + LoadSubCommandList(); +} + +int32 WorldDatabase::LoadNPCSpells(){ + Query query; + MYSQL_ROW row; + int32 count = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT spell_list_id, spell_id, spell_tier, on_spawn_cast, on_aggro_cast, required_hp_ratio FROM spawn_npc_spells where spell_list_id > 0"); + while(result && (row = mysql_fetch_row(result))){ + if(!row[0] || !row[1] || !row[2] || !row[3] || !row[4] || !row[5]) { + LogWrite(NPC__ERROR, 0, "NPC", "---Loading NPC Spell List: %u, found NULL values in entry, SKIP!", row[0] ? atoul(row[0]) : 0); + } + else { + world.AddNPCSpell(atoul(row[0]), atoul(row[1]), atoi(row[2]), atoul(row[3]), atoul(row[4]), atoi(row[5])); + count++; + + LogWrite(NPC__DEBUG, 5, "NPC", "---Loading NPC Spell List: %u, spell id: %u, tier: %i", atoul(row[0]), atoul(row[1]), atoi(row[2])); + } + + } + return count; +} + +int32 WorldDatabase::LoadNPCSkills(ZoneServer* zone){ + Query query; + MYSQL_ROW row; + int32 count = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT skill_list_id, skill_id, starting_value FROM spawn_npc_skills"); + while(result && (row = mysql_fetch_row(result))){ + zone->AddNPCSkill(atoul(row[0]), atoul(row[1]), atoi(row[2])); + count++; + + LogWrite(NPC__DEBUG, 5, "NPC", "---Loading NPC Skill List: %u, skill id: %u, value: %i", atoul(row[0]), atoul(row[1]), atoi(row[2])); + + } + return count; +} + +int32 WorldDatabase::LoadNPCEquipment(ZoneServer* zone){ + Query query; + MYSQL_ROW row; + int32 count = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT equipment_list_id, item_id FROM spawn_npc_equipment"); + while(result && (row = mysql_fetch_row(result))){ + zone->AddNPCEquipment(atoul(row[0]), atoul(row[1])); + count++; + + LogWrite(NPC__DEBUG, 5, "NPC", "---Loading NPC Equipment List: %u, item: %u", atoul(row[0]), atoul(row[1])); + + } + return count; +} + +int8 WorldDatabase::GetAppearanceType(string type){ + int8 ret = 255; + if (type == "soga_hair_face_highlight_color") + ret = APPEARANCE_SOGA_HFHC; + else if (type == "soga_hair_type_highlight_color") + ret = APPEARANCE_SOGA_HTHC; + else if (type == "soga_hair_face_color") + ret = APPEARANCE_SOGA_HFC; + else if (type == "soga_hair_type_color") + ret = APPEARANCE_SOGA_HTC; + else if (type == "soga_hair_highlight") + ret = APPEARANCE_SOGA_HH; + else if (type == "soga_hair_color1") + ret = APPEARANCE_SOGA_HC1; + else if (type == "soga_hair_color2") + ret = APPEARANCE_SOGA_HC2; + else if (type == "hair_type_color") + ret = APPEARANCE_HTC; + else if (type == "soga_skin_color") + ret = APPEARANCE_SOGA_SC; + else if (type == "soga_eye_color") + ret = APPEARANCE_SOGA_EC; + else if (type == "hair_type_highlight_color") + ret = APPEARANCE_HTHC; + else if (type == "hair_face_highlight_color") + ret = APPEARANCE_HFHC; + else if (type == "hair_face_color") + ret = APPEARANCE_HFC; + else if (type == "hair_highlight") + ret = APPEARANCE_HH; + else if (type == "hair_color1") + ret = APPEARANCE_HC1; + else if (type == "wing_color1") + ret = APPEARANCE_WC1; + else if (type == "hair_color2") + ret = APPEARANCE_HC2; + else if (type == "wing_color2") + ret = APPEARANCE_WC2; + else if (type == "skin_color") + ret = APPEARANCE_SC; + else if (type == "eye_color") + ret = APPEARANCE_EC; + else if (type == "soga_eye_brow_type") + ret = APPEARANCE_SOGA_EBT; + else if (type == "soga_cheek_type") + ret = APPEARANCE_SOGA_CHEEKT; + else if (type == "soga_nose_type") + ret = APPEARANCE_SOGA_NT; + else if (type == "soga_chin_type") + ret = APPEARANCE_SOGA_CHINT; + else if (type == "soga_lip_type") + ret = APPEARANCE_SOGA_LT; + else if (type == "eye_brow_type") + ret = APPEARANCE_EBT; + else if (type == "soga_ear_type") + ret = APPEARANCE_SOGA_EART; + else if (type == "soga_eye_type") + ret = APPEARANCE_SOGA_EYET; + else if (type == "cheek_type") + ret = APPEARANCE_CHEEKT; + else if (type == "nose_type") + ret = APPEARANCE_NT; + else if (type == "chin_type") + ret = APPEARANCE_CHINT; + else if (type == "ear_type") + ret = APPEARANCE_EART; + else if (type == "eye_type") + ret = APPEARANCE_EYET; + else if (type == "lip_type") + ret = APPEARANCE_LT; + else if (type == "shirt_color") + ret = APPEARANCE_SHIRT; + else if (type == "unknown_chest_color") + ret = APPEARANCE_UCC; + else if (type == "pants_color") + ret = APPEARANCE_PANTS; + else if (type == "unknown_legs_color") + ret = APPEARANCE_ULC; + else if (type == "unknown9") + ret = APPEARANCE_U9; + else if (type == "body_size") + ret = APPEARANCE_BODY_SIZE; + else if (type == "soga_wing_color1") + ret = APPEARANCE_SOGA_WC1; + else if (type == "soga_wing_color2") + ret = APPEARANCE_SOGA_WC2; + else if (type == "soga_shirt_color") + ret = APPEARANCE_SOGA_SHIRT; + else if (type == "soga_unknown_chest_color") + ret = APPEARANCE_SOGA_UCC; + else if (type == "soga_pants_color") + ret = APPEARANCE_SOGA_PANTS; + else if (type == "soga_unknown_legs_color") + ret = APPEARANCE_SOGA_ULC; + else if (type == "soga_unknown13") + ret = APPEARANCE_SOGA_U13; + else if (type == "body_age") + ret = APPEARANCE_BODY_AGE; + else if (type == "model_color") + ret = APPEARANCE_MC; + else if (type == "soga_model_color") + ret = APPEARANCE_SMC; + else if (type == "soga_body_size") + ret = APPEARANCE_SBS; + else if (type == "soga_body_age") + ret = APPEARANCE_SBA; + return ret; +} + +int32 WorldDatabase::LoadAppearances(ZoneServer* zone, Client* client){ + Query query, query2; + MYSQL_ROW row; + int32 count = 0, spawn_id = 0, new_spawn_id = 0; + Entity* entity = 0; + if(client) + entity = client->GetPlayer(); + map appearance_types; + map > appearance_colors; + EQ2_Color color; + color.red = 0; + color.green = 0; + color.blue = 0; + string type; + MYSQL_RES* result = 0; + if(!client) + result = query.RunQuery2(Q_SELECT, "SELECT distinct `type` FROM npc_appearance where length(type) > 0"); + else + result = query.RunQuery2(Q_SELECT, "SELECT distinct `type` FROM char_colors where length(type) > 0 and char_id=%u", client->GetCharacterID()); + while(result && (row = mysql_fetch_row(result))){ + type = string(row[0]); + appearance_types[type] = GetAppearanceType(type); + if(appearance_types[type] == 255) + LogWrite(WORLD__ERROR, 0, "Appearance", "Unknown appearance type '%s' in LoadAppearances.", type.c_str()); + } + + MYSQL_RES* result2 = 0; + if(!client) + result2 = query2.RunQuery2(Q_SELECT, "SELECT `type`, spawn_id, signed_value, red, green, blue FROM npc_appearance where length(type) > 0 ORDER BY spawn_id"); + else + result2 = query2.RunQuery2(Q_SELECT, "SELECT `type`, char_id, signed_value, red, green, blue FROM char_colors where length(type) > 0 and char_id=%u", client->GetCharacterID()); + while(result2 && (row = mysql_fetch_row(result2))){ + if(!client){ + new_spawn_id = atoul(row[1]); + if(spawn_id != new_spawn_id){ + entity = zone->GetNPC(new_spawn_id, true); + if(!entity) + continue; + if(spawn_id > 0) + count++; + spawn_id = new_spawn_id; + } + } + if(appearance_types[row[0]] < APPEARANCE_SOGA_EBT){ + color.red = atoi(row[3]); + color.green = atoi(row[4]); + color.blue = atoi(row[5]); + } + switch(appearance_types[row[0]]){ + case APPEARANCE_SOGA_HFHC:{ + entity->features.soga_hair_face_highlight_color = color; + break; + } + case APPEARANCE_SOGA_HTHC:{ + entity->features.soga_hair_type_highlight_color = color; + break; + } + case APPEARANCE_SOGA_HFC:{ + entity->features.soga_hair_face_color = color; + break; + } + case APPEARANCE_SOGA_HTC:{ + entity->features.soga_hair_type_color = color; + break; + } + case APPEARANCE_SOGA_HH:{ + entity->features.soga_hair_highlight_color = color; + break; + } + case APPEARANCE_SOGA_HC1:{ + entity->features.soga_hair_color1 = color; + break; + } + case APPEARANCE_SOGA_HC2:{ + entity->features.soga_hair_color2 = color; + break; + } + case APPEARANCE_SOGA_SC:{ + entity->features.soga_skin_color = color; + break; + } + case APPEARANCE_SOGA_EC:{ + entity->features.soga_eye_color = color; + break; + } + case APPEARANCE_HTHC:{ + entity->features.hair_type_highlight_color = color; + break; + } + case APPEARANCE_HFHC:{ + entity->features.hair_face_highlight_color = color; + break; + } + case APPEARANCE_HTC:{ + entity->features.hair_type_color = color; + break; + } + case APPEARANCE_HFC:{ + entity->features.hair_face_color = color; + break; + } + case APPEARANCE_HH:{ + entity->features.hair_highlight_color = color; + break; + } + case APPEARANCE_HC1:{ + entity->features.hair_color1 = color; + break; + } + case APPEARANCE_HC2:{ + entity->features.hair_color2 = color; + break; + } + case APPEARANCE_WC1:{ + entity->features.wing_color1 = color; + break; + } + case APPEARANCE_WC2:{ + entity->features.wing_color2 = color; + break; + } + case APPEARANCE_SC:{ + entity->features.skin_color = color; + break; + } + case APPEARANCE_EC:{ + entity->features.eye_color = color; + break; + } + case APPEARANCE_SOGA_EBT:{ + for(int i=0;i<3;i++) + entity->features.soga_eye_brow_type[i] = atoi(row[3+i]); + break; + } + case APPEARANCE_SOGA_CHEEKT:{ + for(int i=0;i<3;i++) + entity->features.soga_cheek_type[i] = atoi(row[3+i]); + break; + } + case APPEARANCE_SOGA_NT:{ + for(int i=0;i<3;i++) + entity->features.soga_nose_type[i] = atoi(row[3+i]); + break; + } + case APPEARANCE_SOGA_CHINT:{ + for(int i=0;i<3;i++) + entity->features.soga_chin_type[i] = atoi(row[3+i]); + break; + } + case APPEARANCE_SOGA_LT:{ + for(int i=0;i<3;i++) + entity->features.soga_lip_type[i] = atoi(row[3+i]); + break; + } + case APPEARANCE_SOGA_EART:{ + for(int i=0;i<3;i++) + entity->features.soga_ear_type[i] = atoi(row[3+i]); + break; + } + case APPEARANCE_SOGA_EYET:{ + for(int i=0;i<3;i++) + entity->features.soga_eye_type[i] = atoi(row[3+i]); + break; + } + case APPEARANCE_EBT:{ + for(int i=0;i<3;i++) + entity->features.eye_brow_type[i] = atoi(row[3+i]); + break; + } + case APPEARANCE_CHEEKT:{ + for(int i=0;i<3;i++) + entity->features.cheek_type[i] = atoi(row[3+i]); + break; + } + case APPEARANCE_NT:{ + for(int i=0;i<3;i++) + entity->features.nose_type[i] = atoi(row[3+i]); + break; + } + case APPEARANCE_CHINT:{ + for(int i=0;i<3;i++) + entity->features.chin_type[i] = atoi(row[3+i]); + break; + } + case APPEARANCE_EART:{ + for(int i=0;i<3;i++) + entity->features.ear_type[i] = atoi(row[3+i]); + break; + } + case APPEARANCE_EYET:{ + for(int i=0;i<3;i++) + entity->features.eye_type[i] = atoi(row[3+i]); + break; + } + case APPEARANCE_LT:{ + for(int i=0;i<3;i++) + entity->features.lip_type[i] = atoi(row[3+i]); + break; + } + case APPEARANCE_SHIRT:{ + entity->features.shirt_color = color; + break; + } + case APPEARANCE_UCC:{ + break; + } + case APPEARANCE_PANTS:{ + entity->features.pants_color = color; + break; + } + case APPEARANCE_ULC:{ + break; + } + case APPEARANCE_U9:{ + break; + } + case APPEARANCE_BODY_SIZE:{ + entity->features.body_size = color.red; + break; + } + case APPEARANCE_SOGA_WC1:{ + break; + } + case APPEARANCE_SOGA_WC2:{ + break; + } + case APPEARANCE_SOGA_SHIRT:{ + break; + } + case APPEARANCE_SOGA_UCC:{ + break; + } + case APPEARANCE_SOGA_PANTS:{ + break; + } + case APPEARANCE_SOGA_ULC:{ + break; + } + case APPEARANCE_SOGA_U13:{ + break; + } + case APPEARANCE_BODY_AGE: { + entity->features.body_age = color.red; + break; + } + case APPEARANCE_MC:{ + entity->features.model_color = color; + break; + } + case APPEARANCE_SMC:{ + entity->features.soga_model_color = color; + break; + } + case APPEARANCE_SBS: { + entity->features.soga_body_size = color.red; + break; + } + case APPEARANCE_SBA: { + entity->features.soga_body_age = color.red; + break; + } + } + entity->info_changed = true; + } + return count; +} + +void WorldDatabase::LoadNPCs(ZoneServer* zone){ + Query query; + MYSQL_ROW row; + NPC* npc = 0; + int32 id = 0; + int32 total = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT,"SELECT npc.spawn_id, s.name, npc.min_level, npc.max_level, npc.enc_level, s.race, s.model_type, npc.class_, npc.gender, s.command_primary, s.command_secondary, s.show_name, npc.min_group_size, npc.max_group_size, npc.hair_type_id, npc.facial_hair_type_id, npc.wing_type_id, npc.chest_type_id, npc.legs_type_id, npc.soga_hair_type_id, npc.soga_facial_hair_type_id, s.attackable, s.show_level, s.targetable, s.show_command_icon, s.display_hand_icon, s.hp, s.power, s.size, s.collision_radius, npc.action_state, s.visual_state, npc.mood_state, npc.initial_state, npc.activity_status, s.faction_id, s.sub_title, s.merchant_id, s.merchant_type, s.size_offset, npc.attack_type, npc.ai_strategy+0, npc.spell_list_id, npc.secondary_spell_list_id, npc.skill_list_id, npc.secondary_skill_list_id, npc.equipment_list_id, npc.str, npc.sta, npc.wis, npc.intel, npc.agi, npc.heat, npc.cold, npc.magic, npc.mental, npc.divine, npc.disease, npc.poison, npc.aggro_radius, npc.cast_percentage, npc.randomize, npc.soga_model_type, npc.heroic_flag, npc.alignment, npc.elemental, npc.arcane, npc.noxious, s.savagery, s.dissonance, npc.hide_hood, npc.emote_state, s.prefix, s.suffix, s.last_name, s.expansion_flag, s.holiday_flag, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, npc.water_type, npc.flying_type, s.loot_tier, s.loot_drop_type, npc.scared_by_strong_players, npc.action_state_str\n" + "FROM spawn s\n" + "INNER JOIN spawn_npcs npc\n" + "ON s.id = npc.spawn_id\n" + "INNER JOIN spawn_location_entry le\n" + "ON npc.spawn_id = le.spawn_id\n" + "INNER JOIN spawn_location_placement lp\n" + "ON le.spawn_location_id = lp.spawn_location_id\n" + "WHERE lp.zone_id = %u and (lp.instance_id = 0 or lp.instance_id = %u)\n" + "GROUP BY s.id", + zone->GetZoneID(), zone->GetInstanceID()); + while(result && (row = mysql_fetch_row(result))){ + /*npc->SetAppearanceID(atoi(row[12])); + AppearanceData* appearance = world.GetNPCAppearance(npc->GetAppearanceID()); + if(appearance) + memcpy(&npc->appearance, appearance, sizeof(AppearanceData)); + */ + int32 npcXpackFlag = atoul(row[75]); + int32 npcHolidayFlag = atoul(row[76]); + + id = atoul(row[0]); + if(zone->GetNPC(id, true)) + continue; + npc = new NPC(); + + if (!CheckExpansionFlags(zone, npcXpackFlag) || !CheckHolidayFlags(zone, npcHolidayFlag)) + npc->SetOmittedByDBFlag(true); + + npc->SetDatabaseID(id); + strcpy(npc->appearance.name, row[1]); + vector* primary_command_list = zone->GetEntityCommandList(atoul(row[9])); + vector* secondary_command_list = zone->GetEntityCommandList(atoul(row[10])); + if(primary_command_list){ + npc->SetPrimaryCommands(primary_command_list); + npc->primary_command_list_id = atoul(row[9]); + } + if(secondary_command_list){ + npc->SetSecondaryCommands(secondary_command_list); + npc->secondary_command_list_id = atoul(row[10]); + } + npc->appearance.min_level = atoi(row[2]); + npc->appearance.max_level = atoi(row[3]); + npc->appearance.level = atoi(row[2]); + npc->appearance.difficulty = atoi(row[4]); + npc->appearance.race = atoi(row[5]); + npc->appearance.model_type = atoi(row[6]); + npc->appearance.soga_model_type = atoi(row[62]); + npc->appearance.adventure_class = atoi(row[7]); + npc->appearance.gender = atoi(row[8]); + npc->appearance.display_name = atoi(row[11]); + npc->features.hair_type = atoi(row[14]); + npc->features.hair_face_type = atoi(row[15]); + npc->features.wing_type = atoi(row[16]); + npc->features.chest_type = atoi(row[17]); + npc->features.legs_type = atoi(row[18]); + npc->features.soga_hair_type = atoi(row[19]); + npc->features.soga_hair_face_type = atoi(row[20]); + npc->appearance.attackable = atoi(row[21]); + npc->appearance.show_level = atoi(row[22]); + npc->appearance.targetable = atoi(row[23]); + npc->appearance.show_command_icon = atoi(row[24]); + npc->appearance.display_hand_icon = atoi(row[25]); + npc->appearance.hide_hood = atoi(row[70]); + npc->appearance.randomize = atoi(row[61]); + npc->SetTotalHP(atoul(row[26])); + npc->SetTotalHPBaseInstance(atoul(row[26])); + npc->SetTotalPower(atoul(row[27])); + npc->SetTotalPowerBaseInstance(atoul(row[27])); + npc->SetHP(npc->GetTotalHP()); + npc->SetPower(npc->GetTotalPower()); + if(npc->GetTotalHP() == 0){ + npc->SetTotalHP(15*npc->GetLevel() + 1); + npc->SetHP(15*npc->GetLevel() + 1); + } + if(npc->GetTotalPower() == 0){ + npc->SetTotalPower(15*npc->GetLevel() + 1); + npc->SetPower(15*npc->GetLevel() + 1); + } + npc->size = atoi(row[28]); + npc->appearance.pos.collision_radius = atoi(row[29]); + npc->appearance.action_state = atoi(row[30]); + npc->appearance.visual_state = atoi(row[31]); + npc->appearance.mood_state = atoi(row[32]); + npc->appearance.emote_state = atoi(row[71]); + npc->appearance.pos.state = atoi(row[33]); + npc->appearance.activity_status = atoi(row[34]); + npc->faction_id = atoul(row[35]); + if(row[36]){ + std::string sub_title = std::string(row[36]); + if(strncmp(row[36],"", 11) == 0) { + npc->SetCollector(true); + } + if(strlen(row[36]) < sizeof(npc->appearance.sub_title)) + strcpy(npc->appearance.sub_title, row[36]); + else + strncpy(npc->appearance.sub_title, row[36], sizeof(npc->appearance.sub_title)); + } + npc->SetMerchantID(atoul(row[37])); + npc->SetMerchantType(atoi(row[38])); + npc->SetSizeOffset(atoi(row[39])); + npc->SetAIStrategy(atoi(row[41])); + npc->SetPrimarySpellList(atoul(row[42])); + npc->SetSecondarySpellList(atoul(row[43])); + npc->SetPrimarySkillList(atoul(row[44])); + npc->SetSecondarySkillList(atoul(row[45])); + npc->SetEquipmentListID(atoul(row[46])); + + InfoStruct* info = npc->GetInfoStruct(); + info->set_attack_type(atoi(row[40])); + info->set_str_base(atoi(row[47])); + info->set_sta_base(atoi(row[48])); + info->set_wis_base(atoi(row[49])); + info->set_intel_base(atoi(row[50])); + info->set_agi_base(atoi(row[51])); + info->set_heat_base(atoi(row[52])); + info->set_cold_base(atoi(row[53])); + info->set_magic_base(atoi(row[54])); + info->set_mental_base(atoi(row[55])); + info->set_divine_base(atoi(row[56])); + info->set_disease_base(atoi(row[57])); + info->set_poison_base(atoi(row[58])); + info->set_alignment(atoi(row[64])); + + npc->SetAggroRadius(atof(row[59])); + npc->SetCastPercentage(atoi(row[60])); + npc->appearance.heroic_flag = atoi(row[63]); + + info->set_elemental_base(atoi(row[65])); + info->set_arcane_base(atoi(row[66])); + info->set_noxious_base(atoi(row[67])); + npc->SetTotalSavagery(atoul(row[68])); + npc->SetTotalDissonance(atoul(row[69])); + npc->SetSavagery(npc->GetTotalSavagery()); + npc->SetDissonance(npc->GetTotalDissonance()); + if(npc->GetTotalSavagery() == 0){ + npc->SetTotalSavagery(15*npc->GetLevel() + 1); + npc->SetSavagery(15*npc->GetLevel() + 1); + } + if(npc->GetTotalDissonance() == 0){ + npc->SetTotalDissonance(15*npc->GetLevel() + 1); + npc->SetDissonance(15*npc->GetLevel() + 1); + } + npc->SetPrefixTitle(row[72]); + npc->SetSuffixTitle(row[73]); + npc->SetLastName(row[74]); + + // xpack+holiday value handled at top at position 75+76 + + int8 disableSounds = atoul(row[77]); + npc->SetSoundsDisabled(disableSounds); + + npc->SetMerchantLevelRange(atoul(row[78]), atoul(row[79])); + + npc->SetAAXPRewards(atoul(row[80])); + + info->set_water_type(atoul(row[81])); + info->set_flying_type(atoul(row[82])); + + npc->SetLootTier(atoul(row[83])); + + npc->SetLootDropType(atoul(row[84])); + + npc->SetScaredByStrongPlayers(atoul(row[85])); + + if(row[86]){ + std::string action_state_str = std::string(row[86]); + npc->GetInfoStruct()->set_action_state(action_state_str); + } + + zone->AddNPC(id, npc); + total++; + LogWrite(NPC__DEBUG, 5, "NPC", "---Loading NPC: '%s' (%u)", npc->appearance.name, id); + } + LogWrite(NPC__INFO, 0, "NPC", "--Loaded %i NPC(s).", total); + LogWrite(NPC__INFO, 0, "NPC", "--Loaded %i NPC Skill(s).", LoadNPCSkills(zone)); + LogWrite(NPC__INFO, 0, "NPC", "--Loaded %i NPC Equipment Piece(s).", LoadNPCEquipment(zone)); + LogWrite(NPC__INFO, 0, "NPC", "--Loaded %i NPC Appearance(s).", LoadAppearances(zone)); + LogWrite(NPC__INFO, 0, "NPC", "--Loaded %i NPC Equipment Appearance(s).", LoadNPCAppearanceEquipmentData(zone)); +} + + +void WorldDatabase::LoadSpiritShards(ZoneServer* zone){ + Query query; + MYSQL_ROW row; + int32 id = 0; + int32 total = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT,"SELECT timestamp, name, level, race, gender, adventure_class, model_type, soga_model_type, hair_type, hair_face_type, wing_type, chest_type, legs_type, soga_hair_type, soga_hair_face_type, hide_hood, size, collision_radius, action_state, visual_state, mood_state, emote_state, pos_state, activity_status, sub_title, prefix_title, suffix_title, lastname, x, y, z, heading, gridid, id, charid\n" + "FROM character_spirit_shards\n" + "WHERE zoneid = %u and (instanceid = 0 or instanceid = %u)", + zone->GetZoneID(), zone->GetInstanceID()); + while(result && (row = mysql_fetch_row(result))){ + /*npc->SetAppearanceID(atoi(row[12])); + AppearanceData* appearance = world.GetNPCAppearance(npc->GetAppearanceID()); + if(appearance) + memcpy(&npc->appearance, appearance, sizeof(AppearanceData)); + */ + sint64 timestamp = 0; +#ifdef WIN32 + timestamp = _strtoui64(row[0], NULL, 10); +#else + timestamp = strtoull(row[0], 0, 10); +#endif + + if(!row[1]) + continue; + + NPC* shard = new NPC(); + + shard->SetShardCreatedTimestamp(timestamp); + strcpy(shard->appearance.name, row[1]); + + shard->appearance.level = atoul(row[2]); + shard->appearance.race = atoul(row[3]); + shard->appearance.gender = atoul(row[4]); + shard->appearance.adventure_class = atoul(row[5]); + shard->appearance.model_type = atoul(row[6]); + shard->appearance.soga_model_type = atoul(row[7]); + shard->appearance.display_name = 1; + shard->features.hair_type = atoul(row[8]); + shard->features.hair_face_type = atoul(row[9]); + shard->features.wing_type = atoul(row[10]); + shard->features.chest_type = atoul(row[11]); + shard->features.legs_type = atoul(row[12]); + shard->features.soga_hair_type = atoul(row[13]); + shard->features.soga_hair_face_type = atoul(row[14]); + shard->appearance.attackable = 0; + shard->appearance.show_level = 1; + shard->appearance.targetable = 1; + shard->appearance.show_command_icon = 1; + shard->appearance.display_hand_icon = 0; + shard->appearance.hide_hood = atoul(row[15]); + shard->size = atoul(row[16]); + shard->appearance.pos.collision_radius = atoul(row[17]); + shard->appearance.action_state = atoul(row[18]); + shard->appearance.visual_state = atoul(row[19]); // ghostly look + shard->appearance.mood_state = atoul(row[20]); + shard->appearance.emote_state = atoul(row[21]); + shard->appearance.pos.state = atoul(row[22]); + shard->appearance.activity_status = atoul(row[23]); + + if(row[24]) + strncpy(shard->appearance.sub_title, row[24], sizeof(shard->appearance.sub_title)); + + if(row[25]) + shard->SetPrefixTitle(row[25]); + + if(row[26]) + shard->SetSuffixTitle(row[26]); + + if(row[27]) + shard->SetLastName(row[27]); + + shard->SetX(atof(row[28])); + shard->SetY(atof(row[29])); + shard->SetZ(atof(row[30])); + shard->SetHeading(atof(row[31])); + shard->SetSpawnOrigX(shard->GetX()); + shard->SetSpawnOrigY(shard->GetY()); + shard->SetSpawnOrigZ(shard->GetZ()); + shard->SetSpawnOrigHeading(shard->GetHeading()); + shard->SetLocation(atoul(row[32])); + shard->SetShardID(atoul(row[33])); + shard->SetShardCharID(atoul(row[34])); + shard->SetAlive(false); + + const char* script = rule_manager.GetGlobalRule(R_Combat, SpiritShardSpawnScript)->GetString(); + + if(script) + { + shard->SetSpawnScript(script); + zone->CallSpawnScript(shard, SPAWN_SCRIPT_PRESPAWN); + } + + zone->AddSpawn(shard); + + if(script) + zone->CallSpawnScript(shard, SPAWN_SCRIPT_SPAWN); + + total++; + LogWrite(NPC__DEBUG, 5, "NPC", "---Loading Player Spirit Shard: '%s' (%u)", shard->appearance.name, id); + } + LogWrite(NPC__INFO, 0, "NPC", "--Loaded %i Spirit Shard(s).", total); +} + +void WorldDatabase::LoadSigns(ZoneServer* zone){ + Query query; + MYSQL_ROW row; + Sign* sign = 0; + int32 id = 0; + int32 total = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT ss.spawn_id, s.name, s.model_type, s.size, s.show_command_icon, ss.widget_id, ss.widget_x, ss.widget_y, ss.widget_z, s.command_primary, s.command_secondary, s.collision_radius, ss.icon, ss.type, ss.title, ss.description, ss.sign_distance, ss.zone_id, ss.zone_x, ss.zone_y, ss.zone_z, ss.zone_heading, ss.include_heading, ss.include_location, s.transport_id, s.size_offset, s.display_hand_icon, s.visual_state, s.expansion_flag, s.holiday_flag, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, s.loot_tier, s.loot_drop_type, ss.language\n" + "FROM spawn s\n" + "INNER JOIN spawn_signs ss\n" + "ON s.id = ss.spawn_id\n" + "INNER JOIN spawn_location_entry le\n" + "ON ss.spawn_id = le.spawn_id\n" + "INNER JOIN spawn_location_placement lp\n" + "ON le.spawn_location_id = lp.spawn_location_id\n" + "WHERE lp.zone_id = %u\n" + "GROUP BY s.id", + zone->GetZoneID()); + + while(result && (row = mysql_fetch_row(result))){ + int32 signXpackFlag = atoul(row[28]); + int32 signHolidayFlag = atoul(row[29]); + + id = atoul(row[0]); + if(zone->GetSign(id, true)) + continue; + sign = new Sign(); + + if (!CheckExpansionFlags(zone, signXpackFlag) || !CheckHolidayFlags(zone, signHolidayFlag)) + sign->SetOmittedByDBFlag(true); + + sign->SetDatabaseID(id); + strcpy(sign->appearance.name, row[1]); + sign->appearance.model_type = atoi(row[2]); + sign->SetSize(atoi(row[3])); + sign->appearance.show_command_icon = atoi(row[4]); + sign->SetWidgetID(atoul(row[5])); + sign->SetWidgetX(atof(row[6])); + sign->SetWidgetY(atof(row[7])); + sign->SetWidgetZ(atof(row[8])); + vector* primary_command_list = zone->GetEntityCommandList(atoul(row[9])); + if(primary_command_list){ + sign->SetPrimaryCommands(primary_command_list); + sign->primary_command_list_id = atoul(row[9]); + } + + vector* secondary_command_list = zone->GetEntityCommandList(atoul(row[10])); + if (secondary_command_list){ + sign->SetSecondaryCommands(secondary_command_list); + sign->secondary_command_list_id = atoul(row[10]); + } + + sign->appearance.pos.collision_radius = atoi(row[11]); + sign->SetSignIcon(atoi(row[12])); + if(strncasecmp(row[13],"Generic", 7) == 0) + sign->SetSignType(SIGN_TYPE_GENERIC); + else if(strncasecmp(row[13],"Zone", 4) == 0) + sign->SetSignType(SIGN_TYPE_ZONE); + sign->SetSignTitle(row[14]); + sign->SetSignDescription(row[15]); + sign->SetSignDistance(atof(row[16])); + sign->SetSignZoneID(atoul(row[17])); + sign->SetSignZoneX(atof(row[18])); + sign->SetSignZoneY(atof(row[19])); + sign->SetSignZoneZ(atof(row[20])); + sign->SetSignZoneHeading(atof(row[21])); + sign->SetIncludeHeading(atoi(row[22]) == 1); + sign->SetIncludeLocation(atoi(row[23]) == 1); + sign->SetTransporterID(atoul(row[24])); + sign->SetSizeOffset(atoi(row[25])); + sign->appearance.display_hand_icon = atoi(row[26]); + sign->SetVisualState(atoi(row[27])); + + // xpack+holiday value handled at top at position 28+29 + + int8 disableSounds = atoul(row[30]); + sign->SetSoundsDisabled(disableSounds); + + sign->SetMerchantLevelRange(atoul(row[31]), atoul(row[32])); + + sign->SetAAXPRewards(atoul(row[33])); + + sign->SetLootTier(atoul(row[34])); + + sign->SetLootDropType(atoul(row[35])); + + sign->SetLanguage(atoul(row[36])); + + zone->AddSign(id, sign); + total++; + + LogWrite(SIGN__DEBUG, 5, "Sign", "---Loading Sign: '%s' (%u).", sign->appearance.name, id); + + } + LogWrite(SIGN__DEBUG, 0, "Sign", "--Loaded %i Sign(s)", total); +} + +void WorldDatabase::LoadWidgets(ZoneServer* zone){ + Query query; + MYSQL_ROW row; + Widget* widget = 0; + int32 id = 0; + int32 total = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT sw.spawn_id, s.name, s.model_type, s.size, s.show_command_icon, sw.widget_id, sw.widget_x, sw.widget_y, sw.widget_z, s.command_primary, s.command_secondary, s.collision_radius, sw.include_heading, sw.include_location, sw.icon, sw.type, sw.open_heading, sw.open_y, sw.action_spawn_id, sw.open_sound_file, sw.close_sound_file, sw.open_duration, sw.closed_heading, sw.linked_spawn_id, sw.close_y, s.transport_id, s.size_offset, sw.house_id, sw.open_x, sw.open_z, sw.close_x, sw.close_z, s.display_hand_icon, s.expansion_flag, s.holiday_flag, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, s.loot_tier, s.loot_drop_type\n" + "FROM spawn s\n" + "INNER JOIN spawn_widgets sw\n" + "ON s.id = sw.spawn_id\n" + "INNER JOIN spawn_location_entry le\n" + "ON sw.spawn_id = le.spawn_id\n" + "INNER JOIN spawn_location_placement lp\n" + "ON le.spawn_location_id = lp.spawn_location_id\n" + "WHERE lp.zone_id = %u\n" + "GROUP BY s.id", + zone->GetZoneID()); + while(result && (row = mysql_fetch_row(result))){ + int32 widgetXpackFlag = atoul(row[33]); + int32 widgetHolidayFlag = atoul(row[34]); + + id = atoul(row[0]); + if(zone->GetWidget(id, true)) + continue; + widget = new Widget(); + + if (!CheckExpansionFlags(zone, widgetXpackFlag) || !CheckHolidayFlags(zone, widgetHolidayFlag)) + widget->SetOmittedByDBFlag(true); + + widget->SetDatabaseID(id); + strcpy(widget->appearance.name, row[1]); + widget->appearance.model_type = atoi(row[2]); + widget->SetSize(atoi(row[3])); + widget->appearance.show_command_icon = atoi(row[4]); + + if (row[5] == NULL) + widget->SetWidgetID(0xFFFFFFFF); + else + widget->SetWidgetID(atoul(row[5])); + + widget->SetWidgetX(atof(row[6])); + widget->SetWidgetY(atof(row[7])); + widget->SetWidgetZ(atof(row[8])); + vector* primary_command_list = zone->GetEntityCommandList(atoul(row[9])); + if(primary_command_list){ + widget->SetPrimaryCommands(primary_command_list); + widget->primary_command_list_id = atoul(row[9]); + } + + vector* secondary_command_list = zone->GetEntityCommandList(atoul(row[10])); + if (secondary_command_list) { + widget->SetSecondaryCommands(secondary_command_list); + widget->secondary_command_list_id = atoul(row[10]); + } + + widget->appearance.pos.collision_radius = atoi(row[11]); + widget->SetIncludeHeading(atoi(row[12]) == 1); + widget->SetIncludeLocation(atoi(row[13]) == 1); + widget->SetWidgetIcon(atoi(row[14])); + if (strncasecmp(row[15], "Generic", 7) == 0) + widget->SetWidgetType(WIDGET_TYPE_GENERIC); + else if (strncasecmp(row[15], "Door", 4) == 0) + widget->SetWidgetType(WIDGET_TYPE_DOOR); + else if (strncasecmp(row[15], "Lift", 4) == 0) + widget->SetWidgetType(WIDGET_TYPE_LIFT); + + widget->SetOpenHeading(atof(row[16])); + widget->SetOpenY(atof(row[17])); + widget->SetActionSpawnID(atoul(row[18])); + if(row[19] && strlen(row[19]) > 5) + widget->SetOpenSound(row[19]); + if(row[20] && strlen(row[20]) > 5) + widget->SetCloseSound(row[20]); + widget->SetOpenDuration(atoi(row[21])); + widget->SetClosedHeading(atof(row[22])); + widget->SetLinkedSpawnID(atoul(row[23])); + widget->SetCloseY(atof(row[24])); + widget->SetTransporterID(atoul(row[25])); + widget->SetSizeOffset(atoi(row[26])); + widget->SetHouseID(atoul(row[27])); + widget->SetOpenX(atof(row[28])); + widget->SetOpenZ(atof(row[29])); + widget->SetCloseX(atof(row[30])); + widget->SetCloseZ(atof(row[31])); + widget->appearance.display_hand_icon = atoi(row[32]); + + // xpack+holiday value handled at top at position 33+34 + + int8 disableSounds = atoul(row[35]); + widget->SetSoundsDisabled(disableSounds); + + widget->SetMerchantLevelRange(atoul(row[36]), atoul(row[37])); + + widget->SetAAXPRewards(atoul(row[38])); + + widget->SetLootTier(atoul(row[39])); + + widget->SetLootDropType(atoul(row[40])); + + zone->AddWidget(id, widget); + total++; + + LogWrite(WIDGET__DEBUG, 5, "Widget", "---Loading Widget: '%s' (%u).", widget->appearance.name, id); + + } + LogWrite(WIDGET__DEBUG, 0, "Widget", "--Loaded %i Widget(s)", total); +} + +void WorldDatabase::LoadObjects(ZoneServer* zone){ + Query query; + MYSQL_ROW row; + Object* object = 0; + int32 id = 0; + int32 total = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT so.spawn_id, s.name, s.race, s.model_type, s.command_primary, s.command_secondary, s.targetable, s.size, s.show_name, s.visual_state, s.attackable, s.show_level, s.show_command_icon, s.display_hand_icon, s.faction_id, s.collision_radius, s.transport_id, s.size_offset, so.device_id, s.expansion_flag, s.holiday_flag, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, s.loot_tier, s.loot_drop_type\n" + "FROM spawn s\n" + "INNER JOIN spawn_objects so\n" + "ON s.id = so.spawn_id\n" + "INNER JOIN spawn_location_entry le\n" + "ON so.spawn_id = le.spawn_id\n" + "INNER JOIN spawn_location_placement lp\n" + "ON le.spawn_location_id = lp.spawn_location_id\n" + "WHERE lp.zone_id = %u and (lp.instance_id = 0 or lp.instance_id = %u)\n" + "GROUP BY s.id", + zone->GetZoneID(), zone->GetInstanceID()); + + while(result && (row = mysql_fetch_row(result))){ + + int32 objXpackFlag = atoul(row[19]); + int32 objHolidayFlag = atoul(row[20]); + + id = atoul(row[0]); + if(zone->GetObject(id, true)) + continue; + object = new Object(); + + if (!CheckExpansionFlags(zone, objXpackFlag) || !CheckHolidayFlags(zone, objHolidayFlag)) + object->SetOmittedByDBFlag(true); + + object->SetDatabaseID(id); + strcpy(object->appearance.name, row[1]); + vector* primary_command_list = zone->GetEntityCommandList(atoul(row[4])); + vector* secondary_command_list = zone->GetEntityCommandList(atoul(row[5])); + if(primary_command_list){ + object->SetPrimaryCommands(primary_command_list); + object->primary_command_list_id = atoul(row[4]); + } + if(secondary_command_list){ + object->SetSecondaryCommands(secondary_command_list); + object->secondary_command_list_id = atoul(row[5]); + } + object->appearance.race = atoi(row[2]); + object->appearance.model_type = atoi(row[3]); + object->appearance.targetable = atoi(row[6]); + object->size = atoi(row[7]); + object->appearance.display_name = atoi(row[8]); + object->appearance.visual_state = atoi(row[9]); + object->appearance.attackable = atoi(row[10]); + object->appearance.show_level = atoi(row[11]); + object->appearance.show_command_icon = atoi(row[12]); + object->appearance.display_hand_icon = atoi(row[13]); + object->faction_id = atoul(row[14]); + object->appearance.pos.collision_radius = atoi(row[15]); + object->SetTransporterID(atoul(row[16])); + object->SetSizeOffset(atoi(row[17])); + object->SetDeviceID(atoi(row[18])); + + // xpack value handled at top at position 19 + + int8 disableSounds = atoul(row[21]); + object->SetSoundsDisabled(disableSounds); + + object->SetMerchantLevelRange(atoul(row[22]), atoul(row[23])); + + object->SetAAXPRewards(atoul(row[24])); + + object->SetLootTier(atoul(row[25])); + + object->SetLootDropType(atoul(row[26])); + + zone->AddObject(id, object); + total++; + + LogWrite(OBJECT__DEBUG, 5, "Object", "---Loading Object: '%s' (%u)", object->appearance.name, id); + + } + LogWrite(OBJECT__DEBUG, 0, "Object", "--Loaded %i Object(s)", total); +} + +void WorldDatabase::LoadGroundSpawns(ZoneServer* zone){ + Query query; + MYSQL_ROW row; + GroundSpawn* spawn = 0; + int32 id = 0; + int32 total = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT sg.spawn_id, s.name, s.race, s.model_type, s.command_primary, s.command_secondary, s.targetable, s.size, s.show_name, s.visual_state, s.attackable, s.show_level, s.show_command_icon, s.display_hand_icon, s.faction_id, s.collision_radius, sg.number_harvests, sg.num_attempts_per_harvest, sg.groundspawn_id, sg.collection_skill, s.size_offset, s.expansion_flag, s.holiday_flag, s.disable_sounds, s.aaxp_rewards, s.loot_tier, s.loot_drop_type\n" + "FROM spawn s\n" + "INNER JOIN spawn_ground sg\n" + "ON s.id = sg.spawn_id\n" + "INNER JOIN spawn_location_entry le\n" + "ON sg.spawn_id = le.spawn_id\n" + "INNER JOIN spawn_location_placement lp\n" + "ON le.spawn_location_id = lp.spawn_location_id\n" + "WHERE lp.zone_id = %u\n" + "GROUP BY s.id", + zone->GetZoneID()); + while(result && (row = mysql_fetch_row(result))){ + + int32 gsXpackFlag = atoul(row[21]); + int32 gsHolidayFlag = atoul(row[22]); + + id = atoul(row[0]); + if(zone->GetGroundSpawn(id, true)) + continue; + spawn = new GroundSpawn(); + + if (!CheckExpansionFlags(zone, gsXpackFlag) || !CheckHolidayFlags(zone, gsHolidayFlag)) + spawn->SetOmittedByDBFlag(true); + + spawn->SetDatabaseID(id); + spawn->forceMapCheck = true; + + strcpy(spawn->appearance.name, row[1]); + vector* primary_command_list = zone->GetEntityCommandList(atoul(row[4])); + vector* secondary_command_list = zone->GetEntityCommandList(atoul(row[5])); + if(primary_command_list){ + spawn->SetPrimaryCommands(primary_command_list); + spawn->primary_command_list_id = atoul(row[4]); + } + if(secondary_command_list){ + spawn->SetSecondaryCommands(secondary_command_list); + spawn->secondary_command_list_id = atoul(row[5]); + } + spawn->appearance.race = atoi(row[2]); + spawn->appearance.model_type = atoi(row[3]); + spawn->appearance.targetable = atoi(row[6]); + spawn->size = atoi(row[7]); + spawn->appearance.display_name = atoi(row[8]); + spawn->appearance.visual_state = atoi(row[9]); + spawn->appearance.attackable = atoi(row[10]); + spawn->appearance.show_level = atoi(row[11]); + spawn->appearance.show_command_icon = atoi(row[12]); + spawn->appearance.display_hand_icon = atoi(row[13]); + spawn->faction_id = atoul(row[14]); + spawn->appearance.pos.collision_radius = atoi(row[15]); + spawn->SetNumberHarvests(atoi(row[16])); + spawn->SetAttemptsPerHarvest(atoi(row[17])); + spawn->SetGroundSpawnEntryID(atoul(row[18])); + spawn->SetCollectionSkill(row[19]); + spawn->SetSizeOffset(atoi(row[20])); + + // xpack+holiday value handled at top at position 21+22 + + int8 disableSounds = atoul(row[23]); + spawn->SetSoundsDisabled(disableSounds); + + spawn->SetAAXPRewards(atoul(row[24])); + + spawn->SetLootTier(atoul(row[25])); + + spawn->SetLootDropType(atoul(row[26])); + + zone->AddGroundSpawn(id, spawn); + total++; + LogWrite(GROUNDSPAWN__DEBUG, 5, "GSpawn", "---Loading GroundSpawn: '%s' (%u)", spawn->appearance.name, id); + } + LogWrite(GROUNDSPAWN__DEBUG, 0, "GSpawn", "--Loaded %i GroundSpawn(s)", total); +} + +void WorldDatabase::LoadGroundSpawnItems(ZoneServer* zone) { + Query query; + MYSQL_ROW row; + int32 total = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT groundspawn_id, item_id, is_rare, grid_id FROM groundspawn_items;"); + while(result && (row = mysql_fetch_row(result))) + { + zone->AddGroundSpawnItem(atoul(row[0]), atoul(row[1]), atoi(row[2]), atoul(row[3])); + LogWrite(GROUNDSPAWN__DEBUG, 5, "GSpawn", "---Loading GroundSpawn Items: ID: %u\n", atoul(row[0])); + LogWrite(GROUNDSPAWN__DEBUG, 5, "GSpawn", "---item: %ul, rare: %i, grid: %ul", atoul(row[1]), atoi(row[2]), atoul(row[3])); + total++; + } + LogWrite(GROUNDSPAWN__DEBUG, 0, "GSpawn", "--Loaded %i GroundSpawn Item%s.", total, total == 1 ? "" : "s"); +} + +void WorldDatabase::LoadGroundSpawnEntries(ZoneServer* zone) { + Query query; + MYSQL_ROW row; + int32 total = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT groundspawn_id, min_skill_level, min_adventure_level, bonus_table, harvest1, harvest3, harvest5, harvest_imbue, harvest_rare, harvest10, harvest_coin FROM groundspawns WHERE enabled = 1;"); + while(result && (row = mysql_fetch_row(result))) + { + // this is getting ridonkulous... + LogWrite(GROUNDSPAWN__DEBUG, 5, "GSpawn", "---Loading GroundSpawn ID: %u\n" \ + "---min_skill_level: %i, min_adventure_level: %i, bonus_table: %i\n" \ + "---harvest1: %.2f, harvest3: %.2f, harvest5: %.2f\n" \ + "---harvest_imbue: %.2f, harvest_rare: %.2f, harvest10: %.2f\n" \ + "---harvest_coin: %u", atoul(row[0]), atoi(row[1]), atoi(row[2]), atoi(row[3]), atof(row[4]), atof(row[5]), atof(row[6]), atof(row[7]), atof(row[8]), atof(row[9]), atoul(row[10])); + + zone->AddGroundSpawnEntry(atoul(row[0]), atoi(row[1]), atoi(row[2]), atoi(row[3]), atof(row[4]), atof(row[5]), atof(row[6]), atof(row[7]), atof(row[8]), atof(row[9]), atoul(row[10])); + total++; + } + LogWrite(GROUNDSPAWN__DEBUG, 0, "GSpawn", "--Loaded %i GroundSpawn Entr%s.", total, total == 1 ? "y" : "ies"); + LoadGroundSpawnItems(zone); +} + +bool WorldDatabase::LoadCharacterStats(int32 id, int32 account_id, Client* client) +{ + DatabaseResult result; + + if( database_new.Select(&result, "SELECT * FROM character_details WHERE char_id = %i LIMIT 0, 1", id) ) + { + LogWrite(PLAYER__DEBUG, 0, "Player", "Loading character_details for '%s' (char_id: %u)", client->GetPlayer()->GetName(), id); + + while( result.Next() ) + { + InfoStruct* info = client->GetPlayer()->GetInfoStruct(); + + // we need stats assigned before we try to assign hp/power + info->set_str_base(result.GetInt16Str("str")); + info->set_sta_base(result.GetInt16Str("sta")); + info->set_agi_base(result.GetInt16Str("agi")); + info->set_wis_base(result.GetInt16Str("wis")); + info->set_intel_base(result.GetInt16Str("intel")); + info->set_sta(info->get_sta_base()); + info->set_agi(info->get_agi_base()); + info->set_str(info->get_str_base()); + info->set_wis(info->get_wis_base()); + info->set_intel(info->get_intel_base()); + + // must have totals up top before we set the current 'hp' / 'power' + client->GetPlayer()->CalculatePlayerHPPower(); + + client->GetPlayer()->SetHP(result.GetSInt32Str("hp")); + client->GetPlayer()->SetPower(result.GetSInt32Str("power")); + info->set_max_concentration_base(result.GetInt8Str("max_concentration")); + + if (info->get_max_concentration_base() == 0) + info->set_max_concentration_base(5); + + info->set_attack_base(result.GetInt16Str("attack")); + info->set_mitigation_base(result.GetInt16Str("mitigation")); + info->set_avoidance_base(result.GetInt16Str("avoidance")); + info->set_parry_base(result.GetInt16Str("parry")); + info->set_deflection_base(result.GetInt16Str("deflection")); + info->set_block_base(result.GetInt16Str("block")); + + // old resist types + info->set_heat_base(result.GetInt16Str("heat")); + info->set_cold_base(result.GetInt16Str("cold")); + info->set_magic_base(result.GetInt16Str("magic")); + info->set_mental_base(result.GetInt16Str("mental")); + info->set_divine_base(result.GetInt16Str("divine")); + info->set_disease_base(result.GetInt16Str("disease")); + info->set_poison_base(result.GetInt16Str("poison")); + + // + + info->set_coin_copper(result.GetInt32Str("coin_copper")); + info->set_coin_silver(result.GetInt32Str("coin_silver")); + info->set_coin_gold(result.GetInt32Str("coin_gold")); + info->set_coin_plat(result.GetInt32Str("coin_plat")); + const char* bio = result.GetStringStr("biography"); + if(bio && strlen(bio) > 0) + info->set_biography(std::string(bio)); + info->set_status_points(result.GetInt32Str("status_points")); + client->GetPlayer()->GetPlayerInfo()->SetBindZone(result.GetInt32Str("bind_zone_id")); + client->GetPlayer()->GetPlayerInfo()->SetBindX(result.GetFloatStr("bind_x")); + client->GetPlayer()->GetPlayerInfo()->SetBindY(result.GetFloatStr("bind_y")); + client->GetPlayer()->GetPlayerInfo()->SetBindZ(result.GetFloatStr("bind_z")); + client->GetPlayer()->GetPlayerInfo()->SetBindHeading(result.GetFloatStr("bind_heading")); + client->GetPlayer()->GetPlayerInfo()->SetHouseZone(result.GetInt32Str("house_zone_id")); + client->GetPlayer()->SetAssignedAA(result.GetInt16Str("assigned_aa")); + client->GetPlayer()->SetUnassignedAA(result.GetInt16Str("unassigned_aa")); + client->GetPlayer()->SetTradeskillAA(result.GetInt16Str("tradeskill_aa")); + client->GetPlayer()->SetUnassignedTradeskillAA(result.GetInt16Str("unassigned_tradeskill_aa")); + client->GetPlayer()->SetPrestigeAA(result.GetInt16Str("prestige_aa")); + client->GetPlayer()->SetUnassignedPrestigeAA(result.GetInt16Str("unassigned_prestige_aa")); + client->GetPlayer()->SetTradeskillPrestigeAA(result.GetInt16Str("tradeskill_prestige_aa")); + client->GetPlayer()->SetUnassignedTradeskillPrestigeAA(result.GetInt16Str("unassigned_tradeskill_prestige_aa")); + info->set_xp(result.GetInt32Str("xp")); + + info->set_xp_needed(result.GetInt32Str("xp_needed")); + + if(info->get_xp_needed()== 0) + client->GetPlayer()->SetNeededXP(); + + info->set_xp_debt(result.GetFloatStr("xp_debt")); + info->set_xp_vitality(result.GetFloatStr("xp_vitality")); + info->set_ts_xp(result.GetInt32Str("tradeskill_xp")); + info->set_ts_xp_needed(result.GetInt32Str("tradeskill_xp_needed")); + + if (info->get_ts_xp_needed() == 0) + client->GetPlayer()->SetNeededTSXP(); + + info->set_tradeskill_xp_vitality(result.GetFloatStr("tradeskill_xp_vitality")); + info->set_bank_coin_copper(result.GetInt32Str("bank_copper")); + info->set_bank_coin_silver(result.GetInt32Str("bank_silver")); + info->set_bank_coin_gold(result.GetInt32Str("bank_gold")); + info->set_bank_coin_plat(result.GetInt32Str("bank_plat")); + + client->GetPlayer()->SetCombatVoice(result.GetInt16Str("combat_voice")); + client->GetPlayer()->SetEmoteVoice(result.GetInt16Str("emote_voice")); + client->GetPlayer()->SetBiography(result.GetStringStr("biography")); + client->GetPlayer()->GetInfoStruct()->set_flags(result.GetInt32Str("flags")); + client->GetPlayer()->GetInfoStruct()->set_flags2(result.GetInt32Str("flags2")); + client->GetPlayer()->SetLastName(result.GetStringStr("last_name")); + + // new resist types + info->set_elemental_base(result.GetInt16Str("elemental")); + info->set_arcane_base(result.GetInt16Str("arcane")); + info->set_noxious_base(result.GetInt16Str("noxious")); + // new savagery and dissonance + client->GetPlayer()->SetSavagery(result.GetSInt16Str("savagery")); + client->GetPlayer()->SetDissonance(result.GetSInt16Str("dissonance")); + client->GetPlayer()->SetTotalSavageryBase(client->GetPlayer()->GetTotalSavagery()); + client->GetPlayer()->SetTotalDissonanceBase(client->GetPlayer()->GetTotalDissonance()); + + client->GetPlayer()->SetCurrentLanguage(result.GetInt32Str("current_language")); + + std::string petName = result.GetStringStr("pet_name"); + boost::algorithm::to_lower(petName); + + if(petName != "no pet") { // some reason we default to "no pet", the code handles an empty string as setting a random pet name when its summoned + client->GetPlayer()->GetInfoStruct()->set_pet_name(result.GetStringStr("pet_name")); + } + } + + return true; + } + else + { + LogWrite(PLAYER__ERROR, 0, "Player", "Error loading character_details for '%s' (char_id: %u)", client->GetPlayer()->GetName(), id); + return false; + } +} + +bool WorldDatabase::loadCharacter(const char* ch_name, int32 account_id, Client* client){ + Query query, query4; + MYSQL_ROW row, row4; + int32 id = 0; + query.escaped_name = getEscapeString(ch_name); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, current_zone_id, x, y, z, heading, admin_status, race, model_type, class, deity, level, gender, tradeskill_class, tradeskill_level, wing_type, hair_type, chest_type, legs_type, soga_wing_type, soga_hair_type, soga_chest_type, soga_legs_type, 0xFFFFFFFF - crc32(name), facial_hair_type, soga_facial_hair_type, instance_id, group_id, last_saved, DATEDIFF(curdate(), created_date) as accage, alignment, first_world_login FROM characters where name='%s' and account_id=%i AND deleted = 0", query.escaped_name, account_id); + // no character found + if ( result == NULL ) { + LogWrite(PLAYER__ERROR, 0, "Player", "Error loading character for '%s'", ch_name); + return false; + } + + if (mysql_num_rows(result) == 1){ + row = mysql_fetch_row(result); + + id = strtoul(row[0], NULL, 0); + LogWrite(PLAYER__DEBUG, 0, "Player", "Loading character for '%s' (char_id: %u)", ch_name, id); + + client->SetCharacterID(id); + client->GetPlayer()->SetCharacterID(id); + client->SetAccountID(account_id); + client->GetPlayer()->SetName(ch_name); + client->GetPlayer()->SetX(atof(row[2])); + client->GetPlayer()->SetY(atof(row[3])); + client->GetPlayer()->SetZ(atof(row[4])); + client->GetPlayer()->SetHeading(atof(row[5])); + client->SetAdminStatus(atoi(row[6])); + client->GetPlayer()->SetRace(atoi(row[7])); + client->GetPlayer()->SetModelType(atoi(row[8])); + client->GetPlayer()->SetAdventureClass(atoi(row[9])); + client->GetPlayer()->SetDeity(atoi(row[10])); + client->GetPlayer()->SetLevel(atoi(row[11])); + + client->GetPlayer()->SetGender(atoi(row[12])); + client->GetPlayer()->SetTradeskillClass(atoi(row[13])); + client->GetPlayer()->SetTSLevel(atoi(row[14])); + + client->GetPlayer()->features.wing_type = atoi(row[15]); + client->GetPlayer()->features.hair_type = atoi(row[16]); + client->GetPlayer()->features.chest_type = atoi(row[17]); + client->GetPlayer()->features.legs_type = atoi(row[18]); + + client->GetPlayer()->features.wing_type = atoi(row[19]); + client->GetPlayer()->features.soga_hair_type = atoi(row[20]); + client->GetPlayer()->features.soga_chest_type = atoi(row[21]); + client->GetPlayer()->features.soga_legs_type = atoi(row[22]); + client->SetNameCRC(atoul(row[23])); + client->GetPlayer()->features.hair_face_type = atoi(row[24]); + client->GetPlayer()->features.soga_hair_face_type = atoi(row[25]); + int32 instanceid = atoi(row[26]); + + int32 groupid = atoi(row[27]); + client->SetRejoinGroupID(groupid); + + int32 zoneid = atoul(row[1]); +/* +JA Notes on SOGA: I think there are many more settings to add than were commented out here, +because I can load a SOGA model player, but some features are missing (Barbarian WOAD, Skin tons, etc) +SOGA chars looked ok in LoginServer screen tho... odd. +*/ + + // load character instances here + if ( LoadCharacterInstances(client) ) + client->UpdateCharacterInstances(); + + if ( instanceid > 0 ) + client->SetCurrentZoneByInstanceID(instanceid, zoneid); + else + client->SetCurrentZone(zoneid); + + int32 lastsavedtime = atoi(row[28]); + client->SetLastSavedTimeStamp(lastsavedtime); + + if (row[29]) + client->GetPlayer()->GetPlayerInfo()->SetAccountAge(atoi(row[29])); + + client->GetPlayer()->GetInfoStruct()->set_alignment(atoi(row[30])); + + client->GetPlayer()->GetInfoStruct()->set_first_world_login(atoi(row[31])); + + LoadCharacterFriendsIgnoreList(client->GetPlayer()); + MYSQL_RES* result4 = query4.RunQuery2(Q_SELECT, "SELECT `guild_id` FROM `guild_members` WHERE `char_id`=%u", id); + if (result4 && (row4 = mysql_fetch_row(result4))) { + Guild* guild = guild_list.GetGuild(atoul(row4[0])); + if (guild) { + client->GetPlayer()->SetGuild(guild); + string subtitle; + subtitle.append("<").append(guild->GetName()).append(">"); + client->GetPlayer()->SetSubTitle(subtitle.c_str()); + } + } + + LoadCharacterHistory(id, client->GetPlayer()); + LoadCharacterLUAHistory(id, client->GetPlayer()); + LoadPlayerStatistics(client->GetPlayer(), id); + LoadPlayerCollections(client->GetPlayer()); + LoadPlayerRecipes(client->GetPlayer()); + //LoadPlayerAchievements(client->GetPlayer()); + LoadPlayerAchievementsUpdates(client->GetPlayer()); + LoadAppearances(client->GetCurrentZone(), client); + return LoadCharacterStats(id, account_id, client); + } + + // should not be here... + LogWrite(PLAYER__ERROR, 0, "Player", "Error loading character for '%s'", ch_name); + return false; +} + +void WorldDatabase::LoadCharacterQuestRewards(Client* client) { + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT indexed, quest_id, is_temporary, is_collection, has_displayed, tmp_coin, tmp_status, description FROM character_quest_rewards where char_id = %u ORDER BY indexed asc", client->GetCharacterID()); + int8 count = 0; + if(result) + { + while(result && (row = mysql_fetch_row(result))) + { + int32 index = atoul(row[0]); + int32 quest_id = atoul(row[1]); + + bool is_temporary = atoul(row[2]); + + bool is_collection = atoul(row[3]); + + bool has_displayed = atoul(row[4]); + + + int64 tmp_coin = 0; +#ifdef WIN32 + tmp_coin = _strtoui64(row[5], NULL, 10); +#else + tmp_coin = strtoull(row[5], 0, 10); +#endif + + + int32 tmp_status = atoul(row[6]); + + std::string description = std::string(""); + + if(row[7]) { + std::string description = std::string(row[7]); + } + + if(is_collection) { + map* collections = client->GetPlayer()->GetCollectionList()->GetCollections(); + map::iterator itr; + Collection* collection = 0; + for (itr = collections->begin(); itr != collections->end(); itr++) { + collection = itr->second; + if (collection->GetIsReadyToTurnIn()) { + client->GetPlayer()->SetPendingCollectionReward(collection); + break; + } + } + } + if(is_temporary) { + LoadCharacterQuestTemporaryRewards(client, quest_id); + } + client->QueueQuestReward(quest_id, is_temporary, is_collection, has_displayed, tmp_coin, tmp_status, description, true, index); + count++; + } + } + + if(count) { + client->SetQuestUpdateState(true); + } +} + + +void WorldDatabase::LoadCharacterQuestTemporaryRewards(Client* client, int32 quest_id) { + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, count FROM character_quest_temporary_rewards where char_id = %u and quest_id = %u", client->GetCharacterID(), quest_id); + int8 count = 0; + if(result) + { + while(result && (row = mysql_fetch_row(result))) + { + int32 item_id = atoul(row[0]); + int16 item_count = atoul(row[1]); + client->GetPlayer()->AddQuestTemporaryReward(quest_id, item_id, item_count); + } + } +} + +bool WorldDatabase::InsertCharacterStats(int32 character_id, int8 class_id, int8 race_id){ + Query query1; + Query query2; + Query query3; + Query query4; + Query query5; + + /* Blank record */ + query1.RunQuery2(Q_INSERT, "INSERT INTO `character_details` (`char_id`) VALUES (%u)", character_id); + + /* Using the class id and race id */ + query2.RunQuery2(Q_UPDATE, "UPDATE character_details c, starting_details s SET c.max_hp = s.max_hp, c.hp = s.max_hp, c.max_power = s.max_power, c.power = s.max_power, c.str = s.str, c.sta = s.sta, c.agi = s.agi, c.wis = s.wis, c.intel = s.intel,c.heat = s.heat, c.cold = s.cold, c.magic = s.magic, c.mental = s.mental, c.divine = s.divine, c.disease = s.disease, c.poison = s.poison, c.coin_copper = s.coin_copper, c.coin_silver = s.coin_silver, c.coin_gold = s.coin_gold, c.coin_plat = s.coin_plat, c.status_points = s.status_points WHERE s.race_id = %d AND class_id = %d AND char_id = %u", race_id, class_id, character_id); + if (query2.GetAffectedRows() > 0) + return true; + + /* Using the class id and race id = 255 */ + query3.RunQuery2(Q_UPDATE, "UPDATE character_details c, starting_details s SET c.max_hp = s.max_hp, c.hp = s.max_hp, c.max_power = s.max_power, c.power = s.max_power, c.str = s.str, c.sta = s.sta, c.agi = s.agi, c.wis = s.wis, c.intel = s.intel,c.heat = s.heat, c.cold = s.cold, c.magic = s.magic, c.mental = s.mental, c.divine = s.divine, c.disease = s.disease, c.poison = s.poison, c.coin_copper = s.coin_copper, c.coin_silver = s.coin_silver, c.coin_gold = s.coin_gold, c.coin_plat = s.coin_plat, c.status_points = s.status_points WHERE s.race_id = 255 AND class_id = %d AND char_id = %u", class_id, character_id); + if (query3.GetAffectedRows() > 0) + return true; + + /* Using class id = 255 and the race id */ + query4.RunQuery2(Q_UPDATE, "UPDATE character_details c, starting_details s SET c.max_hp = s.max_hp, c.hp = s.max_hp, c.max_power = s.max_power, c.power = s.max_power, c.str = s.str, c.sta = s.sta, c.agi = s.agi, c.wis = s.wis, c.intel = s.intel,c.heat = s.heat, c.cold = s.cold, c.magic = s.magic, c.mental = s.mental, c.divine = s.divine, c.disease = s.disease, c.poison = s.poison, c.coin_copper = s.coin_copper, c.coin_silver = s.coin_silver, c.coin_gold = s.coin_gold, c.coin_plat = s.coin_plat, c.status_points = s.status_points WHERE s.race_id = %d AND class_id = 255 AND char_id = %u", race_id, character_id); + if (query4.GetAffectedRows() > 0) + return true; + + /* Using class id = 255 and race id = 255 */ + query5.RunQuery2(Q_UPDATE, "UPDATE character_details c, starting_details s SET c.max_hp = s.max_hp, c.hp = s.max_hp, c.max_power = s.max_power, c.power = s.max_power, c.str = s.str, c.sta = s.sta, c.agi = s.agi, c.wis = s.wis, c.intel = s.intel,c.heat = s.heat, c.cold = s.cold, c.magic = s.magic, c.mental = s.mental, c.divine = s.divine, c.disease = s.disease, c.poison = s.poison, c.coin_copper = s.coin_copper, c.coin_silver = s.coin_silver, c.coin_gold = s.coin_gold, c.coin_plat = s.coin_plat, c.status_points = s.status_points WHERE s.race_id = 255 AND class_id = 255 AND char_id = %u", character_id); + if (query5.GetAffectedRows() > 0) + return true; + + return false; +} + +int32 WorldDatabase::GetCharacterTimeStamp(int32 character_id, int32 account_id,bool* char_exists){ + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT unix_timestamp FROM characters where id=%i and account_id=%i",character_id,account_id); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + *char_exists = true; + return atoi(row[0]); // Return timestamp + } + else + LogWrite(WORLD__ERROR, 0, "World", "Error in GetCharacterTimeStamp query '%s': %s", query.GetQuery(), query.GetError()); + + *char_exists = false; + return 0; +} + +int32 WorldDatabase::GetCharacterTimeStamp(int32 character_id) { + Query query; + MYSQL_ROW row; + MYSQL_RES *result = query.RunQuery2(Q_SELECT, "SELECT unix_timestamp FROM characters WHERE id=%u", character_id); + int32 ret = 0; + + if (result && (row = mysql_fetch_row(result))) + ret = atoul(row[0]); + + return ret; +} + +bool WorldDatabase::UpdateCharacterTimeStamp(int32 account_id, int32 character_id, int32 timestamp_update){ + Query query; + string update_charts = string("update characters set unix_timestamp=%i where id=%i and account_id=%i"); + query.RunQuery2(Q_UPDATE, update_charts.c_str(),timestamp_update,character_id,account_id); + if(!query.GetAffectedRows()) + { + LogWrite(WORLD__ERROR, 0, "World", "Error in UpdateCharacterTimeStamp query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + + return true; +} + +bool WorldDatabase::insertCharacterProperty(Client* client, char* propName, char* propValue) { + Query query, query2; + + string update_status = string("update character_properties set propvalue='%s' where charid=%i and propname='%s'"); + query.RunQuery2(Q_UPDATE, update_status.c_str(), propValue, client->GetCharacterID(), propName); + if (!query.GetAffectedRows()) + { + query2.RunQuery2(Q_UPDATE, "insert into character_properties (charid, propname, propvalue) values(%i, '%s', '%s')", client->GetCharacterID(), propName, propValue); + if (query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF) { + LogWrite(WORLD__ERROR, 0, "World", "Error in insertCharacterProperty query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + } + return true; +} + +bool WorldDatabase::loadCharacterProperties(Client* client) { + Query query; + MYSQL_ROW row; + int32 id = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT propname, propvalue FROM character_properties where charid = %i", client->GetCharacterID()); + // no character found + if (result == NULL) { + LogWrite(PLAYER__ERROR, 0, "Player", "Error loading character properties for '%s'", client->GetPlayer()->GetName()); + return false; + } + + while (result && (row = mysql_fetch_row(result))) { + char* prop_name = row[0]; + char* prop_value = row[1]; + + if (!prop_name || !prop_value) + continue; + + if (!stricmp(prop_name, CHAR_PROPERTY_SPEED)) + { + float new_speed = atof(prop_value); + client->GetPlayer()->SetSpeed(new_speed, true); + client->GetPlayer()->SetCharSheetChanged(true); + } + else if (!stricmp(prop_name, CHAR_PROPERTY_FLYMODE)) + { + int8 flymode = atoul(prop_value); + if (flymode) // avoid fly mode notification unless enabled + ClientPacketFunctions::SendFlyMode(client, flymode, false); + } + else if (!stricmp(prop_name, CHAR_PROPERTY_INVUL)) + { + int8 invul = atoul(prop_value); + client->GetPlayer()->SetInvulnerable(invul == 1); + if (client->GetPlayer()->GetInvulnerable()) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You are now invulnerable!"); + } + else if (!stricmp(prop_name, CHAR_PROPERTY_GMVISION)) + { + int8 val = atoul(prop_value); + client->GetPlayer()->SetGMVision(val == 1); + client->GetCurrentZone()->SendAllSpawnsForVisChange(client, false); + if (val) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "GM Vision Enabled!"); + } + else if (!stricmp(prop_name, CHAR_PROPERTY_REGIONDEBUG)) + { + int8 val = atoul(prop_value); + + client->SetRegionDebug(val == 1); + if (val) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Region Debug Enabled!"); + } + else if (!stricmp(prop_name, CHAR_PROPERTY_LUADEBUG)) + { + int8 val = atoul(prop_value); + if (val) + { + client->SetLuaDebugClient(true); + if (lua_interface) + lua_interface->UpdateDebugClients(client); + + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You will now receive LUA error messages."); + } + } + else if (!stricmp(prop_name, CHAR_PROPERTY_GROUPLOOTMETHOD)) + { + int8 val = atoul(prop_value); + client->GetPlayer()->GetInfoStruct()->set_group_loot_method(val); + } + else if (!stricmp(prop_name, CHAR_PROPERTY_GROUPLOOTITEMRARITY)) + { + int8 val = atoul(prop_value); + client->GetPlayer()->GetInfoStruct()->set_group_loot_items_rarity(val); + } + else if (!stricmp(prop_name, CHAR_PROPERTY_GROUPAUTOSPLIT)) + { + int8 val = atoul(prop_value); + client->GetPlayer()->GetInfoStruct()->set_group_auto_split(val); + } + else if (!stricmp(prop_name, CHAR_PROPERTY_GROUPDEFAULTYELL)) + { + int8 val = atoul(prop_value); + client->GetPlayer()->GetInfoStruct()->set_group_default_yell(val); + } + else if (!stricmp(prop_name, CHAR_PROPERTY_GROUPAUTOLOCK)) + { + int8 val = atoul(prop_value); + client->GetPlayer()->GetInfoStruct()->set_group_autolock(val); + } + else if (!stricmp(prop_name, CHAR_PROPERTY_GROUPSOLOAUTOLOCK)) + { + int8 val = atoul(prop_value); + client->GetPlayer()->GetInfoStruct()->set_group_solo_autolock(val); + } + else if (!stricmp(prop_name, CHAR_PROPERTY_AUTOLOOTMETHOD)) + { + int8 val = atoul(prop_value); + client->GetPlayer()->GetInfoStruct()->set_group_auto_loot_method(val); + } + else if (!stricmp(prop_name, CHAR_PROPERTY_GROUPLOCKMETHOD)) + { + int8 val = atoul(prop_value); + client->GetPlayer()->GetInfoStruct()->set_group_lock_method(val); + } + else if (!stricmp(prop_name, CHAR_PROPERTY_ASSISTAUTOATTACK)) + { + int8 val = atoul(prop_value); + client->GetPlayer()->GetInfoStruct()->set_assist_auto_attack(val); + } + else if (!stricmp(prop_name, CHAR_PROPERTY_SETACTIVEFOOD)) + { + int32 val = atoul(prop_value); + client->GetPlayer()->SetActiveFoodUniqueID(val, false); + } + else if (!stricmp(prop_name, CHAR_PROPERTY_SETACTIVEDRINK)) + { + int32 val = atoul(prop_value); + client->GetPlayer()->SetActiveDrinkUniqueID(val, false); + } + } + + return true; +} + +//gets the name FROM the db with the right letters in caps +string WorldDatabase::GetPlayerName(char* name){ + Query query; + string ret = ""; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT name FROM characters where name='%s'", getSafeEscapeString(name).c_str()); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + if(row[0]) + ret = string(row[0]); + } + return ret; +} + +int32 WorldDatabase::GetCharacterID(const char* name) { + int32 id = 0; + Query query; + if (name) { + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `id` FROM `characters` WHERE `name`='%s'", name); + if (result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + id = atoul(row[0]); + } + } + return id; +} + +int32 WorldDatabase::GetCharacterCurrentZoneID(int32 character_id) { + int32 id = 0; + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `current_zone_id` FROM `characters` WHERE `id`=%u", character_id); + if (result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + id = atoul(row[0]); + } + return id; +} + +int32 WorldDatabase::GetCharacterAccountID(int32 character_id) { + int32 id = 0; + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `account_id` FROM `characters` WHERE `id`=%u", character_id); + if (result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + id = atoul(row[0]); + } + return id; +} + +sint16 WorldDatabase::GetHighestCharacterAdminStatus(int32 account_id){ + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT max(admin_status) FROM characters where account_id=%i ",account_id); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + if ( row[0] != NULL ) + return atoi(row[0]); // Return characters status + else + return 0; + } + + return 0; +} + +sint16 WorldDatabase::GetLowestCharacterAdminStatus(int32 account_id){ + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT min(admin_status) FROM characters where account_id=%i ",account_id); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + if ( row[0] != NULL ) + return atoi(row[0]); // Return characters status + else + return 0; + } + + return 0; +} + +sint16 WorldDatabase::GetCharacterAdminStatus(char* character_name){ + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT admin_status FROM characters where name='%s'", getSafeEscapeString(character_name).c_str()); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + return atoi(row[0]); // Return characters level + } + else + LogWrite(WORLD__ERROR, 0, "World", "Error in GetCharacterAdminStatus query '%s': %s", query.GetQuery(), query.GetError()); + + return -10; +} + +sint16 WorldDatabase::GetCharacterAdminStatus(int32 account_id , int32 char_id){ + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT admin_status FROM characters where account_id=%i and id=%i",account_id,char_id); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + return atoi(row[0]); // Return characters status + } + else{ + Query query2; + result = query2.RunQuery2(Q_SELECT, "SELECT count(id) FROM characters where account_id=%i and id=%i",account_id,char_id); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + if(atoi(row[0]) == 0) //old character, needs to be deleted FROM login server + return -10; + return -8; + } + else + LogWrite(WORLD__ERROR, 0, "World", "Error in GetCharacterAdminStatus query '%s': %s", query.GetQuery(), query.GetError()); + } + return PLAY_ERROR_PROBLEM; +} + +bool WorldDatabase::UpdateAdminStatus(char* character_name, sint16 flag){ + Query query; + string update_status = string("update characters set admin_status=%i where name='%s'"); + query.RunQuery2(Q_UPDATE, update_status.c_str(),flag,character_name); + if(!query.GetAffectedRows()) + { + LogWrite(WORLD__ERROR, 0, "World", "Error in UpdateAdminStatus query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + + return true; +} + +void WorldDatabase::SaveCharacterFloats(int32 char_id, const char* type, float float1, float float2, float float3, float multiplier){ + Query query; + string create_char = string("insert into char_colors (char_id, type, red, green, blue, signed_value) values(%i,'%s',%i,%i,%i, 1)"); + query.RunQuery2(Q_INSERT, create_char.c_str(), char_id, type, (sint8)(float1*multiplier), (sint8)(float2*multiplier), (sint8)(float3*multiplier)); + if(query.GetError() && strlen(query.GetError()) > 0){ + LogWrite(WORLD__ERROR, 0, "World", "Error in SaveCharacterFloats query '%s': %s", query.GetQuery(), query.GetError()); + } +} + +void WorldDatabase::SaveCharacterColors(int32 char_id, const char* type, EQ2_Color color){ + Query query; + string create_char = string("insert into char_colors (char_id, type, red, green, blue) values(%i,'%s',%i,%i,%i)"); + query.RunQuery2(Q_INSERT, create_char.c_str(), char_id, type, color.red, color.green, color.blue); + if(query.GetError() && strlen(query.GetError()) > 0){ + LogWrite(WORLD__ERROR, 0, "World", "Error in SaveCharacterColors query '%s': %s", query.GetQuery(), query.GetError()); + } +} + +void WorldDatabase::SaveNPCAppearanceEquipment(int32 spawn_id, int8 slot_id, int16 type, int8 red, int8 green, int8 blue, int8 hred, int8 hgreen, int8 hblue){ + Query query; + string appearance = string("INSERT INTO npc_appearance_equip (spawn_id, slot_id, equip_type, red, green, blue, highlight_red, highlight_green, highlight_blue) values (%i, %i, %i, %i, %i, %i, %i, %i, %i) ON DUPLICATE KEY UPDATE equip_type=%i, red=%i, green=%i, blue=%i, highlight_red=%i, highlight_green=%i, highlight_blue=%i"); + query.RunQuery2(Q_INSERT, appearance.c_str(), spawn_id, slot_id, type, red, green, blue, hred, hgreen, hblue, type, red, green, blue, hred, hgreen, hblue); + if(query.GetError() && strlen(query.GetError()) > 0){ + LogWrite(WORLD__ERROR, 0, "World", "Error in SaveNPCAppearanceEquipment query '%s': %s", query.GetQuery(), query.GetError()); + } +} + +int32 WorldDatabase::LoadNPCAppearanceEquipmentData(ZoneServer* zone){ + Query query; + MYSQL_ROW row; + + int32 spawn_id = 0, new_spawn_id = 0, count = 0; + NPC* npc = 0; + int8 slot = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT spawn_id, slot_id, equip_type, red, green, blue, highlight_red, highlight_green, highlight_blue FROM npc_appearance_equip ORDER BY spawn_id"); + while(result && (row = mysql_fetch_row(result))){ + new_spawn_id = atoul(row[0]); + if(new_spawn_id != spawn_id){ + npc = zone->GetNPC(new_spawn_id, true); + if(!npc) + continue; + if(spawn_id > 0) + count++; + spawn_id = new_spawn_id; + } + slot = atoul(row[1]); + if(slot < NUM_SLOTS){ + npc->SetEquipment(slot, atoul(row[2]), atoul(row[3]), atoul(row[4]), atoul(row[5]), atoul(row[6]), atoul(row[7]), atoul(row[8])); + } + } + if(query.GetError() && strlen(query.GetError()) > 0) + LogWrite(WORLD__ERROR, 0, "World", "Error in LoadNPCAppearanceEquipmentData query '%s': %s", query.GetQuery(), query.GetError()); + return count; +} + +int16 WorldDatabase::GetAppearanceID(string name){ + int32 id = 0; + Query query; + MYSQL_ROW row; + query.escaped_name = getEscapeString(name.c_str()); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT appearance_id FROM appearances where name='%s'", query.escaped_name); + if(result && mysql_num_rows(result) == 1){ + row = mysql_fetch_row(result); + id = atoi(row[0]); + } + return id; +} + +vector* WorldDatabase::GetAppearanceIDsLikeName(string name, bool filtered) { + vector* ids = 0; + Query query; + MYSQL_ROW row; + query.escaped_name = getEscapeString(name.c_str()); + MYSQL_RES* result; + if (filtered) + result = query.RunQuery2(Q_SELECT, "SELECT `appearance_id` FROM `appearances` WHERE `name` RLIKE '%s' AND `name` NOT RLIKE 'ghost' AND `name` NOT RLIKE 'headless' AND `name` NOT RLIKE 'elemental' AND `name` NOT RLIKE 'test' AND `name` NOT RLIKE 'zombie' AND `name` NOT RLIKE 'vampire'", query.escaped_name); + else + result = query.RunQuery2(Q_SELECT, "SELECT `appearance_id` FROM `appearances` WHERE `name` RLIKE '%s' AND `name` NOT RLIKE 'ghost' AND `name`", query.escaped_name); + while (result && (row = mysql_fetch_row(result))) { + if (!ids) + ids = new vector; + ids->push_back(atoi(row[0])); + } + return ids; +} + +string WorldDatabase::GetAppearanceName(int16 appearance_id) { + Query query; + MYSQL_ROW row; + string name; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `name` FROM `appearances` WHERE `appearance_id`=%u", appearance_id); + if (result && (row = mysql_fetch_row(result))) + name = string(row[0]); + return name; +} + +void WorldDatabase::UpdateRandomize(int32 spawn_id, sint32 value) { + Query query; + query.RunQuery2(Q_UPDATE, "UPDATE `spawn_npcs` SET `randomize`=`randomize` + %i WHERE `spawn_id`=%u", value, spawn_id); +} + +int32 WorldDatabase::SaveCharacter(PacketStruct* create, int32 loginID){ + Query query; + int8 race_id = create->getType_int8_ByName("race"); + int8 orig_class_id = create->getType_int8_ByName("class");//Normal server + int8 class_id = orig_class_id; + if ( create->GetVersion() <= 546 ) { + class_id = 0; //Classic Server Only + } + + create->PrintPacket(); + + int8 gender_id = create->getType_int8_ByName("gender"); + sint16 auto_admin_status = 0; + + // fetch rules related to setting auto-admin status for server + bool auto_admin_players = rule_manager.GetGlobalRule(R_World, AutoAdminPlayers)->GetBool(); + bool auto_admin_gm = rule_manager.GetGlobalRule(R_World, AutoAdminGMs)->GetBool(); + + /* + The way I think this is supposed to work :) is if any of your chars are already GM, and AutoAdminGMs rule is true, + set the new character's admin_status to your highest status for any character active on your loginID. + - If status > 0, new character > 0 + - If status = 0, new character = 0 + - If status < 0, new character < 0, too... even if auto_admin_gm is true + + If we're not a GM (status = 0) but AutoAdminPlayers rule is true, + set the new character's admin_status to the default set in AutoAdminStatusValue rule. + + Else, if both rules are False, set everyone to 0 like normal. + + */ + + auto_admin_status = GetHighestCharacterAdminStatus(loginID); + + if( auto_admin_status > 0 && auto_admin_gm ) { + LogWrite(WORLD__WARNING, 0, "World", "New character '%s' granted GM status (%i) from accountID: %i", create->getType_EQ2_16BitString_ByName("name").data.c_str(), auto_admin_status, loginID); + } + else if( auto_admin_players ) + { + auto_admin_status = rule_manager.GetGlobalRule(R_World, AutoAdminStatusValue)->GetSInt16(); + LogWrite(WORLD__DEBUG, 0, "World", "New character '%s' granted AutoAdminPlayer status: %i", create->getType_EQ2_16BitString_ByName("name").data.c_str(), auto_admin_status); + } + else { + auto_admin_status = 0; + } + + string create_char = string("Insert into characters (account_id, server_id, name, race, class, gender, deity, body_size, body_age, soga_wing_type, soga_chest_type, soga_legs_type, soga_hair_type, soga_model_type, legs_type, chest_type, wing_type, hair_type, model_type, facial_hair_type, soga_facial_hair_type, created_date, last_saved, admin_status, first_world_login) values(%i, %i, '%s', %i, %i, %i, %i, %f, %f, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, now(), unix_timestamp(), %i, 1)"); + + query.RunQuery2(Q_INSERT, create_char.c_str(), + loginID, + create->getType_int32_ByName("server_id"), + create->getType_EQ2_16BitString_ByName("name").data.c_str(), + race_id, + class_id, + gender_id, + create->getType_int8_ByName("deity"), + create->getType_float_ByName("body_size"), + create->getType_float_ByName("body_age"), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("soga_wing_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("soga_chest_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("soga_legs_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("soga_hair_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("soga_race_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("legs_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("chest_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("wing_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("hair_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("race_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("face_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("soga_face_file").data), + auto_admin_status); + + if(query.GetError() && strlen(query.GetError()) > 0) + { + LogWrite(PLAYER__ERROR, 0, "Player", "Error in SaveCharacter query '%s': %s", query.GetQuery(), query.GetError()); + return 0; + } + + int32 last_insert_id = query.GetLastInsertedID(); + int32 char_id = last_insert_id; + UpdateStartingFactions(char_id, create->getType_int8_ByName("starting_zone")); + UpdateStartingZone(char_id, class_id, race_id, create); + UpdateStartingItems(char_id, class_id, race_id); + UpdateStartingSkills(char_id, class_id, race_id); + UpdateStartingSpells(char_id, class_id, race_id); + UpdateStartingSkillbar(char_id, class_id, race_id); + UpdateStartingTitles(char_id, class_id, race_id, gender_id); + InsertCharacterStats(char_id, class_id, race_id); + UpdateStartingLanguage(char_id, race_id, create->getType_int8_ByName("starting_zone")); + + LoadClaimItems(char_id); //insert claim items, from claim_items to the character_ version. + + AddNewPlayerToServerGuild(loginID, char_id); + + if (create->GetVersion() <= 561) { + float classic_multiplier = 250.0f; + SaveCharacterFloats(char_id, "skin_color", create->getType_float_ByName("skin_color", 0), create->getType_float_ByName("skin_color", 1), create->getType_float_ByName("skin_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "eye_color", create->getType_float_ByName("eye_color", 0), create->getType_float_ByName("eye_color", 1), create->getType_float_ByName("eye_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_color1", create->getType_float_ByName("hair_color1", 0), create->getType_float_ByName("hair_color1", 1), create->getType_float_ByName("hair_color1", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_color2", create->getType_float_ByName("hair_color2", 0), create->getType_float_ByName("hair_color2", 1), create->getType_float_ByName("hair_color2", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_highlight", create->getType_float_ByName("hair_highlight", 0), create->getType_float_ByName("hair_highlight", 1), create->getType_float_ByName("hair_highlight", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_type_color", create->getType_float_ByName("hair_type_color", 0), create->getType_float_ByName("hair_type_color", 1), create->getType_float_ByName("hair_type_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_type_highlight_color", create->getType_float_ByName("hair_type_highlight_color", 0), create->getType_float_ByName("hair_type_highlight_color", 1), create->getType_float_ByName("hair_type_highlight_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_type_color", create->getType_float_ByName("hair_type_color", 0), create->getType_float_ByName("hair_type_color", 1), create->getType_float_ByName("hair_type_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_type_highlight_color", create->getType_float_ByName("hair_type_highlight_color", 0), create->getType_float_ByName("hair_type_highlight_color", 1), create->getType_float_ByName("hair_type_highlight_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_face_color", create->getType_float_ByName("hair_face_color", 0), create->getType_float_ByName("hair_face_color", 1), create->getType_float_ByName("hair_face_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_face_highlight_color", create->getType_float_ByName("hair_face_highlight_color", 0), create->getType_float_ByName("hair_face_highlight_color", 1), create->getType_float_ByName("hair_face_highlight_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "shirt_color", create->getType_float_ByName("shirt_color", 0), create->getType_float_ByName("shirt_color", 1), create->getType_float_ByName("shirt_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "unknown_chest_color", create->getType_float_ByName("unknown_chest_color", 0), create->getType_float_ByName("unknown_chest_color", 1), create->getType_float_ByName("unknown_chest_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "pants_color", create->getType_float_ByName("pants_color", 0), create->getType_float_ByName("pants_color", 1), create->getType_float_ByName("pants_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "unknown_legs_color", create->getType_float_ByName("unknown_legs_color", 0), create->getType_float_ByName("unknown_legs_color", 1), create->getType_float_ByName("unknown_legs_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "unknown9", create->getType_float_ByName("unknown9", 0), create->getType_float_ByName("unknown9", 1), create->getType_float_ByName("unknown9", 2), classic_multiplier); + } + else { + SaveCharacterColors(char_id, "skin_color", create->getType_EQ2_Color_ByName("skin_color")); + SaveCharacterColors(char_id, "model_color", create->getType_EQ2_Color_ByName("model_color")); + SaveCharacterColors(char_id, "eye_color", create->getType_EQ2_Color_ByName("eye_color")); + SaveCharacterColors(char_id, "hair_color1", create->getType_EQ2_Color_ByName("hair_color1")); + SaveCharacterColors(char_id, "hair_color2", create->getType_EQ2_Color_ByName("hair_color2")); + SaveCharacterColors(char_id, "hair_highlight", create->getType_EQ2_Color_ByName("hair_highlight")); + SaveCharacterColors(char_id, "hair_type_color", create->getType_EQ2_Color_ByName("hair_type_color")); + SaveCharacterColors(char_id, "hair_type_highlight_color", create->getType_EQ2_Color_ByName("hair_type_highlight_color")); + SaveCharacterColors(char_id, "hair_face_color", create->getType_EQ2_Color_ByName("hair_face_color")); + SaveCharacterColors(char_id, "hair_face_highlight_color", create->getType_EQ2_Color_ByName("hair_face_highlight_color")); + SaveCharacterColors(char_id, "wing_color1", create->getType_EQ2_Color_ByName("wing_color1")); + SaveCharacterColors(char_id, "wing_color2", create->getType_EQ2_Color_ByName("wing_color2")); + SaveCharacterColors(char_id, "shirt_color", create->getType_EQ2_Color_ByName("shirt_color")); + SaveCharacterColors(char_id, "unknown_chest_color", create->getType_EQ2_Color_ByName("unknown_chest_color")); + SaveCharacterColors(char_id, "pants_color", create->getType_EQ2_Color_ByName("pants_color")); + SaveCharacterColors(char_id, "unknown_legs_color", create->getType_EQ2_Color_ByName("unknown_legs_color")); + SaveCharacterColors(char_id, "unknown9", create->getType_EQ2_Color_ByName("unknown9")); + + SaveCharacterColors(char_id, "soga_skin_color", create->getType_EQ2_Color_ByName("soga_skin_color")); + SaveCharacterColors(char_id, "soga_model_color", create->getType_EQ2_Color_ByName("soga_model_color")); + SaveCharacterColors(char_id, "soga_eye_color", create->getType_EQ2_Color_ByName("soga_eye_color")); + SaveCharacterColors(char_id, "soga_hair_color1", create->getType_EQ2_Color_ByName("soga_hair_color1")); + SaveCharacterColors(char_id, "soga_hair_color2", create->getType_EQ2_Color_ByName("soga_hair_color2")); + SaveCharacterColors(char_id, "soga_hair_highlight", create->getType_EQ2_Color_ByName("soga_hair_highlight")); + SaveCharacterColors(char_id, "soga_hair_type_color", create->getType_EQ2_Color_ByName("soga_hair_type_color")); + SaveCharacterColors(char_id, "soga_hair_type_highlight_color", create->getType_EQ2_Color_ByName("soga_hair_type_highlight_color")); + SaveCharacterColors(char_id, "soga_hair_face_color", create->getType_EQ2_Color_ByName("soga_hair_face_color")); + SaveCharacterColors(char_id, "soga_hair_face_highlight_color", create->getType_EQ2_Color_ByName("soga_hair_face_highlight_color")); + SaveCharacterColors(char_id, "soga_wing_color1", create->getType_EQ2_Color_ByName("soga_wing_color1")); + SaveCharacterColors(char_id, "soga_wing_color2", create->getType_EQ2_Color_ByName("soga_wing_color2")); + SaveCharacterColors(char_id, "soga_shirt_color", create->getType_EQ2_Color_ByName("soga_shirt_color")); + SaveCharacterColors(char_id, "soga_unknown_chest_color", create->getType_EQ2_Color_ByName("soga_unknown_chest_color")); + SaveCharacterColors(char_id, "soga_pants_color", create->getType_EQ2_Color_ByName("soga_pants_color")); + SaveCharacterColors(char_id, "soga_unknown_legs_color", create->getType_EQ2_Color_ByName("soga_unknown_legs_color")); + SaveCharacterColors(char_id, "soga_unknown13", create->getType_EQ2_Color_ByName("soga_unknown13")); + SaveCharacterFloats(char_id, "soga_eye_type", create->getType_float_ByName("soga_eyes2", 0), create->getType_float_ByName("soga_eyes2", 1), create->getType_float_ByName("soga_eyes2", 2)); + SaveCharacterFloats(char_id, "soga_ear_type", create->getType_float_ByName("soga_ears", 0), create->getType_float_ByName("soga_ears", 1), create->getType_float_ByName("soga_ears", 2)); + SaveCharacterFloats(char_id, "soga_eye_brow_type", create->getType_float_ByName("soga_eye_brows", 0), create->getType_float_ByName("soga_eye_brows", 1), create->getType_float_ByName("soga_eye_brows", 2)); + SaveCharacterFloats(char_id, "soga_cheek_type", create->getType_float_ByName("soga_cheeks", 0), create->getType_float_ByName("soga_cheeks", 1), create->getType_float_ByName("soga_cheeks", 2)); + SaveCharacterFloats(char_id, "soga_lip_type", create->getType_float_ByName("soga_lips", 0), create->getType_float_ByName("soga_lips", 1), create->getType_float_ByName("soga_lips", 2)); + SaveCharacterFloats(char_id, "soga_chin_type", create->getType_float_ByName("soga_chin", 0), create->getType_float_ByName("soga_chin", 1), create->getType_float_ByName("soga_chin", 2)); + SaveCharacterFloats(char_id, "soga_nose_type", create->getType_float_ByName("soga_nose", 0), create->getType_float_ByName("soga_nose", 1), create->getType_float_ByName("soga_nose", 2)); + } + SaveCharacterFloats(char_id, "eye_type", create->getType_float_ByName("eyes2", 0), create->getType_float_ByName("eyes2", 1), create->getType_float_ByName("eyes2", 2)); + SaveCharacterFloats(char_id, "ear_type", create->getType_float_ByName("ears", 0), create->getType_float_ByName("ears", 1), create->getType_float_ByName("ears", 2)); + SaveCharacterFloats(char_id, "eye_brow_type", create->getType_float_ByName("eye_brows", 0), create->getType_float_ByName("eye_brows", 1), create->getType_float_ByName("eye_brows", 2)); + SaveCharacterFloats(char_id, "cheek_type", create->getType_float_ByName("cheeks", 0), create->getType_float_ByName("cheeks", 1), create->getType_float_ByName("cheeks", 2)); + SaveCharacterFloats(char_id, "lip_type", create->getType_float_ByName("lips", 0), create->getType_float_ByName("lips", 1), create->getType_float_ByName("lips", 2)); + SaveCharacterFloats(char_id, "chin_type", create->getType_float_ByName("chin", 0), create->getType_float_ByName("chin", 1), create->getType_float_ByName("chin", 2)); + SaveCharacterFloats(char_id, "nose_type", create->getType_float_ByName("nose", 0), create->getType_float_ByName("nose", 1), create->getType_float_ByName("nose", 2)); + SaveCharacterFloats(char_id, "body_size", create->getType_float_ByName("body_size", 0), 0, 0); + return char_id; +} + +int8 WorldDatabase::CheckNameFilter(const char* name, int8 min_length, int8 max_length) { + // the minimum 4 is enforced by the client too + if(!name || strlen(name) < min_length || strlen(name) > max_length) // Even 20 char length is long... + return BADNAMELENGTH_REPLY; + uchar* checkname = (uchar*)name; + for (int32 i = 0; i < strlen(name); i++) + { + if(!alpha_check(checkname[i])) + return NAMEINVALID_REPLY; + } + Query query; + LogWrite(WORLD__DEBUG, 0, "World", "Name check on: %s", name); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT count(*) FROM characters WHERE name='%s'",name); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + if(row[0] != 0 && atoi(row[0]) > 0) + return NAMETAKEN_REPLY; + } + else + LogWrite(WORLD__ERROR, 0, "World", "Error in CheckNameFilter (name exist check) (Name query '%s': %s", query.GetQuery(), query.GetError()); + + Query query3; + LogWrite(WORLD__DEBUG, 0, "World", "Name check on: %s (Bots table)", name); + MYSQL_RES* result3 = query3.RunQuery2(Q_SELECT, "SELECT count(*) FROM bots WHERE name='%s'", name); + if (result3 && mysql_num_rows(result3) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result3); + if (row[0] != 0 && atoi(row[0]) > 0) + return NAMETAKEN_REPLY; + } + else + LogWrite(WORLD__ERROR, 0, "World", "Error in CheckNameFilter (name exist check, bot table) (Name query '%s': %s", query3.GetQuery(), query3.GetError()); + + + + Query query2; + MYSQL_RES* result2 = query2.RunQuery2(Q_SELECT, "SELECT count(*) FROM name_filter WHERE '%s' like name",name); + if(result2 && mysql_num_rows(result2) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result2); + if(row[0] != 0 && atoi(row[0]) > 0) + return NAMEFILTER_REPLY; + else if(row[0] != 0 && atoi(row[0]) == 0) + return CREATESUCCESS_REPLY; + } + else + LogWrite(WORLD__ERROR, 0, "World", "Error in CheckNameFilter (name_filter check) query '%s': %s", query.GetQuery(), query.GetError()); + + return UNKNOWNERROR_REPLY; +} + +char* WorldDatabase::GetCharacterName(int32 character_id){ + + LogWrite(WORLD__TRACE, 9, "World", "Enter: %s", __FUNCTION__); + + Query query; + char* name = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT name FROM characters where id=%u",character_id); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + if(row[0] && strlen(row[0]) > 0) + { + name = new char[strlen(row[0])+1]; + memset(name,0, strlen(row[0])+1); + strcpy(name, row[0]); + } + } + else + LogWrite(WORLD__ERROR, 0, "World", "Error in GetCharacterName query '%s': %s", query.GetQuery(), query.GetError()); + + LogWrite(WORLD__TRACE, 9, "World", "Exit: %s", __FUNCTION__); + return name; +} + +int8 WorldDatabase::GetCharacterLevel(int32 character_id){ + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT level FROM characters where id=%u",character_id); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + return atoi(row[0]); // Return characters level + } + else + LogWrite(WORLD__ERROR, 0, "World", "Error in GetCharacterLevel query '%s': %s", query.GetQuery(), query.GetError()); + + return 0; +} + +int16 WorldDatabase::GetCharacterModelType(int32 character_id){ + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT model_type FROM characters where id=%u",character_id); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + return atoi(row[0]); // Return characters race + } + else + LogWrite(WORLD__ERROR, 0, "World", "Error in GetCharacterModelType query '%s': %s", query.GetQuery(), query.GetError()); + + return 0; +} + +int8 WorldDatabase::GetCharacterClass(int32 character_id){ + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT class FROM characters where id=%u",character_id); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + return atoi(row[0]); // Return characters class + } + else + LogWrite(WORLD__ERROR, 0, "World", "Error in GetCharacterClass query '%s': %s", query.GetQuery(), query.GetError()); + + return 0; +} + +int8 WorldDatabase::GetCharacterGender(int32 character_id){ + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT gender FROM characters where id=%u",character_id); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + return atoi(row[0]); // Return characters gender + } + else + LogWrite(WORLD__ERROR, 0, "World", "Error in GetCharacterGender query '%s': %s", query.GetQuery(), query.GetError()); + + return 0; +} + +void WorldDatabase::DeleteCharacterQuest(int32 quest_id, int32 char_id, bool repeated_quest) { + if (repeated_quest) { + if (!database_new.Query("UPDATE `character_quests` SET `given_date` = `completed_date` WHERE `char_id` = %u AND `quest_id` = %u", char_id, quest_id)) + LogWrite(DATABASE__ERROR, 0, "DBNew", "Error (%u) in DeleteCharacterQuest query:\n%s", database_new.GetError(), database_new.GetErrorMsg()); + } + else { + if (!database_new.Query("DELETE FROM `character_quests` WHERE `char_id` = %u AND `quest_id` = %u", char_id, quest_id)) + LogWrite(DATABASE__ERROR, 0, "DBNew", "Error (%u) in DeleteCharacterQuest query:\n%s", database_new.GetError(), database_new.GetErrorMsg()); + } + + if (!database_new.Query("DELETE FROM `character_quest_progress` WHERE `char_id` = %u AND `quest_id` = %u", char_id, quest_id)) + LogWrite(DATABASE__ERROR, 0, "DBNew", "Error (%u) in DeleteCharacterQuest query:\n%s", database_new.GetError(), database_new.GetErrorMsg()); +} + +void WorldDatabase::SaveCharacterSkills(Client* client){ + vector* skills = client->GetPlayer()->GetSkills()->GetSaveNeededSkills(); + if(skills){ + Query query; + if(skills->size() > 0){ + Skill* skill = 0; + for(int32 i=0;isize();i++){ + skill = skills->at(i); + query.AddQueryAsync(client->GetCharacterID(),this,Q_REPLACE, "replace into character_skills (char_id, skill_id, current_val, max_val) values(%u, %u, %i, %i)", client->GetCharacterID(), skill->skill_id, skill->current_val, skill->max_val); + } + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF) + LogWrite(WORLD__ERROR, 0, "World", "Error in SaveCharacterSkills query '%s': %s", query.GetQuery(), query.GetError()); + } + safe_delete(skills); + } +} + +void WorldDatabase::SaveCharacterQuests(Client* client){ + Query query; + map::iterator itr; + master_quest_list.LockQuests(); //prevent reloading until we are done + client->GetPlayer()->MPlayerQuests.writelock(__FUNCTION__, __LINE__); //prevent all quest modifications until we are done + map* quests = client->GetPlayer()->GetPlayerQuests(); + for(itr = quests->begin(); itr != quests->end(); itr++){ + if(client->GetCurrentQuestID() == itr->first){ + query.AddQueryAsync(client->GetCharacterID(),this,Q_UPDATE, "update character_quests set current_quest = 0 where char_id = %u", client->GetCharacterID()); + query.AddQueryAsync(client->GetCharacterID(), this,Q_UPDATE, "update character_quests set current_quest = 1 where char_id = %u and quest_id = %u", client->GetCharacterID(), itr->first); + } + if(itr->second && itr->second->GetSaveNeeded()){ + query.AddQueryAsync(client->GetCharacterID(), this,Q_INSERT, "insert ignore into character_quests (char_id, quest_id, given_date, quest_giver) values(%u, %u, now(), %u)", client->GetCharacterID(), itr->first, itr->second->GetQuestGiver()); + query.AddQueryAsync(client->GetCharacterID(), this,Q_UPDATE, "update character_quests set tracked = %i, quest_flags = %u, hidden = %i, complete_count = %u where char_id = %u and quest_id = %u", itr->second->IsTracked() ? 1 : 0, itr->second->GetQuestFlags(), itr->second->IsHidden() ? 1 : 0, itr->second->GetCompleteCount(), client->GetCharacterID(), itr->first); + SaveCharacterQuestProgress(client, itr->second); + itr->second->SetSaveNeeded(false); + } + } + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF) + LogWrite(WORLD__ERROR, 0, "World", "Error in SaveCharacterQuests query '%s': %s", query.GetQuery(), query.GetError()); + quests = client->GetPlayer()->GetCompletedPlayerQuests(); + for(itr = quests->begin(); itr != quests->end(); itr++){ + if(itr->second && itr->second->GetSaveNeeded()){ + query.AddQueryAsync(client->GetCharacterID(), this,Q_DELETE, "delete FROM character_quest_progress where char_id = %u and quest_id = %u", client->GetCharacterID(), itr->first); + + /* incase the quest is completed before the quest could be inserted in the PlayerQuests loop, we first try to insert it. If it already exists then we can just update + * the completed_date */ + query.AddQueryAsync(client->GetCharacterID(), this,Q_INSERT, "INSERT INTO character_quests (char_id, quest_id, quest_giver, current_quest, given_date, completed_date, complete_count) values (%u,%u,%u,0, now(),now(), %u) ON DUPLICATE KEY UPDATE completed_date = now(), complete_count = %u, current_quest = 0", client->GetCharacterID(), itr->first, itr->second->GetQuestGiver(), itr->second->GetCompleteCount(), itr->second->GetCompleteCount()); + itr->second->SetSaveNeeded(false); + } + } + client->GetPlayer()->MPlayerQuests.releasewritelock(__FUNCTION__, __LINE__); + master_quest_list.UnlockQuests(); + +} + +void WorldDatabase::SaveCharRepeatableQuest(Client* client, int32 quest_id, int16 quest_complete_count) { + if (!database_new.Query("UPDATE `character_quests` SET `given_date` = now(), complete_count = %u WHERE `char_id` = %u AND `quest_id` = %u", quest_complete_count, client->GetCharacterID(), quest_id)) + LogWrite(DATABASE__ERROR, 0, "DBNew", "DB Error %u\n%s", database_new.GetError(), database_new.GetErrorMsg()); +} + +void WorldDatabase::SaveCharacterQuestProgress(Client* client, Quest* quest){ + vector* steps = quest->GetQuestSteps(); + vector::iterator itr; + QuestStep* step = 0; + quest->MQuestSteps.readlock(__FUNCTION__, __LINE__); + if(steps){ + for(itr = steps->begin(); itr != steps->end(); itr++){ + step = *itr; + if(step && step->GetQuestCurrentQuantity() > 0) { + Query query; + query.RunQuery2(Q_REPLACE, "replace into character_quest_progress (char_id, quest_id, step_id, progress) values(%u, %u, %u, %i)", client->GetCharacterID(), quest->GetQuestID(), step->GetStepID(), step->GetQuestCurrentQuantity()); + + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF) + LogWrite(WORLD__ERROR, 0, "World", "Error in SaveCharacterQuestProgress query '%s': %s", query.GetQuery(), query.GetError()); + } + } + } + quest->MQuestSteps.releasereadlock(__FUNCTION__, __LINE__); +} + +void WorldDatabase::LoadCharacterQuestProgress(Client* client){ + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT character_quest_progress.quest_id, step_id, progress FROM character_quest_progress, character_quests where character_quest_progress.char_id=%u and character_quest_progress.quest_id = character_quests.quest_id and character_quest_progress.char_id = character_quests.char_id ORDER BY character_quest_progress.quest_id",client->GetCharacterID()); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + Quest* quest = 0; + int32 quest_id = 0; + map* progress_map = new map(); + while(result && (row = mysql_fetch_row(result))){ + if(quest_id != atoul(row[0])){ + if(quest_id > 0){ + quest = client->GetPlayer()->GetQuest(quest_id); + if(quest) + client->SetPlayerQuest(quest, progress_map); + } + quest_id = atoul(row[0]); + progress_map->clear(); + } + (*progress_map)[atoul(row[1])] = atoul(row[2]); + } + if(progress_map->size() > 0){ + quest = client->GetPlayer()->GetQuest(quest_id); + if(quest) + client->SetPlayerQuest(quest, progress_map); + } + safe_delete(progress_map); + } + else if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF) + LogWrite(WORLD__ERROR, 0, "World", "Error in LoadCharacterQuestProgress query '%s': %s", query.GetQuery(), query.GetError()); +} + +void WorldDatabase::LoadCharacterQuests(Client* client){ + LogWrite(PLAYER__DEBUG, 0, "Player", "Loading Character Quests..."); + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT quest_id, DAY(given_date), MONTH(given_date), YEAR(given_date), DAY(completed_date), MONTH(completed_date), YEAR(completed_date), quest_giver, tracked, quest_flags, hidden, UNIX_TIMESTAMP(given_date), UNIX_TIMESTAMP(completed_date), complete_count FROM character_quests WHERE char_id=%u ORDER BY current_quest", client->GetCharacterID()); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + Quest* quest = 0; + while(result && (row = mysql_fetch_row(result))){ + quest = master_quest_list.GetQuest(atoul(row[0])); + if(quest) { + + LogWrite(PLAYER__DEBUG, 5, "Player", "\tLoading quest_id: %u", atoul(row[0])); + bool addQuest = true; + + if(row[4] && atoi(row[4]) > 0){ + quest->SetTurnedIn(true); + if(row[4]) + quest->SetDay(atoi(row[4])); + if(row[5]) + quest->SetMonth(atoi(row[5])); + if(row[6] && atoi(row[6]) > 2000) + quest->SetYear(atoi(row[6]) - 2000); + client->GetPlayer()->AddCompletedQuest(quest); + + // Added timestamps to quickly compare given and completed dates + int32 given_timestamp = atoul(row[11]); + int32 completed_timestamp = atoul(row[12]); + + // If given timestamp is greater then completed then this is a repeatable quest we are working on + // so get a fresh quest object to add as an active quest + if (given_timestamp > completed_timestamp) + { + lua_interface->SetLuaUserDataStale(quest); + safe_delete(quest); + quest = master_quest_list.GetQuest(atoul(row[0])); + } + else + addQuest = false; + + quest->SetCompleteCount(atoi(row[13])); + } + + if (addQuest) { + if(row[1]) + quest->SetDay(atoi(row[1])); + if(row[2]) + quest->SetMonth(atoi(row[2])); + if(row[3] && atoi(row[3]) > 2000) + quest->SetYear(atoi(row[3]) - 2000); + quest->SetQuestGiver(atoul(row[7])); + quest->SetTracked(atoi(row[8]) == 1 ? true : false); + quest->SetQuestFlags(atoul(row[9])); + quest->SetHidden(atoi(row[10]) == 1 ? true : false); + client->AddPlayerQuest(quest, false, false); + } + quest->SetSaveNeeded(false); + + // Changed this to call reload with step = 0 for all quests and not + // just quests with quest flags to allow customized set up if needed + if (lua_interface) + lua_interface->CallQuestFunction(quest, "Reload", client->GetPlayer(), 0); + } + } + LoadCharacterQuestProgress(client); + } +} + +void WorldDatabase::LoadCharacterFriendsIgnoreList(Player* player) { + if (player) { + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `name`, `type` FROM `character_social` WHERE `char_id`=%u", player->GetCharacterID()); + while (result && (row = mysql_fetch_row(result))) { + if (strncmp(row[1], "FRIEND", 6) == 0) + player->AddFriend(row[0], false); + else + player->AddIgnore(row[0], false); + } + } +} + +void WorldDatabase::LoadZoneInfo(ZoneServer* zone){ + Query query; + int32 ruleset_id; + char* escaped = getEscapeString(zone->GetZoneName()); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, file, description, underworld, safe_x, safe_y, safe_z, min_status, min_level, max_level, instance_type+0, shutdown_timer, zone_motd, default_reenter_time, default_reset_time, default_lockout_time, force_group_to_zone, safe_heading, xp_modifier, ruleset_id, expansion_id, weather_allowed, sky_file, can_bind, can_gate, city_zone, can_evac FROM zones where name='%s'",escaped); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + zone->SetZoneName(escaped); + zone->SetZoneID(strtoul(row[0], NULL, 0)); + zone->SetZoneFile(row[1]); + zone->SetZoneDescription(row[2]); + zone->SetUnderWorld(atof(row[3])); + zone->SetSafeX(atof(row[4])); + zone->SetSafeY(atof(row[5])); + zone->SetSafeZ(atof(row[6])); + zone->SetMinimumStatus(atoi(row[7])); + zone->SetMinimumLevel(atoi(row[8])); + zone->SetMaximumLevel(atoi(row[9])); + int8 type = (atoi(row[10]) == 0) ? 0 : atoi(row[10]) - 1; + zone->SetInstanceType(type); + zone->SetShutdownTimer(atoul(row[11])); + + char* zone_motd = row[12]; + if (zone_motd && strlen(zone_motd) > 0) + zone->SetZoneMOTD(string(zone_motd)); + + zone->SetDefaultReenterTime(atoi(row[13])); + zone->SetDefaultResetTime(atoi(row[14])); + zone->SetDefaultLockoutTime(atoi(row[15])); + zone->SetForceGroupZoneOption(atoi(row[16])); + zone->SetSafeHeading(atof(row[17])); + zone->SetXPModifier(atof(row[18])); + + if ((ruleset_id = atoul(row[19])) > 0 && !rule_manager.SetZoneRuleSet(zone->GetZoneID(), ruleset_id)) + LogWrite(ZONE__ERROR, 0, "Zones", "Error setting rule set for zone '%s' (%u). A rule set with ID %u does not exist.", zone->GetZoneName(), zone->GetZoneID(), ruleset_id); + + // check data_version to see if client has proper expansion to enter a zone + zone->SetMinimumVersion(GetMinimumClientVersion(atoi(row[20]))); + zone->SetWeatherAllowed(atoi(row[21]) == 0 ? false : true); + + zone->SetZoneSkyFile(row[22]); + + if (zone->IsInstanceZone()) + { + if ( zone->GetInstanceID() < 1 ) + zone->SetupInstance(CreateNewInstance(zone->GetZoneID())); + else + zone->SetupInstance(zone->GetInstanceID()); + } + zone->SetCanBind(atoul(row[23])); + zone->SetCanGate(atoul(row[24])); + zone->SetCityZone(atoi(row[25])); + zone->SetCanEvac(atoul(row[26])); + } + safe_delete_array(escaped); +} + +void WorldDatabase::LoadZoneInfo(ZoneInfo* zone_info) { + Query query; + int32 ruleset_id; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT name, file, description, underworld, safe_x, safe_y, safe_z, min_status, min_level, max_level, instance_type, shutdown_timer, zone_motd, default_reenter_time, default_reset_time, default_lockout_time, force_group_to_zone, lua_script, xp_modifier, ruleset_id, expansion_id, always_loaded, city_zone, start_zone, zone_type, weather_allowed, sky_file FROM zones WHERE id = %u", zone_info->id); + if (result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + strncpy(zone_info->name, row[0], sizeof(zone_info->name)); + strncpy(zone_info->file, row[1], sizeof(zone_info->file)); + strncpy(zone_info->description, row[2], sizeof(zone_info->description)); + zone_info->underworld = atof(row[3]); + zone_info->safe_x = atof(row[4]); + zone_info->safe_y = atof(row[5]); + zone_info->safe_z = atof(row[6]); + zone_info->min_status = atoi(row[7]); + zone_info->min_level = atoi(row[8]); + zone_info->max_level = atoi(row[9]); + zone_info->instance_type = (atoi(row[10]) == 0) ? 0 : atoi(row[10]) - 1; + zone_info->shutdown_timer = atoul(row[11]); + row[12] == NULL ? strncpy(zone_info->zone_motd, "", sizeof(zone_info->zone_motd)) : strncpy(zone_info->zone_motd, row[12], sizeof(zone_info->zone_motd)); + zone_info->default_reenter_time = atoi(row[13]); + zone_info->default_reset_time = atoi(row[14]); + zone_info->default_lockout_time = atoi(row[15]); + zone_info->force_group_to_zone = atoi(row[16]); + row[17] == NULL ? strncpy(zone_info->lua_script, "", sizeof(zone_info->lua_script)) : strncpy(zone_info->lua_script, row[17], sizeof(zone_info->lua_script)); + zone_info->xp_modifier = atof(row[18]); + zone_info->ruleset_id = atoul(row[19]); + if ((ruleset_id = atoul(row[19])) > 0 && !rule_manager.SetZoneRuleSet(zone_info->id, ruleset_id)) + LogWrite(ZONE__ERROR, 0, "Zones", "Error setting rule set for zone '%s' (%u). A rule set with ID %u does not exist.", zone_info->name, zone_info->id, ruleset_id); + + zone_info->expansion_id = atoi(row[20]); + zone_info->min_version = GetMinimumClientVersion(zone_info->expansion_id); + zone_info->always_loaded = atoi(row[21]); + zone_info->city_zone = atoi(row[22]); + zone_info->start_zone = atoi(row[23]); + row[24] == NULL ? strncpy(zone_info->zone_type, "", sizeof(zone_info->zone_type)) : strncpy(zone_info->zone_type, row[24], sizeof(zone_info->zone_type)); + zone_info->weather_allowed = atoi(row[25]); + + strncpy(zone_info->sky_file, row[26], sizeof(zone_info->sky_file)); + } +} + +void WorldDatabase::SaveZoneInfo(int32 zone_id, const char* field, sint32 value) { + Query query; + query.RunQuery2(Q_UPDATE, "UPDATE `zones` SET `%s`=%i WHERE `id`=%u", field, value, zone_id); +} + +void WorldDatabase::SaveZoneInfo(int32 zone_id, const char* field, float value) { + Query query; + query.RunQuery2(Q_UPDATE, "UPDATE `zones` SET `%s`=%f WHERE `id`=%u", field, value, zone_id); +} + +void WorldDatabase::SaveZoneInfo(int32 zone_id, const char* field, const char* value) { + Query query; + query.RunQuery2(Q_UPDATE, "UPDATE `zones` SET `%s`='%s' WHERE `id`=%u", field, const_cast(getEscapeString(value)), zone_id); +} + +int32 WorldDatabase::GetZoneID(const char* name) { + int32 zone_id = 0; + Query query; + char* escaped = getEscapeString(name); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `id` FROM zones WHERE `name`=\"%s\"", escaped); + if (result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + zone_id = atoi(row[0]); + } + safe_delete_array(escaped); + return zone_id; +} + +bool WorldDatabase::GetZoneRequirements(const char* zoneName, sint16* minStatus, int16* minLevel, int16* maxLevel, int16* minVersion) { + Query query; + char* escaped = getEscapeString(zoneName); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT min_status, min_level, max_level, expansion_id FROM zones where name='%s'",escaped); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + sint16 status = (sint16)atoi(row[0]); + int16 levelMin = (int16)atoi(row[1]); + int16 levelMax = (int16)atoi(row[2]); + int8 expansion_id = (int8)atoi(row[3]); + + *minStatus = status; + *minLevel = levelMin; + *maxLevel = levelMax; + + if( expansion_id >= 40 ) // lowest client we support is RoK - exp04 + *minVersion = GetMinimumClientVersion(expansion_id); + else + *minVersion = 0; + + safe_delete_array(escaped); + return true; + } + safe_delete_array(escaped); + + return false; +} + +int16 WorldDatabase::GetMinimumClientVersion(int8 expansion_id) +{ + /* + 1 n/a Classic Expansion + 2 adv01 Bloodline Chronicles + 3 adv02 Splitpaw Saga + 10 exp01 Desert of Flames + 20 exp02 Kingdom of Sky + 21 adv04 Fallen Dynasty + 30 exp03 Echoes of Faydwer + 40 exp04 Rise of Kunark + 50 exp05 The Shadow Odyssey + 60 exp06 Sentinel's Fate + 61 halas Halas Reborn + 70 exp07 Destiny of Velious + 80 exp08 Age of Discovery + */ + + int16 minVer = 0; + + // TODO: eventually replace this with reading values from eq2expansions table + switch(expansion_id) + { + case 40: // ROK + { + minVer = 843; + break; + } + case 50: // TSO + { + minVer = 908; + break; + } + case 60: // SF + { + minVer = 1008; + break; + } + case 61: // Halas + { + minVer = 1045; + break; + } + case 70: // DoV + { + minVer = 1096; + break; + } + case 80: // AoD + { + minVer = 1144; + break; + } + case 90: // CoE + { + minVer = 1188; + break; + } + } + + return minVer; +} + +// returns Expansion Name depending on the connected client's data version +string WorldDatabase::GetExpansionIDByVersion(int16 version) +{ + /* + 0 n/a Classic Expansion + 0 adv01 Bloodline Chronicles + 0 adv02 Splitpaw Saga + 0 exp01 Desert of Flames + 0 exp02 Kingdom of Sky + 0 adv04 Fallen Dynasty + 0 exp03 Echoes of Faydwer + 843 exp04 Rise of Kunark + 908 exp05 The Shadow Odyssey + 1008 exp06 Sentinel's Fate + 1045 halas Halas Reborn + 1096 exp07 Destiny of Velious + 1142 exp08 Age of Discovery + 1188 exp09 Chains of Eternity + 9999 (and beyond) + */ + string ret = ""; + + if( version >= 9999 ) + ret = "Unknown"; + else if( version >= 1188 ) + ret = "Chains of Eternity"; + else if( version >= 1142 ) + ret = "Age of Discovery"; + else if( version >= 1096 ) + ret = "Destiny of Velious"; + else if( version >= 1045 ) + ret = "Halas Reborn"; + else if( version >= 1008 ) + ret = "Sentinel's Fate"; + else if( version >= 908 ) + ret = "The Shadow Odyssey"; + else if( version >= 843 ) + ret = "Rise of Kunark"; + else + ret = "Any"; + + return ret; +} + + +void WorldDatabase::LoadSpecialZones(){ + Query query; + ZoneServer* zone = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, name, always_loaded FROM zones where always_loaded = 1"); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + while(result && (row = mysql_fetch_row(result))){ + zone = new ZoneServer(row[1]); + LoadZoneInfo(zone); + zone->Init(); + + zone->SetAlwaysLoaded(atoi(row[2]) == 1); + } + } +} + +bool WorldDatabase::SpawnGroupRemoveAssociation(int32 group1, int32 group2){ + Query query; + query.RunQuery2(Q_DELETE, "delete FROM spawn_location_group_associations where (group_id1 = %u and group_id2 = %u) or (group_id1 = %u and group_id2 = %u)", group1, group2, group2, group1); + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(WORLD__ERROR, 0, "World", "Error in SpawnGroupRemoveSpawn query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + return true; +} + +bool WorldDatabase::SpawnGroupAddAssociation(int32 group1, int32 group2){ + Query query; + query.RunQuery2(Q_INSERT, "insert ignore into spawn_location_group_associations (group_id1, group_id2) values(%u, %u)", group1, group2); + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(WORLD__ERROR, 0, "World", "Error in SpawnGroupAddAssociation query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + return true; +} + +bool WorldDatabase::SpawnGroupAddSpawn(Spawn* spawn, int32 group_id){ + Query query; + query.RunQuery2(Q_INSERT, "insert ignore into spawn_location_group (group_id, placement_id) values(%u, %u)", group_id, spawn->GetSpawnLocationPlacementID()); + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(WORLD__ERROR, 0, "World", "Error in SpawnGroupAddSpawn query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + return true; +} + +bool WorldDatabase::SpawnGroupRemoveSpawn(Spawn* spawn, int32 group_id){ + Query query; + query.RunQuery2(Q_DELETE, "delete FROM spawn_location_group where group_id = %u and placement_id = %u", group_id, spawn->GetSpawnLocationPlacementID()); + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(WORLD__ERROR, 0, "World", "Error in SpawnGroupRemoveSpawn query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT count(group_id) FROM spawn_location_group where group_id = %u", group_id); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + if((row = mysql_fetch_row(result))){ + if(atoul(row[0]) == 0) + DeleteSpawnGroup(group_id); + } + } + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(WORLD__ERROR, 0, "World", "Error in SpawnGroupRemoveSpawn query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + return true; +} + +int32 WorldDatabase::CreateSpawnGroup(Spawn* spawn, string name) +{ + int32 group_id = 0; + Query query; + + // JA: As of 0.7.1, DB Milestone 2, Content Team needs to use group_id's from Raw Data, so start any manual group_id's > 100,000 + query.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_group (group_id, placement_id, name) SELECT IF(ISNULL(MAX(group_id))=1, 100000, MAX(group_id)+1), %u, '%s' FROM spawn_location_group", spawn->GetSpawnLocationPlacementID(), getSafeEscapeString(name.c_str()).c_str()); + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF) + return 0; + + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT max(group_id) FROM spawn_location_group"); + if(result && mysql_num_rows(result) > 0) + { + MYSQL_ROW row; + + if((row = mysql_fetch_row(result))) + { + if(row[0]) + group_id = atoul(row[0]); + } + } + + return group_id; +} + +void WorldDatabase::DeleteSpawnGroup(int32 id){ + Query query; + query.RunQuery2(Q_DELETE, "delete FROM spawn_location_group where group_id = %u", id); +} + +bool WorldDatabase::SetGroupSpawnChance(int32 id, float chance){ + Query query; + query.RunQuery2(Q_UPDATE, "replace into spawn_location_group_chances (group_id, percentage) values(%u, %f)", id, chance); + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(WORLD__ERROR, 0, "World", "Error in SetGroupSpawnChance query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + return true; +} + +int32 WorldDatabase::LoadSpawnGroupChances(ZoneServer* zone){ + Query query; + int32 count = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT slgc.group_id, slgc.percentage FROM spawn_location_group_chances slgc, spawn_location_group slg, spawn_location_placement slp where slgc.group_id = slg.group_id and slg.placement_id = slp.id and slp.zone_id = %u", zone->GetZoneID()); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + int32 group_id = 0; + float percent = 0; + while(result && (row = mysql_fetch_row(result))){ + group_id = atoul(row[0]); + percent = atof(row[1]); + zone->AddSpawnGroupChance(group_id, percent); + count++; + } + } + return count; +} + +int32 WorldDatabase::LoadSpawnLocationGroupAssociations(ZoneServer* zone){ + Query query; + int32 count = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT distinct slga.group_id1, slga.group_id2 FROM spawn_location_group_associations slga, spawn_location_group slg, spawn_location_placement slp where (slg.group_id = slga.group_id1 or slg.group_id = slga.group_id2) and slg.placement_id = slp.id and slp.zone_id = %u", zone->GetZoneID()); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + while(result && (row = mysql_fetch_row(result))){ + zone->AddSpawnGroupAssociation(atoul(row[0]), atoul(row[1])); + count++; + } + } + return count; +} + +int32 WorldDatabase::LoadSpawnLocationGroups(ZoneServer* zone){ + Query query; + int32 count = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT slg.group_id, slg.placement_id, slp.spawn_location_id FROM spawn_location_group slg, spawn_location_placement slp WHERE slg.placement_id = slp.id and slp.zone_id = %u", zone->GetZoneID()); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + int32 placement_id = 0; + int32 group_id = 0; + int32 spawn_location_id = 0; + while(result && (row = mysql_fetch_row(result))){ + group_id = atoul(row[0]); + placement_id = atoul(row[1]); + spawn_location_id = atoul(row[2]); + zone->AddSpawnGroupLocation(group_id, placement_id, spawn_location_id); + count++; + } + } + return count; +} + +int32 WorldDatabase::ProcessSpawnLocations(ZoneServer* zone, const char* sql_query, int8 type){ + int32 number = 0; + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, sql_query, zone->GetZoneID()); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + int32 spawn_location_id = 0xFFFFFFFF; + SpawnLocation* spawn_location = 0; + while(result && (row = mysql_fetch_row(result))){ + if((spawn_location_id == 0xFFFFFFFF) || atoul(row[0]) != spawn_location_id){ + if(spawn_location){ + zone->AddSpawnLocation(spawn_location_id, spawn_location); + number++; + } + spawn_location = new SpawnLocation(); + } + SpawnEntry* entry = new SpawnEntry; + + spawn_location_id = atoul(row[0]); + entry->spawn_location_id = spawn_location_id; + entry->spawn_entry_id = atoul(row[1]); + entry->spawn_type = type; + entry->spawn_id = atoul(row[9]); + entry->spawn_percentage = atof(row[10]); + entry->respawn = atoul(row[11]); + entry->expire_time = atoul(row[14]); + entry->expire_offset = atoul(row[15]); + //devn00b add stat overrides. Just a slight increase in size. Used in ZoneServer::AddNPCSpawn. + entry->lvl_override = atoul(row[19]); + entry->hp_override = atoul(row[20]); + entry->mp_override = atoul(row[21]); + entry->str_override = atoul(row[22]); + entry->sta_override = atoul(row[23]); + entry->wis_override = atoul(row[24]); + entry->int_override = atoul(row[25]); + entry->agi_override = atoul(row[26]); + entry->heat_override = atoul(row[27]); + entry->cold_override = atoul(row[28]); + entry->magic_override = atoul(row[29]); + entry->mental_override = atoul(row[30]); + entry->divine_override = atoul(row[31]); + entry->disease_override = atoul(row[32]); + entry->poison_override = atoul(row[33]); + entry->difficulty_override = atoul(row[34]); + spawn_location->x = atof(row[2]); + spawn_location->y = atof(row[3]); + spawn_location->z = atof(row[4]); + spawn_location->x_offset = atof(row[5]); + spawn_location->y_offset = atof(row[6]); + spawn_location->z_offset = atof(row[7]); + spawn_location->heading = atof(row[8]); + spawn_location->pitch = atof(row[16]); + spawn_location->roll = atof(row[17]); + spawn_location->conditional = atoi(row[18]); + spawn_location->total_percentage += entry->spawn_percentage; + spawn_location->grid_id = strtoul(row[12], NULL, 0); + spawn_location->placement_id = strtoul(row[13], NULL, 0); + spawn_location->AddSpawn(entry); + + } + if(spawn_location){ + zone->AddSpawnLocation(spawn_location_id, spawn_location); + number++; + } + } + return number; +} + +void WorldDatabase::ResetDatabase(){ + Query query; + query.RunQuery2("delete FROM table_versions where name != 'table_versions'", Q_DELETE); +} + +void WorldDatabase::EnableConstraints(){ + Query query; + query.RunQuery2("/*!40101 SET SQL_MODE=@OLD_SQL_MODE */", Q_DBMS); + query.RunQuery2("/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */", Q_DBMS); +} + +void WorldDatabase::DisableConstraints(){ + Query query; + query.RunQuery2("/*!40101 SET NAMES utf8 */", Q_DBMS); + query.RunQuery2("/*!40101 SET SQL_MODE=''*/", Q_DBMS); + query.RunQuery2("/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */", Q_DBMS); + query.RunQuery2("/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */", Q_DBMS); +} + +void WorldDatabase::LoadSpawns(ZoneServer* zone) +{ + Query query; + int32 npcs = 0, objects = 0, widgets = 0, signs = 0, ground_spawns = 0, spawn_groups = 0, spawn_group_associations = 0, spawn_group_chances = 0; + + LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter LoadSpawns"); + + npcs = ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement slp, spawn_location_name sln, spawn_location_entry sle, spawn_npcs sn where sn.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%i ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_NPC); + objects = ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement slp, spawn_location_name sln, spawn_location_entry sle, spawn_objects so where so.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%i ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_OBJECT); + widgets = ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement slp, spawn_location_name sln, spawn_location_entry sle, spawn_widgets sw where sw.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%i ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_WIDGET); + signs = ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement slp, spawn_location_name sln, spawn_location_entry sle, spawn_signs ss where ss.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%i ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_SIGN); + ground_spawns = ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement slp, spawn_location_name sln, spawn_location_entry sle, spawn_ground sg where sg.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%i ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_GROUNDSPAWN); + spawn_groups = LoadSpawnLocationGroups(zone); + spawn_group_associations = LoadSpawnLocationGroupAssociations(zone); + spawn_group_chances = LoadSpawnGroupChances(zone); + LogWrite(SPAWN__INFO, 0, "Spawn", "Loaded for zone '%s' (%u):\n\t%u NPC(s), %u Object(s), %u Widget(s)\n\t%u Sign(s), %u Ground Spawn(s), %u Spawn Group(s)\n\t%u Spawn Group Association(s), %u Spawn Group Chance(s)", zone->GetZoneName(), zone->GetZoneID(), npcs, objects, widgets, signs, ground_spawns, spawn_groups, spawn_group_associations, spawn_group_chances); + + LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit LoadSpawns"); + +} + +bool WorldDatabase::UpdateSpawnLocationSpawns(Spawn* spawn) { + Query query; + query.RunQuery2(Q_UPDATE, "update spawn_location_placement set x=%f, y=%f, z=%f, heading=%f, x_offset=%f, y_offset=%f, z_offset=%f, respawn=%u, expire_timer=%u, expire_offset=%u, grid_id=%u, pitch=%f, roll=%f where id = %u", + spawn->GetX(), spawn->GetY(), spawn->GetZ(), spawn->GetHeading(), spawn->GetXOffset(), spawn->GetYOffset(), spawn->GetZOffset(), spawn->GetRespawnTime(), spawn->GetExpireTime(), spawn->GetExpireOffsetTime(), spawn->GetLocation(), spawn->GetPitch(), spawn->GetRoll(), spawn->GetSpawnLocationPlacementID()); + if (query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF) { + LogWrite(WORLD__ERROR, 0, "World", "Error in UpdateSpawnLocationSpawns query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + return true; +} + +bool WorldDatabase::UpdateSpawnWidget(int32 widget_id, char* queryString) { + Query query; + query.RunQuery2(Q_UPDATE, "update spawn_widgets set %s where widget_id = %u", + queryString, widget_id); + if (query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF) { + LogWrite(WORLD__ERROR, 0, "World", "Error in UpdateSpawnWidget query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + return true; +} + +vector* WorldDatabase::GetSpawnNameList(const char* in_name){ + Query query; + string names = ""; + vector* ret = 0; + string name = getSafeEscapeString(in_name); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT concat(spawn.id, ', ', name) FROM spawn where name like '%%%s%%'", name.c_str()); + if(result && mysql_num_rows(result) > 0){ + ret = new vector; + MYSQL_ROW row; + int8 num = 0; + while(result && (row = mysql_fetch_row(result))){ + if(num >= 10) + break; + ret->push_back(string(row[0])); + num++; + } + char total[60] = {0}; + if(mysql_num_rows(result) > 10) + sprintf(total, "Total number of results: %u (Limited to 10)", (int32)mysql_num_rows(result)); + else + sprintf(total, "Total number of results: %u", (int32)mysql_num_rows(result)); + ret->push_back(string(total)); + } + return ret; +} +string WorldDatabase::GetZoneName(char* zone_description){ + string ret; + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT name FROM zones where description = '%s'", getSafeEscapeString(zone_description).c_str()); + if(result && mysql_num_rows(result) > 0){ + MYSQL_ROW row = mysql_fetch_row(result); + ret = string(row[0]); + } + return ret; +} + +void WorldDatabase::LoadRevivePoints(vector* revive_points, int32 zone_id){ + if(revive_points && revive_points->size() > 0){ + LogWrite(WORLD__ERROR, 0, "World", "Revive points have already been loaded for this zone!"); + return; + } + else if(!revive_points || zone_id == 0){ + LogWrite(WORLD__ERROR, 0, "World", "LoadRevivePoints called with null variables!"); + return; + } + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT respawn_zone_id, location_name, safe_x, safe_y, safe_z, heading, always_included FROM revive_points where zone_id=%u ORDER BY id asc", zone_id); + if(revive_points && result && mysql_num_rows(result) > 0){ + MYSQL_ROW row; + int32 id = 0; + RevivePoint* point = 0; + while(result && (row=mysql_fetch_row(result))){ + point = new RevivePoint; + point->id = id; + point->zone_id = atoul(row[0]); + point->location_name = string(row[1]); + point->x = atof(row[2]); + point->y = atof(row[3]); + point->z = atof(row[4]); + point->heading = atof(row[5]); + point->always_included = atoul(row[6]); + revive_points->push_back(point); + id++; + } + } +} + +int32 WorldDatabase::GetNextSpawnIDInZone(int32 zone_id) +{ + Query query; + int32 ret = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT MAX(id) FROM spawn where id LIKE '%i____'", zone_id); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + if(row[0]) + ret = atoi(row[0]) + 1; + } + if( ret == 0 ) + ret = zone_id * 10000; // there are no spawns for that zone yet, to start with the first ID + + LogWrite(WORLD__DEBUG, 0, "World", "Next Spawn ID for Zone %i: %u", zone_id, ret); + return ret; + +} + + +bool WorldDatabase::SaveSpawnInfo(Spawn* spawn){ + Query query; + string name = getSafeEscapeString(spawn->GetName()); + string suffix = getSafeEscapeString(spawn->GetSuffixTitle()); + string prefix = getSafeEscapeString(spawn->GetPrefixTitle()); + string last_name = getSafeEscapeString(spawn->GetLastName()); + if(spawn->GetDatabaseID() == 0){ + + int8 isInstanceType = (spawn->GetZone()->GetInstanceType() == Instance_Type::PERSONAL_HOUSE_INSTANCE); + + int32 new_spawn_id = GetNextSpawnIDInZone(spawn->GetZone()->GetZoneID()); + + query.RunQuery2(Q_INSERT, "insert into spawn (id, name, race, model_type, size, targetable, show_name, command_primary, command_secondary, visual_state, attackable, show_level, show_command_icon, display_hand_icon, faction_id, collision_radius, hp, power, prefix, suffix, last_name, is_instanced_spawn, merchant_min_level, merchant_max_level) values(%u, '%s', %i, %i, %i, %i, %i, %u, %u, %i, %i, %i, %i, %i, %u, %i, %u, %u, '%s', '%s', '%s', %u, %u, %u)", + new_spawn_id, name.c_str(), spawn->GetRace(), spawn->GetModelType(), spawn->GetSize(), spawn->appearance.targetable, spawn->appearance.display_name, spawn->GetPrimaryCommandListID(), spawn->GetSecondaryCommandListID(), spawn->GetVisualState(), spawn->appearance.attackable, spawn->appearance.show_level, spawn->appearance.show_command_icon, spawn->appearance.display_hand_icon, 0, spawn->appearance.pos.collision_radius, spawn->GetTotalHP(), spawn->GetTotalPower(), prefix.c_str(), suffix.c_str(), last_name.c_str(), isInstanceType, spawn->GetMerchantMinLevel(), spawn->GetMerchantMaxLevel()); + + if( new_spawn_id > 0 ) + spawn->SetDatabaseID(new_spawn_id); // use the new zone_id range + else if( query.GetLastInsertedID() > 0 ) + spawn->SetDatabaseID(query.GetLastInsertedID()); // else fall back to last_inserted_id + else + return false; // else, hang your head in shame as you are an utter failure + + if(spawn->IsNPC()){ + query.RunQuery2(Q_INSERT, "insert into spawn_npcs (spawn_id, min_level, max_level, enc_level, class_, gender, min_group_size, max_group_size, hair_type_id, facial_hair_type_id, wing_type_id, chest_type_id, legs_type_id, soga_hair_type_id, soga_facial_hair_type_id, soga_model_type, heroic_flag, action_state, mood_state, initial_state, activity_status, hide_hood, emote_state) values(%u, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i)", + spawn->GetDatabaseID(), spawn->GetLevel(), spawn->GetLevel(), spawn->GetDifficulty(), spawn->GetAdventureClass(), spawn->GetGender(), 0, 0, ((NPC*)spawn)->features.hair_type, ((NPC*)spawn)->features.hair_face_type, + ((NPC*)spawn)->features.wing_type, ((NPC*)spawn)->features.chest_type, ((NPC*)spawn)->features.legs_type, ((NPC*)spawn)->features.soga_hair_type, ((NPC*)spawn)->features.soga_hair_face_type, spawn->appearance.soga_model_type, spawn->appearance.heroic_flag, spawn->GetActionState(), spawn->GetMoodState(), spawn->GetInitialState(), spawn->GetActivityStatus(), spawn->appearance.hide_hood, spawn->appearance.emote_state); + } + else if(spawn->IsObject()){ + query.RunQuery2(Q_INSERT, "insert into spawn_objects (spawn_id) values(%u)", spawn->GetDatabaseID()); + } + else if(spawn->IsWidget()){ + Widget* widget = (Widget*)spawn; + query.RunQuery2(Q_INSERT, "insert into spawn_widgets (spawn_id, widget_id) values(%u, %u)", spawn->GetDatabaseID(), widget->GetWidgetID()); + } + else if(spawn->IsSign()){ + query.RunQuery2(Q_INSERT, "insert into spawn_signs (spawn_id, description) values(%u, 'change me')", spawn->GetDatabaseID()); + + } + else if (spawn->IsGroundSpawn()) { + query.RunQuery2(Q_INSERT, "insert into spawn_ground (spawn_id) values(%u)", spawn->GetDatabaseID()); + } + } + else{ + if(spawn->IsNPC()){ + query.RunQuery2(Q_UPDATE, "update spawn_npcs, spawn set name='%s', min_level=%i, max_level=%i, enc_level=%i, race=%i, model_type=%i, class_=%i, gender=%i, show_name=%i, attackable=%i, show_level=%i, targetable=%i, show_command_icon=%i, display_hand_icon=%i, hair_type_id=%i, facial_hair_type_id=%i, wing_type_id=%i, chest_type_id=%i, legs_type_id=%i, soga_hair_type_id=%i, soga_facial_hair_type_id=%i, soga_model_type=%i, size=%i, hp=%u, heroic_flag=%i, power=%u, collision_radius=%i, command_primary=%u, command_secondary=%u, visual_state=%i, action_state=%i, mood_state=%i, initial_state=%i, activity_status=%i, alignment=%i, faction_id=%u, hide_hood=%i, emote_state=%i, suffix ='%s', prefix='%s', last_name='%s', merchant_min_level = %u, merchant_max_level = %u where spawn_npcs.spawn_id = spawn.id and spawn.id = %u", + name.c_str(), spawn->GetLevel(), spawn->GetLevel(), spawn->GetDifficulty(), spawn->GetRace(), spawn->GetModelType(), + spawn->GetAdventureClass(), spawn->GetGender(), spawn->appearance.display_name, spawn->appearance.attackable, spawn->appearance.show_level, spawn->appearance.targetable, spawn->appearance.show_command_icon, spawn->appearance.display_hand_icon, ((NPC*)spawn)->features.hair_type, + ((NPC*)spawn)->features.hair_face_type, ((NPC*)spawn)->features.wing_type, ((NPC*)spawn)->features.chest_type, ((NPC*)spawn)->features.legs_type, ((NPC*)spawn)->features.soga_hair_type, ((NPC*)spawn)->features.soga_hair_face_type, spawn->appearance.soga_model_type, spawn->GetSize(), + spawn->GetTotalHPBase(), spawn->appearance.heroic_flag, spawn->GetTotalPowerBase(), spawn->GetCollisionRadius(), spawn->GetPrimaryCommandListID(), + spawn->GetSecondaryCommandListID(), spawn->GetVisualState(), spawn->GetActionState(), spawn->GetMoodState(), spawn->GetInitialState(), + spawn->GetActivityStatus(), ((NPC*)spawn)->GetAlignment(), spawn->GetFactionID(), spawn->appearance.hide_hood, spawn->appearance.emote_state, + suffix.c_str(), prefix.c_str(), last_name.c_str(), spawn->GetMerchantMinLevel(), spawn->GetMerchantMaxLevel(), spawn->GetDatabaseID()); + } + else if(spawn->IsObject()){ + query.RunQuery2(Q_UPDATE, "update spawn_objects, spawn set name='%s', model_type=%i, show_name=%i, targetable=%i, size=%i, command_primary=%u, command_secondary=%u, visual_state=%i, attackable=%i, show_level=%i, show_command_icon=%i, display_hand_icon=%i, collision_radius=%i, hp = %u, power = %u, device_id = %i, merchant_min_level = %u, merchant_max_level = %u where spawn_objects.spawn_id = spawn.id and spawn.id = %u", + name.c_str(), spawn->GetModelType(), spawn->appearance.display_name, spawn->appearance.targetable, spawn->GetSize(), spawn->GetPrimaryCommandListID(), spawn->GetSecondaryCommandListID(), spawn->GetVisualState(), spawn->appearance.attackable, spawn->appearance.show_level, spawn->appearance.show_command_icon, spawn->appearance.display_hand_icon, + spawn->GetCollisionRadius(), spawn->GetTotalHP(), spawn->GetTotalPower(), ((Object*)spawn)->GetDeviceID(), spawn->GetMerchantMinLevel(), spawn->GetMerchantMaxLevel(), spawn->GetDatabaseID()); + } + else if(spawn->IsWidget()){ + Widget* widget = (Widget*)spawn; + char* openSound = 0; + char* closeSound = 0; + if (widget->GetOpenSound() != NULL) openSound = (char*)widget->GetOpenSound(); else openSound = (char*)string("0").c_str(); + if (widget->GetCloseSound() != NULL) closeSound = (char*)widget->GetCloseSound(); else closeSound = (char*)string("0").c_str(); + query.RunQuery2(Q_UPDATE, "update spawn_widgets, spawn set name='%s', race=%i, model_type=%i, show_name=%i, attackable=%i, show_level=%i, show_command_icon=%i, display_hand_icon=%i, size=%i, hp=%u, power=%u, collision_radius=%i, command_primary=%u, command_secondary=%u, visual_state=%i, faction_id=%u, suffix ='%s', prefix='%s', last_name='%s',widget_id = %u,widget_x = %f,widget_y = %f,widget_z = %f,include_heading = %u,include_location = %u,icon = %u,type='%s',open_heading = %f,closed_heading = %f,open_x = %f,open_y = %f,open_z = %f,action_spawn_id = %u,open_sound_file='%s',close_sound_file='%s',open_duration = %u,close_x = %f,close_y=%f,close_z=%f,linked_spawn_id = %u,house_id = %u, merchant_min_level = %u, merchant_max_level = %u where spawn_widgets.spawn_id = spawn.id and spawn.id = %u", + name.c_str(), spawn->GetRace(), spawn->GetModelType(), spawn->appearance.display_name, spawn->appearance.attackable, spawn->appearance.show_level, spawn->appearance.show_command_icon, spawn->appearance.display_hand_icon, spawn->GetSize(), + spawn->GetTotalHP(), spawn->GetTotalPower(), spawn->GetCollisionRadius(), spawn->GetPrimaryCommandListID(), spawn->GetSecondaryCommandListID(), spawn->GetVisualState(), spawn->GetFactionID(), + suffix.c_str(), prefix.c_str(), last_name.c_str(), widget->GetWidgetID(), widget->GetX(), widget->GetY(), widget->GetZ(), widget->GetIncludeHeading(), widget->GetIncludeLocation(), widget->GetIconValue(), Widget::GetWidgetTypeNameByTypeID(widget->GetWidgetType()).c_str(), + widget->GetOpenHeading(), widget->GetClosedHeading(), widget->GetOpenX(), widget->GetOpenY(), widget->GetOpenZ(), + widget->GetActionSpawnID(), openSound, closeSound,widget->GetOpenDuration(), + widget->GetCloseX(),widget->GetCloseY(),widget->GetCloseZ(),widget->GetLinkedSpawnID(),widget->GetHouseID(), + spawn->GetMerchantMinLevel(), spawn->GetMerchantMaxLevel(), spawn->GetDatabaseID()); + } + else if(spawn->IsSign()){ + Sign* sign = (Sign*)spawn; + query.RunQuery2(Q_UPDATE, "update spawn_signs, spawn set name='%s', race=%i, model_type=%i, show_name=%i, attackable=%i, show_level=%i, show_command_icon=%i, display_hand_icon=%i, size=%i, hp=%u, power=%u, collision_radius=%i, command_primary=%u, command_secondary=%u, visual_state=%i, faction_id=%u, suffix ='%s', prefix='%s', last_name='%s', `type`='%s', zone_id = %u, widget_id = %u, title='%s', widget_x = %f, widget_y = %f, widget_z = %f, icon = %u, description='%s', sign_distance = %f, zone_x = %f, zone_y = %f, zone_z = %f, zone_heading = %f, include_heading = %u, include_location = %u, merchant_min_level = %u, merchant_max_level = %u, language = %u where spawn_signs.spawn_id = spawn.id and spawn.id = %u", + name.c_str(), spawn->GetRace(), spawn->GetModelType(), spawn->appearance.display_name, spawn->appearance.attackable, spawn->appearance.show_level, spawn->appearance.show_command_icon, spawn->appearance.display_hand_icon, spawn->GetSize(), + spawn->GetTotalHP(), spawn->GetTotalPower(), spawn->GetCollisionRadius(), spawn->GetPrimaryCommandListID(), spawn->GetSecondaryCommandListID(), spawn->GetVisualState(), spawn->GetFactionID(), + suffix.c_str(), prefix.c_str(), last_name.c_str(), sign->GetSignType() == SIGN_TYPE_GENERIC ? "Generic" : "Zone", sign->GetSignZoneID(), + sign->GetWidgetID(), sign->GetSignTitle(), sign->GetWidgetX(), sign->GetWidgetY(), sign->GetWidgetZ(), + sign->GetIconValue(), sign->GetSignDescription(), sign->GetSignDistance(), sign->GetSignZoneX(), + sign->GetSignZoneY(), sign->GetSignZoneZ(), sign->GetSignZoneHeading(), sign->GetIncludeHeading(), + sign->GetIncludeLocation(), spawn->GetMerchantMinLevel(), spawn->GetMerchantMaxLevel(), sign->GetLanguage(), spawn->GetDatabaseID()); + } + } + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(SPAWN__ERROR, 0, "Spawn", "Error in SaveSpawnInfo query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + return true; +} + +int32 WorldDatabase::SaveCombinedSpawnLocation(ZoneServer* zone, Spawn* in_spawn, const char* name){ + vector* spawns = in_spawn->GetSpawnGroup(); + uint32 spawnLocationID = 0; + if(spawns && spawns->size() > 0){ + vector::iterator itr; + map::iterator freq_itr; + Spawn* spawn = 0; + map database_spawns; + int32 total = 0; + float x_offset = GetSpawnLocationPlacementOffsetX(in_spawn->GetSpawnLocationID()); + float y_offset = GetSpawnLocationPlacementOffsetY(in_spawn->GetSpawnLocationID()); + float z_offset = GetSpawnLocationPlacementOffsetZ(in_spawn->GetSpawnLocationID()); + int32 spawn_location_id = GetNextSpawnLocation(); + spawnLocationID = spawn_location_id; + if(!name) + name = "Combine SpawnGroup Generated"; + if(!CreateNewSpawnLocation(spawn_location_id, name)){ + safe_delete(spawns); + return 0; + } + for(itr = spawns->begin();itr!=spawns->end();itr++){ + spawn = *itr; + if (spawn) { + RemoveSpawnFromSpawnLocation(spawn); + spawn->SetSpawnLocationID(spawn_location_id); + bool add = true; + for (freq_itr = database_spawns.begin(); freq_itr != database_spawns.end(); freq_itr++) { + if (spawn->GetDatabaseID() == freq_itr->first->GetDatabaseID()) { + freq_itr->second++; + total++; + add = false; + } + } + if (add) { + database_spawns[spawn] = 1; + total++; + } + } + } + for(freq_itr = database_spawns.begin(); freq_itr != database_spawns.end(); freq_itr++){ + int8 percent = (freq_itr->second*100)/total; + if(!SaveSpawnEntry(freq_itr->first, name, percent, x_offset, y_offset, z_offset, freq_itr->first == in_spawn, false)){ + safe_delete(spawns); + return 0; + } + } + for(itr=spawns->begin();itr!=spawns->end();itr++){ + spawn = *itr; + zone->RemoveSpawn(spawn, true, true, true, true, true); + } + safe_delete(spawns); + } + else{ + safe_delete(spawns); + return 0; + } + return spawnLocationID; +} + +bool WorldDatabase::SaveSpawnEntry(Spawn* spawn, const char* spawn_location_name, int8 percent, float x_offset, float y_offset, float z_offset, bool save_zonespawn, bool create_spawnlocation){ + Query query; + Query query2; + int32 count = 0; + if(create_spawnlocation){ + count = GetSpawnLocationCount(spawn->GetSpawnLocationID()); + if(count == 0){ + if(!CreateNewSpawnLocation(spawn->GetSpawnLocationID(), spawn_location_name)) + return false; + } + } + query.RunQuery2(Q_INSERT, "insert into spawn_location_entry (spawn_id, spawn_location_id, spawnpercentage) values(%u, %u, %i)", spawn->GetDatabaseID(), spawn->GetSpawnLocationID(), percent); + + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(SPAWN__ERROR, 0, "Spawn", "Error in SaveSpawnEntry query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + if(save_zonespawn){ + query2.RunQuery2(Q_INSERT, "insert into spawn_location_placement (zone_id, instance_id, spawn_location_id, x, y, z, x_offset, y_offset, z_offset, heading, grid_id) values(%u, %u, %u, %f, %f, %f, %f, %f, %f, %f, %u)", spawn->GetZone()->GetZoneID(), spawn->GetZone()->GetInstanceID(), spawn->GetSpawnLocationID(), spawn->GetX(), spawn->GetY(), spawn->GetZ(),x_offset, y_offset, z_offset, spawn->GetHeading(), spawn->GetLocation()); + if(query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(SPAWN__ERROR, 0, "Spawn", "Error in SaveSpawnEntry query '%s': %s", query2.GetQuery(), query2.GetError()); + return false; + } + spawn->SetSpawnLocationPlacementID(query2.GetLastInsertedID()); + } + return true; +} + +float WorldDatabase::GetSpawnLocationPlacementOffsetX(int32 location_id) { + Query query; + MYSQL_ROW row; + float ret = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `x_offset` FROM `spawn_location_placement` WHERE `spawn_location_id`=%u", location_id); + if (result && (row = mysql_fetch_row(result))) { + if (row[0]) + ret = atof(row[0]); + } + return ret; +} + +float WorldDatabase::GetSpawnLocationPlacementOffsetY(int32 location_id) { + Query query; + MYSQL_ROW row; + float ret = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `y_offset` FROM `spawn_location_placement` WHERE `spawn_location_id`=%u", location_id); + if (result && (row = mysql_fetch_row(result))) { + if (row[0]) + ret = atof(row[0]); + } + return ret; +} + +float WorldDatabase::GetSpawnLocationPlacementOffsetZ(int32 location_id) { + Query query; + MYSQL_ROW row; + float ret = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `z_offset` FROM `spawn_location_placement` WHERE `spawn_location_id`=%u", location_id); + if (result && (row = mysql_fetch_row(result))) { + if (row[0]) + ret = atof(row[0]); + } + return ret; +} + +bool WorldDatabase::CreateNewSpawnLocation(int32 id, const char* name){ + Query query; + if(!name) + name = "Unknown Spawn Location Name"; + string str_name = getSafeEscapeString(name); + query.RunQuery2(Q_INSERT, "insert into spawn_location_name (id, name) values(%u, '%s')", id, str_name.c_str()); + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(SPAWN__ERROR, 0, "Spawn", "Error in CreateNewSpawnLocation query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + return true; +} + +int32 WorldDatabase::GetSpawnLocationCount(int32 location, Spawn* spawn){ + Query query; + int32 ret = 0; + MYSQL_RES* result = 0; + if(spawn) + result = query.RunQuery2(Q_SELECT, "SELECT count(id) FROM spawn_location_entry where spawn_location_id=%u and spawn_id=%u", location, spawn->GetDatabaseID()); + else + result = query.RunQuery2(Q_SELECT, "SELECT count(id) FROM spawn_location_entry where spawn_location_id=%u", location); + if(result && mysql_num_rows(result) > 0){ + MYSQL_ROW row; + while(result && (row = mysql_fetch_row(result)) && row[0]){ + ret = strtoul(row[0], NULL, 0); + } + } + return ret; +} + +int32 WorldDatabase::GetNextSpawnLocation(){ + Query query; + int32 ret = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT max(id) FROM spawn_location_name"); + if(result && mysql_num_rows(result) > 0){ + MYSQL_ROW row; + while(result && (row = mysql_fetch_row(result)) && row[0]){ + ret = strtoul(row[0], NULL, 0); + } + } + ret++; + return ret; +} + +bool WorldDatabase::RemoveSpawnFromSpawnLocation(Spawn* spawn){ + Query query; + Query query2; + int32 count = GetSpawnLocationCount(spawn->GetSpawnLocationID(), spawn); + + query.RunQuery2(Q_DELETE, "delete FROM spawn_location_placement where spawn_location_id=%u", spawn->GetSpawnLocationID()); + if(count == 1) + query.RunQuery2(Q_DELETE, "delete FROM spawn_location_name where id=%u", spawn->GetSpawnLocationID()); + + query2.RunQuery2(Q_DELETE, "delete FROM spawn_location_entry where spawn_id=%u and spawn_location_id = %u", spawn->GetDatabaseID(), spawn->GetSpawnLocationID()); + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(WORLD__ERROR, 0, "World", "Error in RemoveSpawnFromSpawnLocation query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + else if(query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(SPAWN__ERROR, 0, "Spawn", "Error in RemoveSpawnFromSpawnLocation query '%s': %s", query2.GetQuery(), query.GetError()); + return false; + } + return true; +} + +map* WorldDatabase::GetZoneList(const char* name, bool is_admin) +{ + Query query; + map* ret = 0; + string zone_name = getSafeEscapeString(name); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `id`, `name` FROM zones WHERE `name` RLIKE '%s' %s", zone_name.c_str(), (is_admin)?"":" LIMIT 0,10"); + if(result && mysql_num_rows(result) > 0) + { + ret = new map; + MYSQL_ROW row; + while(result && (row = mysql_fetch_row(result))) + { + zone_name = row[1]; + (*ret)[atoi(row[0])] = zone_name; + } + } + + return ret; +} + +void WorldDatabase::UpdateStartingFactions(int32 char_id, int8 choice){ + Query query; + query.RunQuery2(Q_INSERT, "insert into character_factions (char_id, faction_id, faction_level) select %u, faction_id, value FROM starting_factions where starting_city=%i", char_id, choice); +} + +string WorldDatabase::GetStartingZoneName(int8 choice){ + Query query; + string zone_name = ""; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT name FROM zones where start_zone = %u", choice); + if(result && mysql_num_rows(result) > 0){ + MYSQL_ROW row; + while(result && (row = mysql_fetch_row(result))){ + zone_name = string(row[0]); + } + } + return zone_name; +} + +void WorldDatabase::UpdateStartingZone(int32 char_id, int8 class_id, int8 race_id, PacketStruct* create) +{ + Query query,query2; + + int32 packetVersion = create->GetVersion(); + int8 choice = create->getType_int8_ByName("starting_zone"); // 0 = far journey, 1 = isle of refuge + int8 deity = create->getType_int8_ByName("deity"); // aka 'alignment' for early DOF, 0 = evil, 1 = good + int32 startingZoneRuleFlag = rule_manager.GetGlobalRule(R_World, StartingZoneRuleFlag)->GetInt32(); + bool enforceRacialAlignment = rule_manager.GetGlobalRule(R_World, EnforceRacialAlignment)->GetBool(); + + if((startingZoneRuleFlag == 1 || startingZoneRuleFlag == 2) && packetVersion > 561) + { + LogWrite(PLAYER__INFO, 0, "Player", "Starting zone rule flag %u override choice %u to deity value of 0", startingZoneRuleFlag, choice); + choice = 0; + } + + LogWrite(PLAYER__INFO, 0, "Player", "Adding default zone for race: %i, class: %i for char_id: %u (choice: %i), deity(alignment): %u, version: %u.", race_id, class_id, char_id, choice, deity, packetVersion); + + // first, check to see if there is a starting_zones record for this race/class/choice combo (now using extended Archetype/BaseClass/Class combos + MYSQL_RES* result = 0; + + string whereRuleFlag(""); + if(startingZoneRuleFlag > 0) + whereRuleFlag = string(" AND ruleflag & " + std::to_string(startingZoneRuleFlag)); + + string syntaxSelect("SELECT z.name, sz.zone_id, z.safe_x, z.safe_y, z.safe_z, sz.x, sz.y, sz.z, sz.heading, sz.is_instance, z.city_zone, sz.start_alignment FROM"); + if ( class_id == 0 ) + result = query.RunQuery2(Q_SELECT, "%s starting_zones sz, zones z WHERE sz.zone_id = z.id AND class_id = 255 AND race_id IN (%i, 255) AND deity IN (%i, 255) AND choice = %u AND (min_version = 0 or min_version <= %u) AND (max_version = 0 or max_version >= %u)%s", + syntaxSelect.c_str(), race_id, deity, choice, packetVersion, packetVersion, whereRuleFlag.c_str()); + else + result = query.RunQuery2(Q_SELECT, "%s starting_zones sz, zones z WHERE sz.zone_id = z.id AND class_id IN (%i, %i, %i, 255) AND race_id IN (%i, 255) AND deity IN (%i, 255) AND choice IN (%i, 255) AND (min_version = 0 or min_version <= %u) AND (max_version = 0 or max_version >= %u)%s", + syntaxSelect.c_str(), classes.GetBaseClass(class_id), classes.GetSecondaryBaseClass(class_id), class_id, race_id, deity, choice, packetVersion, packetVersion, whereRuleFlag.c_str()); + + if(result && mysql_num_rows(result) > 0) + { + string zone_name = "ERROR"; + MYSQL_ROW row; + + bool zoneSet = false; + + float safeX = 0.0f, safeY = 0.0f, safeZ = 0.0f, x = 0.0f, y = 0.0f, z = 0.0f, heading = 0.0f; + int8 is_instance = 0; + int32 zone_id = 0; + int32 instance_id = 0; + int8 starting_city = 0; + sint8 start_alignment = 0; + + if( result && (row = mysql_fetch_row(result)) ) + { + int8 i=0; + + zoneSet = true; + + zone_name = string(row[i++]); + + zone_id = atoul(row[i++]); + + safeX = atof(row[i++]); + safeY = atof(row[i++]); + safeZ = atof(row[i++]); + + x = atof(row[i++]); + y = atof(row[i++]); + z = atof(row[i++]); + + if ( x == -999999.0f && y == -999999.0f && z == -999999.0f) + { + x = safeX; + y = safeY; + z = safeZ; + } + + heading = atof(row[i++]); + + if(heading == -999999.0f ) + heading = 0.0f; + + is_instance = atoul(row[i++]); + + starting_city = atoul(row[i++]); + + start_alignment = atoi(row[i++]); + } + + + // all EQ2 clients hard-code these restrictions in some form (later clients added Neutral instead of good/evil only) + // start with good races only + if(enforceRacialAlignment && (race_id == DWARF || race_id == FROGLOK || race_id == HALFLING || + race_id == HIGH_ELF || race_id == WOOD_ELF || race_id == FAE)) { + start_alignment = ALIGNMENT_GOOD; // always should good + } + // we drop into this case because it is a special check compared to the straigt good/evil checks on top and below + else if(start_alignment == ALIGNMENT_EVIL && deity == ALIGNMENT_GOOD) { + if (enforceRacialAlignment && (race_id == AERAKYN || race_id == RATONGA || race_id == BARBARIAN || race_id == ERUDITE || + race_id == HUMAN || race_id == VAMPIRE || race_id == HALF_ELF || race_id == GNOME || race_id == KERRA)) { + if(zone_id == 21 || zone_id == 25 || zone_id == 26 || zone_id == 27) { // far journey zones + start_alignment = deity; // inheriting the clients alignment of 'good' due to the fact we start in far journey and it is a shared instance right now (farjourneyfreeport) + } + // otherwise we use the starting zone alignment since these particular races can be evil which will be set in start_alignment of 0 within starting_zones (neriak, freeport, etc.) + } + else if(enforceRacialAlignment) { + LogWrite(WORLD__WARNING, 0, "World", "Starting alignment seems unexpected, zone id %u, race id %u, deity(alignment) choice: %u, start alignment: %i", zone_id, race_id, deity, start_alignment); + } + } + // anyone else is simply evil with the enforcement check + else if(enforceRacialAlignment && (race_id != DWARF && race_id != FROGLOK && race_id != HALFLING && + race_id != HIGH_ELF && race_id != WOOD_ELF && race_id != FAE)) { + start_alignment = ALIGNMENT_EVIL; + } + + if(is_instance) // should only be true if we get a result + { + // this will force a pre-load + ZoneServer* instance_zone = zone_list.GetByInstanceID(0, zone_id); + if (instance_zone) { + instance_id = CreateNewInstance(zone_id); + AddCharacterInstance(char_id, instance_id, string(instance_zone->GetZoneName()), instance_zone->GetInstanceType(), Timer::GetUnixTimeStamp(), 0, instance_zone->GetDefaultLockoutTime(), instance_zone->GetDefaultReenterTime()); + // make sure we inherit the instance id setup in the AddCharacterInstance + instance_zone->SetupInstance(instance_id); + } + } + + query2.RunQuery2(Q_UPDATE, "UPDATE characters SET current_zone_id = %u, x = %f, y = %f, z = %f, heading = %f, starting_city = %i, instance_id = %u, alignment = %u WHERE id = %u", + zone_id, x, y, z, heading, starting_city, instance_id, start_alignment, char_id); + + if(query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(PLAYER__ERROR, 0, "Player", "Error in UpdateStartingZone custom starting_zones, query: '%s': %s", query2.GetQuery(), query2.GetError()); + return; + } + + if(query2.GetAffectedRows() > 0) + { + LogWrite(PLAYER__INFO, 0, "Player", "Setting New Character Starting Zone to '%s' with location %f, %f, %f and heading %f FROM starting_zones table.", zone_name.c_str(), x, y, z, heading); + return; + } + } + else + { + // there was no matching starting_zone value, so use default 'choice' starting city + query2.RunQuery2(Q_UPDATE, "UPDATE characters c, zones z SET c.current_zone_id = z.id, c.x = z.safe_x, c.y = z.safe_y, c.z = z.safe_z, c.starting_city = %i WHERE z.start_zone = %i and c.id = %u", + choice, choice, char_id); + + if(query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF) + { + LogWrite(PLAYER__ERROR, 0, "Player", "Error in UpdateStartingZone player choice, query: '%s': %s", query2.GetQuery(), query2.GetError()); + return; + } + + if(query2.GetAffectedRows() > 0) + { + LogWrite(PLAYER__DEBUG, 0, "Player", "Setting New Character Starting Zone to '%s' FROM player choice.", GetStartingZoneName(choice).c_str()); + return; + } + } + + // if we are here, it's a bad thing. zone tables have no start_city values to match client 'choice', so throw the player into zone according to R_World::DefaultStartingZoneID rule. + // shout a few warnings so the admin fixes this asap! + int16 default_zone_id = rule_manager.GetGlobalRule(R_World, DefaultStartingZoneID)->GetInt16(); + + LogWrite(WORLD__WARNING, 0, "World", "No Starting City defined for player choice: %i! BAD! BAD! BAD! Defaulting player to zone %i.", choice, default_zone_id); + + query.RunQuery2(Q_UPDATE, "UPDATE characters c, zones z SET c.current_zone_id = z.id, c.x = z.safe_x, c.y = z.safe_y, c.z = z.safe_z, c.heading = z.safe_heading, c.starting_city = 1 WHERE z.id = %i and c.id = %u", default_zone_id, char_id); + + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF) + { + LogWrite(PLAYER__ERROR, 0, "Player", "Error in UpdateStartingZone default zone %i, query: '%s': %s", default_zone_id, query.GetQuery(), query.GetError()); + return; + } + + if (query.GetAffectedRows() > 0) { + string zone_name = GetZoneName(1); + if(zone_name.length() > 0) + LogWrite(PLAYER__DEBUG, 0, "Player", "Setting New Character Starting Zone to '%s' due to no valid options!", zone_name.c_str()); + else + LogWrite(PLAYER__DEBUG, 0, "Player", "Unable to set New Character Starting Zone due to no valid options!"); + } + + return; +} + +void WorldDatabase::UpdateStartingItems(int32 char_id, int8 class_id, int8 race_id, bool base_class){ + LogWrite(PLAYER__DEBUG, 0, "Player", "Adding default items for race: %i, class: %i for char_id: %u", race_id, class_id, char_id); + Query query; + Query query2; + vector items; + vector bags; + map total_slots; + map slots_left; + map equip_slots; + map item_list; + int32 item_id = 0; + Item* item = 0; + StartingItem* starting_item = 0; + //first get a list of the starting items for the character + MYSQL_RES* result = 0; + /*if(!base_class) + result = query2.RunQuery2(Q_SELECT, "SELECT `type`, item_id, creator, condition_, attuned, count FROM starting_items where (class_id=%i and race_id=%i) or (class_id=%i and race_id=255) or (class_id=255 and race_id=%i) or (class_id=255 and race_id=255) ORDER BY id", class_id, race_id, class_id, race_id); + else + result = query2.RunQuery2(Q_SELECT, "SELECT `type`, item_id, creator, condition_, attuned, count FROM starting_items where (class_id=%i and race_id=%i) or (class_id=%i and race_id=255) ORDER BY id", class_id, race_id, class_id);*/ + result = query2.RunQuery2(Q_SELECT, "SELECT `type`, item_id, creator, condition_, attuned, count FROM starting_items WHERE class_id IN (%i, %i, %i, 255) AND race_id IN (%i, 255) ORDER BY id", classes.GetBaseClass(class_id), classes.GetSecondaryBaseClass(class_id), class_id, race_id); + if(result && mysql_num_rows(result) > 0){ + MYSQL_ROW row; + while(result && (row = mysql_fetch_row(result))){ + item_id = atoul(row[1]); + item = master_item_list.GetItem(item_id); + if(item){ + starting_item = &(item_list[item]); + starting_item->type = (row[0]) ? string(row[0]) : ""; + starting_item->item_id = atoul(row[1]); + starting_item->creator = (row[2]) ? string(row[2]) : ""; + starting_item->condition = atoi(row[3]); + starting_item->attuned = atoi(row[4]); + starting_item->count = atoi(row[5]); + item = master_item_list.GetItem(starting_item->item_id); + if(item){ + if(bags.size() < NUM_INV_SLOTS && item->IsBag() && item->details.num_slots > 0) + bags.push_back(item); + else + items.push_back(item); + } + } + } + } + slots_left[0] = NUM_INV_SLOTS; + //next create the bags in the inventory + for(int8 i=0;idetails.num_slots; + total_slots[query.GetLastInsertedID()] = item->details.num_slots; + slots_left[0]--; + } + map::iterator itr; + int32 inv_slot = 0; + int8 slot = 0; + //finally process the rest of the items, placing them in the first available slot + for(int32 x=0;xsecond > 0){ + if(itr->first == 0 && slots_left.size() > 1) //we want items to go into bags first, then inventory after bags are full + continue; + inv_slot = itr->first; + slot = total_slots[itr->first] - itr->second; + itr->second--; + if(itr->second == 0) + slots_left.erase(itr); + break; + } + } + query.RunQuery2(Q_INSERT, "insert into character_items (char_id, type, slot, item_id, creator, condition_, attuned, bag_id, count) values(%u, '%s', %i, %u, '%s', %i, %i, %u, %i)", + char_id, item_list[item].type.c_str(), slot, item_list[item].item_id, getSafeEscapeString(item_list[item].creator.c_str()).c_str(), item_list[item].condition, item_list[item].attuned, inv_slot, item_list[item].count); + } + else{ //EQUIPPED Items + for(int8 i=0;islot_data.size();i++){ + if(equip_slots.count(item->slot_data[i]) == 0){ + equip_slots[item->slot_data[i]] = true; + query.RunQuery2(Q_INSERT, "insert into character_items (char_id, type, slot, item_id, creator, condition_, attuned, bag_id, count) values(%u, '%s', %i, %u, '%s', %i, %i, %u, %i)", + char_id, item_list[item].type.c_str(), item->slot_data[i], item_list[item].item_id, getSafeEscapeString(item_list[item].creator.c_str()).c_str(), item_list[item].condition, item_list[item].attuned, 0, item_list[item].count); + break; + } + } + } + } +} + +void WorldDatabase::UpdateStartingSkills(int32 char_id, int8 class_id, int8 race_id) +{ + Query query; + LogWrite(PLAYER__DEBUG, 0, "Player", "Adding default skills for race: %i, class: %i for char_id: %u", race_id, class_id, char_id); + query.RunQuery2(Q_INSERT, "INSERT IGNORE INTO character_skills (char_id, skill_id, current_val, max_val) SELECT %u, skill_id, current_val, max_val FROM starting_skills WHERE class_id IN (%i, %i, %i, 255) AND race_id IN (%i, 255)", + char_id, classes.GetBaseClass(class_id), classes.GetSecondaryBaseClass(class_id), class_id, race_id); +} + +void WorldDatabase::UpdateStartingSpells(int32 char_id, int8 class_id, int8 race_id){ + Query query; + LogWrite(PLAYER__DEBUG, 0, "Player", "Adding default spells for race: %i, class: %i for char_id: %u", race_id, class_id, char_id); + query.RunQuery2(Q_INSERT, "INSERT IGNORE INTO character_spells (char_id, spell_id, tier, knowledge_slot) SELECT %u, spell_id, tier, knowledge_slot FROM starting_spells WHERE class_id IN (%i, %i, %i, 255) AND race_id IN (%i, 255)", + char_id, classes.GetBaseClass(class_id), classes.GetSecondaryBaseClass(class_id), class_id, race_id); +} + +void WorldDatabase::UpdateStartingSkillbar(int32 char_id, int8 class_id, int8 race_id){ + Query query; + LogWrite(PLAYER__DEBUG, 0, "Player", "Adding default skillbar for race: %i, class: %i for char_id: %u", race_id, class_id, char_id); + query.RunQuery2(Q_INSERT, "INSERT IGNORE INTO character_skillbar (char_id, type, hotbar, spell_id, slot, text_val) SELECT %u, type, hotbar, spell_id, slot, text_val FROM starting_skillbar WHERE class_id IN (%i, %i, %i, 255) AND race_id IN (%i, 255)", + char_id, classes.GetBaseClass(class_id), classes.GetSecondaryBaseClass(class_id), class_id, race_id); +} + +void WorldDatabase::UpdateStartingTitles(int32 char_id, int8 class_id, int8 race_id, int8 gender_id) { + Query query; + LogWrite(PLAYER__DEBUG, 0, "Player", "Adding default titles for race: %i, class: %i, gender: %i for char_id: %u", race_id, class_id, gender_id, char_id); + query.RunQuery2(Q_INSERT, "INSERT IGNORE INTO character_titles (char_id, title_id) SELECT %u, title_id FROM starting_titles WHERE class_id IN (%i, %i, %i, 255) AND race_id IN (%i, 255) and gender_id IN (%i, 255)", + char_id, classes.GetBaseClass(class_id), classes.GetSecondaryBaseClass(class_id), class_id, race_id, gender_id); +} + +string WorldDatabase::GetZoneDescription(int32 id){ + Query query; + string ret = ""; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT description FROM zones where id = %u", id); + if(result && mysql_num_rows(result) > 0){ + MYSQL_ROW row; + row = mysql_fetch_row(result); + ret = string(row[0]); + } + return ret; +} + +string WorldDatabase::GetZoneName(int32 id){ + if (zone_names.count(id) > 0){ + return zone_names[id]; + } + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `name` FROM zones where `id` = %u", id); + if(result && mysql_num_rows(result) > 0){ + MYSQL_ROW row; + row = mysql_fetch_row(result); + zone_names[id] = row[0]; + return zone_names[id]; + } + return string(""); +} + +bool WorldDatabase::VerifyZone(const char* name){ + Query query; + char* escaped = getEscapeString(name); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT name FROM zones where name='%s'",escaped); + safe_delete_array(escaped); + if(result && mysql_num_rows(result) > 0) + return true; + else + return false; +} + +int8 WorldDatabase::GetInstanceTypeByZoneID(int32 zoneID) +{ + + std::map::iterator itr = zone_instance_types.find(zoneID); + if(itr != zone_instance_types.end()) + return itr->second; + + DatabaseResult result; + int8 ret = 0; + + LogWrite(INSTANCE__DEBUG, 0, "Instance", "Getting instances for zone_id %u", zoneID); + + if( !database_new.Select(&result, "SELECT instance_type+0 FROM zones WHERE id = %u", zoneID) ) + { + LogWrite(INSTANCE__ERROR, 0, "Instance", "Error in GetInstanceTypeByZoneID() '%s': %i", database_new.GetErrorMsg(), database_new.GetError()); + return ret; + } + + if( result.GetNumRows() > 0 ) + { + result.Next(); + ret = (result.GetInt8Str("instance_type+0") == 0) ? 0 : result.GetInt8Str("instance_type+0") - 1; + zone_instance_types.insert(make_pair(zoneID, ret)); + LogWrite(INSTANCE__DEBUG, 0, "Instance", "Found instance type %i for zone_id %u", ret, zoneID); + } + else + LogWrite(INSTANCE__DEBUG, 0, "Instance", "No instances found for zone_id %u", zoneID); + + return ret; +} + +void WorldDatabase::Save(Client* client){ + Query query; + Player* player = client->GetPlayer(); + if(!player->CheckPlayerInfo()) + return; + + int32 instance_id = 0; + if ( client->GetCurrentZone ( ) != NULL ) + instance_id = client->GetCurrentZone()->GetInstanceID(); + + int32 zone_id = 0; + if(client->GetCurrentZone()) + zone_id = client->GetCurrentZone()->GetZoneID(); + query.AddQueryAsync(client->GetCharacterID(), this, Q_UPDATE, "update characters set current_zone_id=%u, x=%f, y=%f, z=%f, heading=%f, level=%i,instance_id=%i,last_saved=%i, `class`=%i, `tradeskill_level`=%i, `tradeskill_class`=%i, `group_id`=%u, deity = %u, alignment = %u where id = %u", zone_id, player->GetX(), player->GetY(), player->GetZ(), player->GetHeading(), player->GetLevel(), instance_id, client->GetLastSavedTimeStamp(), client->GetPlayer()->GetAdventureClass(), client->GetPlayer()->GetTSLevel(), client->GetPlayer()->GetTradeskillClass(), client->GetPlayer()->GetGroupMemberInfo() ? client->GetPlayer()->GetGroupMemberInfo()->group_id : client->GetRejoinGroupID(), client->GetPlayer()->GetDeity(), client->GetPlayer()->GetInfoStruct()->get_alignment(), client->GetCharacterID()); + query.AddQueryAsync(client->GetCharacterID(), this, Q_UPDATE, "update character_details set hp=%u, power=%u, str=%i, sta=%i, agi=%i, wis=%i, intel=%i, heat=%i, cold=%i, magic=%i, mental=%i, divine=%i, disease=%i, poison=%i, coin_copper=%u, coin_silver=%u, coin_gold=%u, coin_plat=%u, max_hp = %u, max_power=%u, xp = %u, xp_needed = %u, xp_debt = %f, xp_vitality = %f, tradeskill_xp = %u, tradeskill_xp_needed = %u, tradeskill_xp_vitality = %f, bank_copper = %u, bank_silver = %u, bank_gold = %u, bank_plat = %u, status_points = %u, bind_zone_id=%u, bind_x = %f, bind_y = %f, bind_z = %f, bind_heading = %f, house_zone_id=%u, combat_voice = %i, emote_voice = %i, biography='%s', flags=%u, flags2=%u, last_name='%s', assigned_aa = %i, unassigned_aa = %i, tradeskill_aa = %i, unassigned_tradeskill_aa = %i, prestige_aa = %i, unassigned_prestige_aa = %i, tradeskill_prestige_aa = %i, unassigned_tradeskill_prestige_aa = %i, pet_name = '%s' where char_id = %u", + player->GetHP(), player->GetPower(), player->GetStrBase(), player->GetStaBase(), player->GetAgiBase(), player->GetWisBase(), player->GetIntBase(), player->GetHeatResistanceBase(), player->GetColdResistanceBase(), player->GetMagicResistanceBase(), + player->GetMentalResistanceBase(), player->GetDivineResistanceBase(), player->GetDiseaseResistanceBase(), player->GetPoisonResistanceBase(), player->GetCoinsCopper(), player->GetCoinsSilver(), player->GetCoinsGold(), player->GetCoinsPlat(), player->GetTotalHPBase(), player->GetTotalPowerBase(), player->GetXP(), player->GetNeededXP(), player->GetXPDebt(), player->GetXPVitality(), player->GetTSXP(), player->GetNeededTSXP(), player->GetTSXPVitality(), player->GetBankCoinsCopper(), + player->GetBankCoinsSilver(), player->GetBankCoinsGold(), player->GetBankCoinsPlat(), player->GetStatusPoints(), client->GetPlayer()->GetPlayerInfo()->GetBindZoneID(), client->GetPlayer()->GetPlayerInfo()->GetBindZoneX(), client->GetPlayer()->GetPlayerInfo()->GetBindZoneY(), client->GetPlayer()->GetPlayerInfo()->GetBindZoneZ(), client->GetPlayer()->GetPlayerInfo()->GetBindZoneHeading(), client->GetPlayer()->GetPlayerInfo()->GetHouseZoneID(), + client->GetPlayer()->GetCombatVoice(), client->GetPlayer()->GetEmoteVoice(), getSafeEscapeString(client->GetPlayer()->GetBiography().c_str()).c_str(), player->GetFlags(), player->GetFlags2(), client->GetPlayer()->GetLastName(), + client->GetPlayer()->GetAssignedAA(), client->GetPlayer()->GetUnassignedAA(), client->GetPlayer()->GetTradeskillAA(), client->GetPlayer()->GetUnassignedTradeskillAA(), client->GetPlayer()->GetPrestigeAA(), + client->GetPlayer()->GetUnassignedPretigeAA(), client->GetPlayer()->GetTradeskillPrestigeAA(), client->GetPlayer()->GetUnassignedTradeskillPrestigeAA(), client->GetPlayer()->GetInfoStruct()->get_pet_name().c_str(), client->GetCharacterID()); + map::iterator itr; + map* friends = player->GetFriends(); + if(friends && friends->size() > 0){ + for(itr = friends->begin(); itr != friends->end(); itr++){ + if(itr->second == 1){ + query.AddQueryAsync(client->GetCharacterID(), this, Q_INSERT, "insert ignore into character_social (char_id, name, type) values(%u, '%s', 'FRIEND')", client->GetCharacterID(), getSafeEscapeString(itr->first.c_str()).c_str()); + itr->second = 0; + } + else if(itr->second == 2){ + query.AddQueryAsync(client->GetCharacterID(), this, Q_DELETE, "delete FROM character_social where char_id = %u and name = '%s'", client->GetCharacterID(), getSafeEscapeString(itr->first.c_str()).c_str()); + itr->second = 3; + } + } + } + map* ignored = player->GetIgnoredPlayers(); + if(ignored && ignored->size() > 0){ + for(itr = ignored->begin(); itr != ignored->end(); itr++){ + if(itr->second == 1){ + query.AddQueryAsync(client->GetCharacterID(), this, Q_INSERT, "insert ignore into character_social (char_id, name, type) values(%u, '%s', 'IGNORE')", client->GetCharacterID(), getSafeEscapeString(itr->first.c_str()).c_str()); + itr->second = 0; + } + else if(itr->second == 2){ + query.AddQueryAsync(client->GetCharacterID(), this, Q_DELETE, "delete FROM character_social where char_id = %u and name = '%s'", client->GetCharacterID(), getSafeEscapeString(itr->first.c_str()).c_str()); + itr->second = 3; + } + } + } + SavePlayerFactions(client); + SaveCharacterQuests(client); + SaveCharacterSkills(client); + SavePlayerSpells(client); + SavePlayerMail(client); + SavePlayerCollections(client); + client->SaveQuestRewardData(); + + LogWrite(PLAYER__INFO, 3, "Player", "Player '%s' (%u) data saved.", player->GetName(), player->GetCharacterID()); +} + +void WorldDatabase::LoadEntityCommands(ZoneServer* zone) { + int32 total = 0; + int32 id = 0; + EntityCommand* command = 0; + + DatabaseResult result; + if (!database_new.Select(&result, "SELECT `command_list_id`, `command_text`, `distance`, `command`, `error_text`, `cast_time`, `spell_visual` FROM `entity_commands` ORDER BY `id`")) { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return; + } + + while (result.Next()) { + command = new EntityCommand; + + id = result.GetInt32(0); + command->name = result.GetString(1); + command->distance = result.GetFloat(2); + command->command = result.GetString(3); + command->error_text = result.GetString(4); + command->cast_time = result.GetInt16(5); + command->spell_visual = result.GetInt32(6); + command->default_allow_list = true; + + zone->SetEntityCommandList(id, command); + LogWrite(COMMAND__DEBUG, 5, "Command", "---Loading Command: '%s' (%s)", command->name.c_str(), command->command.c_str()); + + total++; + } + LogWrite(COMMAND__DEBUG, 0, "Command", "--Loaded %i entity command(s)", total); +} + +void WorldDatabase::LoadFactionAlliances() +{ + LogWrite(FACTION__DEBUG, 1, "Faction", "-Loading faction alliances..."); + int32 total = 0; + int32 fTotal = 0; + int32 hTotal = 0; + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT faction_id, friend_faction, hostile_faction FROM faction_alliances"); + + if(result && mysql_num_rows(result) > 0) + { + MYSQL_ROW row; + int32 faction_id = 0; + int32 friendly_id = 0; + int32 hostile_id = 0; + + while(result && (row = mysql_fetch_row(result))) + { + faction_id = atoul(row[0]); + friendly_id = atoul(row[1]); + hostile_id = atoul(row[2]); + + if(friendly_id > 0) + { + master_faction_list.AddFriendlyFaction(faction_id, friendly_id); + fTotal++; + LogWrite(FACTION__DEBUG, 5, "Faction", "---Faction %i is friendly towards %i", faction_id, friendly_id); + } + if(hostile_id > 0) + { + master_faction_list.AddHostileFaction(faction_id, hostile_id); + hTotal++; + LogWrite(FACTION__DEBUG, 5, "Faction", "---Faction %i is hostile towards %i", faction_id, hostile_id); + } + total++; + } + } + LogWrite(FACTION__DEBUG, 3, "Faction", "--Loaded %u Alliances: %i friendly, %i hostile", total, fTotal, hTotal); +} + +bool WorldDatabase::UpdateSpawnScriptData(int32 spawn_id, int32 spawn_location_id, int32 spawnentry_id, const char* name){ + bool ret = false; + if((spawn_id > 0 || spawn_location_id > 0 || spawnentry_id > 0) && name){ + Query query, query2; + int32 row_id = 0; + if(spawn_id > 0){ + query.RunQuery2(Q_DELETE, "DELETE FROM spawn_scripts where spawn_id=%u", spawn_id); + query2.RunQuery2(Q_INSERT, "INSERT into spawn_scripts (spawn_id, lua_script) values(%u, '%s')", spawn_id, getSafeEscapeString(name).c_str()); + if(query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF) + LogWrite(LUA__ERROR, 0, "LUA", "Error in UpdateSpawnScriptData, Query: %s, Error: %s", query2.GetQuery(), query2.GetError()); + else{ + row_id = query2.GetLastInsertedID(); + if(row_id > 0) + world.AddSpawnScript(row_id, name); + ret = true; + } + } + else if(spawn_location_id > 0){ + query.RunQuery2(Q_DELETE, "DELETE FROM spawn_scripts where spawn_location_id=%u", spawn_location_id); + query2.RunQuery2(Q_INSERT, "INSERT into spawn_scripts (spawn_location_id, lua_script) values(%u, '%s')", spawn_location_id, getSafeEscapeString(name).c_str()); + if(query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF) + LogWrite(LUA__ERROR, 0, "LUA", "Error in UpdateSpawnScriptData, Query: %s, Error: %s", query2.GetQuery(), query2.GetError()); + else{ + row_id = query2.GetLastInsertedID(); + if(row_id > 0) + world.AddSpawnLocationScript(row_id, name); + ret = true; + } + } + else if(spawnentry_id > 0){ + query.RunQuery2(Q_DELETE, "DELETE FROM spawn_scripts where spawnentry_id=%u", spawnentry_id); + query2.RunQuery2(Q_INSERT, "INSERT into spawn_scripts (spawnentry_id, lua_script) values(%u, '%s')", spawnentry_id, getSafeEscapeString(name).c_str()); + if(query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF) + LogWrite(LUA__ERROR, 0, "LUA", "Error in UpdateSpawnScriptData, Query: %s, Error: %s", query2.GetQuery(), query2.GetError()); + else{ + row_id = query2.GetLastInsertedID(); + if(row_id > 0) + world.AddSpawnEntryScript(row_id, name); + ret = true; + } + } + } + return ret; +} + +void WorldDatabase::LoadSpawnScriptData() { + int32 total = 0; + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT spawn_id, spawnentry_id, spawn_location_id, lua_script FROM spawn_scripts"); + if(result && mysql_num_rows(result) > 0) + { + MYSQL_ROW row; + int32 spawn_id = 0; + int32 spawnentry_id = 0; + int32 spawn_location_id = 0; + + while(result && (row = mysql_fetch_row(result))) + { + spawn_id = atoul(row[0]); + spawnentry_id = atoul(row[1]); + spawn_location_id = atoul(row[2]); + string spawn_script = string(row[3]); + + if(spawnentry_id > 0) + { + world.AddSpawnEntryScript(spawnentry_id, row[3]); + total++; + } + else if(spawn_location_id > 0) + { + world.AddSpawnLocationScript(spawn_location_id, row[3]); + total++; + } + else if(spawn_id > 0) + { + world.AddSpawnScript(spawn_id, row[3]); + total++; + } + else { + if(row[3]) + LogWrite(LUA__ERROR, 0, "LUA", "Invalid Entry in spawn_scripts table for lua_script '%s' (spawn_id, spawnentry_id and spawn_location_id are all 0)", row[3]); + else + LogWrite(LUA__ERROR, 0, "LUA", "Invalid Entry in spawn_scripts table."); + } + + spawn_id = 0; + spawnentry_id = 0; + spawn_location_id = 0; + } + } + LogWrite(LUA__DEBUG, 0, "LUA", "\tLoaded %u SpawnScript%s", total, total == 1 ? "" : "s"); +} + +void WorldDatabase::LoadZoneScriptData() { + Query query; + int32 total = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `id`, `lua_script` FROM `zones`"); + if (result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + while(result && (row = mysql_fetch_row(result))) { + if (row[1]) { + int32 zone_id = atoul(row[0]); + string zone_script = string(row[1]); + if (zone_id > 0 && zone_script.length() > 0) { + + LogWrite(LUA__DEBUG, 5, "LUA", "ZoneScript: %s loaded.", zone_script.c_str()); + + world.AddZoneScript(zone_id, zone_script.c_str()); + total++; + } + else if (zone_id > 0) { + + string tmpScript; + tmpScript.append("ZoneScripts/"); + string zonename = GetZoneName(zone_id); + tmpScript.append(zonename); + tmpScript.append(".lua"); + struct stat buffer; + bool fileExists = (stat(tmpScript.c_str(), &buffer) == 0); + if (fileExists) + { + LogWrite(LUA__INFO, 0, "LUA", "No zonescript file described in the database, overriding with ZoneScript %s for Zone ID %u", (char*)tmpScript.c_str(), zone_id); + world.AddZoneScript(zone_id, tmpScript.c_str()); + } + } + } + } + } + LogWrite(LUA__DEBUG, 0, "LUA", "\tLoaded %u ZoneScript%s", total, total == 1 ? "" : "s"); +} + +int32 WorldDatabase::LoadSpellScriptData() { + Query query; + MYSQL_ROW row; + int32 count; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `lua_script` FROM `spells`"); // WHERE is_active = 1 + + while (result && (row = mysql_fetch_row(result))) { + if (row[0] && strlen(row[0]) > 0) { + if (lua_interface->LoadLuaSpell(row[0])) + LogWrite(SPELL__DEBUG, 5, "Spells", "SpellScript: %s loaded.", row[0]); + } + } + + count = mysql_num_rows(result); + LogWrite(SPELL__DEBUG, 0, "Spells", "\tLoaded %i SpellScript%s", count, count == 1 ? "" : "s"); + + return count; +} + +void WorldDatabase::LoadFactionList() { + int32 total = 0; + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, name, type, description, default_level, negative_change, positive_change FROM factions"); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + Faction* faction = 0; + while(result && (row = mysql_fetch_row(result))){ + faction = new Faction; + faction->id = atoul(row[0]); + faction->name = string(row[1]); + faction->type = string(row[2]); + faction->description = string(row[3]); + faction->default_value = atoi(row[4]); + faction->negative_change = atoi(row[5]); + faction->positive_change = atoi(row[6]); + master_faction_list.AddFaction(faction); + total++; + LogWrite(FACTION__DEBUG, 5, "Faction", "---Loading Faction '%s' (%u)", faction->name.c_str(), faction->id); + } + } + LogWrite(FACTION__DEBUG, 3, "Faction", "--Loaded %u Faction%s", total, total == 1 ? "" : "s"); + LoadFactionAlliances(); +} + +void WorldDatabase::SavePlayerFactions(Client* client){ + LogWrite(PLAYER__DEBUG, 3, "Player", "Saving Player Factions..."); + + Query query; + map* factions = client->GetPlayer()->GetFactions()->GetFactionValues(); + map::iterator itr; + for(itr = factions->begin(); itr != factions->end(); itr++) + query.AddQueryAsync(client->GetCharacterID(), this,Q_INSERT, "insert into character_factions (char_id, faction_id, faction_level) values(%u, %u, %i) ON DUPLICATE KEY UPDATE faction_level=%i", client->GetCharacterID(), itr->first, itr->second, itr->second); +} + +bool WorldDatabase::LoadPlayerFactions(Client* client) { + LogWrite(PLAYER__DEBUG, 0, "Player", "Loading Player Factions..."); + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT faction_id, faction_level FROM character_factions where char_id=%i", client->GetCharacterID()); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + while(result && (row = mysql_fetch_row(result))){ + client->GetPlayer()->GetFactions()->SetFactionValue(atoul(row[0]), atol(row[1])); + } + } + if(query.GetErrorNumber()) + return false; + + return true; +} + +void WorldDatabase::SavePlayerMail(Mail* mail) { + Query query_update; + Query query_insert; + if (mail) { + if(mail->mail_id > 0) + query_update.RunQuery2(Q_UPDATE, "UPDATE `character_mail` SET `already_read`=%u, `coin_copper`=%u, `coin_silver`=%u, `coin_gold`=%u, `coin_plat`=%u, `char_item_id`=%u, `stack`=%u WHERE `id`=%u", mail->already_read, mail->coin_copper, mail->coin_silver, mail->coin_gold, mail->coin_plat, mail->char_item_id, mail->stack, mail->mail_id); + else if (mail->mail_id == 0 || query_update.GetAffectedRows() == 0) + { + query_insert.RunQuery2(Q_INSERT, "INSERT INTO `character_mail` (`player_to_id`, `player_from`, `subject`, `mail_body`, `already_read`, `mail_type`, `coin_copper`, `coin_silver`, `coin_gold`, `coin_plat`, `stack`, `postage_cost`, `attachment_cost`, `char_item_id`, `time_sent`, `expire_time`) VALUES (%u, '%s', '%s', '%s', %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u)", mail->player_to_id, mail->player_from.c_str(), getSafeEscapeString(mail->subject.c_str()).c_str(), getSafeEscapeString(mail->mail_body.c_str()).c_str(), mail->already_read, mail->mail_type, mail->coin_copper, mail->coin_silver, mail->coin_gold, mail->coin_plat, mail->stack, mail->postage_cost, mail->attachment_cost, mail->char_item_id, mail->time_sent, mail->expire_time); + + if(!mail->mail_id) + mail->mail_id = query_insert.GetLastInsertedID(); + } + mail->save_needed = false; + } +} + +void WorldDatabase::SavePlayerMail(Client* client) { + if (client) { + MutexMap* mail_list = client->GetPlayer()->GetMail(); + MutexMap::iterator itr = mail_list->begin(); + while (itr.Next()) { + Mail* mail = itr->second; + if (mail->save_needed) + SavePlayerMail(mail); + } + } +} + +void WorldDatabase::LoadPlayerMail(Client* client, bool new_only) { + LogWrite(PLAYER__DEBUG, 0, "Player", "Loading Player Mail..."); + if (client) { + Query query; + MYSQL_RES* result; + if (new_only) + result = query.RunQuery2(Q_SELECT, "SELECT `id`, `player_to_id`, `player_from`, `subject`, `mail_body`, `already_read`, `mail_type`, `coin_copper`, `coin_silver`, `coin_gold`, `coin_plat`, `stack`, `postage_cost`, `attachment_cost`, `char_item_id`, `time_sent`, `expire_time` FROM `character_mail` WHERE `player_to_id`=%u AND `unread`=1", client->GetCharacterID()); + else + result = query.RunQuery2(Q_SELECT, "SELECT `id`, `player_to_id`, `player_from`, `subject`, `mail_body`, `already_read`, `mail_type`, `coin_copper`, `coin_silver`, `coin_gold`, `coin_plat`, `stack`, `postage_cost`, `attachment_cost`, `char_item_id`, `time_sent`, `expire_time` FROM `character_mail` WHERE `player_to_id`=%u", client->GetCharacterID()); + if (result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + bool hasMail = false; + while (result && (row = mysql_fetch_row(result))) { + + int32 time_sent = atoul(row[15]); + if ( time_sent > Timer::GetUnixTimeStamp() ) + continue; // should have not been received yet + + hasMail = true; + Mail* mail = new Mail; + mail->mail_id = atoul(row[0]); + mail->player_to_id = atoul(row[1]); + mail->player_from = string(row[2]); + mail->subject = string(row[3]); + if (row[4]) + mail->mail_body = string(row[4]); + mail->already_read = atoi(row[5]); + mail->mail_type = atoi(row[6]); + mail->coin_copper = atoul(row[7]); + mail->coin_silver = atoul(row[8]); + mail->coin_gold = atoul(row[9]); + mail->coin_plat = atoul(row[10]); + mail->stack = atoi(row[11]); + mail->postage_cost = atoul(row[12]); + mail->attachment_cost = atoul(row[13]); + mail->char_item_id = atoul(row[14]); + mail->time_sent = time_sent; + mail->expire_time = atoul(row[16]); + mail->save_needed = false; + client->GetPlayer()->AddMail(mail); + + LogWrite(PLAYER__DEBUG, 5, "Player", "Loaded Mail ID %i, to: %i, from: %s", atoul(row[0]), atoul(row[1]), string(row[2]).c_str()); + + } + + if(hasMail) + client->SimpleMessage(CHANNEL_NARRATIVE, "You've got mail! :)"); + } + } +} + +void WorldDatabase::DeletePlayerMail(Mail* mail) { + Query query; + if (mail) + query.RunQuery2(Q_DELETE, "DELETE FROM `character_mail` WHERE `id`=%u", mail->mail_id); + LogWrite(PLAYER__DEBUG, 0, "Player", "Delete Player Mail..."); +} + +vector* WorldDatabase::GetAllPlayerIDs() { + Query query; + vector* ids = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `id` FROM `characters`"); + MYSQL_ROW row; + while (result && (row = mysql_fetch_row(result))) { + if (ids == 0) + ids = new vector; + ids->push_back(atoul(row[0])); + } + return ids; +} + +void WorldDatabase::GetPetNames(ZoneServer* zone) +{ + DatabaseResult result; + int32 total = 0; + + if( database_new.Select(&result, "SELECT pet_name FROM spawn_pet_names") ) + { + while(result.Next()) + { + zone->pet_names.push_back(result.GetStringStr("pet_name")); + total++; + LogWrite(PET__DEBUG, 5, "Pet", "---Loading Pet Name: '%s'", result.GetStringStr("pet_name")); + } + LogWrite(PET__DEBUG, 0, "Pet", "--Loaded %u Pet Names", total); + } +} + + +int32 WorldDatabase::GetMaxHotBarID(){ + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT max(id) FROM character_skillbar"); + int32 ret = 0; + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + if(row && row[0]) + ret = strtoul(row[0], NULL, 0); + } + return ret; +} + +void WorldDatabase::SaveQuickBar(int32 char_id, vector* quickbar_items){ + vector::iterator itr; + QuickBarItem* qbi = 0; + for(itr = quickbar_items->begin(); itr != quickbar_items->end(); itr++){ + qbi = *itr; + if(!qbi) + continue; + if(qbi->deleted == false){ + Query query; + if(qbi->text.size > 0){ + query.AddQueryAsync(char_id, this, Q_REPLACE, "replace into character_skillbar (id, hotbar, slot, char_id, spell_id, type, text_val, tier) values(%u, %u, %u, %u, %u, %i, '%s', %i)", + qbi->unique_id, qbi->hotbar, qbi->slot, char_id, qbi->id, qbi->type, getSafeEscapeString(qbi->text.data.c_str()).c_str(), qbi->tier); + } + else{ + query.AddQueryAsync(char_id, this, Q_REPLACE, "replace into character_skillbar (id, hotbar, slot, char_id, spell_id, type, text_val, tier) values(%u, %u, %u, %u, %u, %i, 'Unused', %i)", + qbi->unique_id, qbi->hotbar, qbi->slot, char_id, qbi->id, qbi->type, qbi->tier); + } + } + else{ + Query query; + query.AddQueryAsync(char_id, this, Q_DELETE, "delete FROM character_skillbar where hotbar=%u and slot=%u and char_id=%u", qbi->hotbar, qbi->slot, char_id); + } + } +} + +map >* WorldDatabase::LoadSpellClasses(){ + map >* ret = new map >(); + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT spell_id, adventure_class_id, tradeskill_class_id, level FROM spell_classes"); + MYSQL_ROW row; + LevelArray* level = 0; + while(result && (row = mysql_fetch_row(result))){ + level = new LevelArray(); + level->adventure_class = atoi(row[1]); + level->tradeskill_class = atoi(row[2]); + level->spell_level = atoi(row[3]); + (*ret)[atoul(row[0])].push_back(level); + } + return ret; +} + +void WorldDatabase::LoadTraits(){ + Query query; + MYSQL_ROW row; + TraitData* trait; + + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `spell_id`, `level`, `class_req`, `race_req`, `isTrait`,`isInate`, `isFocusEffect`, `isTraining`,`tier`, `group`, `item_id` FROM spell_traits where is_active = 1"); + while (result && (row = mysql_fetch_row(result))){ + trait = new TraitData; + int8 i = 0; + trait->spellID = strtoul(row[0], NULL, 0); + trait->level = atoi(row[(++i)]); + trait->classReq = atoi(row[(++i)]); + trait->raceReq = atoi(row[(++i)]); + trait->isTrait = (atoi(row[(++i)]) == 0) ? false : true; + trait->isInate = (atoi(row[(++i)]) == 0) ? false : true; + trait->isFocusEffect = (atoi(row[(++i)]) == 0) ? false : true; + trait->isTraining = (atoi(row[(++i)]) == 0) ? false : true; + trait->tier = atoi(row[(++i)]); + trait->group = atoi(row[(++i)]); + trait->item_id = atoul(row[(++i)]); + + master_trait_list.AddTrait(trait); + } + + LogWrite(SPELL__INFO, 0, "Traits", "Loaded %u Trait(s)", master_trait_list.Size()); +} + +void WorldDatabase::LoadSpells() +{ + DatabaseResult result; + Spell *spell; + SpellData *data; + int32 t_now = Timer::GetUnixTimeStamp(); + int32 total = 0; + map >* level_data = LoadSpellClasses(); + + if( !database_new.Select(&result, "SELECT s.`id`, ts.spell_id, ts.index, `name`, `description`, `type`, `class_skill`, `min_class_skill_req`, `mastery_skill`, `tier`, `is_aa`,`hp_req`, `power_req`,`power_by_level`, `cast_time`, `recast`, `radius`, `max_aoe_targets`, `req_concentration`, `range`, `duration1`, `duration2`, `resistibility`, `hp_upkeep`, `power_upkeep`, `duration_until_cancel`, `target_type`, `recovery`, `power_req_percent`, `hp_req_percent`, `icon`, `icon_heroic_op`, `icon_backdrop`, `success_message`, `fade_message`, `fade_message_others`, `cast_type`, `lua_script`, `call_frequency`, `interruptable`, `spell_visual`, `effect_message`, `min_range`, `can_effect_raid`, `affect_only_group_members`, `hit_bonus`, `display_spell_tier`, `friendly_spell`, `group_spell`, `spell_book_type`, spell_type+0, s.is_active, savagery_req, savagery_req_percent, savagery_upkeep, dissonance_req, dissonance_req_percent, dissonance_upkeep, linked_timer_id, det_type, incurable, control_effect_type, cast_while_moving, casting_flags, persist_through_death, not_maintained, savage_bar, savage_bar_slot, soe_spell_crc, 0xffffffff-CRC32(s.`name`) as 'spell_name_crc', type_group_spell_id, can_fizzle, given_by " + "FROM (spells s, spell_tiers st) " + "LEFT JOIN spell_ts_ability_index ts " + "ON s.`id` = ts.spell_id " + "WHERE s.id = st.spell_id AND s.is_active = 1 " + "ORDER BY s.`id`, `tier`") ) + { + // error + } + else + { + while( result.Next() ) + { + data = new SpellData; + int32 spell_id = result.GetInt32Str("id"); + string spell_name = result.GetStringStr("name"); + + /* General Spell info */ + data->id = spell_id; + data->inherited_spell_id = spell_id; + data->soe_spell_crc = result.GetInt32Str("soe_spell_crc"); + data->tier = result.GetInt8Str("tier"); + data->ts_loc_index = result.GetInt8Str("index"); + data->name.data = spell_name.c_str(); + data->name.size = data->name.data.length(); + data->description.data = result.GetStringStr("description"); + data->description.size = data->description.data.length(); + data->icon = result.GetSInt16Str("icon"); + data->icon_heroic_op = result.GetInt16Str("icon_heroic_op"); + data->icon_backdrop = result.GetInt16Str("icon_backdrop"); + data->spell_visual = result.GetInt32Str("spell_visual"); + data->type = result.GetInt16Str("type"); + data->target_type = result.GetInt8Str("target_type"); + data->cast_type = result.GetInt8Str("cast_type"); + data->spell_book_type = result.GetInt32Str("spell_book_type"); + data->det_type = result.GetInt8Str("det_type"); + data->incurable = (result.GetInt8Str("incurable") == 1); + data->control_effect_type = result.GetInt8Str("control_effect_type"); + data->casting_flags = result.GetInt32Str("casting_flags"); + data->savage_bar = result.GetInt8Str("savage_bar"); + data->savage_bar_slot = result.GetInt8Str("savage_bar_slot"); + data->spell_type = result.IsNullStr("spell_type+0") ? 0 : result.GetInt8Str("spell_type+0"); + + + /* Toggles */ + data->interruptable = ( result.GetInt8Str("interruptable") == 1); + data->duration_until_cancel = ( result.GetInt8Str("duration_until_cancel") == 1); + data->can_effect_raid = result.GetInt8Str("can_effect_raid"); + data->affect_only_group_members = result.GetInt8Str("affect_only_group_members"); + data->display_spell_tier = result.GetInt8Str("display_spell_tier"); + data->friendly_spell = result.GetInt8Str("friendly_spell"); + data->group_spell = result.GetInt8Str("group_spell"); + data->is_active = result.GetInt8Str("is_active"); + data->persist_through_death = ( result.GetInt8Str("persist_through_death") == 1); + data->cast_while_moving = ( result.GetInt8Str("cast_while_moving") == 1); + data->not_maintained = ( result.GetInt8Str("not_maintained") == 1); + data->is_aa = (result.GetInt8Str("is_aa") == 1); + + /* Skill Requirements */ + data->class_skill = result.GetInt32Str("class_skill"); + data->min_class_skill_req = result.GetInt16Str("min_class_skill_req"); + data->mastery_skill = result.GetInt32Str("mastery_skill"); + + /* Cost */ + data->req_concentration = result.GetInt16Str("req_concentration"); + data->hp_req = result.GetInt16Str("hp_req"); + data->hp_upkeep = result.GetInt16Str("hp_upkeep"); + data->hp_req_percent = result.GetInt8Str("hp_req_percent"); + data->power_req = result.GetFloatStr("power_req"); + + + + + + data->power_by_level = ( result.GetInt8Str("power_by_level") == 0)? false : true; + data->power_upkeep = result.GetInt16Str("power_upkeep"); + data->power_req_percent = result.GetInt8Str("power_req_percent"); + data->savagery_req = result.GetInt16Str("savagery_req"); + data->savagery_upkeep = result.GetInt16Str("savagery_upkeep"); + data->savagery_req_percent = result.GetInt8Str("savagery_req_percent"); + data->dissonance_req = result.GetInt16Str("dissonance_req"); + data->dissonance_upkeep = result.GetInt16Str("dissonance_upkeep"); + data->dissonance_req_percent = result.GetInt8Str("dissonance_req_percent"); + + /* Spell Parameters */ + data->call_frequency = result.GetInt32Str("call_frequency"); + data->orig_cast_time = result.GetInt16Str("cast_time"); + data->cast_time = result.GetInt16Str("cast_time"); + data->duration1 = result.GetInt32Str("duration1"); + data->duration2 = result.GetInt32Str("duration2"); + data->hit_bonus = result.GetFloatStr("hit_bonus"); + data->max_aoe_targets = result.GetInt16Str("max_aoe_targets"); + data->min_range = result.GetFloatStr("min_range"); + data->radius = result.GetFloatStr("radius"); + data->range = result.GetFloatStr("range"); + data->recast = result.GetFloatStr("recast"); + data->recovery = result.GetFloatStr("recovery"); + data->resistibility = result.GetFloatStr("resistibility"); + data->linked_timer = result.GetInt32Str("linked_timer_id"); + data->spell_name_crc = result.GetInt32Str("spell_name_crc"); + data->type_group_spell_id = result.GetSInt32Str("type_group_spell_id"); + data->can_fizzle = ( result.GetInt8Str("can_fizzle") == 1); + + string given_by = result.GetStringStr("given_by"); + data->given_by.data = given_by.c_str(); + data->given_by.size = data->given_by.data.length(); + + std::string givenType(given_by); + boost::algorithm::to_lower(givenType); + if(givenType == "unset" || givenType.length() < 1) { + data->given_by_type == GivenByType::GivenBy_Unset; + } + else if(givenType == "tradeskillclass") { + data->given_by_type = GivenByType::GivenBy_TradeskillClass; + } + else if(givenType == "spellscroll") { + data->given_by_type = GivenByType::GivenBy_SpellScroll; + } + else if(givenType == "alternateadvancement") { + data->given_by_type = GivenByType::GivenBy_AltAdvancement; + } + else if(givenType == "race") { + data->given_by_type = GivenByType::GivenBy_Race; + } + else if(givenType == "racialinnate") { + data->given_by_type = GivenByType::GivenBy_RacialInnate; + } + else if(givenType == "racialtradition") { + data->given_by_type = GivenByType::GivenBy_RacialTradition; + } + else if(givenType == "class") { + data->given_by_type = GivenByType::GivenBy_Class; + } + else if(givenType == "charactertrait") { + data->given_by_type = GivenByType::GivenBy_CharacterTrait; + } + else if(givenType == "focusabilities") { + data->given_by_type = GivenByType::GivenBy_FocusAbility; + } + else if(givenType == "classtraining") { + data->given_by_type = GivenByType::GivenBy_ClassTraining; + } + else if(givenType == "warderspell") { + data->given_by_type = GivenByType::GivenBy_WarderSpell; + } + else { + data->given_by_type = GivenByType::GivenBy_Unset; + } + + /* Cast Messaging */ + string message = result.GetStringStr("success_message"); + if( message.length() > 0 ) + data->success_message = message; + + message = result.GetStringStr("fade_message"); + if( message.length() > 0 ) + data->fade_message = string(message); + + message = result.GetStringStr("fade_message_others"); + if (message.length() > 0) + data->fade_message_others = string(message); + + message = result.GetStringStr("effect_message"); + if( message.length() > 0 ) + data->effect_message = string(message); + + string lua_script = result.GetStringStr("lua_script"); + if( lua_script.length() > 0 ) + data->lua_script = string(lua_script); + + + /* Load spell level data */ + spell = new Spell(data); + + if(level_data && level_data->count(data->id) > 0) + { + vector* level_array = &((*level_data)[data->id]); + + for(int8 i=0; isize(); i++) + { + spell->AddSpellLevel(level_array->at(i)->adventure_class, level_array->at(i)->tradeskill_class, level_array->at(i)->spell_level*10); + } + } + + + /* Add spell to master list */ + master_spell_list.AddSpell(data->id, data->tier, spell); + total++; + + if( lua_script.length() > 0 ) + LogWrite(SPELL__DEBUG, 5, "Spells", "\t%i. %s (Tier: %i) - '%s'", spell_id, spell_name.c_str(), data->tier, lua_script.c_str()); + else if(data->is_active) + LogWrite(SPELL__WARNING, 1, "Spells", "\tSpell %s (%u, Tier: %i) set 'Active', but missing LUAScript", spell_name.c_str(), spell_id, data->tier); + + } // end while + } // end else + + LogWrite(SPELL__DEBUG, 0, "Spells", "Loading Spell Effects..."); + LoadSpellEffects(); + + LogWrite(SPELL__DEBUG, 0, "Spells", "Loading Spell LUA Data..."); + LoadSpellLuaData(); + + if(lua_interface) + { + LogWrite(SPELL__DEBUG, 0, "Spells", "Loading Spells Scripts..."); + LoadSpellScriptData(); + } + + if (level_data) { + map >::iterator map_itr; + vector::iterator level_itr; + + for(map_itr = level_data->begin(); map_itr != level_data->end(); map_itr++) + { + for(level_itr = map_itr->second.begin(); level_itr != map_itr->second.end(); level_itr++) + { + safe_delete(*level_itr); + } + } + } + + safe_delete(level_data); + + LogWrite(SPELL__INFO, 0, "Spells", "Loaded %u Spell%s (took %u seconds)", total, total == 1 ? "" : "s", Timer::GetUnixTimeStamp() - t_now); +} + +void WorldDatabase::LoadSpellLuaData(){ + Spell *spell; + Query query; + MYSQL_ROW row; + int32 total = 0; + MYSQL_RES *result = query.RunQuery2(Q_SELECT, "SELECT `spell_id`,`tier`,`value_type`,`value`,`value2`,`dynamic_helper` " + "FROM `spell_data` " + "ORDER BY `index_field`"); + + while (result && (row = mysql_fetch_row(result))) { + if ((spell = master_spell_list.GetSpell(atoul(row[0]), atoi(row[1]))) && row[2] && row[3] && row[4] && row[4]) { + + LogWrite(SPELL__DEBUG, 5, "Spells", "\tLoading Spell LUA Data for spell_id: %u", atoul(row[0])); + + if (!strcmp(row[2], "INT")) + spell->AddSpellLuaDataInt(atoi(row[3]), atoi(row[4]), string(row[5])); + else if (!strcmp(row[2], "FLOAT")) + spell->AddSpellLuaDataFloat(atof(row[3]), atof(row[4]),string(row[5])); + else if (!strcmp(row[2], "BOOL")) + spell->AddSpellLuaDataBool(!(strncasecmp(row[3], "true", 4)), string(row[5])); + else if (!strcmp(row[2], "STRING")) + spell->AddSpellLuaDataString(string(row[3]), string(row[4]), string(row[5])); + else + LogWrite(SPELL__ERROR, 0, "Spells", "Invalid Lua Spell data '%s' for Spell ID: %u", row[2], spell->GetSpellID()); + total++; + } + } + LogWrite(SPELL__DEBUG, 0, "Spells", "\tLoaded %i Spell LUA Data entr%s.", total, total == 1 ? "y" : "ies"); +} + +void WorldDatabase::LoadSpellEffects() { + Spell *spell; + Query query; + MYSQL_ROW row; + int32 total = 0; + MYSQL_RES *result = query.RunQuery2(Q_SELECT, "SELECT `spell_id`,`tier`,`percentage`,`bullet`,`description` " + "FROM `spell_display_effects` " + "ORDER BY `spell_id`,`id` ASC"); + + while (result && (row = mysql_fetch_row(result))) { + if ((spell = master_spell_list.GetSpell(atoul(row[0]), atoi(row[1]))) && row[4]) { + + LogWrite(SPELL__DEBUG, 5, "Spells", "\tLoading Spell Effects for spell_id: %u", atoul(row[0])); + + spell->AddSpellEffect(atoi(row[2]), atoi(row[3]), row[4]); + total++; + } + } + LogWrite(SPELL__DEBUG, 0, "Spells", "\tLoaded %u Spell Effect%s.", total, total == 1 ? "" : "s"); +} + +int32 WorldDatabase::LoadPlayerSkillbar(Client* client){ + client->GetPlayer()->ClearQuickbarItems(); + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, type, spell_id, slot, text_val, hotbar, tier FROM character_skillbar where char_id = %u", client->GetCharacterID()); + MYSQL_ROW row; + int32 count = 0; + while(result && (row = mysql_fetch_row(result))){ + count++; + int8 tier = atoi(row[6]); + Spell* spell = master_spell_list.GetSpell(atoul(row[2]), tier); + if(spell) + client->GetPlayer()->AddQuickbarItem(atoul(row[5]), atoul(row[3]), atoul(row[1]), spell->GetSpellIcon(), spell->GetSpellIconBackdrop(), spell->GetSpellID(), spell->GetSpellTier(), atoul(row[0]), row[4], false); + else if(atoul(row[1]) == QUICKBAR_MACRO) + client->GetPlayer()->AddQuickbarItem(atoul(row[5]), atoul(row[3]), atoul(row[1]), client->GetPlayer()->macro_icons[atoul(row[2])], 0xFFFF, atoul(row[2]), 0, atoul(row[0]), row[4], false); + else + client->GetPlayer()->AddQuickbarItem(atoul(row[5]), atoul(row[3]), atoul(row[1]), 0, 0, atoul(row[2]), 0, atoul(row[0]), row[4], false); + } + return count; +} + +bool WorldDatabase::DeleteCharacter(int32 account_id, int32 character_id){ + Guild *guild; + + if((guild = guild_list.GetGuild(GetGuildIDByCharacterID(character_id)))) + guild->RemoveGuildMember(character_id); + + Query query; + //devn00b: Update this whole thing we were missing many tables. This is ugly but swapped 99% of the delete to async to lighten server load. + + //Do character delete immediately. + query.RunQuery2(Q_DELETE, "DELETE FROM characters WHERE id=%u AND account_id=%u", character_id, account_id); + + //if no character then we shouldn't bother with the rest. + if (!query.GetAffectedRows()) + { + //No error just in case ppl try doing stupid stuff + return false; + } + + //Async + query.AddQueryAsync(character_id, this, Q_DELETE, "DELETE FROM character_languages WHERE char_id=%u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "DELETE FROM character_quest_rewards WHERE char_id=%u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "DELETE FROM character_quest_temporary_rewards WHERE char_id=%u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "DELETE FROM character_claim_items where char_id=%u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from charactersproperties where charid = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_aa where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_achievements where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_achievements_items where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_buyback where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_collections where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_collection_items where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_details where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_factions where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_history where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_houses where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_instances where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_items where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_items_group_members where character_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_lua_history where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_macros where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_pictures where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_properties where charid = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_quests where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_quest_progress where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_recipes where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_recipe_books where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_skillbar where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_skills where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_social where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_spells where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_spell_effects where caster_char_id = %u or target_char_id = %u", character_id, character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_spell_effect_targets where caster_char_id = %u or target_char_id = %u", character_id, character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_spirit_shards where charid = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_titles where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from char_colors where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from statistics where char_id = %u", character_id); + + return true; +} + +void WorldDatabase::DeleteCharacterSpell(int32 character_id, int32 spell_id) { + if (character_id > 0 && spell_id > 0) { + Query query; + query.RunQuery2(Q_DELETE, "DELETE FROM character_spells WHERE char_id=%u AND spell_id=%u", character_id, spell_id); + } +} + +bool WorldDatabase::GetItemResultsToClient (Client* client, const char* varSearch, int maxResults) { + Query query; + MYSQL_ROW row; + int results = 0; + + if( maxResults > 10 && client->GetAdminStatus ( ) < 100 ) + maxResults = 10; + else if( maxResults > 20 ) + maxResults = 20; + + client->Message(CHANNEL_COLOR_YELLOW, "Item Search Results"); + client->Message(CHANNEL_COLOR_YELLOW, "ResultNum) [ItemID] ItemName"); + string itemsearch_query = string("SELECT id, name FROM items where name like '%%%s%%' limit %i"); + + MYSQL_RES* result = query.RunQuery2(Q_SELECT, itemsearch_query.c_str(),getSafeEscapeString(varSearch).c_str(),maxResults); + while(result && (row = mysql_fetch_row(result))){ + results++; + client->Message(CHANNEL_COLOR_YELLOW, "%i) [%s] %s",results,row[0],row[1]); + } + + if(results == 0) + { + client->Message(CHANNEL_COLOR_YELLOW, "No Items Found."); + return false; + } + + client->Message(CHANNEL_COLOR_YELLOW, "%i Items Found.",results); + return true; +} + +void WorldDatabase::SaveWorldTime(WorldTime* time){ + Query query; + query.RunQuery2(Q_REPLACE, "replace into variables (variable_name, variable_value) values('gametime', '%i/%i/%i %i:%i')", time->month, time->day, time->year, time->hour, time->minute); +} + +void WorldDatabase::SaveBugReport(const char* category, const char* subcategory, const char* causes_crash, const char* reproducible, const char* summary, const char* description, const char* version, const char* player, int32 account_id, const char* spawn_name, int32 spawn_id, int32 zone_id){ + Query query; + + int32 dbVersion = rule_manager.GetGlobalRule(R_World, DatabaseVersion)->GetInt32(); + + string bug_report = string("insert into bugs (category, subcategory, causes_crash, reproducible, summary, description, version, player, account_id, spawn_name, spawn_id, zone_id, dbversion, worldversion) values('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %u, '%s', %u, %u, %u, '%s')"); + query.RunQuery2(Q_INSERT, bug_report.c_str(), getSafeEscapeString(category).c_str(), getSafeEscapeString(subcategory).c_str(), + getSafeEscapeString(causes_crash).c_str(), getSafeEscapeString(reproducible).c_str(), getSafeEscapeString(summary).c_str(), + getSafeEscapeString(description).c_str(), getSafeEscapeString(version).c_str(), getSafeEscapeString(player).c_str(), account_id, + getSafeEscapeString(spawn_name).c_str(), spawn_id, zone_id, dbVersion, CURRENT_VERSION); + FixBugReport(); + FixBugReport(); + FixBugReport(); +} + +void WorldDatabase::FixBugReport(){ + Query query; + string bug_report = string("update bugs set description = REPLACE(description,SUBSTRING(description,INSTR(description,'%'), 3),char(CONV(SUBSTRING(description,INSTR(description,'%')+1, 2), 16, 10))), summary = REPLACE(summary,SUBSTRING(summary,INSTR(summary,'%'), 3),char(CONV(SUBSTRING(summary,INSTR(summary,'%')+1, 2), 16, 10)))"); + query.RunQuery2(bug_report.c_str(), Q_UPDATE); +} + +int32 WorldDatabase::LoadQuests(){ + Query query; + MYSQL_ROW row; + std::string querystr = std::string("SELECT `quest_id`, `name`, `type`, `zone`, `level`, `enc_level`, `description`, `lua_script`, `completed_text`, `spawn_id`, `shareable_flag`, `deleteable` FROM `quests`"); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, querystr.c_str()); + Quest* quest = 0; + char* name = 0; + char* type = 0; + char* zone = 0; + int8 level = 0; + int8 enc_level = 0; + char* description = 0; + char* script = 0; + int32 total = 0; + int32 id = 0; + char* completed_description = 0; + int32 return_npc_id = 0; + if(result){ + while(result && (row = mysql_fetch_row(result))){ + id = atoul(row[0]); + name = row[1]; + type = row[2]; + zone = row[3]; + level = atoul(row[4]); + enc_level = atoul(row[5]); + description = row[6]; + script = row[7]; + completed_description = row[8]; + return_npc_id = atoi(row[9]); + if(lua_interface) { + quest = lua_interface->LoadQuest(id, name, type, zone, level, description, script); + } + if(quest){ + + LogWrite(QUEST__DEBUG, 5, "Quests", "\tLoading Quest: '%s' (%u)", name, id); + + LoadQuestDetails(quest); + string compDescription; + if (completed_description == NULL) + { + compDescription = string("Missing! Notify Developer"); + LogWrite(QUEST__WARNING, 5, "Quests", "\tLoading Quest MISSING completed_text in quests table for: '%s' (%u)", name, id); + } + else + compDescription = string(completed_description); + + quest->SetCompletedDescription(string(compDescription)); + quest->SetQuestReturnNPC(return_npc_id); + quest->SetEncounterLevel(enc_level); + quest->SetQuestShareableFlag(atoul(row[10])); + quest->SetCanDeleteQuest(atoul(row[11])); + total++; + master_quest_list.AddQuest(id, quest); + } + else + { + LogWrite(QUEST__ERROR, 5, "Quests", "\tFAILED LOADING QUEST: '%s' (%u), check that script file exists/permissions correct: %s", name, id, (script && strlen(script)) ? script : "Not Set, Missing!"); + } + } + } + LogWrite(QUEST__DEBUG, 0, "Quest", "\tLoaded %i Quest(s)", total); + return total; +} + +void WorldDatabase::LoadQuestDetails(Quest* quest) { + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `type`, `subtype`, `value`, `faction_id`, `quantity` FROM `quest_details` WHERE `quest_id`=%u", quest->GetQuestID()); + string type; + string subtype; + sint64 value; + int32 faction_id; + int32 quantity; + while (result && (row = mysql_fetch_row(result))) { + type = string(row[0]); + subtype = string(row[1]); + value = atoi(row[2]); + faction_id = atoi(row[3]); + quantity = atoi(row[4]); + + + LogWrite(QUEST__DEBUG, 5, "Quests", "\t- Type: %s, SubType: %s, Val: %u, Faction: %u, Qty: %u", type.c_str(), subtype.c_str(), value, faction_id, quantity); + + + if (type == "Prereq") { + if (subtype == "Class") + quest->AddPrereqClass(value); + else if (subtype == "Faction") + quest->AddPrereqFaction(faction_id, value); + else if (subtype == "Item") { + Item* master_item = master_item_list.GetItem(value); + if (master_item) { + Item* item = new Item(master_item); + quest->AddPrereqItem(item); + } + } + else if (subtype == "AdvLevel") + quest->SetPrereqLevel(value); + else if (subtype == "Quest") + quest->AddPrereqQuest(value); + else if (subtype == "Race") + quest->AddPrereqRace(value); + else if (subtype == "TSClass") + quest->AddPrereqTradeskillClass(value); + else if (subtype == "TSLevel") + quest->SetPrereqTSLevel(value); + else if (subtype == "MaxTSLevel") + quest->SetPrereqMaxTSLevel(value); + else if (subtype == "MaxAdvLevel") + quest->SetPrereqMaxLevel(value); + } + else if (type == "Reward") { + if (subtype == "Item") { + Item* master_item = master_item_list.GetItem(value); + if (master_item) { + Item* item = new Item(master_item); + item->details.count = quantity; + quest->AddRewardItem(item); + } + } + else if (subtype == "Selectable") { + Item* master_item = master_item_list.GetItem(value); + if (master_item) { + Item* item = new Item(master_item); + item->details.count = quantity; + quest->AddSelectableRewardItem(item); + } + } + else if (subtype == "Coin") { + int32 copper = 0; + int32 silver = 0; + int32 gold = 0; + int32 plat = 0; + if (value >= 1000000) { + plat = value / 1000000; + value -= 1000000 * plat; + } + if (value >= 10000) { + gold = value / 10000; + value -= 10000 * gold; + } + if (value >= 100) { + silver = value / 100; + value -= 100 * silver; + } + if (value > 0) + copper = value; + quest->AddRewardCoins(copper, silver, gold, plat); + } + else if (subtype == "MaxCoin") { + quest->AddRewardCoinsMax(value); + } + else if (subtype == "Faction") + quest->AddRewardFaction(faction_id, value); + else if (subtype == "Experience") + quest->SetRewardXP(value); + else if (subtype == "TSExperience") + quest->SetRewardTSXP(value); + } + } +} + +void WorldDatabase::LoadMerchantInformation() { + LogWrite(MERCHANT__DEBUG, 0, "Merchant", "\tClearing Merchant Inventory..."); + world.DeleteMerchantItems(); + + LogWrite(MERCHANT__DEBUG, 0, "Merchant", "\tLoading Merchant Inventory..."); + LoadMerchantInventory(); + + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT merchant_id, inventory_id FROM merchants ORDER BY merchant_id"); + int32 total = 0; + int32 last_merchant_id = 0; + int32 id = 0; + MerchantInfo* merchant = 0; + if(result) { + while(result && (row = mysql_fetch_row(result))) { + id = atoul(row[0]); + + LogWrite(MERCHANT__DEBUG, 5, "Merchant", "\tMerchantID: %u, InventoryID: %u", id, atoul(row[1])); + + if(id != last_merchant_id) { + if(merchant) + world.AddMerchantInfo(last_merchant_id, merchant); + merchant = new MerchantInfo; + last_merchant_id = id; + total++; + } + merchant->inventory_ids.push_back(atoul(row[1])); + } + if(merchant) + world.AddMerchantInfo(last_merchant_id, merchant); + } + LogWrite(MERCHANT__DEBUG, 0, "Merchant", "\tLoaded %i Merchant List(s)", total); +} + +void WorldDatabase::LoadMerchantInventory(){ + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT inventory_id, item_id, quantity, price_item_id, price_item_qty, price_item2_id, price_item2_qty, price_status, price_coins, price_stationcash FROM merchant_inventory ORDER BY inventory_id"); + int32 total = 0; + int32 id; + + if(result) { + while(result && (row = mysql_fetch_row(result))) { + MerchantItemInfo ItemInfo; + id = atoul(row[0]); + ItemInfo.item_id = atoul(row[1]); + ItemInfo.quantity = atoi(row[2]); + ItemInfo.price_item_id = atoul(row[3]); + ItemInfo.price_item_qty = atoi(row[4]); + ItemInfo.price_item2_id = atoul(row[5]); + ItemInfo.price_item2_qty = atoi(row[6]); + ItemInfo.price_status = atoul(row[7]); + ItemInfo.price_coins = atoul(row[8]); + ItemInfo.price_stationcash = atoul(row[9]); + LogWrite(MERCHANT__DEBUG, 5, "Merchant", "\tInventoryID: %u, ItemID: %u, Qty: %u", id, ItemInfo.item_id, ItemInfo.quantity); + world.AddMerchantItem(id, ItemInfo); + total++; + } + } + LogWrite(MERCHANT__DEBUG, 0, "Merchant", "\tLoaded %i Merchant Inventory Item(s)", total); +} + +string WorldDatabase::GetMerchantDescription(int32 merchant_id) { + Query query; + MYSQL_ROW row; + string description; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `description` FROM `merchants` WHERE `merchant_id`=%u", merchant_id); + if (result && (row = mysql_fetch_row(result))) + description = string(row[0]); + return description; +} + +void WorldDatabase::LoadTransporters(ZoneServer* zone){ + int32 total = 0; + zone->DeleteGlobalTransporters(); + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT transport_id, transport_type, display_name, message, destination_zone_id, destination_x, destination_y, destination_z, destination_heading, trigger_location_zone_id, trigger_location_x, trigger_location_y, trigger_location_z, trigger_radius, cost, id, min_level, max_level, quest_req, quest_step_req, quest_completed, map_x, map_y, expansion_flag, holiday_flag, min_client_version, max_client_version, flight_path_id, mount_id, mount_red_color, mount_green_color, mount_blue_color FROM transporters ORDER BY transport_id"); + if(result){ + while(result && (row = mysql_fetch_row(result))){ + LogWrite(TRANSPORT__DEBUG, 5, "Transport", "---Loading Transporter ID: %u, transport_type: %s", row[0], row[1]); + LogWrite(TRANSPORT__DEBUG, 7, "Transport", "---display_name: %s, message: %s", row[2], row[3]); + LogWrite(TRANSPORT__DEBUG, 7, "Transport", "---destination_zone_id: %s", row[4]); + LogWrite(TRANSPORT__DEBUG, 7, "Transport", "---destination_x: %s, destination_y: %s, destination_z: %s, destination_heading: %s", row[5], row[6], row[7], row[8]); + LogWrite(TRANSPORT__DEBUG, 7, "Transport", "---trigger_location_zone_id: %s", row[9]); + LogWrite(TRANSPORT__DEBUG, 7, "Transport", "---trigger_location_x: %s, trigger_location_y: %s, trigger_location_z: %s", row[10], row[11], row[12], row[13]); + LogWrite(TRANSPORT__DEBUG, 7, "Transport", "---trigger_radius: %s, cost: %s, id: %s", row[14], row[15]); + string name = ""; + if(row[2]) + name = string(row[2]); + string message = ""; + if(row[3]) + message = string(row[3]); + + if(row[1] && strcmp(row[1], "Zone") == 0) + zone->AddTransporter(atoul(row[0]), TRANSPORT_TYPE_ZONE, name, message, atoul(row[4]), atof(row[5]), atof(row[6]), atof(row[7]), atof(row[8]), atoul(row[14]), atoul(row[15]), atoi(row[16]), atoi(row[17]), atoul(row[18]), atoi(row[19]), atoul(row[20]), atoul(row[21]), atoul(row[22]), atoul(row[23]), atoul(row[24]), atoul(row[25]), atoul(row[26]), atoul(row[27]), atoul(row[28]), atoul(row[29]), atoul(row[30]), atoul(row[31])); + else if (row[1] && strcmp(row[1], "Flight") == 0) + zone->AddTransporter(atoul(row[0]), TRANSPORT_TYPE_FLIGHT, name, message, atoul(row[4]), atof(row[5]), atof(row[6]), atof(row[7]), atof(row[8]), atoul(row[14]), atoul(row[15]), atoi(row[16]), atoi(row[17]), atoul(row[18]), atoi(row[19]), atoul(row[20]), atoul(row[21]), atoul(row[22]), atoul(row[23]), atoul(row[24]), atoul(row[25]), atoul(row[26]), atoul(row[27]), atoul(row[28]), atoul(row[29]), atoul(row[30]), atoul(row[31])); + else if(row[1] && strcmp(row[1], "Location") == 0) + zone->AddLocationTransporter(atoul(row[9]), message, atof(row[10]), atof(row[11]), atof(row[12]), atof(row[13]), atoul(row[4]), atof(row[5]), atof(row[6]), atof(row[7]), atof(row[8]), atoul(row[14]), atoul(row[15])); + else + zone->AddTransporter(atoul(row[0]), TRANSPORT_TYPE_GENERIC, "", message, atoul(row[4]), atof(row[5]), atof(row[6]), atof(row[7]), atof(row[8]), atoul(row[14]), atoul(row[15]), atoi(row[16]), atoi(row[17]), atoul(row[18]), atoi(row[19]), atoul(row[20]), atoul(row[21]), atoul(row[22]), atoul(row[23]), atoul(row[24]), atoul(row[25]), atoul(row[26]), atoul(row[27]), atoul(row[28]), atoul(row[29]), atoul(row[30]), atoul(row[31])); + total++; + } + } + LogWrite(TRANSPORT__DEBUG, 0, "Transport", "--Loaded %i Transporter(s)", total); + LoadTransportMaps(zone); +} + +void WorldDatabase::LoadFogInit(string zone, PacketStruct* packet) +{ + LogWrite(WORLD__TRACE, 9, "World", "Enter: %s", __FUNCTION__); + + if(!packet || zone.length() == 0) + return; + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT highest, lowest, zone_name, explored_map_name, unexplored_map_name, bounds1_x, bounds1_z, bounds2_x, bounds2_z, bounds3_x, bounds3_z, bounds4_x, bounds4_z, explored_key, unexplored_key, map_id FROM map_data where zone_name like '%s%%'", getSafeEscapeString(&zone).c_str()); + if(result){ + int count = mysql_num_rows(result); + int i=0; + int64 explored_key; + int64 unexplored_key; + packet->setArrayLengthByName("num_maps", count); + while(result && (row = mysql_fetch_row(result))){ + packet->setDataByName("highest_z", atof(row[0])); + packet->setDataByName("lowest_z", atof(row[1])); + packet->setDataByName("map_id", atoul(row[15])); + packet->setArrayDataByName("unknown7", 1600, i); + packet->setArrayDataByName("unknown8", 1200, i); + packet->setArrayDataByName("zone_name", row[2], i); + packet->setArrayDataByName("explored_map_name", row[3], i); + packet->setArrayDataByName("unexplored_map_name", row[4], i); + packet->setArrayDataByName("map_bounds1_x", atof(row[5]), i); + packet->setArrayDataByName("map_bounds1_z", atof(row[6]), i); + packet->setArrayDataByName("map_bounds2_x", atof(row[7]), i); + packet->setArrayDataByName("map_bounds2_z", atof(row[8]), i); + packet->setArrayDataByName("map_bounds3_x", atof(row[9]), i); + packet->setArrayDataByName("map_bounds3_z", atof(row[10]), i); + packet->setArrayDataByName("map_bounds4_x", atof(row[11]), i); + packet->setArrayDataByName("map_bounds4_z", atof(row[12]), i); +#ifdef WIN32 + explored_key = _strtoui64(row[13], NULL, 10); + unexplored_key = _strtoui64(row[14], NULL, 10); +#else + explored_key = strtoull(row[13], 0, 10); + unexplored_key = strtoull(row[14], 0, 10); +#endif + packet->setArrayDataByName("explored_key", explored_key, i); + packet->setArrayDataByName("unexplored_key", unexplored_key, i); + i++; + } + } + LogWrite(WORLD__TRACE, 9, "World", "Exit: %s", __FUNCTION__); +} + +string WorldDatabase::GetColumnNames(char* name){ + Query query; + MYSQL_ROW row; + string columns = ""; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "show columns FROM %s", name); + if(result && mysql_num_rows(result) > 0){ + int16 i = 0; + while((row = mysql_fetch_row(result))){ + if(strcmp(row[0], "table_data_version") != 0){ + if(i>0) + columns.append(","); + columns.append(row[0]); + i++; + } + } + } + columns.append(""); + return columns; +} + +void WorldDatabase::ToggleCharacterOnline() { + Query query; + query.RunQuery2(Q_UPDATE, "UPDATE characters SET is_online = 0;"); +} + +void WorldDatabase::ToggleCharacterOnline(Client* client, int8 toggle) { + if (client) { + Query query; + Player* player = client->GetPlayer(); + //if(!player->CheckPlayerInfo()) + // return; + if (player) + { + LogWrite(PLAYER__DEBUG, 0, "Player", "Toggling Character %s", toggle ? "ONLINE!" : "OFFLINE!"); + query.RunQuery2(Q_UPDATE, "UPDATE characters SET is_online=%i WHERE id = %u;", toggle, client->GetCharacterID()); + } + } +} + +void WorldDatabase::LoadPlayerStatistics(Player* player, int32 char_id) { + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT stat_id, stat_value, stat_date FROM statistics WHERE char_id=%i", char_id); + while (result && (row = mysql_fetch_row(result))) { + int32 stat_id = atoi(row[0]); + sint32 stat_value = atoi(row[1]); + int32 stat_date = atoi(row[2]); + player->AddPlayerStatistic(stat_id, stat_value, stat_date); + } +} + +void WorldDatabase::WritePlayerStatistic(Player *player, Statistic* stat) { + if (player && player->GetCharacterID() > 0 && stat) { + Query query; + query.RunQuery2(Q_INSERT, "INSERT INTO statistics (char_id, guild_id, stat_id, stat_value, stat_date) VALUES(%i, %i, %i, %i, %i) ON DUPLICATE KEY UPDATE stat_value = %i, stat_date = %i;", + player->GetCharacterID(), 0, stat->stat_id, stat->stat_value, stat->stat_date, + stat->stat_value, stat->stat_date); + } +} + +void WorldDatabase::LoadServerStatistics() +{ + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT stat_id, stat_value, stat_date FROM statistics WHERE char_id=0 AND guild_id=0"); + while (result && (row = mysql_fetch_row(result))) { + int32 stat_id = atoi(row[0]); + sint32 stat_value = atoi(row[1]); + int32 stat_date = atoi(row[2]); + world.AddServerStatistic(stat_id, stat_value, stat_date); + LogWrite(INIT__DEBUG, 5, "Stats", "Loading Stat ID %i, value: %i", stat_id, stat_value); + } +} + +void WorldDatabase::WriteServerStatistic(Statistic* stat) { + if (stat) { + Query query; + query.RunQuery2(Q_INSERT, "INSERT INTO statistics (char_id, guild_id, stat_id, stat_value, stat_date) VALUES(0, 0, %i, %i, %i) ON DUPLICATE KEY UPDATE stat_value = %i, stat_date = %i;", + stat->stat_id, stat->stat_value, stat->stat_date, + stat->stat_value, stat->stat_date); + } +} + +void WorldDatabase::WriteServerStatistic(int32 stat_id, sint32 stat_value) { + Query query; + query.RunQuery2(Q_INSERT, "INSERT INTO statistics (char_id, guild_id, stat_id, stat_value, stat_date) VALUES(0, 0, %i, %i, %i) ON DUPLICATE KEY UPDATE stat_value = %i, stat_date = %i;", + stat_id, stat_value, Timer::GetUnixTimeStamp(), + stat_value, Timer::GetUnixTimeStamp()); +} + +void WorldDatabase::WriteServerStatisticsNeededQueries() { + Query query1, query2, query3; + MYSQL_ROW row1, row2, row3; + + // Number of unique accounts + MYSQL_RES* result1 = query1.RunQuery2(Q_SELECT, "SELECT COUNT(DISTINCT account_id) FROM characters"); + if (result1 && (row1 = mysql_fetch_row(result1)) && row1[0] != NULL) + WriteServerStatistic(STAT_SERVER_NUM_ACCOUNTS, atoi(row1[0])); + else + WriteServerStatistic(STAT_SERVER_NUM_ACCOUNTS, 0); + + // Number of characters + MYSQL_RES* result2 = query2.RunQuery2(Q_SELECT, "SELECT COUNT(id) FROM characters"); + if (result2 && (row2 = mysql_fetch_row(result2)) && row2[0] != NULL) + WriteServerStatistic(STAT_SERVER_NUM_CHARACTERS, atoi(row2[0])); + else + WriteServerStatistic(STAT_SERVER_NUM_CHARACTERS, 0); + + // Average character level + MYSQL_RES* result3 = query3.RunQuery2(Q_SELECT, "SELECT ROUND(AVG(level)) FROM characters"); + if (result3 && (row3 = mysql_fetch_row(result3)) && row3[0] != NULL) + WriteServerStatistic(STAT_SERVER_AVG_CHAR_LEVEL, atoi(row3[0])); + else + WriteServerStatistic(STAT_SERVER_AVG_CHAR_LEVEL, 0); +} + +map* WorldDatabase::GetInstanceRemovedSpawns(int32 instance_id, int8 type) +{ + DatabaseResult result; + map* ret = NULL; + + LogWrite(SPAWN__TRACE, 1, "Spawn", "Enter %s", __FUNCTION__); + + LogWrite(INSTANCE__DEBUG, 0, "Instance", "Loading removed spawns for instance_id: %u, spawn_type: %i", instance_id, type); + + if( !database_new.Select(&result, "SELECT spawn_location_entry_id, respawn_time FROM instance_spawns_removed WHERE instance_id = %i AND spawn_type = %i", instance_id, type) ) + { + LogWrite(INSTANCE__ERROR, 0, "Instance", "Error in GetInstanceRemovedSpawns() '%s': %i", database_new.GetErrorMsg(), database_new.GetError()); + return ret; + } + else + { + if( result.GetNumRows() > 0 ) + { + ret = new map; + + while( result.Next() ) + { + int32 spawn_location_entry_id = result.GetInt32Str("spawn_location_entry_id"); + /* + respawnTime == 0 - never respawn + respawnTime = 1 - spawn now + respawnTime > 1 (continue timer) + */ + int32 respawntime = result.GetInt32Str("respawn_time"); + + LogWrite(INSTANCE__DEBUG, 5, "Instance", "Found spawn point: %u, respawn time: %i", spawn_location_entry_id, respawntime); + + ret->insert(make_pair(spawn_location_entry_id, respawntime)); + } + } + else + LogWrite(INSTANCE__DEBUG, 0, "Instance", "No removed spawns found for instance_id: %u, spawn_type: %i", instance_id, type); + + } + + LogWrite(SPAWN__TRACE, 1, "Spawn", "Exit %s", __FUNCTION__); + + return ret; +} + +bool WorldDatabase::CheckVectorForValue(vector* vector, int32 value) { + if ( vector != NULL ) + { + for(int32 i=0;isize();i++) + { + int32 compare = vector->at(i); + if ( compare == value ) + return true; + } + } + + return false; +} + +int32 WorldDatabase::CheckSpawnRemoveInfo(map* inmap, int32 spawn_location_entry_id) +{ + LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__); + + map::iterator iter; + + if ( inmap != NULL ) + { + for(iter=inmap->begin();iter!=inmap->end();iter++) + { + if ( iter->first == spawn_location_entry_id ) + return (int32)iter->second; + } + } + + return 1; +} + +int32 WorldDatabase::AddCharacterInstance(int32 char_id, int32 instance_id, string zone_name, int8 instance_type, int32 last_success, int32 last_failure, int32 success_lockout, int32 failure_lockout) +{ + int32 ret = 0; + if( !database_new.Query("INSERT INTO character_instances (char_id, instance_id, instance_zone_name, instance_type, last_success_timestamp, last_failure_timestamp, success_lockout_time, failure_lockout_time) VALUES (%u, %u, '%s', %i, %u, %u, %u, %u) ", char_id, instance_id, database_new.EscapeStr(zone_name).c_str(), instance_type, last_success, last_failure, success_lockout, failure_lockout) ) + { + LogWrite(INSTANCE__ERROR, 0, "Instance", "Error in AddCharacterInstance() '%s': %i", database_new.GetErrorMsg(), database_new.GetError()); + return 0; + } + ret = database_new.LastInsertID(); + + LogWrite(INSTANCE__DEBUG, 0, "Instance", "Adding character %u to instance: %u", char_id, instance_id); + //LogWrite(INSTANCE__DEBUG, 1, "Instance", "-- Reenter: %u, Reset: %u, Lockout: %u", grant_reenter_time_left, grant_reset_time_left, lockout_time); + + return ret; +} + +bool WorldDatabase::UpdateCharacterInstanceTimers(int32 char_id, int32 instance_id, int32 lockout_time, int32 reset_time, int32 reenter_time ) +{ + if ( lockout_time > 0 && reset_time > 0 && reenter_time > 0 ) + database_new.Query("UPDATE character_instances SET lockout_time = %i, grant_reset_time_left = %i, grant_reenter_time_left = %i WHERE char_id = %i AND instance_id = %i", lockout_time, reset_time, reenter_time, char_id, instance_id); + else if ( lockout_time > 0 && reset_time > 0 ) + database_new.Query("UPDATE character_instances SET lockout_time = %i, grant_reset_time_left = %i WHERE char_id = %i AND instance_id = %i", lockout_time, reset_time, char_id, instance_id); + else if ( reset_time > 0 && reenter_time > 0 ) + database_new.Query("UPDATE character_instances SET grant_reset_time_left = %i, grant_reenter_time_left = %i WHERE char_id = %i AND instance_id = %i",reset_time, reenter_time, char_id, instance_id); + else if ( lockout_time > 0 && reenter_time > 0 ) + database_new.Query("UPDATE character_instances SET lockout_time = %i, grant_reenter_time_left = %i WHERE char_id = %i AND instance_id = %i", lockout_time, reenter_time, char_id, instance_id); + else if ( lockout_time > 0 ) + database_new.Query("UPDATE character_instances SET lockout_time = %i WHERE char_id = %i AND instance_id = %i", lockout_time, char_id, instance_id); + else if ( reset_time > 0 ) + database_new.Query("UPDATE character_instances SET grant_reset_time_left = %i WHERE char_id = %i AND instance_id = %i", reset_time, char_id, instance_id); + else if ( reenter_time > 0 ) + database_new.Query("UPDATE character_instances SET grant_reenter_time_left = %i WHERE char_id = %i AND instance_id = %i", reenter_time, char_id, instance_id); + + if( database_new.GetError() ) + { + LogWrite(INSTANCE__ERROR, 0, "Instance", "Error in UpdateCharacterInstanceTimers() '%s': %i", database_new.GetErrorMsg(), database_new.GetError()); + return false; + } + else + { + if ( database_new.AffectedRows() > 0 ) + { + LogWrite(INSTANCE__DEBUG, 0, "Instance", "Updating instance timers for character %u to instance: %u", char_id, instance_id); + LogWrite(INSTANCE__DEBUG, 1, "Instance", "-- Reenter: %u, Reset: %u, Lockout: %u", reenter_time, reset_time, lockout_time); + return true; + } + else + return false; + } +} + +bool WorldDatabase::UpdateCharacterInstance(int32 char_id, string zone_name, int32 instance_id, int8 type, int32 timestamp) { + // type = 1 = success timestamp + // type = 2 = failure timestamp + if (instance_id > 0) { + if (type == 1) { + database_new.Query("UPDATE character_instances SET instance_id = %u, last_success_timestamp = %u WHERE char_id = %u AND instance_zone_name = '%s'", instance_id, timestamp, char_id, database_new.EscapeStr(zone_name).c_str()); + } + else if (type == 2) { + database_new.Query("UPDATE character_instances SET instance_id = %u, last_failure_timestamp = %u WHERE char_id = %u AND instance_zone_name = '%s'", instance_id, timestamp, char_id, database_new.EscapeStr(zone_name).c_str()); + } + else { + database_new.Query("UPDATE character_instances SET instance_id = %u WHERE char_id = %u AND instance_zone_name = '%s'", instance_id, char_id, database_new.EscapeStr(zone_name).c_str()); + } + } + else { + if (type == 1) { + database_new.Query("UPDATE character_instances SET last_success_timestamp = %u WHERE char_id = %u AND instance_zone_name = '%s'", timestamp, char_id, database_new.EscapeStr(zone_name).c_str()); + } + else if (type == 2) { + database_new.Query("UPDATE character_instances SET last_failure_timestamp = %u WHERE char_id = %u AND instance_zone_name = '%s'", timestamp, char_id, database_new.EscapeStr(zone_name).c_str()); + } + } + + if (database_new.GetError()) { + LogWrite(INSTANCE__ERROR, 0, "Instance", "Error in UpdateCharacterInstance() '%s': %i", database_new.GetErrorMsg(), database_new.GetError()); + return false; + } + + return true; +} + +bool WorldDatabase::VerifyInstanceID(int32 char_id, int32 instance_id) { + DatabaseResult result; + database_new.Select(&result, "SELECT COUNT(id) as num_instances FROM instances WHERE id = %u", instance_id); + + if (result.Next() && result.GetInt32Str("num_instances") == 0) { + DeleteCharacterFromInstance(char_id, instance_id); + return false; + } + + return true; +} + +bool WorldDatabase::UpdateInstancedSpawnRemoved(int32 spawn_location_entry_id, int32 spawn_type, int32 respawn_time, int32 instance_id ) +{ + LogWrite(INSTANCE__DEBUG, 0, "Instance", "Updating removed spawns for instance_id: %u", instance_id); + LogWrite(INSTANCE__DEBUG, 1, "Instance", "-- Spawn Location Entry ID: %u, Type: %u, Respawn: %u", spawn_location_entry_id, spawn_type, respawn_time); + + if( !database_new.Query("UPDATE instance_spawns_removed SET respawn_time = %i WHERE spawn_location_entry_id = %i AND spawn_type = %i AND instance_id = %i", respawn_time, spawn_location_entry_id, spawn_type, instance_id) ) + { + LogWrite(INSTANCE__ERROR, 0, "Instance", "Error in UpdateInstancedSpawnRemoved() '%s': %i", database_new.GetErrorMsg(), database_new.GetError()); + return false; + } + + if ( database_new.AffectedRows() > 0 ) + { + LogWrite(INSTANCE__DEBUG, 0, "Instance", "Updated removed spawns for instance_id: %u", instance_id); + return true; + } + else + return false; +} + +int32 WorldDatabase::CreateNewInstance(int32 zone_id) +{ + int32 ret = 0; + + LogWrite(INSTANCE__DEBUG, 0, "Instance", "Creating new instance for zone: %u ", zone_id); + + if( !database_new.Query("INSERT INTO instances (zone_id) VALUES (%u)", zone_id) ) + LogWrite(INSTANCE__ERROR, 0, "Instance", "Error in CreateNewInstance() '%s': %i", database_new.GetErrorMsg(), database_new.GetError()); + else + ret = database_new.LastInsertID(); + + if( ret > 0 ) + LogWrite(INSTANCE__DEBUG, 0, "Instance", "Created new instance_id %u for zone: %u ", ret, zone_id); + + return ret; +} + +int32 WorldDatabase::CreateInstanceSpawnRemoved(int32 spawn_location_entry_id, int32 spawn_type, int32 respawn_time, int32 instance_id ) +{ + int32 ret = 0; + + LogWrite(INSTANCE__DEBUG, 3, "Instance", "Creating new instance spawn removed entries for instance_id: %u ", instance_id); + LogWrite(INSTANCE__DEBUG, 5, "Instance", "-- Spawn Location Entry ID: %u, Type: %u, Respawn: %u", spawn_location_entry_id, spawn_type, respawn_time); + + if( !database_new.Query("INSERT INTO instance_spawns_removed (spawn_location_entry_id, spawn_type, instance_id, respawn_time) values(%u, %u, %u, %u)", spawn_location_entry_id, spawn_type, instance_id, respawn_time) ) + LogWrite(INSTANCE__ERROR, 0, "Instance", "Error in CreateInstanceSpawnRemoved() query '%s': %i", database_new.GetErrorMsg(), database_new.GetError()); + else + ret = database_new.LastInsertID(); + + // potentially spammy, if it calls for every spawn added. Set to level 3 or 5? + if( ret > 0 ) + LogWrite(INSTANCE__DEBUG, 5, "Instance", "Created new spawn removed entry: %u for instance_id %u", ret, instance_id); + + return ret; +} + +bool WorldDatabase::DeleteInstance(int32 instance_id) +{ + if( !database_new.Query("DELETE FROM instances WHERE id = %u", instance_id) ) + { + LogWrite(INSTANCE__ERROR, 0, "Instance", "Error in DeleteInstance() '%s': %i", database_new.GetErrorMsg(), database_new.GetError()); + return false; + } + + /* JA: should not need this delete with FK/Constraints + if( !database_new.Query("DELETE FROM instance_spawns_removed WHERE instance_id = %u", instance_id) ) + { + LogWrite(INSTANCE__ERROR, 0, "Instance", "Error in DeleteInstance() '%s': %i", database_new.GetErrorMsg(), database_new.GetError()); + return false; + } + */ + + // Remove the instance from the character_instances table + database_new.Query("UPDATE `character_instances` SET `instance_id` = 0 WHERE `instance_id` = %u", instance_id); + + LogWrite(INSTANCE__DEBUG, 0, "Instance", "Deleted instance_id %u", instance_id); + + return true; +} + +bool WorldDatabase::DeleteInstanceSpawnRemoved(int32 instance_id, int32 spawn_location_entry_id) +{ + if( !database_new.Query("DELETE FROM instance_spawns_removed WHERE instance_id = %u AND spawn_location_entry_id = %u", instance_id, spawn_location_entry_id) ) + { + LogWrite(INSTANCE__ERROR, 0, "Instance", "Error in DeleteInstanceSpawnRemoved() '%s': %i", database_new.GetErrorMsg(), database_new.GetError()); + return false; + } + + LogWrite(INSTANCE__DEBUG, 0, "Instance", "Deleted removed spawn: %u for instance_id %u", spawn_location_entry_id, instance_id); + + return true; +} + +bool WorldDatabase::DeleteCharacterFromInstance(int32 char_id, int32 instance_id) +{ + LogWrite(INSTANCE__DEBUG, 0, "Instance", "Delete character %u from instance_id %u.", char_id, instance_id); + + if( !database_new.Query("UPDATE `character_instances` SET `instance_id` = 0 WHERE `instance_id` = %u AND `char_id` = %u", instance_id, char_id) ) + { + LogWrite(INSTANCE__ERROR, 0, "Instance", "Error in DeleteCharacterFromInstance() '%s': %i", database_new.GetErrorMsg(), database_new.GetError()); + return false; + } + + if ( database_new.AffectedRows() == 0 ) // didn't find an instance to delete + { + LogWrite(INSTANCE__DEBUG, 1, "Instance", "No instance_id %u for character %u to delete.", instance_id, char_id); + return false; + } + else + { + // delete entire instance if the last player has left + DatabaseResult result; + database_new.Select(&result, "SELECT count(id) as num_instances FROM character_instances where instance_id = %u",instance_id); + + if(result.Next() && result.GetInt32Str("num_instances") == 0) + { + LogWrite(INSTANCE__DEBUG, 0, "Instance", "No characters in instance: Delete instance_id %u.", instance_id); + DeleteInstance(instance_id); + } + } + + return true; +} + +bool WorldDatabase::LoadCharacterInstances(Client* client) +{ + DatabaseResult result; + DatabaseResult result2; + + bool addedInstance = false; + + database_new.Select(&result, "SELECT `id`, `instance_id`, `instance_zone_name`, `instance_type`, `last_success_timestamp`, `last_failure_timestamp`, `success_lockout_time`, `failure_lockout_time` FROM `character_instances` WHERE `char_id` = %u", client->GetCharacterID()); + + if( result.GetNumRows() > 0 ) + { + while( result.Next() ) + { + int32 zone_id = 0; + int32 instance_id = result.GetInt32Str("instance_id"); + // If `instance_id` is greater then 0 then get the zone id with it, else get the zone id from the zone name + if (instance_id != 0) { + if (database_new.Select(&result2, "SELECT `zone_id` FROM `instances` WHERE `id` = %u", instance_id)) { + if (result2.Next()) + zone_id = result2.GetInt32Str("zone_id"); + } + } + if (zone_id == 0) + zone_id = GetZoneID(result.GetStringStr("instance_zone_name")); + + client->GetPlayer()->GetCharacterInstances()->AddInstance( + result.GetInt32Str("id"), + instance_id, + result.GetInt32Str("last_success_timestamp"), + result.GetInt32Str("last_failure_timestamp"), + result.GetInt32Str("success_lockout_time"), + result.GetInt32Str("failure_lockout_time"), + zone_id, + result.GetInt8Str("instance_type"), + string(result.GetStringStr("instance_zone_name")) + ); + addedInstance = true; + } + } + + return addedInstance; +} + +void WorldDatabase::UpdateLoginEquipment() +{ + LogWrite(INIT__LOGIN_DEBUG, 0, "Login", "Updating `character_items` CRC in %s", __FUNCTION__); + + database_new.Query("UPDATE character_items SET login_checksum = CRC32(CRC32(type) + CRC32(slot) + CRC32(item_id)) WHERE `type` = 'EQUIPPED' AND ( slot <= 8 OR slot = 19 )"); +} + +MutexMap* WorldDatabase::GetEquipmentUpdates() +{ + DatabaseResult result; + MutexMap* ret = 0; + LoginEquipmentUpdate update; + int32 count = 0; + + LogWrite(INIT__LOGIN_DEBUG, 0, "Login", "Looking for Login Appearance Updates..."); + + // TODO: Someday store the equipment colors in character_items, for custom colorization of gear (?) + if( database_new.Select(&result, "SELECT ci.id, ci.char_id, ia.equip_type, ia.red, ia.green, ia.blue, ia.highlight_red, ia.highlight_green, ia.highlight_blue, ci.slot FROM characters c JOIN character_items ci ON c.id = ci.char_id JOIN item_appearances ia ON ci.item_id = ia.item_id WHERE c.deleted = 0 AND ci.type = 'EQUIPPED' AND ( ci.slot <= 8 OR ci.slot = 19 ) AND ci.login_checksum <> CRC32(CRC32(`ci`.`type`) + CRC32(ci.slot) + CRC32(ci.item_id)) ORDER BY ci.char_id, ci.slot") ) + { + while( result.Next() ) + { + LogWrite(INIT__LOGIN_DEBUG, 5, "Login", "Found update for char_id %i!", result.GetInt32Str("char_id")); + + if(!ret) + ret = new MutexMap(); + + update.world_char_id = result.GetInt32Str("char_id"); + update.equip_type = result.GetInt16Str("equip_type"); + update.red = result.GetInt8Str("red"); + update.green = result.GetInt8Str("green"); + update.blue = result.GetInt8Str("blue"); + update.highlight_red = result.GetInt8Str("highlight_red"); + update.highlight_green = result.GetInt8Str("highlight_green"); + update.highlight_blue = result.GetInt8Str("highlight_blue"); + update.slot = result.GetInt8Str("slot"); + ret->Put(result.GetInt32Str("id"), update); + count++; + + } + } + + if(count) + LogWrite(INIT__LOGIN_DEBUG, 0, "Login", "Found %i Login Appearance Update%s...", count, count == 1 ? "" : "s"); + + return ret; +} + + +MutexMap* WorldDatabase::GetEquipmentUpdates(int32 char_id) +{ + DatabaseResult result; + MutexMap* ret = 0; + LoginEquipmentUpdate update; + int32 count = 0; + + LogWrite(INIT__LOGIN_DEBUG, 0, "Login", "Looking for Login Appearance Updates for char_id: %u", char_id); + + // TODO: Someday store the equipment colors in character_items, for custom colorization of gear (?) + if( database_new.Select(&result, "SELECT ci.id, ci.char_id, ia.equip_type, ia.red, green, ia.blue, ia.highlight_red, ia.highlight_green, ia.highlight_blue, ci.slot FROM characters c JOIN character_items ci ON c.id = ci.char_id JOIN item_appearances ia ON ci.item_id = ia.item_id WHERE c.deleted = 0 AND ci.type = 'EQUIPPED' AND ( ci.slot <= 8 OR ci.slot = 19 ) AND ci.login_checksum <> CRC32(CRC32(ci.type) + CRC32(ci.slot) + CRC32(ci.item_id)) AND ci.char_id = %u ORDER BY ci.slot", char_id) ) + { + while( result.Next() ) + { + LogWrite(INIT__LOGIN_DEBUG, 5, "Login", "Found update for char_id %i!", result.GetInt32Str("char_id")); + + if(!ret) + ret = new MutexMap(); + + update.world_char_id = char_id; + update.equip_type = result.GetInt16Str("equip_type"); + update.red = result.GetInt8Str("red"); + update.green = result.GetInt8Str("green"); + update.blue = result.GetInt8Str("blue"); + update.highlight_red = result.GetInt8Str("highlight_red"); + update.highlight_green = result.GetInt8Str("highlight_green"); + update.highlight_blue = result.GetInt8Str("highlight_blue"); + update.slot = result.GetInt8Str("slot"); + ret->Put(result.GetInt32Str("id"), update); + count++; + } + } + + if(count) + LogWrite(INIT__LOGIN_DEBUG, 0, "Login", "Found %i Login Appearance Update%s...", count, count == 1 ? "" : "s"); + + return ret; +} + + +void WorldDatabase::UpdateLoginZones() { + Query query; + LogWrite(INIT__LOGIN_DEBUG, 0, "Login", "Updating `zones` CRC in %s", __FUNCTION__); + query.RunQuery2("UPDATE zones SET login_checksum = CRC32(CRC32(id) + CRC32(`name`) + CRC32(`file`) + CRC32(description))", Q_UPDATE); +} + +MutexMap* WorldDatabase::GetZoneUpdates() { + LogWrite(INIT__LOGIN_DEBUG, 0, "Login", "Looking for Login Zone Updates..."); + MutexMap* ret = 0; + LoginZoneUpdate update; + Query query; + MYSQL_ROW row; + int32 count = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, name, description FROM zones where login_checksum != crc32(crc32(id) + crc32(name) + crc32(file) + crc32(description))"); + while(result && (row = mysql_fetch_row(result))) { + if(row[0] && row[1]) { + + LogWrite(INIT__LOGIN_DEBUG, 5, "Login", "Found update for zone_id %i!", atoi(row[0])); + + if(!ret) + ret = new MutexMap(); + update.name = string(row[1]); + if(row[2]) + update.description = string(row[2]); + else + update.description = ""; + ret->Put(atoi(row[0]), update); + count++; + } + } + if(count) + LogWrite(INIT__LOGIN_DEBUG, 0, "Login", "Found %i Login Zone Update%s...", count, count == 1 ? "" : "s"); + return ret; +} + +void WorldDatabase::LoadLocationGrids(ZoneServer* zone) { + if (zone) { + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `id`, `grid_id`, `name`, `include_y`, `discovery` FROM `locations` WHERE `zone_id`=%u", zone->GetZoneID()); + while (result && (row = mysql_fetch_row(result))) { + LocationGrid* grid = new LocationGrid; + grid->id = atoul(row[0]); + grid->grid_id = atoul(row[1]); + grid->name = string(row[2]); + grid->include_y = (atoi(row[3]) == 1); + grid->discovery = (atoi(row[4]) == 1); + if (LoadLocationGridLocations(grid)) + zone->AddLocationGrid(grid); + else + safe_delete(grid); + } + } +} + +bool WorldDatabase::LoadLocationGridLocations(LocationGrid* grid) { + bool ret = false; + if (grid) { + Query query; + int row_count = 0; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `id`, `x`, `y`, `z` FROM `location_details` WHERE `location_id`=%u", grid->id); + if (result) { + while (result && (row = mysql_fetch_row(result))) { + row_count++; + Location* location = new Location; + location->id = atoul(row[0]); + location->x = atof(row[1]); + location->y = atof(row[2]); + location->z = atof(row[3]); + grid->locations.Add(location); + } + ret = true; + } + if(row_count > 0 && row_count < 3) + LogWrite(WORLD__WARNING, 0, "World", "Grid '%s' only has %u location(s). A minimum of 3 is needed for a proper location based grid.", grid->name.c_str(), row_count); + } + return ret; +} + +int32 WorldDatabase::CreateLocation(int32 zone_id, int32 grid_id, const char* name, bool include_y) { + int32 ret = 0; + if (name && strlen(name) > 0) { + Query query; + query.RunQuery2(Q_INSERT, "INSERT INTO `locations` (`zone_id`, `grid_id`, `name`, `include_y`) VALUES (%u, %u, '%s', %u)", zone_id, grid_id, name, include_y == true ? 1 : 0); + ret = query.GetLastInsertedID(); + } + return ret; +} + +bool WorldDatabase::AddLocationPoint(int32 location_id, float x, float y, float z) { + bool ret = false; + if (LocationExists(location_id)) { + Query query; + query.RunQuery2(Q_INSERT, "INSERT INTO `location_details` (`location_id`, `x`, `y`, `z`) VALUES (%u, %f, %f, %f)", location_id, x, y, z); + ret = true; + } + return ret; +} + +bool WorldDatabase::DeleteLocation(int32 location_id) { + bool ret = false; + if (LocationExists(location_id)) { + Query query; + query.RunQuery2(Q_DELETE, "DELETE FROM `locations` WHERE `id`=%u", location_id); + ret = true; + } + return ret; +} + +bool WorldDatabase::DeleteLocationPoint(int32 location_point_id) { + Query query; + query.RunQuery2(Q_DELETE, "DELETE FROM `location_details` WHERE `id`=%u", location_point_id); + return true; +} + +void WorldDatabase::ListLocations(Client* client) { + if (client) { + Query query; + MYSQL_ROW row; + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Listing all locations:"); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `id`, `zone_id`, `grid_id`, `name` FROM `locations`"); + while (result && (row = mysql_fetch_row(result))) { + int32 id = atoul(row[0]); + int32 zone_id = atoul(row[1]); + int32 grid_id = atoul(row[2]); + const char* name = row[3]; + client->Message(CHANNEL_COLOR_YELLOW, "%u) Zone ID: %u Grid ID:%u Name: '%s'", id, zone_id, grid_id, name); + } + } +} + +void WorldDatabase::ListLocationPoints(Client* client, int32 location_id) { + if (client) { + if (LocationExists(location_id)) { + Query query; + client->Message(CHANNEL_COLOR_YELLOW, "Listing all points for location ID %u:", location_id); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `id`, `x`, `y`, `z` FROM `location_details` WHERE `location_id`=%u", location_id); + MYSQL_ROW row; + while (result && (row = mysql_fetch_row(result))) { + int32 id = atoul(row[0]); + float x = atof(row[1]); + float y = atof(row[2]); + float z = atof(row[3]); + client->Message(CHANNEL_COLOR_YELLOW, "%u) (%f, %f, %f)", id, x, y, z); + } + } + else + client->Message(CHANNEL_COLOR_YELLOW, "A location with ID %u does not exist", location_id); + } +} + +bool WorldDatabase::LocationExists(int32 location_id) { + bool ret = false; + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT COUNT(id) FROM `locations` WHERE `id`=%u", location_id); + MYSQL_ROW row; + if (result && (row = mysql_fetch_row(result))) { + if (atoul(row[0]) > 0) + ret = true; + } + return ret; +} + +bool WorldDatabase::QueriesFromFile(const char * file) { + return database_new.QueriesFromFile(file); +} + +bool WorldDatabase::CheckBannedIPs(const char* loginIP) +{ + // til you build the table, all IPs are allowed + return false; +} + +sint32 WorldDatabase::AddMasterTitle(const char* titleName, int8 isPrefix) +{ + if(titleName == nullptr || strlen(titleName) < 1) + { + LogWrite(DATABASE__ERROR, 0, "DBNew", "AddMasterTitle called with missing titleName"); + return -1; + } + + Query query; + Title* title = master_titles_list.GetTitleByName(titleName); + + if(title) + return title->GetID(); + + query.RunQuery2(Q_INSERT, "INSERT INTO titles set title='%s', prefix=%u", titleName, isPrefix); + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(DATABASE__ERROR, 0, "Database", "Error in AddMasterTitle query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + + int32 last_insert_id = query.GetLastInsertedID(); + if(last_insert_id > 0) + { + title = new Title; + title->SetID(last_insert_id); + title->SetName(titleName); + title->SetPrefix(isPrefix); + master_titles_list.AddTitle(title); + return (sint32)last_insert_id; + } + + + return -1; +} + +void WorldDatabase::LoadTitles(){ + Query query; + MYSQL_ROW row; + int32 count = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, title, prefix FROM titles"); + if(result && mysql_num_rows(result) > 0){ + Title* title = 0; + while(result && (row = mysql_fetch_row(result))){ + sint32 idx = atoi(row[0]); + LogWrite(WORLD__DEBUG, 5, "World", "\tLoading Title '%s' (%u), Prefix: %i, Index: %u", row[1], idx, atoi(row[2]), count); + title = new Title; + title->SetID(idx); + title->SetName(row[1]); + title->SetPrefix(atoi(row[2])); + master_titles_list.AddTitle(title); + count++; + } + } + LogWrite(WORLD__DEBUG, 0, "World", "\tLoaded %u Title%s", count, count == 1 ? "" : "s"); +} + +sint32 WorldDatabase::LoadCharacterTitles(int32 char_id, Player *player){ + LogWrite(WORLD__DEBUG, 0, "World", "Loading Titles for player '%s'...", player->GetName()); + Query query; + MYSQL_ROW row; + sint32 index = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT title_id, title, prefix FROM character_titles, titles WHERE character_titles.title_id = titles.id AND character_titles.char_id = %u", char_id); + if(result && mysql_num_rows(result) > 0){ + while(result && (row = mysql_fetch_row(result))){ + LogWrite(WORLD__DEBUG, 5, "World", "\tLoading Title ID: %u, Title: '%s' Index: %u", atoul(row[0]), row[1], index); + player->AddTitle(index, row[1], atoi(row[2])); + index++; + } + } + return index; +} + +sint32 WorldDatabase::GetCharPrefixIndex(int32 char_id, Player *player){ + LogWrite(PLAYER__DEBUG, 0, "Player", "Getting current title index for player '%s'...", player->GetName()); + Query query; + MYSQL_ROW row; + sint32 ret = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT prefix_title FROM character_details WHERE char_id = %u", char_id); + if(result && mysql_num_rows(result) > 0) + while(result && (row = mysql_fetch_row(result))){ + ret = atoi(row[0]); + LogWrite(PLAYER__DEBUG, 5, "Player", "\tPrefix Index: %i", ret); + } + return ret; +} + +sint32 WorldDatabase::GetCharSuffixIndex(int32 char_id, Player *player){ + LogWrite(PLAYER__DEBUG, 0, "Player", "Getting current title index for player '%s'...", player->GetName()); + Query query; + MYSQL_ROW row; + sint32 ret = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT suffix_title FROM character_details WHERE char_id = %u", char_id); + if(result && mysql_num_rows(result) > 0) + while(result && (row = mysql_fetch_row(result))){ + ret = atoi(row[0]); + LogWrite(PLAYER__DEBUG, 5, "Player", "\tSuffix Index: %i", ret); + } + return ret; +} + +void WorldDatabase::SaveCharPrefixIndex(sint32 index, int32 char_id){ + Query query; + query.RunQuery2(Q_UPDATE, "UPDATE character_details SET prefix_title = %i WHERE char_id = %u", index, char_id); + LogWrite(PLAYER__DEBUG, 0, "Player", "Saving Prefix Index %i for character id '%u'...", index, char_id); +} + +void WorldDatabase::SaveCharSuffixIndex(sint32 index, int32 char_id){ + Query query; + query.RunQuery2(Q_SELECT, "UPDATE character_details SET suffix_title = %i WHERE char_id = %u", index, char_id); + LogWrite(PLAYER__DEBUG, 0, "Player", "Saving Suffix Index %i for character id %u...", index, char_id); +} + +sint32 WorldDatabase::AddCharacterTitle(sint32 index, int32 char_id, Spawn* player) { + if(!player || !player->IsPlayer()) + { + LogWrite(DATABASE__ERROR, 0, "DBNew", "AddCharacterTitle spawn is not a player: %s", player ? player->GetName() : "Unset"); + return -1; + } + + Title* title = master_titles_list.GetTitle(index); + if(!title) + { + LogWrite(DATABASE__ERROR, 0, "DBNew", "AddCharacterTitle title index %u missing from master_titles_list for player: %s (%u)", index, player ? player->GetName() : "Unset", char_id); + return -1; + } + + Query query; + LogWrite(PLAYER__DEBUG, 0, "Player", "Adding titles for char_id: %u, index: %i", char_id, index); + query.RunQuery2(Q_INSERT, "INSERT IGNORE INTO character_titles (char_id, title_id) VALUES (%u, %i)", char_id, index); + sint32 curIndex = (sint32)((Player*)player)->GetPlayerTitles()->Size(); + ((Player*)player)->AddTitle(curIndex++, title->GetName(), title->GetPrefix(), title->GetSaveNeeded()); + + return curIndex; +} + +void WorldDatabase::LoadLanguages() +{ + int32 count = 0; + Query query; + MYSQL_ROW row; + + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, language FROM languages"); + + if(result && mysql_num_rows(result) > 0) + { + Language* language = 0; + + while(result && (row = mysql_fetch_row(result))) + { + LogWrite(WORLD__DEBUG, 5, "World", "\tLoading language '%s' , ID: %u", row[1], atoul(row[0])); + language = new Language; + language->SetID(atoul(row[0])); + language->SetName(row[1]); + master_languages_list.AddLanguage(language); + count++; + } + } + LogWrite(WORLD__DEBUG, 0, "World", "\tLoaded %u Language%s", count, count == 1 ? "" : "s"); +} + +int32 WorldDatabase::LoadCharacterLanguages(int32 char_id, Player *player) +{ + LogWrite(WORLD__DEBUG, 0, "World", "Loading Languages for player '%s'...", player->GetName()); + Query query; + MYSQL_ROW row; + int32 count = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT language_id, language FROM character_languages, languages WHERE character_languages.language_id = languages.id AND character_languages.char_id = %u", char_id); + + if(result && mysql_num_rows(result) > 0) + { + while(result && (row = mysql_fetch_row(result))) + { + LogWrite(WORLD__DEBUG, 5, "World", "\tLoading Language ID: %u, Language: '%s'", atoul(row[0]), row[1]); + player->AddLanguage(atoul(row[0]), row[1]); + count++; + } + } + return count; +} + +int16 WorldDatabase::GetCharacterCurrentLang(int32 char_id, Player *player) +{ + LogWrite(PLAYER__DEBUG, 0, "Player", "Getting current language for player '%s'...", player->GetName()); + Query query; + MYSQL_ROW row; + int16 ret = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT current_language FROM character_details WHERE char_id = %u", char_id); + + if(result && mysql_num_rows(result) > 0) + while(result && (row = mysql_fetch_row(result))) + { + ret = atoi(row[0]); + LogWrite(PLAYER__DEBUG, 5, "Player", "\tLanguage ID: %i", ret); + } + return ret; +} + +void WorldDatabase::SaveCharacterCurrentLang(int32 id, int32 char_id, Client *client) +{ + Query query; + query.RunQuery2(Q_UPDATE, "UPDATE character_details SET current_language = %i WHERE char_id = %u", id, char_id); + LogWrite(PLAYER__DEBUG, 3, "Player", "Saving current language ID %i for player '%s'...", id, client->GetPlayer()->GetName()); +} + +void WorldDatabase::SaveCharacterLang(int32 char_id, int32 lang_id) { + if (!database_new.Query("INSERT INTO character_languages (char_id, language_id) VALUES (%u, %u)", char_id, lang_id)) + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); +} + +// JA - this is not used yet, lots more to consider for storing player history +void WorldDatabase::LoadCharacterHistory(int32 char_id, Player *player) +{ + DatabaseResult result; + + // Use -1 on type and subtype to turn the enum into an int and make it a 0 index + if (!database_new.Select(&result, "SELECT type-1, subtype-1, value, value2, location, event_id, event_date FROM character_history WHERE char_id = %u", char_id)) { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return; + } + + while (result.Next()) { + + int8 type = result.GetInt8(0); + int8 subtype = result.GetInt8(1); + + HistoryData* hd = new HistoryData; + hd->Value = result.GetInt32(2); + hd->Value2 = result.GetInt32(3); + strcpy(hd->Location, result.GetString(4)); + // skipped event id as use for it has not been determined yet + hd->EventDate = result.GetInt32(6); + hd->needs_save = false; + + player->LoadPlayerHistory(type, subtype, hd); + } +} + +void WorldDatabase::LoadSpellErrors() { + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `version`, `error_index`, `value` FROM `spell_error_versions`"); + + if (result && mysql_num_rows(result) > 0) { + while ((row = mysql_fetch_row(result))) { + master_spell_list.AddSpellError(atoi(row[0]), atoi(row[1]), atoi(row[2])); + } + } +} + +void WorldDatabase::SaveCharacterHistory(Player* player, int8 type, int8 subtype, int32 value, int32 value2, char* location, int32 event_date) { + string str_type(""); + string str_subtype(""); + switch (type) { + case HISTORY_TYPE_NONE: + str_type = "None"; + break; + case HISTORY_TYPE_DEATH: + str_type = "Death"; + break; + case HISTORY_TYPE_DISCOVERY: + str_type = "Discovery"; + break; + case HISTORY_TYPE_XP: + str_type = "XP"; + break; + default: + LogWrite(PLAYER__ERROR, 0, "Player", "WorldDatabase::SaveCharacterHistory() - Invalid history type given (%i) with subtype (%i), character history NOT saved.", type, subtype); + return; + } + switch (subtype) { + case HISTORY_SUBTYPE_NONE: + str_subtype = "None"; + break; + case HISTORY_SUBTYPE_ADVENTURE: + str_subtype = "Adventure"; + break; + case HISTORY_SUBTYPE_TRADESKILL: + str_subtype = "Tradeskill"; + break; + case HISTORY_SUBTYPE_QUEST: + str_subtype = "Quest"; + break; + case HISTORY_SUBTYPE_AA: + str_subtype = "AA"; + break; + case HISTORY_SUBTYPE_ITEM: + str_subtype = "Item"; + break; + case HISTORY_SUBTYPE_LOCATION: + str_subtype = "Location"; + break; + default: + LogWrite(PLAYER__ERROR, 0, "Player", "WorldDatabase::SaveCharacterHistory() - Invalid history sub type given (%i) with type (%i), character history NOT saved.", subtype, type); + return; + } + + LogWrite(PLAYER__DEBUG, 1, "Player", "Saving character history, type = %s (%i) subtype = %s (%i)", (char*)str_type.c_str(), type, (char*)str_subtype.c_str(), subtype); + + Query query; + query.AddQueryAsync(player->GetCharacterID(), this, Q_REPLACE, "replace into character_history (char_id, type, subtype, value, value2, location, event_date) values (%u, '%s', '%s', %i, %i, '%s', %u)", + player->GetCharacterID(), str_type.c_str(), str_subtype.c_str(), value, value2, getSafeEscapeString(location).c_str(), event_date); +} + +void WorldDatabase::LoadTransportMaps(ZoneServer* zone) { + int32 total = 0; + + LogWrite(TRANSPORT__DEBUG, 0, "Transport", "-Loading Transporter Maps..."); + zone->DeleteTransporterMaps(); + + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `transport_id`, `map_name` FROM `transport_maps`"); + if(result) { + while(result && (row = mysql_fetch_row(result))){ + zone->AddTransportMap(atoul(row[0]), string(row[1])); + total++; + } + } + LogWrite(TRANSPORT__DEBUG, 0, "Transport", "--Loaded %i Transporter Maps", total); +} + +bool WorldDatabase::LoadSign(ZoneServer* zone, int32 spawn_id) { + Sign* sign = 0; + int32 id = 0; + DatabaseResult result; + database_new.Select(&result, "SELECT ss.spawn_id, s.name, s.model_type, s.size, s.show_command_icon, ss.widget_id, ss.widget_x, ss.widget_y, ss.widget_z, s.command_primary, s.command_secondary, s.collision_radius, ss.icon, ss.type, ss.title, ss.description, ss.sign_distance, ss.zone_id, ss.zone_x, ss.zone_y, ss.zone_z, ss.zone_heading, ss.include_heading, ss.include_location, s.transport_id, s.size_offset, s.display_hand_icon, s.visual_state, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, s.loot_tier, s.loot_drop_type, ss.language\n" + "FROM spawn s\n" + "INNER JOIN spawn_signs ss\n" + "ON ss.spawn_id = s.id\n" + "WHERE s.id = %u\n", + spawn_id); + + if (result.GetNumRows() > 0 && result.Next()) { + id = result.GetInt32(0); + sign = new Sign(); + sign->SetDatabaseID(id); + strcpy(sign->appearance.name, result.GetString(1)); + sign->appearance.model_type = result.GetInt16(2); + sign->SetSize(result.GetInt16(3)); + sign->appearance.show_command_icon = result.GetInt8(4); + sign->SetWidgetID(result.GetInt32(5)); + sign->SetWidgetX(result.GetFloat(6)); + sign->SetWidgetY(result.GetFloat(7)); + sign->SetWidgetZ(result.GetFloat(8)); + vector* primary_command_list = zone->GetEntityCommandList(result.GetInt32(9)); + if(primary_command_list){ + sign->SetPrimaryCommands(primary_command_list); + sign->primary_command_list_id = result.GetInt32(9); + } + + vector* secondary_command_list = zone->GetEntityCommandList(result.GetInt32(10)); + if (secondary_command_list) { + sign->SetSecondaryCommands(secondary_command_list); + sign->secondary_command_list_id = result.GetInt32(10); + } + + sign->appearance.pos.collision_radius = result.GetInt16(11); + sign->SetSignIcon(result.GetInt8(12)); + if(strncasecmp(result.GetString(13), "Generic", 7) == 0) + sign->SetSignType(SIGN_TYPE_GENERIC); + else if(strncasecmp(result.GetString(13), "Zone", 4) == 0) + sign->SetSignType(SIGN_TYPE_ZONE); + sign->SetSignTitle(result.GetString(14)); + sign->SetSignDescription(result.GetString(15)); + sign->SetSignDistance(result.GetFloat(16)); + sign->SetSignZoneID(result.GetInt32(17)); + sign->SetSignZoneX(result.GetFloat(18)); + sign->SetSignZoneY(result.GetFloat(19)); + sign->SetSignZoneZ(result.GetFloat(20)); + sign->SetSignZoneHeading(result.GetFloat(21)); + sign->SetIncludeHeading(result.GetInt8(22) == 1); + sign->SetIncludeLocation(result.GetInt8(23) == 1); + sign->SetTransporterID(result.GetInt32(24)); + sign->SetSizeOffset(result.GetInt8(25)); + sign->appearance.display_hand_icon = result.GetInt8(26); + sign->SetVisualState(result.GetInt16(27)); + + sign->SetSoundsDisabled(result.GetInt8(28)); + + sign->SetMerchantLevelRange(result.GetInt32(29), result.GetInt32(30)); + + sign->SetAAXPRewards(result.GetInt32(31)); + + sign->SetLootTier(result.GetInt32(32)); + + sign->SetLootDropType(result.GetInt32(33)); + + sign->SetLanguage(result.GetInt8(34)); + + zone->AddSign(id, sign); + + + LogWrite(SIGN__DEBUG, 0, "Sign", "Loaded Sign: '%s' (%u).", sign->appearance.name, spawn_id); + return true; + } + LogWrite(SIGN__DEBUG, 0, "Sign", "Unable to find a sign for spawn id of %u", spawn_id); + return false; +} + +bool WorldDatabase::LoadWidget(ZoneServer* zone, int32 spawn_id) { + Widget* widget = 0; + int32 id = 0; + DatabaseResult result; + + database_new.Select(&result, "SELECT sw.spawn_id, s.name, s.model_type, s.size, s.show_command_icon, sw.widget_id, sw.widget_x, sw.widget_y, sw.widget_z, s.command_primary, s.command_secondary, s.collision_radius, sw.include_heading, sw.include_location, sw.icon, sw.type, sw.open_heading, sw.open_y, sw.action_spawn_id, sw.open_sound_file, sw.close_sound_file, sw.open_duration, sw.closed_heading, sw.linked_spawn_id, sw.close_y, s.transport_id, s.size_offset, sw.house_id, sw.open_x, sw.open_z, sw.close_x, sw.close_z, s.display_hand_icon, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, s.loot_tier, s.loot_drop_type\n" + "FROM spawn s\n" + "INNER JOIN spawn_widgets sw\n" + "ON sw.spawn_id = s.id\n" + "WHERE s.id = %u", + spawn_id); + + if (result.GetNumRows() > 0 && result.Next()) { + id = result.GetInt32(0); + widget = new Widget(); + widget->SetDatabaseID(id); + strcpy(widget->appearance.name, result.GetString(1)); + widget->appearance.model_type = result.GetInt16(2); + widget->SetSize(result.GetInt16(3)); + widget->appearance.show_command_icon = result.GetInt8(4); + widget->SetWidgetID(result.GetInt32(5)); + widget->SetWidgetX(result.GetFloat(6)); + widget->SetWidgetY(result.GetFloat(7)); + widget->SetWidgetZ(result.GetFloat(8)); + vector* primary_command_list = zone->GetEntityCommandList(result.GetInt32(9)); + if(primary_command_list){ + widget->SetPrimaryCommands(primary_command_list); + widget->primary_command_list_id = result.GetInt32(9); + } + + vector* secondary_command_list = zone->GetEntityCommandList(result.GetInt32(10)); + if (secondary_command_list) { + widget->SetSecondaryCommands(secondary_command_list); + widget->secondary_command_list_id = result.GetInt32(10); + } + + widget->appearance.pos.collision_radius = result.GetInt16(11); + widget->SetIncludeHeading(result.GetInt8(12) == 1); + widget->SetIncludeLocation(result.GetInt8(13) == 1); + widget->SetWidgetIcon(result.GetInt8(14)); + if(strncasecmp(result.GetString(15),"Generic", 7) == 0) + widget->SetWidgetType(WIDGET_TYPE_GENERIC); + else if(strncasecmp(result.GetString(15),"Door", 4) == 0) + widget->SetWidgetType(WIDGET_TYPE_DOOR); + widget->SetOpenHeading(result.GetFloat(16)); + widget->SetOpenY(result.GetFloat(17)); + widget->SetActionSpawnID(result.GetInt32(18)); + if(!result.IsNull(19) && strlen(result.GetString(19)) > 5) + widget->SetOpenSound(result.GetString(19)); + if(!result.IsNull(20) && strlen(result.GetString(20)) > 5) + widget->SetCloseSound(result.GetString(20)); + widget->SetOpenDuration(result.GetInt16(21)); + widget->SetClosedHeading(result.GetFloat(22)); + widget->SetLinkedSpawnID(result.GetInt32(23)); + widget->SetCloseY(result.GetFloat(24)); + widget->SetTransporterID(result.GetInt32(25)); + widget->SetSizeOffset(result.GetInt8(26)); + widget->SetHouseID(result.GetInt32(27)); + widget->SetOpenX(result.GetFloat(28)); + widget->SetOpenZ(result.GetFloat(29)); + widget->SetCloseX(result.GetFloat(30)); + widget->SetCloseZ(result.GetFloat(31)); + widget->appearance.display_hand_icon = result.GetInt8(32); + + widget->SetSoundsDisabled(result.GetInt8(33)); + + widget->SetMerchantLevelRange(result.GetInt32(34), result.GetInt32(35)); + + widget->SetAAXPRewards(result.GetInt32(36)); + + widget->SetLootTier(result.GetInt32(37)); + + widget->SetLootDropType(result.GetInt32(38)); + + zone->AddWidget(id, widget); + + LogWrite(WIDGET__DEBUG, 0, "Widget", "Loaded Widget: '%s' (%u).", widget->appearance.name, spawn_id); + return true; + } + + LogWrite(WIDGET__DEBUG, 0, "Widget", "Unable to find a widget for spawn id of %u", spawn_id); + return false; +} + +bool WorldDatabase::LoadObject(ZoneServer* zone, int32 spawn_id) { + Object* object = 0; + int32 id = 0; + DatabaseResult result; + + database_new.Select(&result, "SELECT so.spawn_id, s.name, s.race, s.model_type, s.command_primary, s.command_secondary, s.targetable, s.size, s.show_name, s.visual_state, s.attackable, s.show_level, s.show_command_icon, s.display_hand_icon, s.faction_id, s.collision_radius, s.transport_id, s.size_offset, so.device_id, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, s.loot_tier, s.loot_drop_type\n" + "FROM spawn s\n" + "INNER JOIN spawn_objects so\n" + "ON so.spawn_id = s.id\n" + "WHERE s.id = %u", + spawn_id); + + if (result.GetNumRows() > 0 && result.Next()) { + id = result.GetInt32(0); + object = new Object(); + object->SetDatabaseID(id); + strcpy(object->appearance.name, result.GetString(1)); + vector* primary_command_list = zone->GetEntityCommandList(result.GetInt32(4)); + vector* secondary_command_list = zone->GetEntityCommandList(result.GetInt32(5)); + if(primary_command_list){ + object->SetPrimaryCommands(primary_command_list); + object->primary_command_list_id = result.GetInt32(4); + } + if(secondary_command_list){ + object->SetSecondaryCommands(secondary_command_list); + object->secondary_command_list_id = result.GetInt32(5); + } + object->appearance.race = result.GetInt8(2); + object->appearance.model_type = result.GetInt16(3); + object->appearance.targetable = result.GetInt8(6); + object->size = result.GetInt16(7); + object->appearance.display_name = result.GetInt8(8); + object->appearance.visual_state = result.GetInt16(9); + object->appearance.attackable = result.GetInt8(10); + object->appearance.show_level = result.GetInt8(11); + object->appearance.show_command_icon = result.GetInt8(12); + object->appearance.display_hand_icon = result.GetInt8(13); + object->faction_id = result.GetInt32(14); + object->appearance.pos.collision_radius = result.GetInt16(15); + object->SetTransporterID(result.GetInt32(16)); + object->SetSizeOffset(result.GetInt8(17)); + object->SetDeviceID(result.GetInt8(18)); + object->SetSoundsDisabled(result.GetInt8(19)); + + object->SetMerchantLevelRange(result.GetInt32(20), result.GetInt32(21)); + + object->SetAAXPRewards(result.GetInt32(22)); + + object->SetLootTier(result.GetInt32(23)); + + object->SetLootDropType(result.GetInt32(24)); + + zone->AddObject(id, object); + + LogWrite(OBJECT__DEBUG, 0, "Object", "Loaded Object: '%s' (%u).", object->appearance.name, spawn_id); + return true; + } + + LogWrite(OBJECT__DEBUG, 0, "Object", "Unable to find an object for spawn id of %u", spawn_id); + return false; +} + +bool WorldDatabase::LoadGroundSpawn(ZoneServer* zone, int32 spawn_id) { + GroundSpawn* spawn = 0; + int32 id = 0; + DatabaseResult result; + + database_new.Select(&result, "SELECT sg.spawn_id, s.name, s.race, s.model_type, s.command_primary, s.command_secondary, s.targetable, s.size, s.show_name, s.visual_state, s.attackable, s.show_level, s.show_command_icon, s.display_hand_icon, s.faction_id, s.collision_radius, sg.number_harvests, sg.num_attempts_per_harvest, sg.groundspawn_id, sg.collection_skill, s.size_offset, s.disable_sounds, s.aaxp_rewards, s.loot_tier, s.loot_drop_type, sg.randomize_heading\n" + "FROM spawn s\n" + "INNER JOIN spawn_ground sg\n" + "ON sg.spawn_id = s.id\n" + "WHERE s.id = %u", + spawn_id); + + if (result.GetNumRows() > 0 && result.Next()) { + id = result.GetInt32(0); + spawn = new GroundSpawn(); + spawn->SetDatabaseID(id); + strcpy(spawn->appearance.name, result.GetString(1)); + vector* primary_command_list = zone->GetEntityCommandList(result.GetInt32(4)); + vector* secondary_command_list = zone->GetEntityCommandList(result.GetInt32(5)); + if(primary_command_list){ + spawn->SetPrimaryCommands(primary_command_list); + spawn->primary_command_list_id = result.GetInt32(4); + } + if(secondary_command_list){ + spawn->SetSecondaryCommands(secondary_command_list); + spawn->secondary_command_list_id = result.GetInt32(5); + } + spawn->appearance.race = result.GetInt8(2); + spawn->appearance.model_type = result.GetInt16(3); + spawn->appearance.targetable = result.GetInt8(6); + spawn->size = result.GetInt16(7); + spawn->appearance.display_name = result.GetInt8(8); + spawn->appearance.visual_state = result.GetInt16(9); + spawn->appearance.attackable = result.GetInt8(10); + spawn->appearance.show_level = result.GetInt8(11); + spawn->appearance.show_command_icon = result.GetInt8(12); + spawn->appearance.display_hand_icon = result.GetInt8(13); + spawn->faction_id = result.GetInt32(14); + spawn->appearance.pos.collision_radius = result.GetInt16(15); + spawn->SetNumberHarvests(result.GetInt8(16)); + spawn->SetAttemptsPerHarvest(result.GetInt8(17)); + spawn->SetGroundSpawnEntryID(result.GetInt32(18)); + spawn->SetCollectionSkill(result.GetString(19)); + spawn->SetSizeOffset(result.GetInt8(20)); + + spawn->SetSoundsDisabled(result.GetInt8(21)); + spawn->SetAAXPRewards(result.GetInt32(22)); + + spawn->SetLootTier(result.GetInt32(23)); + + spawn->SetLootDropType(result.GetInt32(24)); + + spawn->SetRandomizeHeading(result.GetInt32(25)); + + zone->AddGroundSpawn(id, spawn); + + if (!zone->GetGroundSpawnEntries(spawn->GetGroundSpawnEntryID())) + LoadGroundSpawnEntry(zone, spawn->GetGroundSpawnEntryID()); + + LogWrite(GROUNDSPAWN__DEBUG, 0, "GSpawn", "Loaded Ground Spawn: '%s' (%u).", spawn->appearance.name, spawn_id); + return true; + } + + LogWrite(GROUNDSPAWN__DEBUG, 0, "GSpawn", "Unable to find a ground spawn for spawn id of %u", spawn_id); + return false; +} + +void WorldDatabase::LoadGroundSpawnItems(ZoneServer* zone, int32 entry_id) { + DatabaseResult result; + + database_new.Select(&result, "SELECT item_id, is_rare, grid_id\n" + "FROM groundspawn_items\n" + "WHERE groundspawn_id = %u", + entry_id); + + if (result.GetNumRows() > 0 && result.Next()) { + zone->AddGroundSpawnItem(entry_id, result.GetInt32(0), result.GetInt8(1), result.GetInt32(2)); + LogWrite(GROUNDSPAWN__DEBUG, 5, "GSpawn", "---Loading GroundSpawn Items: ID: %u\n", entry_id); + LogWrite(GROUNDSPAWN__DEBUG, 5, "GSpawn", "---item: %ul, rare: %i, grid: %ul", result.GetInt32(0), result.GetInt8(1), result.GetInt32(2)); + } +} + +void WorldDatabase::LoadGroundSpawnEntry(ZoneServer* zone, int32 entry_id) { + DatabaseResult result; + + database_new.Select(&result, "SELECT min_skill_level, min_adventure_level, bonus_table, harvest1, harvest3, harvest5, harvest_imbue, harvest_rare, harvest10, harvest_coin\n" + "FROM groundspawns\n" + "WHERE enabled = 1 AND groundspawn_id = %u", + entry_id); + + if (result.GetNumRows() > 0 && result.Next()) { + // this is getting ridonkulous... + LogWrite(GROUNDSPAWN__DEBUG, 5, "GSpawn", "---Loading GroundSpawn ID: %u\n" \ + "---min_skill_level: %i, min_adventure_level: %i, bonus_table: %i\n" \ + "---harvest1: %.2f, harvest3: %.2f, harvest5: %.2f\n" \ + "---harvest_imbue: %.2f, harvest_rare: %.2f, harvest10: %.2f\n" \ + "---harvest_coin: %u", entry_id, result.GetInt16(0), result.GetInt16(1), result.GetInt8(2), result.GetFloat(3), result.GetFloat(4), result.GetFloat(5), result.GetFloat(6), result.GetFloat(7), result.GetFloat(8), result.GetInt32(9)); + + zone->AddGroundSpawnEntry(entry_id, result.GetInt16(0), result.GetInt16(1), result.GetInt8(2), result.GetFloat(3), result.GetFloat(4), result.GetFloat(5), result.GetFloat(6), result.GetFloat(7), result.GetFloat(8), result.GetInt32(9)); + LoadGroundSpawnItems(zone, entry_id); + } +} + +bool WorldDatabase::LoadNPC(ZoneServer* zone, int32 spawn_id) { + NPC* npc = nullptr; + int32 id = 0; + DatabaseResult result; + + database_new.Select(&result, "SELECT npc.spawn_id, s.name, npc.min_level, npc.max_level, npc.enc_level, s.race, s.model_type, npc.class_, npc.gender, s.command_primary, s.command_secondary, s.show_name, npc.min_group_size, npc.max_group_size, npc.hair_type_id, npc.facial_hair_type_id, npc.wing_type_id, npc.chest_type_id, npc.legs_type_id, npc.soga_hair_type_id, npc.soga_facial_hair_type_id, s.attackable, s.show_level, s.targetable, s.show_command_icon, s.display_hand_icon, s.hp, s.power, s.size, s.collision_radius, npc.action_state, s.visual_state, npc.mood_state, npc.initial_state, npc.activity_status, s.faction_id, s.sub_title, s.merchant_id, s.merchant_type, s.size_offset, npc.attack_type, npc.ai_strategy+0, npc.spell_list_id, npc.secondary_spell_list_id, npc.skill_list_id, npc.secondary_skill_list_id, npc.equipment_list_id, npc.str, npc.sta, npc.wis, npc.intel, npc.agi, npc.heat, npc.cold, npc.magic, npc.mental, npc.divine, npc.disease, npc.poison, npc.aggro_radius, npc.cast_percentage, npc.randomize, npc.soga_model_type, npc.heroic_flag, npc.alignment, npc.elemental, npc.arcane, npc.noxious, s.savagery, s.dissonance, npc.hide_hood, npc.emote_state, s.prefix, s.suffix, s.last_name, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, s.loot_tier, s.loot_drop_type, npc.scared_by_strong_players, npc.action_state_str\n" + "FROM spawn s\n" + "INNER JOIN spawn_npcs npc\n" + "ON npc.spawn_id = s.id\n" + "WHERE s.id = %u", + spawn_id); + + if (result.GetNumRows() > 0 && result.Next()) { + id = result.GetInt32(0); + npc = new NPC(); + npc->SetDatabaseID(id); + strcpy(npc->appearance.name, result.GetString(1)); + vector* primary_command_list = zone->GetEntityCommandList(result.GetInt32(9)); + vector* secondary_command_list = zone->GetEntityCommandList(result.GetInt32(10)); + if(primary_command_list){ + npc->SetPrimaryCommands(primary_command_list); + npc->primary_command_list_id = result.GetInt32(9); + } + if(secondary_command_list){ + npc->SetSecondaryCommands(secondary_command_list); + npc->secondary_command_list_id = result.GetInt32(10); + } + npc->appearance.min_level = result.GetInt8(2); + npc->appearance.max_level = result.GetInt8(3); + npc->appearance.level = result.GetInt8(2); + npc->appearance.difficulty = result.GetInt8(4); + npc->appearance.race = result.GetInt8(5); + npc->appearance.model_type = result.GetInt16(6); + npc->appearance.soga_model_type = result.GetInt16(62); + npc->appearance.adventure_class = result.GetInt8(7); + npc->appearance.gender = result.GetInt8(8); + npc->appearance.display_name = result.GetInt8(11); + npc->features.hair_type = result.GetInt16(14); + npc->features.hair_face_type = result.GetInt16(15); + npc->features.wing_type = result.GetInt16(16); + npc->features.chest_type = result.GetInt16(17); + npc->features.legs_type = result.GetInt16(18); + npc->features.soga_hair_type = result.GetInt16(19); + npc->features.soga_hair_face_type = result.GetInt16(20); + npc->appearance.attackable = result.GetInt8(21); + npc->appearance.show_level = result.GetInt8(22); + npc->appearance.targetable = result.GetInt8(23); + npc->appearance.show_command_icon = result.GetInt8(24); + npc->appearance.display_hand_icon = result.GetInt8(25); + npc->appearance.hide_hood = result.GetInt8(70); + npc->appearance.randomize = result.GetInt32(61); + npc->SetTotalHP(result.GetInt32(26)); + npc->SetTotalPower(result.GetInt32(27)); + npc->SetHP(npc->GetTotalHP()); + npc->SetPower(npc->GetTotalPower()); + if(npc->GetTotalHP() == 0){ + npc->SetTotalHP(15*npc->GetLevel() + 1); + npc->SetHP(15*npc->GetLevel() + 1); + } + if(npc->GetTotalPower() == 0){ + npc->SetTotalPower(15*npc->GetLevel() + 1); + npc->SetPower(15*npc->GetLevel() + 1); + } + npc->size = result.GetInt16(28); + npc->appearance.pos.collision_radius = result.GetInt16(29); + npc->appearance.action_state = result.GetInt16(30); + npc->appearance.visual_state = result.GetInt16(31); + npc->appearance.mood_state = result.GetInt16(32); + npc->appearance.emote_state = result.GetInt16(71); + npc->appearance.pos.state = result.GetInt16(33); + npc->appearance.activity_status = result.GetInt16(34); + npc->faction_id = result.GetInt32(35); + if(!result.IsNull(36)){ + std::string sub_title = std::string(result.GetString(36)); + if(sub_title.find("Collector") != std::string::npos) { + npc->SetCollector(true); + } + if(strlen(result.GetString(36)) < sizeof(npc->appearance.sub_title)) + strcpy(npc->appearance.sub_title, result.GetString(36)); + else + strncpy(npc->appearance.sub_title, result.GetString(36), sizeof(npc->appearance.sub_title)); + } + npc->SetMerchantID(result.GetInt32(37)); + npc->SetMerchantType(result.GetInt8(38)); + npc->SetSizeOffset(result.GetInt8(39)); + npc->SetAIStrategy(result.GetInt8(41)); + npc->SetPrimarySpellList(result.GetInt32(42)); + npc->SetSecondarySpellList(result.GetInt32(43)); + npc->SetPrimarySkillList(result.GetInt32(44)); + npc->SetSecondarySkillList(result.GetInt32(45)); + npc->SetEquipmentListID(result.GetInt32(46)); + + InfoStruct* info = npc->GetInfoStruct(); + info->set_attack_type(result.GetInt8(40)); + info->set_str_base(result.GetInt16(47)); + info->set_sta_base(result.GetInt16(48)); + info->set_wis_base(result.GetInt16(49)); + info->set_intel_base(result.GetInt16(50)); + info->set_agi_base(result.GetInt16(51)); + info->set_heat_base(result.GetInt16(52)); + info->set_cold_base(result.GetInt16(53)); + info->set_magic_base(result.GetInt16(54)); + info->set_mental_base(result.GetInt16(55)); + info->set_divine_base(result.GetInt16(56)); + info->set_disease_base(result.GetInt16(57)); + info->set_poison_base(result.GetInt16(58)); + info->set_alignment(result.GetSInt8(64)); + + npc->SetAggroRadius(result.GetFloat(59)); + npc->SetCastPercentage(result.GetInt8(60)); + npc->appearance.heroic_flag = result.GetInt8(63); + + info->set_elemental_base(result.GetInt16(65)); + info->set_arcane_base(result.GetInt16(66)); + info->set_noxious_base(result.GetInt16(67)); + npc->SetTotalSavagery(result.GetInt32(68)); + npc->SetTotalDissonance(result.GetInt32(69)); + npc->SetSavagery(npc->GetTotalSavagery()); + npc->SetDissonance(npc->GetTotalDissonance()); + if(npc->GetTotalSavagery() == 0){ + npc->SetTotalSavagery(15*npc->GetLevel() + 1); + npc->SetSavagery(15*npc->GetLevel() + 1); + } + if(npc->GetTotalDissonance() == 0){ + npc->SetTotalDissonance(15*npc->GetLevel() + 1); + npc->SetDissonance(15*npc->GetLevel() + 1); + } + npc->SetPrefixTitle(result.GetString(72)); + npc->SetSuffixTitle(result.GetString(73)); + npc->SetLastName(result.GetString(74)); + + npc->SetSoundsDisabled(result.GetInt8(75)); + + npc->SetMerchantLevelRange(result.GetInt32(76), result.GetInt32(77)); + + npc->SetAAXPRewards(result.GetInt32(78)); + + npc->SetLootTier(result.GetInt32(79)); + + npc->SetLootDropType(result.GetInt32(80)); + + npc->SetScaredByStrongPlayers(result.GetInt32(81)); + + if(!result.IsNull(82)){ + std::string action_state_str = std::string(result.GetString(82)); + npc->GetInfoStruct()->set_action_state(action_state_str); + } + + zone->AddNPC(id, npc); + + //skipped spells/skills/equipment as it is all loaded, the following rely on a spawn to load + LoadAppearance(zone, spawn_id); + LoadNPCAppearanceEquipmentData(zone, spawn_id); + + LogWrite(NPC__DEBUG, 0, "NPC", "Loaded NPC: '%s' (%u).", npc->appearance.name, spawn_id); + return true; + } + + LogWrite(NPC__DEBUG, 0, "NPC", "Unable to find a npc for spawn id of %u", spawn_id); + return false; +} + +void WorldDatabase::LoadAppearance(ZoneServer* zone, int32 spawn_id) { + Entity* entity = zone->GetNPC(spawn_id); + if (!entity) + return; + + DatabaseResult result, result2; + map appearance_types; + map > appearance_colors; + EQ2_Color color; + color.red = 0; + color.green = 0; + color.blue = 0; + string type; + + database_new.Select(&result2, "SELECT distinct `type`\n" + "FROM npc_appearance\n" + "WHERE length(`type`) > 0 AND `spawn_id` = %u", + spawn_id); + + while(result2.Next()) { + type = string(result2.GetString(0)); + appearance_types[type] = GetAppearanceType(type); + if(appearance_types[type] == 255) + LogWrite(WORLD__ERROR, 0, "Appearance", "Unknown appearance type '%s' in LoadAppearances.", type.c_str()); + } + + database_new.Select(&result, "SELECT `type`, `signed_value`, `red`, `green`, `blue`\n" + "FROM npc_appearance\n" + "WHERE length(`type`) > 0 AND `spawn_id` = %u", + spawn_id); + + while(result.Next()) { + if(appearance_types[result.GetString(0)] < APPEARANCE_SOGA_EBT){ + color.red = result.GetInt8(2); + color.green = result.GetInt8(3); + color.blue = result.GetInt8(4); + } + switch(appearance_types[result.GetString(0)]){ + case APPEARANCE_SOGA_HFHC:{ + entity->features.soga_hair_face_highlight_color = color; + break; + } + case APPEARANCE_SOGA_HTHC:{ + entity->features.soga_hair_type_highlight_color = color; + break; + } + case APPEARANCE_SOGA_HFC:{ + entity->features.soga_hair_face_color = color; + break; + } + case APPEARANCE_SOGA_HTC:{ + entity->features.soga_hair_type_color = color; + break; + } + case APPEARANCE_SOGA_HH:{ + entity->features.soga_hair_highlight_color = color; + break; + } + case APPEARANCE_SOGA_HC1:{ + entity->features.soga_hair_color1 = color; + break; + } + case APPEARANCE_SOGA_HC2:{ + entity->features.soga_hair_color2 = color; + break; + } + case APPEARANCE_SOGA_SC:{ + entity->features.soga_skin_color = color; + break; + } + case APPEARANCE_SOGA_EC:{ + entity->features.soga_eye_color = color; + break; + } + case APPEARANCE_HTHC:{ + entity->features.hair_type_highlight_color = color; + break; + } + case APPEARANCE_HFHC:{ + entity->features.hair_face_highlight_color = color; + break; + } + case APPEARANCE_HTC:{ + entity->features.hair_type_color = color; + break; + } + case APPEARANCE_HFC:{ + entity->features.hair_face_color = color; + break; + } + case APPEARANCE_HH:{ + entity->features.hair_highlight_color = color; + break; + } + case APPEARANCE_HC1:{ + entity->features.hair_color1 = color; + break; + } + case APPEARANCE_HC2:{ + entity->features.hair_color2 = color; + break; + } + case APPEARANCE_WC1:{ + entity->features.wing_color1 = color; + break; + } + case APPEARANCE_WC2:{ + entity->features.wing_color2 = color; + break; + } + case APPEARANCE_SC:{ + entity->features.skin_color = color; + break; + } + case APPEARANCE_EC:{ + entity->features.eye_color = color; + break; + } + case APPEARANCE_SOGA_EBT:{ + for(int i=0;i<3;i++) + entity->features.soga_eye_brow_type[i] = result.GetInt8(2+i); + break; + } + case APPEARANCE_SOGA_CHEEKT:{ + for(int i=0;i<3;i++) + entity->features.soga_cheek_type[i] = result.GetInt8(2+i); + break; + } + case APPEARANCE_SOGA_NT:{ + for(int i=0;i<3;i++) + entity->features.soga_nose_type[i] = result.GetInt8(2+i); + break; + } + case APPEARANCE_SOGA_CHINT:{ + for(int i=0;i<3;i++) + entity->features.soga_chin_type[i] = result.GetInt8(2+i); + break; + } + case APPEARANCE_SOGA_LT:{ + for(int i=0;i<3;i++) + entity->features.soga_lip_type[i] = result.GetInt8(2+i); + break; + } + case APPEARANCE_SOGA_EART:{ + for(int i=0;i<3;i++) + entity->features.soga_ear_type[i] = result.GetInt8(2+i); + break; + } + case APPEARANCE_SOGA_EYET:{ + for(int i=0;i<3;i++) + entity->features.soga_eye_type[i] = result.GetInt8(2+i); + break; + } + case APPEARANCE_EBT:{ + for(int i=0;i<3;i++) + entity->features.eye_brow_type[i] = result.GetInt8(2+i); + break; + } + case APPEARANCE_CHEEKT:{ + for(int i=0;i<3;i++) + entity->features.cheek_type[i] = result.GetInt8(2+i); + break; + } + case APPEARANCE_NT:{ + for(int i=0;i<3;i++) + entity->features.nose_type[i] = result.GetInt8(2+i); + break; + } + case APPEARANCE_CHINT:{ + for(int i=0;i<3;i++) + entity->features.chin_type[i] = result.GetInt8(2+i); + break; + } + case APPEARANCE_EART:{ + for(int i=0;i<3;i++) + entity->features.ear_type[i] = result.GetInt8(2+i); + break; + } + case APPEARANCE_EYET:{ + for(int i=0;i<3;i++) + entity->features.eye_type[i] = result.GetInt8(2+i); + break; + } + case APPEARANCE_LT:{ + for(int i=0;i<3;i++) + entity->features.lip_type[i] = result.GetInt8(2+i); + break; + } + case APPEARANCE_SHIRT:{ + entity->features.shirt_color = color; + break; + } + case APPEARANCE_UCC:{ + break; + } + case APPEARANCE_PANTS:{ + entity->features.pants_color = color; + break; + } + case APPEARANCE_ULC:{ + break; + } + case APPEARANCE_U9:{ + break; + } + case APPEARANCE_BODY_SIZE:{ + entity->features.body_size = color.red; + break; + } + case APPEARANCE_SOGA_WC1:{ + break; + } + case APPEARANCE_SOGA_WC2:{ + break; + } + case APPEARANCE_SOGA_SHIRT:{ + break; + } + case APPEARANCE_SOGA_UCC:{ + break; + } + case APPEARANCE_SOGA_PANTS:{ + break; + } + case APPEARANCE_SOGA_ULC:{ + break; + } + case APPEARANCE_SOGA_U13:{ + break; + } + case APPEARANCE_BODY_AGE: { + entity->features.body_age = color.red; + break; + } + case APPEARANCE_MC:{ + entity->features.model_color = color; + break; + } + case APPEARANCE_SMC:{ + entity->features.soga_model_color = color; + break; + } + case APPEARANCE_SBS: { + entity->features.soga_body_size = color.red; + break; + } + case APPEARANCE_SBA: { + entity->features.soga_body_age = color.red; + break; + } + } + } + + entity->info_changed = true; +} + +void WorldDatabase::LoadNPCAppearanceEquipmentData(ZoneServer* zone, int32 spawn_id) { + NPC* npc = zone->GetNPC(spawn_id); + if(!npc) { + LogWrite(NPC__ERROR, 0, "NPC", "Unable to get a valid npc (%u) in %s", spawn_id, __FUNCTION__); + return; + } + + DatabaseResult result; + int8 slot = 0; + + if (!database_new.Select(&result, "SELECT slot_id, equip_type, red, green, blue, highlight_red, highlight_green, highlight_blue\n" + "FROM npc_appearance_equip\n" + "WHERE spawn_id = %u\n", + spawn_id)) + { + + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return; + } + + while (result.Next()) { + slot = result.GetInt8(0); + if(slot < NUM_SLOTS) { + npc->SetEquipment(slot, result.GetInt16(1), result.GetInt8(2), result.GetInt8(3), result.GetInt8(4), result.GetInt8(5), result.GetInt8(6), result.GetInt8(7)); + } + } +} + +void WorldDatabase::SaveCharacterPicture(int32 characterID, int8 type, uchar* picture, int32 picture_size) { + stringstream ss_hex; + stringstream ss_query; + ss_hex.flags(ios::hex); + for (int32 i = 0; i < picture_size; i++) + ss_hex << setfill('0') << setw(2) << (int32)picture[i]; + + ss_query << "INSERT INTO `character_pictures` (`char_id`, `pic_type`, `picture`) VALUES (" << characterID << ", " << (int32)type << ", '" << ss_hex.str() << "') ON DUPLICATE KEY UPDATE `picture` = '" << ss_hex.str() << "'"; + + Query query; + query.RunQuery2(ss_query.str(), Q_REPLACE); + + if (query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF) + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error: in SaveCharacterPicture! Error Message: ", query.GetError()); +} + +void WorldDatabase::LoadZoneFlightPaths(ZoneServer* zone) { + DatabaseResult result; + int32 total = 0; + + if (!database_new.Select(&result, "SELECT id, speed, flying, early_dismount FROM flight_paths WHERE zone_id = %u", zone->GetZoneID())) { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return; + } + + while (result.Next()) { + FlightPathInfo* info = new FlightPathInfo; + int32 id = result.GetInt32(0); + info->speed = result.GetFloat(1); + info->flying = result.GetInt8(2) == 1 ? true : false; + info->dismount = result.GetInt8(3) == 1 ? true : false; + + zone->AddFlightPath(id, info); + total++; + } + + LogWrite(ZONE__DEBUG, 0, "Zone", "Loaded %u flight paths for %s", total, zone->GetZoneDescription()); + LoadZoneFlightPathLocations(zone); +} + +void WorldDatabase::LoadZoneFlightPathLocations(ZoneServer* zone) { + DatabaseResult result; + int32 total = 0; + + if (!database_new.Select(&result, "SELECT loc.flight_path, loc.x, loc.y, loc.z FROM flight_paths_locations loc\n" + "INNER JOIN flight_paths path\n" + "ON loc.flight_path = path.id\n" + "WHERE path.zone_id = %u\n" + "ORDER BY loc.id", + zone->GetZoneID())) + { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return; + } + + while (result.Next()) { + FlightPathLocation* loc = new FlightPathLocation; + int32 id = result.GetInt32(0); + loc->X = result.GetFloat(1); + loc->Y = result.GetFloat(2); + loc->Z = result.GetFloat(3); + + zone->AddFlightPathLocation(id, loc); + total++; + } + + LogWrite(ZONE__DEBUG, 0, "Zone", "Loaded %u flight path locations for %s", total, zone->GetZoneDescription()); +} + +void WorldDatabase::SaveCharacterLUAHistory(Player* player, int32 event_id, int32 value, int32 value2) { + Query query; + query.AddQueryAsync(player->GetCharacterID(), this, Q_REPLACE, "REPLACE INTO character_lua_history(char_id, event_id, value, value2) VALUES(% u, % u, % u, % u)", player->GetCharacterID(), event_id, value, value2); + +// if (!database_new.Query("REPLACE INTO character_lua_history (char_id, event_id, value, value2) VALUES (%u, %u, %u, %u)", player->GetCharacterID(), event_id, value, value2)) +// LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); +} + +void WorldDatabase::LoadCharacterLUAHistory(int32 char_id, Player* player) { + DatabaseResult result; + int32 total = 0; + + if (!database_new.Select(&result, "SELECT event_id, value, value2 FROM character_lua_history WHERE char_id = %u", char_id)) { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return; + } + + while (result.Next()) { + int32 id = result.GetInt32(0); + LUAHistory* hd = new LUAHistory; + hd->Value = result.GetInt32(1); + hd->Value2 = result.GetInt32(2); + hd->SaveNeeded = false; + player->LoadLUAHistory(id, hd); + total++; + } + + LogWrite(PLAYER__DEBUG, 0, "Player", "Loaded %u LUA history for %s", total, player->GetName()); +} + +void WorldDatabase::FindSpell(Client* client, char* findString) +{ + + char* find_escaped = getEscapeString(findString); + + DatabaseResult result; + if (!database_new.Select(&result, "SELECT s.`id`, ts.spell_id, ts.index, `name`, `tier` " + "FROM (spells s, spell_tiers st) " + "LEFT JOIN spell_ts_ability_index ts " + "ON s.`id` = ts.spell_id " + "WHERE s.id = st.spell_id and s.name like '%%%s%%' AND s.is_active = 1 " + "ORDER BY s.`id`, `tier` limit 50", find_escaped)) + { + // error + } + else + { + client->Message(CHANNEL_COLOR_YELLOW, "SpellID (SpellTier): SpellName for %s", findString); + while (result.Next()) + { + int32 spell_id = result.GetInt32Str("id"); + string spell_name = result.GetStringStr("name"); + int8 tier = result.GetInt8Str("tier"); + client->Message(CHANNEL_COLOR_YELLOW, "%i (%i): %s", spell_id, tier, spell_name.c_str()); + } + client->Message(CHANNEL_COLOR_YELLOW, "End Spell Results for %s", find_escaped); + } + + safe_delete_array(find_escaped); +} + +void WorldDatabase::LoadChestTraps() { + chest_trap_list.Clear(); + int32 index = 0; + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, applicable_zone_id, chest_min_difficulty, chest_max_difficulty, spell_id, spell_tier FROM chest_traps"); + if (result && mysql_num_rows(result) > 0) { + Title* title = 0; + while (result && (row = mysql_fetch_row(result))) { + int32 dbid = atoul(row[0]); + sint32 applicable_zone_id = atoi(row[1]); + int32 mindifficulty = atoul(row[2]); + int32 maxdifficulty = atoul(row[3]); + int32 spellid = atoul(row[4]); + int32 tier = atoul(row[5]); + ChestTrap* trap = new ChestTrap(dbid,applicable_zone_id,mindifficulty,maxdifficulty,spellid,tier); + chest_trap_list.AddChestTrap(trap); + } + } +} + +bool WorldDatabase::CheckExpansionFlags(ZoneServer* zone, int32 spawnXpackFlag) +{ + if (spawnXpackFlag == 0) + return true; + + int32 globalXpackFlag = rule_manager.GetGlobalRule(R_Expansion, GlobalExpansionFlag)->GetInt32(); + int32 zoneXpackFlag = zone->GetExpansionFlag(); + // zone expansion flag takes priority + if (zoneXpackFlag > 0 && (spawnXpackFlag & zoneXpackFlag) == 0) + return false; + // zone expansion flag fails, then if global expansion flag set, we see if that bit operand doesn't match, skip mob then + else if (zoneXpackFlag == 0 && globalXpackFlag > 0 && (spawnXpackFlag & globalXpackFlag) == 0) + return false; + + return true; +} + +bool WorldDatabase::CheckHolidayFlags(ZoneServer* zone, int32 spawnHolidayFlag) +{ + if (spawnHolidayFlag == 0) + return true; + + int32 globalHolidayFlag = rule_manager.GetGlobalRule(R_Expansion, GlobalHolidayFlag)->GetInt32(); + int32 zoneHolidayFlag = zone->GetHolidayFlag(); + // zone holiday flag takes priority + if (zoneHolidayFlag > 0 && (spawnHolidayFlag & zoneHolidayFlag) == 0) + return false; + // zone holiday flag fails, then if global expansion flag set, we see if that bit operand doesn't match, skip mob then + else if (zoneHolidayFlag == 0 && globalHolidayFlag > 0 && (spawnHolidayFlag & globalHolidayFlag) == 0) + return false; + + return true; +} + +void WorldDatabase::GetHouseSpawnInstanceData(ZoneServer* zone, Spawn* spawn) +{ + if (!spawn) + return; + + if (zone->house_object_database_lookup.count(spawn->GetModelType()) < 1) + zone->house_object_database_lookup.Put(spawn->GetModelType(), spawn->GetDatabaseID()); + + DatabaseResult result; + + database_new.Select(&result, "SELECT pickup_item_id, pickup_unique_item_id\n" + " FROM spawn_instance_data\n" + " WHERE spawn_id = %u and spawn_location_id = %u", + spawn->GetDatabaseID(),spawn->GetSpawnLocationID()); + + if (result.GetNumRows() > 0 && result.Next()) { + spawn->SetPickupItemID(result.GetInt32(0)); + spawn->SetPickupUniqueItemID(result.GetInt32(1)); + + if (spawn->GetZone() != nullptr && spawn->GetMap() != nullptr && spawn->GetMap()->IsMapLoaded()) + { + auto loc = glm::vec3(spawn->GetX(),spawn->GetZ(),spawn->GetY()); + uint32 GridID = 0; + float new_z = spawn->FindBestZ(loc, nullptr, &GridID); + spawn->SetPos(&(spawn->appearance.pos.grid_id), GridID); + } + } +} + +int32 WorldDatabase::FindHouseInstanceSpawn(Spawn* spawn) +{ + DatabaseResult result; + + database_new.Select(&result, "SELECT id\n" + " FROM spawn\n" + " WHERE model_type = %u and is_instanced_spawn=1 limit 1", + spawn->GetModelType()); + + if (result.GetNumRows() > 0 && result.Next()) { + return result.GetInt32(0); + } + + return 0; +} + + +void WorldDatabase::LoadStartingSkills(World* world) +{ + world->MStartingLists.writelock(); + + int32 total = 0; + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT race_id, class_id, skill_id, current_val, max_val, progress FROM starting_skills"); + + if (result) + { + if (mysql_num_rows(result) > 0) + { + Skill* skill = 0; + + while (result && (row = mysql_fetch_row(result))) + { + + StartingSkill skill; + skill.header.race_id = atoul(row[0]); + skill.header.class_id = atoul(row[1]); + skill.skill_id = atoul(row[2]); + skill.current_val = atoul(row[3]); + skill.max_val = atoul(row[4]); + + if (!world->starting_skills.count(skill.header.race_id)) + { + multimap* skills = new multimap(); + skills->insert(make_pair(skill.header.class_id, skill)); + world->starting_skills.insert(make_pair(skill.header.race_id, skills)); + } + else + { + multimap*>::const_iterator skills = world->starting_skills.find(skill.header.race_id); + skills->second->insert(make_pair(skill.header.class_id, skill)); + } + total++; + } + } + } + LogWrite(WORLD__DEBUG, 3, "World", "--Loaded %u Starting Skill(s)", total); + + world->MStartingLists.releasewritelock(); +} + + +void WorldDatabase::LoadVoiceOvers(World* world) +{ + int32 total = 0; + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT type_id, id, indexed, mp3_string, text_string, emote_string, key1, key2, garbled, garble_link_id FROM voiceovers"); + + if (result) + { + if (mysql_num_rows(result) > 0) + { + Skill* skill = 0; + + while (result && (row = mysql_fetch_row(result))) + { + + VoiceOverStruct vos; + vos.mp3_string = std::string(row[3]); + vos.text_string = std::string(row[4]); + vos.emote_string = std::string(row[5]); + vos.key1 = atoul(row[6]); + vos.key2 = atoul(row[7]); + vos.is_garbled = atoul(row[8]); + vos.garble_link_id = atoul(row[9]); + int8 type = atoul(row[0]); + int32 id = atoul(row[1]); + int16 index = atoul(row[2]); + world->AddVoiceOver(type, id, index, &vos); + total++; + } + } + } + LogWrite(WORLD__DEBUG, 3, "World", "--Loaded %u Voiceover(s)", total); +} + + +void WorldDatabase::LoadStartingSpells(World* world) +{ + world->MStartingLists.writelock(); + + int32 total = 0; + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT race_id, class_id, spell_id, tier, knowledge_slot FROM starting_spells"); + + if (result) + { + if (mysql_num_rows(result) > 0) + { + Skill* skill = 0; + + while (result && (row = mysql_fetch_row(result))) + { + + StartingSpell spell; + spell.header.race_id = atoul(row[0]); + spell.header.class_id = atoul(row[1]); + spell.spell_id = atoul(row[2]); + spell.tier = atoul(row[3]); + spell.knowledge_slot = atoul(row[4]); + + if (!world->starting_spells.count(spell.header.race_id)) + { + multimap* spells = new multimap(); + spells->insert(make_pair(spell.header.class_id, spell)); + world->starting_spells.insert(make_pair(spell.header.race_id, spells)); + } + else + { + multimap*>::iterator spells = world->starting_spells.find(spell.header.race_id); + spells->second->insert(make_pair(spell.header.class_id, spell)); + } + total++; + } + } + } + LogWrite(WORLD__DEBUG, 3, "World", "--Loaded %u Starting Spell(s)", total); + + world->MStartingLists.releasewritelock(); +} + + +bool WorldDatabase::DeleteSpiritShard(int32 id){ + Query query; + query.RunQuery2(Q_DELETE, "delete FROM character_spirit_shards where id=%u",id); + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(WORLD__ERROR, 0, "World", "Error in DeleteSpiritShard query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + return true; +} + +int32 WorldDatabase::CreateSpiritShard(const char* name, int32 level, int8 race, int8 gender, int8 adventure_class, + int16 model_type, int16 soga_model_type, int16 hair_type, int16 hair_face_type, int16 wing_type, + int16 chest_type, int16 legs_type, int16 soga_hair_type, int16 soga_hair_face_type, int8 hide_hood, + int16 size, int16 collision_radius, int16 action_state, int16 visual_state, int16 mood_state, int16 emote_state, + int16 pos_state, int16 activity_status, char* sub_title, char* prefix_title, char* suffix_title, char* lastname, + float x, float y, float z, float heading, int32 gridid, int32 charid, int32 zoneid, int32 instanceid) +{ + LogWrite(WORLD__INFO, 3, "World", "Saving Spirit Shard %s %u", name, charid); + + Query query; + char* name_escaped = getEscapeString(name); + + if(!sub_title) + sub_title = ""; + char* subtitle_escaped = getEscapeString(sub_title); + char* prefix_escaped = getEscapeString(prefix_title); + char* suffix_escaped = getEscapeString(suffix_title); + char* lastname_escaped = getEscapeString(lastname); + string insert = string("INSERT INTO character_spirit_shards (name, level, race, gender, adventure_class, model_type, soga_model_type, hair_type, hair_face_type, wing_type, chest_type, legs_type, soga_hair_type, soga_hair_face_type, hide_hood, size, collision_radius, action_state, visual_state, mood_state, emote_state, pos_state, activity_status, sub_title, prefix_title, suffix_title, lastname, x, y, z, heading, gridid, charid, zoneid, instanceid) VALUES ('%s', %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, '%s', '%s', '%s', '%s', %f, %f, %f, %f, %u, %u, %u, %u)"); + query.RunQuery2(Q_INSERT, insert.c_str(), name_escaped, level, race, gender, adventure_class, model_type, soga_model_type, + hair_type, hair_face_type, wing_type, chest_type, legs_type, soga_hair_type, + soga_hair_face_type, hide_hood, size, collision_radius, action_state, visual_state, + mood_state, emote_state, pos_state, activity_status, subtitle_escaped, prefix_escaped, suffix_escaped, + lastname_escaped, x, y, z, heading, gridid, charid, zoneid, instanceid); + + + safe_delete_array(name_escaped); + safe_delete_array(subtitle_escaped); + safe_delete_array(prefix_escaped); + safe_delete_array(suffix_escaped); + safe_delete_array(lastname_escaped); + + return query.GetLastInsertedID(); +} + +void WorldDatabase::LoadCharacterSpellEffects(int32 char_id, Client* client, int8 db_spell_type) +{ + SpellProcess* spellProcess = client->GetCurrentZone()->GetSpellProcess(); + Player* player = client->GetPlayer(); + + if(!spellProcess) + return; + DatabaseResult result; + + multimap restoreSpells; + // Use -1 on type and subtype to turn the enum into an int and make it a 0 index + if (!database_new.Select(&result, "SELECT name, caster_char_id, target_char_id, target_type, spell_id, effect_slot, slot_pos, icon, icon_backdrop, conc_used, tier, total_time, expire_timestamp, lua_file, custom_spell, damage_remaining, effect_bitmask, num_triggers, had_triggers, cancel_after_triggers, crit, last_spellattack_hit, interrupted, resisted, has_damaged, custom_function FROM character_spell_effects WHERE charid = %u and db_effect_type = %u", char_id, db_spell_type)) { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return; + } + InfoStruct* info = player->GetInfoStruct(); + while (result.Next()) { +//result.GetInt8Str + char spell_name[60]; + strncpy(spell_name, result.GetStringStr("name"), 60); + + int32 caster_char_id = result.GetInt32Str("caster_char_id"); + int32 target_char_id = result.GetInt32Str("target_char_id"); + int8 target_type = result.GetInt8Str("target_type"); + int32 spell_id = result.GetInt32Str("spell_id"); + int32 effect_slot = result.GetInt32Str("effect_slot"); + int32 slot_pos = result.GetInt32Str("slot_pos"); + int16 icon = result.GetInt32Str("icon"); + int16 icon_backdrop = result.GetInt32Str("icon_backdrop"); + int8 conc_used = result.GetInt32Str("conc_used"); + int8 tier = result.GetInt32Str("tier"); + float total_time = result.GetFloatStr("total_time"); + int32 expire_timestamp = result.GetInt32Str("expire_timestamp"); + + string lua_file (result.GetStringStr("lua_file")); + + int8 custom_spell = result.GetInt32Str("custom_spell"); + + int32 damage_remaining = result.GetInt32Str("damage_remaining"); + int32 effect_bitmask = result.GetInt32Str("effect_bitmask"); + int16 num_triggers = result.GetInt32Str("num_triggers"); + int8 had_triggers = result.GetInt32Str("had_triggers"); + int8 cancel_after_triggers = result.GetInt32Str("cancel_after_triggers"); + int8 crit = result.GetInt32Str("crit"); + int8 last_spellattack_hit = result.GetInt32Str("last_spellattack_hit"); + int8 interrupted = result.GetInt32Str("interrupted"); + int8 resisted = result.GetInt32Str("resisted"); + int8 has_damaged = result.GetInt32Str("has_damaged"); + std::string custom_function = std::string(result.GetStringStr("custom_function")); + LuaSpell* lua_spell = 0; + if(custom_spell) + { + if((lua_spell = lua_interface->GetSpell(lua_file.c_str())) == nullptr) + { + LogWrite(LUA__WARNING, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: GetSpell(%u, %u, '%s'), custom lua script not loaded, when attempting to load.", spell_id, tier, lua_file.c_str()); + lua_interface->LoadLuaSpell(lua_file); + } + } + + Spell* spell = master_spell_list.GetSpell(spell_id, tier); + + if(!spell) + { + LogWrite(LUA__ERROR, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: GetSpell(%u, %u), spell could not be found!", spell_id, tier); + spell = master_spell_list.GetSpell(spell_id, 0); + if(spell) + LogWrite(LUA__WARNING, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: GetSpell(%u, %u), identified tier 0 as replacement since the GetSpell failed!", spell_id, tier); + else + continue; + } + + bool isMaintained = false; + bool isExistingLuaSpell = false; + MaintainedEffects* effect = nullptr; + Client* tmpCaster = nullptr; + if(caster_char_id == player->GetCharacterID() && (target_char_id == 0xFFFFFFFF || target_char_id == player->GetCharacterID()) && (effect = player->GetMaintainedSpell(spell_id)) != nullptr) + { + safe_delete(lua_spell); + lua_spell = effect->spell; + if(lua_spell) + spell = lua_spell->spell; + isMaintained = true; + isExistingLuaSpell = true; + } + else if ( caster_char_id != player->GetCharacterID() && (tmpCaster = zone_list.GetClientByCharID(caster_char_id)) != nullptr + && tmpCaster->GetPlayer() && (effect = tmpCaster->GetPlayer()->GetMaintainedSpell(spell_id)) != nullptr) + { + if(effect->spell && effect->spell_id == spell_id) + { + safe_delete(lua_spell); + if(tmpCaster->GetCurrentZone() == player->GetZone()) + spellProcess->AddLuaSpellTarget(effect->spell, client->GetPlayer()->GetID()); + lua_spell = effect->spell; + spell = effect->spell->spell; + isExistingLuaSpell = true; + isMaintained = true; + LogWrite(LUA__WARNING, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: GetSpell(%u, %u, '%s'), effect spell id %u maintained spell recovered from %s", spell_id, tier, spell_name, effect ? effect->spell_id : 0, (tmpCaster && tmpCaster->GetPlayer()) ? tmpCaster->GetPlayer()->GetName() : "?"); + } + else + { + LogWrite(LUA__WARNING, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: GetSpell(%u, %u, '%s'), something went wrong loading another characters maintained spell. Effect has spell id %u", spell_id, tier, lua_file.c_str(), effect ? effect->spell_id : 0); + safe_delete(lua_spell); + continue; + } + } + else if(custom_spell && lua_spell) + { + lua_spell->spell = new Spell(spell); + + lua_interface->AddCustomSpell(lua_spell); + } + else if(db_spell_type == DB_TYPE_MAINTAINEDEFFECTS) + { + safe_delete(lua_spell); + if(!target_char_id) + continue; + + lua_spell = lua_interface->GetSpell(spell->GetSpellData()->lua_script.c_str()); + if(lua_spell) + lua_spell->spell = spell; + } + + if(!lua_spell) + { + LogWrite(LUA__ERROR, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: GetSpell(%u, %u, '%s'), lua_spell FAILED, when attempting to load.", spell_id, tier, lua_file.c_str()); + continue; + } + + SpellScriptTimer* timer = nullptr; + if(!isExistingLuaSpell && expire_timestamp != 0xFFFFFFFF && custom_function.size() > 0) + { + timer = new SpellScriptTimer; + + timer->caster = 0; + timer->deleteWhenDone = false; + timer->target = 0; + + timer->time = expire_timestamp; + timer->customFunction = string(custom_function); // TODO + timer->spell = lua_spell; + timer->caster = (caster_char_id == player->GetCharacterID()) ? player->GetID() : 0; + + if(target_char_id == 0xFFFFFFFF && player->HasPet()) + timer->target = player->GetPet()->GetID(); + else + timer->target = (target_char_id == player->GetCharacterID()) ? player->GetID() : 0; + + if(!timer->target && target_char_id) + { + Client* tmpClient = zone_list.GetClientByCharID(target_char_id); + if(tmpClient && tmpClient->GetPlayer() && tmpClient->GetPlayer()->GetZone() == player->GetZone()) + timer->target = tmpClient->GetPlayer()->GetID(); + } + } + + if(!isExistingLuaSpell) + { + lua_spell->crit = crit; + lua_spell->damage_remaining = damage_remaining; + lua_spell->effect_bitmask = effect_bitmask; + lua_spell->had_dmg_remaining = (damage_remaining>0) ? true : false; + lua_spell->had_triggers = had_triggers; + lua_spell->initial_caster_char_id = caster_char_id; + lua_spell->initial_target = (target_char_id == player->GetCharacterID()) ? player->GetID() : 0; + lua_spell->initial_target_char_id = target_char_id; + lua_spell->interrupted = interrupted; + lua_spell->last_spellattack_hit = last_spellattack_hit; + lua_spell->num_triggers = num_triggers; + lua_spell->has_damaged = has_damaged; + lua_spell->is_damage_spell = has_damaged; + } + + if(lua_spell->initial_target == 0 && target_char_id == 0xFFFFFFFF && player->HasPet()) + { + lua_spell->initial_target = player->GetPet()->GetID(); + lua_spell->initial_target_char_id = target_char_id; + } + //lua_spell->num_calls ?? + //if(target_char_id == player->GetCharacterID()) + // lua_spell->targets.push_back(player->GetID()); + + if(db_spell_type == DB_TYPE_SPELLEFFECTS) + { + if (caster_char_id != player->GetCharacterID() && lua_spell->spell->GetSpellData()->group_spell && lua_spell->spell->GetSpellData()->friendly_spell) + { + if(!isExistingLuaSpell) + safe_delete(lua_spell); + continue; + } + + player->MSpellEffects.writelock(); + LogWrite(LUA__WARNING, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: %s lua_spell caster %s (%u), caster char id: %u.", lua_spell->spell->GetName(), lua_spell->caster ? lua_spell->caster->GetName() : "", lua_spell->caster ? lua_spell->caster->GetID() : 0, caster_char_id); + + if(lua_spell->caster && (rule_manager.GetGlobalRule(R_Spells,EnableCrossZoneTargetBuffs)->GetInt8() || (!rule_manager.GetGlobalRule(R_Spells,EnableCrossZoneTargetBuffs)->GetInt8() && lua_spell->caster->GetZone() == player->GetZone()))) + info->spell_effects[effect_slot].caster = lua_spell->caster; + else if(caster_char_id != player->GetCharacterID()) + { + Client* tmpCaster = zone_list.GetClientByCharID(caster_char_id); + if(tmpCaster) + { + if((rule_manager.GetGlobalRule(R_Spells,EnableCrossZoneTargetBuffs)->GetInt8() || (!rule_manager.GetGlobalRule(R_Spells,EnableCrossZoneTargetBuffs)->GetInt8() && lua_spell->caster->GetZone() == player->GetZone()))) + { + info->spell_effects[effect_slot].caster = tmpCaster->GetPlayer(); + LogWrite(LUA__WARNING, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: %s lua_spell caster %s (%u), caster char id: %u, found player %s.", lua_spell->spell->GetName(), lua_spell->caster ? lua_spell->caster->GetName() : "", lua_spell->caster ? lua_spell->caster->GetID() : 0, caster_char_id, tmpCaster->GetPlayer()->GetName()); + } + else + { + LogWrite(LUA__WARNING, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: %s lua_spell caster %s (%u), caster char id: %u, found player %s, SKIPPED due to R_Spells, EnableCrossZoneTargetBuffs.", lua_spell->spell->GetName(), lua_spell->caster ? lua_spell->caster->GetName() : "", lua_spell->caster ? lua_spell->caster->GetID() : 0, caster_char_id, tmpCaster->GetPlayer()->GetName()); + if(!isExistingLuaSpell) + { + safe_delete(lua_spell); + } + else + { + lua_spell->MSpellTargets.writelock(__FUNCTION__, __LINE__); + lua_spell->char_id_targets.insert(make_pair(player->GetCharacterID(),0)); + lua_spell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__); + } + player->MSpellEffects.releasewritelock(); + continue; + } + + } + } + else if(caster_char_id == player->GetCharacterID()) + info->spell_effects[effect_slot].caster = player; + else + { + LogWrite(LUA__ERROR, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: %s lua_spell caster %s (%u), caster char id: %u, failed to find caster will delete: %u", lua_spell->spell->GetName(), lua_spell->caster ? lua_spell->caster->GetName() : "", lua_spell->caster ? lua_spell->caster->GetID() : 0, caster_char_id, isExistingLuaSpell); + if(!isExistingLuaSpell) + safe_delete(lua_spell); + continue; + } + if(spell->GetSpellData()->duration_until_cancel) + info->spell_effects[effect_slot].expire_timestamp = 0xFFFFFFFF; + else + info->spell_effects[effect_slot].expire_timestamp = Timer::GetCurrentTime2() + expire_timestamp; + info->spell_effects[effect_slot].icon = icon; + info->spell_effects[effect_slot].icon_backdrop = icon_backdrop; + info->spell_effects[effect_slot].spell_id = spell_id; + info->spell_effects[effect_slot].tier = tier; + info->spell_effects[effect_slot].total_time = total_time; + info->spell_effects[effect_slot].spell = lua_spell; + + lua_spell->MSpellTargets.writelock(__FUNCTION__, __LINE__); + multimap::iterator entries; + while((entries = lua_spell->char_id_targets.find(player->GetCharacterID())) != lua_spell->char_id_targets.end()) + { + lua_spell->char_id_targets.erase(entries); + } + lua_spell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__); + + lua_spell->slot_pos = slot_pos; + if(!isExistingLuaSpell) + lua_spell->caster = player; // TODO: get actual player + + player->MSpellEffects.releasewritelock(); + + if(!isMaintained) + spellProcess->ProcessSpell(lua_spell, true, "cast", timer); + else + { + // track target id when caster isnt in zone somehow + } + } + else if ( db_spell_type == DB_TYPE_MAINTAINEDEFFECTS ) + { + player->MMaintainedSpells.writelock(); + + DatabaseResult targets; + // Use -1 on type and subtype to turn the enum into an int and make it a 0 index + if (database_new.Select(&targets, "SELECT target_char_id, target_type, db_effect_type, spell_id from character_spell_effect_targets where caster_char_id = %u and effect_slot = %u and slot_pos = %u", char_id, effect_slot, slot_pos)) { + while (targets.Next()) { + int32 target_char = targets.GetInt32Str("target_char_id"); + int8 maintained_target_type = targets.GetInt32Str("target_type"); + int32 in_spell_id = targets.GetInt32Str("spell_id"); + if(spell_id != in_spell_id) + continue; + + int32 idToAdd = 0; + + if(target_char == 0xFFFFFFFF) + { + if( player->HasPet() ) + { + restoreSpells.insert(make_pair(lua_spell, player->GetPet())); + // target added via restoreSpells + } + } + else + { + Client* client2 = zone_list.GetClientByCharID(target_char); + if(client2 && client2->GetPlayer() && client2->GetCurrentZone() == client->GetCurrentZone()) + { + if(maintained_target_type > 0) + { + if(client != client2) + { + lua_spell->MSpellTargets.writelock(__FUNCTION__, __LINE__); + + if(client2->GetPlayer()->GetPet() && maintained_target_type == PET_TYPE_COMBAT) + { + restoreSpells.insert(make_pair(lua_spell, client2->GetPlayer()->GetPet())); + // target added via restoreSpells + } + if(client2->GetPlayer()->GetCharmedPet() && maintained_target_type == PET_TYPE_CHARMED) + { + restoreSpells.insert(make_pair(lua_spell, client2->GetPlayer()->GetCharmedPet())); + // target added via restoreSpells + } + + lua_spell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__); + } + } // end of pet clause + else if(client != client2) // maintained type must be 0, so client + restoreSpells.insert(make_pair(lua_spell, client2->GetPlayer())); + + lua_spell->MSpellTargets.writelock(__FUNCTION__, __LINE__); + multimap::iterator entries; + for(entries = lua_spell->char_id_targets.begin(); entries != lua_spell->char_id_targets.end(); entries++) + { + int32 ent_char_id = entries->first; + int8 ent_target_type = entries->second; + if(ent_char_id == target_char && ent_target_type == maintained_target_type) + entries = lua_spell->char_id_targets.erase(entries); + } + lua_spell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__); + } + else + { + lua_spell->MSpellTargets.writelock(__FUNCTION__, __LINE__); + lua_spell->char_id_targets.insert(make_pair(target_char,maintained_target_type)); + lua_spell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__); + } + } + } + } + + Client* tmpClient = 0; + int32 targetID = 0; + if(target_char_id == 0xFFFFFFFF && player->HasPet()) + targetID = player->GetPet()->GetID(); + else if(target_char_id == player->GetCharacterID()) + { + targetID = player->GetID(); + tmpClient = player->GetClient(); + } + else if((tmpClient = zone_list.GetClientByCharID(target_char_id)) != nullptr && tmpClient->GetPlayer()) + targetID = tmpClient->GetPlayer()->GetID(); + + info->maintained_effects[effect_slot].conc_used = conc_used; + strncpy(info->maintained_effects[effect_slot].name, spell_name, 60); + info->maintained_effects[effect_slot].slot_pos = slot_pos; + info->maintained_effects[effect_slot].target = targetID; + info->maintained_effects[effect_slot].target_type = target_type; + if(spell->GetSpellData()->duration_until_cancel) + info->maintained_effects[effect_slot].expire_timestamp = 0xFFFFFFFF; + else + info->maintained_effects[effect_slot].expire_timestamp = Timer::GetCurrentTime2() + expire_timestamp; + info->maintained_effects[effect_slot].icon = icon; + info->maintained_effects[effect_slot].icon_backdrop = icon_backdrop; + info->maintained_effects[effect_slot].spell_id = spell_id; + info->maintained_effects[effect_slot].tier = tier; + info->maintained_effects[effect_slot].total_time = total_time; + info->maintained_effects[effect_slot].spell = lua_spell; + if(!isExistingLuaSpell) + lua_spell->caster = player; + player->MMaintainedSpells.releasewritelock(); + + LogWrite(LUA__WARNING, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: %s process spell caster %s (%u), caster char id: %u, target id %u (%s).", lua_spell->spell->GetName(), lua_spell->caster ? lua_spell->caster->GetName() : "", + lua_spell->caster ? lua_spell->caster->GetID() : 0, caster_char_id, targetID, tmpClient ? tmpClient->GetPlayer()->GetName() : ""); + if(tmpClient && lua_spell->initial_target_char_id == tmpClient->GetCharacterID()) + { + lua_spell->initial_target = tmpClient->GetPlayer()->GetID(); + spellProcess->AddLuaSpellTarget(lua_spell, lua_spell->initial_target, false); + } + + spellProcess->ProcessSpell(lua_spell, true, "cast", timer); + } + if(!isExistingLuaSpell && expire_timestamp != 0xFFFFFFFF && !isMaintained) + { + lua_spell->timer.SetTimer(expire_timestamp); + lua_spell->timer.SetAtTrigger(lua_spell->spell->GetSpellData()->duration1 * 100); + lua_spell->timer.Start(); + } + + if(target_char_id == player->GetCharacterID() && lua_spell->spell->GetSpellData()->det_type) + player->AddDetrimentalSpell(lua_spell, expire_timestamp); + + if(!isExistingLuaSpell) + { + if(timer) + spellProcess->AddSpellScriptTimer(timer); + + lua_spell->num_calls = 1; + lua_spell->restored = true; + } + + if(!lua_spell->resisted && (lua_spell->spell->GetSpellDuration() > 0 || lua_spell->spell->GetSpellData()->duration_until_cancel)) + spellProcess->AddActiveSpell(lua_spell); + + if ( db_spell_type == DB_TYPE_MAINTAINEDEFFECTS ) + { + // restore concentration on zoning/reloading characters maintained effects + spellProcess->AddConcentration(lua_spell); + if (num_triggers > 0) + ClientPacketFunctions::SendMaintainedExamineUpdate(client, slot_pos, num_triggers, 0); + if (damage_remaining > 0) + ClientPacketFunctions::SendMaintainedExamineUpdate(client, slot_pos, damage_remaining, 1); + } + } + + if(db_spell_type == DB_TYPE_SPELLEFFECTS) + { + // if the cross_zone_target_buff option is disabled then we check for all possible targets within the current zone + // if cross_zone_target_buff is enabled, we only check to restore pets, the players get restored by their own spell effects (we don't directly track the pets, indirectly we do through the player casting and their targets) + int8 cross_zone_target_buff = rule_manager.GetGlobalRule(R_Spells,EnableCrossZoneTargetBuffs)->GetInt8(); + + DatabaseResult targets; + if ( + (!cross_zone_target_buff && database_new.Select(&targets, "SELECT caster_char_id, target_type, spell_id from character_spell_effect_targets where target_char_id = %u", player->GetCharacterID())) || + (database_new.Select(&targets, "SELECT caster_char_id, target_type, spell_id from character_spell_effect_targets where target_char_id = %u and target_type > 0", player->GetCharacterID()))) + { + while (targets.Next()) { + int32 caster_char_id = targets.GetInt32Str("caster_char_id"); + int8 prev_target_type = targets.GetInt32Str("target_type"); + int32 in_spell_id = targets.GetInt32Str("spell_id"); + Client* tmpCaster = nullptr; + MaintainedEffects* effect = nullptr; + if (caster_char_id != player->GetCharacterID() && (tmpCaster = zone_list.GetClientByCharID(caster_char_id)) != nullptr && (cross_zone_target_buff || + tmpCaster->GetCurrentZone() == player->GetZone()) && tmpCaster->GetPlayer() && (effect = tmpCaster->GetPlayer()->GetMaintainedSpell(in_spell_id)) != nullptr) + { + if(prev_target_type > 0) + { + if(player->HasPet()) + { + if(player->GetPet() && prev_target_type == PET_TYPE_COMBAT) + restoreSpells.insert(make_pair(effect->spell, player->GetPet())); + if(player->GetCharmedPet() && prev_target_type == PET_TYPE_CHARMED) + restoreSpells.insert(make_pair(effect->spell, player->GetCharmedPet())); + } + } + else if(!player->GetSpellEffect(effect->spell_id, tmpCaster->GetPlayer())) + { + if(effect->spell->initial_target_char_id == player->GetCharacterID()) + effect->spell->initial_target = player->GetID(); + restoreSpells.insert(make_pair(effect->spell, player)); + } + } + } + } + } + + multimap::const_iterator itr; + + for (itr = restoreSpells.begin(); itr != restoreSpells.end(); itr++) + { + LuaSpell* tmpSpell = itr->first; + Entity* target = itr->second; + if(!target) + { + target = client->GetPlayer()->GetPet(); + if(!target) + continue; + } + + Entity* caster = tmpSpell->caster; + if(!caster) + caster = client->GetPlayer(); + + int32 group_id_target = target->group_id; + if(target->IsPet() && target->GetOwner()) + group_id_target = target->GetOwner()->group_id; + + if(caster != target && caster->GetPet() != target && + tmpSpell->spell->GetSpellData()->group_spell && tmpSpell->spell->GetSpellData()->friendly_spell && (caster->group_id == 0 || group_id_target == 0 || caster->group_id != group_id_target)) + { + LogWrite(LUA__WARNING, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: %s player no longer grouped with %s to reload bonuses for spell %s (target_groupid: %u, caster_groupid: %u).", target->GetName(), caster ? caster->GetName() : "?", tmpSpell->spell->GetName(), group_id_target, caster->group_id); + continue; + } + + LogWrite(LUA__WARNING, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: %s using caster %s to reload bonuses for spell %s.", player->GetName(), caster ? caster->GetName() : "?", tmpSpell->spell->GetName()); + + spellProcess->AddLuaSpellTarget(tmpSpell, target->GetID()); + + target->AddSpellEffect(tmpSpell, tmpSpell->timer.GetRemainingTime() != 0 ? tmpSpell->timer.GetRemainingTime() : 0); + vector* sb_list = caster->GetAllSpellBonuses(tmpSpell); + for (int32 x = 0; x < sb_list->size(); x++) { + BonusValues* bv = sb_list->at(x); + target->AddSpellBonus(tmpSpell, bv->type, bv->value, bv->class_req, bv->race_req, bv->faction_req); + } + sb_list->clear(); + safe_delete(sb_list); + // look for a skill bonus on the caster's spell + if(caster->IsPlayer()) + { + SkillBonus* sb = ((Player*)caster)->GetSkillBonus(tmpSpell->spell->GetSpellID()); + if (sb) { + map::iterator itr_skills; + for (itr_skills = sb->skills.begin(); itr_skills != sb->skills.end(); itr_skills++) + target->AddSkillBonus(sb->spell_id, (*itr_skills).second->skill_id, (*itr_skills).second->value); + } + } + + } + +} + +//devn00b: We need to handle non-found factions so the subtraction/addition works on 1st kill. Need to find a better way to handle this, but for now.. +bool WorldDatabase::VerifyFactionID(int32 char_id, int32 faction_id) { + DatabaseResult result; + database_new.Select(&result, "SELECT COUNT(id) as faction_exists from character_factions where faction_id=%u and char_id=%u", faction_id, char_id); + + if (result.Next() && result.GetInt32Str("faction_exists") == 0) + return false; + + return true; +} + +//using int/32 for starting city, should be large enough to support future zones (LOL). TODO: Will probably need more support bolted on, to support PVP and such. +void WorldDatabase::UpdateStartingLanguage(int32 char_id, uint8 race_id, int32 starting_city) +{ + int8 rule = rule_manager.GetGlobalRule(R_World, StartingZoneLanguages)->GetInt8(); + //this should never need to be done but lets make sure we got all the stuff we need. char_id at min since used for both. + if(!char_id || (rule == 0 && !race_id) || (rule == 1 && !starting_city)) + return; + + std::string query_string = "SELECT language_id FROM starting_languages "; + //check the rule, and that they gave us a race, if so use default entries to match live. + if(rule == 0 && race_id >= 0) { + query_string.append("WHERE race=" + std::to_string(race_id)); + } + //if we have a starting city supplied, and the rule is set to use it, deal with it + else if(rule == 1) { + query_string.append("WHERE starting_city=" + std::to_string(starting_city) + " or (starting_city=0 and race_id=" + std::to_string(race_id) + ")"); + } + + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT,query_string.c_str()); + LogWrite(PLAYER__DEBUG, 0, "Player", "Adding Languages for starting_city: %i based on rule R_World:StartingZoneLanguages value %u", starting_city, rule); + if (result) { + if (mysql_num_rows(result) > 0) { + while (result && (row = mysql_fetch_row(result))){ + //add custom languages to the character_languages db. + Query query2; + query2.RunQuery2(Q_INSERT, "INSERT IGNORE INTO character_languages (char_id, language_id) VALUES (%u,%u)",char_id, atoul(row[0])); + } + } + } +} + + +//devn00b: load the items the server has into a character_ db for easy access. Should be done on char create. +void WorldDatabase::LoadClaimItems(int32 char_id) +{ + if (!char_id) { + LogWrite(WORLD__DEBUG, 3, "World", "-- There was an error in LoadClaimItems (missing char_id)"); + return; + } + + int16 total = 0; + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, item_id, max_claim, one_per_char, veteran_reward_time from claim_items"); + + if (result) + { + if (mysql_num_rows(result) > 0) + { + while (result && (row = mysql_fetch_row(result))) + { + int32 item_id = atoul(row[1]); + int16 max_claim = atoul(row[2]); + int16 curr_claim = max_claim; + int8 one_per_char = atoul(row[3]); + int64 vet_reward_time = atoi64(row[4]); + int32 acct_id = GetCharacterAccountID(char_id); + + if (one_per_char == 1) { + max_claim = 1; + curr_claim = 1; + } + Query query2; + MYSQL_RES* res = query2.RunQuery2(Q_INSERT, "insert ignore into character_claim_items (char_id, item_id, max_claim, curr_claim, one_per_char, veteran_reward_time, account_id) values (%i, %i, %i, %i, %i, %I64i, %i)", char_id, item_id, max_claim, curr_claim, one_per_char, vet_reward_time, acct_id); + total++; + } + } + } + LogWrite(WORLD__DEBUG, 3, "World", "--Loaded %u Claim Item(s)", total); +} + +int16 WorldDatabase::CountCharClaimItems(int32 char_id) { + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT count(*) from character_claim_items where char_id=%i and curr_claim >0", char_id); + + if (result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + return atoi(row[0]); // Return count + } + + return 0; +} + +vector WorldDatabase::LoadCharacterClaimItems(int32 char_id) { + vector claim; + + //make sure we have a charID shouldn't need this but adding anyway. + if (!char_id) + return claim; + + int32 acct_id = GetCharacterAccountID(char_id); + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, max_claim, curr_claim, one_per_char, last_claim, veteran_reward_time from character_claim_items where char_id=%u and curr_claim > 0", char_id); + int16 i = 0; + + if (result) + { + if (mysql_num_rows(result) > 0) + { + + while (result && (row = mysql_fetch_row(result))) + { + ClaimItems c; + int8 max_claim = atoi(row[1]); + int8 curr_claim = atoi(row[2]); + + //if one per char is set set max/cur to 1. + if (atoi(row[3]) == 1) { + max_claim = 1; + curr_claim = 1; + } + if (atoi(row[3]) == 0) { + + //handle getting lowest claim amt from table, to prevent new chars skewing. + Query query2; + MYSQL_ROW row2; + MYSQL_RES* result2 = query2.RunQuery2(Q_SELECT, "SELECT min(curr_claim) AS min_claim FROM character_claim_items WHERE account_id=%i and item_id=%i", acct_id, atoi(row[0])); + + //our current claim is now the lowest. + if (result2 && (row2 = mysql_fetch_row(result2)) && row2[0] != NULL) { + int8 curr_claim = atoi(row2[0]); + } + + } + + c.item_id = atoi(row[0]); + c.max_claim = max_claim; + c.curr_claim = curr_claim; + c.one_per_char = atoi(row[3]); + c.vet_reward_time = atoi(row[5]); + claim.push_back(c); + i++; + } + return claim; + } + } + return claim; +} + +void WorldDatabase::ClaimItem(int32 char_id, int32 item_id, Client* client) { + + if (!client || !item_id || !char_id) { + return; + } + + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, max_claim, curr_claim, one_per_char, last_claim, veteran_reward_time, one_per_char from character_claim_items where char_id=%u and item_id=%u", char_id, item_id); + + if (result) + { + if (mysql_num_rows(result) > 0) + { + while (result && (row = mysql_fetch_row(result))) + { + Item* item = master_item_list.GetItem(item_id); + Player* player = client->GetPlayer(); + InfoStruct* info = player->GetInfoStruct(); + + int32 item_id = atoi(row[0]); + int8 max_claim = atoi(row[1]); + int8 min_claim = 0; + int8 curr_claim = atoi(row[2]); + int32 vet_reward_req = atoi(row[5]); + int8 one_per_char = atoi(row[6]); + int32 acct_id = GetCharacterAccountID(char_id); + char claim_msg[512] = { 0 }; + + //no claims left. + if (curr_claim == 0) { + client->Message(CHANNEL_COLOR_RED, "You have nothing to claim."); + return; + } + + //On live, characters must wait 6 seconds between claims. So..Lots of stuff here. + uint32 last_claim = info->get_last_claim_time(); //load our last claim + time_t now = time(0); //get the current epoc time + uint32 curr_time = now; // current time. + uint32 total_time = curr_time - last_claim; //Time since last claim (Current time - last claim) + + if (total_time < 6) { + uint32 ttw = 6 - total_time; + char tmp[64] = { 0 }; + sprintf(tmp, "You must wait %u more seconds.", ttw); + client->Message(CHANNEL_COLOR_RED, tmp); + return; + } + //handle the 2 different messages found from live (11/2022) + if (item->generic_info.item_type == 18) { + sprintf(claim_msg, "You have consumed the item\nYou have chosen to give this character a %s", item->name.c_str()); + } + else { + sprintf(claim_msg, "You have consumed the item"); + } + + + //not one per char, so remove 1 from account. + if (one_per_char == 0) { + Query query3; + MYSQL_ROW row3; + MYSQL_RES* result3 = query3.RunQuery2(Q_SELECT, "SELECT min(curr_claim) AS min_claim FROM character_claim_items WHERE account_id=%i and item_id=%i", acct_id, item_id); + + if (result3 && (row3 = mysql_fetch_row(result3)) && row3[0] != NULL) { + int16 min_claim = atoi(row3[0]); + + //remove 1 from claim on one per char + int32 acct_id = GetCharacterAccountID(char_id); + Query query2; + query2.RunQuery2(Q_UPDATE, "UPDATE `character_claim_items` SET curr_claim=(SELECT min(curr_claim) AS min_claim FROM character_claim_items WHERE account_id=%i and item_id=%i) - 1 , `last_claim`=%u WHERE account_id=%i and item_id=%i AND curr_claim > 0", acct_id, item_id, curr_time, acct_id, item_id); + //give the item to the player, and update last claim time. + bool item_added = client->AddItem(item_id); + if (item_added == 0) { + return; + } + info->set_last_claim_time(curr_time); + client->Message(CHANNEL_COLOR_YELLOW, claim_msg); + //reload the window to show new count. + client->ShowClaimWindow(); + return; + } + } + + Query query4; + query4.RunQuery2(Q_UPDATE, "UPDATE `character_claim_items` SET `curr_claim`=0 , `last_claim`=%u WHERE char_id=%i and item_id=%i", curr_time, char_id, item_id); + //give the item to the player, and update last claim time. + //client->AddItem(item_id); + bool item_added = client->AddItem(item_id); + if (item_added == 0) { + return; + } + info->set_last_claim_time(curr_time); + client->Message(CHANNEL_COLOR_YELLOW, claim_msg); + //reload the window to show new count. + client->ShowClaimWindow(); + return; + } + } + } + return; +} + +//returns account age in seconds +int32 WorldDatabase::GetAccountAge(int32 account_id) { + if (!account_id) + return 0; + + int32 acct_age = 0; + int32 acct_created = 0; + time_t now = time(0); //get the current epoc time + uint32 curr_time = now; // current time. + + Query query; + //order by created date and grab the oldest one. + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT UNIX_TIMESTAMP(created_date) FROM characters WHERE account_id=%i ORDER BY created_date LIMIT 1", account_id); + if (result && mysql_num_rows(result) > 0) { + MYSQL_ROW row = mysql_fetch_row(result); + acct_created = atoi(row[0]); + } + if (!curr_time || !acct_created) + return 0; + + acct_age = curr_time - acct_created; + return acct_age; +} + +void WorldDatabase::SaveSignMark(int32 char_id, int32 sign_id, char* char_name, Client* client) { + + if (!database_new.Query("update spawn_signs set char_id=%u, char_name='%s' where widget_id=%u", char_id, char_name, sign_id)) { + LogWrite(SIGN__DEBUG, 0, "Sign", "ERROR in WorldDatabase::SaveSignMark"); + return; + } + +} + +string WorldDatabase::GetSignMark(int32 char_id, int32 sign_id, char* char_name) { + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT char_name from spawn_signs where widget_id=%u", sign_id); + char* charname = 0; + + if (result && mysql_num_rows(result) > 0) { + MYSQL_ROW row = mysql_fetch_row(result); + + charname = new char[strlen(row[0]) + 1]; + memset(charname, 0, strlen(row[0]) + 1); + strcpy(charname, row[0]); + } + + return charname; +} + +int32 WorldDatabase::GetMysqlExpCurve(int level) { + Query query; + MYSQL_ROW row; + //this should never happen but just in case. + if(!level){ + level = 95; + } + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT exp_needed from exp_per_level where level=%u", level); + if (result && mysql_num_rows(result) > 0) { + MYSQL_ROW row = mysql_fetch_row(result); + return atoi(row[0]); + } + //return 1 so we dont break shit later divide by 0 and all that. + return 1; +} \ No newline at end of file diff --git a/source/WorldServer/WorldDatabase.h b/source/WorldServer/WorldDatabase.h new file mode 100644 index 0000000..30e98cb --- /dev/null +++ b/source/WorldServer/WorldDatabase.h @@ -0,0 +1,664 @@ +/* + 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 . +*/ +#ifndef EQ2WORLD_EMU_DATABASE_H +#define EQ2WORLD_EMU_DATABASE_H + +#ifdef WIN32 + #include + #include +#endif +#include +#include +#include +#include +#include + +#include "../common/database.h" +#include "../common/types.h" +#include "../common/MiscFunctions.h" +#include "../common/Mutex.h" +#include "../common/DatabaseNew.h" +#include "client.h" +#include "Object.h" +#include "Widget.h" +#include "Sign.h" +#include "NPC.h" +#include "zoneserver.h" +#include "Collections/Collections.h" +#include "Achievements/Achievements.h" +#include "Recipes/Recipe.h" +#include "../common/PacketStruct.h" +#include "Spells.h" +#include "Titles.h" +#include "Rules/Rules.h" +#include "Languages.h" +#include "World.h" + +using namespace std; + +#define APPEARANCE_SOGA_HFHC 0 +#define APPEARANCE_SOGA_HTHC 1 +#define APPEARANCE_SOGA_HFC 2 +#define APPEARANCE_SOGA_HTC 3 +#define APPEARANCE_SOGA_HH 4 +#define APPEARANCE_SOGA_HC1 5 +#define APPEARANCE_SOGA_HC2 6 +#define APPEARANCE_SOGA_SC 7 +#define APPEARANCE_SOGA_EC 8 +#define APPEARANCE_HTHC 9 +#define APPEARANCE_HFHC 10 +#define APPEARANCE_HTC 11 +#define APPEARANCE_HFC 12 +#define APPEARANCE_HH 13 +#define APPEARANCE_HC1 14 +#define APPEARANCE_HC2 15 +#define APPEARANCE_WC1 16 +#define APPEARANCE_WC2 17 +#define APPEARANCE_SC 18 +#define APPEARANCE_EC 19 +#define APPEARANCE_SHIRT 20 +#define APPEARANCE_UCC 21 +#define APPEARANCE_PANTS 22 +#define APPEARANCE_ULC 23 +#define APPEARANCE_U9 24 +#define APPEARANCE_BODY_SIZE 25 +#define APPEARANCE_SOGA_WC1 26 +#define APPEARANCE_SOGA_WC2 27 +#define APPEARANCE_SOGA_SHIRT 28 +#define APPEARANCE_SOGA_UCC 29 +#define APPEARANCE_SOGA_PANTS 30 +#define APPEARANCE_SOGA_ULC 31 +#define APPEARANCE_SOGA_U13 32 +#define APPEARANCE_SOGA_EBT 33 +#define APPEARANCE_SOGA_CHEEKT 34 +#define APPEARANCE_SOGA_NT 35 +#define APPEARANCE_SOGA_CHINT 36 +#define APPEARANCE_SOGA_LT 37 +#define APPEARANCE_SOGA_EART 38 +#define APPEARANCE_SOGA_EYET 39 +#define APPEARANCE_EBT 40 +#define APPEARANCE_CHEEKT 41 +#define APPEARANCE_NT 42 +#define APPEARANCE_CHINT 43 +#define APPEARANCE_EART 44 +#define APPEARANCE_EYET 45 +#define APPEARANCE_LT 46 +#define APPEARANCE_BODY_AGE 47 +#define APPEARANCE_MC 48 +#define APPEARANCE_SMC 49 +#define APPEARANCE_SBS 50 +#define APPEARANCE_SBA 51 + +#define CHAR_PROPERTY_SPEED "modify_speed" +#define CHAR_PROPERTY_FLYMODE "modify_flymode" +#define CHAR_PROPERTY_INVUL "modify_invul" +#define CHAR_PROPERTY_REGIONDEBUG "modify_regiondebug" +#define CHAR_PROPERTY_GMVISION "modify_gmvision" +#define CHAR_PROPERTY_LUADEBUG "modify_luadebug" + +#define CHAR_PROPERTY_GROUPLOOTMETHOD "group_loot_method" +#define CHAR_PROPERTY_GROUPLOOTITEMRARITY "group_loot_item_rarity" +#define CHAR_PROPERTY_GROUPAUTOSPLIT "group_auto_split" +#define CHAR_PROPERTY_GROUPDEFAULTYELL "group_default_yell" +#define CHAR_PROPERTY_GROUPAUTOLOCK "group_autolock" +#define CHAR_PROPERTY_GROUPLOCKMETHOD "group_lock_method" +#define CHAR_PROPERTY_GROUPSOLOAUTOLOCK "group_solo_autolock" +#define CHAR_PROPERTY_AUTOLOOTMETHOD "group_auto_loot_method" + +#define CHAR_PROPERTY_ASSISTAUTOATTACK "assist_auto_attack" + +#define CHAR_PROPERTY_SETACTIVEFOOD "set_active_food" +#define CHAR_PROPERTY_SETACTIVEDRINK "set_active_drink" + +#define DB_TYPE_SPELLEFFECTS 1 +#define DB_TYPE_MAINTAINEDEFFECTS 2 + +struct StartingItem{ + string type; + int32 item_id; + string creator; + int8 condition; + int8 attuned; + int16 count; +}; + +struct ClaimItems { + int32 char_id; + int32 item_id; + int8 max_claim; + int8 curr_claim; + int8 one_per_char; + int32 vet_reward_time; +}; + +class Bot; + +class WorldDatabase : public Database { +public: + WorldDatabase(); + ~WorldDatabase(); + + bool ConnectNewDatabase(); + + void PingNewDB(); + + string GetZoneName(int32 id); + string GetZoneDescription(int32 id); + int32 LoadCharacterSkills(int32 char_id, Player* player); + void DeleteCharacterSkill(int32 char_id, Skill* skill); + void DeleteCharacterSpell(int32 character_id, int32 spell_id); + int32 LoadCharacterSpells(int32 char_id, Player* player); + int32 LoadItemBlueStats(); + void SaveQuickBar(int32 char_id, vector* quickbar_items); + void SavePlayerSpells(Client* client); + int32 LoadSkills(); + void LoadCommandList(); + map >* LoadCharacterMacros(int32 char_id); + void UpdateCharacterMacro(int32 char_id, int8 number, const char* name, int16 icon, vector* updates); + void SaveWorldTime(WorldTime* time); + + bool SaveSpawnInfo(Spawn* spawn); + int32 GetNextSpawnIDInZone(int32 zone_id); + bool SaveSpawnEntry(Spawn* spawn, const char* spawn_location_name, int8 percent, float x_offset, float y_offset, float z_offset, bool save_zonespawn = true, bool create_spawnlocation = true); + float GetSpawnLocationPlacementOffsetX(int32 location_id); + float GetSpawnLocationPlacementOffsetY(int32 location_id); + float GetSpawnLocationPlacementOffsetZ(int32 location_id); + int32 GetNextSpawnLocation(); + bool CreateNewSpawnLocation(int32 id, const char* name); + bool RemoveSpawnFromSpawnLocation(Spawn* spawn); + int32 GetSpawnLocationCount(int32 location, Spawn* spawn = 0); + vector* GetSpawnNameList(const char* in_name); + void LoadSubCommandList(); + void LoadGlobalVariables(); + void UpdateVitality(int32 timestamp, float amount); + void SaveVariable(const char* name, const char* value, const char* comment); + void LoadVisualStates(); + void LoadAppearanceMasterList(); + void Save(Client* client); + void SaveItems(Client* client); + void SaveItem(int32 account_id, int32 char_id, Item* item, const char* type); + void DeleteBuyBack(int32 char_id, int32 item_id, int16 quantity, int32 price); + void LoadBuyBacks(Client* client); + void SaveBuyBacks(Client* client); + void SaveBuyBack(int32 char_id, int32 item_id, int16 quantity, int32 price); + void DeleteItem(int32 char_id, Item* item, const char* type); + void SaveCharacterColors(int32 char_id, const char* type, EQ2_Color color); + void SaveCharacterFloats(int32 char_id, const char* type, float float1, float float2, float float3, float multiplier = 100.0f); + int16 GetAppearanceID(string name); + vector* GetAppearanceIDsLikeName(string name, bool filtered = true); + string GetAppearanceName(int16 appearance_id); + void UpdateRandomize(int32 spawn_id, sint32 value); + int32 SaveCharacter(PacketStruct* create, int32 loginID); + int32 LoadNPCAppearanceEquipmentData(ZoneServer* zone); + void SaveNPCAppearanceEquipment(int32 spawn_id, int8 slot_id, int16 type, int8 red=0, int8 green=0, int8 blue=0, int8 hred=0, int8 hgreen=0, int8 hblue=0); + void LoadSpecialZones(); + void SaveCharacterSkills(Client* client); + void SaveCharacterQuests(Client* client); + void SaveCharacterQuestProgress(Client* client, Quest* quest); + void DeleteCharacterQuest(int32 quest_id, int32 char_id, bool repeated_quest = false); + void LoadCharacterQuests(Client* client); + void LoadPlayerAA(Player *player); + void LoadCharacterQuestProgress(Client* client); + void LoadCharacterFriendsIgnoreList(Player* player); + void LoadZoneInfo(ZoneServer* zone); + void LoadZoneInfo(ZoneInfo* zone_info); + int32 GetZoneID(const char* name); + void SaveZoneInfo(int32 zone_id, const char* field, sint32 value); + void SaveZoneInfo(int32 zone_id, const char* field, float value); + void SaveZoneInfo(int32 zone_id, const char* field, const char* value); + bool GetZoneRequirements(const char* zoneName,sint16* minStatus, int16* minLevel, int16* maxLevel, int16* minVersion); + int16 GetMinimumClientVersion(int8 expansion_id); + string GetExpansionIDByVersion(int16 version); + int32 CheckTableVersions(char* tablename); + bool RunDatabaseQueries(TableQuery* queries, bool output_result = true, bool data = false); + void UpdateTableVersion(char* name, int32 version); + void UpdateDataTableVersion(char* name, int32 version); + void UpdateStartingFactions(int32 char_id, int8 choice); + string GetStartingZoneName(int8 choice); + void UpdateStartingZone(int32 char_id, int8 class_id, int8 race_id, PacketStruct* create); + void UpdateStartingItems(int32 char_id, int8 class_id, int8 race_id, bool base_class = false); + void UpdateStartingSkills(int32 char_id, int8 class_id, int8 race_id); + void UpdateStartingSpells(int32 char_id, int8 class_id, int8 race_id); + void UpdateStartingSkillbar(int32 char_id, int8 class_id, int8 race_id); + void UpdateStartingTitles(int32 char_id, int8 class_id, int8 race_id, int8 gender_id); + bool UpdateSpawnLocationSpawns(Spawn* spawn); + bool UpdateSpawnWidget(int32 widget_id, char* query); + bool CheckVersionTable(); + void LoadFactionAlliances(); + void LoadFactionList(); + bool LoadPlayerFactions(Client* client); + void SavePlayerFactions(Client* client); + bool VerifyFactionID(int32 char_id, int32 faction_id); + void LoadSpawnScriptData(); + void LoadZoneScriptData(); + int32 LoadSpellScriptData(); + bool UpdateSpawnScriptData(int32 spawn_id, int32 spawn_location_id, int32 spawnentry_id, const char* name); + map* GetZoneList(const char* name, bool is_admin = false); + bool VerifyZone(const char* name); + int8 GetInstanceTypeByZoneID(int32 zoneID); + /*void loadNPCAppearance(int32 appearance_id); + void LoadNPCAppearances();*/ + void ResetDatabase(); + void EnableConstraints(); + void DisableConstraints(); + int32 SaveCombinedSpawnLocation(ZoneServer* zone, Spawn* spawn, const char* name); + int32 ProcessSpawnLocations(ZoneServer* zone, const char* sql_query, int8 type); + int32 LoadSpawnLocationGroupAssociations(ZoneServer* zone); + int32 LoadSpawnLocationGroups(ZoneServer* zone); + int32 LoadSpawnGroupChances(ZoneServer* zone); + bool SpawnGroupAddAssociation(int32 group1, int32 group2); + bool SpawnGroupRemoveAssociation(int32 group1, int32 group2); + bool SpawnGroupAddSpawn(Spawn* spawn, int32 group_id); + bool SpawnGroupRemoveSpawn(Spawn* spawn, int32 group_id); + int32 CreateSpawnGroup(Spawn* spawn, string name); + void DeleteSpawnGroup(int32 id); + bool SetGroupSpawnChance(int32 id, float chance); + void LoadGroundSpawnEntries(ZoneServer* zone); + void LoadGroundSpawnItems(ZoneServer* zone); + void LoadSpawns(ZoneServer* zone); + int8 GetAppearanceType(string type); + void LoadNPCs(ZoneServer* zone); + void LoadSpiritShards(ZoneServer* zone); + int32 LoadAppearances(ZoneServer* zone, Client* client = 0); + int32 LoadNPCSpells(); + int32 LoadNPCSkills(ZoneServer* zone); + int32 LoadNPCEquipment(ZoneServer* zone); + void LoadObjects(ZoneServer* zone); + void LoadGroundSpawns(ZoneServer* zone); + void LoadWidgets(ZoneServer* zone); + void LoadSigns(ZoneServer* zone); + void ReloadItemList(int32 item_id = 0); + void LoadItemList(int32 item_id = 0); + int32 LoadItemStats(int32 item_id = 0); + int32 LoadItemModStrings(int32 item_id = 0); + int32 LoadItemAppearances(int32 item_id = 0); + int32 LoadItemLevelOverride(int32 item_id = 0); + int32 LoadItemEffects(int32 item_id = 0); + int32 LoadBookPages(int32 item_id = 0); + int32 LoadNextUniqueItemID(); + int32 LoadSkillItems(int32 item_id = 0); + int32 LoadRangeWeapons(int32 item_id = 0); + int32 LoadThrownWeapons(int32 item_id = 0); + int32 LoadBaubles(int32 item_id = 0); + int32 LoadBooks(int32 item_id = 0); + int32 LoadItemsets(int32 item_id = 0); + int32 LoadHouseItem(int32 item_id = 0); + int32 LoadRecipeBookItems(int32 item_id = 0); + int32 LoadArmor(int32 item_id = 0); + int32 LoadAdornments(int32 item_id = 0); + int32 LoadClassifications(); + int32 LoadShields(int32 item_id = 0); + int32 LoadBags(int32 item_id = 0); + int32 LoadFoods(int32 item_id = 0); + int32 LoadWeapons(int32 item_id = 0); + int32 LoadRanged(); + int32 LoadHouseContainers(int32 item_id = 0); + void LoadBrokerItemStats(); + + void SaveSignMark(int32 char_id, int32 sign_id, char* char_name, Client* client); + string GetSignMark(int32 char_id, int32 sign_id, char* char_name); // returns the string containing the character name + + + map >* LoadSpellClasses(); + void LoadTransporters(ZoneServer* zone); + void LoadTransportMaps(ZoneServer* zone); + void LoadDataFromRow(DatabaseResult *result, Item* item); + void LoadCharacterItemList(int32 account_id, int32 char_id, Player* player, int16); + bool loadCharacter(const char* name, int32 account_id, Client* client); + bool LoadCharacterStats(int32 id, int32 account_id, Client* client); + void LoadCharacterQuestRewards(Client* client); + void LoadCharacterQuestTemporaryRewards(Client* client, int32 quest_id); + bool InsertCharacterStats(int32 character_id, int8 class_id, int8 race_id); + bool UpdateCharacterTimeStamp(int32 account_id, int32 character_id, int32 timestamp); + bool insertCharacterProperty(Client* client, char* propName, char* propValue); + bool loadCharacterProperties(Client* client); + string GetPlayerName(char* name); + int32 GetCharacterTimeStamp(int32 character_id, int32 account_id,bool* char_exists); + int32 GetCharacterTimeStamp(int32 character_id); + sint32 GetLatestDataTableVersion(char* name); + sint16 GetLowestCharacterAdminStatus(int32 account_id); + sint16 GetHighestCharacterAdminStatus(int32 account_id); + sint16 GetCharacterAdminStatus(char* character_name); + sint16 GetCharacterAdminStatus(int32 account_id , int32 char_id); + bool UpdateAdminStatus(char* character_name, sint16 flag); + void LoadMerchantInformation(); + void LoadMerchantInventory(); + string GetMerchantDescription(int32 merchant_id); + void LoadPlayerStatistics(Player* player, int32 char_id); + void WritePlayerStatistic(Player* player, Statistic* stat); + void LoadServerStatistics(); + void WriteServerStatistic(Statistic* stat); + void WriteServerStatistic(int32 stat_id, sint32 stat_value); + void WriteServerStatisticsNeededQueries(); + void SavePlayerMail(Mail* mail); + void SavePlayerMail(Client* client); + void LoadPlayerMail(Client* client, bool new_only = false); + void DeletePlayerMail(Mail* mail); + vector* GetAllPlayerIDs(); + void GetPetNames(ZoneServer* zone); + //void LoadMerchantMultipliers(); + + char* GetCharacterName(int32 character_id); + int8 GetCharacterLevel(int32 character_id); + int16 GetCharacterModelType(int32 character_id); + int8 GetCharacterClass(int32 character_id); + int8 GetCharacterGender(int32 character_id); + int32 GetCharacterID(const char* name); + int32 GetCharacterCurrentZoneID(int32 character_id); + int32 GetCharacterAccountID(int32 character_id); + void LoadEntityCommands(ZoneServer* zone); + void LoadSpells(); + void LoadSpellEffects(); + void LoadSpellLuaData(); + void LoadTraits(); + int32 LoadPlayerSkillbar(Client* client); + string GetColumnNames(char* name); + + string GetZoneName(char* zone_description); + bool GetItemResultsToClient (Client* client, const char* varSearch, int maxResults=20); + void LoadRevivePoints(vector* revive_points, int32 zone_id); + void SaveBugReport(const char* category, const char* subcategory, const char* causes_crash, const char* reproducible, const char* summary, const char* description, const char* version, const char* player, int32 account_id, const char* spawn_name, int32 spawn_id, int32 zone_id); + void FixBugReport(); + + int32 LoadQuests(); + void LoadQuestDetails(Quest* quest); + + bool DeleteCharacter(int32 account_id, int32 character_id); + + int32 GetMaxHotBarID(); + + int8 CheckNameFilter(const char* name, int8 min_length = 4, int8 max_length = 15); + static int32 NextUniqueHotbarID(){ + next_id++; + return next_id; + } + void LoadFogInit(string zone, PacketStruct* packet); + static int32 next_id; + + void ToggleCharacterOnline(); + void ToggleCharacterOnline(Client* client, int8 toggle); + + // Zone Instance DB Functions + map* GetInstanceRemovedSpawns(int32 instance_id, int8 type); + int32 CreateNewInstance(int32 zone_id); + //int32 AddCharacterInstance(int32 char_id, int32 instance_id, int32 grant_reenter_time_left=0, int32 grant_reset_time_left=0, int32 lockout_time=0); + int32 AddCharacterInstance(int32 char_id, int32 instance_id, string zone_name, int8 instance_type, int32 last_success, int32 last_failure, int32 success_lockout, int32 failure_lockout); + bool UpdateCharacterInstanceTimers(int32 char_id, int32 instance_id, int32 lockout_time=0, int32 reset_time=0, int32 reenter_time=0 ); + bool UpdateCharacterInstance(int32 char_id, string zone_name, int32 instance_id, int8 type = 0, int32 timestamp = 0); + bool VerifyInstanceID(int32 char_id, int32 instance_id); + bool CheckVectorForValue(vector* vector, int32 value); + int32 CheckSpawnRemoveInfo(map* inmap, int32 spawn_location_entry_id); + bool UpdateInstancedSpawnRemoved(int32 spawn_location_entry_id, int32 spawn_type, int32 respawn_time, int32 instance_id ); + int32 CreateInstanceSpawnRemoved(int32 spawn_location_entry_id, int32 spawn_type, int32 respawn_time, int32 instance_id ); + bool DeleteInstance(int32 instance_id); + bool DeleteInstanceSpawnRemoved(int32 instance_id, int32 spawn_location_entry_id); + bool DeleteCharacterFromInstance(int32 char_id, int32 instance_id); + bool LoadCharacterInstances(Client* client); + // + + MutexMap* GetEquipmentUpdates(); + MutexMap* GetEquipmentUpdates(int32 char_id); + void UpdateLoginEquipment(); + MutexMap* GetZoneUpdates(); + void UpdateLoginZones(); + void LoadLocationGrids(ZoneServer* zone); + bool LoadLocationGridLocations(LocationGrid* grid); + int32 CreateLocation(int32 zone_id, int32 grid_id, const char* name, bool include_y); + bool AddLocationPoint(int32 location_id, float x, float y, float z); + bool DeleteLocation(int32 location_id); + bool DeleteLocationPoint(int32 location_point_id); + void ListLocations(Client* client); + void ListLocationPoints(Client* client, int32 location_id); + bool LocationExists(int32 location_id); + + + bool GetTableVersions(vector *table_versions); + bool QueriesFromFile(const char *file); + + + /* Achievements */ + void LoadAchievements(); + int32 LoadAchievementRequirements(Achievement *achievement); + int32 LoadAchievementRewards(Achievement *achievement); + void LoadPlayerAchievements(Player *player); + int32 LoadPlayerAchievementsUpdates(Player *player); + int32 LoadPlayerAchievementsUpdateItems(AchievementUpdate *update, int32 player_id); + + /* Alternate Advancement */ + void LoadAltAdvancements(); + void LoadTreeNodes(); + + /* Collections */ + void LoadCollections(); + int32 LoadCollectionItems(Collection *collection); + int32 LoadCollectionRewards(Collection *collection); + void LoadPlayerCollections(Player *player); + void LoadPlayerCollectionItems(Player *player, Collection *collection); + void SavePlayerCollections(Client *client); + void SavePlayerCollection(Client *client, Collection *collection); + void SavePlayerCollectionItems(Client *client, Collection *collection); + void SavePlayerCollectionItem(Client *client, Collection *collection, int32 item_id); + + /* Commands */ + map* GetSpawnTemplateListByName(const char* name); + map* GetSpawnTemplateListByID(int32 location_id); + int32 SaveSpawnTemplate(int32 placement_id, const char* template_name); + bool RemoveSpawnTemplate(int32 template_id); + int32 CreateSpawnFromTemplateByID(Client* client, int32 template_id); + int32 CreateSpawnFromTemplateByName(Client* client, const char* template_name); + bool SaveZoneSafeCoords(int32 zone_id, float x, float y, float z, float heading); + bool SaveSignZoneToCoords(int32 spawn_id, float x, float y, float z, float heading); + + /* Guilds */ + void LoadGuilds(); + int32 LoadGuildMembers(Guild* guild); + void LoadGuildEvents(Guild* guild); + void LoadGuildRanks(Guild* guild); + void LoadGuildEventFilters(Guild* guild); + void LoadGuildPointsHistory(Guild* guild, GuildMember* guild_member); + void LoadGuildRecruiting(Guild* guild); + void SaveGuild(Guild* guild, bool new_guild = false); + void SaveGuildMembers(Guild* guild); + void SaveGuildEvents(Guild* guild); + void SaveGuildRanks(Guild* guild); + void SaveGuildEventFilters(Guild* guild); + void SaveGuildPointsHistory(Guild* guild); + void SaveGuildRecruiting(Guild* guild); + void DeleteGuild(Guild* guild); + void DeleteGuildMember(Guild* guild, int32 character_id); + void DeleteGuildEvent(Guild* guild, int64 event_id); + void DeleteGuildPointHistory(Guild* guild, int32 character_id, PointHistory* point_history); + void ArchiveGuildEvent(Guild* guild, GuildEvent* guild_event); + void SaveHiddenGuildEvent(Guild* guild, GuildEvent* guild_event); + void LoadGuildDefaultRanks(Guild* guild); + void LoadGuildDefaultEventFilters(Guild* guild); + bool AddNewPlayerToServerGuild(int32 account_id, int32 char_id); + int32 GetGuildIDByCharacterID(int32 char_id); + + /* Chat */ + void LoadChannels(); + + /* Recipes */ + void LoadRecipes(); + void LoadRecipeBooks(); + void LoadPlayerRecipes(Player *player); + int32 LoadPlayerRecipeBooks(int32 char_id, Player *player); + void SavePlayerRecipeBook(Player* player, int32 recipebook_id); + void LoadRecipeComponents(); + void UpdatePlayerRecipe(Player* player, int32 recipe_id, int8 highest_rank); + void SavePlayerRecipe(Player* player, int32 recipe_id); + + /* Tradeskills */ + void LoadTradeskillEvents(); + + /* Rules */ + void LoadGlobalRuleSet(); + void LoadRuleSets(bool reload=false); + void LoadRuleSetDetails(RuleSet *rule_set); + + /* Titles */ + sint32 AddMasterTitle(const char* titleName, int8 isPrefix = 0); + void LoadTitles(); + sint32 LoadCharacterTitles(int32 char_id, Player *player); + sint32 GetCharPrefixIndex(int32 char_id, Player *player); + sint32 GetCharSuffixIndex(int32 char_id, Player *player); + void SaveCharPrefixIndex(sint32 index, int32 char_id); + void SaveCharSuffixIndex(sint32 index, int32 char_id); + sint32 AddCharacterTitle(sint32 index, int32 char_id, Spawn* player); + + /* Languages */ + void LoadLanguages(); + int32 LoadCharacterLanguages(int32 char_id, Player *player); + int16 GetCharacterCurrentLang(int32 char_id, Player *player); + void SaveCharacterCurrentLang(int32 id, int32 char_id, Client *client); + void UpdateStartingLanguage(int32 char_id, uint8 race_id, int32 starting_city=0); + + /// Saves the given language for the given player + /// Character ID to save the language to + /// Language ID to save to the character + void SaveCharacterLang(int32 char_id, int32 lang_id); + + /* Tradeskills */ + + /* Character History */ + void SaveCharacterHistory(Player* player, int8 type, int8 subtype, int32 value, int32 value2, char* location, int32 event_date); + + /* Housing */ + void LoadHouseZones(); + int64 AddPlayerHouse(int32 char_id, int32 house_id, int32 instance_id, int32 upkeep_due); + void SetHouseUpkeepDue(int32 char_id, int32 house_id, int32 instance_id, int32 upkeep_due); + void RemovePlayerHouse(int32 char_id, int32 house_id); + void UpdateHouseEscrow(int32 house_id, int32 instance_id, int64 amount_coins, int32 amount_status); + void LoadPlayerHouses(); + void LoadDeposits(PlayerHouse* house); + void LoadHistory(PlayerHouse* house); + void AddHistory(PlayerHouse* house, char* name, char* reason, int32 timestamp, int64 amount = 0, int32 status = 0, int8 pos_flag = 0); + + /* World */ + bool CheckBannedIPs(const char* loginIP); + + /* Heroic OP */ + void LoadHOStarters(); + void LoadHOWheel(); + + /* Claim Items */ + void LoadClaimItems(int32 char_id); + int16 CountCharClaimItems(int32 char_id); + vector LoadCharacterClaimItems(int32 char_id); + void ClaimItem(int32 char_id, int32 item_id, Client* client); + int32 GetAccountAge(int32 account_id); + + /* Race Types */ + void LoadRaceTypes(); + + /* Loot */ + void LoadLoot(ZoneServer* zone); + void LoadGlobalLoot(ZoneServer* zone); + bool LoadSpawnLoot(ZoneServer* zone, Spawn* spawn); + void AddLootTableToSpawn(Spawn* spawn, int32 loottable_id); + bool RemoveSpawnLootTable(Spawn* spawn, int32 loottable_id); + + void LoadCharacterHistory(int32 char_id, Player *player); + void LoadSpellErrors(); + + /* Load single spawns */ + bool LoadSign(ZoneServer* zone, int32 spawn_id); + bool LoadWidget(ZoneServer* zone, int32 spawn_id); + bool LoadObject(ZoneServer* zone, int32 spawn_id); + bool LoadGroundSpawn(ZoneServer* zone, int32 spawn_id); + void LoadGroundSpawnEntry(ZoneServer* zone, int32 entry_id); + void LoadGroundSpawnItems(ZoneServer* zone, int32 entry_id); + bool LoadNPC(ZoneServer* zone, int32 spawn_id); + void LoadAppearance(ZoneServer* zone, int32 spawn_id); + void LoadNPCAppearanceEquipmentData(ZoneServer* zone, int32 spawn_id); + + /* Save character Pictures */ + /// Saves the pictures that clients send to the server + /// The ID of the character + /// The type of image this is, 0 = paperdoll, 1 = headshot + /// The raw png data + void SaveCharacterPicture(int32 characterID, int8 type, uchar* picture, int32 picture_size); + + /* Quests */ + /// Updates the given date for the quest in the DB for a repeatable quest + /// Client to save the quest for + /// ID of the quest to save + /// Number of times the quest has already been completed + void SaveCharRepeatableQuest(Client* client, int32 quest_id, int16 quest_complete_count); + + + /* Zone Flight Paths */ + void LoadZoneFlightPaths(ZoneServer* zone); + void LoadZoneFlightPathLocations(ZoneServer* zone); + + /* Character LUA History */ + void SaveCharacterLUAHistory(Player* player, int32 event_id, int32 value, int32 value2); + void LoadCharacterLUAHistory(int32 char_id, Player* player); + + /* Bots - BotDB.cpp */ + int32 CreateNewBot(int32 char_id, string name, int8 race, int8 advClass, int8 gender, int16 model_id, int32& index); + void SaveBotAppearance(Bot* bot); + void SaveBotColors(int32 bot_id, const char* type, EQ2_Color color); + void SaveBotFloats(int32 bot_id, const char* type, float float1, float float2, float float3); + bool LoadBot(int32 char_id, int32 bot_index, Bot* bot); + void LoadBotAppearance(Bot* bot); + void SaveBotItem(int32 bot_id, int32 item_id, int8 slot); + void LoadBotEquipment(Bot* bot); + string GetBotList(int32 char_id); + void DeleteBot(int32 char_id, int32 bot_index); + void SetBotStartingItems(Bot* bot, int8 class_id, int8 race_id); + void LoadTransmuting(); + + void FindSpell(Client* client, char* findString); + + void LoadChestTraps(); + + bool CheckExpansionFlags(ZoneServer* zone, int32 spawnXpackFlag); + bool CheckHolidayFlags(ZoneServer* zone, int32 spawnHolidayFlag); + void GetHouseSpawnInstanceData(ZoneServer* zone, Spawn* spawn); + int32 FindHouseInstanceSpawn(Spawn* spawn); + + /* Starting Character Abilities */ + + void LoadStartingSkills(World* world); + void LoadStartingSpells(World* world); + void LoadVoiceOvers(World* world); + + int32 CreateSpiritShard(const char* name, int32 level, int8 race, int8 gender, int8 adventure_class, + int16 model_type, int16 soga_model_type, int16 hair_type, int16 hair_face_type, int16 wing_type, + int16 chest_type, int16 legs_type, int16 soga_hair_type, int16 soga_hair_face_type, int8 hide_hood, + int16 size, int16 collision_radius, int16 action_state, int16 visual_state, int16 mood_state, int16 emote_state, + int16 pos_state, int16 activity_status, char* sub_title, char* prefix_title, char* suffix_title, char* lastname, + float x, float y, float z, float heading, int32 gridid, int32 charid, int32 zoneid, int32 instanceid); + bool DeleteSpiritShard(int32 id); + + void LoadCharacterSpellEffects(int32 char_id, Client *client, int8 db_spell_type); + + int32 GetMysqlExpCurve(int level); +private: + DatabaseNew database_new; + std::map zone_names; + string skills; + int32 max_zonename; + char** zonename_array; + std::map zone_instance_types; +}; +#endif + diff --git a/source/WorldServer/WorldTCPConnection.h b/source/WorldServer/WorldTCPConnection.h new file mode 100644 index 0000000..545cd9b --- /dev/null +++ b/source/WorldServer/WorldTCPConnection.h @@ -0,0 +1,33 @@ +/* + 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 . +*/ +#ifndef WorldTCPCONNECTION_H +#define WorldTCPCONNECTION_H +class WorldTCPConnection +{ +public: + WorldTCPConnection() { } + virtual ~WorldTCPConnection() { } + virtual void SendEmoteMessage(const char* to, int32 to_guilddbid, sint16 to_minstatus, int32 type, const char* message, ...) { } + virtual void SendEmoteMessageRaw(const char* to, int32 to_guilddbid, sint16 to_minstatus, int32 type, const char* message) { } + + virtual inline bool IsConsole() { return false; } +}; + +#endif diff --git a/source/WorldServer/Zone/ChestTrap.cpp b/source/WorldServer/Zone/ChestTrap.cpp new file mode 100644 index 0000000..260d3b9 --- /dev/null +++ b/source/WorldServer/Zone/ChestTrap.cpp @@ -0,0 +1,285 @@ +#include "ChestTrap.h" +#include +#include + +//required for c++17 compat (random_shuffle removed, replaced with shuffle) +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) +#include +#else +#include // std::random_shuffle +#endif + +int32 ChestTrapList::Size() { + + MChestTrapList.readlock(__FUNCTION__, __LINE__); + int32 size = chesttrap_list.size(); + MChestTrapList.releasereadlock(__FUNCTION__, __LINE__); + return size; +} + +void ChestTrapList::AddChestTrap(ChestTrap* trap) { + if (trap->GetDBID() < 1) + return; + + MChestTrapList.writelock(__FUNCTION__, __LINE__); + if (chesttrap_list.count(trap->GetDBID()) > 0) + { + ChestTrap* tmpTrap = chesttrap_list[trap->GetDBID()]; + chesttrap_list.erase(trap->GetDBID()); + safe_delete(tmpTrap); + } + + chesttrap_list[trap->GetDBID()] = trap; + MChestTrapList.releasewritelock(__FUNCTION__, __LINE__); +} + +bool ChestTrapList::GetChestTrap(int32 id, ChestTrap::ChestTrapInfo* cti) { + ChestTrap* res = 0; + MChestTrapList.readlock(__FUNCTION__, __LINE__); + if (chesttrap_list.count(id) > 0) + res = chesttrap_list[id]; + + memset(cti, 0, sizeof(ChestTrap::ChestTrapInfo)); + if (res) + memcpy(cti, res->GetChestTrapInfo(), sizeof(ChestTrap::ChestTrapInfo)); + MChestTrapList.releasereadlock(__FUNCTION__, __LINE__); + + return cti; +} + +bool ChestTrapList::GetNextTrap(int32 zoneid, int32 chest_difficulty, ChestTrap::ChestTrapInfo* cti) +{ + MChestListsInUse.writelock(__FUNCTION__, __LINE__); + ChestTrapList* zoneTrapList = GetChestListByZone(zoneid); + ChestTrapList* zoneDifficultyTrapList = zoneTrapList->GetChestListByDifficulty(chest_difficulty); + + bool ret = zoneTrapList->GetNextChestTrap(cti); + MChestListsInUse.releasewritelock(__FUNCTION__, __LINE__); + + return ret; +} + +void ChestTrapList::Clear() { + MChestListsInUse.writelock(__FUNCTION__, __LINE__); + ClearTraps(); + ClearTrapList(); + MChestListsInUse.releasewritelock(__FUNCTION__, __LINE__); +} + +bool ChestTrapList::GetNextChestTrap(ChestTrap::ChestTrapInfo* cti) { + MChestTrapList.readlock(__FUNCTION__, __LINE__); + if (cycleItr == chesttrap_list.end()) + { + MChestTrapList.releasereadlock(__FUNCTION__, __LINE__); + //re-shuffle the map, we reached the end + shuffleMap(this); + } + else + MChestTrapList.releasereadlock(__FUNCTION__, __LINE__); + + if (cycleItr == chesttrap_list.end()) + return false; + + MChestTrapList.writelock(__FUNCTION__, __LINE__); + ChestTrap* trap = cycleItr->second; + + memset(cti, 0, sizeof(ChestTrap::ChestTrapInfo)); + if (trap) + memcpy(cti, trap->GetChestTrapInfo(), sizeof(ChestTrap::ChestTrapInfo)); + + cycleItr++; + MChestTrapList.releasewritelock(__FUNCTION__, __LINE__); + + return true; +} + +ChestTrapList* ChestTrapList::GetChestListByDifficulty(int32 difficulty) { + ChestTrapList* usedList = 0; + + int32 id = 0; + if (ChestTrapParent) + { + usedList = GetChestTrapList(ChestTrapBaseList::DIFFICULTY); + id = ChestTrapBaseList::DIFFICULTY; + } + else + { + usedList = GetChestTrapListByID(difficulty); + id = difficulty; + } + + if (usedList && usedList->IsListLoaded()) + return usedList; + else if (!usedList) + { + usedList = new ChestTrapList(); + AddChestTrapList(usedList, id); + } + + MChestTrapList.writelock(__FUNCTION__, __LINE__); + map::iterator itr; + for (itr = chesttrap_list.begin(); itr != chesttrap_list.end(); itr++) { + ChestTrap* curTrap = itr->second; + if ((curTrap->GetMinChestDifficulty() <= difficulty && difficulty <= curTrap->GetMaxChestDifficulty()) || + (curTrap->GetMinChestDifficulty() == 0 && curTrap->GetMaxChestDifficulty() == 0)) + usedList->AddChestTrap(curTrap); + } + + shuffleMap(usedList); + usedList->SetListLoaded(true); + + MChestTrapList.releasewritelock(__FUNCTION__, __LINE__); + + return usedList; +} + +ChestTrapList* ChestTrapList::GetChestListByZone(int32 zoneid) { + ChestTrapList* usedList = 0; + + int32 id = 0; + if (ChestTrapParent) + { + usedList = GetChestTrapList(ChestTrapBaseList::ZONE); + id = ChestTrapBaseList::ZONE; + } + else + { + usedList = GetChestTrapListByID(zoneid); + id = zoneid; + } + + if (usedList && usedList->IsListLoaded()) + return usedList; + else if (!usedList) + { + usedList = new ChestTrapList(); + AddChestTrapList(usedList, id); + } + + MChestTrapList.writelock(__FUNCTION__, __LINE__); + map::iterator itr; + for (itr = chesttrap_list.begin(); itr != chesttrap_list.end(); itr++) { + ChestTrap* curTrap = itr->second; + if (curTrap->GetApplicableZoneID() == zoneid || curTrap->GetApplicableZoneID() == -1) + usedList->AddChestTrap(curTrap); + } + + shuffleMap(usedList); + usedList->SetListLoaded(true); + + MChestTrapList.releasewritelock(__FUNCTION__, __LINE__); + + return usedList; +} + +map* ChestTrapList::GetAllChestTraps() { return &chesttrap_list; } +bool ChestTrapList::IsListLoaded() { return ListLoaded; } +void ChestTrapList::SetListLoaded(bool val) { ListLoaded = val; } + +void ChestTrapList::AddChestTrapList(ChestTrapList* traplist, int32 id) { + if (chesttrap_innerlist.count(id) > 0) + { + ChestTrapList* tmpTrapList = chesttrap_innerlist[id]; + chesttrap_innerlist.erase(id); + safe_delete(tmpTrapList); + } + + chesttrap_innerlist[id] = traplist; +} + + +ChestTrapList* ChestTrapList::GetChestTrapList(ChestTrapBaseList listName) { + ChestTrapList* ctl = 0; + MChestTrapInnerList.readlock(__FUNCTION__, __LINE__); + if (chesttrap_innerlist.count(listName) > 0) + ctl = chesttrap_innerlist[listName]; + MChestTrapInnerList.releasereadlock(__FUNCTION__, __LINE__); + + return ctl; +} + +ChestTrapList* ChestTrapList::GetChestTrapListByID(int32 id) { + ChestTrapList* ctl = 0; + MChestTrapInnerList.readlock(__FUNCTION__, __LINE__); + if (chesttrap_innerlist.count(id) > 0) + ctl = chesttrap_innerlist[id]; + MChestTrapInnerList.releasereadlock(__FUNCTION__, __LINE__); + + return ctl; +} + +void ChestTrapList::ClearTraps() { + MChestTrapList.writelock(__FUNCTION__, __LINE__); + map::iterator itr; + for (itr = chesttrap_list.begin(); itr != chesttrap_list.end(); itr++) + safe_delete(itr->second); + chesttrap_list.clear(); + MChestTrapList.releasewritelock(__FUNCTION__, __LINE__); +} + +void ChestTrapList::ClearTrapList() { + MChestTrapInnerList.writelock(__FUNCTION__, __LINE__); + map::iterator itr2; + for (itr2 = chesttrap_innerlist.begin(); itr2 != chesttrap_innerlist.end(); itr2++) + safe_delete(itr2->second); + chesttrap_innerlist.clear(); + MChestTrapInnerList.releasewritelock(__FUNCTION__, __LINE__); + + // reinstantiate the base lists (zone/difficulty/etc) + InstantiateLists(ChestTrapParent); +} + +void ChestTrapList::SetupMutexes() +{ + MChestTrapList.SetName("ChestTrapList"); + MChestTrapInnerList.SetName("MChestTrapInnerList"); + MChestListsInUse.SetName("MChestListsInUse"); +} + +void ChestTrapList::InstantiateLists(bool parent) +{ + if (parent) + { + difficultyList = new ChestTrapList(false); + zoneList = new ChestTrapList(false); + MChestTrapInnerList.writelock(__FUNCTION__, __LINE__); + chesttrap_innerlist[ChestTrapBaseList::DIFFICULTY] = difficultyList; + chesttrap_innerlist[ChestTrapBaseList::ZONE] = zoneList; + MChestTrapInnerList.releasewritelock(__FUNCTION__, __LINE__); + } +} + +void ChestTrapList::shuffleMap(ChestTrapList* list) { + std::vector tmp_chests; + + map::iterator itr; + for (itr = chesttrap_list.begin(); itr != chesttrap_list.end(); itr++) { + ChestTrap* curTrap = itr->second; + tmp_chests.push_back(curTrap); + } + +#ifdef WIN32 + //c++17/windows removed random_shuffle replaced with this ugly bullshit 9/22/22 + //taken right from their example. + std::random_device rd; + std::mt19937 g(rd()); + std::shuffle(tmp_chests.begin(), tmp_chests.end(),g); +#else + //let linux continue on, with the function as is since it still works. + std::random_shuffle(tmp_chests.begin(), tmp_chests.end()); +#endif + + chesttrap_list.clear(); + + + + int count = 0; + + for (std::vector::iterator it = tmp_chests.begin(); it != tmp_chests.end(); ++it) + { + chesttrap_list[count] = *it; + count++; + } + + cycleItr = chesttrap_list.begin(); +} diff --git a/source/WorldServer/Zone/ChestTrap.h b/source/WorldServer/Zone/ChestTrap.h new file mode 100644 index 0000000..9d4c9d3 --- /dev/null +++ b/source/WorldServer/Zone/ChestTrap.h @@ -0,0 +1,134 @@ +#include +#include +#include +#include "../../common/Mutex.h" +#include "../../common/types.h" +#pragma once + +using namespace std; + +enum ChestTrapBaseList { + DIFFICULTY = 0, + ZONE = 1 +}; + +class ChestTrap { +public: + struct ChestTrapInfo { + int32 id; + int32 applicable_zone_id; + int32 min_chest_difficulty; + int32 max_chest_difficulty; + int32 spell_id; + int32 spell_tier; + }; + + //Constructors **must** always set all ChestTrapInfo as we don't memset so a data value will be wack if not set! + ChestTrap(int32 dbid, sint32 zoneid, int32 mindifficulty, int32 maxdifficulty, int32 spellid, int32 tier) + { + s_ChestTrapInfo.id = dbid; + s_ChestTrapInfo.applicable_zone_id = zoneid; + s_ChestTrapInfo.min_chest_difficulty = mindifficulty; + s_ChestTrapInfo.max_chest_difficulty = maxdifficulty; + s_ChestTrapInfo.spell_id = spellid; + s_ChestTrapInfo.spell_tier = tier; + } + + ChestTrap(ChestTrap* parent) + { + s_ChestTrapInfo.id = parent->GetDBID(); + s_ChestTrapInfo.applicable_zone_id = parent->GetApplicableZoneID(); + s_ChestTrapInfo.min_chest_difficulty = parent->GetMinChestDifficulty(); + s_ChestTrapInfo.max_chest_difficulty = parent->GetMaxChestDifficulty(); + s_ChestTrapInfo.spell_id = parent->GetSpellID(); + s_ChestTrapInfo.spell_tier = parent->GetSpellTier(); + } + + int32 GetDBID() { return s_ChestTrapInfo.id; } + sint32 GetApplicableZoneID() { return s_ChestTrapInfo.applicable_zone_id; } + int32 GetMinChestDifficulty() { return s_ChestTrapInfo.min_chest_difficulty; } + int32 GetMaxChestDifficulty() { return s_ChestTrapInfo.max_chest_difficulty; } + int32 GetSpellID() { return s_ChestTrapInfo.spell_id; } + int32 GetSpellTier() { return s_ChestTrapInfo.spell_tier; } + + ChestTrapInfo* GetChestTrapInfo() { return &s_ChestTrapInfo; } +private: + ChestTrapInfo s_ChestTrapInfo; +}; + +class ChestTrapList { +public: + ChestTrapList() { + SetupMutexes(); + + ChestTrapParent = true; + // instantiate the parent lists for zone/difficulty/etc, later on we will do the inverse of each map, zone->difficulty and difficulty->zone + InstantiateLists(true); + ListLoaded = true; + } + + // not to be called externally from ChestTrapList/ChestTrap + ChestTrapList(bool parentClass) { + SetupMutexes(); + + ChestTrapParent = parentClass; + + ListLoaded = false; + } + + ~ChestTrapList() { + Clear(); + } + + int32 Size(); + + void AddChestTrap(ChestTrap* trap); + + bool GetChestTrap(int32 id, ChestTrap::ChestTrapInfo* cti); + + bool GetNextTrap(int32 zoneid, int32 chest_difficulty, ChestTrap::ChestTrapInfo* cti); + + void Clear(); +private: + // randomized maps so we just iterate the map for our next 'random' result + bool GetNextChestTrap(ChestTrap::ChestTrapInfo* cti); + + ChestTrapList* GetChestListByDifficulty(int32 difficulty); + + ChestTrapList* GetChestListByZone(int32 zoneid); + + map* GetAllChestTraps(); + bool IsListLoaded(); + void SetListLoaded(bool val); + + void AddChestTrapList(ChestTrapList* trap, int32 id); + + void SetCycleIterator(map::iterator itr); + + ChestTrapList* GetChestTrapList(ChestTrapBaseList listName); + ChestTrapList* GetChestTrapListByID(int32 id); + + void ClearTraps(); + void ClearTrapList(); + + void SetupMutexes(); + + void InstantiateLists(bool parent); + + void shuffleMap(ChestTrapList* list); + + bool ChestTrapParent; + bool ListLoaded; + map chesttrap_list; + map chesttrap_innerlist; + + ChestTrapList* difficultyList; + ChestTrapList* zoneList; + + map::iterator cycleItr; + + Mutex MChestTrapList; + Mutex MChestTrapInnerList; + + Mutex MChestListsInUse; +}; diff --git a/source/WorldServer/Zone/map.cpp b/source/WorldServer/Zone/map.cpp new file mode 100644 index 0000000..cd9be04 --- /dev/null +++ b/source/WorldServer/Zone/map.cpp @@ -0,0 +1,983 @@ +#include "map.h" +#include "raycast_mesh.h" +#include "../../common/Log.h" + +#ifdef WIN32 +#define _snprintf snprintf +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +struct Map::impl +{ + RaycastMesh *rm; +}; + + +inline bool file_exists(const std::string& name) { + std::ifstream f(name.c_str()); + return f.good(); +} + +ThreadReturnType LoadMapAsync(void* mapToLoad) +{ + Map* map = (Map*)mapToLoad; + map->SetMapLoaded(false); + + + std::string filename = "Maps/"; + filename += map->GetFileName(); + + std::string deflatedFileName = filename + ".EQ2MapDeflated"; + + filename += ".EQ2Map"; + + if(file_exists(deflatedFileName)) + filename = deflatedFileName; + + map->SetFileName(filename); + + if (map->Load(filename)) + map->SetMapLoaded(true); + + map->SetMapLoading(false); + THREAD_RETURN(NULL); +} + +Map::Map(string zonename, string file) { + CheckMapMutex.SetName(file + "MapMutex"); + SetMapLoaded(false); + m_ZoneName = zonename; + m_ZoneFile = file; + imp = nullptr; + m_MinY = 9999999.0f; + m_MaxY = -9999999.0f; +} + +Map::~Map() { + SetMapLoaded(false); + if(imp) { + imp->rm->release(); + safe_delete(imp); + } + + std::map::iterator itr; + for(itr = grid_map_border.begin(); itr != grid_map_border.end(); itr++) { + safe_delete(itr->second); + } + grid_map_border.clear(); +} + +float Map::FindBestZ(glm::vec3 &start, glm::vec3 *result, std::map* ignored_widgets, uint32* GridID, uint32* WidgetID) +{ + if (!IsMapLoaded()) + return BEST_Z_INVALID; + if (!imp) + return BEST_Z_INVALID; + + glm::vec3 tmp; + if(!result) + result = &tmp; + + start.z += 1.0f;//RuleI(Map, FindBestZHeightAdjust); + glm::vec3 from(start.x, start.y, start.z); + glm::vec3 to(start.x, start.y, BEST_Z_INVALID); + float hit_distance; + bool hit = false; + + hit = imp->rm->raycast((const RmReal*)&from, (const RmReal*)&to, (RmReal*)result, nullptr, &hit_distance, (RmUint32*)GridID, (RmUint32*)WidgetID, (RmMap*)ignored_widgets); + if(hit) { + return result->z; + } + + // Find nearest Z above us + + to.z = -BEST_Z_INVALID; + hit = imp->rm->raycast((const RmReal*)&from, (const RmReal*)&to, (RmReal*)result, nullptr, &hit_distance, (RmUint32*)GridID, (RmUint32*)WidgetID, (RmMap*)ignored_widgets); + if (hit) + { + return result->z; + } + + return BEST_Z_INVALID; +} + +float Map::FindClosestZ(glm::vec3 &start, glm::vec3 *result, std::map* ignored_widgets, uint32 *GridID, uint32* WidgetID) { + if (!IsMapLoaded()) + return false; + // Unlike FindBestZ, this method finds the closest Z value above or below the specified point. + // + if (!imp) + return false; + + float ClosestZ = BEST_Z_INVALID; + + glm::vec3 tmp; + if (!result) + result = &tmp; + + glm::vec3 from(start.x, start.y, start.z); + glm::vec3 to(start.x, start.y, BEST_Z_INVALID); + float hit_distance; + bool hit = false; + // first check is below us + hit = imp->rm->raycast((const RmReal*)&from, (const RmReal*)&to, (RmReal*)result, nullptr, &hit_distance, (RmUint32*)GridID, (RmUint32*)WidgetID, (RmMap*)ignored_widgets); + if (hit) { + ClosestZ = result->z; + + } + + // Find nearest Z above us + to.z = -BEST_Z_INVALID; + hit = imp->rm->raycast((const RmReal*)&from, (const RmReal*)&to, (RmReal*)result, nullptr, &hit_distance, (RmUint32*)GridID, (RmUint32*)WidgetID, (RmMap*)ignored_widgets); + if (hit) { + if (std::abs(from.z - result->z) < std::abs(ClosestZ - from.z)) + return result->z; + } + + return ClosestZ; +} + +bool Map::LineIntersectsZone(glm::vec3 start, glm::vec3 end, float step, std::map* ignored_widgets, glm::vec3 *result) { + if (!IsMapLoaded()) + return false; + if(!imp) + return false; + return imp->rm->raycast((const RmReal*)&start, (const RmReal*)&end, (RmReal*)result, nullptr, nullptr, nullptr, nullptr, (RmMap*)ignored_widgets); +} + +bool Map::LineIntersectsZoneNoZLeaps(glm::vec3 start, glm::vec3 end, float step_mag, std::map* ignored_widgets, glm::vec3 *result) { + if (!IsMapLoaded()) + return false; + if (!imp) + return false; + + float z = BEST_Z_INVALID; + glm::vec3 step; + glm::vec3 cur; + cur.x = start.x; + cur.y = start.y; + cur.z = start.z; + + step.x = end.x - start.x; + step.y = end.y - start.y; + step.z = end.z - start.z; + float factor = step_mag / sqrt(step.x*step.x + step.y*step.y + step.z*step.z); + + step.x *= factor; + step.y *= factor; + step.z *= factor; + + int steps = 0; + + if (step.x > 0 && step.x < 0.001f) + step.x = 0.001f; + if (step.y > 0 && step.y < 0.001f) + step.y = 0.001f; + if (step.z > 0 && step.z < 0.001f) + step.z = 0.001f; + if (step.x < 0 && step.x > -0.001f) + step.x = -0.001f; + if (step.y < 0 && step.y > -0.001f) + step.y = -0.001f; + if (step.z < 0 && step.z > -0.001f) + step.z = -0.001f; + + //while we are not past end + //always do this once, even if start == end. + while(cur.x != end.x || cur.y != end.y || cur.z != end.z) + { + steps++; + glm::vec3 me; + me.x = cur.x; + me.y = cur.y; + me.z = cur.z; + glm::vec3 hit; + + float best_z = FindBestZ(me, &hit, ignored_widgets); + float diff = best_z - z; + diff = diff < 0 ? -diff : diff; + + if (z <= BEST_Z_INVALID || best_z <= BEST_Z_INVALID || diff < 12.0) + z = best_z; + else + return true; + + //look at current location + if(LineIntersectsZone(start, end, step_mag, ignored_widgets, result)) + { + return true; + } + + //move 1 step + if (cur.x != end.x) + cur.x += step.x; + if (cur.y != end.y) + cur.y += step.y; + if (cur.z != end.z) + cur.z += step.z; + + //watch for end conditions + if ( (cur.x > end.x && end.x >= start.x) || (cur.x < end.x && end.x <= start.x) || (step.x == 0) ) { + cur.x = end.x; + } + if ( (cur.y > end.y && end.y >= start.y) || (cur.y < end.y && end.y <= start.y) || (step.y == 0) ) { + cur.y = end.y; + } + if ( (cur.z > end.z && end.z >= start.z) || (cur.z < end.z && end.z < start.z) || (step.z == 0) ) { + cur.z = end.z; + } + } + + //walked entire line and didnt run into anything... + return false; +} + +bool Map::CheckLoS(glm::vec3 myloc, glm::vec3 oloc, std::map* ignored_widgets) +{ + if (!IsMapLoaded()) + return false; + if(!imp) + return false; + + return !imp->rm->raycast((const RmReal*)&myloc, (const RmReal*)&oloc, nullptr, nullptr, nullptr, nullptr, nullptr, (RmMap*)ignored_widgets); +} + +// returns true if a collision happens +bool Map::DoCollisionCheck(glm::vec3 myloc, glm::vec3 oloc, std::map* ignored_widgets, glm::vec3 &outnorm, float &distance) { + if (!IsMapLoaded()) + return false; + if(!imp) + return false; + + return imp->rm->raycast((const RmReal*)&myloc, (const RmReal*)&oloc, nullptr, (RmReal *)&outnorm, (RmReal *)&distance, nullptr, nullptr, (RmMap*)ignored_widgets); +} + +Map *Map::LoadMapFile(std::string zonename, std::string file) { + + std::string filename = "Maps/"; + filename += file; + + std::string deflatedFileName = filename + ".EQ2MapDeflated"; + + filename += ".EQ2Map"; + + if(file_exists(deflatedFileName)) + filename = deflatedFileName; + + LogWrite(MAP__INFO, 7, "Map", "Attempting to load Map File [{%s}]", filename.c_str()); + + auto m = new Map(zonename, file); + m->SetMapLoading(true); + m->SetFileName(filename); +#ifdef WIN32 + _beginthread(LoadMapAsync, 0, (void*)m); +#else + pthread_t t1; + pthread_create(&t1, NULL, LoadMapAsync, (void*)m); + pthread_detach(t1); +#endif + + return m; +} + +/** + * @param filename + * @return + */ +bool Map::Load(const std::string &filename) +{ + FILE *map_file = fopen(filename.c_str(), "rb"); + if (map_file) { + LogWrite(MAP__INFO, 7, "Map", "Loading Map File [{%s}]", filename.c_str()); + bool loaded_map_file = LoadV2(map_file); + fclose(map_file); + + if (loaded_map_file) { + LogWrite(MAP__INFO, 7, "Map", "Loaded Map File [{%s}]", filename.c_str()); + } + else { + LogWrite(MAP__ERROR, 7, "Map", "FAILED Loading Map File [{%s}]", filename.c_str()); + } + return loaded_map_file; + } + else { + return false; + } + + return false; +} + +struct ModelEntry +{ + struct Poly + { + uint32 v1, v2, v3; + uint8 vis; + }; + std::vector verts; + std::vector polys; +}; + + +bool Map::LoadV2(FILE* f) { + + std::size_t foundDeflated = m_FileName.find(".EQ2MapDeflated"); + if(foundDeflated != std::string::npos) + return LoadV2Deflated(f); + + // Read the string for the zone file name this was created for + int8 strSize; + char name[256]; + fread(&strSize, sizeof(int8), 1, f); + LogWrite(MAP__DEBUG, 0, "Map", "strSize = %u", strSize); + + size_t len = fread(&name, sizeof(char), strSize, f); + name[len] = '\0'; + LogWrite(MAP__DEBUG, 0, "Map", "name = %s", name); + + string fileName(name); + std::size_t found = fileName.find(m_ZoneName); + // Make sure file contents are for the correct zone + if (found == std::string::npos) { + fclose(f); + LogWrite(MAP__ERROR, 0, "Map", "Map::LoadV2() map contents (%s) do not match its name (%s).", &name, m_ZoneName.c_str()); + return false; + } + // Read the min bounds + fread(&m_MinX, sizeof(float), 1, f); + fread(&m_MinZ, sizeof(float), 1, f); + + // Read the max bounds + fread(&m_MaxX, sizeof(float), 1, f); + fread(&m_MaxZ, sizeof(float), 1, f); + + // Read the number of grids + int32 NumGrids; + fread(&NumGrids, sizeof(int32), 1, f); + + std::vector verts; + std::vector indices; + std::vector grids; + std::vector widgets; + + uint32 face_count = 0; + // Loop through the grids loading the face list + for (int32 i = 0; i < NumGrids; i++) { + // Read the grid id + int32 GridID; + fread(&GridID, sizeof(int32), 1, f); + + // Read the number of vertices + int32 NumFaces; + fread(&NumFaces, sizeof(int32), 1, f); + + face_count += NumFaces; + // Loop through the vertices list reading + // 3 at a time to creat a triangle (face) + GridMapBorder* border = GetMapGridBorder(GridID); + + for (int32 y = 0; y < NumFaces; ) { + // Each vertex need an x,y,z coordinate and +// we will be reading 3 to create the face + float x1, x2, x3; + float y1, y2, y3; + float z1, z2, z3; + + // Read the first vertex + fread(&x1, sizeof(float), 1, f); + fread(&y1, sizeof(float), 1, f); + fread(&z1, sizeof(float), 1, f); + y++; + + // Read the second vertex + fread(&x2, sizeof(float), 1, f); + fread(&y2, sizeof(float), 1, f); + fread(&z2, sizeof(float), 1, f); + y++; + + // Read the third (final) vertex + fread(&x3, sizeof(float), 1, f); + fread(&y3, sizeof(float), 1, f); + fread(&z3, sizeof(float), 1, f); + y++; + + glm::vec3 a(x1, z1, y1); + glm::vec3 b(x2, z2, y2); + glm::vec3 c(x3, z3, y3); + + MapMinMaxY(y1); + MapMinMaxY(y2); + MapMinMaxY(y3); + + size_t sz = verts.size(); + verts.push_back(a); + indices.push_back((uint32)sz); + + verts.push_back(b); + indices.push_back((uint32)sz + 1); + + verts.push_back(c); + indices.push_back((uint32)sz + 2); + + grids.push_back((uint32)GridID); + widgets.push_back((uint32)0); + MapGridMinMaxBorderArray(border, a, b, c); + } + } + face_count = face_count / 3; + + if (imp) { + imp->rm->release(); + imp->rm = nullptr; + } + else { + imp = new impl; + } + + imp->rm = createRaycastMesh((RmUint32)verts.size(), (const RmReal*)&verts[0], face_count, &indices[0], &grids[0], &widgets[0]); + + if (!imp->rm) { + delete imp; + imp = nullptr; + return false; + } + + return true; +} + +bool Map::LoadV3Deflated(std::ifstream* file, std::streambuf * const srcbuf) { + + std::vector verts; + std::vector indices; + std::vector grids; + std::vector widgets; + + int8 strSize = 0; + char* buf = new char[1024]; + + int32 mapVersion = 0; + srcbuf->sgetn(buf,sizeof(int32)); + memcpy(&mapVersion,&buf[0],sizeof(int32)); + LogWrite(MAP__DEBUG, 0, "Map", "MapVersion = %u", mapVersion); + + srcbuf->sgetn(buf,sizeof(int8)); + memcpy(&strSize,&buf[0],sizeof(int8)); + LogWrite(MAP__DEBUG, 0, "Map", "strSize = %u", strSize); + + char name[256]; + srcbuf->sgetn(&name[0],strSize); + name[strSize] = '\0'; + LogWrite(MAP__DEBUG, 0, "Map", "name = %s", name); + string fileName(name); + std::size_t found = fileName.find(m_ZoneName); + // Make sure file contents are for the correct zone + if (found == std::string::npos) { + file->close(); + safe_delete_array(buf); + LogWrite(MAP__ERROR, 0, "Map", "Map::LoadV3Deflated() map contents (%s) do not match its name (%s).", &name, m_ZoneFile.c_str()); + return false; + } + // Read the min bounds + srcbuf->sgetn(buf,sizeof(float)); + memcpy(&m_MinX,&buf[0],sizeof(float)); + srcbuf->sgetn(buf,sizeof(float)); + memcpy(&m_MinY,&buf[0],sizeof(float)); + srcbuf->sgetn(buf,sizeof(float)); + memcpy(&m_MinZ,&buf[0],sizeof(float)); + + srcbuf->sgetn(buf,sizeof(float)); + memcpy(&m_MaxX,&buf[0],sizeof(float)); + srcbuf->sgetn(buf,sizeof(float)); + memcpy(&m_MaxY,&buf[0],sizeof(float)); + srcbuf->sgetn(buf,sizeof(float)); + memcpy(&m_MaxZ,&buf[0],sizeof(float)); + + // Read the number of grids + int32 NumGrids; + srcbuf->sgetn(buf,sizeof(int32)); + memcpy(&NumGrids,&buf[0],sizeof(int32)); + + uint32 face_count = 0; + // Loop through the grids loading the face list + for (int32 i = 0; i < NumGrids; i++) { + // Read the grid id + int32 GridID; + srcbuf->sgetn(buf,sizeof(int32)); + memcpy(&GridID,&buf[0],sizeof(int32)); + + // Read the number of vertices + int32 vertex_map_count; + srcbuf->sgetn(buf,sizeof(int32)); + memcpy(&vertex_map_count,&buf[0],sizeof(int32)); + + GridMapBorder* border = GetMapGridBorder(GridID); + for(int32 m = 0; m < vertex_map_count; m++) { + int32 WidgetID; + srcbuf->sgetn(buf,sizeof(int32)); + memcpy(&WidgetID,&buf[0],sizeof(int32)); + + float w_x1, w_y1, w_z1; + + // read widget coords + srcbuf->sgetn(buf,sizeof(float)*3); + memcpy(&w_x1,&buf[0],sizeof(float)); + memcpy(&w_y1,&buf[4],sizeof(float)); + memcpy(&w_z1,&buf[8],sizeof(float)); + + glm::vec3 a(w_x1, w_y1, w_z1); + widget_map.insert(make_pair(WidgetID, a)); + + int32 NumFaces; + srcbuf->sgetn(buf,sizeof(int32)); + memcpy(&NumFaces,&buf[0],sizeof(int32)); + + face_count += NumFaces; + + for (int32 y = 0; y < NumFaces; ) { + // Each vertex need an x,y,z coordinate and + // we will be reading 3 to create the face + float x1, x2, x3; + float y1, y2, y3; + float z1, z2, z3; + + // Read the first vertex + srcbuf->sgetn(buf,sizeof(float)*3); + memcpy(&x1,&buf[0],sizeof(float)); + memcpy(&y1,&buf[4],sizeof(float)); + memcpy(&z1,&buf[8],sizeof(float)); + y++; + + // Read the second vertex + srcbuf->sgetn(buf,sizeof(float)*3); + memcpy(&x2,&buf[0],sizeof(float)); + memcpy(&y2,&buf[4],sizeof(float)); + memcpy(&z2,&buf[8],sizeof(float)); + y++; + + // Read the third (final) vertex + srcbuf->sgetn(buf,sizeof(float)*3); + memcpy(&x3,&buf[0],sizeof(float)); + memcpy(&y3,&buf[4],sizeof(float)); + memcpy(&z3,&buf[8],sizeof(float)); + y++; + + glm::vec3 a(x1, z1, y1); + glm::vec3 b(x2, z2, y2); + glm::vec3 c(x3, z3, y3); + + size_t sz = verts.size(); + verts.push_back(a); + indices.push_back((uint32)sz); + + verts.push_back(b); + indices.push_back((uint32)sz + 1); + + verts.push_back(c); + indices.push_back((uint32)sz + 2); + + grids.push_back(GridID); + widgets.push_back(WidgetID); + MapGridMinMaxBorderArray(border, a, b, c); + } + + } + // Loop through the vertices list reading + // 3 at a time to creat a triangle (face) + } + face_count = face_count / 3; + + if (imp) { + imp->rm->release(); + imp->rm = nullptr; + } + else { + imp = new impl; + } + + imp->rm = createRaycastMesh((RmUint32)verts.size(), (const RmReal*)&verts[0], face_count, &indices[0], &grids[0], &widgets[0]); + + file->close(); + safe_delete_array(buf); + if (!imp->rm) { + delete imp; + imp = nullptr; + return false; + } + + return true; +} + +bool Map::LoadV2Deflated(FILE* f) { + std::ifstream file(m_FileName.c_str(), ios_base::in | ios_base::binary); + boost::iostreams::filtering_streambuf inbuf; + inbuf.push(boost::iostreams::gzip_decompressor()); + inbuf.push(file); + ostream out(&inbuf); + std::streambuf * const srcbuf = out.rdbuf(); + std::streamsize size = srcbuf->in_avail(); + if(size == -1) + { + file.close(); + LogWrite(MAP__ERROR, 0, "Map", "Map::LoadV2Deflated() unable to deflate (%s).", m_ZoneFile.c_str()); + return false; + } + // Read the string for the zone file name this was created for + int8 strSize; + char* buf = new char[1024]; + srcbuf->sgetn(buf,sizeof(int8)); + memcpy(&strSize,&buf[0],sizeof(int8)); + LogWrite(MAP__DEBUG, 0, "Map", "strSize = %u", strSize); + + char name[256]; + srcbuf->sgetn(&name[0],strSize); + name[strSize] = '\0'; + LogWrite(MAP__DEBUG, 0, "Map", "name = %s", name); + string fileName(name); + + if(fileName.find("EQ2EmuMapTool") != std::string::npos) { + safe_delete_array(buf); + return(LoadV3Deflated(&file, srcbuf)); + } + + + std::size_t found = fileName.find(m_ZoneName); + // Make sure file contents are for the correct zone + if (found == std::string::npos) { + file.close(); + safe_delete_array(buf); + LogWrite(MAP__ERROR, 0, "Map", "Map::LoadV2Deflated() map contents (%s) do not match its name (%s).", &name, m_ZoneFile.c_str()); + return false; + } + // Read the min bounds + srcbuf->sgetn(buf,sizeof(float)); + memcpy(&m_MinX,&buf[0],sizeof(float)); + srcbuf->sgetn(buf,sizeof(float)); + memcpy(&m_MinZ,&buf[0],sizeof(float)); + + srcbuf->sgetn(buf,sizeof(float)); + memcpy(&m_MaxX,&buf[0],sizeof(float)); + srcbuf->sgetn(buf,sizeof(float)); + memcpy(&m_MaxZ,&buf[0],sizeof(float)); + + // Read the number of grids + int32 NumGrids; + srcbuf->sgetn(buf,sizeof(int32)); + memcpy(&NumGrids,&buf[0],sizeof(int32)); + + std::vector verts; + std::vector indices; + std::vector grids; + std::vector widgets; + + uint32 face_count = 0; + // Loop through the grids loading the face list + for (int32 i = 0; i < NumGrids; i++) { + // Read the grid id + int32 GridID; + srcbuf->sgetn(buf,sizeof(int32)); + memcpy(&GridID,&buf[0],sizeof(int32)); + + // Read the number of vertices + int32 NumFaces; + srcbuf->sgetn(buf,sizeof(int32)); + memcpy(&NumFaces,&buf[0],sizeof(int32)); + + face_count += NumFaces; + // Loop through the vertices list reading + // 3 at a time to creat a triangle (face) + GridMapBorder* border = GetMapGridBorder(GridID); + + for (int32 y = 0; y < NumFaces; ) { + // Each vertex need an x,y,z coordinate and +// we will be reading 3 to create the face + float x1, x2, x3; + float y1, y2, y3; + float z1, z2, z3; + + // Read the first vertex + srcbuf->sgetn(buf,sizeof(float)*3); + memcpy(&x1,&buf[0],sizeof(float)); + memcpy(&y1,&buf[4],sizeof(float)); + memcpy(&z1,&buf[8],sizeof(float)); + y++; + + // Read the second vertex + srcbuf->sgetn(buf,sizeof(float)*3); + memcpy(&x2,&buf[0],sizeof(float)); + memcpy(&y2,&buf[4],sizeof(float)); + memcpy(&z2,&buf[8],sizeof(float)); + y++; + + // Read the third (final) vertex + srcbuf->sgetn(buf,sizeof(float)*3); + memcpy(&x3,&buf[0],sizeof(float)); + memcpy(&y3,&buf[4],sizeof(float)); + memcpy(&z3,&buf[8],sizeof(float)); + y++; + + glm::vec3 a(x1, z1, y1); + glm::vec3 b(x2, z2, y2); + glm::vec3 c(x3, z3, y3); + + MapMinMaxY(y1); + MapMinMaxY(y2); + MapMinMaxY(y3); + + size_t sz = verts.size(); + verts.push_back(a); + indices.push_back((uint32)sz); + + verts.push_back(b); + indices.push_back((uint32)sz + 1); + + verts.push_back(c); + indices.push_back((uint32)sz + 2); + + grids.push_back(GridID); + widgets.push_back((uint32)0); + MapGridMinMaxBorderArray(border, a, b, c); + } + } + face_count = face_count / 3; + + if (imp) { + imp->rm->release(); + imp->rm = nullptr; + } + else { + imp = new impl; + } + + imp->rm = createRaycastMesh((RmUint32)verts.size(), (const RmReal*)&verts[0], face_count, &indices[0], &grids[0], &widgets[0]); + + file.close(); + safe_delete_array(buf); + if (!imp->rm) { + delete imp; + imp = nullptr; + return false; + } + + return true; +} + + +void Map::RotateVertex(glm::vec3 &v, float rx, float ry, float rz) { + glm::vec3 nv = v; + + nv.y = (std::cos(rx) * v.y) - (std::sin(rx) * v.z); + nv.z = (std::sin(rx) * v.y) + (std::cos(rx) * v.z); + + v = nv; + + nv.x = (std::cos(ry) * v.x) + (std::sin(ry) * v.z); + nv.z = -(std::sin(ry) * v.x) + (std::cos(ry) * v.z); + + v = nv; + + nv.x = (std::cos(rz) * v.x) - (std::sin(rz) * v.y); + nv.y = (std::sin(rz) * v.x) + (std::cos(rz) * v.y); + + v = nv; +} + +void Map::ScaleVertex(glm::vec3 &v, float sx, float sy, float sz) { + v.x = v.x * sx; + v.y = v.y * sy; + v.z = v.z * sz; +} + +void Map::TranslateVertex(glm::vec3 &v, float tx, float ty, float tz) { + v.x = v.x + tx; + v.y = v.y + ty; + v.z = v.z + tz; +} + +void Map::MapMinMaxY(float y) { + if(y < m_MinY) + m_MinY = y; + if(y > m_MaxY) + m_MaxY = y; +} + +void Map::MapGridMinMaxBorderArray(GridMapBorder* border, glm::vec3 a, glm::vec3 b, glm::vec3 c) { + if(!border) + return; + + MapGridMinMaxBorder(border, a); + MapGridMinMaxBorder(border, b); + MapGridMinMaxBorder(border, c); +} + +void Map::MapGridMinMaxBorder(GridMapBorder* border, glm::vec3 a) { + if(!border) + return; + + if(a.x < border->m_MinX) + border->m_MinX = a.x; + if(a.x > border->m_MaxX) + border->m_MaxX = a.x; + if(a.y < border->m_MinY) + border->m_MinY = a.y; + if(a.y > border->m_MaxY) + border->m_MaxY = a.y; + if(a.z < border->m_MinZ) + border->m_MinZ = a.z; + if(a.z > border->m_MaxZ) + border->m_MaxZ = a.z; +} + +bool Map::IsPointInGrid(GridMapBorder* border, glm::vec3 a, float radius) { + return border != nullptr && (a.x >= (border->m_MinX - radius) && a.x <= (border->m_MaxX + radius) && a.y >= (border->m_MinY - radius) && a.y <= (border->m_MaxY + radius) && a.z >= (border->m_MinZ - radius) && a.z <= (border->m_MaxZ + radius)); +} + +std::vector Map::GetGridsByPoint(glm::vec3 a, float radius) { + std::vector grids; + std::map::iterator itr; + for(itr = grid_map_border.begin(); itr != grid_map_border.end(); itr++) { + if(IsPointInGrid(itr->second, a, radius)) { + grids.push_back(itr->first); + } + } + return grids; +} + +GridMapBorder* Map::GetMapGridBorder(int32 grid_id, bool instantiate_border) { + std::map::iterator itr = grid_map_border.find(grid_id); + GridMapBorder* border = nullptr; + if(itr != grid_map_border.end()) { + border = itr->second; + } + else if(instantiate_border) { + border = new GridMapBorder; + border->m_MinX = 999999.0f; + border->m_MaxX = -999999.0f; + border->m_MinY = 999999.0f; + border->m_MaxY = -999999.0f; + border->m_MinZ = 999999.0f; + border->m_MaxZ = -999999.0f; + grid_map_border.insert(make_pair(grid_id, border)); + } + return border; +} + +void MapRange::AddVersionRange(std::string zoneName) { + boost::filesystem::path targetDir("Maps/"); + + // crash fix since the dir isn't present + if(!boost::filesystem::is_directory(targetDir)) + { + LogWrite(MAP__ERROR, 7, "Map", "Unable to find directory %s", targetDir.c_str()); + return; + } + + boost::filesystem::recursive_directory_iterator iter(targetDir), eod; + boost::smatch base_match; + std::string formula = "(.*\\/|.*\\\\)((" + zoneName + ")(\\-([0-9]+)\\-([0-9]+))?)(\\.EQ2Map|\\.EQ2MapDeflated)$"; + boost::regex re(formula.c_str()); + LogWrite(MAP__INFO, 0, "Map", "Map Formula to match: %s", formula.c_str()); + BOOST_FOREACH(boost::filesystem::path + const & i, make_pair(iter, eod)) { + if (is_regular_file(i)) { + std::string fileName(i.string()); + + if (boost::regex_match(fileName, base_match, re)) { + boost::ssub_match base_sub_match = base_match[2]; + boost::ssub_match base_sub_match2 = base_match[5]; + boost::ssub_match base_sub_match3 = base_match[6]; + std::string baseMatch(base_sub_match.str().c_str()); + std::string baseMatch2(base_sub_match2.str().c_str()); + std::string baseMatch3(base_sub_match3.str().c_str()); + LogWrite(MAP__INFO, 0, "Map", "Map To Load: %s, size: %i, string: %s, min: %s, max: %s\n", i.string().c_str(), base_match.size(), baseMatch.c_str(), baseMatch2.c_str(), baseMatch3.c_str()); + + Map * zonemap = Map::LoadMapFile(zoneName, base_sub_match.str().c_str()); + + int32 min_version = 0, max_version = 0; + if (strlen(base_sub_match2.str().c_str()) > 0) + min_version = atoul(base_sub_match2.str().c_str()); + + if (strlen(base_sub_match2.str().c_str()) > 0) + max_version = atoul(base_sub_match3.str().c_str()); + version_map.insert(std::make_pair(new VersionRange(min_version, max_version), zonemap)); + } + } + } +} + +MapRange::MapRange() +{ + +} + +MapRange::~MapRange() +{ + Clear(); +} + +void MapRange::Clear() +{ + map::iterator itr; + for (itr = version_map.begin(); itr != version_map.end(); itr++) + { + VersionRange* range = itr->first; + Map* map = itr->second; + delete range; + delete map; + } + + version_map.clear(); +} + +map::iterator MapRange::FindVersionRange(int32 min_version, int32 max_version) +{ + map::iterator itr; + for (itr = version_map.begin(); itr != version_map.end(); itr++) + { + VersionRange* range = itr->first; + // if min and max version are both in range + if (range->GetMinVersion() <= min_version && max_version <= range->GetMaxVersion()) + return itr; + // if the min version is in range, but max range is 0 + else if (range->GetMinVersion() <= min_version && range->GetMaxVersion() == 0) + return itr; + // if min version is 0 and max_version has a cap + else if (range->GetMinVersion() == 0 && max_version <= range->GetMaxVersion()) + return itr; + } + + return version_map.end(); +} + +map::iterator MapRange::FindMapByVersion(int32 version) +{ + map::iterator enditr = version_map.end(); + map::iterator itr; + for (itr = version_map.begin(); itr != version_map.end(); itr++) + { + VersionRange* range = itr->first; + // if min and max version are both in range + if(range->GetMinVersion() == 0 && range->GetMaxVersion() == 0) + enditr = itr; + else if (version >= range->GetMinVersion() && version <= range->GetMaxVersion()) + return itr; + } + + return enditr; +} \ No newline at end of file diff --git a/source/WorldServer/Zone/map.h b/source/WorldServer/Zone/map.h new file mode 100644 index 0000000..b6cdd92 --- /dev/null +++ b/source/WorldServer/Zone/map.h @@ -0,0 +1,151 @@ +/* + EQEMu: Everquest Server Emulator + + Copyright (C) 2001-2014 EQEMu Development Team (http://eqemulator.net) + + 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 except by those people which sell it, which + are required to give you total support for your newly bought product; + 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 ZONE_MAP_H +#define ZONE_MAP_H + +#include "../../common/types.h" +#include "../../common/MiscFunctions.h" +#include "../../common/Mutex.h" +#include "position.h" +#include + +#define BEST_Z_INVALID -99999 + +struct GridMapBorder { + float m_MinX; + float m_MaxX; + float m_MinY; + float m_MaxY; + float m_MinZ; + float m_MaxZ; +}; +class Map +{ +public: + Map(string zonename, string filename); + ~Map(); + + float FindBestZ(glm::vec3 &start, glm::vec3 *result, std::map* ignored_widgets, uint32 *GridID = 0, uint32* WidgetID = 0); + float FindClosestZ(glm::vec3 &start, glm::vec3 *result, std::map* ignored_widgets, uint32 *GridID = 0, uint32* WidgetID = 0); + bool LineIntersectsZone(glm::vec3 start, glm::vec3 end, float step, std::map* ignored_widgets, glm::vec3 *result); + bool LineIntersectsZoneNoZLeaps(glm::vec3 start, glm::vec3 end, float step_mag, std::map* ignored_widgets, glm::vec3 *result); + bool CheckLoS(glm::vec3 myloc, glm::vec3 oloc, std::map* ignored_widgets); + bool DoCollisionCheck(glm::vec3 myloc, glm::vec3 oloc, std::map* ignored_widgets, glm::vec3 &outnorm, float &distance); + + bool Load(const std::string& filename); + + static Map *LoadMapFile(std::string zonename, std::string file); + + std::string GetFileName() { return m_ZoneFile; } + void SetMapLoaded(bool val) { + CheckMapMutex.writelock(); + mapLoaded = val; + CheckMapMutex.releasewritelock(); + } + bool IsMapLoaded() { + bool isMapLoaded = false; + CheckMapMutex.readlock(); + isMapLoaded = mapLoaded; + CheckMapMutex.releasereadlock(); + return isMapLoaded; + } + + void SetMapLoading(bool val) { + CheckMapMutex.writelock(); + mapLoading = val; + CheckMapMutex.releasewritelock(); + } + bool IsMapLoading() { + bool isMapLoading = false; + CheckMapMutex.readlock(); + isMapLoading = mapLoading; + CheckMapMutex.releasereadlock(); + return isMapLoading; + } + float GetMinX() { return m_MinX; } + float GetMaxX() { return m_MaxX; } + float GetMinY() { return m_MinY; } + float GetMaxY() { return m_MaxY; } + float GetMinZ() { return m_MinZ; } + float GetMaxZ() { return m_MaxZ; } + + bool isPointWithinMap(double x, double y, double z, double minX, double minY, double minZ, double maxX, double maxY, double maxZ) { + return (x >= m_MinX && x <= m_MaxX && y >= m_MinY && y <= m_MaxY && z >= m_MinZ && z <= m_MaxZ); + } + + void SetFileName(std::string newfile) { m_FileName = string(newfile); } + + void MapMinMaxY(float y); + void MapGridMinMaxBorderArray(GridMapBorder* border, glm::vec3 a, glm::vec3 b, glm::vec3 c); + void MapGridMinMaxBorder(GridMapBorder* border, glm::vec3 a); + bool IsPointInGrid(GridMapBorder* border, glm::vec3 a, float radius); + std::vector GetGridsByPoint(glm::vec3 a, float radius); + GridMapBorder* GetMapGridBorder(int32 grid_id, bool instantiate_border = true); + + std::map widget_map; + std::map grid_map_border; +private: + void RotateVertex(glm::vec3 &v, float rx, float ry, float rz); + void ScaleVertex(glm::vec3 &v, float sx, float sy, float sz); + void TranslateVertex(glm::vec3 &v, float tx, float ty, float tz); + bool LoadV2(FILE *f); + bool LoadV2Deflated(FILE *f); + bool LoadV3Deflated(std::ifstream* file, std::streambuf * const srcbuf); + + string m_FileName; + string m_ZoneFile; + string m_ZoneName; + int32 m_CellSize; + + float m_MinX; + float m_MinY; + float m_MinZ; + float m_MaxX; + float m_MaxY; + float m_MaxZ; + + struct impl; + impl *imp; + bool mapLoaded; + bool mapLoading; + Mutex CheckMapMutex; +}; + +class MapRange { +public: + MapRange(); + + ~MapRange(); + + void Clear(); + + void AddVersionRange(std::string zoneName); + + map::iterator FindVersionRange(int32 min_version, int32 max_version); + map::iterator FindMapByVersion(int32 version); + map::iterator GetRangeEnd() { return version_map.end(); } +private: + std::map version_map; + string name; +}; + +#endif diff --git a/source/WorldServer/Zone/mob_movement_manager.cpp b/source/WorldServer/Zone/mob_movement_manager.cpp new file mode 100644 index 0000000..5f9a43e --- /dev/null +++ b/source/WorldServer/Zone/mob_movement_manager.cpp @@ -0,0 +1,1279 @@ +#include "mob_movement_manager.h" +#include "../Entity.h" +#include "../zoneserver.h" +#include "region_map.h" +#include "map.h" +#include "../../common/timer.h" +#include "pathfinder_interface.h" +#include "position.h" +#include "../../common/Log.h" + +#include +#include +#include +#include + +#ifdef WIN32 +#include +#endif + +extern double frame_time; + +class IMovementCommand { +public: + IMovementCommand() = default; + virtual ~IMovementCommand() = default; + virtual bool Process(MobMovementManager* mob_movement_manager, Entity* mob) = 0; + virtual bool Started() const = 0; +}; + +class RotateToCommand : public IMovementCommand { +public: + RotateToCommand(double rotate_to, double dir, MobMovementMode mob_movement_mode) + { + m_rotate_to = rotate_to; + m_rotate_to_dir = dir; + m_rotate_to_mode = mob_movement_mode; + m_started = false; + } + + virtual ~RotateToCommand() + { + + } + + virtual bool Process(MobMovementManager* mob_movement_manager, Entity* mob) + { + auto rotate_to_speed = m_rotate_to_mode == MovementRunning ? 200.0 : 16.0; //todo: get this from mob + + auto from = mob_movement_manager->FixHeading(mob->GetHeading()); + auto to = mob_movement_manager->FixHeading(m_rotate_to); + auto diff = to - from; + + while (diff < -256.0) { + diff += 512.0; + } + + while (diff > 256) { + diff -= 512.0; + } + + auto dist = std::abs(diff); + + if (!m_started) { + m_started = true; + //mob->SetMoving(true); + + /*if (dist > 15.0f && rotate_to_speed > 0.0 && rotate_to_speed <= 25.0) { //send basic rotation + mob_movement_manager->SendCommandToClients( + mob, + 0.0, + 0.0, + 0.0, + m_rotate_to_dir * rotate_to_speed, + 0, + ClientRangeClose + ); + }*/ + } + + auto td = rotate_to_speed * 19.0 * frame_time; + + if (td >= dist) { + mob->SetHeading(to); + //mob->SetMoving(false); + //mob_movement_manager->SendCommandToClients(mob, 0.0, 0.0, 0.0, 0.0, 0, ClientRangeCloseMedium); + return true; + } + + from += td * m_rotate_to_dir; + mob->SetHeading(mob_movement_manager->FixHeading(from)); + return false; + } + + virtual bool Started() const + { + return m_started; + } + +private: + double m_rotate_to; + double m_rotate_to_dir; + MobMovementMode m_rotate_to_mode; + bool m_started; +}; + +class MoveToCommand : public IMovementCommand { +public: + MoveToCommand(float x, float y, float z, MobMovementMode mob_movement_mode) + { + m_distance_moved_since_correction = 0.0; + m_move_to_x = x; + m_move_to_y = y; + m_move_to_z = z; + m_move_to_mode = mob_movement_mode; + m_last_sent_time = 0.0; + m_last_sent_speed = 0; + m_started = false; + m_total_h_dist = 0.0; + m_total_v_dist = 0.0; + } + + virtual ~MoveToCommand() + { + + } + + /** + * @param mob_movement_manager + * @param mob + * @return + */ + virtual bool Process(MobMovementManager* mob_movement_manager, Entity* mob) + { + //Send a movement packet when you start moving + double current_time = static_cast(Timer::GetCurrentTime2()) / 1000.0; + int current_speed = 0; + + if (m_move_to_mode == MovementRunning) { + current_speed = ((Spawn*)mob)->GetSpeed(); + } + + if (!m_started) { + m_started = true; + //rotate to the point + //mob->SetMoving(true); + mob->SetHeading(mob->GetFaceTarget(m_move_to_x, m_move_to_z)); + + m_last_sent_speed = current_speed; + m_last_sent_time = current_time; + // Z/Y are flipped due to EverQuest 2 using Y as up/down + m_total_h_dist = DistanceNoZ(glm::vec4(mob->GetX(),mob->GetZ(),mob->GetY(),mob->GetHeading()), glm::vec4(m_move_to_x, m_move_to_z, 0.0f, 0.0f)); + m_total_v_dist = m_move_to_y - mob->GetY(); + //mob_movement_manager->SendCommandToClients(mob, 0.0, 0.0, 0.0, 0.0, current_speed, ClientRangeCloseMedium); + } + + //When speed changes + if (current_speed != m_last_sent_speed) { + //if (RuleB(Map, FixZWhenPathing)) { + // mob->FixZ(); + //} + + m_distance_moved_since_correction = 0.0; + + m_last_sent_speed = current_speed; + m_last_sent_time = current_time; + //mob_movement_manager->SendCommandToClients(mob, 0.0, 0.0, 0.0, 0.0, current_speed, ClientRangeCloseMedium); + } + + //If x seconds have passed without sending an update. + if (current_time - m_last_sent_time >= 5.0) { + //if (RuleB(Map, FixZWhenPathing)) { + //mob->FixZ(); + //} + + m_distance_moved_since_correction = 0.0; + + m_last_sent_speed = current_speed; + m_last_sent_time = current_time; + //mob_movemesnt_manager->SendCommandToClients(mob, 0.0, 0.0, 0.0, 0.0, current_speed, ClientRangeCloseMedium); + } + + glm::vec3 p = glm::vec3(mob->GetX(), mob->GetY(), mob->GetZ()); + // our X/Z versus the mobs X/Z + glm::vec2 tar(m_move_to_x, m_move_to_z); + glm::vec2 pos(p.x, p.z); + double len = glm::distance(pos, tar); + if (len < .01) { + return true; + } + + //mob->SetMoved(true); + + glm::vec2 dir = tar - pos; + glm::vec2 ndir = glm::normalize(dir); + double distance_moved = frame_time * current_speed * 0.4f * 1.45f; + + //mob->SetX(m_move_to_x); + //mob->SetY(m_move_to_z); + //mob->SetZ(m_move_to_y); + + mob->ClearRunningLocations(); + + if (distance_moved > len) { + //if (RuleB(Map, FixZWhenPathing)) { + //mob->FixZ(); + //} + // we use npos.y because higher up that is the equilvaent Z + mob->AddRunningLocation(m_move_to_x, m_move_to_y, m_move_to_z, current_speed, distance_moved, true, true, "", true); + return false; + } + else { + glm::vec2 npos = pos + (ndir * static_cast(distance_moved)); + + len -= distance_moved; + double total_distance_traveled = m_total_h_dist - len; + double start_y = m_move_to_y - m_total_v_dist; + double y_at_pos = start_y + (m_total_v_dist * (total_distance_traveled / m_total_h_dist)); + + // we use npos.y because higher up that is the equilvaent Z + mob->AddRunningLocation(m_move_to_x, m_move_to_y, m_move_to_z, current_speed, distance_moved, true, true, "", true); + + // mob->SetX(npos.x); + // mob->SetY(z_at_pos); + // mob->SetZ(npos.y); + + //if (RuleB(Map, FixZWhenPathing)) { + // m_distance_moved_since_correction += distance_moved; + // if (m_distance_moved_since_correction > 10.0f /*RuleR(Map, DistanceCanTravelBeforeAdjustment)*/) { + // m_distance_moved_since_correction = 0.0; + //mob->FixZ(); + //} + // } + } + + return false; + } + + virtual bool Started() const + { + return m_started; + } + +protected: + double m_distance_moved_since_correction; + double m_move_to_x; + double m_move_to_y; + double m_move_to_z; + MobMovementMode m_move_to_mode; + bool m_started; + + double m_last_sent_time; + int m_last_sent_speed; + double m_total_h_dist; + double m_total_v_dist; +}; + +class SwimToCommand : public MoveToCommand { +public: + SwimToCommand(float x, float y, float z, MobMovementMode mob_movement_mode) : MoveToCommand(x, y, z, mob_movement_mode) + { + + } + + virtual bool Process(MobMovementManager* mob_movement_manager, Entity* mob) + { + //Send a movement packet when you start moving + double current_time = static_cast(Timer::GetCurrentTime2()) / 1000.0; + int current_speed = 0; + + if (m_move_to_mode == MovementRunning) { + if (mob->IsFeared()) { + current_speed = mob->GetBaseSpeed(); + } + else { + //runback overrides + if (mob->GetSpeed() > mob->GetMaxSpeed()) + current_speed = mob->GetSpeed(); + else + current_speed = mob->GetMaxSpeed(); + } + } + else { + current_speed = mob->GetBaseSpeed(); + } + + if (!m_started) { + m_started = true; + //rotate to the point + //mob->SetMoving(true); + mob->SetHeading(mob->GetFaceTarget(m_move_to_x, m_move_to_z)); + + m_last_sent_speed = current_speed; + m_last_sent_time = current_time; + m_total_h_dist = DistanceNoZ(glm::vec4(mob->GetX(),mob->GetZ(),mob->GetY(),mob->GetHeading()), glm::vec4(m_move_to_x, m_move_to_z, 0.0f, 0.0f)); + m_total_v_dist = m_move_to_y - mob->GetY(); + //mob_movement_manager->SendCommandToClients(mob, 0.0, 0.0, 0.0, 0.0, current_speed, ClientRangeCloseMedium); + } + + //When speed changes + if (current_speed != m_last_sent_speed) { + m_last_sent_speed = current_speed; + m_last_sent_time = current_time; + //mob_movement_manager->SendCommandToClients(mob, 0.0, 0.0, 0.0, 0.0, current_speed, ClientRangeCloseMedium); + } + + //If x seconds have passed without sending an update. + if (current_time - m_last_sent_time >= 1.5) { + m_last_sent_speed = current_speed; + m_last_sent_time = current_time; + //mob_movement_manager->SendCommandToClients(mob, 0.0, 0.0, 0.0, 0.0, current_speed, ClientRangeCloseMedium); + } + + glm::vec4 p = glm::vec4(mob->GetX(), mob->GetZ(), mob->GetY(), mob->GetHeading()); + glm::vec2 tar(m_move_to_x, m_move_to_y); + glm::vec2 pos(p.x, p.y); + double len = glm::distance(pos, tar); + if (len == 0) { + return true; + } + + //mob->SetMoved(true); + + glm::vec2 dir = tar - pos; + glm::vec2 ndir = glm::normalize(dir); + double distance_moved = frame_time * current_speed * 0.4f * 1.45f; + + mob->SetX(m_move_to_x); + mob->SetZ(m_move_to_z); + mob->SetY(m_move_to_y); + if (distance_moved > len) { + return true; + } + else { + glm::vec2 npos = pos + (ndir * static_cast(distance_moved)); + + len -= distance_moved; + double total_distance_traveled = m_total_h_dist - len; + double start_y = m_move_to_y - m_total_v_dist; + double y_at_pos = start_y + (m_total_v_dist * (total_distance_traveled / m_total_h_dist)); + + mob->SetX(npos.x); + mob->SetZ(npos.y); + mob->SetY(y_at_pos); + } + + return false; + } +}; + +class TeleportToCommand : public IMovementCommand { +public: + TeleportToCommand(float x, float y, float z, float heading) + { + m_teleport_to_x = x; + m_teleport_to_y = y; + m_teleport_to_z = z; + m_teleport_to_heading = heading; + } + + virtual ~TeleportToCommand() + { + + } + + virtual bool Process(MobMovementManager* mob_movement_manager, Entity* mob) + { + mob->SetX(m_teleport_to_x); + mob->SetZ(m_teleport_to_z); + mob->SetY(m_teleport_to_y); + mob->SetHeading(mob_movement_manager->FixHeading(m_teleport_to_heading)); + //mob_movement_manager->SendCommandToClients(mob, 0.0, 0.0, 0.0, 0.0, 0, ClientRangeAny); + + return true; + } + + virtual bool Started() const + { + return false; + } + +private: + + double m_teleport_to_x; + double m_teleport_to_y; + double m_teleport_to_z; + double m_teleport_to_heading; +}; + +class StopMovingCommand : public IMovementCommand { +public: + StopMovingCommand() + { + } + + virtual ~StopMovingCommand() + { + + } + + virtual bool Process(MobMovementManager* mob_movement_manager, Entity* mob) + { + mob->ClearRunningLocations(); + return true; + } + + virtual bool Started() const + { + return false; + } +}; + +class EvadeCombatCommand : public IMovementCommand { +public: + EvadeCombatCommand() + { + } + + virtual ~EvadeCombatCommand() + { + + } + + virtual bool Process(MobMovementManager* mob_movement_manager, Entity* mob) + { + return true; + } + + virtual bool Started() const + { + return false; + } +}; + +struct MovementStats { + MovementStats() + { + LastResetTime = static_cast(Timer::GetCurrentTime2()) / 1000.0; + TotalSent = 0ULL; + TotalSentMovement = 0ULL; + TotalSentPosition = 0ULL; + TotalSentHeading = 0ULL; + } + + double LastResetTime; + uint64_t TotalSent; + uint64_t TotalSentMovement; + uint64_t TotalSentPosition; + uint64_t TotalSentHeading; +}; + +struct NavigateTo { + NavigateTo() + { + navigate_to_x = 0.0; + navigate_to_y = 0.0; + navigate_to_z = 0.0; + navigate_to_heading = 0.0; + last_set_time = 0.0; + } + + double navigate_to_x; + double navigate_to_y; + double navigate_to_z; + double navigate_to_heading; + double last_set_time; +}; + +struct MobMovementEntry { + std::deque> Commands; + NavigateTo NavTo; +}; + +void AdjustRoute(std::list &nodes, Entity *who) +{ + if (who->GetZone() == nullptr || !who->GetMap() /*|| !zone->HasWaterMap()*/) { + return; + } + + auto offset = who->GetYOffset(); + + for (auto &node : nodes) { + //if (!zone->watermap->InLiquid(node.pos)) { + auto best_z = who->FindBestZ(node.pos, nullptr); + if (best_z != BEST_Z_INVALID) { + node.pos.z = best_z + offset; + } + //} // todo: floating logic? + } +} + +struct MobMovementManager::Implementation { + std::map Entries; + std::vector Clients; + MovementStats Stats; +}; + +MobMovementManager::MobMovementManager() +{ + MobListMutex.SetName("MobMovementManager"); + _impl.reset(new Implementation()); +} + +MobMovementManager::~MobMovementManager() +{ +} + +void MobMovementManager::Process() +{ + MobListMutex.readlock(); + for (auto &iter : _impl->Entries) { + auto &ent = iter.second; + auto &commands = ent.Commands; + + if (commands.size() < 1) + continue; + + iter.first->MCommandMutex.writelock(); + while (true != commands.empty()) { + auto &cmd = commands.front(); + auto r = cmd->Process(this, iter.first); + + if (true != r) { + break; + } + + commands.pop_front(); + } + iter.first->MCommandMutex.releasewritelock(); + } + MobListMutex.releasereadlock(); +} + +/** + * @param mob + */ +void MobMovementManager::AddMob(Entity *mob) +{ + MobListMutex.writelock(); + _impl->Entries.insert(std::make_pair(mob, MobMovementEntry())); + MobListMutex.releasewritelock(); +} + +/** + * @param mob + */ +void MobMovementManager::RemoveMob(Entity *mob) +{ + MobListMutex.writelock(); + auto iter = _impl->Entries.find(mob); + if(iter != _impl->Entries.end()) + _impl->Entries.erase(iter); + MobListMutex.releasewritelock(); +} + +/** + * @param client + */ +void MobMovementManager::AddClient(Client *client) +{ + _impl->Clients.push_back(client); +} + +/** + * @param client + */ +void MobMovementManager::RemoveClient(Client *client) +{ + auto iter = _impl->Clients.begin(); + while (iter != _impl->Clients.end()) { + if (client == *iter) { + _impl->Clients.erase(iter); + return; + } + + ++iter; + } +} + +/** + * @param who + * @param to + * @param mob_movement_mode + */ +void MobMovementManager::RotateTo(Entity *who, float to, MobMovementMode mob_movement_mode) +{ + MobListMutex.readlock(); + auto iter = _impl->Entries.find(who); + + if (iter == _impl->Entries.end()) + { + MobListMutex.releasereadlock(); + return; // does not exist in navigation + } + + auto &ent = (*iter); + + if (true != ent.second.Commands.empty()) { + MobListMutex.releasereadlock(); + return; + } + + PushRotateTo(ent.second, who, to, mob_movement_mode); + MobListMutex.releasereadlock(); +} + +/** + * @param who + * @param x + * @param y + * @param z + * @param heading + */ +void MobMovementManager::Teleport(Entity *who, float x, float y, float z, float heading) +{ + MobListMutex.readlock(); + auto iter = _impl->Entries.find(who); + + if (iter == _impl->Entries.end()) + { + MobListMutex.releasereadlock(); + return; // does not exist in navigation + } + + auto &ent = (*iter); + + ent.second.Commands.clear(); + + PushTeleportTo(ent.second, x, y, z, heading); + MobListMutex.releasereadlock(); +} + +/** + * @param who + * @param x + * @param y + * @param z + * @param mode + */ +void MobMovementManager::NavigateTo(Entity *who, float x, float y, float z, MobMovementMode mode, bool overrideDistance) +{ + glm::vec3 targPos(x, z, y); + glm::vec3 origPos(who->GetX(), who->GetZ(), who->GetY()); + + if (IsPositionEqualWithinCertainZ(targPos, origPos, 6.0f)) { + return; + } + MobListMutex.readlock(); + auto iter = _impl->Entries.find(who); + + if (iter == _impl->Entries.end()) + { + MobListMutex.releasereadlock(); + return; // does not exist in navigation + } + + auto &ent = (*iter); + auto &nav = ent.second.NavTo; + + double current_time = static_cast(Timer::GetCurrentTime2()) / 1000.0; + if ((current_time - nav.last_set_time) > 0.5) { + //Can potentially recalc + + auto within = IsPositionWithinSimpleCylinder( + glm::vec3(who->GetX(), who->GetZ(), who->GetY()), + glm::vec3(nav.navigate_to_x, nav.navigate_to_z, nav.navigate_to_y), + 1.5f, + 6.0f + ); + + who->MCommandMutex.writelock(); + + if (within && ent.second.Commands.size() > 0 && nav.last_set_time != 0) + { + //who->ClearRunningLocations(); + //StopNavigation((Entity*)who); + who->MCommandMutex.releasewritelock(); + MobListMutex.releasereadlock(); + return; + } + else if (!within && ent.second.Commands.size() > 0 && nav.last_set_time != 0) + { + who->MCommandMutex.releasewritelock(); + MobListMutex.releasereadlock(); + return; + } + + LogWrite(MAP__DEBUG, 0, "Map", "%s %f %f %f: within: %i, commands: %i, lastnav: %f %f %f", who->GetName(), + who->GetX(),who->GetY(),who->GetZ(),within, + ent.second.Commands.size(), nav.navigate_to_x, nav.navigate_to_y, nav.navigate_to_z); + //auto heading_match = IsHeadingEqual(0.0, nav.navigate_to_heading); + + //if (/*false == within ||*/ false == heading_match || ent.second.Commands.size() == 0) { + ent.second.Commands.clear(); + + //Path is no longer valid, calculate a new path + UpdatePath(who, x, y, z, mode); + nav.navigate_to_x = x; + nav.navigate_to_y = y; + nav.navigate_to_z = z; + nav.navigate_to_heading = 0.0; + nav.last_set_time = current_time; + + who->MCommandMutex.releasewritelock(); + //} + } + MobListMutex.releasereadlock(); +} + +/** + * @param who + */ +void MobMovementManager::StopNavigation(Entity *who) +{ + MobListMutex.readlock(); + auto iter = _impl->Entries.find(who); + + if (iter == _impl->Entries.end()) + { + MobListMutex.releasereadlock(); + return; // does not exist in navigation + } + + auto &ent = (*iter); + auto &nav = ent.second.NavTo; + + nav.navigate_to_x = 0.0; + nav.navigate_to_y = 0.0; + nav.navigate_to_z = 0.0; + nav.navigate_to_heading = 0.0; + nav.last_set_time = 0.0; + + who->MCommandMutex.writelock(); + if (true == ent.second.Commands.empty()) { + PushStopMoving(ent.second); + who->MCommandMutex.releasewritelock(); + MobListMutex.releasereadlock(); + return; + } + + if (!who->IsRunning()) { + ent.second.Commands.clear(); + who->MCommandMutex.releasewritelock(); + MobListMutex.releasereadlock(); + return; + } + + ent.second.Commands.clear(); + PushStopMoving(ent.second); + + who->MCommandMutex.releasewritelock(); + MobListMutex.releasereadlock(); +} + +void MobMovementManager::DisruptNavigation(Entity* who) +{ + MobListMutex.readlock(); + auto iter = _impl->Entries.find(who); + + if (iter == _impl->Entries.end()) + { + MobListMutex.releasereadlock(); + return; // does not exist in navigation + } + + auto& ent = (*iter); + auto& nav = ent.second.NavTo; + + nav.navigate_to_x = 0.0; + nav.navigate_to_y = 0.0; + nav.navigate_to_z = 0.0; + nav.navigate_to_heading = 0.0; + nav.last_set_time = 0.0; + + if (!who->IsRunning()) { + who->MCommandMutex.writelock(); + ent.second.Commands.clear(); + who->MCommandMutex.releasewritelock(); + } + MobListMutex.releasereadlock(); +} + +/** + * @param in + * @return + */ +float MobMovementManager::FixHeading(float in) +{ + auto h = in; + while (h > 512.0) { + h -= 512.0; + } + + while (h < 0.0) { + h += 512.0; + } + + return h; +} + +void MobMovementManager::ClearStats() +{ + _impl->Stats.LastResetTime = static_cast(Timer::GetCurrentTime2()) / 1000.0; + _impl->Stats.TotalSent = 0; + _impl->Stats.TotalSentHeading = 0; + _impl->Stats.TotalSentMovement = 0; + _impl->Stats.TotalSentPosition = 0; +} + + +/** + * @param who + * @param x + * @param y + * @param z + * @param mob_movement_mode + */ +void MobMovementManager::UpdatePath(Entity *who, float x, float y, float z, MobMovementMode mob_movement_mode) +{ + if (!who->GetMap() /*|| !zone->HasWaterMap()*/) { + MobListMutex.readlock(); + auto iter = _impl->Entries.find(who); + + if (iter == _impl->Entries.end()) + { + MobListMutex.releasereadlock(); + return; // does not exist in navigation + } + + auto &ent = (*iter); + + PushMoveTo(ent.second, x, y, z, mob_movement_mode); + PushStopMoving(ent.second); + MobListMutex.releasereadlock(); + return; + } + /* + if (who-?()) { + UpdatePathBoat(who, x, y, z, mob_movement_mode); + } + else if (who->IsUnderwaterOnly()) { + UpdatePathUnderwater(who, x, y, z, mob_movement_mode); + }*/ + //else { + UpdatePathGround(who, x, y, z, mob_movement_mode); + //} +} + +/** + * @param who + * @param x + * @param y + * @param z + * @param mode + */ +void MobMovementManager::UpdatePathGround(Entity *who, float x, float y, float z, MobMovementMode mode) +{ + PathfinderOptions opts; + opts.smooth_path = true; + opts.step_size = 100.0f;//RuleR(Pathing, NavmeshStepSize); + opts.offset = who->GetYOffset()+1.0f; + opts.flags = PathingNotDisabled ^ PathingZoneLine; + + //This is probably pointless since the nav mesh tool currently sets zonelines to disabled anyway + auto partial = false; + auto stuck = false; + auto route = who->GetZone()->pathing->FindPath( + glm::vec3(who->GetX(), who->GetZ(), who->GetY()), + glm::vec3(x, z, y), + partial, + stuck, + opts + ); + + MobListMutex.readlock(); + auto eiter = _impl->Entries.find(who); + + if (eiter == _impl->Entries.end()) + { + MobListMutex.releasereadlock(); + return; // does not exist in navigation + } + + auto &ent = (*eiter); + + if (route.size() == 0) { + HandleStuckBehavior(who, x, y, z, mode); + MobListMutex.releasereadlock(); + return; + } + + AdjustRoute(route, who); + + //avoid doing any processing if the mob is stuck to allow normal stuck code to work. + if (!stuck) { + + //there are times when the routes returned are no differen than where the mob is currently standing. What basically happens + //is a mob will get 'stuck' in such a way that it should be moving but the 'moving' place is the exact same spot it is at. + //this is a problem and creates an area of ground that if a mob gets to, will stay there forever. If socal this creates a + //"Ball of Death" (tm). This code tries to prevent this by simply warping the mob to the requested x/y. Better to have a warp than + //have stuck mobs. + + auto routeNode = route.begin(); + bool noValidPath = true; + while (routeNode != route.end() && noValidPath == true) { + auto ¤tNode = (*routeNode); + + if (routeNode == route.end()) { + continue; + } + + if (!(currentNode.pos.x == who->GetX() && currentNode.pos.y == who->GetZ())) { + //if one of the nodes to move to, is not our current node, pass it. + noValidPath = false; + break; + } + //move to the next node + routeNode++; + + } + + if (noValidPath) { + //we are 'stuck' in a path, lets just get out of this by 'teleporting' to the next position. + PushTeleportTo( + ent.second, + x, + y, + z, + CalculateHeadingAngleBetweenPositions(who->GetX(), who->GetZ(), x, z) + ); + + MobListMutex.releasereadlock(); + return; + } + } + + auto iter = route.begin(); + + glm::vec3 previous_pos(who->GetX(), who->GetZ(), who->GetY()); + + bool first_node = true; + while (iter != route.end()) { + auto ¤t_node = (*iter); + + iter++; + + if (iter == route.end()) { + continue; + } + + previous_pos = current_node.pos; + auto &next_node = (*iter); + + if (first_node) { + + if (mode == MovementWalking) { + auto h = who->GetFaceTarget(next_node.pos.x, next_node.pos.y); + PushRotateTo(ent.second, who, h, mode); + } + + first_node = false; + } + + //move to / teleport to node + 1 + if (next_node.teleport && next_node.pos.x != 0.0f && next_node.pos.y != 0.0f) { + float calcedHeading = + CalculateHeadingAngleBetweenPositions( + current_node.pos.x, + current_node.pos.y, + next_node.pos.x, + next_node.pos.y + ); + PushTeleportTo( + ent.second, + next_node.pos.x, + next_node.pos.z, + next_node.pos.y, + calcedHeading + ); + } + else { +/* if (who->GetZone()->watermap->InLiquid(previous_pos)) { + PushSwimTo(ent.second, next_node.pos.x, next_node.pos.y, next_node.pos.z, mode); + } + else {*/ + PushMoveTo(ent.second, next_node.pos.x, next_node.pos.z, next_node.pos.y, mode); +// } + } + } + + if (stuck) { + HandleStuckBehavior(who, x, y, z, mode); + } + else { + PushStopMoving(ent.second); + } + MobListMutex.releasereadlock(); +} + +/** + * @param who + * @param x + * @param y + * @param z + * @param movement_mode + */ +void MobMovementManager::UpdatePathUnderwater(Entity *who, float x, float y, float z, MobMovementMode movement_mode) +{ + MobListMutex.readlock(); + auto eiter = _impl->Entries.find(who); + + if (eiter == _impl->Entries.end()) + { + MobListMutex.releasereadlock(); + return; // does not exist in navigation + } + + auto &ent = (*eiter); + if (/*zone->watermap->InLiquid(who->GetPosition()) && zone->watermap->InLiquid(glm::vec3(x, y, z)) &&*/ + who->CheckLoS(glm::vec3(who->GetX(),who->GetZ(),who->GetY()), glm::vec3(x, z, y))) { + PushSwimTo(ent.second, x, y, z, movement_mode); + PushStopMoving(ent.second); + MobListMutex.releasereadlock(); + return; + } + + PathfinderOptions opts; + opts.smooth_path = true; + opts.step_size = 100.0f;// RuleR(Pathing, NavmeshStepSize); + opts.offset = who->GetYOffset(); + opts.flags = PathingNotDisabled ^ PathingZoneLine; + + auto partial = false; + auto stuck = false; + auto route = who->GetZone()->pathing->FindPath( + glm::vec3(who->GetX(), who->GetY(), who->GetZ()), + glm::vec3(x, y, z), + partial, + stuck, + opts + ); + + if (route.size() == 0) { + HandleStuckBehavior(who, x, z, y, movement_mode); + MobListMutex.releasereadlock(); + return; + } + + AdjustRoute(route, who); + + auto iter = route.begin(); + glm::vec3 previous_pos(who->GetX(), who->GetY(), who->GetZ()); + bool first_node = true; + + while (iter != route.end()) { + auto ¤t_node = (*iter); + +/* if (!zone->watermap->InLiquid(current_node.pos)) { + stuck = true; + + while (iter != route.end()) { + iter = route.erase(iter); + } + + break; + } + else {*/ + iter++; +// } + } + + if (route.size() == 0) { + HandleStuckBehavior(who, x, y, z, movement_mode); + MobListMutex.releasereadlock(); + return; + } + + iter = route.begin(); + + while (iter != route.end()) { + auto ¤t_node = (*iter); + + iter++; + + if (iter == route.end()) { + continue; + } + + previous_pos = current_node.pos; + auto &next_node = (*iter); + + if (first_node) { + + if (movement_mode == MovementWalking) { + auto h = who->GetFaceTarget(next_node.pos.x, next_node.pos.y); + PushRotateTo(ent.second, who, h, movement_mode); + } + + first_node = false; + } + + //move to / teleport to node + 1 + if (next_node.teleport && next_node.pos.x != 0.0f && next_node.pos.y != 0.0f) { + float calcHeading = CalculateHeadingAngleBetweenPositions( + current_node.pos.x, + current_node.pos.y, + next_node.pos.x, + next_node.pos.y + ); + + PushTeleportTo( + ent.second, next_node.pos.x, next_node.pos.z, next_node.pos.y, calcHeading); + } + else { + PushSwimTo(ent.second, next_node.pos.x, next_node.pos.z, next_node.pos.y, movement_mode); + } + } + + if (stuck) { + HandleStuckBehavior(who, x, y, z, movement_mode); + } + else { + PushStopMoving(ent.second); + } + MobListMutex.releasereadlock(); +} + +/** + * @param who + * @param x + * @param y + * @param z + * @param mode + */ +void MobMovementManager::UpdatePathBoat(Entity *who, float x, float y, float z, MobMovementMode mode) +{ + MobListMutex.readlock(); + auto eiter = _impl->Entries.find(who); + + if (eiter == _impl->Entries.end()) + { + MobListMutex.releasereadlock(); + return; // does not exist in navigation + } + + auto &ent = (*eiter); + + PushSwimTo(ent.second, x, y, z, mode); + PushStopMoving(ent.second); + MobListMutex.releasereadlock(); +} + +/** + * @param ent + * @param x + * @param y + * @param z + * @param heading + */ +void MobMovementManager::PushTeleportTo(MobMovementEntry &ent, float x, float y, float z, float heading) +{ + ent.Commands.push_back(std::unique_ptr(new TeleportToCommand(x, y, z, heading))); +} + +/** + * @param ent + * @param x + * @param y + * @param z + * @param mob_movement_mode + */ +void MobMovementManager::PushMoveTo(MobMovementEntry &ent, float x, float y, float z, MobMovementMode mob_movement_mode) +{ + ent.Commands.push_back(std::unique_ptr(new MoveToCommand(x, y, z, mob_movement_mode))); +} + +/** + * @param ent + * @param x + * @param y + * @param z + * @param mob_movement_mode + */ +void MobMovementManager::PushSwimTo(MobMovementEntry &ent, float x, float y, float z, MobMovementMode mob_movement_mode) +{ + ent.Commands.push_back(std::unique_ptr(new SwimToCommand(x, y, z, mob_movement_mode))); +} + +/** + * @param ent + * @param who + * @param to + * @param mob_movement_mode + */ +void MobMovementManager::PushRotateTo(MobMovementEntry &ent, Entity *who, float to, MobMovementMode mob_movement_mode) +{ + auto from = FixHeading(who->GetHeading()); + to = FixHeading(to); + + float diff = to - from; + + if (std::abs(diff) < 0.001f) { + return; + } + + while (diff < -256.0) { + diff += 512.0; + } + + while (diff > 256) { + diff -= 512.0; + } + + ent.Commands.push_back(std::unique_ptr(new RotateToCommand(to, diff > 0 ? 1.0 : -1.0, mob_movement_mode))); +} + +/** + * @param mob_movement_entry + */ +void MobMovementManager::PushStopMoving(MobMovementEntry &mob_movement_entry) +{ + mob_movement_entry.Commands.push_back(std::unique_ptr(new StopMovingCommand())); +} + +/** + * @param mob_movement_entry + */ +void MobMovementManager::PushEvadeCombat(MobMovementEntry &mob_movement_entry) +{ + mob_movement_entry.Commands.push_back(std::unique_ptr(new EvadeCombatCommand())); +} + +/** + * @param who + * @param x + * @param y + * @param z + * @param mob_movement_mode + */ +void MobMovementManager::HandleStuckBehavior(Entity *who, float x, float y, float z, MobMovementMode mob_movement_mode) +{ + //LogDebug("Handle stuck behavior for {0} at ({1}, {2}, {3}) with movement_mode {4}", who->GetName(), x, y, z, mob_movement_mode); + + MobListMutex.readlock(); + auto sb = RunToTarget;//who->GetStuckBehavior(); + MobStuckBehavior behavior = RunToTarget; + + if (sb >= 0 && sb < MaxStuckBehavior) { + behavior = (MobStuckBehavior) sb; + } + + auto eiter = _impl->Entries.find(who); + + if (eiter == _impl->Entries.end()) + { + MobListMutex.releasereadlock(); + return; // does not exist in navigation + } + + auto &ent = (*eiter); + + switch (sb) { + case RunToTarget: + PushMoveTo(ent.second, x, y, z, mob_movement_mode); + PushStopMoving(ent.second); + break; + case WarpToTarget: + PushTeleportTo(ent.second, x, y, z, 0.0f); + PushStopMoving(ent.second); + break; + case TakeNoAction: + PushStopMoving(ent.second); + break; + case EvadeCombat: + PushEvadeCombat(ent.second); + break; + } + + MobListMutex.releasereadlock(); +} diff --git a/source/WorldServer/Zone/mob_movement_manager.h b/source/WorldServer/Zone/mob_movement_manager.h new file mode 100644 index 0000000..9520261 --- /dev/null +++ b/source/WorldServer/Zone/mob_movement_manager.h @@ -0,0 +1,111 @@ +#pragma once +#include +#include "../Entity.h" +#include "../../common/Mutex.h" +class Mob; +class Client; + +struct RotateCommand; +struct MovementCommand; +struct MobMovementEntry; +struct PlayerPositionUpdateServer_Struct; + +enum ClientRange : int +{ + ClientRangeNone = 0, + ClientRangeClose = 1, + ClientRangeMedium = 2, + ClientRangeCloseMedium = 3, + ClientRangeLong = 4, + ClientRangeCloseLong = 5, + ClientRangeMediumLong = 6, + ClientRangeAny = 7 +}; + +enum MobMovementMode : int +{ + MovementWalking = 0, + MovementRunning = 1 +}; + +enum MobStuckBehavior : int +{ + RunToTarget, + WarpToTarget, + TakeNoAction, + EvadeCombat, + MaxStuckBehavior +}; + +class MobMovementManager +{ +public: + ~MobMovementManager(); + void Process(); + void AddMob(Entity *mob); + void RemoveMob(Entity *mob); + void AddClient(Client *client); + void RemoveClient(Client *client); + + void RotateTo(Entity *who, float to, MobMovementMode mob_movement_mode = MovementRunning); + void Teleport(Entity *who, float x, float y, float z, float heading); + void NavigateTo(Entity *who, float x, float y, float z, MobMovementMode mode = MovementRunning, bool overrideDistance=false); + void StopNavigation(Entity *who); + void DisruptNavigation(Entity* who); + /* + void SendCommandToClients( + Entity *mob, + float delta_x, + float delta_y, + float delta_z, + float delta_heading, + int anim, + ClientRange range, + Client* single_client = nullptr, + Client* ignore_client = nullptr + );*/ + + float FixHeading(float in); + void ClearStats(); + + static MobMovementManager &Get() { + static MobMovementManager inst; + return inst; + } + + MobMovementManager(); + bool IsRunningCommandProcess() { + bool isRunning = false; + MobListMutex.readlock(); + isRunning = RunningCommandProcess; + MobListMutex.releasereadlock(); + return isRunning; + } + + bool SetCommandProcess(bool status) { + MobListMutex.writelock(); + RunningCommandProcess = status; + MobListMutex.releasewritelock(); + return true; + } +private: + MobMovementManager(const MobMovementManager&); + MobMovementManager& operator=(const MobMovementManager&); + + void UpdatePath(Entity *who, float x, float y, float z, MobMovementMode mob_movement_mode); + void UpdatePathGround(Entity *who, float x, float y, float z, MobMovementMode mode); + void UpdatePathUnderwater(Entity *who, float x, float y, float z, MobMovementMode movement_mode); + void UpdatePathBoat(Entity *who, float x, float y, float z, MobMovementMode mode); + void PushTeleportTo(MobMovementEntry &ent, float x, float y, float z, float heading); + void PushMoveTo(MobMovementEntry &ent, float x, float y, float z, MobMovementMode mob_movement_mode); + void PushSwimTo(MobMovementEntry &ent, float x, float y, float z, MobMovementMode mob_movement_mode); + void PushRotateTo(MobMovementEntry &ent, Entity *who, float to, MobMovementMode mob_movement_mode); + void PushStopMoving(MobMovementEntry &mob_movement_entry); + void PushEvadeCombat(MobMovementEntry &mob_movement_entry); + void HandleStuckBehavior(Entity *who, float x, float y, float z, MobMovementMode mob_movement_mode); + + struct Implementation; + std::unique_ptr _impl; + Mutex MobListMutex; + bool RunningCommandProcess; +}; diff --git a/source/WorldServer/Zone/pathfinder_interface.cpp b/source/WorldServer/Zone/pathfinder_interface.cpp new file mode 100644 index 0000000..f320eca --- /dev/null +++ b/source/WorldServer/Zone/pathfinder_interface.cpp @@ -0,0 +1,21 @@ +#include "../../common/Log.h" +#include "../client.h" + +#include "pathfinder_null.h" +#include "pathfinder_nav_mesh.h" +#include "pathfinder_waypoint.h" +#include + +IPathfinder *IPathfinder::Load(const std::string &zone) { + struct stat statbuffer; + //std::string navmesh_path = fmt::format("maps/nav/{0}.nav", zone); + std::string navmesh_path = "Maps/nav/" + zone + ".nav"; + + if (stat(navmesh_path.c_str(), &statbuffer) == 0) { + return new PathfinderNavmesh(navmesh_path); + } + else + LogWrite(MAP__INFO, 7, "Map", "Could not find Navmesh File [{%s}]", navmesh_path.c_str()); + + return new PathfinderNull(); +} diff --git a/source/WorldServer/Zone/pathfinder_interface.h b/source/WorldServer/Zone/pathfinder_interface.h new file mode 100644 index 0000000..29a9167 --- /dev/null +++ b/source/WorldServer/Zone/pathfinder_interface.h @@ -0,0 +1,81 @@ +#pragma once + +#include "map.h" +#include + +class Client; +class Seperator; + +enum PathingPolyFlags +{ + PathingNormal = 1, + PathingWater = 2, + PathingLava = 4, + PathingZoneLine = 8, + PathingPvP = 16, + PathingSlime = 32, + PathingIce = 64, + PathingVWater = 128, + PathingGeneralArea = 256, + PathingPortal = 512, + PathingPrefer = 1024, + PathingDisabled = 2048, + PathingAll = 65535, + PathingNotDisabled = PathingAll ^ PathingDisabled +}; + +struct PathfinderOptions +{ + PathfinderOptions() { + flags = PathingNotDisabled; + smooth_path = true; + step_size = 10.0f; + flag_cost[0] = 1.0f; + flag_cost[1] = 3.0f; + flag_cost[2] = 5.0f; + flag_cost[3] = 1.0f; + flag_cost[4] = 2.0f; + flag_cost[5] = 2.0f; + flag_cost[6] = 4.0f; + flag_cost[7] = 1.0f; + flag_cost[8] = 0.1f; + flag_cost[9] = 0.1f; + offset = 3.25f; + } + + int flags; + bool smooth_path; + float step_size; + float flag_cost[10]; + float offset; +}; + +class IPathfinder +{ +public: + struct IPathNode + { + IPathNode(const glm::vec3 &p) { + pos = p; + teleport = false; + } + + IPathNode(bool tp) { + teleport = tp; + } + + glm::vec3 pos; + bool teleport; + }; + + typedef std::list IPath; + + IPathfinder() { } + virtual ~IPathfinder() { } + + virtual IPath FindRoute(const glm::vec3 &start, const glm::vec3 &end, bool &partial, bool &stuck, int flags = PathingNotDisabled) = 0; + virtual IPath FindPath(const glm::vec3 &start, const glm::vec3 &end, bool &partial, bool &stuck, const PathfinderOptions& opts) = 0; + virtual glm::vec3 GetRandomLocation(const glm::vec3 &start) = 0; + + static IPathfinder *Load(const std::string &zone); +}; diff --git a/source/WorldServer/Zone/pathfinder_nav_mesh.cpp b/source/WorldServer/Zone/pathfinder_nav_mesh.cpp new file mode 100644 index 0000000..d52b7dc --- /dev/null +++ b/source/WorldServer/Zone/pathfinder_nav_mesh.cpp @@ -0,0 +1,514 @@ +#include +#include +#include +#include "../../common/Log.h" +#include "pathfinder_nav_mesh.h" +#include +#include +#include "../zoneserver.h" +#include "region_map.h" +#include "../client.h" + +struct PathfinderNavmesh::Implementation +{ + dtNavMesh *nav_mesh; + dtNavMeshQuery *query; +}; + +PathfinderNavmesh::PathfinderNavmesh(const std::string &path) +{ + m_impl.reset(new Implementation()); + m_impl->nav_mesh = nullptr; + m_impl->query = nullptr; + Load(path); +} + +PathfinderNavmesh::~PathfinderNavmesh() +{ + Clear(); +} + +IPathfinder::IPath PathfinderNavmesh::FindRoute(const glm::vec3 &start, const glm::vec3 &end, bool &partial, bool &stuck, int flags) +{ + partial = false; + + if (!m_impl->nav_mesh) { + return IPath(); + } + + if (!m_impl->query) { + m_impl->query = dtAllocNavMeshQuery(); + } + + m_impl->query->init(m_impl->nav_mesh, 4092 /*RuleI(Pathing, MaxNavmeshNodes)*/); + glm::vec3 current_location(start.x, start.z, start.y); + glm::vec3 dest_location(end.x, end.z, end.y); + + dtQueryFilter filter; + filter.setIncludeFlags(flags); + filter.setAreaCost(0, 1.0f); //Normal + filter.setAreaCost(1, 3.0f); //Water + filter.setAreaCost(2, 5.0f); //Lava + filter.setAreaCost(4, 1.0f); //PvP + filter.setAreaCost(5, 2.0f); //Slime + filter.setAreaCost(6, 2.0f); //Ice + filter.setAreaCost(7, 4.0f); //V Water (Frigid Water) + filter.setAreaCost(8, 1.0f); //General Area + filter.setAreaCost(9, 0.1f); //Portal + filter.setAreaCost(10, 0.1f); //Prefer + + dtPolyRef start_ref; + dtPolyRef end_ref; + glm::vec3 ext(5.0f, 100.0f, 5.0f); + + m_impl->query->findNearestPoly(¤t_location[0], &ext[0], &filter, &start_ref, 0); + m_impl->query->findNearestPoly(&dest_location[0], &ext[0], &filter, &end_ref, 0); + + if (!start_ref || !end_ref) { + return IPath(); + } + + int npoly = 0; + dtPolyRef path[1024] = { 0 }; + auto status = m_impl->query->findPath(start_ref, end_ref, ¤t_location[0], &dest_location[0], &filter, path, &npoly, 1024); + + if (npoly) { + glm::vec3 epos = dest_location; + if (path[npoly - 1] != end_ref) { + m_impl->query->closestPointOnPoly(path[npoly - 1], &dest_location[0], &epos[0], 0); + partial = true; + + auto dist = DistanceSquared(epos, current_location); + if (dist < 10000.0f) { + stuck = true; + } + } + + float straight_path[2048 * 3]; + unsigned char straight_path_flags[2048]; + + int n_straight_polys; + dtPolyRef straight_path_polys[2048]; + + status = m_impl->query->findStraightPath(¤t_location[0], &epos[0], path, npoly, + straight_path, straight_path_flags, + straight_path_polys, &n_straight_polys, 2048, DT_STRAIGHTPATH_AREA_CROSSINGS); + + if (dtStatusFailed(status)) { + return IPath(); + } + + if (n_straight_polys) { + IPath Route; + for (int i = 0; i < n_straight_polys; ++i) + { + glm::vec3 node; + node.x = straight_path[i * 3]; + node.z = straight_path[i * 3 + 1]; + node.y = straight_path[i * 3 + 2]; + + Route.push_back(node); + + unsigned short flag = 0; + if (dtStatusSucceed(m_impl->nav_mesh->getPolyFlags(straight_path_polys[i], &flag))) { + if (flag & 512) { + Route.push_back(true); + } + } + } + + return Route; + } + } + + IPath Route; + Route.push_back(end); + return Route; +} + +IPathfinder::IPath PathfinderNavmesh::FindPath(const glm::vec3 &start, const glm::vec3 &end, bool &partial, bool &stuck, const PathfinderOptions &opts) +{ + partial = false; + + if (!m_impl->nav_mesh) { + return IPath(); + } + + if (!m_impl->query) { + m_impl->query = dtAllocNavMeshQuery(); + } + + m_impl->query->init(m_impl->nav_mesh, 4092 /*RuleI(Pathing, MaxNavmeshNodes)*/); + glm::vec3 current_location(start.x, start.z, start.y); + glm::vec3 dest_location(end.x, end.z, end.y); + + dtQueryFilter filter; + filter.setIncludeFlags(opts.flags); + filter.setAreaCost(0, opts.flag_cost[0]); //Normal + filter.setAreaCost(1, opts.flag_cost[1]); //Water + filter.setAreaCost(2, opts.flag_cost[2]); //Lava + filter.setAreaCost(4, opts.flag_cost[3]); //PvP + filter.setAreaCost(5, opts.flag_cost[4]); //Slime + filter.setAreaCost(6, opts.flag_cost[5]); //Ice + filter.setAreaCost(7, opts.flag_cost[6]); //V Water (Frigid Water) + filter.setAreaCost(8, opts.flag_cost[7]); //General Area + filter.setAreaCost(9, opts.flag_cost[8]); //Portal + filter.setAreaCost(10, opts.flag_cost[9]); //Prefer + + static const int max_polys = 256; + dtPolyRef start_ref; + dtPolyRef end_ref; + glm::vec3 ext(10.0f, 200.0f, 10.0f); + + m_impl->query->findNearestPoly(¤t_location[0], &ext[0], &filter, &start_ref, 0); + m_impl->query->findNearestPoly(&dest_location[0], &ext[0], &filter, &end_ref, 0); + + if (!start_ref || !end_ref) { + return IPath(); + } + + int npoly = 0; + dtPolyRef path[max_polys] = { 0 }; + auto status = m_impl->query->findPath(start_ref, end_ref, ¤t_location[0], &dest_location[0], &filter, path, &npoly, max_polys); + + if (npoly) { + glm::vec3 epos = dest_location; + if (path[npoly - 1] != end_ref) { + m_impl->query->closestPointOnPoly(path[npoly - 1], &dest_location[0], &epos[0], 0); + partial = true; + + auto dist = DistanceSquared(epos, current_location); + if (dist < 10000.0f) { + stuck = true; + } + } + + int n_straight_polys; + glm::vec3 straight_path[max_polys]; + unsigned char straight_path_flags[max_polys]; + dtPolyRef straight_path_polys[max_polys]; + + auto status = m_impl->query->findStraightPath(¤t_location[0], &epos[0], path, npoly, + (float*)&straight_path[0], straight_path_flags, + straight_path_polys, &n_straight_polys, 2048, DT_STRAIGHTPATH_AREA_CROSSINGS | DT_STRAIGHTPATH_ALL_CROSSINGS); + + if (dtStatusFailed(status)) { + return IPath(); + } + + if (n_straight_polys) { + if (opts.smooth_path) { + IPath Route; + + //Add the first point + { + auto &flag = straight_path_flags[0]; + if (flag & DT_STRAIGHTPATH_OFFMESH_CONNECTION) { + auto &p = straight_path[0]; + + Route.push_back(glm::vec3(p.x, p.z, p.y)); + } + else { + auto &p = straight_path[0]; + + float h = 0.0f; + if (dtStatusSucceed(GetPolyHeightOnPath(path, npoly, p, &h))) { + p.y = h + opts.offset; + } + + Route.push_back(glm::vec3(p.x, p.z, p.y)); + } + } + + for (int i = 0; i < n_straight_polys - 1; ++i) + { + auto &flag = straight_path_flags[i]; + + if (flag & DT_STRAIGHTPATH_OFFMESH_CONNECTION) { + auto &poly = straight_path_polys[i]; + + auto &p2 = straight_path[i + 1]; + glm::vec3 node(p2.x, p2.z, p2.y); + Route.push_back(node); + + unsigned short pflag = 0; + if (dtStatusSucceed(m_impl->nav_mesh->getPolyFlags(straight_path_polys[i], &pflag))) { + if (pflag & 512) { + Route.push_back(true); + } + } + } + else { + auto &p1 = straight_path[i]; + auto &p2 = straight_path[i + 1]; + auto dist = glm::distance(p1, p2); + auto dir = glm::normalize(p2 - p1); + float total = 0.0f; + glm::vec3 previous_pt = p1; + + while (total < dist) { + glm::vec3 current_pt; + float dist_to_move = opts.step_size; + float ff = opts.step_size / 2.0f; + + if (total + dist_to_move + ff >= dist) { + current_pt = p2; + total = dist; + } + else { + total += dist_to_move; + current_pt = p1 + dir * total; + } + + float h = 0.0f; + if (dtStatusSucceed(GetPolyHeightOnPath(path, npoly, current_pt, &h))) { + current_pt.y = h + opts.offset; + } + + Route.push_back(glm::vec3(current_pt.x, current_pt.z, current_pt.y)); + previous_pt = current_pt; + } + } + } + + return Route; + } + else { + IPath Route; + for (int i = 0; i < n_straight_polys; ++i) + { + auto ¤t = straight_path[i]; + glm::vec3 node(current.x, current.z, current.y); + Route.push_back(node); + + unsigned short flag = 0; + if (dtStatusSucceed(m_impl->nav_mesh->getPolyFlags(straight_path_polys[i], &flag))) { + if (flag & 512) { + Route.push_back(true); + } + } + } + + return Route; + } + } + } + + return IPath(); +} + +glm::vec3 PathfinderNavmesh::GetRandomLocation(const glm::vec3 &start) +{ + if (start.x == 0.0f && start.y == 0.0) + return glm::vec3(0.f); + + if (!m_impl->nav_mesh) { + return glm::vec3(0.f); + } + + if (!m_impl->query) { + m_impl->query = dtAllocNavMeshQuery(); + m_impl->query->init(m_impl->nav_mesh, 4092 /*RuleI(Pathing, MaxNavmeshNodes)*/); + } + + dtQueryFilter filter; + filter.setIncludeFlags(65535U ^ 2048); + filter.setAreaCost(0, 1.0f); //Normal + filter.setAreaCost(1, 3.0f); //Water + filter.setAreaCost(2, 5.0f); //Lava + filter.setAreaCost(4, 1.0f); //PvP + filter.setAreaCost(5, 2.0f); //Slime + filter.setAreaCost(6, 2.0f); //Ice + filter.setAreaCost(7, 4.0f); //V Water (Frigid Water) + filter.setAreaCost(8, 1.0f); //General Area + filter.setAreaCost(9, 0.1f); //Portal + filter.setAreaCost(10, 0.1f); //Prefer + + dtPolyRef randomRef; + float point[3]; + + dtPolyRef start_ref; + glm::vec3 current_location(start.x, start.z, start.y); + glm::vec3 ext(5.0f, 100.0f, 5.0f); + + m_impl->query->findNearestPoly(¤t_location[0], &ext[0], &filter, &start_ref, 0); + + if (!start_ref) + { + return glm::vec3(0.f); + } + + if (dtStatusSucceed(m_impl->query->findRandomPointAroundCircle(start_ref, ¤t_location[0], 100.f, &filter, []() { return MakeRandomFloat(0.0,1.0); /*(float)zone->random.Real(0.0, 1.0);*/ }, &randomRef, point))) + { + return glm::vec3(point[0], point[2], point[1]); + } + + return glm::vec3(0.f); +} + +void PathfinderNavmesh::Clear() +{ + if (m_impl->nav_mesh) { + dtFreeNavMesh(m_impl->nav_mesh); + } + + if (m_impl->query) { + dtFreeNavMeshQuery(m_impl->query); + } +} + +void PathfinderNavmesh::Load(const std::string &path) +{ + Clear(); + + FILE *f = fopen(path.c_str(), "rb"); + if (f) { + NavMeshSetHeader header; + size_t readLen = fread(&header, sizeof(NavMeshSetHeader), 1, f); + if (readLen != 1) + { + fclose(f); + return; + } + if (header.magic != NAVMESHSET_MAGIC) + { + fclose(f); + return; + } + if (header.version != NAVMESHSET_VERSION) + { + fclose(f); + return; + } + + dtNavMesh* mesh = dtAllocNavMesh(); + if (!mesh) + { + fclose(f); + return; + } + dtStatus status = mesh->init(&header.params); + if (dtStatusFailed(status)) + { + fclose(f); + return; + } + + // Read tiles. + for (int i = 0; i < header.numTiles; ++i) + { + NavMeshTileHeader tileHeader; + readLen = fread(&tileHeader, sizeof(tileHeader), 1, f); + if (readLen != 1) + { + fclose(f); + return; + } + + if (!tileHeader.tileRef || !tileHeader.dataSize) + break; + + unsigned char* data = (unsigned char*)dtAlloc(tileHeader.dataSize, DT_ALLOC_PERM); + if (!data) break; + memset(data, 0, tileHeader.dataSize); + readLen = fread(data, tileHeader.dataSize, 1, f); + if (readLen != 1) + { + dtFree(data); + fclose(f); + return; + } + + mesh->addTile(data, tileHeader.dataSize, DT_TILE_FREE_DATA, tileHeader.tileRef, 0); + } + + m_impl->nav_mesh = mesh; + + LogWrite(MAP__INFO, 7, "Map", "Loaded Navmesh File [{%s}]", path.c_str()); + } +} + +void PathfinderNavmesh::ShowPath(Client * c, const glm::vec3 &start, const glm::vec3 &end) +{ +/* auto &list = entity_list.GetNPCList(); + + for (auto &iter : list) { + auto npc = iter.second; + auto name = npc->GetName(); + + if (strstr(name, "PathNode") != nullptr) { + npc->Depop(); + } + } + + PathfinderOptions opts; + opts.smooth_path = true; + opts.step_size = RuleR(Pathing, NavmeshStepSize); + bool partial = false; + bool stuck = false; + auto path = FindPath(start, end, partial, stuck, opts); + + for (auto &node : path) { + if (!node.teleport) { + NPC::SpawnNPC("PathNode 2253 1 0 1 2 1", glm::vec4(node.pos, 1.0)); + } + }*/ +} + +dtStatus PathfinderNavmesh::GetPolyHeightNoConnections(dtPolyRef ref, const float *pos, float *height) const +{ + auto *m_nav = m_impl->nav_mesh; + + if (!m_nav) { + return DT_FAILURE; + } + + const dtMeshTile* tile = 0; + const dtPoly* poly = 0; + if (dtStatusFailed(m_nav->getTileAndPolyByRef(ref, &tile, &poly))) { + return DT_FAILURE | DT_INVALID_PARAM; + } + + if (poly->getType() != DT_POLYTYPE_OFFMESH_CONNECTION) { + const unsigned int ip = (unsigned int)(poly - tile->polys); + const dtPolyDetail* pd = &tile->detailMeshes[ip]; + for (int j = 0; j < pd->triCount; ++j) + { + const unsigned char* t = &tile->detailTris[(pd->triBase + j) * 4]; + const float* v[3]; + for (int k = 0; k < 3; ++k) + { + if (t[k] < poly->vertCount) + v[k] = &tile->verts[poly->verts[t[k]] * 3]; + else + v[k] = &tile->detailVerts[(pd->vertBase + (t[k] - poly->vertCount)) * 3]; + } + float h; + if (dtClosestHeightPointTriangle(pos, v[0], v[1], v[2], h)) + { + if (height) + *height = h; + return DT_SUCCESS; + } + } + } + + return DT_FAILURE | DT_INVALID_PARAM; +} + +dtStatus PathfinderNavmesh::GetPolyHeightOnPath(const dtPolyRef *path, const int path_len, const glm::vec3 &pos, float *h) const +{ + if (!path || !path_len) { + return DT_FAILURE; + } + + for (int i = 0; i < path_len; ++i) { + dtPolyRef ref = path[i]; + + if (dtStatusSucceed(GetPolyHeightNoConnections(ref, &pos[0], h))) { + return DT_SUCCESS; + } + } + + return DT_FAILURE; +} diff --git a/source/WorldServer/Zone/pathfinder_nav_mesh.h b/source/WorldServer/Zone/pathfinder_nav_mesh.h new file mode 100644 index 0000000..889144f --- /dev/null +++ b/source/WorldServer/Zone/pathfinder_nav_mesh.h @@ -0,0 +1,43 @@ +#pragma once + +#include "pathfinder_interface.h" +#include +#include + +static const int NAVMESHSET_MAGIC = 'M' << 24 | 'S' << 16 | 'E' << 8 | 'T'; //'MSET'; +static const int NAVMESHSET_VERSION = 1; + +struct NavMeshSetHeader +{ + int magic; + int version; + int numTiles; + dtNavMeshParams params; +}; + +struct NavMeshTileHeader +{ + dtTileRef tileRef; + int dataSize; +}; + +class PathfinderNavmesh : public IPathfinder +{ +public: + PathfinderNavmesh(const std::string &path); + virtual ~PathfinderNavmesh(); + + virtual IPath FindRoute(const glm::vec3 &start, const glm::vec3 &end, bool &partial, bool &stuck, int flags = PathingNotDisabled); + virtual IPath FindPath(const glm::vec3 &start, const glm::vec3 &end, bool &partial, bool &stuck, const PathfinderOptions& opts); + virtual glm::vec3 GetRandomLocation(const glm::vec3 &start); + +private: + void Clear(); + void Load(const std::string &path); + void ShowPath(Client *c, const glm::vec3 &start, const glm::vec3 &end); + dtStatus GetPolyHeightNoConnections(dtPolyRef ref, const float *pos, float *height) const; + dtStatus GetPolyHeightOnPath(const dtPolyRef *path, const int path_len, const glm::vec3 &pos, float *h) const; + + struct Implementation; + std::unique_ptr m_impl; +}; diff --git a/source/WorldServer/Zone/pathfinder_null.cpp b/source/WorldServer/Zone/pathfinder_null.cpp new file mode 100644 index 0000000..35d0a13 --- /dev/null +++ b/source/WorldServer/Zone/pathfinder_null.cpp @@ -0,0 +1,26 @@ +#include "pathfinder_null.h" + +IPathfinder::IPath PathfinderNull::FindRoute(const glm::vec3 &start, const glm::vec3 &end, bool &partial, bool &stuck, int flags) +{ + partial = false; + stuck = false; + IPath ret; + ret.push_back(start); + ret.push_back(end); + return ret; +} + +IPathfinder::IPath PathfinderNull::FindPath(const glm::vec3 &start, const glm::vec3 &end, bool &partial, bool &stuck, const PathfinderOptions &opts) +{ + partial = false; + stuck = false; + IPath ret; + ret.push_back(start); + ret.push_back(end); + return ret; +} + +glm::vec3 PathfinderNull::GetRandomLocation(const glm::vec3 &start) +{ + return glm::vec3(0.0f); +} diff --git a/source/WorldServer/Zone/pathfinder_null.h b/source/WorldServer/Zone/pathfinder_null.h new file mode 100644 index 0000000..f451fd4 --- /dev/null +++ b/source/WorldServer/Zone/pathfinder_null.h @@ -0,0 +1,14 @@ +#pragma once + +#include "pathfinder_interface.h" + +class PathfinderNull : public IPathfinder +{ +public: + PathfinderNull() { } + virtual ~PathfinderNull() { } + + virtual IPath FindRoute(const glm::vec3 &start, const glm::vec3 &end, bool &partial, bool &stuck, int flags = PathingNotDisabled); + virtual IPath FindPath(const glm::vec3 &start, const glm::vec3 &end, bool &partial, bool &stuck, const PathfinderOptions& opts); + virtual glm::vec3 GetRandomLocation(const glm::vec3 &start); +}; diff --git a/source/WorldServer/Zone/pathfinder_waypoint.cpp b/source/WorldServer/Zone/pathfinder_waypoint.cpp new file mode 100644 index 0000000..56dab85 --- /dev/null +++ b/source/WorldServer/Zone/pathfinder_waypoint.cpp @@ -0,0 +1,517 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pathfinder_waypoint.h" +#include "../zoneserver.h" +#include "../client.h" + +#pragma pack(1) +struct NeighbourNode { + int16 id; + float distance; + uint8 Teleport; + int16 DoorID; +}; + +struct PathNode { + uint16 id; + glm::vec3 v; + float bestz; + NeighbourNode Neighbours[50]; +}; + +struct PathFileHeader { + uint32 version; + uint32 PathNodeCount; +}; +#pragma pack() + +struct Edge +{ + float distance; + bool teleport; + int door_id; +}; + +struct Node +{ + int id; + glm::vec3 v; + float bestz; + std::map edges; +}; + +template +class distance_heuristic : public boost::astar_heuristic +{ +public: + typedef typename boost::graph_traits::vertex_descriptor Vertex; + + distance_heuristic(NodeMap n, Vertex goal) + : m_node(n), m_goal(goal) {} + CostType operator()(Vertex u) + { + CostType dx = m_node[m_goal].v.x - m_node[u].v.x; + CostType dy = m_node[m_goal].v.y - m_node[u].v.y; + CostType dz = m_node[m_goal].v.z - m_node[u].v.z; + return ::sqrt(dx * dx + dy * dy + dz * dz); + } +private: + NodeMap m_node; + Vertex m_goal; +}; + +struct found_goal {}; +template +class astar_goal_visitor : public boost::default_astar_visitor +{ +public: + astar_goal_visitor(Vertex goal) : m_goal(goal) {} + template + void examine_vertex(Vertex u, Graph& g) { + if (u == m_goal) + throw found_goal(); + } +private: + Vertex m_goal; +}; + +typedef boost::geometry::model::point Point; +typedef std::pair RTreeValue; +typedef boost::adjacency_list> GraphType; +typedef boost::property_map::type WeightMap; + +struct PathfinderWaypoint::Implementation { + bool PathFileValid; + boost::geometry::index::rtree> Tree; + GraphType Graph; + std::vector Nodes; + std::string FileName; +}; + +PathfinderWaypoint::PathfinderWaypoint(const std::string &path) +{ + m_impl.reset(new Implementation()); + m_impl->PathFileValid = false; + m_impl->FileName = path; + Load(path); +} + +PathfinderWaypoint::~PathfinderWaypoint() +{ +} + +IPathfinder::IPath PathfinderWaypoint::FindRoute(const glm::vec3 &start, const glm::vec3 &end, bool &partial, bool &stuck, int flags) +{ + stuck = false; + partial = false; + std::vector result_start_n; + m_impl->Tree.query(boost::geometry::index::nearest(Point(start.x, start.y, start.z), 1), std::back_inserter(result_start_n)); + if (result_start_n.size() == 0) { + return IPath(); + } + + std::vector result_end_n; + m_impl->Tree.query(boost::geometry::index::nearest(Point(end.x, end.y, end.z), 1), std::back_inserter(result_end_n)); + if (result_end_n.size() == 0) { + return IPath(); + } + + auto &nearest_start = *result_start_n.begin(); + auto &nearest_end = *result_end_n.begin(); + + if (nearest_start.second == nearest_end.second) { + IPath Route; + Route.push_back(start); + Route.push_back(end); + return Route; + } + + std::vector p(boost::num_vertices(m_impl->Graph)); + try { + boost::astar_search(m_impl->Graph, nearest_start.second, + distance_heuristic(&m_impl->Nodes[0], nearest_end.second), + boost::predecessor_map(&p[0]) + .visitor(astar_goal_visitor(nearest_end.second))); + } + catch (found_goal) + { + IPath Route; + + Route.push_front(end); + for (size_t v = nearest_end.second;; v = p[v]) { + if (p[v] == v) { + Route.push_front(m_impl->Nodes[v].v); + break; + } + else { + auto &node = m_impl->Nodes[v]; + + auto iter = node.edges.find((int)p[v + 1]); + if (iter != node.edges.end()) { + auto &edge = iter->second; + if (edge.teleport) { + Route.push_front(m_impl->Nodes[v].v); + Route.push_front(true); + } + else { + Route.push_front(m_impl->Nodes[v].v); + } + } + else { + Route.push_front(m_impl->Nodes[v].v); + } + } + } + + Route.push_front(start); + return Route; + } + + return IPath(); +} + +glm::vec3 PathfinderWaypoint::GetRandomLocation(const glm::vec3 &start) +{ + if (m_impl->Nodes.size() > 0) { + auto idx = MakeRandomInt(0, (int)m_impl->Nodes.size() - 1);// zone->random.Int(0, (int)m_impl->Nodes.size() - 1); + auto &node = m_impl->Nodes[idx]; + + return node.v; + } + + return glm::vec3(); +} + +void PathfinderWaypoint::Load(const std::string &filename) { + PathFileHeader Head; + Head.PathNodeCount = 0; + Head.version = 2; + + FILE *f = fopen(filename.c_str(), "rb"); + if (f) { + char Magic[10]; + + fread(&Magic, 9, 1, f); + + if (strncmp(Magic, "EQEMUPATH", 9)) + { + //LogError("Bad Magic String in .path file"); + fclose(f); + return; + } + + fread(&Head, sizeof(Head), 1, f); + + /*LogInfo("Path File Header: Version [{}], PathNodes [{}]", + (long)Head.version, (long)Head.PathNodeCount); + */ + if (Head.version == 2) + { + LoadV2(f, Head); + return; + } + else if (Head.version == 3) { + LoadV3(f, Head); + return; + } + else { + //LogError("Unsupported path file version"); + fclose(f); + return; + } + } +} + +void PathfinderWaypoint::LoadV2(FILE *f, const PathFileHeader &header) +{ + std::unique_ptr PathNodes(new PathNode[header.PathNodeCount]); + + fread(PathNodes.get(), sizeof(PathNode), header.PathNodeCount, f); + + int MaxNodeID = header.PathNodeCount - 1; + + m_impl->PathFileValid = true; + + m_impl->Nodes.reserve(header.PathNodeCount); + for (uint32 i = 0; i < header.PathNodeCount; ++i) + { + auto &n = PathNodes[i]; + Node node; + node.id = i; + node.v = n.v; + node.bestz = n.bestz; + m_impl->Nodes.push_back(node); + } + + auto weightmap = boost::get(boost::edge_weight, m_impl->Graph); + for (uint32 i = 0; i < header.PathNodeCount; ++i) { + for (uint32 j = 0; j < 50; ++j) + { + auto &node = m_impl->Nodes[i]; + if (PathNodes[i].Neighbours[j].id > MaxNodeID) + { + /*LogError("Path Node [{}], Neighbour [{}] ([{}]) out of range", i, j, PathNodes[i].Neighbours[j].id); + m_impl->PathFileValid = false;*/ + } + + if (PathNodes[i].Neighbours[j].id > 0) { + Edge edge; + edge.distance = PathNodes[i].Neighbours[j].distance; + edge.door_id = PathNodes[i].Neighbours[j].DoorID; + edge.teleport = PathNodes[i].Neighbours[j].Teleport; + + node.edges[PathNodes[i].Neighbours[j].id] = edge; + } + } + } + + BuildGraph(); + fclose(f); +} + +void PathfinderWaypoint::LoadV3(FILE *f, const PathFileHeader &header) +{ + m_impl->Nodes.reserve(header.PathNodeCount); + + uint32 edge_count = 0; + fread(&edge_count, sizeof(uint32), 1, f); + + for (uint32 i = 0; i < header.PathNodeCount; ++i) + { + uint32 id = 0; + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; + float best_z = 0.0f; + + fread(&id, sizeof(uint32), 1, f); + fread(&x, sizeof(float), 1, f); + fread(&y, sizeof(float), 1, f); + fread(&z, sizeof(float), 1, f); + fread(&best_z, sizeof(float), 1, f); + + Node n; + n.id = id; + n.bestz = best_z; + n.v.x = x; + n.v.y = y; + n.v.z = z; + + m_impl->Nodes.push_back(n); + } + + for (uint32 j = 0; j < edge_count; ++j) { + uint32 from = 0; + uint32 to = 0; + int8 teleport = 0; + float distance = 0.0f; + int32 door_id = 0; + + fread(&from, sizeof(uint32), 1, f); + fread(&to, sizeof(uint32), 1, f); + fread(&teleport, sizeof(int8), 1, f); + fread(&distance, sizeof(float), 1, f); + fread(&door_id, sizeof(int32), 1, f); + + Edge e; + e.teleport = teleport > 0 ? true : false; + e.distance = distance; + e.door_id = door_id; + + auto &n = m_impl->Nodes[from]; + n.edges[to] = e; + } + + m_impl->PathFileValid = true; + + BuildGraph(); + fclose(f); +} + +void PathfinderWaypoint::ShowNodes() +{ + for (size_t i = 0; i < m_impl->Nodes.size(); ++i) + { + ShowNode(m_impl->Nodes[i]); + } +} + +void PathfinderWaypoint::ShowPath(Client *c, const glm::vec3 &start, const glm::vec3 &end) +{ + bool partial = false; + bool stuck = false; + auto path = FindRoute(start, end, partial, stuck); + std::vector points; + + FindPerson_Point p; + for (auto &node : path) + { + if (!node.teleport) { + p.x = node.pos.x; + p.y = node.pos.y; + p.z = node.pos.z; + + points.push_back(p); + } + } + +// c->SendPathPacket(points); +} + +void PathfinderWaypoint::NodeInfo(Client *c) +{ + if (!c->GetPlayer()->GetTarget()) { + return; + } + + auto node = FindPathNodeByCoordinates(c->GetPlayer()->GetTarget()->GetX(), c->GetPlayer()->GetTarget()->GetZ(), c->GetPlayer()->GetTarget()->GetY()); + if (node == nullptr) { + return; + } + +/* c->Message(Chat::White, "Pathing node: %i at (%.2f, %.2f, %.2f) with bestz %.2f", + node->id, node->v.x, node->v.y, node->v.z, node->bestz); + + for (auto &edge : node->edges) { + c->Message(Chat::White, "id: %i, distance: %.2f, door id: %i, is teleport: %i", + edge.first, + edge.second.distance, + edge.second.door_id, + edge.second.teleport); + }*/ +} + +Node *PathfinderWaypoint::FindPathNodeByCoordinates(float x, float y, float z) +{ + for (auto &node : m_impl->Nodes) { + auto dist = Distance(glm::vec3(x, y, z), node.v); + + if (dist < 0.1) { + return &node; + } + } + + return nullptr; +} + +void PathfinderWaypoint::BuildGraph() +{ + m_impl->Graph = GraphType(); + m_impl->Tree = boost::geometry::index::rtree>(); + + for (auto &node : m_impl->Nodes) { + RTreeValue rtv; + rtv.first = Point(node.v.x, node.v.y, node.v.z); + rtv.second = node.id; + m_impl->Tree.insert(rtv); + boost::add_vertex(m_impl->Graph); + } + + //Populate edges now that we've created all the nodes + auto weightmap = boost::get(boost::edge_weight, m_impl->Graph); + for (auto &node : m_impl->Nodes) { + for (auto &edge : node.edges) { + GraphType::edge_descriptor e; + bool inserted; + boost::tie(e, inserted) = boost::add_edge(node.id, edge.first, m_impl->Graph); + weightmap[e] = edge.second.distance; + } + } +} + +std::string DigitToWord(int i) +{ + std::string digit = std::to_string(i); + std::string ret; + for (size_t idx = 0; idx < digit.length(); ++idx) { + if (!ret.empty()) { + ret += "_"; + } + + switch (digit[idx]) { + case '0': + ret += "Zero"; + break; + case '1': + ret += "One"; + break; + case '2': + ret += "Two"; + break; + case '3': + ret += "Three"; + break; + case '4': + ret += "Four"; + break; + case '5': + ret += "Five"; + break; + case '6': + ret += "Six"; + break; + case '7': + ret += "Seven"; + break; + case '8': + ret += "Eight"; + break; + case '9': + ret += "Nine"; + break; + default: + break; + } + } + + return ret; +} + +void PathfinderWaypoint::ShowNode(const Node &n) { +/* auto npc_type = new NPCType; + memset(npc_type, 0, sizeof(NPCType)); + + sprintf(npc_type->name, "%s", DigitToWord(n.id).c_str()); + sprintf(npc_type->lastname, "%i", n.id); + npc_type->current_hp = 4000000; + npc_type->max_hp = 4000000; + npc_type->race = 2254; + npc_type->gender = 2; + npc_type->class_ = 9; + npc_type->deity = 1; + npc_type->level = 75; + npc_type->npc_id = 0; + npc_type->loottable_id = 0; + npc_type->texture = 1; + npc_type->light = 0; + npc_type->runspeed = 0; + npc_type->d_melee_texture1 = 1; + npc_type->d_melee_texture2 = 1; + npc_type->merchanttype = 1; + npc_type->bodytype = 1; + npc_type->show_name = true; + + npc_type->STR = 150; + npc_type->STA = 150; + npc_type->DEX = 150; + npc_type->AGI = 150; + npc_type->INT = 150; + npc_type->WIS = 150; + npc_type->CHA = 150; + + npc_type->findable = 1; + auto position = glm::vec4(n.v.x, n.v.y, n.v.z, 0.0f); + auto npc = new NPC(npc_type, nullptr, position, GravityBehavior::Flying); + npc->GiveNPCTypeData(npc_type); + + entity_list.AddNPC(npc, true, true);*/ +} diff --git a/source/WorldServer/Zone/pathfinder_waypoint.h b/source/WorldServer/Zone/pathfinder_waypoint.h new file mode 100644 index 0000000..0bca53a --- /dev/null +++ b/source/WorldServer/Zone/pathfinder_waypoint.h @@ -0,0 +1,36 @@ +#pragma once + +#include "pathfinder_interface.h" + +struct PathFileHeader; +struct Node; + +struct FindPerson_Point { + float y; + float x; + float z; +}; + +class PathfinderWaypoint : public IPathfinder +{ +public: + PathfinderWaypoint(const std::string &path); + virtual ~PathfinderWaypoint(); + + virtual IPath FindRoute(const glm::vec3 &start, const glm::vec3 &end, bool &partial, bool &stuck, int flags = PathingNotDisabled); + virtual glm::vec3 GetRandomLocation(const glm::vec3 &start); + +private: + void Load(const std::string &filename); + void LoadV2(FILE *f, const PathFileHeader &header); + void LoadV3(FILE *f, const PathFileHeader &header); + void ShowNodes(); + void ShowPath(Client *c, const glm::vec3 &start, const glm::vec3 &end); + void NodeInfo(Client *c); + Node *FindPathNodeByCoordinates(float x, float y, float z); + void BuildGraph(); + void ShowNode(const Node &n); + + struct Implementation; + std::unique_ptr m_impl; +}; diff --git a/source/WorldServer/Zone/position.cpp b/source/WorldServer/Zone/position.cpp new file mode 100644 index 0000000..dfaf2fe --- /dev/null +++ b/source/WorldServer/Zone/position.cpp @@ -0,0 +1,255 @@ +#include "position.h" + +#include +#include +#include +#include "../../common/string_util.h" + +static const float position_eps = 0.0001f; + +std::string to_string(const glm::vec4 &position) { + return StringFormat("(%.3f, %.3f, %.3f, %.3f)", position.x,position.y,position.z,position.w); +} + +std::string to_string(const glm::vec3 &position){ + return StringFormat("(%.3f, %.3f, %.3f)", position.x,position.y,position.z); +} + +std::string to_string(const glm::vec2 &position){ + return StringFormat("(%.3f, %.3f)", position.x,position.y); +} + +bool IsOrigin(const glm::vec2 &position) { + return glm::dot(position, position) == 0; +} + +bool IsOrigin(const glm::vec3 &position) { + return glm::dot(position, position) == 0; +} + +bool IsOrigin(const glm::vec4 &position) { + return IsOrigin(glm::vec3(position)); +} + +/** +* Produces the non square root'ed distance between the two points within the XY plane. +*/ +float DistanceSquared(const glm::vec2& point1, const glm::vec2& point2) { + auto diff = point1 - point2; + return glm::dot(diff, diff); +} + +/** +* Produces the distance between the two points on the XY plane. +*/ +float Distance(const glm::vec2& point1, const glm::vec2& point2) { + return std::sqrt(DistanceSquared(point1, point2)); +} + +/** +* Produces the non square root'ed distance between the two points. +*/ +float DistanceSquared(const glm::vec3& point1, const glm::vec3& point2) { + auto diff = point1 - point2; + return glm::dot(diff, diff); +} + +/** +* Produces the non square root'ed distance between the two points. +*/ +float DistanceSquared(const glm::vec4& point1, const glm::vec4& point2) { + return DistanceSquared(static_cast(point1), static_cast(point2)); +} + +/** +* Produces the distance between the two points. +*/ +float Distance(const glm::vec3& point1, const glm::vec3& point2) { + return std::sqrt(DistanceSquared(point1, point2)); +} + +/** +* Produces the distance between the two points. +*/ +float Distance(const glm::vec4& point1, const glm::vec4& point2) { + return Distance(static_cast(point1), static_cast(point2)); +} + +/** +* Produces the distance between the two points within the XY plane. +*/ +float DistanceNoZ(const glm::vec3& point1, const glm::vec3& point2) { + return Distance(static_cast(point1),static_cast(point2)); +} + +/** +* Produces the distance between the two points within the XY plane. +*/ +float DistanceNoZ(const glm::vec4& point1, const glm::vec4& point2) { + return Distance(static_cast(point1),static_cast(point2)); +} + +/** +* Produces the non square root'ed distance between the two points within the XY plane. +*/ +float DistanceSquaredNoZ(const glm::vec3& point1, const glm::vec3& point2) { + return DistanceSquared(static_cast(point1),static_cast(point2)); +} + +/** +* Produces the non square root'ed distance between the two points within the XY plane. +*/ +float DistanceSquaredNoZ(const glm::vec4& point1, const glm::vec4& point2) { + return DistanceSquared(static_cast(point1),static_cast(point2)); +} + +/** +* Determines if 'position' is within (inclusive) the axis aligned +* box (3 dimensional) formed from the points minimum and maximum. +*/ +bool IsWithinAxisAlignedBox(const glm::vec3 &position, const glm::vec3 &minimum, const glm::vec3 &maximum) { + auto actualMinimum = glm::vec3(std::min(minimum.x, maximum.x), std::min(minimum.y, maximum.y),std::min(minimum.z, maximum.z)); + auto actualMaximum = glm::vec3(std::max(minimum.x, maximum.x), std::max(minimum.y, maximum.y),std::max(minimum.z, maximum.z)); + + bool xcheck = position.x >= actualMinimum.x && position.x <= actualMaximum.x; + bool ycheck = position.y >= actualMinimum.y && position.y <= actualMaximum.y; + bool zcheck = position.z >= actualMinimum.z && position.z <= actualMaximum.z; + + return xcheck && ycheck && zcheck; +} + +/** +* Determines if 'position' is within (inclusive) the axis aligned +* box (2 dimensional) formed from the points minimum and maximum. +*/ +bool IsWithinAxisAlignedBox(const glm::vec2 &position, const glm::vec2 &minimum, const glm::vec2 &maximum) { + auto actualMinimum = glm::vec2(std::min(minimum.x, maximum.x), std::min(minimum.y, maximum.y)); + auto actualMaximum = glm::vec2(std::max(minimum.x, maximum.x), std::max(minimum.y, maximum.y)); + + bool xcheck = position.x >= actualMinimum.x && position.x <= actualMaximum.x; + bool ycheck = position.y >= actualMinimum.y && position.y <= actualMaximum.y; + + return xcheck && ycheck; +} + +/** +* Gives the heading directly 180 degrees from the +* current heading. +* Takes the EQfloat from the glm::vec4 and returns +* an EQFloat. +*/ +float GetReciprocalHeading(const glm::vec4& point1) { + return GetReciprocalHeading(point1.w); +} + +/** +* Gives the heading directly 180 degrees from the +* current heading. +* Takes an EQfloat and returns an EQFloat. +*/ +float GetReciprocalHeading(const float heading) +{ + float result = 0; + + // Convert to radians + float h = (heading / 512.0f) * 6.283184f; + + // Calculate the reciprocal heading in radians + result = h + 3.141592f; + + // Convert back to eq heading from radians + result = (result / 6.283184f) * 512.0f; + + return result; +} + +bool IsHeadingEqual(const float h1, const float h2) +{ + return std::abs(h2 - h1) < 0.01f; +} + +bool IsPositionEqual(const glm::vec2 &p1, const glm::vec2 &p2) +{ + return std::abs(p1.x - p2.x) < position_eps && std::abs(p1.y - p2.y) < position_eps; +} + +bool IsPositionEqual(const glm::vec3 &p1, const glm::vec3 &p2) +{ + return std::abs(p1.x - p2.x) < position_eps && std::abs(p1.y - p2.y) < position_eps && std::abs(p1.z - p2.z) < position_eps; +} + +bool IsPositionEqual(const glm::vec4 &p1, const glm::vec4 &p2) +{ + return std::abs(p1.x - p2.x) < position_eps && std::abs(p1.y - p2.y) < position_eps && std::abs(p1.z - p2.z) < position_eps; +} + +bool IsPositionEqualWithinCertainZ(const glm::vec3 &p1, const glm::vec3 &p2, float z_eps) { + return std::abs(p1.x - p2.x) < position_eps && std::abs(p1.y - p2.y) < position_eps && std::abs(p1.z - p2.z) < z_eps; +} + +bool IsPositionEqualWithinCertainZ(const glm::vec4 &p1, const glm::vec4 &p2, float z_eps) { + return std::abs(p1.x - p2.x) < position_eps && std::abs(p1.y - p2.y) < position_eps && std::abs(p1.z - p2.z) < z_eps; +} + +bool IsPositionWithinSimpleCylinder(const glm::vec3 &p1, const glm::vec3 &cylinder_center, float cylinder_radius, float cylinder_height) +{ + //If we're outside the height of cylinder then we're not in it (duh) + auto d = std::abs(p1.z - cylinder_center.z); + if (d > cylinder_height / 2.0) { + return false; + } + + glm::vec2 p1d(p1.x, p1.y); + glm::vec2 ccd(cylinder_center.x, cylinder_center.y); + + //If we're outside the radius of the cylinder then we're not in it (also duh) + d = Distance(p1d, ccd); + if (d > cylinder_radius) { + return false; + } + + return true; +} + +bool IsPositionWithinSimpleCylinder(const glm::vec4 &p1, const glm::vec4 &cylinder_center, float cylinder_radius, float cylinder_height) +{ + //If we're outside the height of cylinder then we're not in it (duh) + auto d = std::abs(p1.z - cylinder_center.z); + if (d > cylinder_height / 2.0) { + return false; + } + + glm::vec2 p1d(p1.x, p1.y); + glm::vec2 ccd(cylinder_center.x, cylinder_center.y); + + //If we're outside the radius of the cylinder then we're not in it (also duh) + d = Distance(p1d, ccd); + if (d > cylinder_radius) { + return false; + } + + return true; +} + +float CalculateHeadingAngleBetweenPositions(float x1, float y1, float x2, float y2) +{ + float y_diff = std::abs(y1 - y2); + float x_diff = std::abs(x1 - x2); + if (y_diff < 0.0000009999999974752427) + y_diff = 0.0000009999999974752427; + + float angle = atan2(x_diff, y_diff) * 180.0f * 0.3183099014828645f; // angle, nice "pi" + + // return the right thing based on relative quadrant + // I'm sure this could be improved for readability, but whatever + if (y1 >= y2) { + if (x2 >= x1) + return (90.0f - angle + 90.0f) * 511.5f * 0.0027777778f; + if (x2 <= x1) + return (angle + 180.0f) * 511.5f * 0.0027777778f; + } + if (y1 > y2 || x2 > x1) + return angle * 511.5f * 0.0027777778f; + else + return (90.0f - angle + 270.0f) * 511.5f * 0.0027777778f; +} diff --git a/source/WorldServer/Zone/position.h b/source/WorldServer/Zone/position.h new file mode 100644 index 0000000..3cd4bbe --- /dev/null +++ b/source/WorldServer/Zone/position.h @@ -0,0 +1,67 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.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 except by those people which sell it, which + are required to give you total support for your newly bought product; + 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 POSITION_H +#define POSITION_H + +#include +#include "../../depends/glm/vec2.hpp" + +#include "../../depends/glm/vec3.hpp" +#include "../../depends/glm/vec4.hpp" +#include "../../depends/glm/geometric.hpp" + +std::string to_string(const glm::vec4 &position); +std::string to_string(const glm::vec3 &position); +std::string to_string(const glm::vec2 &position); + +bool IsWithinAxisAlignedBox(const glm::vec3 &position, const glm::vec3 &minimum, const glm::vec3 &maximum); +bool IsWithinAxisAlignedBox(const glm::vec2 &position, const glm::vec2 &minimum, const glm::vec2 &maximum); + +bool IsOrigin(const glm::vec2 &position); +bool IsOrigin(const glm::vec3 &position); +bool IsOrigin(const glm::vec4 &position); + +float DistanceSquared(const glm::vec2& point1, const glm::vec2& point2); +float Distance(const glm::vec2& point1, const glm::vec2& point2); +float DistanceSquared(const glm::vec3& point1, const glm::vec3& point2); +float Distance(const glm::vec3& point1, const glm::vec3& point2); +float DistanceNoZ(const glm::vec3& point1, const glm::vec3& point2); +float DistanceSquaredNoZ(const glm::vec3& point1, const glm::vec3& point2); + +float DistanceSquared(const glm::vec4& point1, const glm::vec4& point2); +float Distance(const glm::vec4& point1, const glm::vec4& point2); +float DistanceNoZ(const glm::vec4& point1, const glm::vec4& point2); +float DistanceSquaredNoZ(const glm::vec4& point1, const glm::vec4& point2); + +float GetReciprocalHeading(const glm::vec4& point1); +float GetReciprocalHeading(const float heading); + +bool IsHeadingEqual(const float h1, const float h2); + +bool IsPositionEqual(const glm::vec2 &p1, const glm::vec2 &p2); +bool IsPositionEqual(const glm::vec3 &p1, const glm::vec3 &p2); +bool IsPositionEqual(const glm::vec4 &p1, const glm::vec4 &p2); +bool IsPositionEqualWithinCertainZ(const glm::vec3 &p1, const glm::vec3 &p2, float z_eps); +bool IsPositionEqualWithinCertainZ(const glm::vec4 &p1, const glm::vec4 &p2, float z_eps); + +bool IsPositionWithinSimpleCylinder(const glm::vec3 &p1, const glm::vec3 &cylinder_center, float cylinder_radius, float cylinder_height); +bool IsPositionWithinSimpleCylinder(const glm::vec4 &p1, const glm::vec4 &cylinder_center, float cylinder_radius, float cylinder_height); + +float CalculateHeadingAngleBetweenPositions(float x1, float y1, float x2, float y2); + +#endif diff --git a/source/WorldServer/Zone/raycast_mesh.cpp b/source/WorldServer/Zone/raycast_mesh.cpp new file mode 100644 index 0000000..ba2f698 --- /dev/null +++ b/source/WorldServer/Zone/raycast_mesh.cpp @@ -0,0 +1,973 @@ +#include "raycast_mesh.h" +#include +#include +#include +#include +#include +#include +#include + +// This code snippet allows you to create an axis aligned bounding volume tree for a triangle mesh so that you can do +// high-speed raycasting. +// +// There are much better implementations of this available on the internet. In particular I recommend that you use +// OPCODE written by Pierre Terdiman. +// @see: http://www.codercorner.com/Opcode.htm +// +// OPCODE does a whole lot more than just raycasting, and is a rather significant amount of source code. +// +// I am providing this code snippet for the use case where you *only* want to do quick and dirty optimized raycasting. +// I have not done performance testing between this version and OPCODE; so I don't know how much slower it is. However, +// anytime you switch to using a spatial data structure for raycasting, you increase your performance by orders and orders +// of magnitude; so this implementation should work fine for simple tools and utilities. +// +// It also serves as a nice sample for people who are trying to learn the algorithm of how to implement AABB trees. +// AABB = Axis Aligned Bounding Volume trees. +// +// http://www.cgal.org/Manual/3.5/doc_html/cgal_manual/AABB_tree/Chapter_main.html +// +// +// This code snippet was written by John W. Ratcliff on August 18, 2011 and released under the MIT. license. +// +// mailto:jratcliffscarab@gmail.com +// +// The official source can be found at: http://code.google.com/p/raycastmesh/ +// +// + +#pragma warning(disable:4100) + +namespace RAYCAST_MESH +{ + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/** +* A method to compute a ray-AABB intersection. +* Original code by Andrew Woo, from "Graphics Gems", Academic Press, 1990 +* Optimized code by Pierre Terdiman, 2000 (~20-30% faster on my Celeron 500) +* Epsilon value added by Klaus Hartmann. (discarding it saves a few cycles only) +* +* Hence this version is faster as well as more robust than the original one. +* +* Should work provided: +* 1) the integer representation of 0.0f is 0x00000000 +* 2) the sign bit of the RmReal is the most significant one +* +* Report bugs: p.terdiman@codercorner.com +* +* \param aabb [in] the axis-aligned bounding box +* \param origin [in] ray origin +* \param dir [in] ray direction +* \param coord [out] impact coordinates +* \return true if ray intersects AABB +*/ +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#define RAYAABB_EPSILON 0.00001f +//! Integer representation of a RmRealing-point value. +#define IR(x) ((RmUint32&)x) + +bool intersectRayAABB(const RmReal MinB[3],const RmReal MaxB[3],const RmReal origin[3],const RmReal dir[3],RmReal coord[3]) +{ + bool Inside = true; + RmReal MaxT[3]; + MaxT[0]=MaxT[1]=MaxT[2]=-1.0f; + + // Find candidate planes. + for(RmUint32 i=0;i<3;i++) + { + if(origin[i] < MinB[i]) + { + coord[i] = MinB[i]; + Inside = false; + + // Calculate T distances to candidate planes + if(IR(dir[i])) MaxT[i] = (MinB[i] - origin[i]) / dir[i]; + } + else if(origin[i] > MaxB[i]) + { + coord[i] = MaxB[i]; + Inside = false; + + // Calculate T distances to candidate planes + if(IR(dir[i])) MaxT[i] = (MaxB[i] - origin[i]) / dir[i]; + } + } + + // Ray origin inside bounding box + if(Inside) + { + coord[0] = origin[0]; + coord[1] = origin[1]; + coord[2] = origin[2]; + return true; + } + + // Get largest of the maxT's for final choice of intersection + RmUint32 WhichPlane = 0; + if(MaxT[1] > MaxT[WhichPlane]) WhichPlane = 1; + if(MaxT[2] > MaxT[WhichPlane]) WhichPlane = 2; + + // Check final candidate actually inside box + if(IR(MaxT[WhichPlane])&0x80000000) return false; + + for(RmUint32 i=0;i<3;i++) + { + if(i!=WhichPlane) + { + coord[i] = origin[i] + MaxT[WhichPlane] * dir[i]; + #ifdef RAYAABB_EPSILON + if(coord[i] < MinB[i] - RAYAABB_EPSILON || coord[i] > MaxB[i] + RAYAABB_EPSILON) return false; + #else + if(coord[i] < MinB[i] || coord[i] > MaxB[i]) return false; + #endif + } + } + return true; // ray hits box +} + + + + +bool intersectLineSegmentAABB(const RmReal bmin[3],const RmReal bmax[3],const RmReal p1[3],const RmReal dir[3],RmReal &dist,RmReal intersect[3]) +{ + bool ret = false; + + if ( dist > RAYAABB_EPSILON ) + { + ret = intersectRayAABB(bmin,bmax,p1,dir,intersect); + if ( ret ) + { + RmReal dx = p1[0]-intersect[0]; + RmReal dy = p1[1]-intersect[1]; + RmReal dz = p1[2]-intersect[2]; + RmReal d = dx*dx+dy*dy+dz*dz; + if ( d < dist*dist ) + { + dist = sqrtf(d); + } + else + { + ret = false; + } + } + } + return ret; +} + +/* a = b - c */ +#define vector(a,b,c) \ + (a)[0] = (b)[0] - (c)[0]; \ + (a)[1] = (b)[1] - (c)[1]; \ + (a)[2] = (b)[2] - (c)[2]; + +#define innerProduct(v,q) \ + ((v)[0] * (q)[0] + \ + (v)[1] * (q)[1] + \ + (v)[2] * (q)[2]) + +#define crossProduct(a,b,c) \ + (a)[0] = (b)[1] * (c)[2] - (c)[1] * (b)[2]; \ + (a)[1] = (b)[2] * (c)[0] - (c)[2] * (b)[0]; \ + (a)[2] = (b)[0] * (c)[1] - (c)[0] * (b)[1]; + + +static inline bool rayIntersectsTriangle(const RmReal *p,const RmReal *d,const RmReal *v0,const RmReal *v1,const RmReal *v2,RmReal &t) +{ + RmReal e1[3],e2[3],h[3],s[3],q[3]; + RmReal a,f,u,v; + + vector(e1,v1,v0); + vector(e2,v2,v0); + crossProduct(h,d,e2); + a = innerProduct(e1,h); + + if (a > -0.00001 && a < 0.00001) + return(false); + + f = 1/a; + vector(s,p,v0); + u = f * (innerProduct(s,h)); + + if (u < 0.0 || u > 1.0) + return(false); + + crossProduct(q,s,e1); + v = f * innerProduct(d,q); + if (v < 0.0 || u + v > 1.0) + return(false); + // at this stage we can compute t to find out where + // the intersection point is on the line + t = f * innerProduct(e2,q); + if (t > 0) // ray intersection + return(true); + else // this means that there is a line intersection + // but not a ray intersection + return (false); +} + +static RmReal computePlane(const RmReal *A,const RmReal *B,const RmReal *C,RmReal *n) // returns D +{ + RmReal vx = (B[0] - C[0]); + RmReal vy = (B[1] - C[1]); + RmReal vz = (B[2] - C[2]); + + RmReal wx = (A[0] - B[0]); + RmReal wy = (A[1] - B[1]); + RmReal wz = (A[2] - B[2]); + + RmReal vw_x = vy * wz - vz * wy; + RmReal vw_y = vz * wx - vx * wz; + RmReal vw_z = vx * wy - vy * wx; + + RmReal mag = sqrt((vw_x * vw_x) + (vw_y * vw_y) + (vw_z * vw_z)); + + if ( mag < 0.000001f ) + { + mag = 0; + } + else + { + mag = 1.0f/mag; + } + + RmReal x = vw_x * mag; + RmReal y = vw_y * mag; + RmReal z = vw_z * mag; + + + RmReal D = 0.0f - ((x*A[0])+(y*A[1])+(z*A[2])); + + n[0] = x; + n[1] = y; + n[2] = z; + + return D; +} + + +#define TRI_EOF 0xFFFFFFFF + +enum AxisAABB +{ + AABB_XAXIS, + AABB_YAXIS, + AABB_ZAXIS +}; + +enum ClipCode +{ + CC_MINX = (1<<0), + CC_MAXX = (1<<1), + CC_MINY = (1<<2), + CC_MAXY = (1<<3), + CC_MINZ = (1<<4), + CC_MAXZ = (1<<5), +}; + + +class BoundsAABB +{ +public: + + + void setMin(const RmReal *v) + { + mMin[0] = v[0]; + mMin[1] = v[1]; + mMin[2] = v[2]; + } + + void setMax(const RmReal *v) + { + mMax[0] = v[0]; + mMax[1] = v[1]; + mMax[2] = v[2]; + } + + void setMin(RmReal x,RmReal y,RmReal z) + { + mMin[0] = x; + mMin[1] = y; + mMin[2] = z; + } + + void setMax(RmReal x,RmReal y,RmReal z) + { + mMax[0] = x; + mMax[1] = y; + mMax[2] = z; + } + + void include(const RmReal *v) + { + if ( v[0] < mMin[0] ) mMin[0] = v[0]; + if ( v[1] < mMin[1] ) mMin[1] = v[1]; + if ( v[2] < mMin[2] ) mMin[2] = v[2]; + + if ( v[0] > mMax[0] ) mMax[0] = v[0]; + if ( v[1] > mMax[1] ) mMax[1] = v[1]; + if ( v[2] > mMax[2] ) mMax[2] = v[2]; + } + + void getCenter(RmReal *center) const + { + center[0] = (mMin[0]+mMax[0])*0.5f; + center[1] = (mMin[1]+mMax[1])*0.5f; + center[2] = (mMin[2]+mMax[2])*0.5f; + } + + bool intersects(const BoundsAABB &b) const + { + if ((mMin[0] > b.mMax[0]) || (b.mMin[0] > mMax[0])) return false; + if ((mMin[1] > b.mMax[1]) || (b.mMin[1] > mMax[1])) return false; + if ((mMin[2] > b.mMax[2]) || (b.mMin[2] > mMax[2])) return false; + return true; + } + + bool containsTriangle(const RmReal *p1,const RmReal *p2,const RmReal *p3) const + { + BoundsAABB b; + b.setMin(p1); + b.setMax(p1); + b.include(p2); + b.include(p3); + return intersects(b); + } + + bool containsTriangleExact(const RmReal *p1,const RmReal *p2,const RmReal *p3,RmUint32 &orCode) const + { + bool ret = false; + + RmUint32 andCode; + orCode = getClipCode(p1,p2,p3,andCode); + if ( andCode == 0 ) + { + ret = true; + } + + return ret; + } + + inline RmUint32 getClipCode(const RmReal *p1,const RmReal *p2,const RmReal *p3,RmUint32 &andCode) const + { + andCode = 0xFFFFFFFF; + RmUint32 c1 = getClipCode(p1); + RmUint32 c2 = getClipCode(p2); + RmUint32 c3 = getClipCode(p3); + andCode&=c1; + andCode&=c2; + andCode&=c3; + return c1|c2|c3; + } + + inline RmUint32 getClipCode(const RmReal *p) const + { + RmUint32 ret = 0; + + if ( p[0] < mMin[0] ) + { + ret|=CC_MINX; + } + else if ( p[0] > mMax[0] ) + { + ret|=CC_MAXX; + } + + if ( p[1] < mMin[1] ) + { + ret|=CC_MINY; + } + else if ( p[1] > mMax[1] ) + { + ret|=CC_MAXY; + } + + if ( p[2] < mMin[2] ) + { + ret|=CC_MINZ; + } + else if ( p[2] > mMax[2] ) + { + ret|=CC_MAXZ; + } + + return ret; + } + + inline void clamp(const BoundsAABB &aabb) + { + if ( mMin[0] < aabb.mMin[0] ) mMin[0] = aabb.mMin[0]; + if ( mMin[1] < aabb.mMin[1] ) mMin[1] = aabb.mMin[1]; + if ( mMin[2] < aabb.mMin[2] ) mMin[2] = aabb.mMin[2]; + if ( mMax[0] > aabb.mMax[0] ) mMax[0] = aabb.mMax[0]; + if ( mMax[1] > aabb.mMax[1] ) mMax[1] = aabb.mMax[1]; + if ( mMax[2] > aabb.mMax[2] ) mMax[2] = aabb.mMax[2]; + } + + RmReal mMin[3]; + RmReal mMax[3]; +}; + + +class NodeAABB; + +class NodeInterface +{ +public: + virtual NodeAABB * getNode(void) = 0; + virtual void getFaceNormal(RmUint32 tri,RmReal *faceNormal) = 0; +}; + + + + + + class NodeAABB + { + public: + NodeAABB(void) + { + mLeft = NULL; + mRight = NULL; + mLeafTriangleIndex= TRI_EOF; + } + + NodeAABB(RmUint32 vcount,const RmReal *vertices,RmUint32 tcount,RmUint32 *indices, + RmUint32 maxDepth, // Maximum recursion depth for the triangle mesh. + RmUint32 minLeafSize, // minimum triangles to treat as a 'leaf' node. + RmReal minAxisSize, + NodeInterface *callback, + TriVector &leafTriangles) // once a particular axis is less than this size, stop sub-dividing. + + { + mLeft = NULL; + mRight = NULL; + mLeafTriangleIndex = TRI_EOF; + TriVector triangles; + triangles.reserve(tcount); + for (RmUint32 i=0; i dx ) + { + axis = AABB_YAXIS; + laxis = dy; + } + + if ( dz > dx && dz > dy ) + { + axis = AABB_ZAXIS; + laxis = dz; + } + + RmUint32 count = triangles.size(); + + // if the number of triangles is less than the minimum allowed for a leaf node or... + // we have reached the maximum recursion depth or.. + // the width of the longest axis is less than the minimum axis size then... + // we create the leaf node and copy the triangles into the leaf node triangle array. + if ( count < minLeafSize || depth >= maxDepth || laxis < minAxisSize ) + { + // Copy the triangle indices into the leaf triangles array + mLeafTriangleIndex = leafTriangles.size(); // assign the array start location for these leaf triangles. + leafTriangles.push_back(count); + for (auto i = triangles.begin(); i != triangles.end(); ++i) { + RmUint32 tri = *i; + leafTriangles.push_back(tri); + } + } + else + { + RmReal center[3]; + mBounds.getCenter(center); + BoundsAABB b1,b2; + splitRect(axis,mBounds,b1,b2,center); + + // Compute two bounding boxes based upon the split of the longest axis + + BoundsAABB leftBounds,rightBounds; + + TriVector leftTriangles; + TriVector rightTriangles; + + + // Create two arrays; one of all triangles which intersect the 'left' half of the bounding volume node + // and another array that includes all triangles which intersect the 'right' half of the bounding volume node. + for (auto i = triangles.begin(); i != triangles.end(); ++i) { + + RmUint32 tri = (*i); + + { + RmUint32 i1 = indices[tri*3+0]; + RmUint32 i2 = indices[tri*3+1]; + RmUint32 i3 = indices[tri*3+2]; + + const RmReal *p1 = &vertices[i1*3]; + const RmReal *p2 = &vertices[i2*3]; + const RmReal *p3 = &vertices[i3*3]; + + RmUint32 addCount = 0; + RmUint32 orCode=0xFFFFFFFF; + if ( b1.containsTriangleExact(p1,p2,p3,orCode)) + { + addCount++; + if ( leftTriangles.empty() ) + { + leftBounds.setMin(p1); + leftBounds.setMax(p1); + } + leftBounds.include(p1); + leftBounds.include(p2); + leftBounds.include(p3); + leftTriangles.push_back(tri); // Add this triangle to the 'left triangles' array and revise the left triangles bounding volume + } + // if the orCode is zero; meaning the triangle was fully self-contiained int he left bounding box; then we don't need to test against the right + if ( orCode && b2.containsTriangleExact(p1,p2,p3,orCode)) + { + addCount++; + if ( rightTriangles.empty() ) + { + rightBounds.setMin(p1); + rightBounds.setMax(p1); + } + rightBounds.include(p1); + rightBounds.include(p2); + rightBounds.include(p3); + rightTriangles.push_back(tri); // Add this triangle to the 'right triangles' array and revise the right triangles bounding volume. + } + assert( addCount ); + } + } + + if ( !leftTriangles.empty() ) // If there are triangles in the left half then... + { + leftBounds.clamp(b1); // we have to clamp the bounding volume so it stays inside the parent volume. + mLeft = callback->getNode(); // get a new AABB node + new ( mLeft ) NodeAABB(leftBounds); // initialize it to default constructor values. + // Then recursively split this node. + mLeft->split(leftTriangles,vcount,vertices,tcount,indices,depth+1,maxDepth,minLeafSize,minAxisSize,callback,leafTriangles); + } + + if ( !rightTriangles.empty() ) // If there are triangles in the right half then.. + { + rightBounds.clamp(b2); // clamps the bounding volume so it stays restricted to the size of the parent volume. + mRight = callback->getNode(); // allocate and default initialize a new node + new ( mRight ) NodeAABB(rightBounds); + // Recursively split this node. + mRight->split(rightTriangles,vcount,vertices,tcount,indices,depth+1,maxDepth,minLeafSize,minAxisSize,callback,leafTriangles); + } + + } + } + + void splitRect(AxisAABB axis,const BoundsAABB &source,BoundsAABB &b1,BoundsAABB &b2,const RmReal *midpoint) + { + switch ( axis ) + { + case AABB_XAXIS: + { + b1.setMin( source.mMin ); + b1.setMax( midpoint[0], source.mMax[1], source.mMax[2] ); + + b2.setMin( midpoint[0], source.mMin[1], source.mMin[2] ); + b2.setMax(source.mMax); + } + break; + case AABB_YAXIS: + { + b1.setMin(source.mMin); + b1.setMax(source.mMax[0], midpoint[1], source.mMax[2]); + + b2.setMin(source.mMin[0], midpoint[1], source.mMin[2]); + b2.setMax(source.mMax); + } + break; + case AABB_ZAXIS: + { + b1.setMin(source.mMin); + b1.setMax(source.mMax[0], source.mMax[1], midpoint[2]); + + b2.setMin(source.mMin[0], source.mMin[1], midpoint[2]); + b2.setMax(source.mMax); + } + break; + } + } + + + virtual void raycast(bool &hit, + const RmReal *from, + const RmReal *to, + const RmReal *dir, + RmReal *hitLocation, + RmReal *hitNormal, + RmReal *hitDistance, + RmUint32 *GridID, + RmUint32 *WidgetID, + const RmReal *vertices, + const RmUint32 *indices, + const RmUint32 *grids, + const RmUint32 *widgets, + RmReal &nearestDistance, + NodeInterface *callback, + RmUint32 *raycastTriangles, + RmUint32 raycastFrame, + const TriVector &leafTriangles, + RmUint32 &nearestTriIndex, + RmMap* ignored_widgets) + { + RmReal sect[3]; + RmReal nd = nearestDistance; + if ( !intersectLineSegmentAABB(mBounds.mMin,mBounds.mMax,from,dir,nd,sect) ) + { + return; + } + if ( mLeafTriangleIndex != TRI_EOF ) + { + const RmUint32 *scan = &leafTriangles[mLeafTriangleIndex]; + RmUint32 count = *scan++; + for (RmUint32 i=0; ifind(widgets[tri]) != ignored_widgets->end()) { + continue; + } + + nearestDistance = t; + if ( hitLocation ) + { + hitLocation[0] = from[0]+dir[0]*t; + hitLocation[1] = from[1]+dir[1]*t; + hitLocation[2] = from[2]+dir[2]*t; + } + if ( hitNormal ) + { + callback->getFaceNormal(tri,hitNormal); + } + if ( hitDistance ) + { + *hitDistance = t; + } + if(GridID) { + *GridID = grids[tri]; + } + if(WidgetID) { + *WidgetID = widgets[tri]; + } + + nearestTriIndex = tri; + hit = true; + } + } + } + } + } + else + { + if ( mLeft ) + { + mLeft->raycast(hit,from,to,dir,hitLocation,hitNormal,hitDistance,GridID,WidgetID,vertices,indices,grids,widgets,nearestDistance,callback,raycastTriangles,raycastFrame,leafTriangles,nearestTriIndex,ignored_widgets); + } + if ( mRight ) + { + mRight->raycast(hit,from,to,dir,hitLocation,hitNormal,hitDistance,GridID,WidgetID,vertices,indices,grids,widgets,nearestDistance,callback,raycastTriangles,raycastFrame,leafTriangles,nearestTriIndex,ignored_widgets); + } + } + } + + NodeAABB *mLeft; // left node + NodeAABB *mRight; // right node + BoundsAABB mBounds; // bounding volume of node + RmUint32 mLeafTriangleIndex; // if it is a leaf node; then these are the triangle indices. + }; + +class MyRaycastMesh : public RaycastMesh, public NodeInterface +{ +public: + + MyRaycastMesh(RmUint32 vcount,const RmReal *vertices,RmUint32 tcount,const RmUint32 *indices,const RmUint32 *grids,const RmUint32 *widgets,RmUint32 maxDepth,RmUint32 minLeafSize,RmReal minAxisSize) + { + mRaycastFrame = 0; + if ( maxDepth < 2 ) + { + maxDepth = 2; + } + if ( maxDepth > 15 ) + { + maxDepth = 15; + } + RmUint32 pow2Table[16] = { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 65536 }; + mMaxNodeCount = 0; + for (RmUint32 i=0; i<=maxDepth; i++) + { + mMaxNodeCount+=pow2Table[i]; + } + mNodes = new NodeAABB[mMaxNodeCount]; + mNodeCount = 0; + mVcount = vcount; + mVertices = (RmReal *)::malloc(sizeof(RmReal)*3*vcount); + memcpy(mVertices,vertices,sizeof(RmReal)*3*vcount); + mTcount = tcount; + mIndices = (RmUint32 *)::malloc(sizeof(RmUint32)*tcount*3); + memcpy(mIndices,indices,sizeof(RmUint32)*tcount*3); + mRaycastTriangles = (RmUint32 *)::malloc(tcount*sizeof(RmUint32)); + memset(mRaycastTriangles,0,tcount*sizeof(RmUint32)); + mGrids = (RmUint32 *)::malloc(sizeof(RmUint32)*tcount); + memcpy(mGrids,grids,sizeof(RmUint32)*tcount); + mWidgets = (RmUint32 *)::malloc(sizeof(RmUint32)*tcount); + memcpy(mWidgets,widgets,sizeof(RmUint32)*tcount); + mRoot = getNode(); + mFaceNormals = NULL; + new ( mRoot ) NodeAABB(mVcount,mVertices,mTcount,mIndices,maxDepth,minLeafSize,minAxisSize,this,mLeafTriangles); + } + + ~MyRaycastMesh(void) + { + delete []mNodes; + ::free(mVertices); + ::free(mIndices); + ::free(mFaceNormals); + ::free(mRaycastTriangles); + ::free(mGrids); + ::free(mWidgets); + } + + virtual bool raycast(const RmReal *from,const RmReal *to,RmReal *hitLocation,RmReal *hitNormal,RmReal *hitDistance,RmUint32 *GridID,RmUint32 *WidgetID, RmMap* ignored_widgets) + { + bool ret = false; + + RmReal dir[3]; + dir[0] = to[0] - from[0]; + dir[1] = to[1] - from[1]; + dir[2] = to[2] - from[2]; + RmReal distance = sqrtf( dir[0]*dir[0] + dir[1]*dir[1]+dir[2]*dir[2] ); + if ( distance < 0.0000000001f ) return false; + RmReal recipDistance = 1.0f / distance; + dir[0]*=recipDistance; + dir[1]*=recipDistance; + dir[2]*=recipDistance; + mRaycastFrame++; + RmUint32 nearestTriIndex=TRI_EOF; + mRoot->raycast(ret,from,to,dir,hitLocation,hitNormal,hitDistance,GridID,WidgetID,mVertices,mIndices,mGrids,mWidgets,distance,this,mRaycastTriangles,mRaycastFrame,mLeafTriangles,nearestTriIndex,ignored_widgets); + return ret; + } + + virtual void release(void) + { + delete this; + } + + virtual const RmReal * getBoundMin(void) const // return the minimum bounding box + { + return mRoot->mBounds.mMin; + } + virtual const RmReal * getBoundMax(void) const // return the maximum bounding box. + { + return mRoot->mBounds.mMax; + } + + virtual NodeAABB * getNode(void) + { + assert( mNodeCount < mMaxNodeCount ); + NodeAABB *ret = &mNodes[mNodeCount]; + mNodeCount++; + return ret; + } + + virtual void getFaceNormal(RmUint32 tri,RmReal *faceNormal) + { + if ( mFaceNormals == NULL ) + { + mFaceNormals = (RmReal *)::malloc(sizeof(RmReal)*3*mTcount); + for (RmUint32 i=0; ifind(mWidgets[tri]) != ignored_widgets->end()) { + continue; + } + + nearestDistance = t; + if ( hitLocation ) + { + hitLocation[0] = from[0]+dir[0]*t; + hitLocation[1] = from[1]+dir[1]*t; + hitLocation[2] = from[2]+dir[2]*t; + } + + if ( hitNormal ) + { + getFaceNormal(tri,hitNormal); + } + + if ( hitDistance ) + { + *hitDistance = t; + } + + if(GridID) { + *GridID = mGrids[tri]; + } + if(WidgetID) { + *WidgetID = mWidgets[tri]; + } + + ret = true; + } + } + } + return ret; + } + + RmUint32 mRaycastFrame; + RmUint32 *mRaycastTriangles; + RmUint32 mVcount; + RmReal *mVertices; + RmReal *mFaceNormals; + RmUint32 mTcount; + RmUint32 *mIndices; + NodeAABB *mRoot; + RmUint32 mNodeCount; + RmUint32 mMaxNodeCount; + NodeAABB *mNodes; + TriVector mLeafTriangles; + RmUint32 *mGrids; + RmUint32 *mWidgets; +}; + +}; + + + +using namespace RAYCAST_MESH; + + +RaycastMesh * createRaycastMesh(RmUint32 vcount, // The number of vertices in the source triangle mesh + const RmReal *vertices, // The array of vertex positions in the format x1,y1,z1..x2,y2,z2.. etc. + RmUint32 tcount, // The number of triangles in the source triangle mesh + const RmUint32 *indices, // The triangle indices in the format of i1,i2,i3 ... i4,i5,i6, ... + const RmUint32 *grids, + const RmUint32 *widgets, + RmUint32 maxDepth, // Maximum recursion depth for the triangle mesh. + RmUint32 minLeafSize, // minimum triangles to treat as a 'leaf' node. + RmReal minAxisSize ) // once a particular axis is less than this size, stop sub-dividing. +{ + auto m = new MyRaycastMesh(vcount, vertices, tcount, indices, grids, widgets, maxDepth, minLeafSize, minAxisSize); + return static_cast< RaycastMesh * >(m); +} \ No newline at end of file diff --git a/source/WorldServer/Zone/raycast_mesh.h b/source/WorldServer/Zone/raycast_mesh.h new file mode 100644 index 0000000..1bfea8d --- /dev/null +++ b/source/WorldServer/Zone/raycast_mesh.h @@ -0,0 +1,73 @@ +#ifndef RAYCAST_MESH_H + +#define RAYCAST_MESH_H + +// This code snippet allows you to create an axis aligned bounding volume tree for a triangle mesh so that you can do +// high-speed raycasting. +// +// There are much better implementations of this available on the internet. In particular I recommend that you use +// OPCODE written by Pierre Terdiman. +// @see: http://www.codercorner.com/Opcode.htm +// +// OPCODE does a whole lot more than just raycasting, and is a rather significant amount of source code. +// +// I am providing this code snippet for the use case where you *only* want to do quick and dirty optimized raycasting. +// I have not done performance testing between this version and OPCODE; so I don't know how much slower it is. However, +// anytime you switch to using a spatial data structure for raycasting, you increase your performance by orders and orders +// of magnitude; so this implementation should work fine for simple tools and utilities. +// +// It also serves as a nice sample for people who are trying to learn the algorithm of how to implement AABB trees. +// AABB = Axis Aligned Bounding Volume trees. +// +// http://www.cgal.org/Manual/3.5/doc_html/cgal_manual/AABB_tree/Chapter_main.html +// +// +// This code snippet was written by John W. Ratcliff on August 18, 2011 and released under the MIT. license. +// +// mailto:jratcliffscarab@gmail.com +// +// The official source can be found at: http://code.google.com/p/raycastmesh/ +// +// + +typedef float RmReal; +typedef unsigned int RmUint32; +#include +#include +using namespace std; +typedef std::vector< RmUint32 > TriVector; +typedef std::map< unsigned int , bool> RmMap; + +class RaycastMesh +{ +public: + virtual bool raycast(const RmReal *from,const RmReal *to,RmReal *hitLocation,RmReal *hitNormal,RmReal *hitDistance, RmUint32 *GridID, RmUint32 *WidgetID, RmMap* ignored_widgets) = 0; + virtual bool bruteForceRaycast(const RmReal *from,const RmReal *to,RmReal *hitLocation,RmReal *hitNormal,RmReal *hitDistance, RmUint32 *GridID, RmUint32 *WidgetID, RmMap* ignored_widgets) = 0; + + virtual const RmReal * getBoundMin(void) const = 0; // return the minimum bounding box + virtual const RmReal * getBoundMax(void) const = 0; // return the maximum bounding box. + virtual void release(void) = 0; +protected: + virtual ~RaycastMesh(void) { }; +}; + + +RaycastMesh * createRaycastMesh(RmUint32 vcount, // The number of vertices in the source triangle mesh + const RmReal *vertices, // The array of vertex positions in the format x1,y1,z1..x2,y2,z2.. etc. + RmUint32 tcount, // The number of triangles in the source triangle mesh + const RmUint32 *indices, // The triangle indices in the format of i1,i2,i3 ... i4,i5,i6, ... + const RmUint32 *grids, + const RmUint32 *widgets, + RmUint32 maxDepth=15, // Maximum recursion depth for the triangle mesh. + RmUint32 minLeafSize=4, // minimum triangles to treat as a 'leaf' node. + RmReal minAxisSize=0.01f // once a particular axis is less than this size, stop sub-dividing. + ); + +#ifdef USE_MAP_MMFS +#include + +RaycastMesh* loadRaycastMesh(std::vector& rm_buffer, bool& load_success); +void serializeRaycastMesh(RaycastMesh* rm, std::vector& rm_buffer); +#endif /*USE_MAP_MMFS*/ + +#endif \ No newline at end of file diff --git a/source/WorldServer/Zone/region_map.cpp b/source/WorldServer/Zone/region_map.cpp new file mode 100644 index 0000000..981f050 --- /dev/null +++ b/source/WorldServer/Zone/region_map.cpp @@ -0,0 +1,125 @@ + + +#include "region_map.h" +#include "region_map_v1.h" +#include "../../common/Log.h" + + +#ifdef WIN32 +#define _snprintf snprintf +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @param name + * @return + */ +inline bool file_exists(const std::string& name) { + std::ifstream f(name.c_str()); + return f.good(); +} + +/** + * @param zone_name + * @return + */ +RegionMap* RegionMap::LoadRegionMapfile(std::string filename, std::string zone_name) { + std::string loadedFile = "Regions/"; + loadedFile += filename; + loadedFile += ".EQ2Region"; + FILE* f = fopen(loadedFile.c_str(), "rb"); + + LogWrite(REGION__DEBUG, 7, "Region", "Attempting load of %s", filename.c_str()); + + if (!f) + { + LogWrite(REGION__ERROR, 7, "Region", "Failed to load of %s", filename.c_str()); + return nullptr; + } + + // Read the string for the zone file name this was created for + int8 strSize; + char name[256]; + fread(&strSize, sizeof(int8), 1, f); + LogWrite(REGION__DEBUG, 7, "Region", "strSize = %u", strSize); + + size_t len = fread(&name, sizeof(char), strSize, f); + name[len] = '\0'; + LogWrite(REGION__DEBUG, 7, "Region", "name = %s", name); + + string inFileName(name); + boost::algorithm::to_lower(inFileName); + string zoneNameLwr(zone_name); + boost::algorithm::to_lower(zoneNameLwr); + + std::size_t found = inFileName.find(zoneNameLwr); + // Make sure file contents are for the correct zone + if (found == std::string::npos) { + fclose(f); + LogWrite(REGION__ERROR, 0, "Region", "RegionMap::LoadRegionMapfile() map contents (%s) do not match its name (%s).", inFileName, zoneNameLwr.c_str()); + return nullptr; + } + + int32 regionMapVersion; + fread(®ionMapVersion, sizeof(int32), 1, f); + LogWrite(REGION__INFO, 0, "Region", "Loading %s RegionMapVersion = %u", name, regionMapVersion); + + RegionMapV1* regionmap = new RegionMapV1(); + regionmap->Load(f, zoneNameLwr, regionMapVersion); + + return regionmap; +} + + +void RegionMapRange::AddVersionRange(std::string zoneName) { + boost::filesystem::path targetDir("Regions/"); + + // crash fix since the dir isn't present + if(!boost::filesystem::is_directory(targetDir)) + { + LogWrite(REGION__ERROR, 7, "Region", "Unable to find directory %s", targetDir.c_str()); + return; + } + + boost::filesystem::recursive_directory_iterator iter(targetDir), eod; + boost::smatch base_match; + std::string formula = "(.*\\/|.*\\\\)((" + zoneName + ")(\\-([0-9]+)\\-([0-9]+))?)\\.EQ2Region$"; + boost::regex re(formula.c_str()); + LogWrite(REGION__INFO, 0, "Region", "Region Formula to match: %s\n", formula.c_str()); + + BOOST_FOREACH(boost::filesystem::path + const & i, make_pair(iter, eod)) { + if (is_regular_file(i)) { + std::string fileName(i.string()); + if (boost::regex_match(fileName, base_match, re)) { + boost::ssub_match base_sub_match = base_match[2]; + boost::ssub_match base_sub_match2 = base_match[5]; + boost::ssub_match base_sub_match3 = base_match[6]; + std::string baseMatch(base_sub_match.str().c_str()); + std::string baseMatch2(base_sub_match2.str().c_str()); + std::string baseMatch3(base_sub_match3.str().c_str()); + LogWrite(REGION__INFO, 0, "Region", "Region to Load: %s, size: %i, string: %s, min: %s, max: %s\n", fileName.c_str(), base_match.size(), baseMatch.c_str(), baseMatch2.c_str(), baseMatch3.c_str()); + RegionMap * regionmap = RegionMap::LoadRegionMapfile(base_sub_match.str().c_str(), zoneName); + + int32 min_version = 0, max_version = 0; + if (strlen(base_sub_match2.str().c_str()) > 0) + min_version = atoul(base_sub_match2.str().c_str()); + + if (strlen(base_sub_match2.str().c_str()) > 0) + max_version = atoul(base_sub_match3.str().c_str()); + version_map.insert(std::make_pair(new VersionRange(min_version, max_version), regionmap)); + } + } + } +} \ No newline at end of file diff --git a/source/WorldServer/Zone/region_map.h b/source/WorldServer/Zone/region_map.h new file mode 100644 index 0000000..e2414f9 --- /dev/null +++ b/source/WorldServer/Zone/region_map.h @@ -0,0 +1,130 @@ +#ifndef EQ2EMU_REGION_MAP_H +#define EQ2EMU_REGION_MAP_H + +#include "../../common/types.h" +#include "../../common/MiscFunctions.h" + +#include "position.h" +#include + +class Client; +class Spawn; +class ZoneServer; +class Region_Node; +class ZBSP_Node; + +enum WaterRegionType : int { + RegionTypeUnsupported = -2, + RegionTypeUntagged = -1, + RegionTypeNormal = 0, + RegionTypeWater = 1, + RegionTypeLava = 2, + RegionTypeZoneLine = 3, + RegionTypePVP = 4, + RegionTypeSlime = 5, + RegionTypeIce = 6, + RegionTypeVWater = 7 +}; + +enum WaterRegionClass : int32 { + ClassWaterVolume = 0, // matching .region file type by name "watervol" + ClassWaterRegion = 1, // matching .region file type by name "waterregion" + ClassWaterRegion2 = 2, // represents .region file name "water_region" potentially defunct and just a WaterVolume (0) + ClassWaterOcean = 3, // represents .region file with "ocean" and a select node as a parent + ClassWaterCavern = 4, // represents .region file with matches on name "ocean" and "water" + ClassWaterOcean2 = 5 // represents .region file with matches on name "ocean" without previous matches (no select node parent and no water string match) +}; + +class RegionMap +{ +public: + RegionMap() { } + virtual ~RegionMap() { } + + static RegionMap* LoadRegionMapfile(std::string filename, std::string zone_name); + virtual WaterRegionType ReturnRegionType(const glm::vec3& location, int32 gridid=0) const = 0; + virtual bool InWater(const glm::vec3& location, int32 gridid=0) const = 0; + virtual bool InLava(const glm::vec3& location, int32 gridid=0) const = 0; + virtual bool InLiquid(const glm::vec3& location) const = 0; + virtual bool InPvP(const glm::vec3& location) const = 0; + virtual bool InZoneLine(const glm::vec3& location) const = 0; + + virtual void IdentifyRegionsInGrid(Client* client, const glm::vec3& location) const = 0; + virtual void MapRegionsNearSpawn(Spawn* spawn, Client* client=0) const = 0; + virtual void UpdateRegionsNearSpawn(Spawn* spawn, Client* client=0) const = 0; + virtual void TicRegionsNearSpawn(Spawn* spawn, Client* client=0) const = 0; + + virtual void InsertRegionNode(ZoneServer* zone, int32 version, std::string regionName, std::string envName, uint32 gridID, uint32 triggerWidgetID, float dist = 0.0f) = 0; + virtual void RemoveRegionNode(std::string regionName) = 0; +protected: + virtual bool Load(FILE *fp) { return false; } +}; + + +class RegionMapRange { +public: + RegionMapRange() + { + + } + + ~RegionMapRange() + { + map::iterator itr; + for (itr = version_map.begin(); itr != version_map.end(); itr++) + { + VersionRange* range = itr->first; + RegionMap* map = itr->second; + delete range; + delete map; + } + + version_map.clear(); + } + + void AddVersionRange(std::string zoneName); + + map::iterator FindVersionRange(int32 min_version, int32 max_version) + { + map::iterator itr; + for (itr = version_map.begin(); itr != version_map.end(); itr++) + { + VersionRange* range = itr->first; + // if min and max version are both in range + if (range->GetMinVersion() <= min_version && max_version <= range->GetMaxVersion()) + return itr; + // if the min version is in range, but max range is 0 + else if (range->GetMinVersion() <= min_version && range->GetMaxVersion() == 0) + return itr; + // if min version is 0 and max_version has a cap + else if (range->GetMinVersion() == 0 && max_version <= range->GetMaxVersion()) + return itr; + } + + return version_map.end(); + } + + map::iterator FindRegionByVersion(int32 version) + { + map::iterator enditr = version_map.end(); + map::iterator itr; + for (itr = version_map.begin(); itr != version_map.end(); itr++) + { + VersionRange* range = itr->first; + // if min and max version are both in range + if(range->GetMinVersion() == 0 && range->GetMaxVersion() == 0) + enditr = itr; + else if (version >= range->GetMinVersion() && version <= range->GetMaxVersion()) + return itr; + } + + return enditr; + } + + map::iterator GetRangeEnd() { return version_map.end(); } +private: + std::map version_map; + string name; +}; + +#endif diff --git a/source/WorldServer/Zone/region_map_v1.cpp b/source/WorldServer/Zone/region_map_v1.cpp new file mode 100644 index 0000000..78dee76 --- /dev/null +++ b/source/WorldServer/Zone/region_map_v1.cpp @@ -0,0 +1,868 @@ +#include "region_map_v1.h" +#include "../../common/Log.h" +#include "../client.h" +#include "../Spawn.h" +#include "../LuaInterface.h" +#include "../World.h" + +#undef snprintf +#include + +extern LuaInterface* lua_interface; +extern World world; + +RegionMapV1::RegionMapV1() { + mVersion = 1; +} + +RegionMapV1::~RegionMapV1() { + std::unique_lock lock(MRegions); + map::const_iterator itr; + int region_num = 0; + for (itr = Regions.begin(); itr != Regions.end();) + { + Region_Node* node = itr->first; + ZBSP_Node* bsp_node = itr->second; + map::const_iterator deleteItr = itr; + itr++; + Regions.erase(deleteItr); + safe_delete(node); + safe_delete_array(bsp_node); + } + + Regions.clear(); +} + +WaterRegionType RegionMapV1::ReturnRegionType(const glm::vec3& location, int32 gridid) const { + return BSPReturnRegionType(1, glm::vec3(location.x, location.y, location.z), gridid); +} + +bool RegionMapV1::InWater(const glm::vec3& location, int32 gridid) const { + return ReturnRegionType(location, gridid) == RegionTypeWater; +} + +bool RegionMapV1::InLava(const glm::vec3& location, int32 gridid) const { + return ReturnRegionType(location, gridid) == RegionTypeLava; +} + +bool RegionMapV1::InLiquid(const glm::vec3& location) const { + return InWater(location) || InLava(location); +} + +bool RegionMapV1::InPvP(const glm::vec3& location) const { + return ReturnRegionType(location) == RegionTypePVP; +} + +bool RegionMapV1::InZoneLine(const glm::vec3& location) const { + return ReturnRegionType(location) == RegionTypeZoneLine; +} + +std::string RegionMapV1::TestFile(std::string testFile) +{ + std::string tmpStr(testFile); + std::size_t pos = tmpStr.find("."); + if ( pos != testFile.npos ) + tmpStr = testFile.substr (0, pos); + + string tmpScript("RegionScripts/"); + tmpScript.append(mZoneNameLower); + tmpScript.append("/" + tmpStr + ".lua"); + std::ifstream f(tmpScript.c_str()); + return f.good() ? tmpScript : string(""); +} + + +bool RegionMapV1::Load(FILE* fp, std::string inZoneNameLwr, int32 version) { + mZoneNameLower = string(inZoneNameLwr.c_str()); + + uint32 region_size; + if (fread(®ion_size, sizeof(uint32), 1, fp) != 1) { + return false; + } + + LogWrite(REGION__DEBUG, 0, "RegionMap", "region count = %u", region_size); + + for (int i = 0; i < region_size; i++) + { + uint32 region_num; + if (fread(®ion_num, sizeof(uint32), 1, fp) != 1) { + return false; + } + + uint32 region_type; + if (fread(®ion_type, sizeof(uint32), 1, fp) != 1) { + return false; + } + + float x, y, z, dist; + if (fread(&x, sizeof(float), 1, fp) != 1) { + return false; + } + if (fread(&y, sizeof(float), 1, fp) != 1) { + return false; + } + if (fread(&z, sizeof(float), 1, fp) != 1) { + return false; + } + if (fread(&dist, sizeof(float), 1, fp) != 1) { + return false; + } + + int8 strSize; + char envName[256] = {""}; + char regionName[256] = {""}; + uint32 grid_id = 0; + + if ( version > 1 ) + { + fread(&strSize, sizeof(int8), 1, fp); + LogWrite(REGION__DEBUG, 7, "Region", "Region environment strSize = %u", strSize); + + if(strSize) + { + size_t len = fread(&envName, sizeof(char), strSize, fp); + envName[len] = '\0'; + } + + LogWrite(REGION__DEBUG, 7, "Region", "Region environment file name = %s", envName); + + fread(&strSize, sizeof(int8), 1, fp); + LogWrite(REGION__DEBUG, 7, "Region", "Region name strSize = %u", strSize); + + if(strSize) + { + size_t len = fread(®ionName, sizeof(char), strSize, fp); + regionName[len] = '\0'; + } + + LogWrite(REGION__DEBUG, 7, "Region", "Region name file name = %s", regionName); + + if (fread(&grid_id, sizeof(uint32), 1, fp) != 1) { + return false; + } + + } + + int32 bsp_tree_size; + if (fread(&bsp_tree_size, sizeof(int32), 1, fp) != 1) { + return false; + } + + LogWrite(REGION__DEBUG, 7, "Region", "region x,y,z,dist = %f, %f, %f, %f, region bsp tree size: %i\n", x, y, z, dist, bsp_tree_size); + + ZBSP_Node* BSP_Root = new ZBSP_Node[bsp_tree_size]; + if (fread(BSP_Root, sizeof(ZBSP_Node), bsp_tree_size, fp) != bsp_tree_size) { + LogWrite(REGION__ERROR, 0, "RegionMap", "Failed to load region."); + return false; + } + + Region_Node* tmpNode = new Region_Node; + tmpNode->x = x; + tmpNode->y = y; + tmpNode->z = z; + tmpNode->dist = dist; + tmpNode->region_type = region_type; + tmpNode->regionName = string(regionName); + tmpNode->regionEnvFileName = string(envName); + tmpNode->grid_id = grid_id; + tmpNode->regionScriptName = string(""); + + tmpNode->regionScriptName = TestFile(regionName); + + if ( tmpNode->regionScriptName.size() < 1 ) + { + tmpNode->regionScriptName = TestFile(envName); + } + if ( tmpNode->regionScriptName.size() < 1 ) + { + tmpNode->regionScriptName = TestFile("default"); + } + + tmpNode->vert_count = bsp_tree_size; + + MRegions.lock(); + Regions.insert(make_pair(tmpNode, BSP_Root)); + MRegions.unlock(); + } + + fclose(fp); + + LogWrite(REGION__DEBUG, 0, "RegionMap", "completed load!"); + + return true; +} + +void RegionMapV1::IdentifyRegionsInGrid(Client *client, const glm::vec3 &location) const +{ + std::shared_lock lock(MRegions); + map::const_iterator itr; + int region_num = 0; + + int32 grid = 0; + int32 widget_id = 0; + float x =0.0f,y = 0.0f,z = 0.0f; + if (client->GetPlayer()->GetMap() != nullptr && client->GetPlayer()->GetMap()->IsMapLoaded()) + { + auto loc = glm::vec3(location.x, location.z, location.y); + + float new_z = client->GetPlayer()->FindBestZ(loc, nullptr, &grid, &widget_id); + + std::map::iterator itr = client->GetPlayer()->GetMap()->widget_map.find(widget_id); + if(itr != client->GetPlayer()->GetMap()->widget_map.end()) { + x = itr->second.x; + y = itr->second.y; + z = itr->second.z; + } + } + else + client->SimpleMessage(CHANNEL_COLOR_RED, "No map to establish grid id, using grid id 0 (attempt match all)."); + + client->Message(2, "Region check against location %f / %f / %f. Grid to try: %u, player grid is %u, widget id is %u. Widget location is %f %f %f.", location.x, location.y, location.z, grid, client->GetPlayer()->GetLocation(), widget_id, x, y, z); + for (itr = Regions.begin(); itr != Regions.end(); itr++) + { + Region_Node *node = itr->first; + ZBSP_Node *BSP_Root = itr->second; + + if (grid == 0 || node->grid_id == grid) + { + float x1 = node->x - location.x; + float y1 = node->y - location.y; + float z1 = node->z - location.z; + float dist = sqrt(x1 *x1 + y1 *y1 + z1 *z1); + glm::vec3 testLoc(location.x, location.y, location.z); + if(!BSP_Root) { + if(client) + client->Message(CHANNEL_COLOR_YELLOW, "[%s] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, Script: %s. X: %f, Y: %f, Z: %f, Distance: %f, Widget ID Marker: %u", (widget_id == node->trigger_widget_id) ? "IN REGION" : "WIDGET MARKER", region_num, + node->grid_id, node->regionName.c_str(), node->regionEnvFileName.c_str(), node->regionScriptName.c_str(), node->x, node->y, node->z, node->dist, node->trigger_widget_id); + } + else if (dist <= node->dist) + { + WaterRegionType regionType = RegionTypeUntagged; + + if (node->region_type == ClassWaterRegion) + regionType = BSPReturnRegionWaterRegion(node, BSP_Root, 1, testLoc, dist); + else + regionType = BSPReturnRegionTypeNode(node, BSP_Root, 1, testLoc, dist); + + if (regionType != RegionTypeNormal) + { + client->Message(CHANNEL_COLOR_YELLOW, "[DETECTED IN REGION %i] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, distance: %f, X/Y/Z: %f / %f / %f. Script: %s", regionType, region_num, node->grid_id, node->regionName.c_str(), node->regionEnvFileName.c_str(), + node->dist, node->x, node->y, node->z, node->regionScriptName.c_str()); + } + else + { + client->Message(CHANNEL_COLOR_RED, "[IN DIST RANGE] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, distance: %f, X/Y/Z: %f / %f / %f. Script: %s", region_num, node->grid_id, node->regionName.c_str(), node->regionEnvFileName.c_str(), + node->dist, node->x, node->y, node->z, node->regionScriptName.c_str()); + } + } + else + client->Message(CHANNEL_COLOR_RED, "[OUT OF RANGE] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, distance: %f, X/Y/Z: %f / %f / %f. Script: %s", region_num, node->grid_id, node->regionName.c_str(), node->regionEnvFileName.c_str(), + node->dist, node->x, node->y, node->z, node->regionScriptName.c_str()); + } + else + client->Message(CHANNEL_COLOR_RED, "[OUT OF GRID] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, distance: %f, X/Y/Z: %f / %f / %f. Script: %s", region_num, node->grid_id, node->regionName.c_str(), node->regionEnvFileName.c_str(), + node->dist, node->x, node->y, node->z, node->regionScriptName.c_str()); + + region_num++; + } +} + +void RegionMapV1::MapRegionsNearSpawn(Spawn *spawn, Client *client) const +{ + std::shared_lock lock(MRegions); + map::const_iterator itr; + int region_num = 0; + + spawn->RegionMutex.writelock(); + + glm::vec3 testLoc(spawn->GetX(), spawn->GetY(), spawn->GetZ()); + for (itr = Regions.begin(); itr != Regions.end(); itr++) + { + Region_Node *node = itr->first; + ZBSP_Node *BSP_Root = itr->second; + + if (node->regionScriptName.size() < 1) // only track ones that are used with LUA scripting + continue; + + if(!BSP_Root) { + int32 currentGridID = spawn->GetLocation(); + bool inRegion = false; + if(!(inRegion = spawn->InRegion(node, nullptr)) && currentGridID == node->grid_id && + ( node->trigger_widget_id == spawn->trigger_widget_id || (node->dist > 0.0f && spawn->GetDistance(node->x, node->y, node->z) < node->dist)) ) { + int32 returnValue = spawn->InsertRegionToSpawn(node, nullptr, RegionTypeUntagged); + if (client) + client->Message(CHANNEL_COLOR_YELLOW, "[ENTER REGION %i %u] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, distance: %f, X/Y/Z: %f / %f / %f. Script: %s", RegionTypeUntagged, returnValue, region_num, node->grid_id, node->regionName.c_str(), node->regionEnvFileName.c_str(), + node->dist, node->x, node->y, node->z, node->regionScriptName.c_str()); + } + continue; + } + + float x1 = node->x - testLoc.x; + float y1 = node->y - testLoc.y; + float z1 = node->z - testLoc.z; + float dist = sqrt(x1 *x1 + y1 *y1 + z1 *z1); + if (dist <= node->dist) + { + WaterRegionType regionType = RegionTypeUntagged; + + if (node->region_type == ClassWaterRegion) + regionType = BSPReturnRegionWaterRegion(node, BSP_Root, 1, testLoc, dist); + else + regionType = BSPReturnRegionTypeNode(node, BSP_Root, 1, testLoc, dist); + + if (regionType != RegionTypeNormal) + { + if (!spawn->InRegion(node, BSP_Root)) + { + spawn->DeleteRegion(node, BSP_Root); + int32 returnValue = spawn->InsertRegionToSpawn(node, BSP_Root, regionType); + if (client) + client->Message(CHANNEL_COLOR_YELLOW, "[ENTER REGION %i %u] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, distance: %f, X/Y/Z: %f / %f / %f. Script: %s", regionType, returnValue, region_num, node->grid_id, node->regionName.c_str(), node->regionEnvFileName.c_str(), + node->dist, node->x, node->y, node->z, node->regionScriptName.c_str()); + } + } + else + { + if(spawn->HasRegionTracked(node, BSP_Root, false)) { + continue; + } // UpdateRegionsNearSpawn will capture it for nodes that have BSP_Root's + if (spawn->InRegion(node, BSP_Root)) + { + if (client) + client->Message(CHANNEL_COLOR_RED, "[LEAVE REGION] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, distance: %f, X/Y/Z: %f / %f / %f. Script: %s", region_num, node->grid_id, node->regionName.c_str(), node->regionEnvFileName.c_str(), + node->dist, node->x, node->y, node->z, node->regionScriptName.c_str()); + WaterRegionType whatWasRegionType = (WaterRegionType) spawn->GetRegionType(node, BSP_Root); + lua_interface->RunRegionScript(node->regionScriptName, "LeaveRegion", spawn->GetZone(), spawn, whatWasRegionType); + } + spawn->DeleteRegion(node, BSP_Root); + + spawn->InsertRegionToSpawn(node, BSP_Root, RegionTypeNormal, false); + if (client) + client->Message(CHANNEL_COLOR_RED, "[NEAR REGION] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, distance: %f, X/Y/Z: %f / %f / %f. Script: %s", region_num, node->grid_id, node->regionName.c_str(), node->regionEnvFileName.c_str(), + node->dist, node->x, node->y, node->z, node->regionScriptName.c_str()); + } + } + + region_num++; + } + + spawn->RegionMutex.releasewritelock(); +} + +void RegionMapV1::UpdateRegionsNearSpawn(Spawn *spawn, Client *client) const +{ + std::shared_lock lock(MRegions); + map, Region_Status>::iterator testitr; + int region_num = 0; + + spawn->RegionMutex.writelock(); + + glm::vec3 testLoc(spawn->GetX(), spawn->GetY(), spawn->GetZ()); + + map deleteNodes; + for (testitr = spawn->Regions.begin(); testitr != spawn->Regions.end(); testitr++) + { + map::const_iterator actualItr = testitr->first.begin(); + Region_Node *node = actualItr->first; + ZBSP_Node *BSP_Root = actualItr->second; + + std::map::const_iterator dead_itr = dead_nodes.find(node); + if(dead_itr != dead_nodes.end()) { + deleteNodes.insert(make_pair(node, BSP_Root)); + continue; + } + if(!BSP_Root) { + continue; + } + + float x1 = node->x - testLoc.x; + float y1 = node->y - testLoc.y; + float z1 = node->z - testLoc.z; + float dist = sqrt(x1 *x1 + y1 *y1 + z1 *z1); + if (dist <= node->dist) + { + WaterRegionType regionType = RegionTypeUntagged; + + if (node->region_type == ClassWaterRegion) + regionType = BSPReturnRegionWaterRegion(node, BSP_Root, 1, testLoc, dist); + else + regionType = BSPReturnRegionTypeNode(node, BSP_Root, 1, testLoc, dist); + + if (regionType != RegionTypeNormal) + { + if (!testitr->second.inRegion) + { + testitr->second.inRegion = true; + int32 returnValue = 0; + lua_interface->RunRegionScript(node->regionScriptName, "EnterRegion", spawn->GetZone(), spawn, regionType, &returnValue); + if (client) + client->Message(CHANNEL_COLOR_YELLOW, "[ENTER RANGE %i %u] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, distance: %f, X/Y/Z: %f / %f / %f. Script: %s", regionType, returnValue, region_num, node->grid_id, node->regionName.c_str(), node->regionEnvFileName.c_str(), + node->dist, node->x, node->y, node->z, node->regionScriptName.c_str()); + testitr->second.timerTic = returnValue; + testitr->second.lastTimerTic = returnValue ? Timer::GetCurrentTime2() : 0; + } + } + else + { + if (testitr->second.inRegion) + { + testitr->second.inRegion = false; + testitr->second.timerTic = 0; + testitr->second.lastTimerTic = 0; + if (client) + client->Message(CHANNEL_COLOR_RED, "[LEAVE RANGE] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, distance: %f, X/Y/Z: %f / %f / %f. Script: %s", region_num, node->grid_id, node->regionName.c_str(), node->regionEnvFileName.c_str(), + node->dist, node->x, node->y, node->z, node->regionScriptName.c_str()); + WaterRegionType whatWasRegionType = (WaterRegionType) spawn->GetRegionType(node, BSP_Root); + lua_interface->RunRegionScript(node->regionScriptName, "LeaveRegion", spawn->GetZone(), spawn, whatWasRegionType); + } + } + } + else + { + if (client) + client->Message(CHANNEL_COLOR_RED, "[LEAVE RANGE - OOR] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, distance: %f, X/Y/Z: %f / %f / %f. Script: %s", region_num, node->grid_id, node->regionName.c_str(), node->regionEnvFileName.c_str(), + node->dist, node->x, node->y, node->z, node->regionScriptName.c_str()); + deleteNodes.insert(make_pair(node, BSP_Root)); + } + + region_num++; + } + + map::const_iterator deleteItr; + for (deleteItr = deleteNodes.begin(); deleteItr != deleteNodes.end(); deleteItr++) + { + Region_Node *tmpNode = deleteItr->first; + ZBSP_Node *bspNode = deleteItr->second; + spawn->DeleteRegion(tmpNode, bspNode); + } + + spawn->RegionMutex.releasewritelock(); +} + +void RegionMapV1::TicRegionsNearSpawn(Spawn *spawn, Client *client) const +{ + std::shared_lock lock(MRegions); + map, Region_Status>::iterator testitr; + int region_num = 0; + + spawn->RegionMutex.writelock(); + + for (testitr = spawn->Regions.begin(); testitr != spawn->Regions.end();) + { + map::const_iterator actualItr = testitr->first.begin(); + + if(actualItr == testitr->first.end()) { + testitr++; + continue; + } + + Region_Node *node = actualItr->first; + ZBSP_Node *BSP_Root = actualItr->second; + + std::map::const_iterator dead_itr = dead_nodes.find(node); + if(dead_itr != dead_nodes.end()) { + testitr++; + continue; + } + + if(!BSP_Root) { + bool passDistCheck = false; + int32 currentGridID = spawn->GetLocation(); + if(testitr->second.timerTic && currentGridID == node->grid_id && (node->trigger_widget_id == spawn->trigger_widget_id || (node->dist > 0.0f && spawn->GetDistance(node->x, node->y, node->z) <= node->dist && (passDistCheck = true))) + && Timer::GetCurrentTime2() >= (testitr->second.lastTimerTic + testitr->second.timerTic)) { + testitr->second.lastTimerTic = Timer::GetCurrentTime2(); + if (client) + client->Message(CHANNEL_COLOR_RED, "[TICK] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, distance: %f, X/Y/Z: %f / %f / %f. Script: %s", region_num, node->grid_id, node->regionName.c_str(), node->regionEnvFileName.c_str(), + node->dist, node->x, node->y, node->z, node->regionScriptName.c_str()); + + int32 returnValue = 0; + lua_interface->RunRegionScript(node->regionScriptName, "Tick", spawn->GetZone(), spawn, RegionTypeUntagged, &returnValue); + + if (returnValue == 1) + { + testitr->second.lastTimerTic = 0; + testitr->second.timerTic = 0; + } + } + else if(currentGridID != node->grid_id || (node->trigger_widget_id != spawn->trigger_widget_id && !passDistCheck)) { + if (client) + client->Message(CHANNEL_COLOR_RED, "[LEAVE REGION] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, distance: %f, X/Y/Z: %f / %f / %f. Script: %s", region_num, node->grid_id, node->regionName.c_str(), node->regionEnvFileName.c_str(), + node->dist, node->x, node->y, node->z, node->regionScriptName.c_str()); + lua_interface->RunRegionScript(node->regionScriptName, "LeaveRegion", spawn->GetZone(), spawn, RegionTypeUntagged); + map, Region_Status>::iterator endItr = testitr; + endItr++; + spawn->DeleteRegion(node, nullptr); + if(endItr == spawn->Regions.end()) { + break; + } + else { + testitr++; + continue; + } + + } + } + else if (testitr->second.timerTic && testitr->second.inRegion && Timer::GetCurrentTime2() >= (testitr->second.lastTimerTic + testitr->second.timerTic)) + { + testitr->second.lastTimerTic = Timer::GetCurrentTime2(); + if (client) + client->Message(CHANNEL_COLOR_RED, "[TICK] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, distance: %f, X/Y/Z: %f / %f / %f. Script: %s", region_num, node->grid_id, node->regionName.c_str(), node->regionEnvFileName.c_str(), + node->dist, node->x, node->y, node->z, node->regionScriptName.c_str()); + WaterRegionType whatWasRegionType = RegionTypeNormal; // default will be 0 + + if (BSP_Root->special == SPECIAL_REGION_LAVA_OR_DEATH) + whatWasRegionType = RegionTypeLava; // 2 + else if (BSP_Root->special == SPECIAL_REGION_WATER) + whatWasRegionType = RegionTypeWater; // 1 + + int32 returnValue = 0; + lua_interface->RunRegionScript(node->regionScriptName, "Tick", spawn->GetZone(), spawn, whatWasRegionType, &returnValue); + + if (returnValue == 1) + { + testitr->second.lastTimerTic = 0; + testitr->second.timerTic = 0; + } + } + + region_num++; + testitr++; + } + + spawn->RegionMutex.releasewritelock(); +} + +WaterRegionType RegionMapV1::BSPReturnRegionType(int32 node_number, const glm::vec3& location, int32 gridid) const { + std::shared_lock lock(MRegions); + map::const_iterator itr; + int region_num = 0; + for (itr = Regions.begin(); itr != Regions.end(); itr++) + { + + Region_Node* node = itr->first; + + // did not match grid id of current region, skip + //if ( gridid > 0 && gridid != node->grid_id) + // continue; + + ZBSP_Node* BSP_Root = itr->second; + + float x1 = node->x - location.x; + float y1 = node->y - location.y; + float z1 = node->z - location.z; + float dist = sqrt(x1 * x1 + y1 * y1 + z1 * z1); + +#ifdef REGIONDEBUG + printf("Region %i (%i) dist %f / node dist %f. NodeXYZ: %f %f %f, XYZ: %f %f %f.\n", region_num, node->region_type, dist, node->dist, node->x, node->y, node->z, location.x, location.y, location.z); +#endif + + if (dist <= node->dist) + { + ZBSP_Node* BSP_Root = itr->second; + + WaterRegionType regionType = RegionTypeUntagged; + + if (node->region_type == ClassWaterRegion) + regionType = BSPReturnRegionWaterRegion(node, BSP_Root, node_number, location, dist); + else + regionType = BSPReturnRegionTypeNode(node, BSP_Root, node_number, location, dist); + + if (regionType != RegionTypeNormal) + return regionType; + } + region_num++; + } + + return(RegionTypeNormal); +} + +WaterRegionType RegionMapV1::BSPReturnRegionTypeNode(const Region_Node* region_node, const ZBSP_Node* BSP_Root, int32 node_number, const glm::vec3& location, float distToNode) const { + if(node_number > region_node->vert_count) + { + LogWrite(REGION__DEBUG, 0, "Region", "Region %s grid %u (%s) - Node %u is out of range for region max vert count of %i. Hit at location %f %f %f.", + region_node->regionName.c_str(), region_node->grid_id, region_node->regionScriptName.c_str(), node_number, region_node->vert_count, + location.x, location.y, location.z); + return (RegionTypeWater); + } + + const ZBSP_Node* current_node = &BSP_Root[node_number - 1]; + float distance; + +#ifdef REGIONDEBUG + printf("left = %u, right %u (Size: %i)\n", current_node->left, current_node->right, region_node->vert_count); +#endif + + if (region_node->region_type == ClassWaterRegion2) + { + distance = (location.x * current_node->normal[0]) + + (location.y * current_node->normal[1]) + + (location.z * current_node->normal[2]) + + current_node->splitdistance; + } + else { + distance = (location.x * current_node->normal[0]) + + (location.y * current_node->normal[1]) + + (location.z * current_node->normal[2]) - + current_node->splitdistance; + } + + float absDistance = distance; + if (absDistance < 0.0f) + absDistance *= -1.0f; + + float absSplitDist = current_node->splitdistance; + if (absSplitDist < 0.0f) + absSplitDist *= -1.0f; + +#ifdef REGIONDEBUG + printf("distance = %f, normals: %f %f %f, location: %f %f %f, split distance: %f\n", distance, current_node->left, current_node->right, current_node->normal[0], current_node->normal[1], current_node->normal[2], + location.x, location.y, location.z, current_node->splitdistance); +#endif + + if ((current_node->left == -2) && + (current_node->right == -1 || current_node->right == -2)) { + if (region_node->region_type == ClassWaterOcean || region_node->region_type == ClassWaterOcean2) + { + if ( region_node->region_type == ClassWaterOcean && current_node->right == -1 && + current_node->normal[1] >= 0.9f && distance > 0 ) + return RegionTypeWater; + else + return EstablishDistanceAtAngle(region_node, current_node, distance, absDistance, absSplitDist, true); + } + else + { + if (distance > 0) + return(RegionTypeWater); + else + return RegionTypeNormal; + } + } + else if ((region_node->region_type == ClassWaterOcean || region_node->region_type == ClassWaterOcean2) && current_node->normal[1] != 1.0f && current_node->normal[1] != -1.0f) + { + float fraction = abs(current_node->normal[0] * current_node->normal[2]); + float diff = distToNode / region_node->dist; + if (distance > 0) + diff = distance * diff; + +#ifdef REGIONDEBUG + printf("Diff: %f (%f + %f), fraction %f\n", diff, distToNode, distance, fraction); +#endif + if ((abs(diff) / 2.0f) > (absSplitDist * (1.0f / fraction)) * 2.0f) + return RegionTypeNormal; + } + + if (distance == 0.0f) { + return(RegionTypeNormal); + } + + if (distance > 0.0f) { + +#ifdef REGIONDEBUG + printf("to left node %i\n", current_node->left); +#endif + if (current_node->left == -2) + { + switch(region_node->region_type) + { + case ClassWaterVolume: + case ClassWaterOcean: + return RegionTypeWater; + break; + + case ClassWaterOcean2: + return EstablishDistanceAtAngle(region_node, current_node, distance, absDistance, absSplitDist, false); + break; + + case ClassWaterCavern: + return EstablishDistanceAtAngle(region_node, current_node, distance, absDistance, absSplitDist, true); + break; + + default: + return RegionTypeNormal; + break; + } + } + else if (current_node->left == -1) { + return(RegionTypeNormal); + } + return BSPReturnRegionTypeNode(region_node, BSP_Root, current_node->left + 1, location, distToNode); + } + +#ifdef REGIONDEBUG + printf("to right node %i, sign bit %i\n", current_node->right, signbit(current_node->normal[1])); +#endif + if (current_node->right == -1) { + if (region_node->region_type == ClassWaterOcean2 && signbit(current_node->normal[1]) == 0 && absDistance < absSplitDist) + return RegionTypeWater; + else if ((region_node->region_type == ClassWaterOcean || region_node->region_type == ClassWaterOcean2) && + (current_node->normal[1] > 0.0f && distance < 0.0f && absDistance < absSplitDist)) + { + return(RegionTypeWater); + } + return(RegionTypeNormal); + } + + return BSPReturnRegionTypeNode(region_node, BSP_Root, current_node->right + 1, location, distToNode); +} + + +WaterRegionType RegionMapV1::BSPReturnRegionWaterRegion(const Region_Node* region_node, const ZBSP_Node* BSP_Root, int32 node_number, const glm::vec3& location, float distToNode) const { + if(node_number > region_node->vert_count) + { + LogWrite(REGION__DEBUG, 0, "Region", "Region %s grid %u (%s) - Node %u is out of range for region max vert count of %i. Hit at location %f %f %f.", + region_node->regionName.c_str(), region_node->grid_id, region_node->regionScriptName.c_str(), node_number, region_node->vert_count, + location.x, location.y, location.z); + return (RegionTypeNormal); + } + + const ZBSP_Node* current_node = &BSP_Root[node_number - 1]; + float distance; + +#ifdef REGIONDEBUG + printf("left = %u, right %u\n", current_node->left, current_node->right); +#endif + + distance = (location.x * current_node->normal[0]) + + (location.y * current_node->normal[1]) + + (location.z * current_node->normal[2]) - + current_node->splitdistance; + +#ifdef REGIONDEBUG + printf("distance = %f, normals: %f %f %f, location: %f %f %f, split distance: %f\n", distance, current_node->left, current_node->right, current_node->normal[0], current_node->normal[1], current_node->normal[2], + location.x, location.y, location.z, current_node->splitdistance); +#endif + + if (distance > 0.0f) { +#ifdef REGIONDEBUG + printf("to left node %i\n", current_node->left); +#endif + if (current_node->left == -1) { + return(RegionTypeNormal); + } + else if (current_node->left == -2) { + switch(current_node->special) + { + case SPECIAL_REGION_LAVA_OR_DEATH: + return(RegionTypeLava); + break; + case SPECIAL_REGION_WATER: + return(RegionTypeWater); + break; + default: + return(RegionTypeUntagged); + break; + } + } + return BSPReturnRegionWaterRegion(region_node, BSP_Root, current_node->left + 1, location, distToNode); + } + +#ifdef REGIONDEBUG + printf("to right node %i, sign bit %i\n", current_node->right, signbit(current_node->normal[1])); +#endif + + if (current_node->right == -1) { + return(RegionTypeNormal); + } + + return BSPReturnRegionWaterRegion(region_node, BSP_Root, current_node->right + 1, location, distToNode); +} + +WaterRegionType RegionMapV1::EstablishDistanceAtAngle(const Region_Node* region_node, const ZBSP_Node* current_node, float distance, float absDistance, float absSplitDist, bool checkEdgedAngle) const { + float fraction = abs(current_node->normal[0] * current_node->normal[2]); +#ifdef REGIONDEBUG + printf("Distcheck: %f < %f\n", absDistance, absSplitDist); +#endif + if (absDistance < absSplitDist && + (current_node->normal[0] >= 1.0f || current_node->normal[0] <= -1.0f || + (current_node->normal[1] >= .9f && distance < 0.0f) || + (current_node->normal[1] <= -.9f && distance > 0.0f))) + { + return RegionTypeWater; + } + else if (fraction > 0.0f && (region_node->region_type == ClassWaterOcean2 || checkEdgedAngle)) + { + if (current_node->normal[2] >= 1.0f || current_node->normal[2] <= -1.0f) + return RegionTypeNormal; + else if (current_node->normal[1] == 0.0f && (current_node->normal[0] < -0.5f || current_node->normal[0] > 0.5f) && + ((abs(absDistance * current_node->normal[0]) / 2.0f) < ((abs(absSplitDist * (1.0f / fraction)))))) + { + return RegionTypeWater; + } + else if (current_node->normal[1] == 0.0f && (current_node->normal[2] < -0.5f || current_node->normal[2] > 0.5f) && + ((abs(absDistance * current_node->normal[2]) / 2.0f) < ((abs(absSplitDist * (1.0f / fraction)))))) + { + return RegionTypeWater; + } + } + + return RegionTypeNormal; +} + +void RegionMapV1::InsertRegionNode(ZoneServer* zone, int32 version, std::string regionName, std::string envName, uint32 gridID, uint32 triggerWidgetID, float dist) +{ + Region_Node* tmpNode = new Region_Node; + + tmpNode->x = 0.0f; + tmpNode->y = 0.0f; + tmpNode->z = 0.0f; + + if(!zone) + return; + + Map* current_map = world.GetMap(std::string(zone->GetZoneFile()), version); + if(current_map) { + std::map::iterator itr = current_map->widget_map.find(triggerWidgetID); + if(itr != current_map->widget_map.end()) { + tmpNode->x = itr->second.x; + tmpNode->y = itr->second.y; + tmpNode->z = itr->second.z; + } + } + + tmpNode->dist = dist; + tmpNode->region_type = RegionTypeUntagged; + tmpNode->regionName = string(regionName); + tmpNode->regionEnvFileName = string(envName); + tmpNode->grid_id = gridID; + tmpNode->regionScriptName = string(""); + tmpNode->trigger_widget_id = triggerWidgetID; + + tmpNode->regionScriptName = TestFile(regionName); + + if ( tmpNode->regionScriptName.size() < 1 ) + { + tmpNode->regionScriptName = TestFile(envName); + } + if ( tmpNode->regionScriptName.size() < 1 ) + { + tmpNode->regionScriptName = TestFile("default"); + } + + tmpNode->vert_count = 0; + + ZBSP_Node* BSP_Root = nullptr; + + MRegions.lock(); + Regions.insert(make_pair(tmpNode, BSP_Root)); + MRegions.unlock(); +} + +void RegionMapV1::RemoveRegionNode(std::string name) { + + std::unique_lock lock(MRegions); + map::const_iterator itr; + for (itr = Regions.begin(); itr != Regions.end();) + { + Region_Node *node = itr->first; + ZBSP_Node *BSP_Root = itr->second; + if(node->regionName.find(name) != node->regionName.npos) { + itr = Regions.erase(itr); + dead_nodes.insert(make_pair(node, true)); + safe_delete(node); + safe_delete_array(BSP_Root); + } + else { + itr++; + } + } +} \ No newline at end of file diff --git a/source/WorldServer/Zone/region_map_v1.h b/source/WorldServer/Zone/region_map_v1.h new file mode 100644 index 0000000..0016a42 --- /dev/null +++ b/source/WorldServer/Zone/region_map_v1.h @@ -0,0 +1,90 @@ +#ifndef EQ2EMU_REGION_MAP_V1_H +#define EQ2EMU_REGION_MAP_V1_H + +#include +#include +#include + +#include "region_map.h" + +class Client; +class Spawn; + +#define SPECIAL_REGION_LAVA_OR_DEATH 4294967293 +#define SPECIAL_REGION_WATER 1 + +#pragma pack(1) +typedef struct ZBSP_Node { + int32 node_number; + float normal[3], splitdistance; + int32 region; + int32 special; + int32 left, right; +} ZBSP_Node; + +typedef struct Region_Node { + int32 region_type; + float x; + float y; + float z; + float dist; + string regionEnvFileName; + string regionName; + int32 grid_id; + string regionScriptName; + int32 vert_count; + int32 trigger_widget_id; +} Region_Node; +#pragma pack() + +struct Region_Status { + bool inRegion; + int32 timerTic; + int32 lastTimerTic; + int32 regionType; +}; + +class RegionMapV1 : public RegionMap +{ +public: + RegionMapV1(); + ~RegionMapV1(); + + virtual WaterRegionType ReturnRegionType(const glm::vec3& location, int32 grid_id=0) const; + virtual bool InWater(const glm::vec3& location, int32 grid_id=0) const; + virtual bool InLava(const glm::vec3& location, int32 grid_id=0) const; + virtual bool InLiquid(const glm::vec3& location) const; + virtual bool InPvP(const glm::vec3& location) const; + virtual bool InZoneLine(const glm::vec3& location) const; + + virtual void IdentifyRegionsInGrid(Client* client, const glm::vec3& location) const; + virtual void MapRegionsNearSpawn(Spawn* spawn, Client* client=0) const; + virtual void UpdateRegionsNearSpawn(Spawn* spawn, Client* client=0) const; + virtual void TicRegionsNearSpawn(Spawn* spawn, Client* client=0) const; + + virtual void InsertRegionNode(ZoneServer* zone, int32 version, std::string regionName, std::string envName, uint32 gridID, uint32 triggerWidgetID, float dist = 0.0f); + virtual void RemoveRegionNode(std::string regionName); +protected: + virtual bool Load(FILE *fp, std::string inZoneLowerName, int32 regionVersion); + +private: + WaterRegionType BSPReturnRegionType(int32 node_number, const glm::vec3& location, int32 gridid=0) const; + WaterRegionType BSPReturnRegionTypeNode(const Region_Node* node, const ZBSP_Node* BSP_Root, int32 node_number, const glm::vec3& location, float distToNode=0.0f) const; + + WaterRegionType BSPReturnRegionWaterRegion(const Region_Node* node, const ZBSP_Node* BSP_Root, int32 node_number, const glm::vec3& location, float distToNode=0.0f) const; + map Regions; + + WaterRegionType EstablishDistanceAtAngle(const Region_Node* region_node, const ZBSP_Node* current_node, float distance, float absDistance, float absSplitDist, bool checkEdgedAngle=false) const; + + std::string TestFile(std::string testFile); + + friend class RegionMap; + + int32 mVersion; + std::string mZoneNameLower; + + mutable std::shared_mutex MRegions; + std::map dead_nodes; +}; + +#endif diff --git a/source/WorldServer/classes.cpp b/source/WorldServer/classes.cpp new file mode 100644 index 0000000..ba9d7fd --- /dev/null +++ b/source/WorldServer/classes.cpp @@ -0,0 +1,199 @@ +/* + 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 . +*/ +#include "../common/debug.h" +#include "../common/Log.h" +#include "classes.h" +#include "../common/MiscFunctions.h" +#include + +Classes::Classes(){ + class_map["COMMONER"] = 0; + class_map["FIGHTER"] = 1; + class_map["WARRIOR"] = 2; + class_map["GUARDIAN"] = 3; + class_map["BERSERKER"] = 4; + class_map["BRAWLER"] = 5; + class_map["MONK"] = 6; + class_map["BRUISER"] = 7; + class_map["CRUSADER"] = 8; + class_map["SHADOWKNIGHT"] = 9; + class_map["PALADIN"] = 10; + class_map["PRIEST"] = 11; + class_map["CLERIC"] = 12; + class_map["TEMPLAR"] = 13; + class_map["INQUISITOR"] = 14; + class_map["DRUID"] = 15; + class_map["WARDEN"] = 16; + class_map["FURY"] = 17; + class_map["SHAMAN"] = 18; + class_map["MYSTIC"] = 19; + class_map["DEFILER"] = 20; + class_map["MAGE"] = 21; + class_map["SORCERER"] = 22; + class_map["WIZARD"] = 23; + class_map["WARLOCK"] = 24; + class_map["ENCHANTER"] = 25; + class_map["ILLUSIONIST"] = 26; + class_map["COERCER"] = 27; + class_map["SUMMONER"] = 28; + class_map["CONJUROR"] = 29; + class_map["NECROMANCER"] = 30; + class_map["SCOUT"] = 31; + class_map["ROGUE"] = 32; + class_map["SWASHBUCKLER"] = 33; + class_map["BRIGAND"] = 34; + class_map["BARD"] = 35; + class_map["TROUBADOR"] = 36; + class_map["DIRGE"] = 37; + class_map["PREDATOR"] = 38; + class_map["RANGER"] = 39; + class_map["ASSASSIN"] = 40; + class_map["ANIMALIST"] = 41; + class_map["BEASTLORD"] = 42; + class_map["SHAPER"] = 43; + class_map["CHANNELER"] = 44; + class_map["ARTISAN"] = 45; + class_map["CRAFTSMAN"] = 46; + class_map["PROVISIONER"] = 47; + class_map["WOODWORKER"] = 48; + class_map["CARPENTER"] = 49; + class_map["OUTFITTER"] = 50; + class_map["ARMORER"] = 51; + class_map["WEAPONSMITH"] = 52; + class_map["TAILOR"] = 53; + class_map["SCHOLAR"] = 54; + class_map["JEWELER"] = 55; + class_map["SAGE"] = 56; + class_map["ALCHEMIST"] = 57; +} + +int8 Classes::GetBaseClass(int8 class_id) { + int8 ret = 0; + if(class_id>=WARRIOR && class_id <= PALADIN) + ret = FIGHTER; + if((class_id>=CLERIC && class_id <= DEFILER) || (class_id == SHAPER || class_id == CHANNELER)) + ret = PRIEST; + if(class_id>=SORCERER && class_id <= NECROMANCER) + ret = MAGE; + if(class_id>=ROGUE && class_id <= BEASTLORD) + ret = SCOUT; + LogWrite(WORLD__DEBUG, 5, "World", "%s returning base class ID: %i", __FUNCTION__, ret); + return ret; +} + +int8 Classes::GetSecondaryBaseClass(int8 class_id){ + int8 ret = 0; + if(class_id==GUARDIAN || class_id == BERSERKER) + ret = WARRIOR; + if(class_id==MONK || class_id == BRUISER) + ret = BRAWLER; + if(class_id==SHADOWKNIGHT || class_id == PALADIN) + ret = CRUSADER; + if(class_id==TEMPLAR || class_id == INQUISITOR) + ret = CLERIC; + if(class_id==WARDEN || class_id == FURY) + ret = DRUID; + if(class_id==MYSTIC || class_id == DEFILER) + ret = SHAMAN; + if(class_id==WIZARD || class_id == WARLOCK) + ret = SORCERER; + if(class_id==ILLUSIONIST || class_id == COERCER) + ret = ENCHANTER; + if(class_id==CONJUROR || class_id == NECROMANCER) + ret = SUMMONER; + if(class_id==SWASHBUCKLER || class_id == BRIGAND) + ret = ROGUE; + if(class_id==TROUBADOR || class_id == DIRGE) + ret = BARD; + if(class_id==RANGER || class_id == ASSASSIN) + ret = PREDATOR; + if(class_id==BEASTLORD) + ret = ANIMALIST; + if(class_id == CHANNELER) + ret = SHAPER; + LogWrite(WORLD__DEBUG, 5, "World", "%s returning secondary class ID: %i", __FUNCTION__, ret); + return ret; +} + +int8 Classes::GetTSBaseClass(int8 class_id) { + int8 ret = 0; + if (class_id + 42 >= ARTISAN) + ret = ARTISAN - 44; + else + ret = class_id; + + LogWrite(WORLD__DEBUG, 5, "World", "%s returning base tradeskill class ID: %i", __FUNCTION__, ret); + return ret; +} + +int8 Classes::GetSecondaryTSBaseClass(int8 class_id) { + int8 ret = class_id + 42; + if (ret == ARTISAN) + ret = ARTISAN - 44; + else if (ret >= CRAFTSMAN && ret < OUTFITTER) + ret = CRAFTSMAN - 44; + else if (ret >= OUTFITTER && ret < SCHOLAR) + ret = OUTFITTER - 44; + else if (ret >= SCHOLAR) + ret = SCHOLAR - 44; + else + ret = class_id; + + LogWrite(WORLD__DEBUG, 5, "World", "%s returning secondary tradeskill class ID: %i", __FUNCTION__, ret); + return ret; +} + +sint8 Classes::GetClassID(const char* name){ + string class_name = string(name); + class_name = ToUpper(class_name); + if(class_map.count(class_name) == 1) { + LogWrite(WORLD__DEBUG, 5, "World", "%s returning class ID: %i for class name %s", __FUNCTION__, class_map[class_name], class_name.c_str()); + return class_map[class_name]; + } + LogWrite(WORLD__WARNING, 0, "World", "Could not find class_id in function: %s (return -1)", __FUNCTION__); + return -1; +} + +const char* Classes::GetClassName(int8 class_id){ + map::iterator itr; + for(itr = class_map.begin(); itr != class_map.end(); itr++){ + if(itr->second == class_id) { + LogWrite(WORLD__DEBUG, 5, "World", "%s returning class name: %s for class_id %i", __FUNCTION__, itr->first.c_str(), class_id); + return itr->first.c_str(); + } + } + LogWrite(WORLD__WARNING, 0, "World", "Could not find class name in function: %s (return 0)", __FUNCTION__); + return 0; +} + +string Classes::GetClassNameCase(int8 class_id) { + map::iterator itr; + for (itr = class_map.begin(); itr != class_map.end(); itr++){ + if (itr->second == class_id) { + string class_name = string(itr->first); + transform(itr->first.begin() + 1, itr->first.end(), class_name.begin() + 1, ::tolower); + class_name[0] = ::toupper(class_name[0]); + LogWrite(WORLD__DEBUG, 5, "World", "%s returning class name: %s for class_id %i", __FUNCTION__, class_name.c_str(), class_id); + return class_name; + } + } + LogWrite(WORLD__WARNING, 0, "World", "Could not find class name in function: %s (return blank)", __FUNCTION__); + return ""; +} diff --git a/source/WorldServer/classes.h b/source/WorldServer/classes.h new file mode 100644 index 0000000..58beefe --- /dev/null +++ b/source/WorldServer/classes.h @@ -0,0 +1,119 @@ +/* + 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 . +*/ +#ifndef CLASSES_CH +#define CLASSES_CH +#include "../common/types.h" +#include +using namespace std; + +#define COMMONER 0 +#define FIGHTER 1 +#define WARRIOR 2 +#define GUARDIAN 3 +#define BERSERKER 4 +#define BRAWLER 5 +#define MONK 6 +#define BRUISER 7 +#define CRUSADER 8 +#define SHADOWKNIGHT 9 +#define PALADIN 10 +#define PRIEST 11 +#define CLERIC 12 +#define TEMPLAR 13 +#define INQUISITOR 14 +#define DRUID 15 +#define WARDEN 16 +#define FURY 17 +#define SHAMAN 18 +#define MYSTIC 19 +#define DEFILER 20 +#define MAGE 21 +#define SORCERER 22 +#define WIZARD 23 +#define WARLOCK 24 +#define ENCHANTER 25 +#define ILLUSIONIST 26 +#define COERCER 27 +#define SUMMONER 28 +#define CONJUROR 29 +#define NECROMANCER 30 +#define SCOUT 31 +#define ROGUE 32 +#define SWASHBUCKLER 33 +#define BRIGAND 34 +#define BARD 35 +#define TROUBADOR 36 +#define DIRGE 37 +#define PREDATOR 38 +#define RANGER 39 +#define ASSASSIN 40 +#define ANIMALIST 41 +#define BEASTLORD 42 +#define SHAPER 43 +#define CHANNELER 44 + +//Tradeskills +// 0 - transmuting/tinkering +#define ARTISAN 45 // 1 +#define CRAFTSMAN 46 // 2 +#define PROVISIONER 47 // 3 +#define WOODWORKER 48 // 4 +#define CARPENTER 49 // 5 +#define OUTFITTER 50 // 6 +#define ARMORER 51 // 7 +#define WEAPONSMITH 52 // 8 +#define TAILOR 53 // 9 +#define SCHOLAR 54 // 10 +#define JEWELER 55 // 11 +#define SAGE 56 // 12 +#define ALCHEMIST 57 // 13 +//43 - artisan + //44 - craftsman + //45 - provisioner + //46 - Woodworker + //47 - carpenter + //48 - armorer + //49 - weaponsmith + //50 - tailor + //51 - + //52 - jeweler + //53 - sage + //54 - alch +#define CLASSIC_MAX_ADVENTURE_CLASS 40 // there is a 41, but its 'scantestbase' +#define CLASSIC_MAX_TRADESKILL_CLASS 13 +#define MAX_CLASSES 58 + +class Classes { +public: + Classes(); + char* GetEQClassName(int8 class_, int8 level); + const char* GetClassName(int8 class_id); + string GetClassNameCase(int8 class_id); + sint8 GetClassID(const char* name); + int8 GetBaseClass(int8 class_id); + int8 GetSecondaryBaseClass(int8 class_id); + int8 GetTSBaseClass(int8 class_id); + int8 GetSecondaryTSBaseClass(int8 class_id); + +private: + map class_map; +}; +#endif + diff --git a/source/WorldServer/client.cpp b/source/WorldServer/client.cpp new file mode 100644 index 0000000..342b6d8 --- /dev/null +++ b/source/WorldServer/client.cpp @@ -0,0 +1,13140 @@ +/* +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 . +*/ +#include "../common/debug.h" +#include "../common/Log.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Player.h" +#include "Commands/Commands.h" +#include "ClientPacketFunctions.h" +#include "../common/ConfigReader.h" +#include "Guilds/Guild.h" +#include "Variables.h" +#include "Rules/Rules.h" +#include "Titles.h" +#include "Chat/Chat.h" +#include "SpellProcess.h" +#include "Zone/ChestTrap.h" +#include "../../common/GlobalHeaders.h" + +//#include "Quests.h" + +#ifdef WIN32 +#include +#include +#define snprintf _snprintf +#define vsnprintf _vsnprintf +#define strncasecmp _strnicmp +#define strcasecmp _stricmp +#else +#include +#ifdef FREEBSD +#include +#endif +#include +#include +#include +#endif + +#if defined(__GNUC__) +#define _snprintf snprintf +#endif + + +#include "client.h" +#include "../common/emu_opcodes.h" +#include "../common/packet_dump.h" +#include "WorldDatabase.h" +#include "races.h" +#include "classes.h" +#include "LoginServer.h" +#include "World.h" +#include "../common/EQ2_Common_Structs.h" +#include "net.h" +#include "../common/MiscFunctions.h" +#include "Skills.h" +#include "LuaInterface.h" +#include "Quests.h" +#include "Collections/Collections.h" +#include "Achievements/Achievements.h" +#include "Traits/Traits.h" +#include "Recipes/Recipe.h" +#include "Tradeskills/Tradeskills.h" +#include "AltAdvancement/AltAdvancement.h" +#include "Bots/Bot.h" +#include "VisualStates.h" + +extern WorldDatabase database; +extern const char* ZONE_NAME; +extern LoginServer loginserver; +extern sint32 numclients; +extern NetConnection net; +extern Commands commands; +extern ClientList client_list; +extern ZoneList zone_list; +extern ZoneAuth zone_auth; +extern MasterItemList master_item_list; +extern MasterSkillList master_skill_list; +extern MasterSpellList master_spell_list; +extern MasterTraitList master_trait_list; +extern MasterQuestList master_quest_list; +extern MasterFactionList master_faction_list; +extern MasterRecipeList master_recipe_list; +extern volatile bool RunLoops; +extern ConfigReader configReader; +extern LuaInterface* lua_interface; +extern World world; +extern Variables variables; +extern Classes classes; +extern Races races; +extern GuildList guild_list; +extern MasterCollectionList master_collection_list; +extern MasterAchievementList master_achievement_list; +extern RuleManager rule_manager; +extern Chat chat; +extern MasterAAList master_aa_list; +extern MasterAAList master_tree_nodes; +extern ChestTrapList chest_trap_list; +extern MasterRecipeBookList master_recipebook_list; +extern VisualStates visual_states; + +using namespace std; + +Client::Client(EQStream* ieqs) : underworld_cooldown_timer(5000), pos_update(125), quest_pos_timer(2000), lua_debug_timer(30000), delayTimer(500), transmuteID(0), temp_placement_timer(10), spawn_removal_timer(250) { + eqs = ieqs; + ip = eqs->GetrIP(); + port = ntohs(eqs->GetrPort()); + merchant_transaction = nullptr; + mail_window.item = nullptr; // don't want this to be set(loose ptr) when using ResetSendMail to provide rest of the defaults + ResetSendMail(false); + timestamp_flag = 0; + current_quest_id = 0; + last_update_time = 0; + quest_updates = false; + + //autobootup_timeout = new Timer(10000); + //autobootup_timeout->Disable(); + + CLE_keepalive_timer = new Timer(15000); + connect = new Timer(1000); + connect->Disable(); + zoneID = 0; + account_name[0] = 0; + character_id = 0; + account_id = 0; + pwaitingforbootup = 0; + current_zone = 0; + connected_to_zone = false; + connected = false; + camp_timer = 0; + linkdead_timer = 0; + client_zoning = false; + zoning_id = 0; + zoning_x = 0; + zoning_y = 0; + zoning_z = 0; + zoning_instance_id = 0; + player_pos_changed = false; + player_pos_timer = Timer::GetCurrentTime2()+1000; + enabled_player_pos_timer = true; + ++numclients; + if (world.GetServerStatisticValue(STAT_SERVER_MOST_CONNECTIONS) < numclients) + world.UpdateServerStatistic(STAT_SERVER_MOST_CONNECTIONS, numclients, true); + remove_from_list = false; + new_client_login = NewLoginState::LOGIN_NONE; + UpdateWindowTitle(0); + num_active_failures = 0; + player = new Player(); + player->SetClient(this); + combine_spawn = 0; + lua_debug = false; + ready_for_spawns = false; + ready_for_updates = false; + lua_debug_timer.Disable(); + transport_spawn = 0; + MBuyBack.SetName("Client::MBuyBack"); + MDeletePlayer.SetName("Client::MDeletePlayer"); + MQuestPendingUpdates.SetName("Client::MQuestPendingUpdates"); + search_items = 0; + version = 0; + next_conversation_id = 0; + pending_guild_invite.guild = 0; + pending_guild_invite.invited_by = 0; + m_recipeListSent = false; + m_resurrect.SetName("Client::m_resurrect"); + current_rez.expire_timer = 0; + current_rez.should_delete = true; + pending_last_name = 0; + should_target = false; + initial_spawns_sent = false; + MQuestTimers.SetName("Client::quest_timers"); + memset(&incoming_paperdoll, 0, sizeof(incoming_paperdoll)); + on_auto_mount = false; + should_load_spells = true; + spawnPlacementMode = ServerSpawnPlacementMode::DEFAULT; + delayedLogin = false; + delayedAccountID = 0; + delayedAccessKey = 0; + delayTimer.Disable(); + tempPlacementSpawn = nullptr; + placement_unique_item_id = 0; + SetHasOwnerOrEditAccess(false); + temporary_transport_id = 0; + rejoin_group_id = 0; + lastRegionRemapTime = 0; + regionDebugMessaging = false; + client_reloading_zone = false; + last_saved_timestamp = 0; + MQueueStateCmds.SetName("Client::MQueueStateCmds"); + save_spell_state_timer.Disable(); + save_spell_state_time_bucket = 0; + player_loading_complete = false; + MItemDetails.SetName("Client::MItemDetails"); + MSpellDetails.SetName("Client::MSpellDetails"); + hasSentTempPlacementSpawn = false; + spawn_removal_timer.Start(); + disable_save = false; + SetZoningDestination(nullptr); + underworld_cooldown_timer.Disable(); + player_pos_change_count = 0; + pov_ghost_spawn_id = 0; + recipe_orig_packet = nullptr; + recipe_xor_packet = nullptr; + recipe_packet_count = 0; + recipe_orig_packet_size = 0; +} + +Client::~Client() { + RemoveClientFromZone(); + + //let the stream factory know were done with this stream + if (eqs) { + eqs->Close(); + try { + eqs->ReleaseFromUse(); + } + catch (...) {} + } + eqs = NULL; + + //safe_delete(autobootup_timeout); + + safe_delete(linkdead_timer); + vector::iterator itr; + QueuedQuest* queued_quest = 0; + for (itr = quest_queue.begin(); itr != quest_queue.end(); itr++) { + queued_quest = *itr; + safe_delete(queued_quest); + } + quest_queue.clear(); + + vector::iterator rwd_itr; + QuestRewardData* quest_rwd_data = 0; + for (rwd_itr = quest_pending_reward.begin(); rwd_itr != quest_pending_reward.end(); rwd_itr++) { + quest_rwd_data = *rwd_itr; + safe_delete(quest_rwd_data); + } + quest_pending_reward.clear(); + + safe_delete(CLE_keepalive_timer); + safe_delete(connect); + --numclients; + UpdateWindowTitle(0); +} + + +void Client::RemoveClientFromZone() { + if(player && player->GetZone()) + player->GetZone()->GetSpellProcess()->RemoveSpellTimersFromSpawn(player, true, false, true, true); + + if (GetTempPlacementSpawn() && GetCurrentZone()) { + Spawn* tmp = GetTempPlacementSpawn(); + SetTempPlacementSpawn(nullptr); + GetCurrentZone()->RemoveSpawn(tmp, true, false, true, true, true); + } + + if (current_zone && player) { + if (player->GetGroupMemberInfo()) { + TempRemoveGroup(); + } + world.GetGroupManager()->ClearPendingInvite(player); + } + if (lua_interface) + lua_interface->RemoveDebugClients(this); + + if (player) + zone_list.RemoveClientFromMap(player->GetName(), this); + + safe_delete(camp_timer); + safe_delete(search_items); + safe_delete(current_rez.expire_timer); + safe_delete(pending_last_name); + safe_delete_array(incoming_paperdoll.image_bytes); + + MDeletePlayer.writelock(__FUNCTION__, __LINE__); + player = nullptr; + MDeletePlayer.releasewritelock(__FUNCTION__, __LINE__); + + deque::iterator itr; + MBuyBack.writelock(__FUNCTION__, __LINE__); + for (itr = buy_back_items.begin(); itr != buy_back_items.end();) { + safe_delete(*itr); + itr = buy_back_items.erase(itr); + } + MBuyBack.releasewritelock(__FUNCTION__, __LINE__); +} + + +void Client::QueuePacket(EQ2Packet* app, bool attemptedCombine) { + if (eqs) { + if (!eqs->CheckActive()) { + client_list.Remove(this); + eqs = 0; + } + } + if (app && eqs && version > 0) + eqs->EQ2QueuePacket(app, attemptedCombine); + else { + safe_delete(app); + } + +} + +void Client::PopulateSkillMap() { + EQ2Packet* app = master_skill_list.GetPopulateSkillsPacket(GetVersion()); + if (app) + QueuePacket(app); + else { + LogWrite(WORLD__ERROR, 0, "World", "Unable to send populate skills packet for version: %i!", GetVersion()); + Disconnect(); //the client cant proceed without the skill packet, might as well kick it now + } + +} + +void Client::SendLoginInfo() { + if(GetPlayer()->IsReturningFromLD()) + firstlogin = true; + + if (firstlogin) { + LogWrite(WORLD__DEBUG, 0, "World", "Increment Server_Accepted_Connection + 1"); + world.UpdateServerStatistic(STAT_SERVER_ACCEPTED_CONNECTION, 1); + LogWrite(CCLIENT__DEBUG, 0, "Client", "Populate Skill Map..."); + PopulateSkillMap(); + // JA: Check client version and move player to valid zone if current client does not support last saved zone (loading SF client on DoV saved zone) IT CAN HAPPEN! + LogWrite(MISC__TODO, 1, "TODO", "Check client version at login, move char if invalid zone file"); + } + + LogWrite(CCLIENT__DEBUG, 0, "Client", "Toggle Character Online..."); + database.ToggleCharacterOnline(this, 1); + + int32 count = 0; + + if(!GetPlayer()->IsReturningFromLD()) + { + count = database.LoadCharacterTitles(GetCharacterID(), player); + if (count == 0) { + LogWrite(CCLIENT__DEBUG, 0, "Client", "No character titles found!"); + LogWrite(CCLIENT__DEBUG, 0, "Client", "Initializing starting values - Titles"); + database.UpdateStartingTitles(GetCharacterID(), player->GetAdventureClass(), player->GetRace(), player->GetGender()); + } + } + + if(!GetPlayer()->IsReturningFromLD()) + { + count = database.LoadCharacterLanguages(GetCharacterID(), player); + if (count == 0) + LogWrite(CCLIENT__DEBUG, 0, "Client", "No character languages loaded!"); + + count = database.LoadPlayerRecipeBooks(GetCharacterID(), player); + if (count == 0) + LogWrite(CCLIENT__DEBUG, 0, "Client", "No character recipe books found!"); + } + + ClientPacketFunctions::SendLoginAccepted(this); + + ClientPacketFunctions::SendAbilities ( this ); + + ClientPacketFunctions::SendCommandNamePacket(this); + + ClientPacketFunctions::SendQuickBarInit(this); + + // we only need to send the MOTD if it is the first time the person is logging in. + if (firstlogin) { + ClientPacketFunctions::SendMOTD(this); + ClientPacketFunctions::SendCharacterMacros(this); + zone_list.CheckFriendList(this); + } + + if(!GetPlayer()->IsReturningFromLD()) + { + database.LoadCharacterItemList(GetAccountID(), GetCharacterID(), player, GetVersion()); + if (firstlogin && player->item_list.GetNumberOfItems() == 0 && player->GetEquipmentList()->GetNumberOfItems() == 0) //re-add starting items if missing + { + LogWrite(CCLIENT__WARNING, 0, "Client", "Player has no items - reloading starting items: '%s' (%u)", player->GetName(), GetCharacterID()); + database.UpdateStartingItems(GetCharacterID(), player->GetAdventureClass(), player->GetRace()); + database.LoadCharacterItemList(GetAccountID(), GetCharacterID(), player, GetVersion()); + } + GetPlayer()->item_list.SetMaxItemIndex(); + database.LoadPlayerFactions(this); + database.LoadCharacterQuests(this); + database.LoadCharacterQuestRewards(this); + database.LoadPlayerMail(this); + } + LogWrite(CCLIENT__DEBUG, 0, "Client", "Send Quest Journal..."); + SendQuestJournal(true, 0, false); + + if (version > 561) // right version? possibly not! + master_aa_list.DisplayAA(this, 0, 3); + + if (version > 373) + SendCollectionList(); + SendBiography(); + + map::iterator itr; + Quest* quest = 0; + GetPlayer()->MPlayerQuests.readlock(__FUNCTION__, __LINE__); + for (itr = player->player_quests.begin(); itr != player->player_quests.end(); itr++) { + quest = itr->second; + if (quest->IsTracked()) { + quest->SetTracked(false); + QueuePacket(itr->second->QuestJournalReply(version, GetNameCRC(), player)); + quest->SetTracked(true); + QueuePacket(itr->second->QuestJournalReply(version, GetNameCRC(), player)); + } + } + GetPlayer()->MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + + // SendAchievementsList(); + + /*Guild* guild = player->GetGuild(); + if (guild) { + guild->UpdateGuildMemberInfo(GetPlayer()); + if (firstlogin) + guild->SendGuildMOTD(this); + guild->SendGuildUpdate(this); + guild->SendGuildMember(GetPlayer(), firstlogin); + guild->SendGuildEventList(this); + guild->SendGuildBankEventList(this); + guild->SendAllGuildEvents(this); + guild->SendGuildMemberList(this); + }*/ + if (version > 373) { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Loading Faction Updates..."); + EQ2Packet* outapp = player->GetFactions()->FactionUpdate(GetVersion()); + if (outapp) { + LogWrite(CCLIENT__PACKET, 0, "Client", "Dump/Print Packet in func: %s, line: %i", __FUNCTION__, __LINE__); + //DumpPacket(outapp); + QueuePacket(outapp); + } + } + + LogWrite(CCLIENT__DEBUG, 0, "Client", "Send Command List..."); + ClientPacketFunctions::SendCommandList(this); + + LogWrite(CCLIENT__DEBUG, 0, "Client", "Send Language Updates..."); + + // kos doesn't like one of these language or instance list + SendLanguagesUpdate(database.GetCharacterCurrentLang(GetCharacterID(), player)); + + //ClientPacketFunctions::SendInstanceList(this); + + SendZoneInfo(); + /*Spell* spell = 0; + vector::iterator itr; + for(itr = player->GetQuickbar()->begin(); itr != player->GetQuickbar()->end(); itr++){ + if((*itr)->type == 1){ + spell = master_spell_list.GetSpell((*itr)->id); + if(spell) + QueuePacket(spell->serialize(this, false, 0x20)); + } + }*/ +} + +void Client::SendPlayerDeathWindow() +{ + LogWrite(CCLIENT__DEBUG, 0, "Client", "SendPlayerDeathWindow"); + vector* results = GetCurrentZone()->GetRevivePoints(this); + vector::iterator itr; + + if (results && results->size() > 0) + { + PacketStruct* packet = configReader.getStruct("WS_DeathWindow", GetVersion()); + if (packet) + { + packet->setArrayLengthByName("location_count", results->size()); + RevivePoint* point = 0; + int32 i = 0; + + for (itr = results->begin(); itr != results->end(); itr++, i++) + { + point = *itr; + if (point) + { + packet->setArrayDataByName("location_id", point->id, i); + //zone_name = database.GetZoneName(point->zone_id); + string zone_name = database.GetZoneDescription(point->zone_id); + if (zone_name.length() > 0) + packet->setArrayDataByName("zone_name", zone_name.c_str(), i); + packet->setArrayDataByName("location_name", point->location_name.c_str(), i); + packet->setArrayDataByName("distance", GetPlayer()->GetDistance(point->x, point->y, point->z), i); + } + + if (point->id == 0xFFFFFFFF)//tmp location + safe_delete(point); + } +#if EQDEBUG >= 3 + LogWrite(CCLIENT__DEBUG, 0, "Client", "WS_DeathWindow Packet:"); + packet->PrintPacket(); +#endif + EQ2Packet* outapp = packet->serialize(); + QueuePacket(outapp); + safe_delete(packet); + } + // done with the revive points so lets free up the pointer + safe_delete(results); + } + +} + +void Client::DisplayDeadWindow() +{ + LogWrite(ZONE__DEBUG, 0, "Zone", "DisplayDeadWindow()"); + + player->SetHP(0); + player->SetPower(0); + GetCurrentZone()->TriggerCharSheetTimer(); + + if(GetVersion() <= 561) { + ClientPacketFunctions::SendServerControlFlagsClassic(this, 8, 1); + ClientPacketFunctions::SendServerControlFlagsClassic(this, 16, 1); + } + else { + ClientPacketFunctions::SendServerControlFlags(this, 1, 8, 1); + ClientPacketFunctions::SendServerControlFlags(this, 1, 16, 1); + } + + PacketStruct* packet = configReader.getStruct("WS_ServerUpdateTarget", GetVersion()); + if (packet) + { + packet->setDataByName("spawn_id", 0xFFFFFFFF); + QueuePacket(packet->serialize()); + safe_delete(packet); + } + + SendPlayerDeathWindow(); + +} + +void Client::HandlePlayerRevive(int32 point_id) +{ + if(GetVersion() <= 561) { + ClientPacketFunctions::SendServerControlFlagsClassic(this, 8, 0); + ClientPacketFunctions::SendServerControlFlagsClassic(this, 16, 0); + } + else { + ClientPacketFunctions::SendServerControlFlags(this, 1, 8, 0); + ClientPacketFunctions::SendServerControlFlags(this, 1, 16, 0); + } + + SimpleMessage(CHANNEL_NARRATIVE, "You regain consciousness!"); + PacketStruct* packet = configReader.getStruct("WS_Resurrected", GetVersion()); + if (packet) + { + QueuePacket(packet->serialize()); + safe_delete(packet); + } + float origX, origY, origZ, origHeading = 0.0f; + + origX = player->GetX(); + origY = player->GetY(); + origZ = player->GetZ(); + origHeading = player->GetHeading(); + ZoneServer* originalZone = GetCurrentZone(); + int32 origGridID = GetPlayer()->GetLocation(); + + float x, y, z, heading; + RevivePoint* revive_point = 0; + if (point_id != 0xFFFFFFFF) + revive_point = GetCurrentZone()->GetRevivePoint(point_id); + + string zone_desc; + const char* location_name = "Unknown"; + + player->SetAlive(true); + player->SetResurrecting(true); + player->SetHP(player->GetTotalHP()); + player->SetPower(player->GetTotalPower()); + + //revive at zone safe coords + if (!revive_point) + { + LogWrite(CCLIENT__WARNING, 0, "Client", "No Revive Point! Spawning player at safe coordinates!"); + x = GetCurrentZone()->GetSafeX(); + y = GetCurrentZone()->GetSafeY(); + z = GetCurrentZone()->GetSafeZ(); + heading = GetCurrentZone()->GetSafeHeading(); + zone_desc = GetCurrentZone()->GetZoneDescription(); + location_name = "Zone Safe Point"; + Zone(GetCurrentZone()->GetZoneName(), false); + } + else + { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Sending player to chosen Revive Point."); + x = revive_point->x; + y = revive_point->y; + z = revive_point->z; + heading = revive_point->heading; + zone_desc = database.GetZoneDescription(revive_point->zone_id); + location_name = revive_point->location_name.c_str(); + Zone(GetCurrentZone()->GetZoneName(), false); + } + player->SetX(x); + player->SetY(y); + player->SetZ(z); + player->SetHeading(heading); + + LogWrite(CCLIENT__DEBUG, 0, "Client", "Attempt Revive @ %s, %.2f, %.2f, %.2f, %.2f, HP: %i, Pow: %i, %s", + zone_desc.c_str(), + player->GetX(), + player->GetY(), + player->GetZ(), + player->GetHeading(), + player->GetHP(), + player->GetPower(), + location_name); + + //player->ClearEverything(); + Save(); + + if (revive_point && revive_point->zone_id != GetCurrentZone()->GetZoneID() && revive_point->zone_id != 0) + { + string zone_name = database.GetZoneName(revive_point->zone_id); + if (zone_name.length() == 0) + { + LogWrite(CCLIENT__WARNING, 0, "Client", "Unable to zone player to revive zone ID '%u', using current zone's safe coords.", revive_point->zone_id); + x = GetCurrentZone()->GetSafeX(); + y = GetCurrentZone()->GetSafeY(); + z = GetCurrentZone()->GetSafeZ(); + heading = GetCurrentZone()->GetSafeHeading(); + location_name = "Zone Safe Point"; + } + else + { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Sending player to revive zone ID '%u', using current zone's safe coords.", revive_point->zone_id); + location_name = revive_point->location_name.c_str(); + //player->ClearEverything(); + Save(); + Zone(zone_name.c_str(), false); + } + } + + zone_desc = GetCurrentZone()->GetZoneDescription(); + Message(CHANNEL_NARRATIVE, "Reviving in %s at %s.", zone_desc.c_str(), location_name); + player->SetSpawnType(4); + if (version > 373) { + packet = configReader.getStruct("WS_CancelMoveObjectMode", GetVersion()); + if (packet) + { + QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + + packet = configReader.getStruct("WS_TeleportWithinZone", GetVersion()); + if (packet) + { + packet->setDataByName("x", x); + packet->setDataByName("y", y); + packet->setDataByName("z", z); + QueuePacket(packet->serialize()); + safe_delete(packet); + } + + SendControlGhost(); + + packet = configReader.getStruct("WS_SetPOVGhostCmd", GetVersion()); + if (packet) + { + packet->setDataByName("spawn_id", 0xFFFFFFFF); + QueuePacket(packet->serialize()); + safe_delete(packet); + } + + if(rule_manager.GetGlobalRule(R_Combat, EnableSpiritShards)->GetBool()) + { + NPC* shard = player->InstantiateSpiritShard(origX, origY, origZ, origHeading, origGridID, originalZone); + + if(shard->GetSpawnScript() && strlen(shard->GetSpawnScript()) > 0) + originalZone->CallSpawnScript(shard, SPAWN_SCRIPT_PRESPAWN); + + originalZone->RemoveSpawn(player, false, true, true, true, true); + + originalZone->AddSpawn(shard); + + if(shard->GetSpawnScript() && strlen(shard->GetSpawnScript()) > 0) + originalZone->CallSpawnScript(shard, SPAWN_SCRIPT_SPAWN); + } + + m_resurrect.writelock(__FUNCTION__, __LINE__); + if (current_rez.active) + current_rez.should_delete = true; + m_resurrect.releasewritelock(__FUNCTION__, __LINE__); +} + +void Client::SendControlGhost(int32 send_id, int8 unknown2) { + PacketStruct* packet = configReader.getStruct("WS_SetControlGhost", GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", send_id); + packet->setDataByName("speed", GetPlayer()->GetSpeed()); + packet->setDataByName("size", 0.51); + packet->setDataByName("unknown2", unknown2); + packet->setDataByName("air_speed", player->GetAirSpeed()); + EQ2Packet* app = packet->serialize(); + QueuePacket(app); + safe_delete(packet); + } +} + +void Client::SendCharInfo() { + EQ2Packet* app; + + player->SetEquippedItemAppearances(); + + ClientPacketFunctions::SendCharacterData(this); + + SendCharPOVGhost(); + + SendControlGhost(player->GetIDWithPlayerSpawn(player), 255); + + //sending bad spawn packet? + + //SendAchievementsList(); + //if (version > 561) + //ClientPacketFunctions::SendHousingList(this); + + ClientPacketFunctions::SendCharacterSheet(this); + ClientPacketFunctions::SendTraitList(this);// moved from below + ClientPacketFunctions::SendAbilities(this); + + ClientPacketFunctions::SendSkillBook(this); + if (!IsReloadingZone() && !player->IsResurrecting() && GetVersion() >= 546) { + ClientPacketFunctions::SendUpdateSpellBook(this); + } + else { + player->SetResurrecting(false); + } + + GetCurrentZone()->AddSpawn(player); + if(IsReloadingZone() && (zoning_x || zoning_y || zoning_z)) { + GetPlayer()->SetX(zoning_x); + GetPlayer()->SetY(zoning_y); + GetPlayer()->SetZ(zoning_z); + GetPlayer()->SetHeading(zoning_h); + + EQ2Packet* packet = GetPlayer()->Move(zoning_x, zoning_y, zoning_z, GetVersion(), zoning_h); + QueuePacket(packet); + } + //SendCollectionList(); + Guild* guild = player->GetGuild(); + if (guild) + guild->GuildMemberLogin(this, firstlogin); + + app = player->GetPlayerItemList()->serialize(GetPlayer(), GetVersion()); + if (app) { + LogWrite(CCLIENT__PACKET, 0, "Client", "Dump/Print Packet in func: %s, line: %i", __FUNCTION__, __LINE__); + //DumpPacket(app); + QueuePacket(app); + } + app = player->GetEquipmentList()->serialize(GetVersion(), player); + if (app) { + QueuePacket(app); + } + + app = player->GetAppearanceEquipmentList()->serialize(GetVersion(), player); + if (app) { + QueuePacket(app); + } + + vector* items = player->GetPlayerItemList()->GetItemsFromBagID(-3); // bank items + if (items && items->size() > 0) { + for (int32 i = 0; i < items->size(); i++) { + EQ2Packet* outapp = items->at(i)->serialize(GetVersion(), false, GetPlayer()); + + LogWrite(CCLIENT__PACKET, 0, "Client", "Dump/Print Packet in func: %s, line: %i", __FUNCTION__, __LINE__); + //DumpPacket(outapp); + QueuePacket(outapp); + } + } + + if (firstlogin && (app = chat.GetWorldChannelList(this)) != NULL) + QueuePacket(app); + + safe_delete(items); + items = player->GetPlayerItemList()->GetItemsFromBagID(-4); //shared bank items + if (items && items->size() > 0) { + for (int32 i = 0; i < items->size(); i++) + QueuePacket(items->at(i)->serialize(GetVersion(), false, GetPlayer())); + } + safe_delete(items); + if (version >= 373) { + SendTitleUpdate(); + } + + GetPlayer()->UpdateWeapons(); + if(!GetPlayer()->IsReturningFromLD()) { + database.LoadBuyBacks(this); + } + if (version > 561) + master_aa_list.DisplayAA(this, 0, 0); + + string zone_motd = GetCurrentZone()->GetZoneMOTD(); + if (zone_motd.length() > 0 && zone_motd[0] != ' ') { + string zone_motd_send = "Zone MOTD: " + zone_motd; + SimpleMessage(CHANNEL_NARRATIVE, zone_motd_send.c_str()); + } + const char* zone_script = world.GetZoneScript(GetCurrentZone()->GetZoneID()); + if (zone_script && lua_interface) + lua_interface->RunZoneScript(zone_script, "player_entry", GetCurrentZone(), GetPlayer()); + this->client_zoning = false; + this->zoning_id = 0; + this->zoning_instance_id = 0; + SetZoningDestination(nullptr); + + if (player->GetHP() < player->GetTotalHP() || player->GetPower() < player->GetTotalPower()) + GetCurrentZone()->AddDamagedSpawn(player); + + if (firstlogin) + firstlogin = false; + + player->ClearProcs(); + items = player->GetEquippedItemList(); + if (items && items->size() > 0) { + for (int32 i = 0; i < items->size(); i++) { + Item* item = items->at(i); + if (item && item->GetItemScript() && lua_interface) + lua_interface->RunItemScript(item->GetItemScript(), "equipped", item, player); + } + } + + //Allow this player to change their last name if they meet the level requirement + if (!player->get_character_flag(CF_ENABLE_CHANGE_LASTNAME) && player->GetLevel() >= rule_manager.GetGlobalRule(R_Player, MinLastNameLevel)->GetInt8()) + player->set_character_flag(CF_ENABLE_CHANGE_LASTNAME); + + safe_delete(items); + + if (!player->Alive()) + DisplayDeadWindow(); + + ClientPacketFunctions::SendLocalizedTextMessage(this); + + if (GetCurrentZone()->GetInstanceID()) + { + PlayerHouse* ph = world.GetPlayerHouseByInstanceID(GetCurrentZone()->GetInstanceID()); + if (ph) { + //HouseZone* hz = world.GetHouseZone(ph->house_id); + string name = string(GetPlayer()->GetName()); + if (name.compare(ph->player_name) == 0) + SetHasOwnerOrEditAccess(true); + } + } + + bool groupMentor = false; + GetPlayer()->group_id = rejoin_group_id; + if(!world.RejoinGroup(this, rejoin_group_id)) + GetPlayer()->group_id = 0; + else + { + Entity* ent = world.GetGroupManager()->IsPlayerInGroup(rejoin_group_id, GetPlayer()->GetGroupMemberInfo()->mentor_target_char_id); + if(ent && ent->IsPlayer()) + { + GetPlayer()->SetMentorStats(ent->GetLevel(), ent->GetID(), false); + groupMentor = true; + } + } + + if(!groupMentor) + GetPlayer()->SetMentorStats(GetPlayer()->GetLevel(), 0, false); + + if(!GetPlayer()->IsReturningFromLD()) { + database.LoadCharacterSpellEffects(GetCharacterID(), this, DB_TYPE_MAINTAINEDEFFECTS); + database.LoadCharacterSpellEffects(GetCharacterID(), this, DB_TYPE_SPELLEFFECTS); + } + else { + Spawn* pet_spawn = nullptr; + if(GetPlayer()->GetPet()) + pet_spawn = GetPlayer()->GetPet(); + else if(GetPlayer()->GetCharmedPet()) + pet_spawn = GetPlayer()->GetCharmedPet(); + else if(GetPlayer()->GetCosmeticPet()) + pet_spawn = GetPlayer()->GetCosmeticPet(); + else if(GetPlayer()->GetDeityPet()) + pet_spawn = GetPlayer()->GetDeityPet(); + + if(pet_spawn) { + GetPlayer()->GetInfoStruct()->set_pet_id(GetPlayer()->GetIDWithPlayerSpawn(pet_spawn)); + } + } + + GetPlayer()->SetSaveSpellEffects(false); + GetPlayer()->SetCharSheetChanged(true); + GetPlayer()->SetReturningFromLD(false); +} + +void Client::SendZoneSpawns() { + //Allows us to place spawns almost anywhere + if (version > 373) { + uchar blah[] = { 0x00,0x3C,0x1C,0x46,0x00,0x3C,0x1C,0x46,0x00,0x3C,0x1C,0x46 }; + EQ2Packet* app = new EQ2Packet(OP_MoveableObjectPlacementCriteri, blah, sizeof(blah)); + QueuePacket(app); + } + + ClientPacketFunctions::SendSkillSlotMappings(this); + ClientPacketFunctions::SendGameWorldTime(this); + GetCurrentZone()->StartZoneInitialSpawnThread(this); +} + +void Client::SendCharPOVGhost() { + bool use_ghost_pov = false; + PacketStruct* set_pov = configReader.getStruct("WS_SetPOVGhostCmd", GetVersion()); + int32 ghost_id = 0; + if (set_pov) { + if(pov_ghost_spawn_id) { + Spawn* spawn = GetCurrentZone()->GetSpawnByID(pov_ghost_spawn_id); + ghost_id = player->GetIDWithPlayerSpawn(spawn); + if(spawn) { + use_ghost_pov = true; + } + } + if(use_ghost_pov) { + set_pov->setDataByName("spawn_id", ghost_id); + } + else { + set_pov->setDataByName("spawn_id", player->GetIDWithPlayerSpawn(player)); + } + EQ2Packet* app_pov = set_pov->serialize(); + QueuePacket(app_pov); + safe_delete(set_pov); + } + +} + +void Client::SendZoneInfo() { + ZoneServer* zone = GetCurrentZone(); + if (zone) { + EQ2Packet* packet = zone->GetZoneInfoPacket(this); + QueuePacket(packet); + if (version > 561) { + PacketStruct* fog_packet = configReader.getStruct("WS_FogInit", GetVersion()); + + LogWrite(CCLIENT__PACKET, 0, "Client", "Dump/Print Packet in func: %s, line: %i", __FUNCTION__, __LINE__); +#if EQDEBUG >= 9 + fog_packet->PrintPacket(); +#endif + + if (fog_packet) { + database.LoadFogInit(zone->GetZoneFile(), fog_packet); + QueuePacket(fog_packet->serialize()); + safe_delete(fog_packet); + } + + zone->SendFlightPathsPackets(this); + } + } + /* + uchar blah[] ={0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x00,0x01,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF + ,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF + ,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x10,0x49,0x2B,0x62,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00}; + EQ2Packet* appA = new EQ2Packet(OP_GuildUpdateMsg, blah, sizeof(blah)); + QueuePacket(appA); + + uchar blahA[] ={0x45,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00 + ,0x00,0x10,0xE2,0x10,0x6C,0x00,0x00,0x00,0x00}; + EQ2Packet* appB = new EQ2Packet(OP_KeymapDataMsg, blahA, sizeof(blahA)); + QueuePacket(appB); + */ + + LogWrite(CCLIENT__DEBUG, 0, "Client", "SendFriendList"); + SendFriendList(); + LogWrite(CCLIENT__DEBUG, 0, "Client", "SendIgnoreList"); + SendIgnoreList(); +} + +void Client::SendDefaultGroupOptions() { + /* + 0 - loot method + 1 - loot items rarity + 2 - Auto split coin + 4 - default yell method + 6 - group autolock + 7 - solo autolock + */ + PacketStruct* default_options = configReader.getStruct("WS_DefaultGroupOptions", GetVersion()); + if (default_options) { + default_options->setDataByName("loot_method", GetPlayer()->GetInfoStruct()->get_group_loot_method()); + default_options->setDataByName("loot_items_rarity", GetPlayer()->GetInfoStruct()->get_group_loot_items_rarity()); + default_options->setDataByName("auto_split_coin", GetPlayer()->GetInfoStruct()->get_group_auto_split()); + default_options->setDataByName("default_yell_method", GetPlayer()->GetInfoStruct()->get_group_default_yell()); + default_options->setDataByName("group_autolock", GetPlayer()->GetInfoStruct()->get_group_autolock()); + default_options->setDataByName("default_group_lock_method", GetPlayer()->GetInfoStruct()->get_group_lock_method()); + if(GetVersion() > 561) { + default_options->setDataByName("solo_autolock", GetPlayer()->GetInfoStruct()->get_group_solo_autolock()); + default_options->setDataByName("auto_loot_method", GetPlayer()->GetInfoStruct()->get_group_auto_loot_method()); + } + EQ2Packet* app7 = default_options->serialize(); + QueuePacket(app7); + safe_delete(default_options); + } +} + +bool Client::HandlePacket(EQApplicationPacket* app) { + bool ret = true; + //cout << "INCOMING PACKET!!!!!!!: " << app->GetOpcodeName() << endl; + //DumpPacket(app); +#if EQDEBUG >= 9 + LogWrite(PACKET__DEBUG, 9, "Packet", "[EQDEBUG] Received Packet:"); + DumpPacket(app, true); +#endif + + EmuOpcode opcode = app->GetOpcode(); + +#if EQDEBUG >= 9 + const char* name = app->GetOpcodeName(); + if (name) + cout << name; + else + cout << "Unknown"; + cout << " Packet: OPCode: 0x" << hex << setw(2) << setfill('0') << app->GetOpcode() << dec << ", size: " << setw(5) << setfill(' ') << app->Size() << endl; + DumpPacket(app); +#endif + + //if (opcode != OP_UpdatePositionMsg) { + // LogWrite(PACKET__DEBUG, 0, "opcode %s received", app->GetOpcodeName()); + //} + + if (!connected_to_zone && opcode != OP_LoginByNumRequestMsg) + { + opcode = _maxEmuOpcode; // skip since this is not a valid packet, sent before we allowed the login + } + + switch (opcode) { + case _maxEmuOpcode: + break; + case OP_LoginByNumRequestMsg: { + LogWrite(OPCODE__DEBUG, 0, "Opcode", "Opcode 0x%X (%i): OP_LoginByNumRequestMsg", opcode, opcode); + + PacketStruct* request; + request = configReader.getStruct("LoginByNumRequest", 1); + if (request) { + if(request->LoadPacketData(app->pBuffer, app->size)) { + // test the original location of Version for clients older than 1212 + version = request->getType_int16_ByName("version"); + + if (version == 0 || version >= 1208 || EQOpcodeManager.count(GetOpcodeVersion(version)) == 0) { + // must be new client data version method, re-fetch the packet + safe_delete(request); + request = configReader.getStruct("LoginByNumRequest", 1208); + + if (request && request->LoadPacketData(app->pBuffer, app->size)) { + // Xinux suggests using an INT16 here. Our first new version = 57000 + version = request->getType_int16_ByName("version"); + } + else { + LogWrite(LOGIN__ERROR, 0, "Login", "Nasty Horrible things happening. Tell a dev asap! Version: %i", version); + break; + } + } + + if (EQOpcodeManager.count(GetOpcodeVersion(version)) == 0) { + LogWrite(WORLD__ERROR, 0, "World", "Incompatible version: %i", version); + ClientPacketFunctions::SendLoginDenied(this); + + /* reset version and protect server from trying to send packets out to a bad client + ** cause of Dec 6th/Dec 7th 2023 crash + ** Client::MakeSpawnChangePacket + ** int16 opcode_val = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqUpdateGhostCmd); <-- crashes pulling opcode with bad version + */ + version = 546; + ready_for_updates = false; + ready_for_spawns = false; + return false; + } + + int32 account_id = request->getType_int32_ByName("account_id"); + int32 access_code = request->getType_int32_ByName("access_code"); + + if (!HandleNewLogin(account_id, access_code)) + return false; + } + } + safe_delete(request); + break; + } + case OP_DefaultGroupOptionsMsg: { + PacketStruct* packet = configReader.getStruct("WS_DefaultGroupOptions", GetVersion()); + if (packet) { + if (packet->LoadPacketData(app->pBuffer, app->size)) { + packet->PrintPacket(); + int8 loot_method = packet->getType_int8_ByName("loot_method"); + int8 loot_items_rarity = packet->getType_int8_ByName("loot_items_rarity"); + int8 auto_split_coin = packet->getType_int8_ByName("auto_split_coin"); + int8 default_yell_method = packet->getType_int8_ByName("default_yell_method"); + int8 autolock = packet->getType_int8_ByName("group_autolock"); + int8 group_lock_method = packet->getType_int8_ByName("default_group_lock_method"); + int8 solo_autolock = packet->getType_int8_ByName("solo_autolock"); + int8 auto_loot_method = 0; + + if (GetVersion() > 561) { + auto_loot_method = packet->getType_int8_ByName("auto_loot_method"); + if (auto_loot_method > AutoLootMode::METHOD_DECLINE) + auto_loot_method = AutoLootMode::METHOD_DECLINE; + } + GetPlayer()->GetInfoStruct()->set_group_loot_method(loot_method); + GetPlayer()->GetInfoStruct()->set_group_loot_items_rarity(loot_items_rarity); + GetPlayer()->GetInfoStruct()->set_group_auto_split(auto_split_coin); + GetPlayer()->GetInfoStruct()->set_group_default_yell(default_yell_method); + GetPlayer()->GetInfoStruct()->set_group_autolock(autolock); + GetPlayer()->GetInfoStruct()->set_group_lock_method(group_lock_method); + GetPlayer()->GetInfoStruct()->set_group_solo_autolock(solo_autolock); + GetPlayer()->GetInfoStruct()->set_group_auto_loot_method(auto_loot_method); + + database.insertCharacterProperty(this, CHAR_PROPERTY_GROUPLOOTMETHOD, (char*)std::to_string(loot_method).c_str()); + database.insertCharacterProperty(this, CHAR_PROPERTY_GROUPLOOTITEMRARITY, (char*)std::to_string(loot_items_rarity).c_str()); + database.insertCharacterProperty(this, CHAR_PROPERTY_GROUPAUTOSPLIT, (char*)std::to_string(auto_split_coin).c_str()); + database.insertCharacterProperty(this, CHAR_PROPERTY_GROUPDEFAULTYELL, (char*)std::to_string(default_yell_method).c_str()); + database.insertCharacterProperty(this, CHAR_PROPERTY_GROUPAUTOLOCK, (char*)std::to_string(autolock).c_str()); + database.insertCharacterProperty(this, CHAR_PROPERTY_GROUPLOCKMETHOD, (char*)std::to_string(group_lock_method).c_str()); + database.insertCharacterProperty(this, CHAR_PROPERTY_GROUPSOLOAUTOLOCK, (char*)std::to_string(solo_autolock).c_str()); + database.insertCharacterProperty(this, CHAR_PROPERTY_AUTOLOOTMETHOD, (char*)std::to_string(auto_loot_method).c_str()); + + if (this->GetPlayer()->GetGroupMemberInfo() && this->GetPlayer()->GetGroupMemberInfo()->leader) + { + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + PlayerGroup* group = world.GetGroupManager()->GetGroup(this->GetPlayer()->GetGroupMemberInfo()->group_id); + if (group) + { + GroupOptions goptions; + goptions.loot_method = loot_method; + goptions.loot_items_rarity = loot_items_rarity; + goptions.auto_split = auto_split_coin; + goptions.default_yell = default_yell_method; + goptions.group_autolock = autolock; + goptions.solo_autolock = solo_autolock; + goptions.auto_loot_method = auto_loot_method; + group->SetDefaultGroupOptions(&goptions); + } + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } + } + safe_delete(packet); + } + break; + } + case OP_MapRequest: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_MapRequest", opcode, opcode); + PacketStruct* packet = configReader.getStruct("WS_MapRequest", GetVersion()); + if (packet && app->size > 2 && GetCurrentZone()) { + if(packet->LoadPacketData(app->pBuffer, app->size)) { + PacketStruct* fog_packet = configReader.getStruct("WS_FogInit", GetVersion()); + if (fog_packet) { + LogWrite(PACKET__DEBUG, 0, "Packet", "In OP_MapRequest: Fog Packet"); + database.LoadFogInit(packet->getType_EQ2_16BitString_ByName("zone").data, fog_packet); + fog_packet->setDataByName("unknown1", 1); + fog_packet->setDataByName("unknown3", 1); + + LogWrite(CCLIENT__PACKET, 0, "Client", "Dump/Print Packet in func: %s, line: %i", __FUNCTION__, __LINE__); + //fog_packet->PrintPacket(); + QueuePacket(fog_packet->serialize()); + safe_delete(fog_packet); + } + } + safe_delete(packet); + } + break; + } + case OP_RequestCampMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_RequestCampMsg", opcode, opcode); + + PacketStruct* request = configReader.getStruct("WS_RequestCamp", GetVersion()); + if (request && request->LoadPacketData(app->pBuffer, app->size)) { + LogWrite(CCLIENT__DEBUG, 0, "CClient", "Client '%s' (%u) is camping...", GetPlayer()->GetName(), GetPlayer()->GetCharacterID()); + LogWrite(CCLIENT__DEBUG, 0, "CClient", "WS_RequestCamp - quit: %i, camp_desktop: %i, camp_char_select: %i, (to) char_name: %s", + request->getType_int8_ByName("quit"), + request->getType_int8_ByName("camp_desktop"), + request->getType_int16_ByName("camp_char_select"), + (request->getType_EQ2_16BitString_ByName("char_name").data.length() > 0) ? request->getType_EQ2_16BitString_ByName("char_name").data.c_str() : ""); + + //DumpPacket(app->pBuffer, app->size); + //request->PrintPacket(); + + if (!camp_timer) { + int16 camp_time = 20; // default if rule cannot be found + if (GetAdminStatus() >= 100) + camp_time = rule_manager.GetGlobalRule(R_World, GMCampTimer)->GetInt16(); + else + camp_time = rule_manager.GetGlobalRule(R_World, PlayerCampTimer)->GetInt16(); + + PacketStruct* response = configReader.getStruct("WS_Camp", GetVersion()); + if (response) { + bool disconnect = false; + if (request->getType_int8_ByName("camp_desktop") == 1 && request->getType_int8_ByName("quit") == 1) { + // Command: /camp desktop + // Command: /quit + response->setDataByName("camp_desktop", 1); + disconnect = true; + } + else { + // Command: /camp + response->setDataByName("camp_desktop", request->getType_int8_ByName("camp_desktop")); + response->setDataByName("camp_char_select", request->getType_int16_ByName("camp_char_select")); + response->setDataByName("seconds", camp_time); + } + + camp_timer = new Timer(camp_time * 1000); + camp_timer->Enable(); + + if (request->getType_EQ2_16BitString_ByName("char_name").data.length() > 0) { + // /camp {char_name} + response->setDataByName("char_name", request->getType_EQ2_16BitString_ByName("char_name").data.c_str()); + } + else if (request->getType_int8_ByName("camp_desktop") == 0 && request->getType_int16_ByName("camp_char_select") == 0) { + // /camp (go back to char selection screen) + response->setDataByName("char_name", " "); + response->setDataByName("camp_char_select", 1); + } + + LogWrite(CCLIENT__DEBUG, 0, "CClient", "WS_Camp - seconds: %i, camp_desktop: %i, camp_char_select: %i, (to) char_name: %s", + response->getType_int8_ByName("seconds"), + response->getType_int8_ByName("camp_desktop"), + response->getType_int8_ByName("camp_char_select"), + (response->getType_EQ2_16BitString_ByName("char_name").data.length() > 0) ? response->getType_EQ2_16BitString_ByName("char_name").data.c_str() : ""); + + // JA: trying to recognize /camp vs LD (see ZoneServer::ClientProcess()) + if ((player->GetActivityStatus() & ACTIVITY_STATUS_CAMPING) == 0) + player->SetActivityStatus(player->GetActivityStatus() + ACTIVITY_STATUS_CAMPING); + //response->PrintPacket(); + QueuePacket(response->serialize()); + safe_delete(response); + if (disconnect) + Disconnect(); + } + } + } + safe_delete(request); + break; + } + case OP_StoodMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_StoodMsg", opcode, opcode); + if (camp_timer) + { + // JA: clear camping flag + if ((player->GetActivityStatus() & ACTIVITY_STATUS_CAMPING) > 0) + player->SetActivityStatus(player->GetActivityStatus() - ACTIVITY_STATUS_CAMPING); + safe_delete(camp_timer); + EQ2Packet* outapp = new EQ2Packet(OP_CampAbortedMsg, 0, 0); + QueuePacket(outapp); + } + player->SetTempVisualState(0); + break; + } + case OP_StandMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_StandMsg", opcode, opcode); + if (camp_timer) + { + // JA: clear camping flag + if ((player->GetActivityStatus() & ACTIVITY_STATUS_CAMPING) > 0) + player->SetActivityStatus(player->GetActivityStatus() - ACTIVITY_STATUS_CAMPING); + safe_delete(camp_timer); + EQ2Packet* outapp = new EQ2Packet(OP_CampAbortedMsg, 0, 0); + QueuePacket(outapp); + } + player->SetTempVisualState(539); + break; + } + case OP_SitMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_SitMsg", opcode, opcode); + player->SetTempVisualState(538); + break; + } + case OP_SatMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_SatMsg", opcode, opcode); + player->SetTempVisualState(540); + break; + } + case OP_QuestJournalOpenMsg: + case OP_QuestJournalInspectMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_QuestJournalOpenMsg, OP_QuestJournalInspectMsg", opcode, opcode); + if (app->size < sizeof(int32)) + break; + int32 quest_id = 0; + memcpy(&quest_id, app->pBuffer, sizeof(int32)); + GetPlayer()->SendQuest(quest_id); + break; + } + case OP_QuestJournalSetVisibleMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_QuestJournalSetVisibleMsg", opcode, opcode); + PacketStruct* packet = configReader.getStruct("WS_QuestJournalVisible", GetVersion()); + if (packet) { + if(packet->LoadPacketData(app->pBuffer, app->size)) { + int32 quest_id = packet->getType_int32_ByName("quest_id"); + bool hidden = packet->getType_int8_ByName("visible") == 1 ? false : true; + GetPlayer()->MPlayerQuests.readlock(__FUNCTION__, __LINE__); + map* player_quests = player->GetPlayerQuests(); + if (player_quests) { + if (player_quests->count(quest_id) > 0) + player_quests->at(quest_id)->SetHidden(hidden); + else + LogWrite(CCLIENT__ERROR, 0, "Client", "OP_QuestJournalSetVisibleMsg error: Player does not have quest with id of %u", quest_id); + } + else + LogWrite(CCLIENT__ERROR, 0, "Client", "OP_QuestJournalSetVisibleMsg error: Unable to get player(%s) quests", player->GetName()); + GetPlayer()->MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + } + safe_delete(packet); + } + break; + } + case OP_MacroUpdateMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_MacroUpdateMsg", opcode, opcode); + PacketStruct* macro_update = configReader.getStruct("WS_MacroUpdate", GetVersion()); + if (macro_update) { + if(macro_update->LoadPacketData(app->pBuffer, app->size)) { + vector* update = new vector; + int8 number = macro_update->getType_int8_ByName("number"); + int16 icon = macro_update->getType_int16_ByName("icon"); + string name = macro_update->getType_EQ2_8BitString_ByName("name").data; + int8 count = macro_update->getType_int8_ByName("macro_count"); + + if (GetVersion() <= 373) { + update->push_back(macro_update->getType_EQ2_8BitString_ByName("command").data); + } + else { + for (int8 i = 0; i < count; i++) { + char tmp_command[15] = { 0 }; + sprintf(tmp_command, "command_%i", i); + update->push_back(macro_update->getType_EQ2_16BitString_ByName(tmp_command).data); + } + } + if (name.length() == 0) + database.UpdateCharacterMacro(GetCharacterID(), number, 0, icon, update); + else + database.UpdateCharacterMacro(GetCharacterID(), number, name.c_str(), icon, update); + safe_delete(update); + } + safe_delete(macro_update); + } + break; + } + case OP_DialogSelectMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_DialogSelectMsg", opcode, opcode); + PacketStruct* packet = configReader.getStruct("WS_DialogSelect", GetVersion()); + if (packet) { + if(packet->LoadPacketData(app->pBuffer, app->size)) { + int32 conversation_id = packet->getType_int32_ByName("conversation_id"); + int32 response_index = packet->getType_int32_ByName("response"); + HandleDialogSelectMsg(conversation_id, response_index); + } + } + safe_delete(packet); + break; + } + case OP_CancelMoveObjectModeMsg: { + SetSpawnPlacementMode(Client::ServerSpawnPlacementMode::DEFAULT); + if (GetTempPlacementSpawn()) + { + Spawn* tmp = GetTempPlacementSpawn(); + SetTempPlacementSpawn(nullptr); + SetPlacementUniqueItemID(0); + GetCurrentZone()->RemoveSpawn(tmp, true, false, true, true, true); + break; // break out early if we are tied to a temp spawn + } + + // if we are moving some other object? other use-cases not covered + break; + } + case OP_PositionMoveableObject: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_PositionMoveableObject", opcode, opcode); + PacketStruct* place_object = configReader.getStruct("WS_PlaceMoveableObject", GetVersion()); + if (place_object && place_object->LoadPacketData(app->pBuffer, app->size)) { + Spawn* spawn = 0; + + if (GetTempPlacementSpawn()) + spawn = GetTempPlacementSpawn(); + else + spawn = GetPlayer()->GetSpawnWithPlayerID(place_object->getType_int32_ByName("spawn_id")); + + if (!spawn) { + SimpleMessage(CHANNEL_COLOR_YELLOW, "Unable to find spawn."); + break; + } + else if (GetCurrentZone()->GetInstanceType() == PERSONAL_HOUSE_INSTANCE && !HasOwnerOrEditAccess()) + { + SimpleMessage(CHANNEL_COLOR_RED, "This is not your home!"); + break; + } + + + int32 uniqueID = spawn->GetPickupUniqueItemID(); + if(uniqueID) { + Item* uniqueItem = GetPlayer()->item_list.GetItemFromUniqueID(uniqueID); + if(uniqueItem && uniqueItem->CheckFlag2(HOUSE_LORE) && GetCurrentZone()->HouseItemSpawnExists(uniqueItem->details.item_id)) { + Message(CHANNEL_COLOR_RED, "Item %s is house lore and you cannot place another.", uniqueItem->name.c_str()); + break; + } + } + + // handles instantiation logic + adding to zone of a new house object + PopulateHouseSpawn(place_object); + + float newHeading = place_object->getType_float_ByName("heading") + 180; + + char query[256]; + + switch (GetSpawnPlacementMode()) + { + case ServerSpawnPlacementMode::OPEN_HEADING: + { + if (spawn && spawn->IsWidget()) + { + Widget* widget = (Widget*)spawn; + widget->SetOpenHeading(newHeading); + widget->SetIncludeHeading(true); + + spawn->position_changed = true; + + _snprintf(query, 256, "open_heading=%f,include_heading=1", newHeading); + if (database.UpdateSpawnWidget(widget->GetWidgetID(), query)) + SimpleMessage(CHANNEL_COLOR_YELLOW, "Successfully saved widget open heading information."); + } + else + SimpleMessage(CHANNEL_COLOR_YELLOW, "Spawn is not widget, unable to set close heading information."); + break; + } + case ServerSpawnPlacementMode::CLOSE_HEADING: + { + if (spawn && spawn->IsWidget()) + { + Widget* widget = (Widget*)spawn; + widget->SetClosedHeading(newHeading); + widget->SetIncludeHeading(true); + + spawn->position_changed = true; + _snprintf(query, 256, "closed_heading=%f,include_heading=1", newHeading); + if (database.UpdateSpawnWidget(widget->GetWidgetID(), query)) + SimpleMessage(CHANNEL_COLOR_YELLOW, "Successfully saved widget close heading information."); + + if (spawn->GetSpawnLocationID()) + { + Query query; + query.RunQuery2(Q_INSERT, "update spawn_location_placement set heading = %f where id = %u", newHeading, spawn->GetSpawnLocationID()); + } + } + else + SimpleMessage(CHANNEL_COLOR_YELLOW, "Spawn is not widget, unable to set close heading information."); + break; + } + default: + { + spawn->SetX(place_object->getType_float_ByName("x")); + spawn->SetY(place_object->getType_float_ByName("y")); + spawn->SetZ(place_object->getType_float_ByName("z")); + spawn->SetHeading(newHeading); + spawn->SetSpawnOrigX(spawn->GetX()); + spawn->SetSpawnOrigY(spawn->GetY()); + spawn->SetSpawnOrigZ(spawn->GetZ()); + spawn->SetSpawnOrigHeading(spawn->GetHeading()); + if (spawn->GetSpawnLocationID() > 0 && database.UpdateSpawnLocationSpawns(spawn)) + SimpleMessage(CHANNEL_COLOR_YELLOW, "Successfully saved spawn information."); + else if (spawn->GetSpawnLocationID() > 0) + SimpleMessage(CHANNEL_COLOR_YELLOW, "Error saving spawn information, see console window for details."); + } + } + + PopulateHouseSpawnFinalize(); + + SetSpawnPlacementMode(Client::ServerSpawnPlacementMode::DEFAULT); + } + safe_delete(place_object); + break; + } + case OP_CampAbortedMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_CampAbortedMsg", opcode, opcode); + if (camp_timer) + { + // JA: clear camping flag + if ((player->GetActivityStatus() & ACTIVITY_STATUS_CAMPING) > 0) + player->SetActivityStatus(player->GetActivityStatus() - ACTIVITY_STATUS_CAMPING); + safe_delete(camp_timer); + EQ2Packet* outapp = new EQ2Packet(OP_CampAbortedMsg, 0, 0); + QueuePacket(outapp); + } + break; + } + case OP_DoneLoadingUIResourcesMsg: { + if(GetVersion() <= 561) { + ClientPacketFunctions::SendUpdateSpellBook(this); + } + // need to quickly flash the DoF client the rest of their inventory + if(GetVersion() <= 561) { + EQ2Packet* item_app = player->GetPlayerItemList()->serialize(GetPlayer(), GetVersion()); + if (item_app) { + QueuePacket(item_app); + } + } + + EQ2Packet* app = new EQ2Packet(OP_DoneLoadingUIResourcesMsg, 0, 0); + QueuePacket(app); + if(!player_loading_complete) + { + const char* zone_script = world.GetZoneScript(GetCurrentZone()->GetZoneID()); + if (zone_script && lua_interface) + lua_interface->RunZoneScript(zone_script, "player_loadcomplete", GetCurrentZone(), GetPlayer()); + player_loading_complete = true; + } + break; + } + case OP_DoneLoadingZoneResourcesMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_DoneLoadingZoneResourcesMsg", opcode, opcode); + SendZoneSpawns(); + break; + } + case OP_DefaultGroupOptionsRequestMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_DefaultGroupOptionsRequestMsg", opcode, opcode); + SendDefaultGroupOptions(); + break; + } + case OP_DoneLoadingEntityResourcesMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_DoneLoadingEntityResourcesMsg", opcode, opcode); + if (!IsReadyForSpawns()) { + if(GetPlayer()->GetMap()) { + auto loc = glm::vec3(GetPlayer()->GetX(), GetPlayer()->GetZ(), GetPlayer()->GetY()); + uint32 GridID = 0; + float new_z = GetPlayer()->FindBestZ(loc, nullptr, &GridID); + GetPlayer()->SetLocation(GridID); + } + SetReadyForSpawns(true); + } + player->CalculateApplyWeight(); + SendCharInfo(); + GetPlayer()->GetZone()->GetSpellProcess()->SendSpellBookUpdate(this); + pos_update.Start(); + quest_pos_timer.Start(); + break; + } + case OP_LootItemsRequestMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_LootItemsRequestMsg", opcode, opcode); + HandleLootItemRequestPacket(app); + break; + } + case OP_StoppedLootingMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_StoppedLootingMsg", opcode, opcode); + if (app->size < sizeof(int32)) + break; + + int32 loot_id = 0; + memcpy(&loot_id, app->pBuffer, sizeof(int32)); + Spawn* spawn = GetCurrentZone()->GetSpawnByID(loot_id); + if(spawn) { + spawn->SetSpawnLootWindowCompleted(GetPlayer()->GetID()); + spawn->SetLooterSpawnID(0); + } + break; + } + case OP_WaypointSelectMsg: { + PacketStruct* packet = configReader.getStruct("WS_WaypointSelect", GetVersion()); + if (packet) { + if (packet->LoadPacketData(app->pBuffer, app->size)) { + int32 selection = packet->getType_int32_ByName("selection"); + if (selection > 0) { + SelectWaypoint(selection); + } + } + } + safe_delete(packet); + break; + } + case OP_KnowledgeWindowSlotMappingMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_KnowledgeWindowSlotMappingMsg", opcode, opcode); + PacketStruct* packet = configReader.getStruct("WS_SpellSlotMapping", GetVersion()); + if (packet) { + if (packet->LoadPacketData(app->pBuffer, app->size)) { + int num_updates = packet->getType_int16_ByName("spell_count"); + int32 spell_id = 0; + int16 slot_id = 0; + char tmp_spell_id[15]; + char tmp_slot[15]; + for (int i = 0; i < num_updates; i++) { + memset(tmp_spell_id, 0, 15); + memset(tmp_slot, 0, 15); + sprintf(tmp_spell_id, "spell_id_%i", i); + sprintf(tmp_slot, "slot_id_%i", i); + spell_id = packet->getType_int32_ByName(tmp_spell_id); + if (spell_id > 0) { + slot_id = packet->getType_int16_ByName(tmp_slot); + SpellBookEntry* spell = player->GetSpellBookSpell(spell_id); + if (spell && spell->slot != slot_id) { + spell->slot = slot_id; + spell->save_needed = true; + } + } + } + } + safe_delete(packet); + } + //SendKnowledgeWindowSlot(); + break; + } + case OP_ReadyToZoneMsg: { + if (!IsReadyForSpawns()) + { + LogWrite(WORLD__INFO, 0, "World", "OP_ReadyToZone: Player %s is logging into zone, skipping disconnect."); + } + else + { + if(zoning_destination) { + SetCurrentZone(zoning_destination); + } + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_ReadyToZoneMsg", opcode, opcode); + bool succeed_override_zone = true; + if(!GetCurrentZone()) { + LogWrite(WORLD__ERROR, 0, "World", "OP_ReadyToZone: Player %s attempting to zone and zone is not there! Emergency boot!", player->GetName()); + if(zoning_instance_id) { + ZoneServer* zone = zone_list.GetByInstanceID(zoning_instance_id, zoning_id, true); + if(!zone) { + LogWrite(WORLD__WARNING, 0, "World", "OP_ReadyToZone: Emergency boot failed for %s, unable to get zoneserver instance id %u zone id %u.", player->GetName(), zoning_instance_id, zoning_id); + succeed_override_zone = false; + } + else { + SetCurrentZone(zone); + LogWrite(WORLD__WARNING, 0, "World", "OP_ReadyToZone: Unique instance has been created for %s through emergency boot!", player->GetName()); + + } + } + else if(zoning_id) { + + ZoneServer* zone = zone_list.Get(zoning_id, true, true); + if(!zone) { + LogWrite(WORLD__WARNING, 0, "World", "OP_ReadyToZone: Emergency boot failed for %s, unable to get zoneserver zone id %u.", player->GetName(), zoning_id); + succeed_override_zone = false; + } + else { + SetCurrentZone(zone); + LogWrite(WORLD__WARNING, 0, "World", "OP_ReadyToZone: Unique zone has been created for %s through emergency boot!", player->GetName()); + } + } + } + + if (client_zoning) + LogWrite(WORLD__INFO, 0, "World", "OP_ReadyToZone: Player %s zoning to %s", player->GetName(), GetCurrentZone()->GetZoneName()); + else + LogWrite(WORLD__ERROR, 0, "World", "OP_ReadyToZone: Player %s attempting to zone without server authorization.", player->GetName()); + Disconnect(); + } + break; + } + case OP_ClientFellMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_ClientFellMsg (ouch!)", opcode, opcode); + PacketStruct* request = configReader.getStruct("WS_ClientFell", GetVersion()); + if (request && request->LoadPacketData(app->pBuffer, app->size)) { + float height = request->getType_float_ByName("height"); + /*int32 spawn_id = request->getType_int32_ByName("spawn_id"); + if(GetPlayer()->GetSpawnWithPlayerID(spawn_id) != GetPlayer()){ + cout << "Error: " << GetPlayer()->GetName() << " called ClientFell with an invalid ID of: " << spawn_id << endl; + break; + }*/ + float safe_height = 13.0f; + float safe_skill_with_bonus = GetPlayer()->CalculateSkillWithBonus("Safe Fall", ITEM_STAT_SAFE_FALL, true); + if (safe_skill_with_bonus > 0.0f) + safe_height += (1 + safe_skill_with_bonus) / 5; + + if (height > safe_height) { + int16 damage = (int16)ceil((height - safe_height) * 125); + if (height >= 80) + damage = 30000; + //cout << "Detected fall height:" << height << " damage:" << damage << endl; + if (damage > 0) { + GetPlayer()->TakeDamage(damage); + if (GetPlayer()->GetPlayerStatisticValue(STAT_PLAYER_HIGHEST_FALLING_HIT) < damage) + GetPlayer()->UpdatePlayerStatistic(STAT_PLAYER_HIGHEST_FALLING_HIT, damage, true); + if (!GetPlayer()->GetInvulnerable()) + GetPlayer()->SetCharSheetChanged(true); + GetCurrentZone()->SendDamagePacket(0, GetPlayer(), DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE, GetPlayer()->GetInvulnerable() ? DAMAGE_PACKET_RESULT_INVULNERABLE : DAMAGE_PACKET_RESULT_SUCCESSFUL, DAMAGE_PACKET_DAMAGE_TYPE_FALLING, damage, 0); + if (GetPlayer()->GetHP() == 0) { + GetCurrentZone()->KillSpawn(false, GetPlayer(), 0); + } + } + } + } + safe_delete(request); + break; + } + case OP_MapFogDataUpdateMsg: { + + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_MapFogDataUpdateMsg", opcode, opcode); + LogWrite(MISC__TODO, 3, "TODO", "Handle (OP_MapFogDataUpdateMsg), ignoring it for now\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__); + break; + } + case OP_SelectZoneTeleporterDestinatio: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_SelectZoneTeleporterDestinatio", opcode, opcode); + ProcessTeleportLocation(app); + break; + } + case OP_SendLatestRequestMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_SendLatestRequestMsg", opcode, opcode); + if(GetVersion() < 60085) { + // this does not exist in newer clients like AoM, confirmed to exist in DoF, other clients will need review at a later time + uchar blah25[] = { 0x01 }; + EQ2Packet* app25 = new EQ2Packet(OP_ClearDataMsg, blah25, sizeof(blah25)); + QueuePacket(app25); + } + break; + } + case OP_RequestRecipeDetailsMsg: { + PacketStruct* packet = configReader.getStruct("WS_RequestRecipeDetail", GetVersion()); + if (packet) { + if(packet->LoadPacketData(app->pBuffer, app->size)) { + vector recipes; + int32 recipe_id = 0; + char recipe_prop_name[30]; + int32 num_recipes = packet->getType_int32_ByName("num_recipes"); + // WS_RecipeDetails + for (int32 i = 0; i < num_recipes; i++) { + memset(recipe_prop_name, 0, 30); + snprintf(recipe_prop_name, 30, "recipe_id_%i", i); + recipe_id = packet->getType_int32_ByName(recipe_prop_name); + if(recipe_id > 0) { + recipes.push_back(recipe_id); + } + } + SendRecipeDetails(&recipes); + } + + safe_delete(packet); + } + break; + } + case OP_ShowCreateFromRecipeUIMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_ShowCreateFromRecipeUIMsg", opcode, opcode); + break; + /*uchar blah[] ={0x09,0x0e,0x00,0x51,0x75,0x65,0x65,0x6e,0x27,0x73,0x20,0x43,0x6f,0x6c,0x6f,0x6e + ,0x79,0x00,0x00,0x00,0x00,0x40,0x40,0xff,0xff,0xff}; + EQ2Packet* app = new EQ2Packet(OP_EncounterBrokenMsg, blah, sizeof(blah)); + QueuePacket(app); + + uchar blah2[] = {0x00,0x00,0xff,0xff,0xff,0xff}; + app = new EQ2Packet(OP_CreateCharFromCBBRequestMsg, blah2, sizeof(blah2)); + QueuePacket(app); + + uchar blah3[] ={0x09,0x17,0x00,0x5c,0x23,0x46,0x46,0x45,0x34,0x30,0x30,0x20,0x51,0x75,0x65,0x65 + ,0x6e,0x27,0x73,0x20,0x43,0x6f,0x6c,0x6f,0x6e,0x79,0x00,0x00,0x00,0x00,0xa0,0x40 + ,0xff,0xff,0xff}; + app = new EQ2Packet(OP_CreateCharFromCBBRequestMsg, blah3, sizeof(blah3)); + QueuePacket(app); + + uchar blah4[] ={0x0b,0x00,0x21,0x00,0x00,0x00,0x1d,0x81,0x42,0x17,0x81,0x42,0x17,0x81,0x42,0x17 + ,0x81,0x42,0x17,0x81,0x42,0x17,0x81,0x42,0x17,0x81,0x42,0x17,0x81,0x42,0x17,0x81 + ,0x42,0x17,0x81,0x42,0x17,0x81,0x42}; + app = new EQ2Packet(OP_UpdateSpellBookMsg, blah4, sizeof(blah4)); + QueuePacket(app); + uchar blah5[] ={0x00,0x00}; + app = new EQ2Packet(OP_RecipeDetailsMsg, blah5, sizeof(blah5)); + QueuePacket(app); + break;*/ + //player->GetPlayerInfo()->GetInfo()->cur_power = 100; + //EQ2Packet* app = player->GetPlayerInfo()->serialize(1); + //QueuePacket(app); + } + case OP_BeginItemCreationMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_BeginItemCreationMsg", opcode, opcode); + //DumpPacket(app->pBuffer, app->size); + PacketStruct* packet = configReader.getStruct("WS_BeginItemCreation", GetVersion()); + if (packet) { + if(packet->LoadPacketData(app->pBuffer, app->size)) { + Recipe* recipe = master_recipe_list.GetRecipe(GetPlayer()->GetCurrentRecipe()); + if(recipe) { + int32 item = 0; + int8 qty = 0; + vector> items; + char tmp_item_id[30]; + if(GetVersion() > 1193) { + int8 num_primary_selected_items = packet->getType_int8_ByName("num_primary_selected_items"); + for (int8 i = 0; i < num_primary_selected_items; i++) { + memset(tmp_item_id, 0, 30); + sprintf(tmp_item_id, "primary_selected_item_id_%i", i); + item = packet->getType_int32_ByName(tmp_item_id); + sprintf(tmp_item_id, "primary_selected_item_qty_%i", i); + qty = packet->getType_int16_ByName(tmp_item_id); + if (item > 0) + items.push_back(make_pair(item,qty)); + item = 0; + } + } + else { + item = packet->getType_int32_ByName("primary_component_id"); + qty = 1; + if (item > 0) + items.push_back(make_pair(item,qty)); + } + int8 build_components = packet->getType_int8_ByName("num_build_components"); + + if(GetVersion() > 1193) { + for (int8 i = 0; i < build_components; i++) { + memset(tmp_item_id, 0, 30); + sprintf(tmp_item_id, "num_selected_items_%i", i); + int8 num_selected_items = packet->getType_int8_ByName(tmp_item_id); + for (int8 j = 0; j < num_selected_items; j++) { + memset(tmp_item_id, 0, 30); + sprintf(tmp_item_id, "selected_id%i_%i", i,j); + item = packet->getType_int32_ByName(tmp_item_id); + sprintf(tmp_item_id, "selected_qty%i_%i", i, j); + qty = packet->getType_int16_ByName(tmp_item_id); + if (item > 0) + items.push_back(make_pair(item, qty)); + + item = 0; + } + } + } + else { + for (int8 i = 0; i < build_components; i++) { + memset(tmp_item_id, 0, 30); + sprintf(tmp_item_id, "component_id_%i", i); + int32 item = packet->getType_int32_ByName(tmp_item_id); + sprintf(tmp_item_id, "component_qty_%i", i); + qty = packet->getType_int32_ByName(tmp_item_id); + if (item > 0) + items.push_back(make_pair(item, qty)); + } + } + if(GetVersion() > 1193) { + int8 num_fuel_items = packet->getType_int8_ByName("num_fuel_items"); + for (int8 i = 0; i < num_fuel_items; i++) { + memset(tmp_item_id, 0, 30); + sprintf(tmp_item_id, "fuel_id_%i", i); + item = packet->getType_int32_ByName(tmp_item_id); + sprintf(tmp_item_id, "fuel_qty_%i", i); + qty = packet->getType_int16_ByName(tmp_item_id); + if (item > 0) + items.push_back(make_pair(item, qty)); + item = 0; + } + } + else { + + item = packet->getType_int32_ByName("fuel_id"); + qty = packet->getType_int16_ByName("fuel_qty"); + if (item > 0) + items.push_back(make_pair(item, qty)); + } + + GetCurrentZone()->GetTradeskillMgr()->BeginCrafting(this, items); + } + else { + LogWrite(CCLIENT__ERROR, 0, "Client", "Client '%s' (%u) attempted to call OP_BeginItemCreationMsg, but with no recipe selected.", GetPlayer()->GetName(), GetPlayer()->GetCharacterID()); + } + } + safe_delete(packet); + } + break; + } + case OP_StopItemCreationMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_StopItemCreationMsg", opcode, opcode); + //DumpPacket(app->pBuffer, app->size); + GetCurrentZone()->GetTradeskillMgr()->StopCrafting(this); + break; + } + case OP_SysClient: + case OP_SignalMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_SysClient/OP_SignalMsg", opcode, opcode); + + PacketStruct* packet = configReader.getStruct("WS_Signal", 1); + if (packet) { + if(packet->LoadPacketData(app->pBuffer, app->size)) { + EQ2_16BitString str = packet->getType_EQ2_16BitString_ByName("signal"); + if (strcmp(str.data.c_str(), "sys_client_avatar_ready") == 0) { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Client '%s' (%u) is ready for spawn updates.", GetPlayer()->GetName(), GetPlayer()->GetCharacterID()); + SetReloadingZone(false); + if(GetPlayer()->IsDeletedSpawn()) { + GetPlayer()->SetDeletedSpawn(false); + } + ResetZoningCoords(); + SetReadyForUpdates(); + GetPlayer()->SendSpawnChanges(true); + ProcessStateCommands(); + GetPlayer()->changed = true; + GetPlayer()->info_changed = true; + GetPlayer()->vis_changed = true; + player_pos_changed = true; + GetPlayer()->AddChangedZoneSpawn(); + ProcessZoneIgnoreWidgets(); + if (version <= 561) { + master_trait_list.ChooseNextTrait(this); + } + + const char* zone_script = world.GetZoneScript(GetPlayer()->GetZone()->GetZoneID()); + + if (zone_script && lua_interface) { + lua_interface->RunZoneScript(zone_script, "enter_location", GetPlayer()->GetZone(), GetPlayer(), GetPlayer()->GetLocation()); + } + + if (GetPlayer()->GetHP() < GetPlayer()->GetTotalHP() || GetPlayer()->GetPower() < GetPlayer()->GetTotalPower()) + GetCurrentZone()->AddDamagedSpawn(GetPlayer()); + } + else { + LogWrite(CCLIENT__WARNING, 0, "Client", "Player %s reported SysClient/SignalMsg state %s.", GetPlayer()->GetName(), str.data.c_str()); + } + const char* zone_script = world.GetZoneScript(player->GetZone()->GetZoneID()); + if (zone_script && lua_interface) + { + lua_interface->RunZoneScript(zone_script, "signal_changed", player->GetZone(), player, 0, str.data.c_str()); + } + } + safe_delete(packet); + } + break; + } + case OP_EntityVerbsRequestMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_EntityVerbsRequestMsg", opcode, opcode); + HandleVerbRequest(app); + break; + } + case OP_EntityVerbsVerbMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_EntityVerbsVerbMsg", opcode, opcode); + PacketStruct* packet = configReader.getStruct("WS_EntityVerbsVerb", GetVersion()); + if (packet) { + if(packet->LoadPacketData(app->pBuffer, app->size)) { + int32 spawn_id = packet->getType_int32_ByName("spawn_id"); + player->SetTarget(player->GetSpawnWithPlayerID(spawn_id)); + Spawn* spawn = player->GetTarget(); + if (spawn && !spawn->IsNPC() && !spawn->IsPlayer()) { + string command = packet->getType_EQ2_16BitString_ByName("command").data; + + if (!HandleHouseEntityCommands(spawn, spawn_id, command)) + { + if (EntityCommandPrecheck(spawn, command.c_str())) { + if (spawn->IsGroundSpawn()) + ((GroundSpawn*)spawn)->HandleUse(this, command); + else if (spawn->IsObject()) + ((Object*)spawn)->HandleUse(this, command); + else if (spawn->IsWidget()) + ((Widget*)spawn)->HandleUse(this, command); + else if (spawn->IsSign()) + ((Sign*)spawn)->HandleUse(this, command); + } + } + } + else { + EQ2_16BitString command = packet->getType_EQ2_16BitString_ByName("command"); + if (command.size > 0) { + string command_name = command.data; + if (command_name.find(" ") < 0xFFFFFFFF) { + if (GetVersion() <= 561) { //this version uses commands in the form "Buy From Merchant" instead of buy_from_merchant + string::size_type pos = command_name.find(" "); + while(pos != string::npos){ + command_name.replace(pos, 1, "_"); + pos = command_name.find(" "); + } + } + else + command_name = command_name.substr(0, command_name.find(" ")); + } + int32 handler = commands.GetCommandHandler(command_name.c_str()); + if (handler != 0xFFFFFFFF) { + if (command.data == command_name) { + command.data = ""; + command.size = 0; + } + else { + command.data = command.data.substr(command.data.find(" ") + 1); + command.size = command.data.length(); + } + commands.Process(handler, &command, this); + } + else { + if (spawn && spawn->IsNPC()) { + if (EntityCommandPrecheck(spawn, command.data.c_str())) { + if (!((NPC*)spawn)->HandleUse(this, command.data)) { + command_name = command.data; + string::size_type pos = command_name.find(" "); + while (pos != string::npos) { + command_name.replace(pos, 1, "_"); + pos = command_name.find(" "); + } + if (!((NPC*)spawn)->HandleUse(this, command_name)) { //convert the spaces to underscores and see if that makes a difference + LogWrite(WORLD__ERROR, 0, "World", "Unhandled command in OP_EntityVerbsVerbMsg: %s", command.data.c_str()); + } + } + } + } + else + LogWrite(WORLD__ERROR, 0, "World", "Unknown command in OP_EntityVerbsVerbMsg: %s", command.data.c_str()); + } + } + } + } + safe_delete(packet); + } + break; + } + case OP_SkillInfoRequest: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_SkillInfoRequest", opcode, opcode); + HandleSkillInfoRequest(app); + break; + } + case OP_UpdateTargetMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_UpdateTargetMsg", opcode, opcode); + int16 index = 0; + memcpy(&index, app->pBuffer, sizeof(int16)); + if (index == 0xFFFF) + GetPlayer()->SetTarget(0); + else { + Spawn* spawn = GetPlayer()->GetSpawnByIndex(index); + if(spawn) + GetPlayer()->SetTarget(spawn); + else { + LogWrite(PLAYER__ERROR, 1, "Player", "Player %s tried to target %u index, but that index was not valid.", GetPlayer()->GetName(), index); + } + } + if (GetPlayer()->GetTarget()) + GetCurrentZone()->CallSpawnScript(GetPlayer()->GetTarget(), SPAWN_SCRIPT_TARGETED, GetPlayer()); + //player->SetTarget((int16*)app->pBuffer); + break; + } + case OP_ExamineInfoRequestMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_ExamineInfoRequestMsg", opcode, opcode); + HandleExamineInfoRequest(app); + break; + } + case OP_QuickbarUpdateMsg: + //case OP_QuickbarAddMsg: + { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_QuickbarUpdateMsg, OP_QuickbarAddMsg", opcode, opcode); + HandleQuickbarUpdateRequest(app); + break; + } + case OP_PredictionUpdateMsg: { + LogWrite(OPCODE__DEBUG, 7, "Opcode", "Opcode 0x%X (%i): OP_PredictionUpdateMsg from %s", opcode, opcode, GetPlayer()->GetName()); + if (version <= 561) { + int8 offset = 9; + if (app->pBuffer[0] == 0xFF) + offset += 2; + if (app->size > offset) { + if (player->IsCasting()) { + float distance = 0; + float x = player->GetX(); + float y = player->GetY(); + float z = player->GetZ(); + player->PrepareIncomingMovementPacket(app->size - offset, app->pBuffer + offset, version); + distance = player->GetDistance(x, y, z, false); + if (distance > .5) + current_zone->Interrupted(player, 0, SPELL_ERROR_INTERRUPTED, false, true); + } + else + player->PrepareIncomingMovementPacket(app->size - offset, app->pBuffer + offset, version); + player_pos_changed = true; + + GetPlayer()->changed = true; + GetPlayer()->info_changed = true; + GetPlayer()->vis_changed = true; + GetPlayer()->AddChangedZoneSpawn(); + //DumpPacket(app); + } + } + else { + EQ2Packet* app = new EQ2Packet(OP_PredictionUpdateMsg, 0, 0); + QueuePacket(app); + LogWrite(CCLIENT__PACKET, 0, "Client", "Dump/Print Packet in func: %s, line: %i", __FUNCTION__, __LINE__); + } + break; + } + case OP_RemoteCmdMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_RemoteCmdMsg", opcode, opcode); + if (app->size > 0) { + EQ2_CommandString remote(app->pBuffer, app->size); + + LogWrite(PACKET__DEBUG, 1, "Packet", "RemoteCmdMsg Packet dump:"); +#if EQDEBUG >= 9 + DumpPacket(app); +#endif + commands.Process(remote.handler, &remote.command, this); + } + else //bad client, disconnect + Disconnect(); + break; + } + case OP_CancelSpellCast: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_CancelSpellCast", opcode, opcode); + current_zone->Interrupted(player, 0, 0, true); + SimpleMessage(CHANNEL_SPELLS_OTHER, "You stop casting."); + break; + } + case OP_UpdatePositionMsg: { + LogWrite(OPCODE__DEBUG, 7, "Opcode", "Opcode 0x%X (%i): OP_UpdatePositionMsg from %s", opcode, opcode, GetPlayer()->GetName()); + int8 offset = 13; + if (app->pBuffer[0] == 0xFF) + offset += 2; + if (app->size > offset) { + if (player->IsCasting()) { + float distance = 0; + float x = player->GetX(); + float y = player->GetY(); + float z = player->GetZ(); + player->PrepareIncomingMovementPacket(app->size - offset, app->pBuffer + offset, version); + distance = player->GetDistance(x, y, z, false); + if (distance > .5) + current_zone->Interrupted(player, 0, SPELL_ERROR_INTERRUPTED, false, true); + } + else + player->PrepareIncomingMovementPacket(app->size - offset, app->pBuffer + offset, version); + player_pos_changed = true; + + GetPlayer()->changed = true; + GetPlayer()->info_changed = true; + GetPlayer()->vis_changed = true; + GetPlayer()->AddChangedZoneSpawn(); + LogWrite(CCLIENT__PACKET, 0, "Client", "Dump/Print Packet in func: %s, line: %i", __FUNCTION__, __LINE__); + //DumpPacket(app); + } + break; + } + case OP_MailSendMessageMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_MailSendMessageMsg", opcode, opcode); + HandleSentMail(app); + break; + } + case OP_StopTrackingMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_StopTrackingMsg", opcode, opcode); + player->GetZone()->RemovePlayerTracking(player, TRACKING_STOP); + break; + } + case OP_BeginTrackingMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_BeginTrackingMsg", opcode, opcode); + PacketStruct* packet = configReader.getStruct("WS_BeginTracking", GetVersion()); + if (packet) { + if(packet->LoadPacketData(app->pBuffer, app->size)) { + int32 spawn_id = packet->getType_int32_ByName("spawn_id"); + Spawn* spawn = player->GetSpawnWithPlayerID(spawn_id); + if (spawn) { + AddWaypoint(spawn->GetName(), WAYPOINT_CATEGORY_TRACKING, spawn_id); + BeginWaypoint(spawn->GetName(), spawn->GetX(), spawn->GetY(), spawn->GetZ()); + player->GetZone()->RemovePlayerTracking(player, TRACKING_CLOSE_WINDOW); + } + } + safe_delete(packet); + } + break; + } + case OP_BioUpdateMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_BioUpdateMsg", opcode, opcode); + PacketStruct* packet = configReader.getStruct("WS_BioUpdate", GetVersion()); + if (packet) { + if(packet->LoadPacketData(app->pBuffer, app->size)) { + player->SetBiography(packet->getType_EQ2_16BitString_ByName("biography").data); + } + safe_delete(packet); + } + break; + } + case OP_RewardPackMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_RewardPackMsg", opcode, opcode); + + /* This logging is still here because I remember another system using this packet and just want to make sure we can figure out that it's being sent + when we come across it (scatman) */ + const char* name = app->GetOpcodeName(); + if (name) + LogWrite(WORLD__DEBUG, 0, "World", "%s Received OP_RewardPackMsg %04i", name, app->GetRawOpcode()); + else + LogWrite(WORLD__DEBUG, 0, "World", "Received OP_RewardPackMsg %04i", app->GetRawOpcode()); + //DumpPacket(app); + PacketStruct* packet = configReader.getStruct("WS_RewardPackMsg", GetVersion()); + if (packet) { + if(packet->LoadPacketData(app->pBuffer, app->size)) { + string recruiter_name = packet->getType_EQ2_16BitString_ByName("recruiter_name").data; + + /* Player has contacted a guild recruiter */ + if (recruiter_name.length() > 0) { + Guild* guild = guild_list.GetGuild(packet->getType_int32_ByName("guild_id")); + Client* recruiter = zone_list.GetClientByCharName(recruiter_name); + if (recruiter && guild) { + Message(CHANNEL_GUILD_EVENT, "Contact request sent to %s of %s.", recruiter->GetPlayer()->GetName(), guild->GetName()); + recruiter->Message(CHANNEL_GUILD_EVENT, "%s [%u %s], [0 Unskilled] (%s) is requesting to speak to YOU about joining the guild.", player->GetName(), player->GetLevel(), classes.GetClassNameCase(player->GetAdventureClass()).c_str(), races.GetRaceNameCase(player->GetRace())); + recruiter->PlaySound("ui_guild_page"); + } + } + /* New picture taken for guild recruiting */ + else { + //DumpPacket(app->pBuffer, app->size); + int32 guild_id = 0; + int16 picture_data_size = 0; + unsigned char* recruiter_picture_data = 0; + memcpy(&guild_id, app->pBuffer + 4, sizeof(int32)); + memcpy(&picture_data_size, app->pBuffer + 15, sizeof(int16)); + Guild* guild = guild_list.GetGuild(guild_id); + if (guild) { + GuildMember* gm = guild->GetGuildMember(player); + if (gm) { + safe_delete_array(gm->recruiter_picture_data); + recruiter_picture_data = new unsigned char[picture_data_size]; + for (int16 i = 0; i < picture_data_size; i++) + memcpy(recruiter_picture_data + i, app->pBuffer + 17 + i, 2); + gm->recruiter_picture_data = recruiter_picture_data; + gm->recruiter_picture_data_size = picture_data_size; + guild->SetMemberSaveNeeded(true); + } + } + } + } + safe_delete(packet); + } + break; + } + case OP_PetOptions: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_PetOptions", opcode, opcode); + Spawn* target = player->GetTarget(); + PacketStruct* packet = configReader.getStruct("WS_PetOptions", GetVersion()); + if (packet && target && (target == player->GetPet() || target == player->GetCharmedPet() || target == player->GetDeityPet() || target == player->GetCosmeticPet())) { + bool change = false; + if(packet->LoadPacketData(app->pBuffer, app->size)) { + string name = packet->getType_EQ2_16BitString_ByName("pet_name").data; + if (strlen(name.c_str()) != 0 && SetPetName(name.c_str())) { + target->SetName(name.c_str()); + GetCurrentZone()->SendUpdateTitles(target); + change = true; + } + + int8 pet_behavior = player->GetInfoStruct()->get_pet_behavior(); + // Check protect self setting and update if needed + if (packet->getType_int8_ByName("protect_self") == 1) { + if ((pet_behavior & 2) == 0) { + player->GetInfoStruct()->set_pet_behavior(pet_behavior + 2); + change = true; + } + } + else { + if ((pet_behavior & 2) != 0) { + player->GetInfoStruct()->set_pet_behavior(pet_behavior - 2); + change = true; + } + } + + // Check protect master setting and update if needed + if (packet->getType_int8_ByName("protect_master") == 1) { + if ((pet_behavior & 1) == 0) { + player->GetInfoStruct()->set_pet_behavior(pet_behavior + 1); + change = true; + } + } + else { + if ((pet_behavior & 1) != 0) { + player->GetInfoStruct()->set_pet_behavior(pet_behavior - 1); + change = true; + } + } + + int8 pet_movement = player->GetInfoStruct()->get_pet_movement(); + // Check stay/follow setting and update if needed + if (packet->getType_int8_ByName("stay_follow_toggle") == 1) { + if (pet_movement != 2) { + player->GetInfoStruct()->set_pet_movement(2); + change = true; + } + } + else { + if (pet_movement != 1) { + player->GetInfoStruct()->set_pet_movement(1); + change = true; + } + } + + // Ranged/Melee settings are not implemented yet + + if (change) + player->SetCharSheetChanged(true); + + } + safe_delete(packet); + } + break; + } + case OP_RecipeBook: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_RecipeBook", opcode, opcode); + SendRecipeList(); + break; + } + case OP_BuyPlayerHouseMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_BuyPlayerHouseMsg", opcode, opcode); + //DumpPacket(app); + int64 bank_money = GetPlayer()->GetBankCoinsPlat(); + PacketStruct* packet = configReader.getStruct("WS_BuyHouse", GetVersion()); + if (packet) { + if(packet->LoadPacketData(app->pBuffer, app->size)) { + int64 house_id = 0; + if(GetVersion() <= 561) { + house_id = packet->getType_int32_ByName("house_id"); + } + else { + house_id = packet->getType_int64_ByName("house_id"); + } + HouseZone* hz = world.GetHouseZone(house_id); + if (hz) { + bool got_bank_money = BankHasCoin(hz->cost_coin); + int8 disable_alignment_req = rule_manager.GetGlobalRule(R_Player, DisableHouseAlignmentRequirement)->GetInt8(); + std::vector houses = world.GetAllPlayerHouses(GetCharacterID()); + if (houses.size() > 24) + { + SimpleMessage(CHANNEL_COLOR_YELLOW, "You already own 25 houses and may not own another."); + safe_delete(packet); + break; + } + if(disable_alignment_req && hz->alignment > 0 && hz->alignment != GetPlayer()->GetAlignment()) + { + std::string req = "You must be of "; + if (hz->alignment == 1) + req.append("Good"); + else + req.append("Evil"); + req.append(" alignment to purchase this house"); + SimpleMessage(CHANNEL_COLOR_YELLOW, req.c_str()); + safe_delete(packet); + break; + } + int32 status_req = hz->cost_status; + int32 available_status = player->GetInfoStruct()->get_status_points(); + if (status_req <= available_status && (!hz->cost_coin || (hz->cost_coin && player->RemoveCoins(hz->cost_coin)))) + + { + player->GetInfoStruct()->subtract_status_points(status_req); + ZoneServer* instance_zone = zone_list.GetByInstanceID(0, hz->zone_id, false, false); + int32 upkeep_due = Timer::GetUnixTimeStamp() + 604800; // 604800 = 7 days + int64 unique_id = database.AddPlayerHouse(GetPlayer()->GetCharacterID(), hz->id, instance_zone->GetInstanceID(), upkeep_due); + world.AddPlayerHouse(GetPlayer()->GetCharacterID(), hz->id, unique_id, instance_zone->GetInstanceID(), upkeep_due, 0, 0, GetPlayer()->GetName()); + //ClientPacketFunctions::SendHousingList(this); + PlayerHouse* ph = world.GetPlayerHouseByUniqueID(unique_id); + ClientPacketFunctions::SendBaseHouseWindow(this, hz, ph, GetVersion() <= 561 ? house_id : this->GetPlayer()->GetID()); + PlaySound("coin_cha_ching"); + } + else if (status_req <= available_status && got_bank_money == 1) { + player->GetInfoStruct()->subtract_status_points(status_req); + bool bankwithdrawl = BankWithdrawalNoBanker(hz->cost_coin); + + //this should NEVER happen since we check with got_bank_money, however adding it here should something go nutty. + if (bankwithdrawl == 0) { + PlaySound("buy_failed"); + SimpleMessage(CHANNEL_COLOR_RED, "There was an error in bankwithdrawl function."); + safe_delete(packet); + break; + } + + ZoneServer* instance_zone = zone_list.GetByInstanceID(0, hz->zone_id, false, false); + int32 upkeep_due = Timer::GetUnixTimeStamp() + 604800; // 604800 = 7 days + int64 unique_id = database.AddPlayerHouse(GetPlayer()->GetCharacterID(), hz->id, instance_zone->GetInstanceID(), upkeep_due); + world.AddPlayerHouse(GetPlayer()->GetCharacterID(), hz->id, unique_id, instance_zone->GetInstanceID(), upkeep_due, 0, 0, GetPlayer()->GetName()); + PlayerHouse* ph = world.GetPlayerHouseByUniqueID(unique_id); + ClientPacketFunctions::SendBaseHouseWindow(this, hz, ph, GetVersion() <= 561 ? house_id : this->GetPlayer()->GetID()); + PlaySound("coin_cha_ching"); + } + else + { + SimpleMessage(CHANNEL_COLOR_YELLOW, "You do not have enough money to purchase the house."); + PlaySound("buy_failed"); + } + } + } + } + + safe_delete(packet); + break; + } + case OP_EnterHouseMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_EnterHouseMsg", opcode, opcode); + //DumpPacket(app); + PacketStruct* packet = configReader.getStruct("WS_EnterHouse", GetVersion()); + if (packet) { + if(packet->LoadPacketData(app->pBuffer, app->size)) { + PlayerHouse* ph = nullptr; + HouseZone* hz = nullptr; + int64 house_id = 0; + int32 spawn_index = 0; + + if(GetVersion() <= 561) { + spawn_index = packet->getType_int32_ByName("house_id"); + } + else { + house_id = packet->getType_int64_ByName("house_id"); + } + + ZoneServer* house = GetHouseZoneServer(spawn_index, house_id); + if (house) { + Zone(house, true); + } + } + } + + safe_delete(packet); + break; + } + case OP_PayHouseUpkeepMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_PayHouseUpkeepMsg", opcode, opcode); + + PacketStruct* packet = configReader.getStruct("WS_PayUpkeep", GetVersion()); + + if (packet) { + if(packet->LoadPacketData(app->pBuffer, app->size)) { + int64 house_id = 0; + + if(GetVersion() <= 561) { + house_id = packet->getType_int32_ByName("house_id"); + } + else { + house_id = packet->getType_int64_ByName("house_id"); + } + HouseZone* hz = nullptr; + PlayerHouse* ph = world.GetPlayerHouse(this, GetVersion() <= 561 ? house_id : 0, GetVersion() > 561 ? house_id : 0, &hz); + if (ph) + { + if (!hz) + { + Message(CHANNEL_COLOR_YELLOW, "HouseZone ID %u does NOT exist!", ph->house_id); + safe_delete(packet); + break; + } + + int32 upkeep_due = Timer::GetUnixTimeStamp() + 604800; + if (((sint64)ph->upkeep_due - (sint64)Timer::GetUnixTimeStamp()) > 0) + { + upkeep_due = ph->upkeep_due + 604800; // 604800 = 7 days + + if (upkeep_due > (Timer::GetUnixTimeStamp() + 7257600)) // 84 days max upkeep to pay https://eq2.zam.com/wiki/Housing_%28EQ2%29#Upkeep + { + Message(CHANNEL_COLOR_YELLOW, "You cannot pay more than 3 months of upkeep."); + PlaySound("buy_failed"); + safe_delete(packet); + break; + } + } + bool escrowChange = false; + int64 statusReq = hz->upkeep_status; + int64 tmpRecoverStatus = 0; + if(ph->escrow_status && statusReq >= ph->escrow_status ) + { + escrowChange = true; + tmpRecoverStatus = ph->escrow_status; + statusReq -= ph->escrow_status; + ph->escrow_status = 0; + } + else if (ph->escrow_status && statusReq && statusReq <= ph->escrow_status) + { + escrowChange = true; + ph->escrow_status -= statusReq; + tmpRecoverStatus = statusReq; + statusReq = 0; + } + + int64 coinReq = hz->upkeep_coin; + int64 tmpRecoverCoins = 0; + if (ph->escrow_coins && coinReq >= ph->escrow_coins) // more required to upkeep than in escrow, subtract what we have left + { + escrowChange = true; + tmpRecoverCoins = ph->escrow_coins; + coinReq -= ph->escrow_coins; + ph->escrow_coins = 0; + } + else if (ph->escrow_coins && coinReq && coinReq <= ph->escrow_coins) + { + escrowChange = true; + // more than enough in escrow, subtract and make our cost 0! + ph->escrow_coins -= coinReq; + tmpRecoverCoins = coinReq; + coinReq = 0; + } + + int32 available_status_points = player->GetInfoStruct()->get_status_points(); + if(!statusReq || (statusReq && statusReq <= available_status_points)) + { + if(coinReq && player->RemoveCoins(coinReq)) + coinReq = 0; + + if(!coinReq && statusReq && player->GetInfoStruct()->subtract_status_points(statusReq)) + statusReq = 0; + } + bool got_bank_money = BankHasCoin(hz->upkeep_coin); + if (!coinReq && !statusReq) // TODO: Need option to take from bank if player does not have enough coin on them + { + database.AddHistory(ph, GetPlayer()->GetName(), "Paid Upkeep", Timer::GetUnixTimeStamp(), hz->upkeep_coin, 0, 0); + + if (escrowChange) + database.UpdateHouseEscrow(ph->house_id, ph->instance_id, ph->escrow_coins, ph->escrow_status); + + ph->upkeep_due = upkeep_due; + database.SetHouseUpkeepDue(GetCharacterID(), ph->house_id, ph->instance_id, ph->upkeep_due); + //ClientPacketFunctions::SendHousingList(this); + ClientPacketFunctions::SendBaseHouseWindow(this, hz, ph, GetVersion() <= 561 ? house_id : this->GetPlayer()->GetID()); + PlaySound("coin_cha_ching"); + } + else if (!statusReq && got_bank_money == 1) { + bool bankwithdrawl = BankWithdrawalNoBanker(hz->upkeep_coin); + + //this should NEVER happen since we check with got_bank_money, however adding it here should something go nutty. + if (bankwithdrawl == 0) { + PlaySound("buy_failed"); + SimpleMessage(CHANNEL_COLOR_RED, "There was an error in bankwithdrawl function."); + safe_delete(packet); + break; + } + + database.AddHistory(ph, GetPlayer()->GetName(), "Paid Upkeep", Timer::GetUnixTimeStamp(), hz->upkeep_coin, 0, 0); + + if (escrowChange) + database.UpdateHouseEscrow(ph->house_id, ph->instance_id, ph->escrow_coins, ph->escrow_status); + + ph->upkeep_due = upkeep_due; + database.SetHouseUpkeepDue(GetCharacterID(), ph->house_id, ph->instance_id, ph->upkeep_due); + ClientPacketFunctions::SendBaseHouseWindow(this, hz, ph, GetVersion() <= 561 ? house_id : this->GetPlayer()->GetID()); + PlaySound("coin_cha_ching"); + } + else + { + // recover the escrow we were going to use but could not spend due to lack of funds + if (tmpRecoverCoins) + ph->escrow_coins += tmpRecoverCoins; + if(tmpRecoverStatus) + ph->escrow_status += tmpRecoverStatus; + + SimpleMessage(CHANNEL_COLOR_YELLOW, "You do not have enough money or status to pay for upkeep."); + PlaySound("buy_failed"); + } + } + else + Message(CHANNEL_COLOR_YELLOW, "PlayerHouse ID %u does NOT exist!", house_id); + } + } + + safe_delete(packet); + break; + } + case OP_ExitHouseMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_ExitHouseMsg", opcode, opcode); + int32 instance_id = GetCurrentZone()->GetInstanceID(); + if (instance_id > 0) { + PlayerHouse* ph = world.GetPlayerHouseByInstanceID(instance_id); + if (ph) { + HouseZone* hz = world.GetHouseZone(ph->house_id); + if (hz) { + ZoneServer* new_zone = zone_list.Get(hz->exit_zone_id); + + // determine if this is an instanced zone that already exists + ZoneServer* instance_zone = GetPlayer()->GetGroupMemberInZone(hz->exit_zone_id); + if (instance_zone || new_zone) { + GetPlayer()->SetX(hz->exit_x); + GetPlayer()->SetY(hz->exit_y); + GetPlayer()->SetZ(hz->exit_z); + GetPlayer()->SetHeading(hz->exit_heading); + if (instance_zone) + Zone(instance_zone->GetInstanceID(), false, true); + else + Zone(new_zone, false); + } + } + } + } + break; + } + case OP_QuestJournalWaypointMsg: { + //DumpPacket(app); + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_QuestJournalWaypointMsg", opcode, opcode); + PacketStruct* packet = configReader.getStruct("WS_QuestJournalWaypoint", GetVersion()); + if (packet) { + if(packet->LoadPacketData(app->pBuffer, app->size)) { + if(GetVersion() <= 561) { + int32 quest_id = packet->getType_int32_ByName("quest_id"); + GetPlayer()->MPlayerQuests.writelock(__FUNCTION__, __LINE__); + if (player->player_quests.count(quest_id) > 0 && player->player_quests[quest_id]) { + if(player->player_quests[quest_id]->GetTracked()) + player->player_quests[quest_id]->SetTracked(false); + else + player->player_quests[quest_id]->SetTracked(true); + + player->player_quests[quest_id]->SetSaveNeeded(true); + } + GetPlayer()->MPlayerQuests.releasewritelock(__FUNCTION__, __LINE__); + } + else { + int32 quests = packet->getType_int32_ByName("num_quests"); + + if (quests > 100) // just picking a number higher than max allowed + { + LogWrite(CCLIENT__ERROR, 0, "Client", "num_quests = %u - quantity too high, aborting load.", quests); + break; + } + + LogWrite(CCLIENT__DEBUG, 0, "Client", "num_quests = %u", quests); + + for (int32 i = 0; i < quests; i++) { + int32 id = packet->getType_int32_ByName("quest_id_0", i); + if (id == 0) + continue; + LogWrite(CCLIENT__DEBUG, 5, "Client", "quest_id = %u", id); + bool tracked = packet->getType_int8_ByName("quest_tracked_0", i) == 1 ? true : false; + GetPlayer()->MPlayerQuests.writelock(__FUNCTION__, __LINE__); + if (player->player_quests.count(id) > 0 && player->player_quests[id]) { + player->player_quests[id]->SetTracked(tracked); + player->player_quests[id]->SetSaveNeeded(true); + } + GetPlayer()->MPlayerQuests.releasewritelock(__FUNCTION__, __LINE__); + } + } + } + safe_delete(packet); + } + + break; + } + case OP_PaperdollImage: { +/* PacketStruct* packet = configReader.getStruct("WS_PaperdollImage", version); + if (packet && packet->LoadPacketData(app->pBuffer, app->size)) { + + //First check if this is a new image... delete an existing partial image if one exists + int8 packet_index = packet->getType_int8_ByName("packetIndex"); + if (packet_index == 0) { + safe_delete_array(incoming_paperdoll.image_bytes); + incoming_paperdoll.last_received_packet_index = 0; + incoming_paperdoll.current_size_bytes = 0; + } + //return if this packet is not the one we are expecting... + else if (packet_index != incoming_paperdoll.last_received_packet_index + 1) { + safe_delete(packet); + break; + } + + //Check how many packets we're supposed to be receiving for this/these images + incoming_paperdoll.image_num_packets = packet->getType_int8_ByName("totalNumPackets"); + + //Check the image type, if this is a new type in the same series of packets we have a new image + int8 img_type = packet->getType_int8_ByName("image_type"); + if (packet_index != 0 && img_type != incoming_paperdoll.image_type) { + //We have a new image. Save the old data and clear before continuing + SavePlayerImages(); + } + incoming_paperdoll.image_type = img_type; + + //Get the size of the image data in this packet + sint64 image_size = packet->getType_int32_ByName("imageSize"); + if (image_size <= 0 || image_size > 1048576) { + //If this packet is saying that the array is size <= 0 or > 1 MiB return out... it shouldn't be those sizes ever + safe_delete(packet); + break; + } + + //Create a new array + int32 new_image_size = image_size; + uchar* new_image = new uchar[incoming_paperdoll.current_size_bytes + new_image_size]; + if (incoming_paperdoll.image_bytes) { + memcpy(new_image, incoming_paperdoll.image_bytes, incoming_paperdoll.current_size_bytes); + safe_delete_array(incoming_paperdoll.image_bytes); + } + + //variable i should be the index in the packet of the first PNG file byte + vector* d_structs = packet->getStructs(); + vector::iterator itr; + int32 i = 0; + for (itr = d_structs->begin(); itr != d_structs->end(); itr++) { + DataStruct* ds = (*itr); + if (strcmp(ds->GetName(), "pngData_0") != 0) + i += ds->GetDataSizeInBytes(); + else + break; + } + + //Return if this packet is bad and we would read out of bounds + if (app->size - i < new_image_size) { + safe_delete(packet); + safe_delete_array(new_image); + break; + } + + uchar* tmp = new_image + incoming_paperdoll.current_size_bytes; + memcpy(tmp, app->pBuffer + i, new_image_size); + + incoming_paperdoll.current_size_bytes += new_image_size; + incoming_paperdoll.image_bytes = new_image; + + //Check if this is the last packet we're expecting for this image. Create a final image if so + if (incoming_paperdoll.image_num_packets == 1 || + incoming_paperdoll.last_received_packet_index + 2 == incoming_paperdoll.image_num_packets) { + SavePlayerImages(); + } + + incoming_paperdoll.last_received_packet_index = packet_index; + } + safe_delete(packet); +*/ + break; + } + + case OP_ReadyForTakeOffMsg: + { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_ReadyForTakeOffMsg", opcode, opcode); + + int32 index = GetCurrentZone()->GetFlightPathIndex(GetPendingFlightPath()); + if (GetPendingFlightPath() > 0) { + if (index != -1) { + PacketStruct* packet = configReader.getStruct("WS_ClearForTakeOff", GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", GetPlayer()->GetIDWithPlayerSpawn(GetPlayer())); + packet->setDataByName("path_id", index); + packet->setDataByName("speed", GetCurrentZone()->GetFlightPathSpeed(GetPendingFlightPath())); + QueuePacket(packet->serialize()); + safe_delete(packet); + + on_auto_mount = true; + } + } + else { + LogWrite(CCLIENT__ERROR, 0, "Client", "OP_ReadyForTakeOffMsg recieved but unable to get an index for path (%u) in zone (%u)", GetPendingFlightPath(), GetCurrentZone()->GetZoneID()); + Message(CHANNEL_ERROR, "Unable to get index for path (%u) in zone (%u)", GetPendingFlightPath(), GetCurrentZone()->GetZoneID()); + EndAutoMount(); + } + + SetPendingFlightPath(0); + + } + else + LogWrite(CCLIENT__ERROR, 0, "Client", "OP_ReadyForTakeOffMsg recieved but there is no pending flight path..."); + + break; + } + + case OP_EarlyLandingRequestMsg: + { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_EarlyLandingRequestMsg", opcode, opcode); + + EndAutoMount(); + + break; + } + + case OP_SubmitCharCust: + { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_SubmitCharCust", opcode, opcode); + PacketStruct* packet = configReader.getStruct("WS_SubmitCharCust", version); + if (packet && packet->LoadPacketData(app->pBuffer, app->size, GetVersion() <= 561 ? false : true)) { + int8 type = packet->getType_int8_ByName("type"); + if (type == 0) { + /*if (player->custNPC) { + player->custNPCTarget->CustomizeAppearance(packet); + current_zone->SendSpawnChanges(player->custNPCTarget); + } + else {*/ + player->CustomizeAppearance(packet); + current_zone->SendSpawnChanges(player); + //} + } + } + safe_delete(packet); + /* + if (player->custNPC) { + memcpy(&player->appearance, &player->SavedApp, sizeof(AppearanceData)); + memcpy(&player->features, &player->SavedFeatures, sizeof(CharFeatures)); + + if (player->custNPCTarget->IsBot()) + database.SaveBotAppearance((Bot*)player->custNPCTarget); + + player->custNPC = false; + player->custNPCTarget = 0; + player->changed = true; + player->info_changed = true; + current_zone->SendSpawnChanges(player, this); + }*/ + + break; + } + + default: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): Unknown in %s", opcode, opcode, __FILE__); + const char* name = app->GetOpcodeName(); + if (name) + LogWrite(OPCODE__DEBUG, 1, "Opcode", "%s Received %04X (%i)", name, app->GetRawOpcode(), app->GetRawOpcode()); + else + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Received %04X (%i)", app->GetRawOpcode(), app->GetRawOpcode()); + + // keeping this around for debugging purposes + DumpPacket(app); + + } + } + if (!eqs || !eqs->CheckActive()) { + return false; + } + + return ret; +} + +bool Client::HandleLootItem(Spawn* entity, Item* item, Spawn* target, bool overrideLootRestrictions) { + if (!item) { + SimpleMessage(CHANNEL_COLOR_YELLOW, "Unable to find item to loot!"); + return false; + } + int32 conflictItemList = 0, conflictequipmentList = 0, conflictAppearanceEquipmentList = 0; + int16 lore_stack_count = 0; + + Player* lootingPlayer = player; + Client* lootingClient = this; + if (target != nullptr && target != lootingPlayer && target->IsPlayer()) { + lootingPlayer = (Player*)target; + lootingClient = lootingPlayer->GetClient(); + } + + // needs to only be checked before expiration of loot restrictions + if (entity && !overrideLootRestrictions) { + if (entity->GetLootGroupID() > 0 && (!lootingPlayer->GetGroupMemberInfo() || lootingPlayer->GetGroupMemberInfo()->group_id != entity->GetLootGroupID())) { + LogWrite(LOOT__ERROR, 0, "Loot", "%s: Loot Group ID from %s did not match Item: %s (%u), expected group id %u.", entity->GetName(), lootingPlayer->GetName(), item->name.c_str(), item->details.item_id, entity->GetLootGroupID()); + return false; + } + if (entity->GetLootMethod() != GroupLootMethod::METHOD_FFA) { + switch (entity->GetLootMethod()) { + case GroupLootMethod::METHOD_LEADER: { + if (entity->GetLootGroupID() > 0 && (!lootingPlayer->GetGroupMemberInfo() || (lootingPlayer->GetGroupMemberInfo() && (lootingPlayer->GetGroupMemberInfo()->group_id != entity->GetLootGroupID() || !lootingPlayer->GetGroupMemberInfo()->leader)))) { + LogWrite(LOOT__ERROR, 0, "Loot", "%s: Loot Attempt from %s was not allowed with Item: %s (%u), must be group leader.", entity->GetName(), lootingPlayer->GetName(), item->name.c_str(), item->details.item_id); + return false; + } + break; + } + case GroupLootMethod::METHOD_LOTTO: + case GroupLootMethod::METHOD_NEED_BEFORE_GREED: { + if (entity->IsLootTimerRunning()) { + LogWrite(LOOT__INFO, 0, "Loot", "%s: Loot Timer is still running, flag player %s to lotto Item: %s (%u).", entity->GetName(), lootingPlayer->GetName(), item->name.c_str(), item->details.item_id); + return false; + } + break; + } + } + } + } + + if (((conflictItemList = lootingPlayer->item_list.CheckSlotConflict(item, true, true, &lore_stack_count)) == LORE || + (conflictequipmentList = lootingPlayer->equipment_list.CheckSlotConflict(item, true, &lore_stack_count)) == LORE || + (conflictAppearanceEquipmentList = lootingPlayer->appearance_equipment_list.CheckSlotConflict(item, true, &lore_stack_count)) == LORE) && !item->CheckFlag(STACK_LORE)) { + Message(CHANNEL_COLOR_RED, "You cannot loot %s due to lore conflict.", item->name.c_str()); + return false; + } + else if (conflictItemList == STACK_LORE || conflictequipmentList == STACK_LORE || conflictAppearanceEquipmentList == STACK_LORE) { + Message(CHANNEL_COLOR_RED, "You cannot loot %s due to stack lore conflict.", item->name.c_str()); + return false; + } + + if (lootingPlayer->item_list.HasFreeSlot() || lootingPlayer->item_list.CanStack(item)) { + if (lootingPlayer->item_list.AssignItemToFreeSlot(item)) { + + if (item->CheckFlag2(HEIRLOOM)) { // TODO: RAID Support + GroupMemberInfo* gmi = lootingClient->GetPlayer()->GetGroupMemberInfo(); + if (gmi && gmi->group_id) + { + PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + if (members) { + for (int8 i = 0; i < members->size(); i++) { + Entity* member = members->at(i)->member; + if (!member) + continue; + + if ((member->GetZone() != lootingClient->GetPlayer()->GetZone())) + continue; + + if (member->IsPlayer()) { + item->grouped_char_ids.insert(std::make_pair(((Player*)member)->GetCharacterID(), true)); + item->save_needed = true; + } + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + } + } + + int8 type = CHANNEL_LOOT; + if (entity) { + lootingClient->Message(type, "You loot %s from the corpse of %s", item->CreateItemLink(GetVersion()).c_str(), entity->GetName()); + } + else { + lootingClient->Message(type, "You found a %s.", item->CreateItemLink(GetVersion()).c_str()); + } + Guild* guild = lootingPlayer->GetGuild(); + if (guild && item->details.tier >= ITEM_TAG_LEGENDARY) { + char adjective[32]; + int8 type; + memset(adjective, 0, sizeof(adjective)); + if (item->details.tier >= ITEM_TAG_MYTHICAL) { + strncpy(adjective, "Mythical", sizeof(adjective) - 1); + type = GUILD_EVENT_LOOTS_MYTHICAL_ITEM; + } + else if (item->details.tier >= ITEM_TAG_FABLED) { + strncpy(adjective, "Fabled", sizeof(adjective) - 1); + type = GUILD_EVENT_LOOTS_FABELED_ITEM; + } + else { + strncpy(adjective, "Legendary", sizeof(adjective) - 1); + type = GUILD_EVENT_LOOTS_LEGENDARY_ITEM; + } + guild->AddNewGuildEvent(type, "%s has looted the %s %s", Timer::GetUnixTimeStamp(), true, lootingPlayer->GetName(), adjective, item->CreateItemLink(GetVersion()).c_str()); + guild->SendMessageToGuild(type, "%s has looted the %s %s", lootingPlayer->GetName(), adjective, item->CreateItemLink(GetVersion()).c_str()); + } + + if (item->GetItemScript() && lua_interface) + lua_interface->RunItemScript(item->GetItemScript(), "obtained", item, lootingPlayer); + + lootingClient->CheckPlayerQuestsItemUpdate(item); + + if (GetVersion() <= 561) { + EQ2Packet* outapp = lootingPlayer->SendInventoryUpdate(GetVersion()); + if (outapp) + lootingClient->QueuePacket(outapp); + } + return true; + } + else + lootingClient->SimpleMessage(CHANNEL_COLOR_RED, "Could not find free slot to place item."); + } + else + lootingClient->SimpleMessage(CHANNEL_COLOR_YELLOW, "Unable to loot item: Inventory is FULL."); + + return false; +} + +bool Client::HandleLootItemByID(Spawn* entity, int32 item_id, Spawn* target) { + if (!entity) { + return false; + } + Item* item = entity->LootItem(item_id); + bool success = false; + success = HandleLootItem(entity, item, target); + if (!success) + entity->AddLootItem(item); + + return success; +} + +void Client::HandleLootItemRequestPacket(EQApplicationPacket* app) { + PacketStruct* packet = configReader.getStruct("WS_LootItem", GetVersion()); + if (packet) { + if (packet->LoadPacketData(app->pBuffer, app->size)) { + int32 loot_id = packet->getType_int32_ByName("loot_id"); + bool loot_all = (packet->getType_int8_ByName("loot_all") == 1); + int32 target_id = packet->getType_int32_ByName("target_id"); + int8 button_clicked = packet->getType_int8_ByName("button_clicked"); + Spawn* spawn = GetCurrentZone()->GetSpawnByID(loot_id); + if(!spawn) { + safe_delete(packet); + return; + } + Item* item = nullptr; + vector* items = player->GetPendingLootItems(loot_id); + if (items) { + int32 items_looted = 0; + int32 item_id = packet->getType_int32_ByName("item_id_0"); + for (int32 i = 0; i < items->size(); i++) { + Item* master_item = items->at(i); + if (master_item && (loot_all || master_item->details.item_id == item_id)) { + item = new Item(master_item); + if (item) { + loot_all = HandleLootItem(0, item); + if (loot_all) { + player->RemovePendingLootItem(loot_id, item->details.item_id); + + if (GetVersion() <= 561) { + EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion()); + if (outapp) + QueuePacket(outapp); + } + if(master_item->details.item_id == item_id) { + break; + } + } + } + + if (!loot_all) + break; + } + } + if(((loot_all && !item_id) || (loot_all && item_id && items->size() == 1)) || items->size() < 1) { + CloseLoot(loot_id); + } + else { + vector* items2 = player->GetPendingLootItems(loot_id); + SendLootResponsePacket(spawn->GetLootCoins(), items2, spawn, true); + safe_delete(items2); + } + safe_delete(items); + safe_delete(packet); + return; + } + + spawn->LockLoot(); + bool unlockedLoot = false; + if (spawn && !spawn->Alive() && spawn->IsNPC() && ((NPC*)spawn)->Brain()->CheckLootAllowed(player)) { + if (loot_all) { + switch (spawn->GetLootMethod()) { + case GroupLootMethod::METHOD_LOTTO: { + spawn->AddLottoItemRequest(0xFFFFFFFF, GetPlayer()->GetID()); + break; + } + case GroupLootMethod::METHOD_NEED_BEFORE_GREED: { + spawn->AddNeedGreedItemRequest(0xFFFFFFFF, GetPlayer()->GetID(), true); + } + default: { + if (!unlockedLoot) { + spawn->UnlockLoot(); + unlockedLoot = true; + } + int32 item_id = 0; + while (loot_all && ((item_id = spawn->GetLootItemID()) > 0)) { + loot_all = HandleLootItemByID(spawn, item_id, GetPlayer()); + } + break; + } + } + spawn->UnlockLoot(); + if (spawn->GetLootMethod() == GroupLootMethod::METHOD_LOTTO) { + CloseLoot(loot_id); + } + } + else { + int8 item_count = packet->getType_int8_ByName("item_count"); + for (int8 cur = 0; cur < item_count; cur++) { + char item_field_name[64]; + snprintf(item_field_name, 64, "item_id_%u", cur); + int32 item_id = packet->getType_int32_ByName(item_field_name); + Spawn* target = this->GetPlayer(); + if (target_id != 0xFFFFFFFF && GetPlayer()->GetGroupMemberInfo()) { + Spawn* destTarget = GetPlayer()->GetSpawnWithPlayerID(target_id); + if (destTarget && (!destTarget->IsPlayer() || !world.GetGroupManager()->IsInGroup(GetPlayer()->GetGroupMemberInfo()->group_id, ((Player*)destTarget)))) { + SimpleMessage(CHANNEL_COMMAND_TEXT, "HACKS!!"); + safe_delete(packet); + spawn->UnlockLoot(); + return; + } + target = destTarget; + } + bool breakLoopAllLooted = false; + switch (spawn->GetLootMethod()) { + case GroupLootMethod::METHOD_LOTTO: { + spawn->AddLottoItemRequest(item_id, GetPlayer()->GetID()); + break; + } + case GroupLootMethod::METHOD_NEED_BEFORE_GREED: { + if (button_clicked == 3) { // decline + break; + } + if (GetVersion() <= 561) { + button_clicked = 1; // selecting is need + } + spawn->AddNeedGreedItemRequest(item_id, GetPlayer()->GetID(), (button_clicked == 1)); + break; + } + default: { + if (!unlockedLoot) { + spawn->UnlockLoot(); + unlockedLoot = true; + } + if (!loot_all) { + HandleLootItemByID(spawn, item_id, target); + } + else { + while (loot_all && ((item_id = spawn->GetLootItemID()) > 0)) { + loot_all = HandleLootItemByID(spawn, item_id, target); + } + breakLoopAllLooted = true; + } + break; + } + } + if (breakLoopAllLooted) { + break; + } + } + if (!unlockedLoot) { + spawn->UnlockLoot(); + } + if (spawn->GetLootMethod() == GroupLootMethod::METHOD_LOTTO || + (spawn->GetLootMethod() == GroupLootMethod::METHOD_NEED_BEFORE_GREED && item_count >= spawn->GetLootCount())) { + CloseLoot(loot_id); + } + } + + if (GetVersion() > 561) { + EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion()); + if (outapp) + QueuePacket(outapp); + } + if (spawn->GetLootMethod() != GroupLootMethod::METHOD_LOTTO && spawn->GetLootMethod() != GroupLootMethod::METHOD_NEED_BEFORE_GREED) { + LootSpawnRequest(spawn); + } + else { + spawn->SetSpawnLootWindowCompleted(GetPlayer()->GetID()); + } + + if (!spawn->HasLoot()) { + CloseLoot(loot_id); + if (spawn->IsNPC()) + GetCurrentZone()->RemoveDeadSpawn(spawn); + } + } + else { + spawn->UnlockLoot(); + if (!spawn) { + LogWrite(WORLD__ERROR, 0, "World", "Unknown id of %u when looting!", loot_id); + SimpleMessage(CHANNEL_COLOR_YELLOW, "Unable to find spawn to loot!"); + } + else + SimpleMessage(CHANNEL_COLOR_YELLOW, "You are not unable to loot that at this time."); + } + } + + safe_delete(packet); + } +} + +void Client::HandleSkillInfoRequest(EQApplicationPacket* app) { + PacketStruct* request = 0; + // cout << "Request: \n"; + // DumpPacket(app); + switch (app->pBuffer[0]) { + case 0: { //items + request = configReader.getStruct("WS_SkillInfoItemRequest", GetVersion()); + if (request) { + if(request->LoadPacketData(app->pBuffer, app->size)) { + Item* item = GetPlayer()->GetEquipmentList()->GetItemFromUniqueID(request->getType_int32_ByName("unique_id")); + if (!item) + item = GetPlayer()->item_list.GetItemFromUniqueID(request->getType_int32_ByName("unique_id"), true); + if (item) { + PacketStruct* response = configReader.getStruct("WS_SkillInfoItemResponse", GetVersion()); + if (response) { + response->setDataByName("request_type", request->getType_int32_ByName("request_type")); + response->setDataByName("unique_id", request->getType_int32_ByName("unique_id")); + response->setSmallStringByName("name", item->name.c_str()); + EQ2Packet* app2 = response->serialize(); + //DumpPacket(app2); + QueuePacket(app2); + safe_delete(response); + } + } + } + safe_delete(request); + } + break; + } + case 2: {//spells + request = configReader.getStruct("WS_SkillInfoSpellRequest", GetVersion()); + if (request) { + if(request->LoadPacketData(app->pBuffer, app->size)) { + int32 id = request->getType_int32_ByName("id"); + int8 tier = request->getType_int32_ByName("unique_id"); //on live this is really unique_id, but I'm going to make it tier instead :) + Spell* spell = master_spell_list.GetSpell(id, tier); + PacketStruct* response = configReader.getStruct("WS_SkillInfoResponse", GetVersion()); + if (response) { + response->setDataByName("request_type", 2); + response->setDataByName("unique_id", tier); + response->setDataByName("id", id); + if (spell) + response->setSmallStringByName("name", spell->GetName()); + else + response->setSmallStringByName("name", "Unknown Spell"); + EQ2Packet* app2 = response->serialize(); + // DumpPacket(app2); + QueuePacket(app2); + safe_delete(response); + } + } + safe_delete(request); + } + break; + } + default: { + LogWrite(WORLD__ERROR, 0, "World", "Unknown SkillInfoRequest type of %i", (int)app->pBuffer[0]); + } + } + safe_delete(request); + +} + +void Client::HandleExamineInfoRequest(EQApplicationPacket* app) { + PacketStruct* request = 0; + if (!app || app->size == 0) + return; + //LogWrite(CCLIENT__DEBUG, 0, "Client", "Request2:"); + + int8 type = app->pBuffer[0]; + DumpPacket(app->pBuffer,app->size); + //283: item: 0, effect: 1, recipe: 2, spell: 3 + if (version <= 373) { + if (type == 1) + type = 4; + else if (type == 2) + type = 5; + } + if (type == 3) { + Spell* spell = 0; + bool trait_display; + + request = configReader.getStruct((GetVersion() <= 373) ? "WS_ExamineInfoRequest" : "WS_ExamineInfoRequestMsg", GetVersion()); + if (!request) { + return; + } + if(!request->LoadPacketData(app->pBuffer, app->size)) { + safe_delete(request); + return; + } + + int32 id = request->getType_int32_ByName("id"); + int32 tier = request->getType_int32_ByName("tier"); + int32 trait_tier = request->getType_int32_ByName("unknown_id"); + + if(GetVersion() > 373 && GetVersion() <= 561) { + trait_tier = request->getType_int32_ByName("unique_id"); + } + bool display = true; + if (version <= 373 && request->getType_int8_ByName("display") == 1) // this is really requesting a partial packet + display = false; + else if (version <= 561) + display = request->getType_int8_ByName("display"); + else if (version > 561) + display = false; // clients default is false otherwise it pops up a window when hovering over the knowledge book abilities + + LogWrite(CCLIENT__DEBUG, 5, "Client", "Client::HandleExamineInfoRequest from %s: Type: (%i) Tier: (%u) Unknown ID: (%u) Item ID: (%u)",GetPlayer()->GetName(),type,tier,trait_tier,id); + + if (trait_tier != 0xFFFFFFFF) { + spell = master_spell_list.GetSpell(id, trait_tier); + if (!spell) { + spell = master_spell_list.GetSpell(id, trait_tier + 1); + } + trait_display = true; + } + else { + spell = master_spell_list.GetSpell(id, tier); + trait_display = false; + } + + // if we can't find it on the master spell list, see if it is a custom spell + if (!spell) + { + lua_interface->FindCustomSpellLock(); + LuaSpell* tmpSpell = lua_interface->FindCustomSpell(id); + if (tmpSpell) + spell = tmpSpell->spell; + lua_interface->FindCustomSpellUnlock(); + } + + if(!spell) { // fix ui timeout for classic, isle of refuge, dof, kos clients + int8 playerTier = GetPlayer()->GetSpellTier(id); + spell = master_spell_list.GetSpell(id, playerTier); + LogWrite(CCLIENT__WARNING, 0, "Client", "Client::HandleExamineInfoRequest from %s: Failed to find tier 1 spell. Last resort try to get the spell from the player book, spell %u, tier %u", GetPlayer()->GetName(),id,playerTier); + } + + if (spell && !CountSentSpell(spell->GetSpellID(), spell->GetSpellTier())) { + if (!spell->IsCopiedSpell()) + SetSentSpell(spell->GetSpellID(), spell->GetSpellTier()); + + EQ2Packet* app = spell->SerializeSpell(this, display, trait_display); + //DumpPacket(app); + QueuePacket(app); + } + else if(spell && GetVersion() <=561 && CountSentSpell(spell->GetSpellID(), spell->GetSpellTier())) { + EQ2Packet* app = spell->SerializeSpell(this, display, trait_display, GetVersion() <= 561 ? true : false); + //DumpPacket(app); + QueuePacket(app); + } + else { + LogWrite(CCLIENT__ERROR, 0, "Client", "Client::HandleExamineInfoRequest from %s: Failed to successfully send Type: (%i) Tier: (%u) Unknown ID: (%u) Item ID: (%u)",GetPlayer()->GetName(),type,tier,trait_tier,id); + } + } + else if (type == 0) { + request = configReader.getStruct("WS_ExamineInfoItemRequest", GetVersion()); + if (!request) { + return; + } + if(!request->LoadPacketData(app->pBuffer, app->size)) { + safe_delete(request); + return; + } + + int32 id = request->getType_int32_ByName("id"); + + Item* item = 0; + + // translate from unique id to spawn id for houses + Spawn* spawn = this->GetCurrentZone()->GetSpawnFromUniqueItemID(id); + + bool wasSpawn = false; + if (spawn) + { + item = master_item_list.GetItem(spawn->GetPickupItemID()); + if (item) + { + wasSpawn = true; + item = new Item(item); + item->details.unique_id = spawn->GetPickupUniqueItemID(); + } + } + + if (!item) + item = GetPlayer()->item_list.GetItemFromUniqueID(id, true); + if (!item) + item = GetPlayer()->GetEquipmentList()->GetItemFromUniqueID(id); + if (!item) + item = GetPlayer()->GetAppearanceEquipmentList()->GetItemFromUniqueID(id); + if (!item) + item = master_item_list.GetItem(id); + if (item) {// && sent_item_details.count(id) == 0){ + MItemDetails.writelock(__FUNCTION__, __LINE__); + sent_item_details[id] = true; + MItemDetails.releasewritelock(__FUNCTION__, __LINE__); + EQ2Packet* app = item->serialize(GetVersion(), false, GetPlayer()); + + QueuePacket(app); + if (wasSpawn) + delete item; + } + else { + LogWrite(WORLD__ERROR, 0, "World", "HandleExamineInfoRequest: Unknown Item ID = %u", id); + DumpPacket(app); + } + } + else if (type == 1) { + request = configReader.getStruct("WS_ExamineInfoItemRequest", GetVersion()); + if (!request) { + return; + } + if(!request->LoadPacketData(app->pBuffer, app->size)) { + safe_delete(request); + return; + } + int32 id = request->getType_int32_ByName("id"); + int32 unique_id = request->getType_int32_ByName("unique_id"); + + Item* item = GetPlayer()->item_list.GetItemFromUniqueID(unique_id, true); + if (!item) + item = GetPlayer()->GetEquipmentList()->GetItemFromUniqueID(unique_id); + if (!item) + item = GetPlayer()->GetAppearanceEquipmentList()->GetItemFromUniqueID(unique_id); + if (!item) + item = master_item_list.GetItem(id); + if (item) { + MItemDetails.writelock(__FUNCTION__, __LINE__); + sent_item_details[id] = true; + MItemDetails.releasewritelock(__FUNCTION__, __LINE__); + EQ2Packet* app = item->serialize(GetVersion(), false, GetPlayer()); + QueuePacket(app); + } + else { + LogWrite(WORLD__ERROR, 0, "World", "HandleExamineInfoRequest: Unknown Item ID = %u", id); + DumpPacket(app); + } + } + else if (type == 2) { + request = configReader.getStruct("WS_ExamineInfoItemLinkRequest", GetVersion()); + if (!request) { + return; + } + + if(!request->LoadPacketData(app->pBuffer, app->size)) { + safe_delete(request); + return; + } + int32 id = request->getType_int32_ByName("item_id"); + + LogWrite(CCLIENT__DEBUG, 5, "Client", "Client::HandleExamineInfoRequest from %s: Found Type: (%i) Item ID: (%u)",GetPlayer()->GetName(),type,id); + //int32 unknown_0 = request->getType_int32_ByName("unknown",0); + //int32 unknown_1 = request->getType_int32_ByName("unknown",1); + //int8 unknown2 = request->getType_int8_ByName("unknown2"); + //int32 unique_id = request->getType_int32_ByName("unique_id"); + //int16 unknown5 = request->getType_sint16_ByName("unknown5"); + //printf("Type: (%i) Unknown_0: (%u) Unknown_1: (%u) Unknown2: (%i) Unique ID: (%u) Unknown5: (%i) Item ID: (%u)\n",type,unknown_0,unknown_1,unknown2,unique_id,unknown5,id); + Item* item = master_item_list.GetItem(id); + if (item) { + //only display popup for non merchant links + EQ2Packet* app = item->serialize(GetVersion(), (request->getType_int8_ByName("show_popup") != 0), GetPlayer(), true, 0, 0, GetVersion() > 561 ? true : false); + + QueuePacket(app); + } + else { + LogWrite(WORLD__ERROR, 0, "World", "HandleExamineInfoRequest: Unknown Item ID = %u", id); + DumpPacket(app); + } + } + else if (type == 4) { //spell effect + request = configReader.getStruct("WS_ExamineSpellEffectRequest", GetVersion()); + if (!request) { + return; + } + if(!request->LoadPacketData(app->pBuffer, app->size)) { + safe_delete(request); + return; + } + int32 id = request->getType_int32_ByName("id"); + int16 display = request->getType_int8_ByName("partial_info"); + SpellEffects* effect = player->GetSpellEffect(id); + //printf("Type: (%i) Unknown5: (%i) Item ID: (%u)\n",type,unknown5,id); + if (effect) { + LogWrite(CCLIENT__DEBUG, 5, "Client", "Client::HandleExamineInfoRequest from %s: Found Type: (%i) Item ID: (%u)",GetPlayer()->GetName(),type,id); + int8 tier = effect->tier; + Spell* spell = master_spell_list.GetSpell(id, tier); + + // if we can't find it on the master spell list, see if it is a custom spell + if (!spell) + { + lua_interface->FindCustomSpellLock(); + LuaSpell* tmpSpell = lua_interface->FindCustomSpell(id); + EQ2Packet* pack = 0; + if (tmpSpell) + spell = tmpSpell->spell; + lua_interface->FindCustomSpellUnlock(); + } + + if (spell && (version <= 561 || !CountSentSpell(id, tier))) { // fix DoF and KoS clients UI Timeout + if (!spell->IsCopiedSpell()) + SetSentSpell(spell->GetSpellID(), spell->GetSpellTier()); + int8 type = 0; + if (version <= 373) + type = 1; + EQ2Packet* app = spell->SerializeSpecialSpell(this, false, type, 0x81); + //DumpPacket(app); + QueuePacket(app); + } + } + else { + LogWrite(CCLIENT__ERROR, 0, "Client", "Client::HandleExamineInfoRequest from %s: Cannot Find Type: (%i) Item ID: (%u)",GetPlayer()->GetName(),type,id); + } + } + else if (type == 5) { // recipe info + request = configReader.getStruct((GetVersion() <= 373) ? "WS_ExamineInfoRequest" : "WS_ExamineInfoRequestMsg", GetVersion()); + if (!request) + return; + if(!request->LoadPacketData(app->pBuffer, app->size)) { + safe_delete(request); + return; + } + + int32 id = 0; + if(GetVersion() < 546) { + id = request->getType_int32_ByName("id"); + } + else if(GetVersion() <= 561) { + id = request->getType_int32_ByName("unique_id"); + } + else { + id = request->getType_int32_ByName("unknown_id"); + } + Recipe* recipe = master_recipe_list.GetRecipe(id); + if (recipe) { + EQ2Packet* app = recipe->SerializeRecipe(this, recipe, false, GetItemPacketType(GetVersion()), 0x02); + //DumpPacket(app); + QueuePacket(app); + } + + } + else if (type == 6) { // AA spell info + Spell* spell = 0; + //Spell* spell2 = 0; + //AltAdvanceData* data = 0; + request = configReader.getStruct((GetVersion() <= 373) ? "WS_ExamineInfoRequest" : "WS_ExamineInfoRequestMsg", GetVersion()); + if (!request) + return; + if(!request->LoadPacketData(app->pBuffer, app->size)) { + safe_delete(request); + return; + } + int32 id = request->getType_int32_ByName("id"); + int32 tier = GetPlayer()->GetSpellTier(id); + LogWrite(WORLD__INFO, 0, "World", "Examine Info Request->Unique ID: %u Tier: %u ", id, tier); + //data = master_aa_list.GetAltAdvancement(id); + //LogWrite(WORLD__INFO, 0, "World", "SOE Spell CRC: %u", data->spell_crc); + //spell = master_spell_list.GetSpellByCRC(data->spell_crc); + spell = master_spell_list.GetSpell(id, tier); + + if (!spell) + spell = master_spell_list.GetSpell(id, 1); + + if (!spell) + { + lua_interface->FindCustomSpellLock(); + LuaSpell* tmpSpell = lua_interface->FindCustomSpell(id); + if (tmpSpell) + spell = tmpSpell->spell; + lua_interface->FindCustomSpellUnlock(); + } + + if (!spell) + { + LogWrite(CCLIENT__ERROR, 0, "Client", "WORLD", "Client::HandleExamineInfoRequest from %s: FAILED Examine Info Request-> Spell ID: %u, tier: %i", GetPlayer()->GetName(), id, tier); + return; + } + + //if (spell && sent_spell_details.count(spell->GetSpellID()) == 0) { + if (!spell->IsCopiedSpell()) + SetSentSpell(spell->GetSpellID(), spell->GetSpellTier()); + // EQ2Packet* app = spell->SerializeAASpell(this,tier, data, false, GetItemPacketType(GetVersion()), 0x04); + LogWrite(WORLD__INFO, 0, "WORLD", "Examine Info Request-> Spell ID: %u", spell->GetSpellID()); + if(GetVersion() > 561) { + EQ2Packet* app = master_spell_list.GetAASpellPacket(id, tier, this, false, 0x4F);//0x45 change version to match client + /////////////////////////////////////////GetAASpellPacket(int32 id, int8 tier, Client* client, bool display, int8 packet_type) { + //DumpPacket(app); + QueuePacket(app); + } + //} + } + else { + LogWrite(CCLIENT__ERROR, 0, "World", "Client::HandleExamineInfoRequest from %s: Unknown examine request: %i", GetPlayer()->GetName(), (int)type); + DumpPacket(app); + } + safe_delete(request); + +} + +void Client::HandleQuickbarUpdateRequest(EQApplicationPacket* app) { + PacketStruct* request = configReader.getStruct("WS_QuickBarUpdateRequest", GetVersion()); + if (request) { + if(request->LoadPacketData(app->pBuffer, app->size)) { + int32 id = request->getType_int32_ByName("id"); + int32 bar = request->getType_int32_ByName("hotbar_number"); + int32 slot = request->getType_int32_ByName("hotkey_slot"); + int32 type = request->getType_int32_ByName("type"); + int8 tier = request->getType_int32_ByName("unique_id"); + EQ2_16BitString text = request->getType_EQ2_16BitString_ByName("text"); + Spell* spell = 0; + if (type == 0xFFFFFFFF) + GetPlayer()->RemoveQuickbarItem(bar, slot); + else { + if (type == QUICKBAR_NORMAL) + spell = master_spell_list.GetSpell(id, tier); + if (spell) + GetPlayer()->AddQuickbarItem(bar, slot, type, spell->GetSpellIcon(), spell->GetSpellIconBackdrop(), id, tier, 0, text.data.c_str()); + else + GetPlayer()->AddQuickbarItem(bar, slot, type, 0, 0, id, 0, 0, text.data.c_str()); + } + } + safe_delete(request); + } + +} + +bool Client::Process(bool zone_process) { + + bool ret = true; + // EQS can become null if player is linkdead, we want to always be able to process the camp/linkdead timers when active + if ((camp_timer && camp_timer->Check()) || (linkdead_timer && linkdead_timer->Check())) { + ResetSendMail(); + if(getConnection()) + getConnection()->SendDisconnect(false); + safe_delete(camp_timer); + if(linkdead_timer) { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Player %s triggered linkdead timer, disconnecting", GetPlayer()->GetName()); + // we remove the linkdead status and force a camp out immediately + if((GetPlayer()->GetActivityStatus() & ACTIVITY_STATUS_LINKDEAD) > 0) { + GetPlayer()->SetActivityStatus(GetPlayer()->GetActivityStatus() - ACTIVITY_STATUS_LINKDEAD); + } + if ((GetPlayer()->GetActivityStatus() & ACTIVITY_STATUS_CAMPING) == 0) { + GetPlayer()->SetActivityStatus(GetPlayer()->GetActivityStatus() + ACTIVITY_STATUS_CAMPING); + } + } + safe_delete(linkdead_timer); + ret = false; + } + + if (!eqs) { + return false; + } + if ((connected_to_zone && !zone_process) || (!connected_to_zone && zone_process)) { + return true; + } + + switch(new_client_login) { + case NewLoginState::LOGIN_SEND: { + LogWrite(CCLIENT__DEBUG, 0, "Client", "SendLoginInfo to new client..."); + SendLoginInfo(); + + new_client_login = NewLoginState::LOGIN_NONE; + break; + } + case NewLoginState::LOGIN_INITIAL_LOAD: { + bool isDBActive = database.IsActiveQuery(GetCharacterID()); + + // wait for starting skills/spells to load and reload from DB. + if(!isDBActive) { + if (GetPlayer()->GetInfoStruct()->get_reload_player_spells()) { + database.LoadCharacterSpells(GetCharacterID(), GetPlayer()); + GetPlayer()->GetInfoStruct()->set_reload_player_spells(0); + } + new_client_login = NewLoginState::LOGIN_SEND; + } + break; + } + case NewLoginState::LOGIN_ALLOWED: { + int32 count = 0; + + if(!GetPlayer()->IsReturningFromLD()) + { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Loading Character Skills for player '%s'...", player->GetName()); + count = database.LoadCharacterSkills(GetCharacterID(), player); + + LogWrite(CCLIENT__DEBUG, 0, "Client", "Loading Character Spells for player '%s'...", player->GetName()); + count = database.LoadCharacterSpells(GetCharacterID(), player); + } + else + { + LogWrite(CCLIENT__INFO, 0, "Client", "Player is returning from linkdead status (Player does not need reload) thus skipping database loading for '%s'...", player->GetName()); + } + + // get the latest character starting skills / spells, may have been updated after character creation + if(GetPlayer()->GetInfoStruct()->get_first_world_login()) { + world.SyncCharacterAbilities(this); + Query query; + query.AddQueryAsync(GetCharacterID(), &database, Q_UPDATE, "UPDATE characters set first_world_login = 0 where id=%u", GetCharacterID()); + GetPlayer()->GetInfoStruct()->set_first_world_login(0); + } + + new_client_login = NewLoginState::LOGIN_INITIAL_LOAD; + break; + } + case NewLoginState::LOGIN_DELAYED: { + if(!delay_msg_timer.Enabled() || delay_msg_timer.Check()) { + LogWrite(CCLIENT__INFO, 0, "Client", "Wait for zone %s to load for new client %s...", GetCurrentZone()->GetZoneName(), GetPlayer()->GetName()); + delay_msg_timer.Start(1000, true); + } + if(!GetCurrentZone()->IsLoading()) { + new_client_login = NewLoginState::LOGIN_ALLOWED; + } + + return true; + break; + } + } + + delay_msg_timer.Disable(); + + sockaddr_in to; + + memset((char*)&to, 0, sizeof(to)); + to.sin_family = AF_INET; + to.sin_port = port; + to.sin_addr.s_addr = ip; + + /************ Get all packets from packet manager out queue and process them ************/ + EQApplicationPacket* app = 0; + if (eqs && !eqs->CheckActive()) { + num_active_failures++; + + LogWrite(CCLIENT__DEBUG, 7, "Client", "%s, num_active_failures = %i", __FUNCTION__, num_active_failures); + + if (num_active_failures > 100) { + return false; + } + return true; + } + while (ret && eqs && (app = eqs->PopPacket())) { + ret = HandlePacket(app); + + LogWrite(CCLIENT__DEBUG, 5, "Client", "Func: %s, Line: %i, Opcode: '%s'", __FUNCTION__, __LINE__, app->GetOpcodeName()); + + delete app; + } + + if (GetCurrentZone() && !GetCurrentZone()->IsLoading() && GetCurrentZone()->GetSpawnByID(GetPlayer()->GetID()) && should_load_spells) { + //database.LoadCharacterActiveSpells(player); + player->UnlockAllSpells(true); + + should_load_spells = false; + } + + if(spawn_removal_timer.Check() && GetPlayer()) { + GetPlayer()->ProcessSpawnRangeUpdates(); + GetPlayer()->CheckSpawnStateQueue(); + } + + if (delayedLogin && delayTimer.Enabled() && delayTimer.Check()) + { + if (!HandleNewLogin(delayedAccountID, delayedAccessKey)) + return false; + } + + if (quest_updates) { + LogWrite(CCLIENT__DEBUG, 1, "Client", "%s, ProcessQuestUpdates", __FUNCTION__, __LINE__); + ProcessQuestUpdates(); + } + int32 queue_timer_delay = rule_manager.GetGlobalRule(R_Client, QuestQueueTimer)->GetInt32(); + if(queue_timer_delay < 10) { + queue_timer_delay = 10; + } + if (last_update_time > 0 && last_update_time < (Timer::GetCurrentTime2() - queue_timer_delay)) { + LogWrite(CCLIENT__DEBUG, 1, "Client", "%s, CheckQuestQueue", __FUNCTION__, __LINE__); + CheckQuestQueue(); + } + + MSaveSpellStateMutex.lock(); + if(save_spell_state_timer.Check()) + { + save_spell_state_timer.Disable(); + GetPlayer()->SaveSpellEffects(); + } + MSaveSpellStateMutex.unlock(); + + if (temp_placement_timer.Check()) { + if(GetTempPlacementSpawn() && GetPlayer()->WasSentSpawn(GetTempPlacementSpawn()->GetID()) && !hasSentTempPlacementSpawn) { + SendMoveObjectMode(GetTempPlacementSpawn(), 0); + hasSentTempPlacementSpawn = true; + temp_placement_timer.Disable(); + } + } + if (pos_update.Check()) + { + ProcessStateCommands(); + + GetPlayer()->ResetMentorship(); // check if we need to asynchronously reset mentorship + + if(GetPlayer()->GetRegionMap()) + GetPlayer()->GetRegionMap()->TicRegionsNearSpawn(this->GetPlayer(), regionDebugMessaging ? this : nullptr); + + if(!player_pos_changed && IsReadyForUpdates() && player_pos_timer < Timer::GetCurrentTime2() && enabled_player_pos_timer) { + if(version > 373) { + GetPlayer()->info_changed = true; + GetPlayer()->vis_changed = true; + GetPlayer()->position_changed = true; + GetPlayer()->changed = true; + GetPlayer()->AddChangedZoneSpawn(); + } + player_pos_timer = Timer::GetCurrentTime2()+5000; + enabled_player_pos_timer = false; + } + if(player_pos_changed && IsReadyForUpdates()) { + player_pos_timer = Timer::GetCurrentTime2()+500; + enabled_player_pos_timer = true; + if(!underworld_cooldown_timer.Enabled() || (underworld_cooldown_timer.Enabled() && underworld_cooldown_timer.Check())) { + bool underworld = false; + if(rule_manager.GetGlobalRule(R_Zone, UseMapUnderworldCoords)->GetBool()) { + if(GetCurrentZone()->GetUnderWorld() != -1000000.0f) { + if(GetPlayer()->GetY() < GetCurrentZone()->GetUnderWorld()) + underworld = true; + } + else if(GetPlayer()->GetMap() && GetPlayer()->GetMap()->GetMinY() != 9999999.0f && GetPlayer()->GetY() < (GetPlayer()->GetMap()->GetMinY() + rule_manager.GetGlobalRule(R_Zone, MapUnderworldCoordOffset)->GetFloat())) { + underworld = true; + } + } + else if(GetPlayer()->GetMap() && GetPlayer()->GetY() < GetCurrentZone()->GetUnderWorld()) { + underworld = true; + } + if(underworld) { + player->SetX(GetCurrentZone()->GetSafeX()); + player->SetY(GetCurrentZone()->GetSafeY()); + player->SetZ(GetCurrentZone()->GetSafeZ()); + player->SetHeading(GetCurrentZone()->GetSafeHeading()); + EQ2Packet* app = GetPlayer()->Move(player->GetX(), player->GetY(), player->GetZ(), GetVersion(), player->GetHeading()); + if(app){ + QueuePacket(app); + } + SimpleMessage(CHANNEL_COLOR_RED, "You have been teleported to a safe location in the zone, because you appeared to have fallen through the world."); + } + underworld_cooldown_timer.Start(); + } + //GetPlayer()->CalculateLocation(); + client_list.CheckPlayersInvisStatus(this); + + player_pos_changed = false; + + GetCurrentZone()->CheckTransporters(this); + + if(GetPlayer()->GetRegionMap()) + { + GetPlayer()->GetRegionMap()->MapRegionsNearSpawn(this->GetPlayer(), regionDebugMessaging ? this : nullptr); + } + } + else if (IsReadyForUpdates() && GetPlayer()->GetRegionMap()) { + GetPlayer()->GetRegionMap()->UpdateRegionsNearSpawn(this->GetPlayer(), regionDebugMessaging ? this : nullptr); + } + } + if (lua_interface && lua_debug && lua_debug_timer.Check()) + lua_interface->UpdateDebugClients(this); + if (quest_pos_timer.Check()) + CheckPlayerQuestsLocationUpdate(); + if (player->GetSkills()->HasSkillUpdates()) { + vector* skills = player->GetSkills()->GetSkillUpdates(); + if (skills) { + Skill* skill = 0; + vector::iterator itr; + for (itr = skills->begin(); itr != skills->end(); itr++) { + skill = *itr; + SkillChanged(skill, skill->previous_val, skill->current_val); + } + EQ2Packet* app = GetPlayer()->skill_list.GetSkillPacket(GetVersion()); + if (app) QueuePacket(app); + safe_delete(skills); + } + } + m_resurrect.writelock(__FUNCTION__, __LINE__); + if (current_rez.should_delete || (current_rez.expire_timer && current_rez.expire_timer->Check(false))) { + safe_delete(current_rez.expire_timer); + current_rez.expire_timer = 0; + current_rez.active = false; + current_rez.caster = 0; + current_rez.crit = false; + current_rez.crit_mod = 0; + current_rez.expire_timer = 0; + current_rez.heal_name = ""; + current_rez.hp_perc = 0; + current_rez.mp_perc = 0; + current_rez.no_calcs = false; + current_rez.range = 0; + current_rez.should_delete = false; + current_rez.spell_name = ""; + current_rez.spell_visual = 0; + current_rez.subspell = 0; + } + m_resurrect.releasewritelock(__FUNCTION__, __LINE__); + + // Quest timers + Quest* failed_step = 0; + MQuestTimers.writelock(__FUNCTION__, __LINE__); + if (quest_timers.size() > 0) { + vector::iterator itr; + GetPlayer()->MPlayerQuests.readlock(__FUNCTION__, __LINE__); + map* player_quests = player->GetPlayerQuests(); + for (itr = quest_timers.begin(); itr != quest_timers.end(); itr++) { + if (player_quests->count(*itr) > 0 && player_quests->at(*itr)->GetStepTimer() != 0) { + Quest* quest = player_quests->at(*itr); + if (Timer::GetUnixTimeStamp() >= quest->GetStepTimer()) { + failed_step = quest; + break; + } + } + else { + itr = quest_timers.erase(itr); + break; + } + } + GetPlayer()->MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + } + MQuestTimers.releasewritelock(__FUNCTION__, __LINE__); + + if (failed_step) + failed_step->StepFailed(failed_step->GetTimerStep()); + + if (player->ControlFlagsChanged()) + player->SendControlFlagUpdates(this); + + if (!eqs || (eqs && !eqs->CheckActive())) + ret = false; + + // redundant to client disconnect + // if(!ret) + // Save(); + + return ret; +} + +ClientList::ClientList() { + MClients.SetName("ClientList::MClients"); + +} + +ClientList::~ClientList() { +} + +void ClientList::ReloadQuests() { + list::iterator client_iter; + Client* client = 0; + MClients.readlock(__FUNCTION__, __LINE__); + for (client_iter = client_list.begin(); client_iter != client_list.end(); client_iter++) { + client = *client_iter; + if (client) + client->ReloadQuests(); + } + MClients.releasereadlock(__FUNCTION__, __LINE__); + +} + +void ClientList::CheckPlayersInvisStatus(Client* owner) { + if (!owner->GetPlayer() || (!owner->GetPlayer()->IsInvis() && !owner->GetPlayer()->IsStealthed())) + return; + + list::iterator client_iter; + Client* client = 0; + MClients.readlock(__FUNCTION__, __LINE__); + for (client_iter = client_list.begin(); client_iter != client_list.end(); client_iter++) { + client = *client_iter; + if (client == owner || client->GetPlayer() == NULL) + continue; + + if (client->GetPlayer()->CheckChangeInvisHistory((Entity*)owner->GetPlayer())) + client->GetPlayer()->GetZone()->SendSpawnChanges(owner->GetPlayer(), client, true, true); + } + MClients.releasereadlock(__FUNCTION__, __LINE__); + +} + +void ClientList::RemovePlayerFromInvisHistory(int32 spawnID) { + list::iterator client_iter; + Client* client = 0; + MClients.readlock(__FUNCTION__, __LINE__); + for (client_iter = client_list.begin(); client_iter != client_list.end(); client_iter++) { + client = *client_iter; + if (!client->GetPlayer()) + continue; + + client->GetPlayer()->RemoveTargetInvisHistory(spawnID); + } + MClients.releasereadlock(__FUNCTION__, __LINE__); +} + + + +int32 ClientList::Count() { + return client_list.size(); +} + +void ClientList::Add(Client* client) { + MClients.writelock(__FUNCTION__, __LINE__); + client_list.push_back(client); + MClients.releasewritelock(__FUNCTION__, __LINE__); + +} + +Client* ClientList::FindByAccountID(int32 account_id) { + list::iterator client_iter; + Client* client = 0; + Client* ret = 0; + MClients.readlock(__FUNCTION__, __LINE__); + for (client_iter = client_list.begin(); client_list.size() > 0 && client_iter != client_list.end(); client_iter++) { + client = *client_iter; + if (client->GetAccountID() == account_id) { + ret = client; + break; + } + } + MClients.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +Client* ClientList::FindByName(char* charName) { + list::iterator client_iter; + Client* client = 0; + Client* ret = 0; + MClients.readlock(__FUNCTION__, __LINE__); + for (client_iter = client_list.begin(); client_list.size() > 0 && client_iter != client_list.end(); client_iter++) { + client = *client_iter; + if (!client || !client->GetPlayer()) + continue; + + if (!strncmp(client->GetPlayer()->GetName(), charName, strlen(charName))) { + ret = client; + break; + } + } + MClients.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +Client* ClientList::Get(int32 ip, int16 port) { + list::iterator client_iter; + Client* client = 0; + Client* ret = 0; + MClients.readlock(__FUNCTION__, __LINE__); + for (client_iter = client_list.begin(); client_list.size() > 0 && client_iter != client_list.end(); client_iter++) { + client = *client_iter; + if (client->GetIP() == ip && client->GetPort() == port) { + ret = client; + break; + } + } + MClients.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +void ClientList::Process() { + + list::iterator client_iter; + list::iterator erase_iter; + Client* client = 0; + MClients.readlock(__FUNCTION__, __LINE__); + erase_iter = client_list.end(); + for (client_iter = client_list.begin(); client_iter != client_list.end(); client_iter++) { + client = *client_iter; + // have a sanity check because the client list can sometimes obtain null client pointers + if (!client || client->remove_from_list || (!client->Process())) { // if we should be removing from list, don't process any further + erase_iter = client_iter; + break; + } + } + MClients.releasereadlock(__FUNCTION__, __LINE__); + if (erase_iter != client_list.end()) { + client = *erase_iter; + MClients.writelock(__FUNCTION__, __LINE__); + client_list.erase(erase_iter); + MClients.releasewritelock(__FUNCTION__, __LINE__); + if (client && !client->remove_from_list) { + struct in_addr in; + in.s_addr = client->GetIP(); + LogWrite(WORLD__INFO, 0, "World", "Removing client from ip: %s port: %i", inet_ntoa(in), client->GetPort()); + safe_delete(client); + } + } + +} + +void ClientList::RemoveConnection(EQStream* eqs) { + Client* client; + + if (eqs) { + list::iterator client_iter; + MClients.readlock(__FUNCTION__, __LINE__); + for (client_iter = client_list.begin(); client_iter != client_list.end(); client_iter++) { + client = *client_iter; + if (client->getConnection() == eqs) + client->Disconnect(true); + } + MClients.releasereadlock(__FUNCTION__, __LINE__); + } + +} + +bool ClientList::ContainsStream(EQStream* eqs) { + if (!eqs) { + return false; + } + list::iterator client_iter; + bool ret = false; + MClients.readlock(__FUNCTION__, __LINE__); + for (client_iter = client_list.begin(); client_iter != client_list.end(); client_iter++) { + if ((*client_iter)->getConnection() && (*client_iter)->getConnection()->GetRemotePort() == eqs->GetRemotePort() && (*client_iter)->getConnection()->GetRemoteIP() == eqs->GetRemoteIP()) { + ret = true; + break; + } + } + MClients.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +void ClientList::Remove(Client* client, bool remove_data) { + client->remove_from_list = true; + if (remove_data) { + safe_delete(client); + } + +} + +void Client::SetCurrentZone(ZoneServer* zone) { + current_zone = zone; + if(player) { + player->SetZone(zone, GetVersion()); + } +} + +void Client::SetCurrentZone(int32 id) { + if (current_zone) { + //current_zone->GetCombat()->RemoveHate(player); + current_zone->RemoveSpawn(player, false, true, true, true, true); + } + SetCurrentZone(zone_list.Get(id)); + +} + +void Client::SetCurrentZoneByInstanceID(int32 id, int32 zoneid) { + if (current_zone) { + //current_zone->GetCombat()->RemoveHate(player); + current_zone->RemoveSpawn(player, false, true, true, true, true); + } + SetCurrentZone(zone_list.GetByInstanceID(id, zoneid)); + +} + +ZoneServer* Client::GetCurrentZone() { + return current_zone; +} + +int8 Client::GetMessageChannelColor(int8 channel_type) { + if (GetVersion() >= 973 && GetVersion() <= 1000) { + if (channel_type == CHANNEL_LOOT) + return CHANNEL_COLOR_NEW_LOOT; + } + else if (GetVersion() >= 973) { + if (channel_type == CHANNEL_LOOT) + return CHANNEL_COLOR_NEWEST_LOOT; + } + if (GetVersion() <= 283) { + if (channel_type <=12) + return channel_type; + switch (channel_type) { + case CHANNEL_GROUP_CHAT: + case CHANNEL_GROUP_SAY: + case CHANNEL_RAID_SAY: + case CHANNEL_GUILD_CHAT: + case CHANNEL_GUILD_SAY: + case CHANNEL_OFFICER_SAY: + case CHANNEL_GUILD_MOTD: + return channel_type - 1; + case CHANNEL_PRIVATE_CHAT: + case CHANNEL_NONPLAYER_TELL: + return channel_type - 5; + case CHANNEL_PRIVATE_TELL: + case CHANNEL_TELL_FROM_CS: + return channel_type - 6; + case CHANNEL_CHAT_CHANNEL_TEXT: + case CHANNEL_OUT_OF_CHARACTER: + case CHANNEL_AUCTION: + case CHANNEL_CUSTOM_CHANNEL: + case CHANNEL_CHARACTER_TEXT: + case CHANNEL_REWARD: + case CHANNEL_DEATH: + case CHANNEL_PET_CHAT: + case CHANNEL_SKILL: + return channel_type - 7; + case CHANNEL_SPELLS: + case CHANNEL_YOU_CAST: + case CHANNEL_YOU_FAIL: + return channel_type - 8; + case CHANNEL_FRIENDLY_CAST: + case CHANNEL_FRIENDLY_FAIL: + case CHANNEL_OTHER_CAST: + case CHANNEL_OTHER_FAIL: + case CHANNEL_HOSTILE_CAST: + case CHANNEL_HOSTILE_FAIL: + case CHANNEL_WORN_OFF: + case CHANNEL_SPELLS_OTHER: + return channel_type - 9; + case CHANNEL_COMBAT: + return channel_type - 15; + case CHANNEL_HEROIC_OPPORTUNITY: + case CHANNEL_NON_MELEE_DAMAGE: + case CHANNEL_DAMAGE_SHIELD: + return channel_type - 16; + case CHANNEL_MELEE_COMBAT: + case CHANNEL_WARNINGS: + case CHANNEL_YOU_HIT: + case CHANNEL_YOU_MISS: + case CHANNEL_ATTACKER_HITS: + case CHANNEL_ATTACKER_MISSES: + return channel_type - 18; + case CHANNEL_OTHER_HIT: + case CHANNEL_OTHER_MISSES: + case CHANNEL_CRITICAL_HIT: + return channel_type - 22; + case CHANNEL_OTHER: + case CHANNEL_MONEY_SPLIT: + case CHANNEL_LOOT: + return channel_type - 30; + case CHANNEL_COMMAND_TEXT: + case CHANNEL_BROADCAST: + case CHANNEL_WHO: + case CHANNEL_COMMANDS: + case CHANNEL_MERCHANT: + case CHANNEL_MERCHANT_BUY_SELL: + case CHANNEL_CONSIDER_MESSAGE: + case CHANNEL_CON_MINUS_2: + case CHANNEL_CON_MINUS_1: + case CHANNEL_CON_0: + case CHANNEL_CON_1: + case CHANNEL_CON_2: + return channel_type - 31; + default: { + return CHANNEL_DEFAULT; + } + } + } + else if (GetVersion() <= 373) { + if (channel_type <=18) + return channel_type; + switch (channel_type) { + case CHANNEL_PRIVATE_CHAT: + case CHANNEL_NONPLAYER_TELL: + return 22; + case CHANNEL_PRIVATE_TELL: + case CHANNEL_TELL_FROM_CS: + return 23; + case CHANNEL_CHAT_CHANNEL_TEXT: + case CHANNEL_OUT_OF_CHARACTER: + case CHANNEL_CUSTOM_CHANNEL: + case CHANNEL_CHARACTER_TEXT: + case CHANNEL_REWARD: + case CHANNEL_DEATH: + case CHANNEL_PET_CHAT: + case CHANNEL_SKILL: + return 26; + case CHANNEL_AUCTION: + return 27; + case CHANNEL_SPELLS: + case CHANNEL_YOU_CAST: + case CHANNEL_YOU_FAIL: + return channel_type - 8; + case CHANNEL_FRIENDLY_CAST: + case CHANNEL_FRIENDLY_FAIL: + case CHANNEL_OTHER_CAST: + case CHANNEL_OTHER_FAIL: + case CHANNEL_HOSTILE_CAST: + case CHANNEL_HOSTILE_FAIL: + case CHANNEL_WORN_OFF: + case CHANNEL_SPELLS_OTHER: + return channel_type - 9; + case CHANNEL_COMBAT: + return channel_type - 15; + case CHANNEL_HEROIC_OPPORTUNITY: + case CHANNEL_NON_MELEE_DAMAGE: + case CHANNEL_DAMAGE_SHIELD: + return channel_type - 16; + case CHANNEL_MELEE_COMBAT: + case CHANNEL_WARNINGS: + case CHANNEL_YOU_HIT: + case CHANNEL_YOU_MISS: + case CHANNEL_ATTACKER_HITS: + case CHANNEL_ATTACKER_MISSES: + return channel_type - 18; + case CHANNEL_OTHER_HIT: + case CHANNEL_OTHER_MISSES: + case CHANNEL_CRITICAL_HIT: + return channel_type - 22; + case CHANNEL_OTHER: + case CHANNEL_MONEY_SPLIT: + case CHANNEL_LOOT: + return channel_type - 30; + case CHANNEL_COMMAND_TEXT: + case CHANNEL_BROADCAST: + case CHANNEL_WHO: + case CHANNEL_COMMANDS: + case CHANNEL_MERCHANT: + case CHANNEL_MERCHANT_BUY_SELL: + case CHANNEL_CONSIDER_MESSAGE: + case CHANNEL_CON_MINUS_2: + case CHANNEL_CON_MINUS_1: + case CHANNEL_CON_0: + case CHANNEL_CON_1: + case CHANNEL_CON_2: + return 68; + default: { + return CHANNEL_DEFAULT; + } + } + } + else if (GetVersion() <= 561) { + if (channel_type < 20) + return channel_type; + switch (channel_type) { + case CHANNEL_GUILD_MOTD: + case CHANNEL_GUILD_MEMBER_ONLINE: + case CHANNEL_GUILD_EVENT: + return channel_type + 1; + case CHANNEL_PRIVATE_CHAT: + case CHANNEL_NONPLAYER_TELL: + return channel_type - 1; + case CHANNEL_PRIVATE_TELL: + case CHANNEL_TELL_FROM_CS: + case CHANNEL_ARENA: + case CHANNEL_CHAT_CHANNEL_TEXT: + case CHANNEL_OUT_OF_CHARACTER: + case CHANNEL_AUCTION: + case CHANNEL_CUSTOM_CHANNEL: + case CHANNEL_CHARACTER_TEXT: + case CHANNEL_REWARD: + case CHANNEL_DEATH: + case CHANNEL_PET_CHAT: + case CHANNEL_SKILL: + case CHANNEL_FACTION: + case CHANNEL_SPELLS: + case CHANNEL_YOU_CAST: + case CHANNEL_YOU_FAIL: + return channel_type - 2; + case CHANNEL_FRIENDLY_CAST: + case CHANNEL_FRIENDLY_FAIL: + case CHANNEL_OTHER_CAST: + case CHANNEL_OTHER_FAIL: + case CHANNEL_HOSTILE_CAST: + case CHANNEL_HOSTILE_FAIL: + case CHANNEL_WORN_OFF: + case CHANNEL_SPELLS_OTHER: + case CHANNEL_HEAL_SPELLS: + case CHANNEL_HEALS: + case CHANNEL_FRIENDLY_HEALS: + case CHANNEL_OTHER_HEALS: + case CHANNEL_HOSTILE_HEALS: + return channel_type - 3; + case CHANNEL_COMBAT: + case CHANNEL_GENERAL_COMBAT: + case CHANNEL_HEROIC_OPPORTUNITY: + case CHANNEL_NON_MELEE_DAMAGE: + case CHANNEL_DAMAGE_SHIELD: + case CHANNEL_WARD: + return channel_type - 4; + case CHANNEL_MELEE_COMBAT: + case CHANNEL_WARNINGS: + case CHANNEL_YOU_HIT: + case CHANNEL_YOU_MISS: + case CHANNEL_ATTACKER_HITS: + case CHANNEL_ATTACKER_MISSES: + case CHANNEL_YOUR_PET_HITS: + case CHANNEL_YOUR_PET_MISSES: + case CHANNEL_ATTACKER_HITS_PET: + case CHANNEL_ATTACKER_MISSES_PET: + case CHANNEL_OTHER_HIT: + case CHANNEL_OTHER_MISSES: + return channel_type - 5; + case CHANNEL_OTHER: + case CHANNEL_MONEY_SPLIT: + case CHANNEL_LOOT: + return channel_type - 14; + case CHANNEL_COMMAND_TEXT: + case CHANNEL_BROADCAST: + case CHANNEL_WHO: + case CHANNEL_COMMANDS: + case CHANNEL_MERCHANT: + case CHANNEL_MERCHANT_BUY_SELL: + case CHANNEL_CONSIDER_MESSAGE: + case CHANNEL_CON_MINUS_2: + case CHANNEL_CON_MINUS_1: + case CHANNEL_CON_0: + case CHANNEL_CON_1: + case CHANNEL_CON_2: + case CHANNEL_TRADESKILLS: + case CHANNEL_HARVESTING: + case CHANNEL_HARVESTING_WARNINGS: + return channel_type - 15; + default: { + return CHANNEL_DEFAULT; + } + } + } + else { + switch (channel_type) { + default: { + return channel_type; + } + } + } + return channel_type; +} + +void Client::HandleTellMessage(Client* from, const char* message, const char* to, int32 current_language_id) { + if (!from || GetPlayer()->IsIgnored(from->GetPlayer()->GetName())) + return; + PacketStruct* packet = configReader.getStruct("WS_HearChat", GetVersion()); + if (packet) { + packet->setDataByName("from", from->GetPlayer()->GetName()); + packet->setDataByName("to", to); + packet->setDataByName("channel", GetMessageChannelColor(CHANNEL_PRIVATE_TELL)); + packet->setDataByName("from_spawn_id", 0xFFFFFFFF); + packet->setDataByName("to_spawn_id", 0xFFFFFFFF); + packet->setDataByName("unknown2", 1, 1); + packet->setDataByName("show_bubble", 1); + + if (current_language_id == 0 || GetPlayer()->HasLanguage(current_language_id)) { + packet->setDataByName("understood", 1); + } + + packet->setDataByName("time", 2); + packet->setDataByName("language", current_language_id); + packet->setMediumStringByName("message", message); + EQ2Packet* outpacket = packet->serialize(); + QueuePacket(outpacket); + safe_delete(packet); + } +} + +void Client::SimpleMessage(int8 color, const char* message) { + PacketStruct* command_packet = configReader.getStruct("WS_DisplayText", GetVersion()); + if (command_packet) { + command_packet->setDataByName("color", GetMessageChannelColor(color));//convert this to the correct client type (different clients have different chat numbers) + command_packet->setMediumStringByName("text", message); + command_packet->setDataByName("unknown02", 0x00ff); + EQ2Packet* outapp = command_packet->serialize(); + QueuePacket(outapp); + safe_delete(command_packet); + } +} + +void Client::SendSpellUpdate(Spell* spell, bool add_silently, bool add_to_hotbar) { + PacketStruct* packet = configReader.getStruct("WS_SpellGainedMsg", GetVersion()); + if (packet) { + int8 xxx = spell->GetSpellData()->is_aa; + packet->setDataByName("spell_type", spell->GetSpellData()->type); + packet->setDataByName("spell_id", spell->GetSpellID()); + packet->setDataByName("unique_id", spell->GetSpellData()->spell_name_crc); + packet->setDataByName("spell_name", spell->GetName()); + if(add_silently) + packet->setDataByName("add_silently", 1); + if(add_to_hotbar) + packet->setDataByName("add_to_hotbar", 1); + packet->setDataByName("unknown", xxx); + packet->setDataByName("display_spell_tier", 1); + packet->setDataByName("unknown3", 1); + packet->setDataByName("tier", spell->GetSpellTier()); + packet->setDataByName("icon", spell->GetSpellIcon()); + packet->setDataByName("icon_type", spell->GetSpellIconBackdrop()); + packet->setDataByName("unknown5", 0xFFFFFFFF); + //packet->PrintPacket(); + + EQ2Packet* outapp = packet->serialize(); + //DumpPacket(outapp); + QueuePacket(outapp); + safe_delete(packet); + } + +} + +void Client::Message(int8 type, const char* message, ...) { + va_list argptr; + char buffer[4096]; + + va_start(argptr, message); + vsnprintf(buffer, sizeof(buffer), message, argptr); + va_end(argptr); + SimpleMessage(type, buffer); +} + +void Client::Disconnect(bool send_disconnect) +{ + LogWrite(CCLIENT__DEBUG, 0, "CClient", "Client Disconnect..."); + this->Save(); + this->GetPlayer()->WritePlayerStatistics(); + + SetConnected(false); + + if (send_disconnect && getConnection()) + getConnection()->SendDisconnect(true); + + eqs = 0; +} + +bool Client::Summon(const char* search_name) { + Spawn* target = 0; + if (search_name || GetPlayer()->GetTarget()) { + Client* search_client = 0; + if (search_name) { + target = GetCurrentZone()->FindSpawn(GetPlayer(), search_name); + if (target && target->IsPlayer()) + search_client = target->GetClient(); + if (!target) { + search_client = zone_list.GetClientByCharName(string(search_name)); + if (search_client) + target = search_client->GetPlayer(); + } + } + else + target = GetPlayer()->GetTarget(); + if (target && target != GetPlayer()) { + target->SetX(GetPlayer()->GetX()); + target->SetY(GetPlayer()->GetY()); + target->SetZ(GetPlayer()->GetZ()); + target->SetHeading(GetPlayer()->GetHeading()); + if (!target->IsPlayer()) { + target->SetSpawnOrigX(target->GetX()); + target->SetSpawnOrigY(target->GetY()); + target->SetSpawnOrigZ(target->GetZ()); + target->SetSpawnOrigHeading(target->GetHeading()); + } + target->SetLocation(GetPlayer()->GetLocation()); + if(target->IsNPC()) { + ((NPC*)target)->HaltMovement(); + } + } + else if (target) + Message(CHANNEL_COLOR_RED, "Error: You cannot summon yourself!"); + if (search_client && search_client != this) { + search_client->Message(CHANNEL_COLOR_YELLOW, "You have been summoned by '%s'!", GetPlayer()->GetName()); + Message(CHANNEL_COLOR_YELLOW, "Summoning '%s'...", search_client->GetPlayer()->GetName()); + if (search_client->GetCurrentZone() != GetCurrentZone()) + search_client->Zone(GetCurrentZone()->GetZoneName(), false); + else { + EQ2Packet* app = search_client->GetPlayer()->Move(GetPlayer()->GetX(), GetPlayer()->GetY(), GetPlayer()->GetZ(), search_client->GetVersion()); + if (app) + search_client->QueuePacket(app); + } + } + } + + if (!target) + return false; + else + return true; +} + +std::string Client::IdentifyInstanceLockout(int32 zoneID, bool displayClient) { + int8 instanceType = database.GetInstanceTypeByZoneID(zoneID); + if(instanceType < 1) + return std::string(""); + + ZoneServer* instance_zone = nullptr; + InstanceData* data = GetPlayer()->GetCharacterInstances()->FindInstanceByZoneID(zoneID); + if (data) { + // If lockout instances check to see if we are locked out + if (instanceType == SOLO_LOCKOUT_INSTANCE || instanceType == GROUP_LOCKOUT_INSTANCE || instanceType == RAID_LOCKOUT_INSTANCE) { + int32 time = 0; + // Check success timer + if (data->last_success_timestamp > 0) { + if (Timer::GetUnixTimeStamp() < data->last_success_timestamp + data->success_lockout_time) { + // Timer has not expired yet can't re enter + LogWrite(INSTANCE__DEBUG, 0, "Instance", "Success lockout not expired for character %s in zone %u", GetPlayer()->GetName(), zoneID); + time = (data->last_success_timestamp + data->success_lockout_time) - Timer::GetUnixTimeStamp(); + } + } + + // Check failure timer + if (data->last_failure_timestamp > 0) { + if (Timer::GetUnixTimeStamp() < data->last_failure_timestamp + data->failure_lockout_time) { + // Timer has not expired yet + LogWrite(INSTANCE__DEBUG, 0, "Instance", "Failure lockout not expired for character %s in zone %u", GetPlayer()->GetName(), zoneID); + time = (data->last_failure_timestamp + data->failure_lockout_time) - Timer::GetUnixTimeStamp(); + } + } + + // Time > 0 then we are locked out, make the message and send it and return true + if (time > 0) { + string time_msg = ""; + int16 hour; + int8 min; + int8 sec; + hour = time / 3600; + time = time % 3600; + min = time / 60; + time = time % 60; + sec = time; + + if (hour > 0) { + char temp[10]; + snprintf(temp, 9," %i", hour); + time_msg.append(temp); + time_msg.append(" hour"); + time_msg.append((hour > 1) ? "s" : ""); + } + if (min > 0) { + char temp[5]; + snprintf(temp, 4," %i", min); + time_msg.append(temp); + time_msg.append(" minute"); + time_msg.append((min > 1) ? "s" : ""); + } + // Only add seconds if minutes and hours are 0 + if (hour == 0 && min == 0 && sec > 0) { + char temp[5]; + snprintf(temp, 4," %i", sec); + time_msg.append(temp); + time_msg.append(" second"); + time_msg.append((sec > 1) ? "s" : ""); + } + + if(displayClient) + Message(CHANNEL_COLOR_YELLOW, "You may not enter again for%s.", time_msg.c_str()); + return time_msg; + } + } + } + return std::string(""); +} + +ZoneServer* Client::IdentifyInstance(int32 zoneID) { + int8 instanceType = database.GetInstanceTypeByZoneID(zoneID); + if(instanceType < 1) + return nullptr; + + ZoneServer* instance_zone = nullptr; + InstanceData* data = GetPlayer()->GetCharacterInstances()->FindInstanceByZoneID(zoneID); + if (data) { + std::string lockoutTime = IdentifyInstanceLockout(zoneID); + // If lockout instances check to see if we are locked out + if (lockoutTime.length() > 0) { + return nullptr; + } + + // Need to update `character_instances` table with new timestamps (for persistent) and instance id's + instance_zone = zone_list.GetByInstanceID(data->instance_id, zoneID, false, false); + + // if we got an instance_zone and the instance_id from the data is 0 or data instance id is not the same as the zone instance id then update values + if (instance_zone && (data->instance_id == 0 || data->instance_id != instance_zone->GetInstanceID())) { + if (instanceType == SOLO_PERSIST_INSTANCE || instanceType == GROUP_PERSIST_INSTANCE || instanceType == RAID_PERSIST_INSTANCE) { + database.UpdateCharacterInstance(GetCharacterID(), string(instance_zone->GetZoneName()), instance_zone->GetInstanceID(), 1, Timer::GetUnixTimeStamp()); + data->last_success_timestamp = Timer::GetUnixTimeStamp(); + } + else + database.UpdateCharacterInstance(GetCharacterID(), string(instance_zone->GetZoneName()), instance_zone->GetInstanceID()); + + data->instance_id = instance_zone->GetInstanceID(); + } + } + return instance_zone; +} + +bool Client::TryZoneInstance(int32 zoneID, bool zone_coords_valid) { + ZoneServer* instance_zone = NULL; + int8 instanceType = 0; + + // determine if this is a group instanced zone that already exists + instance_zone = GetPlayer()->GetGroupMemberInZone(zoneID); + + if (instance_zone != NULL) + Zone(instance_zone->GetInstanceID(), zone_coords_valid); + else if ((instanceType = database.GetInstanceTypeByZoneID(zoneID)) > 0) + { + // best to check if we already have our own instance! + if((instance_zone = IdentifyInstance(zoneID)) == nullptr) + { + switch (instanceType) + { + case SOLO_LOCKOUT_INSTANCE: + case GROUP_LOCKOUT_INSTANCE: + case RAID_LOCKOUT_INSTANCE: + { + instance_zone = zone_list.GetByInstanceID(0, zoneID); + if (instance_zone) { + // once lockout instance zone shuts down you can't renenter if you have a lockout or if you don't you get a new zone + // so delete `instances` entry for the zone when it shuts down. + int32 db_id = database.AddCharacterInstance(GetPlayer()->GetCharacterID(), instance_zone->GetInstanceID(), string(instance_zone->GetZoneName()), instance_zone->GetInstanceType(), 0, 0, instance_zone->GetDefaultLockoutTime(), instance_zone->GetDefaultReenterTime()); + + if (db_id > 0) + GetPlayer()->GetCharacterInstances()->AddInstance(db_id, instance_zone->GetInstanceID(), 0, 0, instance_zone->GetDefaultLockoutTime(), instance_zone->GetDefaultReenterTime(), zoneID, instance_zone->GetInstanceType(), string(instance_zone->GetZoneName())); + } + break; + } + case SOLO_PERSIST_INSTANCE: + case GROUP_PERSIST_INSTANCE: + case RAID_PERSIST_INSTANCE: + { + instance_zone = zone_list.GetByInstanceID(0, zoneID); + if (instance_zone) { + int32 db_id = database.AddCharacterInstance(GetPlayer()->GetCharacterID(), instance_zone->GetInstanceID(), string(instance_zone->GetZoneName()), instance_zone->GetInstanceType(), Timer::GetUnixTimeStamp(), 0, instance_zone->GetDefaultLockoutTime(), instance_zone->GetDefaultReenterTime()); + + if (db_id > 0) + GetPlayer()->GetCharacterInstances()->AddInstance(db_id, instance_zone->GetInstanceID(), Timer::GetUnixTimeStamp(), 0, instance_zone->GetDefaultLockoutTime(), instance_zone->GetDefaultReenterTime(), zoneID, instance_zone->GetInstanceType(), string(instance_zone->GetZoneName())); + } + break; + } + case PUBLIC_INSTANCE: + case TRADESKILL_INSTANCE: + { + // if its public/tradeskill, look for a public already setup + instance_zone = zone_list.GetByLowestPopulation(zoneID); + if (instance_zone) { + // Check the current population against the max population, if greater or equal start a new version + if (instance_zone->GetClientCount() >= rule_manager.GetGlobalRule(R_Zone, MaxPlayers)->GetInt32()) + instance_zone = zone_list.GetByInstanceID(0, zoneID); + } + else + instance_zone = zone_list.GetByInstanceID(0, zoneID); + + break; + } + + case PERSONAL_HOUSE_INSTANCE: + case GUILD_HOUSE_INSTANCE: + { + // Because of the way housing works (need to load a specific instance id supplied in a packet) we can't + // use this function without some rework, so it will all be handled in Client::HandlePacket() + // with the OP_EnterHouseMsg opcode + break; + } + + case QUEST_INSTANCE: + { + instance_zone = zone_list.GetByInstanceID(0, zoneID); + break; + /* + ALTER TABLE `zones` CHANGE COLUMN `instance_type` `instance_type` ENUM('NONE','GROUP_LOCKOUT_INSTANCE','GROUP_PERSIST_INSTANCE','RAID_LOCKOUT_INSTANCE','RAID_PERSIST_INSTANCE','SOLO_LOCKOUT_INSTANCE','SOLO_PERSIST_INSTANCE','TRADESKILL_INSTANCE','PUBLIC_INSTANCE','PERSONAL_HOUSE_INSTANCE','GUILD_HOUSE_INSTANCE','QUEST_INSTANCE') NOT NULL DEFAULT 'NONE' COLLATE 'latin1_general_ci' AFTER `start_zone`; + */ + } + + default: + { + // NONE + } + + } + + } + + if (instance_zone != NULL) + Zone(instance_zone, zone_coords_valid); + } + + + if (instance_zone != NULL) + return true; + else + return false; +} + +bool Client::GotoSpawn(const char* search_name, bool forceTarget) { + Spawn* target = 0; + if (search_name || GetPlayer()->GetTarget()) { + Client* search_client = 0; + if (search_name) { + target = GetCurrentZone()->FindSpawn(GetPlayer(), search_name); + if (!target) { + search_client = zone_list.GetClientByCharName(search_name); + if (search_client) + target = search_client->GetPlayer(); + } + if (target) + GetPlayer()->SetTarget(target); + } + else + target = GetPlayer()->GetTarget(); + + float y = (target != nullptr) ? target->GetY() : 0.0f; + if(target && target->GetMap() != GetPlayer()->GetMap()) { + auto loc = glm::vec3(target->GetX(), target->GetZ(), target->GetY()); + y = GetPlayer()->FindBestZ(loc, nullptr); + } + if (target && target != GetPlayer()) { + GetPlayer()->SetX(target->GetX()); + GetPlayer()->SetY(y); + GetPlayer()->SetZ(target->GetZ()); + GetPlayer()->SetHeading(target->GetHeading()); + GetPlayer()->SetLocation(target->GetLocation()); + Message(CHANNEL_COLOR_YELLOW, "Warping to '%s'", target->GetName()); + } + else if (target) + Message(CHANNEL_COLOR_RED, "Error: You cannot goto yourself!"); + if (search_client && search_client->GetCurrentZone() != GetCurrentZone()) + Zone(search_client->GetCurrentZone()->GetZoneName(), false); + else if (target) { + EQ2Packet* app = GetPlayer()->Move(target->GetX(), y, target->GetZ(), GetVersion(), (target->GetHeading() + 180.0f)); + if (app) + QueuePacket(app); + } + } + + if (!target) + return false; + else + return true; +} + +bool Client::CheckZoneAccess(const char* zoneName) { + + LogWrite(CCLIENT__DEBUG, 0, "Client", "Zone access check for %s (%u), client: %u", zoneName, database.GetZoneID(zoneName), GetVersion()); + + ZoneServer* zone = zone_list.Get(zoneName, false, false, false); + + // JA: implemented /zone lock|unlock commands (2012.07.28) + if (zone && zone->GetZoneLockState()) + { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Zone currently LOCKED: '%s' (%ul)", zoneName, zone->GetZoneID()); + Message(CHANNEL_COLOR_RED, "This zone is locked, and you don't have the key! (%s).", zoneName); + return false; + } + + sint16 zoneMinStatus = 0; + int16 zoneMinLevel = 0; + int16 zoneMaxLevel = 0; + int16 zoneMinVersion = 0; + if (!zone) + { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Grabbing zone requirements for %s", zoneName); + bool success = database.GetZoneRequirements(zoneName, &zoneMinStatus, &zoneMinLevel, &zoneMaxLevel, &zoneMinVersion); + + if (!success) { // couldn't even find the zone, this shouldn't happen though.. + return true; + } + } + else + { + zoneMinStatus = zone->GetMinimumStatus(); + zoneMinLevel = zone->GetMinimumLevel(); + zoneMaxLevel = zone->GetMaximumLevel(); + zoneMinVersion = zone->GetMinimumVersion(); + } + + LogWrite(CCLIENT__DEBUG, 0, "Client", "Access Requirements: status %i, level %i - %i, req >= %i version", zoneMinStatus, zoneMinLevel, zoneMaxLevel, zoneMinVersion); + + // use ZoneLevelOverrideStatus in both min_level and max_level checks + sint16 ZoneLevelOverrideStatus = rule_manager.GetGlobalRule(R_Zone, MinZoneLevelOverrideStatus)->GetSInt16(); + + if ((zoneMinVersion > 0) && (GetVersion() < zoneMinVersion)) + { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Zone MinVersion of %i challenge...", zoneMinVersion); + Message(CHANNEL_COLOR_RED, "You do not have the required expansion pack to enter here (%s).", database.GetExpansionIDByVersion(zoneMinVersion).c_str()); + LogWrite(CCLIENT__DEBUG, 0, "Client", "Client denied access to zone '%s' (version req: %i)", zoneName, zoneMinVersion); + return false; + } + + + if ((zoneMinLevel > 1) && (player->GetLevel() < zoneMinLevel)) + { + if (ZoneLevelOverrideStatus && ZoneLevelOverrideStatus > GetAdminStatus()) + { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Player denied access to zone '%s' (level req: %i)", zoneName, player->GetLevel()); + Message(CHANNEL_COLOR_RED, "Your level is too low to enter here (%s)", zoneMinLevel); + return false; + } + } + + if ((zoneMaxLevel > 1) && (player->GetLevel() > zoneMaxLevel)) + { + if (ZoneLevelOverrideStatus && ZoneLevelOverrideStatus > GetAdminStatus()) + { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Player denied access to zone '%s' (level req: %i)", zoneName, player->GetLevel()); + Message(CHANNEL_COLOR_RED, "Your level is too high to enter here (%s)", zoneMaxLevel); + return false; + } + } + + if ((zoneMinStatus > 0) && (GetAdminStatus() < zoneMinStatus)) + { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Zone MinStatus of %i challenge...", zoneMinStatus); + + sint16 ZoneAccessOverrideStatus = rule_manager.GetGlobalRule(R_Zone, MinZoneAccessOverrideStatus)->GetSInt16(); + if (ZoneAccessOverrideStatus && ZoneAccessOverrideStatus > GetAdminStatus()) + { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Player denied access to zone '%s' (status req: %i)", zoneName, GetAdminStatus()); + Message(CHANNEL_COLOR_RED, "You do not have permission to enter here (%i).", zoneMinStatus); + return false; + } + } + return true; +} + +void Client::Zone(int32 instanceid, bool set_coords, bool byInstanceID, bool is_spell) { + Zone(zone_list.GetByInstanceID(instanceid, 0, false, true), set_coords, is_spell); + +} + +void Client::Zone(ZoneServer* new_zone, bool set_coords, bool is_spell) { + if (!new_zone) { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Zone Request Denied! No 'new_zone' value"); + return; + } + + if(GetVersion() == 373 && GetAdminStatus() == 0) { + if(strncasecmp(new_zone->GetZoneFile(),"boat_06p_tutorial02",19) && strncasecmp(new_zone->GetZoneFile(),"tutorial_island02",17) + && strncasecmp(new_zone->GetZoneFile(),"tutorial_island02_epic",22)) { // accounts for 01 and 02 zones + SimpleMessage(CHANNEL_COLOR_RED, "This client does not currently support beyond the boat tutorial (farjourneyfreeport) or island of refuge (islerefuge1)"); + return; + } + } + else if(GetVersion() < 546 && GetVersion() > 561) { + if(strncasecmp(new_zone->GetZoneFile(),"design_path_script",18) == 0) { + SimpleMessage(CHANNEL_COLOR_RED, "This zone is only available for KoS and earlier clients."); + return; + } + } + + client_zoning = true; + zoning_id = new_zone->GetZoneID(); + zoning_instance_id = new_zone->GetInstanceID(); + + LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Setting player Resurrecting to 'true'", __FUNCTION__); + player->SetResurrecting(true); + + LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Removing player from fighting...", __FUNCTION__); + //GetCurrentZone()->GetCombat()->RemoveHate(player); + SaveSpells(); + + ResetSendMail(); + // Remove players pet from zone if there is one + ((Entity*)player)->DismissAllPets(); + + LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Removing player from current zone...", __FUNCTION__); + GetCurrentZone()->RemoveSpawn(player, false, true, true, true, !is_spell); + + GetPlayer()->DeleteSpellEffects(true); + + LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Setting zone to '%s'...", __FUNCTION__, new_zone->GetZoneName()); + SetZoningDestination(new_zone); + SetCurrentZone(new_zone); + + if (player->GetGroupMemberInfo()) + { + LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Player in group, updating group info...", __FUNCTION__); + player->UpdateGroupMemberInfo(); + world.GetGroupManager()->SendGroupUpdate(player->GetGroupMemberInfo()->group_id, this); + } + + // block out the member info for the group + TempRemoveGroup(); + + UpdateTimeStampFlag(ZONE_UPDATE_FLAG); + + if (set_coords) + { + LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Zoning player to coordinates x: %2f, y: %2f, z: %2f, heading: %2f in zone '%s'...", + __FUNCTION__, + GetCurrentZone()->GetSafeX(), + GetCurrentZone()->GetSafeY(), + GetCurrentZone()->GetSafeZ(), + GetCurrentZone()->GetSafeHeading(), + new_zone->GetZoneName() + ); + player->SetX(GetCurrentZone()->GetSafeX()); + player->SetY(GetCurrentZone()->GetSafeY()); + player->SetZ(GetCurrentZone()->GetSafeZ()); + player->SetHeading(GetCurrentZone()->GetSafeHeading()); + + SetZoningCoords(GetCurrentZone()->GetSafeX(), GetCurrentZone()->GetSafeY(), + GetCurrentZone()->GetSafeZ(), GetCurrentZone()->GetSafeHeading()); + } + else { + ResetZoningCoords(); + } + + LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Saving Player info...", __FUNCTION__); + Save(); + + char* new_zone_ip = 0; + if (IsPrivateAddress(this->GetIP()) && strlen(net.GetInternalWorldAddress()) > 0) + new_zone_ip = net.GetInternalWorldAddress(); + else + new_zone_ip = net.GetWorldAddress(); + LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: New Zone IP '%s'...", __FUNCTION__, new_zone_ip); + + int32 key = Timer::GetUnixTimeStamp(); + LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Sending ZoneChangeMsg...", __FUNCTION__); + ClientPacketFunctions::SendZoneChange(this, new_zone_ip, net.GetWorldPort(), key); + + LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Sending to zone_auth.AddAuth...", __FUNCTION__); + zone_auth.AddAuth(new ZoneAuthRequest(GetAccountID(), player->GetName(), key)); + if (version > 373) { + PacketStruct* packet = configReader.getStruct("WS_CancelMoveObjectMode", version); + if (packet) + { + QueuePacket(packet->serialize()); + safe_delete(packet); + } + } +} + +void Client::Zone(const char* new_zone, bool set_coords, bool is_spell) +{ + LogWrite(CCLIENT__DEBUG, 0, "Client", "Zone Request to '%s'", new_zone); + Zone(zone_list.Get(new_zone), set_coords, is_spell); +} + +float Client::DistanceFrom(Client* client) { + float ret = 0; + if (client && client != this) { + ret = pow(player->GetX() - client->player->GetX(), 2) + pow(player->GetY() - client->player->GetY(), 2) + pow(player->GetZ() - client->player->GetZ(), 2); + ret = sqrt(ret); + } + + return ret; +} + +void Client::DetermineCharacterUpdates() { + ServerPacket* outpack = new ServerPacket(ServerOP_BasicCharUpdate, sizeof(CharDataUpdate_Struct)); + CharDataUpdate_Struct* cdu = (CharDataUpdate_Struct*)outpack->pBuffer; + cdu->account_id = GetAccountID(); + cdu->char_id = GetCharacterID(); + int32 timestamp = Timer::GetUnixTimeStamp(); + int8 flag = GetTimeStampFlag(); + if (flag & LEVEL_UPDATE_FLAG) + { + cdu->update_field = LEVEL_UPDATE_FLAG; + cdu->update_data = player->GetLevel(); + loginserver.SendPacket(outpack); + } + //if(flag&CLASS_UPDATE_FLAG && player->GetLevel() >= 20)// Perseverance only + if (flag & CLASS_UPDATE_FLAG) + { + cdu->update_field = CLASS_UPDATE_FLAG; + cdu->update_data = player->GetAdventureClass(); + loginserver.SendPacket(outpack); + } + if (flag & GENDER_UPDATE_FLAG) + { + cdu->update_field = GENDER_UPDATE_FLAG; + cdu->update_data = player->GetGender(); + loginserver.SendPacket(outpack); + } + if (flag & DELETE_UPDATE_FLAG) { + LogWrite(MISC__TODO, 1, "TODO", "Delete update req in func: %s, line: %i", __FUNCTION__, __LINE__); + } + + safe_delete(outpack); // Zone, armor and name use a different structure + + if (flag & RACE_UPDATE_FLAG) + { + outpack = new ServerPacket(ServerOP_RaceUpdate, sizeof(RaceUpdate_Struct)); + RaceUpdate_Struct* ru = (RaceUpdate_Struct*)outpack->pBuffer; + ru->account_id = GetAccountID(); + ru->char_id = GetCharacterID(); + ru->race = player->GetRace(); + ru->model_type = player->GetModelType(); + loginserver.SendPacket(outpack); + safe_delete(outpack); + } + + if (flag & ZONE_UPDATE_FLAG) { + ServerPacket* outpack = new ServerPacket(ServerOP_ZoneUpdate, CHARZONESTRUCT_MAXSIZE); + memset(outpack->pBuffer, 0, CHARZONESTRUCT_MAXSIZE); + CharZoneUpdate_Struct* czu = (CharZoneUpdate_Struct*)outpack->pBuffer; + czu->account_id = GetAccountID(); + czu->char_id = GetCharacterID(); + czu->zone_id = GetCurrentZone()->GetZoneID(); + const char* zone_file = GetCurrentZone()->GetZoneFile(); + czu->zone_length = strlen(zone_file); + if (czu->zone_length > 64) + czu->zone_length = 64; + strncpy(czu->new_zone, zone_file, czu->zone_length); + loginserver.SendPacket(outpack); + safe_delete(outpack); + } + if (flag & ARMOR_UPDATE_FLAG) { + LogWrite(MISC__TODO, 1, "TODO", "Armor update req in func: %s, line: %i", __FUNCTION__, __LINE__); + } + if (flag & NAME_UPDATE_FLAG) { + LogWrite(MISC__TODO, 1, "TODO", "Name update req in func: %s, line: %i", __FUNCTION__, __LINE__); + } + + database.UpdateCharacterTimeStamp(GetAccountID(), GetCharacterID(), timestamp); + +} + +void Client::Save() { + if (GetCharacterID() == 0 || IsSaveDisabled()) + return; + + if (current_zone) { + DetermineCharacterUpdates(); + + UpdateCharacterInstances(); + + this->SetLastSavedTimeStamp(Timer::GetCurrentTime2()); + database.Save(this); + if (GetPlayer()->UpdateQuickbarNeeded()) { + database.SaveQuickBar(GetCharacterID(), GetPlayer()->GetQuickbar()); + GetPlayer()->ResetQuickbarNeeded(); + } + database.SaveItems(this); + database.SaveBuyBacks(this); + + GetPlayer()->SaveHistory(); + GetPlayer()->SaveLUAHistory(); + MSaveSpellStateMutex.lock(); + GetPlayer()->SaveSpellEffects(); + MSaveSpellStateMutex.unlock(); + } + +} + +void Client::UpdateCharacterInstances() { + if (GetPlayer() != NULL) + GetPlayer()->GetCharacterInstances()->ProcessInstanceTimers(GetPlayer()); + + /*if ( GetPlayer() != NULL ) + { + // determine the last timestamp then get a new one, determine the difference in the timestamp + // to use for applying the update to each instances timer + int32 lastSaveTS = GetLastSavedTimeStamp(); + int32 newSaveTS = Timer::GetUnixTimeStamp(); + int32 diffTS = newSaveTS - lastSaveTS; + + // update instance timers + GetPlayer()->GetCharacterInstances().ProcessInstanceTimers(GetPlayer(),diffTS); + + // update with the new timestamp and save the db + this->SetLastSavedTimeStamp(newSaveTS); + }*/ + +} + +void Client::HandleVerbRequest(EQApplicationPacket* app) { + PacketStruct* packet = configReader.getStruct("WS_EntityVerbsRequest", GetVersion()); + if (packet) { + if(packet->LoadPacketData(app->pBuffer, app->size)) { + int32 spawn_id = packet->getType_int32_ByName("spawn_id"); + PacketStruct* out = configReader.getStruct("WS_EntityVerbsResponse", GetVersion()); + Spawn* spawn = GetPlayer()->GetSpawnWithPlayerID(spawn_id); + vector commands; + vector delete_commands; + if (out && spawn) { + for (int32 i = 0; i < spawn->primary_command_list.size(); i++) + { + // default is a deny list not allow, only allow if on the iterator list and itr second is not false (deny) + if (!spawn->primary_command_list[i]->default_allow_list) + { + map::iterator itr = spawn->primary_command_list[i]->allow_or_deny.find(GetPlayer()->GetCharacterID()); + if (itr == spawn->primary_command_list[i]->allow_or_deny.end() || !itr->second) + continue; + } + else + { + // default is allow list, only deny if added to the list as deny (false itr second) + map::iterator itr = spawn->primary_command_list[i]->allow_or_deny.find(GetPlayer()->GetCharacterID()); + if (itr != spawn->primary_command_list[i]->allow_or_deny.end() && !itr->second) + continue; + } + commands.push_back(spawn->primary_command_list[i]); + } + for (int32 i = 0; i < spawn->secondary_command_list.size(); i++) + commands.push_back(spawn->secondary_command_list[i]); + if (spawn->IsPlayer()) { + if (player->IsFriend(spawn->GetName())) + delete_commands.push_back(player->CreateEntityCommand("remove from friends list", 10000, "friend_remove", "", 0, 0)); + else + delete_commands.push_back(player->CreateEntityCommand("add to friends list", 10000, "friend_add", "", 0, 0)); + if (player->IsIgnored(spawn->GetName())) + delete_commands.push_back(player->CreateEntityCommand("remove from ignore list", 10000, "ignore_remove", "", 0, 0)); + else + { + delete_commands.push_back(player->CreateEntityCommand("add to ignore list", 10000, "ignore_add", "", 0, 0)); + delete_commands.push_back(player->CreateEntityCommand("Trade", 10, "start_trade", "", 0, 0)); + } + if (((Player*)spawn)->GetGroupMemberInfo()) { + if (player->IsGroupMember((Player*)spawn) && player->GetGroupMemberInfo()->leader) { //group leader + delete_commands.push_back(player->CreateEntityCommand("kick from group", 10000, "kickfromgroup", "", 0, 0)); + delete_commands.push_back(player->CreateEntityCommand("make group leader", 10000, "makeleader", "", 0, 0)); + } + if(spawn->IsPlayer() && player->GetGroupMemberInfo() && !player->GetGroupMemberInfo()->mentor_target_char_id) + delete_commands.push_back(player->CreateEntityCommand("Mentor", 10000, "mentor", "", 0, 0)); + else if(spawn->IsPlayer() && player->GetGroupMemberInfo() && player->GetGroupMemberInfo()->mentor_target_char_id == ((Player*)spawn)->GetCharacterID()) + delete_commands.push_back(player->CreateEntityCommand("Stop Mentoring", 10000, "unmentor", "", 0, 0)); + } + else if (!player->GetGroupMemberInfo() || (player->GetGroupMemberInfo()->leader && world.GetGroupManager()->GetGroupSize(player->GetGroupMemberInfo()->group_id) < 6)) + delete_commands.push_back(player->CreateEntityCommand("invite to group", 10000, "invite", "", 0, 0)); + commands.insert(commands.end(), delete_commands.begin(), delete_commands.end()); + } + out->setDataByName("spawn_id", spawn_id); + out->setArrayLengthByName("num_verbs", commands.size()); + + for (int32 i = 0; i < commands.size(); i++) { + out->setArrayDataByName("command", commands[i]->command.c_str(), i); + out->setArrayDataByName("distance", commands[i]->distance, i); + if (commands[i]->error_text.length() == 0) + out->setArrayAddToPacketByName("error", false, i); + else { + out->setArrayDataByName("display_error", 1, i); + out->setArrayDataByName("error", commands[i]->error_text.c_str(), i); + } + out->setArrayDataByName("display_text", commands[i]->name.c_str(), i); + } + EQ2Packet* outapp = out->serialize(); + //DumpPacket(outapp); + QueuePacket(outapp); + safe_delete(out); + for (int32 i = 0; i < delete_commands.size(); i++) { + safe_delete(delete_commands[i]); + } + } + } + safe_delete(packet); + } + +} + +void Client::SkillChanged(Skill* skill, int16 previous_value, int16 new_value) { + if (previous_value != new_value) { + Message(CHANNEL_SKILL, "You get %s at %s (%i/%i).", new_value > previous_value ? "better" : "worse", skill->name.data.c_str(), new_value, skill->max_val); + char tmp[200] = { 0 }; + sprintf(tmp, "\\#6EFF6EYou get %s at \12\\#C8FFC8%s\\#6EFF6E! (%i/%i)", new_value > previous_value ? "better" : "worse", skill->name.data.c_str(), new_value, skill->max_val); + SendPopupMessage(6, tmp, new_value > previous_value ? "skill_up" : "skill_down", 2.75f, 0xFF, 0xFF, 0xFF); + } + +} + +void Client::SendPopupMessage(int8 unknown, const char* text, const char* type, float size, int8 red, int8 green, int8 blue) +{ + /* JA notes on the unknown: + 2 = ding glimmer + 16 = Achievement Unlocked + 6 no longer does anything + */ + + PacketStruct* packet = configReader.getStruct("WS_OnScreenMsg", GetVersion()); + if (packet) { + packet->setDataByName("unknown", unknown); + packet->setMediumStringByName("text", text); + if (type && strlen(type) > 0) + packet->setMediumStringByName("message_type", type); + packet->setDataByName("size", size); + packet->setDataByName("red", red); + packet->setDataByName("green", green); + packet->setDataByName("blue", blue); + QueuePacket(packet->serialize()); + safe_delete(packet); + } + +} + +void Client::ChangeLevel(int16 old_level, int16 new_level) { + if (new_level < 1) { + SimpleMessage(CHANNEL_COLOR_RED, "You cannot be lower than level 1!"); + return; + } + + if (player->GetLevel() != new_level) { + player->SetLevel(new_level); + if (player->GetGroupMemberInfo()) { + player->UpdateGroupMemberInfo(); + world.GetGroupManager()->SendGroupUpdate(player->GetGroupMemberInfo()->group_id); + } + } + + if (new_level > old_level) { + player->UpdatePlayerHistory(HISTORY_TYPE_XP, HISTORY_SUBTYPE_ADVENTURE, new_level, player->GetAdventureClass()); + } + + if (player->GetPet()) { + NPC* pet = (NPC*)player->GetPet(); + if (pet->GetMaxPetLevel() == 0 || new_level <= pet->GetMaxPetLevel()) { + pet->SetLevel(new_level); + pet->UpdateWeapons(); + PacketStruct* command_packet = configReader.getStruct("WS_CannedEmote", GetVersion()); + if (command_packet) { + command_packet->setDataByName("spawn_id", player->GetIDWithPlayerSpawn(pet)); + command_packet->setDataByName("anim_type", 1753); + QueuePacket(command_packet->serialize()); + safe_delete(command_packet); + } + } + } + + PacketStruct* level_update = configReader.getStruct("WS_LevelChanged", GetVersion()); + if (level_update) { + level_update->setDataByName("old_level", old_level); + level_update->setDataByName("new_level", new_level); + QueuePacket(level_update->serialize()); + safe_delete(level_update); + GetCurrentZone()->StartZoneSpawnsForLevelThread(this); + GetCurrentZone()->SendCastSpellPacket(322, player, player); //send level up spell effect + //GetCurrentZone()->SendAllSpawnsForLevelChange(this); + } + + PacketStruct* command_packet = configReader.getStruct("WS_CannedEmote", GetVersion()); + if (command_packet) { + command_packet->setDataByName("spawn_id", player->GetIDWithPlayerSpawn(player)); + command_packet->setDataByName("anim_type", 1753); + QueuePacket(command_packet->serialize()); + safe_delete(command_packet); + } + + if (!player->get_character_flag(CF_ENABLE_CHANGE_LASTNAME) && new_level >= rule_manager.GetGlobalRule(R_Player, MinLastNameLevel)->GetInt8()) + player->set_character_flag(CF_ENABLE_CHANGE_LASTNAME); + + SendNewAdventureSpells(); + + GetPlayer()->GetInfoStruct()->set_level(new_level); + GetPlayer()->UpdateWeapons(); + // GetPlayer()->SetLevel(new_level); + + LogWrite(MISC__TODO, 1, "TODO", "Get new HP/POWER/stat based on default values from DB\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__); + + GetPlayer()->CalculatePlayerHPPower(); + GetPlayer()->CalculateBonuses(); + GetPlayer()->SetHP(GetPlayer()->GetTotalHP()); + GetPlayer()->SetPower(GetPlayer()->GetTotalPower()); + /*InfoStruct* info = player->GetInfoStruct(); + info->set_agi_base(new_level * 2 + 15); + info->set_intel_base(new_level * 2 + 15); + info->set_wis_base(new_level * 2 + 15); + info->set_str_base(new_level * 2 + 15); + info->set_sta_base(new_level * 2 + 15); + info->set_cold_base((int16)(new_level * 1.5 + 10)); + info->set_heat_base((int16)(new_level * 1.5 + 10)); + info->set_disease_base((int16)(new_level * 1.5 + 10)); + info->set_mental_base((int16)(new_level * 1.5 + 10)); + info->set_magic_base((int16)(new_level * 1.5 + 10)); + info->set_divine_base((int16)(new_level * 1.5 + 10)); + info->set_poison_base((int16)(new_level * 1.5 + 10)); + GetPlayer()->GetInfoStruct()->set_poison_base((int16)(new_level * 1.5 + 10));*/ + UpdateTimeStampFlag(LEVEL_UPDATE_FLAG); + GetPlayer()->SetCharSheetChanged(true); + + Message(CHANNEL_REWARD, "You are now level %i!", new_level); + LogWrite(WORLD__DEBUG, 0, "World", "Player: %s leveled from %u to %u", GetPlayer()->GetName(), old_level, new_level); + int16 new_skill_cap = 5 * new_level; + PlayerSkillList* player_skills = player->GetSkills(); + + player_skills->SetSkillCapsByType(SKILL_TYPE_ARMOR, new_skill_cap); + player_skills->SetSkillCapsByType(SKILL_TYPE_SHIELD, new_skill_cap); + + if(rule_manager.GetGlobalRule(R_Player, AutoSkillUpBaseSkills)->GetBool()) { + //SKILL_TYPE_ARMOR/SKILL_TYPE_SHIELD always has the same current / max values + player_skills->SetSkillValuesByType(SKILL_TYPE_ARMOR, new_skill_cap, false); + player_skills->SetSkillValuesByType(SKILL_TYPE_SHIELD, new_skill_cap, false); + } + + player_skills->SetSkillCapsByType(SKILL_TYPE_CLASS, new_skill_cap); + player_skills->SetSkillCapsByType(SKILL_TYPE_WEAPON, new_skill_cap); + + if(rule_manager.GetGlobalRule(R_Player, AutoSkillUpBaseSkills)->GetBool()) { + //SKILL_TYPE_CLASS/SKILL_TYPE_WEAPON always has the same current/max values + player_skills->SetSkillValuesByType(SKILL_TYPE_CLASS, new_skill_cap, false); + player_skills->SetSkillValuesByType(SKILL_TYPE_WEAPON, new_skill_cap, false); + } + + player_skills->SetSkillCapsByType(SKILL_TYPE_COMBAT, new_skill_cap); + player_skills->SetSkillCapsByType(SKILL_TYPE_GENERAL, new_skill_cap); + player_skills->SetSkillCapsByType(SKILL_TYPE_SPELLCASTING, new_skill_cap); + player_skills->SetSkillCapsByType(SKILL_TYPE_AVOIDANCE, new_skill_cap); + player_skills->SetSkillCapsByType(SKILL_TYPE_WEAPONRY, new_skill_cap); + + if (new_level > player->GetTSLevel()) + player_skills->SetSkillCapsByType(SKILL_TYPE_HARVESTING, new_skill_cap); + + //SKILL_ID_DUALWIELD, SKILL_ID_FISTS, SKILL_ID_DESTROYING, and SKILL_ID_MAGIC_AFFINITY always have the current_val equal to max_val + if (player_skills->HasSkill(SKILL_ID_DUALWIELD)) + player_skills->SetSkill(SKILL_ID_DUALWIELD, new_skill_cap); + if (player_skills->HasSkill(SKILL_ID_FISTS)) + player_skills->SetSkill(SKILL_ID_FISTS, new_skill_cap); + if (player_skills->HasSkill(SKILL_ID_DESTROYING)) + player_skills->SetSkill(SKILL_ID_DESTROYING, new_skill_cap); + if (player_skills->HasSkill(SKILL_ID_MAGIC_AFFINITY)) + player_skills->SetSkill(SKILL_ID_MAGIC_AFFINITY, new_skill_cap); + + Guild* guild = GetPlayer()->GetGuild(); + if (guild) { + int8 event_type = 0; + if (new_level < 10) + event_type = GUILD_EVENT_GAINS_ADV_LEVEL_1_10; + else if (new_level == 10) + event_type = GUILD_EVENT_GAINS_ADV_LEVEL_10; + else if (new_level >= 11 && new_level < 20) + event_type = GUILD_EVENT_GAINS_ADV_LEVEL_11_20; + else if (new_level == 20) + event_type = GUILD_EVENT_GAINS_ADV_LEVEL_20; + else if (new_level >= 21 && new_level < 30) + event_type = GUILD_EVENT_GAINS_ADV_LEVEL_21_30; + else if (new_level == 30) + event_type = GUILD_EVENT_GAINS_ADV_LEVEL_30; + else if (new_level >= 31 && new_level < 40) + event_type = GUILD_EVENT_GAINS_ADV_LEVEL_31_40; + else if (new_level == 40) + event_type = GUILD_EVENT_GAINS_ADV_LEVEL_40; + else if (new_level >= 41 && new_level < 50) + event_type = GUILD_EVENT_GAINS_ADV_LEVEL_41_50; + else if (new_level == 50) + event_type = GUILD_EVENT_GAINS_ADV_LEVEL_50; + else if (new_level >= 51 && new_level < 60) + event_type = GUILD_EVENT_GAINS_ADV_LEVEL_51_60; + else if (new_level == 60) + event_type = GUILD_EVENT_GAINS_ADV_LEVEL_60; + else if (new_level >= 61 && new_level < 70) + event_type = GUILD_EVENT_GAINS_ADV_LEVEL_61_70; + else if (new_level == 70) + event_type = GUILD_EVENT_GAINS_ADV_LEVEL_70; + else if (new_level >= 71 && new_level < 80) + event_type = GUILD_EVENT_GAINS_ADV_LEVEL_71_80; + else if (new_level == 80) + event_type = GUILD_EVENT_GAINS_ADV_LEVEL_80; + + guild->AddNewGuildEvent(event_type, "%s has gained an adventure level and is now a level %u %s.", Timer::GetUnixTimeStamp(), true, GetPlayer()->GetName(), new_level, classes.GetClassNameCase(GetPlayer()->GetAdventureClass()).c_str()); + guild->SendMessageToGuild(event_type, "%s has gained an adventure level and is now a level %u %s.", GetPlayer()->GetName(), new_level, classes.GetClassNameCase(GetPlayer()->GetAdventureClass()).c_str()); + guild->UpdateGuildMemberInfo(GetPlayer()); + guild->SendGuildMember(GetPlayer()); + guild->SendGuildMemberList(); + } + + // Need to send the trait list every time the players level changes + // Also need to force the char sheet update or else there can be a large delay from when you level + // to when you are actually able to select traits. + QueuePacket(GetPlayer()->GetPlayerInfo()->serialize(GetVersion())); + + GetPlayer()->need_trait_update = true; + if (version > 561) { + QueuePacket(master_trait_list.GetTraitListPacket(this)); + master_aa_list.DisplayAA(this, 0, 0); + } + else + master_trait_list.ChooseNextTrait(this); + + if (GetPlayer()->SpawnedBots.size() > 0) { + map::iterator itr; + for (itr = GetPlayer()->SpawnedBots.begin(); itr != GetPlayer()->SpawnedBots.end(); itr++) { + Spawn* bot = GetCurrentZone()->GetSpawnByID(itr->second); + if (bot && bot->IsBot()) + ((Bot*)bot)->ChangeLevel(old_level, new_level); + } + } + + if (GetPlayer()->GetHP() < GetPlayer()->GetTotalHP() || GetPlayer()->GetPower() < GetPlayer()->GetTotalPower()) + GetPlayer()->GetZone()->AddDamagedSpawn(GetPlayer()); +} + +void Client::ChangeTSLevel(int16 old_level, int16 new_level) { + if (new_level < 1) { + SimpleMessage(CHANNEL_COLOR_RED, "You cannot be lower than level 1!"); + return; + } + if ((player->GetTSLevel() >= 9 && player->GetTradeskillClass() == 1) || (player->GetTSLevel() >= 19 && (player->GetTradeskillClass() == 1 || player->GetTradeskillClass() == 2 || player->GetTradeskillClass() == 6 || player->GetTradeskillClass() == 10))) { + SimpleMessage(CHANNEL_COLOR_YELLOW, "You can not gain levels until you select your next class!"); + return; + } + + if (new_level > old_level) + player->UpdatePlayerHistory(HISTORY_TYPE_XP, HISTORY_SUBTYPE_TRADESKILL, new_level, player->GetTradeskillClass()); + + if (player->GetTSLevel() != new_level) { + player->SetTSLevel(new_level); + if (player->GetGroupMemberInfo()) { + player->UpdateGroupMemberInfo(); + world.GetGroupManager()->SendGroupUpdate(player->GetGroupMemberInfo()->group_id); + } + } + // Only tradeskill skills should increace, and then only those related to your class + PacketStruct* level_update = configReader.getStruct("WS_LevelChanged", GetVersion()); + if (level_update) { + level_update->setDataByName("old_level", old_level); + level_update->setDataByName("new_level", new_level); + level_update->setDataByName("type", 1); + QueuePacket(level_update->serialize()); + safe_delete(level_update); + } + + // provide new spells upon levelling + SendNewTradeskillSpells(); + + PacketStruct* command_packet = configReader.getStruct("WS_CannedEmote", GetVersion()); + if (command_packet) { + command_packet->setDataByName("spawn_id", GetPlayer()->GetIDWithPlayerSpawn(GetPlayer())); + command_packet->setDataByName("anim_type", 1753); + QueuePacket(command_packet->serialize()); + safe_delete(command_packet); + } + GetPlayer()->GetInfoStruct()->set_tradeskill_level(new_level); + GetPlayer()->SetTSLevel(new_level); + + + UpdateTimeStampFlag(LEVEL_UPDATE_FLAG); + GetPlayer()->SetCharSheetChanged(true); + Message(CHANNEL_NARRATIVE, "Your tradeskill level is now %i!", new_level); + LogWrite(WORLD__DEBUG, 0, "World", "Player: %s leveled from %u to %u", GetPlayer()->GetName(), old_level, new_level); + + PlayerSkillList* player_skills = player->GetSkills(); + int16 specialize_skill_cap = new_level * 5; + int16 artisan_skill_cap = std::max(specialize_skill_cap, 49); + int16 specialize_10_skill_cap = std::max(specialize_skill_cap, 99); + + int8 ts_class = player->GetTradeskillClass(); + int8 base_ts_class = classes.GetSecondaryTSBaseClass(ts_class); + int32 skill_id_1, skill_id_2, skill_id_3; + + switch (base_ts_class) { + case ARTISAN: + player_skills->SetSkillCapsByType(SKILL_TYPE_OUTFITTER, artisan_skill_cap); + player_skills->SetSkillCapsByType(SKILL_TYPE_SCHOLAR, artisan_skill_cap); + player_skills->SetSkillCapsByType(SKILL_TYPE_CRAFTSMAN, artisan_skill_cap); + break; + case OUTFITTER: + player_skills->SetSkillCapsByType(SKILL_TYPE_SCHOLAR, artisan_skill_cap); + player_skills->SetSkillCapsByType(SKILL_TYPE_CRAFTSMAN, artisan_skill_cap); + + skill_id_1 = SKILL_ID_TAILORING; + skill_id_2 = SKILL_ID_METALSHAPING; + skill_id_3 = SKILL_ID_METALWORKING; + + if (ts_class == TAILOR) { + player_skills->SetSkillCap(skill_id_1, specialize_skill_cap); + skill_id_1 = 0; + } + else if (ts_class == ARMORER) { + player_skills->SetSkillCap(skill_id_2, specialize_skill_cap); + skill_id_2 = 0; + } + else if (ts_class == WEAPONSMITH) { + player_skills->SetSkillCap(skill_id_3, specialize_skill_cap); + skill_id_3 = 0; + } + + if (skill_id_1) player_skills->SetSkillCap(skill_id_1, specialize_10_skill_cap); + if (skill_id_2) player_skills->SetSkillCap(skill_id_2, specialize_10_skill_cap); + if (skill_id_3) player_skills->SetSkillCap(skill_id_3, specialize_10_skill_cap); + break; + case SCHOLAR: + player_skills->SetSkillCapsByType(SKILL_TYPE_OUTFITTER, artisan_skill_cap); + player_skills->SetSkillCapsByType(SKILL_TYPE_CRAFTSMAN, artisan_skill_cap); + + skill_id_1 = SKILL_ID_SCRIBING; + skill_id_2 = SKILL_ID_CHEMISTRY; + skill_id_3 = SKILL_ID_ARTIFICING; + + if (ts_class == SAGE) { + player_skills->SetSkillCap(skill_id_1, specialize_skill_cap); + skill_id_1 = 0; + } + else if (ts_class == ALCHEMIST) { + player_skills->SetSkillCap(skill_id_2, specialize_skill_cap); + skill_id_2 = 0; + } + else if (ts_class == JEWELER) { + player_skills->SetSkillCap(skill_id_3, specialize_skill_cap); + skill_id_3 = 0; + } + + if (skill_id_1) player_skills->SetSkillCap(skill_id_1, specialize_10_skill_cap); + if (skill_id_2) player_skills->SetSkillCap(skill_id_2, specialize_10_skill_cap); + if (skill_id_3) player_skills->SetSkillCap(skill_id_3, specialize_10_skill_cap); + break; + case CRAFTSMAN: + player_skills->SetSkillCapsByType(SKILL_TYPE_OUTFITTER, artisan_skill_cap); + player_skills->SetSkillCapsByType(SKILL_TYPE_SCHOLAR, artisan_skill_cap); + + skill_id_1 = SKILL_ID_ARTISTRY; + skill_id_2 = SKILL_ID_FLETCHING; + skill_id_3 = SKILL_ID_SCULPTING; + + if (ts_class == PROVISIONER) { + player_skills->SetSkillCap(skill_id_1, specialize_skill_cap); + skill_id_1 = 0; + } + else if (ts_class == WOODWORKER) { + player_skills->SetSkillCap(skill_id_2, specialize_skill_cap); + skill_id_2 = 0; + } + else if (ts_class == CARPENTER) { + player_skills->SetSkillCap(skill_id_3, specialize_skill_cap); + skill_id_3 = 0; + } + + if (skill_id_1) player_skills->SetSkillCap(skill_id_1, specialize_10_skill_cap); + if (skill_id_2) player_skills->SetSkillCap(skill_id_2, specialize_10_skill_cap); + if (skill_id_3) player_skills->SetSkillCap(skill_id_3, specialize_10_skill_cap); + break; + default: + break; + } + + if (new_level > player->GetAdventureClass()) + player_skills->SetSkillCapsByType(SKILL_TYPE_HARVESTING, specialize_skill_cap); + + Guild* guild = GetPlayer()->GetGuild(); + if (guild) { + int8 event_type = 0; + if (new_level < 10) + event_type = GUILD_EVENT_GAINS_TS_LEVEL_1_10; + else if (new_level == 10) + event_type = GUILD_EVENT_GAINS_TS_LEVEL_10; + else if (new_level >= 11 && new_level < 20) + event_type = GUILD_EVENT_GAINS_TS_LEVEL_11_20; + else if (new_level == 20) + event_type = GUILD_EVENT_GAINS_TS_LEVEL_20; + else if (new_level >= 21 && new_level < 30) + event_type = GUILD_EVENT_GAINS_TS_LEVEL_21_30; + else if (new_level == 30) + event_type = GUILD_EVENT_GAINS_TS_LEVEL_30; + else if (new_level >= 31 && new_level < 40) + event_type = GUILD_EVENT_GAINS_TS_LEVEL_31_40; + else if (new_level == 40) + event_type = GUILD_EVENT_GAINS_TS_LEVEL_40; + else if (new_level >= 41 && new_level < 50) + event_type = GUILD_EVENT_GAINS_TS_LEVEL_41_50; + else if (new_level == 50) + event_type = GUILD_EVENT_GAINS_TS_LEVEL_50; + else if (new_level >= 51 && new_level < 60) + event_type = GUILD_EVENT_GAINS_TS_LEVEL_51_60; + else if (new_level == 60) + event_type = GUILD_EVENT_GAINS_TS_LEVEL_60; + else if (new_level >= 61 && new_level < 70) + event_type = GUILD_EVENT_GAINS_TS_LEVEL_61_70; + else if (new_level == 70) + event_type = GUILD_EVENT_GAINS_TS_LEVEL_70; + else if (new_level >= 71 && new_level < 80) + event_type = GUILD_EVENT_GAINS_TS_LEVEL_71_80; + else if (new_level == 80) + event_type = GUILD_EVENT_GAINS_TS_LEVEL_80; + guild->AddNewGuildEvent(event_type, "%s has gained a tradeskill level and is now a level %u %s.", Timer::GetUnixTimeStamp(), true, GetPlayer()->GetName(), new_level, classes.GetClassNameCase(GetPlayer()->GetTradeskillClass() + 42).c_str()); + guild->SendMessageToGuild(event_type, "%s has gained a tradeskill level and is now a level %u %s.", GetPlayer()->GetName(), new_level, classes.GetClassNameCase(GetPlayer()->GetTradeskillClass() + 42).c_str()); + guild->UpdateGuildMemberInfo(GetPlayer()); + guild->SendGuildMember(GetPlayer()); + guild->SendGuildMemberList(); + } + + // Need to send the trait list every time the players level changes + // Also need to force the char sheet update or else there can be a large delay from when you level + // to when you are actually able to select traits. + QueuePacket(GetPlayer()->GetPlayerInfo()->serialize(GetVersion())); + QueuePacket(master_trait_list.GetTraitListPacket(this)); +} + +void Client::CloseLoot(int32 spawn_id) { + if (GetVersion() > 561) { + PacketStruct* packet = configReader.getStruct("WS_CloseWindow", GetVersion()); + if (packet) { + packet->setDataByName("window_id", 4); + EQ2Packet* outapp = packet->serialize(); + if (outapp) { + //DumpPacket(outapp); + QueuePacket(outapp); + } + safe_delete(packet); + } + } + + if(spawn_id > 0){ + PacketStruct* packet = configReader.getStruct("WS_StoppedLooting", GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", spawn_id); + EQ2Packet* outapp = packet->serialize(); + if (outapp) + QueuePacket(outapp); + safe_delete(packet); + } + + Spawn* spawn = GetPlayer()->GetSpawnWithPlayerID(spawn_id); + if(spawn) { + spawn->CloseLoot(GetPlayer()); + } + } +} + +string Client::GetCoinMessage(int32 total_coins) { + if (total_coins == 0) { + return " 0 Copper"; + } + char tmp[64] = { 0 }; + string message = ""; + int32 val = 0; + if (total_coins >= 1000000) { + val = total_coins / 1000000; + total_coins -= 1000000 * val; + sprintf(tmp, " %u Platinum", val); + message.append(tmp); + memset(tmp, 0, 64); + } + if (total_coins >= 10000) { + val = total_coins / 10000; + total_coins -= 10000 * val; + sprintf(tmp, " %u Gold", val); + message.append(tmp); + memset(tmp, 0, 64); + } + if (total_coins >= 100) { + val = total_coins / 100; + total_coins -= 100 * val; + sprintf(tmp, " %u Silver", val); + message.append(tmp); + memset(tmp, 0, 64); + } + if (total_coins > 0) { + sprintf(tmp, " %u Copper", (int32)total_coins); + message.append(tmp); + } + + return message; +} + +void Client::SendLootResponsePacket(int32 total_coins, vector* items, Spawn* entity, bool ignore_loot_tier) { + if (!entity) { + CloseLoot(0); + return; + } + if (total_coins > 0) { + player->AddCoins(total_coins); + //PlaySound("coin_cha_ching"); + string message = ""; + if (entity->GetHP() == 0) { + message = "You loot "; + entity->SetLootCoins(0, false); + } + else + message = "You receive "; + message.append(GetCoinMessage(total_coins)); + if (entity->GetHP() == 0) + message.append(" from the corpse of ").append(entity->GetName()); + int8 type = CHANNEL_LOOT; + + SimpleMessage(type, message.c_str()); + } + + entity->StartLootTimer(GetPlayer()); + + PacketStruct* packet = configReader.getStruct("WS_UpdateLoot", GetVersion()); + if (packet) { + entity->AddSpawnLootWindowCompleted(GetPlayer()->GetID(), false); + vector::iterator itr; + int32 packet_size = 0; + EQ2Packet* outapp = 0; + uchar* data = 0; + vector send_items; + if (items && items->size() > 0) { + for (int i = 0; i < items->size(); i++) { + Item* item = (*items)[i]; + + if (entity->GetLootMethod() > GroupLootMethod::METHOD_FFA && !ignore_loot_tier) { + bool skipItem = entity->IsItemInLootTier(item); + if (!skipItem) { + send_items.push_back(item); + } + } + else { + send_items.push_back(item); + } + } + } + if (GetVersion() >= 374) { + if (GetVersion() > 561) { + if (send_items.size() > 0) { + packet->setDataByName("loot_count", send_items.size()); + packet->setDataByName("display", 1); + } + packet->setDataByName("loot_type", entity->GetLootMethod()); + + packet->setDataByName("lotto_timeout", entity->GetLootTimeRemaining() / 1000); + + packet->setDataByName("loot_id", entity->GetID()); + EQ2Packet* tmpPacket = packet->serialize(); + packet_size += tmpPacket->size; + if (send_items.size() > 0) { + data = new uchar[send_items.size() * 1000 + packet_size]; + memset(data, 0, send_items.size() * 1000 + packet_size); + } + else { + data = new uchar[packet_size]; + memset(data, 0, packet_size); + } + uchar* ptr = data; + memcpy(ptr, tmpPacket->pBuffer, tmpPacket->size); + ptr += tmpPacket->size; + safe_delete(tmpPacket); + Item* item = 0; + if (send_items.size() > 0) { + for (itr = send_items.begin(); itr != send_items.end(); itr++) { + item = *itr; + memcpy(ptr, &item->details.item_id, sizeof(int32)); + ptr += sizeof(int32); + packet_size += sizeof(int32); + + tmpPacket = item->serialize(GetVersion(), true, GetPlayer(), false, 1, 0, false, true); + + int8 offset = 0; + if (GetVersion() >= 1188) { + offset = 13; + } + else if (GetVersion() >= 860) { + offset = 11; + } + else if (GetVersion() <= 561) { + offset = 19; + } + else { + offset = 10; + } + + memcpy(ptr, tmpPacket->pBuffer + offset, tmpPacket->size - offset); + ptr += tmpPacket->size - offset; + packet_size += tmpPacket->size - offset; + + safe_delete(tmpPacket); + } + } + packet_size -= sizeof(int32); + memcpy(data, &packet_size, sizeof(int32)); + packet_size += sizeof(int32); + outapp = new EQ2Packet(OP_ClientCmdMsg, data, packet_size); + } + else { + if (send_items.size() > 0) { + packet->setArrayLengthByName("loot_count", send_items.size()); + Item* item = 0; + if (send_items.size() > 0) { + int i = 0; + for (itr = send_items.begin(); itr != send_items.end(); itr++) { + item = *itr; + packet->setArrayDataByName("loot_id", item->details.item_id, i); + packet->setItemArrayDataByName("item", item, GetPlayer(), i, 0, 2, true); + i++; + } + } + packet->setDataByName("display", 1); + } + packet->setDataByName("loot_type", entity->GetLootMethod()); // normal + packet->setDataByName("lotto_timeout", entity->GetLootTimeRemaining() / 1000); // 60 seconds + packet->setDataByName("spawn_id", entity->GetID()); + outapp = packet->serialize(); + } + } + else { + if (send_items.size() > 0) { + packet->setArrayLengthByName("loot_count", send_items.size()); + for (int i = 0; i < send_items.size(); i++) { + Item* item = (send_items)[i]; + packet->setArrayDataByName("name", item->name.c_str(), i); + packet->setArrayDataByName("item_id", item->details.item_id, i); + packet->setArrayDataByName("count", item->details.count, i); + packet->setArrayDataByName("icon", item->GetIcon(GetVersion()), i); + if (item->generic_info.skill_req1 > 0 && item->generic_info.skill_req1 < 0xFFFFFFFF) + packet->setArrayDataByName("ability_id", item->generic_info.skill_req1, i); + else if (item->generic_info.skill_req2 > 0 && item->generic_info.skill_req2 < 0xFFFFFFFF) + packet->setArrayDataByName("ability_id", item->generic_info.skill_req2, i); + else + packet->setArrayDataByName("ability_id", 0xFFFFFFFF, i); + } + } + packet->setDataByName("display", 1); + packet->setDataByName("loot_type", entity->GetLootMethod()); // normal + packet->setDataByName("lotto_timeout", entity->GetLootTimeRemaining() / 1000); // 60 seconds + packet->setDataByName("object_id", entity->GetID()); + outapp = packet->serialize(); + } + if (outapp) { + QueuePacket(outapp); + } + safe_delete_array(data); + safe_delete(packet); + + if (!items || items->size() == 0) + CloseLoot(entity->GetID()); + + } + +} + +bool Client::LootSpawnByMethod(Spawn* entity) { + bool sentLoot = false; + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + GroupMemberInfo* gmi = GetPlayer()->GetGroupMemberInfo(); + if (gmi && gmi->group_id) + { + PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id); + if (group) + { + int8 auto_split_coin = group->GetGroupOptions()->auto_split; + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + int32 split_coin_per_player = 0; + int32 coins_remain_after_split = entity->GetLootCoins(); + int32 total_coins = entity->GetLootCoins(); + + if (auto_split_coin) { + int8 members_in_zone = 0; + + for (int8 i = 0; i < members->size(); i++) { + Entity* member = members->at(i)->member; + if (!member || !member->IsPlayer()) + continue; + + if (member->GetZone() != GetPlayer()->GetZone()) + continue; + + members_in_zone++; + } + + if (members_in_zone < 1) // this should not happen, but divide by zero checked + members_in_zone = 0; + + split_coin_per_player = entity->GetLootCoins() / members_in_zone; + coins_remain_after_split = entity->GetLootCoins() - (split_coin_per_player * members_in_zone); + entity->SetLootCoins(0, false); + } + + LogWrite(LOOT__INFO, 0, "Loot", "%s: Group LootSpawnByMethod %u, auto coin split %u, split coin per player %u, remaining coin after split %u", entity->GetName(), entity->GetLootMethod(), auto_split_coin, split_coin_per_player, coins_remain_after_split); + bool startWithLooter = true; + + for (int8 i = 0; i < members->size(); i++) { + Entity* member = members->at(i)->member; + if (!member || !member->IsPlayer()) + continue; + + if (member->GetZone() != GetPlayer()->GetZone()) + continue; + + // this will make sure we properly send the loot window to the initial requester if there is no item rarity matches + if (startWithLooter && member != GetPlayer()) + continue; + else if (!startWithLooter && member == GetPlayer()) + continue; + else if (startWithLooter) { + i = 0; + startWithLooter = false; + } + + if (auto_split_coin && (split_coin_per_player + coins_remain_after_split) > 0) { + player->AddCoins(split_coin_per_player + coins_remain_after_split); + if (((Player*)member)->GetClient()) { + ((Player*)member)->GetClient()->Message(CHANNEL_MONEY_SPLIT, "Your share of %s from the corpse of %s is %s.", GetCoinMessage(total_coins).c_str(), entity->GetLootName(), GetCoinMessage(split_coin_per_player + coins_remain_after_split).c_str()); + } + if (coins_remain_after_split > 0) // overflow of coin division went to the first player + coins_remain_after_split = 0; + } + switch (entity->GetLootMethod()) { + case GroupLootMethod::METHOD_LOTTO: + case GroupLootMethod::METHOD_NEED_BEFORE_GREED: { + if (((Player*)member)->GetClient()) { + switch (member->GetInfoStruct()->get_group_auto_loot_method()) { + case 1: { // lotto, need + if (entity->GetLootMethod() == GroupLootMethod::METHOD_LOTTO) { + entity->AddLottoItemRequest(0xFFFFFFFF, GetPlayer()->GetID()); + } + else { // *need* before greed + entity->AddNeedGreedItemRequest(0xFFFFFFFF, GetPlayer()->GetID(), true); + } + entity->AddSpawnLootWindowCompleted(member->GetID(), true); + // if it already exists we have to override the setting + entity->SetSpawnLootWindowCompleted(GetPlayer()->GetID()); + break; + } + case 2: { // decline + entity->AddSpawnLootWindowCompleted(member->GetID(), true); + // if it already exists we have to override the setting + entity->SetSpawnLootWindowCompleted(GetPlayer()->GetID()); + break; + } + default: { // presume 0 or higher than 2 + ((Player*)member)->GetClient()->SendLootResponsePacket((!auto_split_coin && member == GetPlayer()) ? entity->GetLootCoins() : 0, entity->GetLootItems(), entity, true); + break; + } + } + sentLoot = true; + } + break; + } + case GroupLootMethod::METHOD_ROUND_ROBIN: { + entity->AddSpawnLootWindowCompleted(member->GetID(), true); + sentLoot = true; + break; + } + case GroupLootMethod::METHOD_LEADER: { + if (member->GetGroupMemberInfo()->leader) + ((Player*)member)->GetClient()->SendLootResponsePacket((!auto_split_coin && member == GetPlayer()) ? entity->GetLootCoins() : 0, entity->GetLootItems(), entity); + break; + } + case GroupLootMethod::METHOD_FFA: { + if(member == GetPlayer()) { + ((Player*)member)->GetClient()->SendLootResponsePacket((!auto_split_coin && member == GetPlayer()) ? entity->GetLootCoins() : 0, entity->GetLootItems(), entity); + } + break; + } + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + } + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + return sentLoot; +} +void Client::LootSpawnRequest(Spawn* entity, bool attemptDisarm) { + bool lootAllowed = false; + bool sentLoot = false; + std::vector item_list; + if (entity->IsNPC()) { + entity->LockLoot(); + lootAllowed = ((NPC*)entity)->Brain()->CheckLootAllowed(GetPlayer()); + entity->UnlockLoot(); + + if (lootAllowed) { + OpenChest(entity, attemptDisarm); + } + else { + SimpleMessage(CHANNEL_COLOR_YELLOW, "You are not allowed to loot at this time."); + return; + } + + entity->LockLoot(); + if (((NPC*)entity)->Brain()->CheckLootAllowed(GetPlayer())) { + lootAllowed = true; + if ((sentLoot = LootSpawnByMethod(entity))) { + entity->GetLootItemsList(&item_list); + } + else { + SendLootResponsePacket(entity->GetLootCoins(), entity->GetLootItems(), entity); + } + } + entity->UnlockLoot(); + + if (lootAllowed) { + entity->DistributeGroupLoot_RoundRobin(&item_list, true); + } + } +} + +void Client::OpenChest(Spawn* entity, bool attemptDisarm) +{ + if (!entity) + return; + + int8 chest_difficulty = 0; + int32 state = 0; + // Check for the chest and set the action state + /*4034 = small chest | 5864 = treasure chest | 5865 = ornate treasure chest | 4015 = exquisite chest*/ + string modelName; + if (entity->GetModelType() == 4034) { + // small chest, open with copper coins + // does not include traps, however can be disarmed + chest_difficulty = 1; + state = 11899; + modelName.append("Small Chest"); + } + else if (entity->GetModelType() == 5864) { + // treasure chest, open with silver coins + chest_difficulty = 2; + state = 11901; + modelName.append("Treasure Chest"); + } + else if (entity->GetModelType() == 5865) { + // ornate chest, open with gold coins + chest_difficulty = 3; + state = 11900; + modelName.append("Ornate Chest"); + } + else if (entity->GetModelType() == 4015) { + // exquisite chest, open with gold coins and jewels as well as a glow effect + chest_difficulty = 5; + state = 11898; + modelName.append("Exquisite Chest"); + } + bool firstChestOpen = false; + + if (chest_difficulty > 0 && !entity->HasTrapTriggered()) + { + ChestTrap::ChestTrapInfo nextTrap; + + bool ret = chest_trap_list.GetNextTrap(GetCurrentZone()->GetZoneID(), chest_difficulty, &nextTrap); + + Skill* disarmSkill = GetPlayer()->GetSkillByName("Disarm Trap", false); + firstChestOpen = true; + entity->SetTrapTriggered(true, state); + if (ret) + { + if (disarmSkill && attemptDisarm) + { + if (disarmSkill->CheckDisarmSkill(entity->GetLevel(), chest_difficulty) < 1) + { + CastGroupOrSelf(entity && entity->IsEntity() ? (Entity*)entity : 0, nextTrap.spell_id, nextTrap.spell_tier, + rule_manager.GetGlobalRule(R_Loot, ChestTriggerRadiusGroup)->GetFloat()); + Message(CHANNEL_NARRATIVE, "You trigger the trap on %s!", modelName.c_str()); + } + else + { + Message(CHANNEL_NARRATIVE, "You disarm the trap on %s", modelName.c_str()); + } + + // despite fail/succeed we should always try to increase skill if disarm is available + GetPlayer()->GetSkillByName("Disarm Trap", true); + } + else // no disarm skill, always fail + { + CastGroupOrSelf(entity && entity->IsEntity() ? (Entity*)entity : 0, nextTrap.spell_id, nextTrap.spell_tier, + rule_manager.GetGlobalRule(R_Loot, ChestTriggerRadiusGroup)->GetFloat()); + Message(CHANNEL_NARRATIVE, "You trigger the trap on %s!", modelName.c_str()); + } + } + } + else if (!entity->HasTrapTriggered()) + { + firstChestOpen = true; + entity->SetTrapTriggered(true, state); + } + + // We set the visual state with out updating so those not in range will see it opened when it is finally sent to them, + // for those in range the SendStateCommand will cause it to animate open. + + // players not currently in radius will have it queued with client->QueueStateCommand when SendSpawn takes place + if (firstChestOpen) + GetCurrentZone()->SendStateCommand(entity, state); +} + +void Client::CastGroupOrSelf(Entity* source, uint32 spellID, uint32 spellTier, float restrictiveRadius) +{ + Spell* spell = master_spell_list.GetSpell(spellID, spellTier); + SpellProcess* spellProcess = GetCurrentZone()->GetSpellProcess(); + if (source == NULL) + source = (Entity*)GetPlayer(); + if (spell) + { + GroupMemberInfo* gmi = GetPlayer()->GetGroupMemberInfo(); + if (gmi && gmi->group_id) + { + PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + + for (int8 i = 0; i < members->size(); i++) { + Entity* member = members->at(i)->member; + if(!member) + continue; + + if (!member->Alive() || (member->GetZone() != source->GetZone())) + continue; + // if we have a radius provided then check if the group member is outside the radius or not + if (restrictiveRadius > 0.0f && member->GetDistance(source) > restrictiveRadius) + continue; + + spellProcess->CastInstant(spell, source, (Entity*)GetPlayer()); + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + } + else + spellProcess->CastInstant(spell, source, (Entity*)GetPlayer()); + } +} + +Spawn* Client::GetBanker() { + return banker; +} + +void Client::SetBanker(Spawn* in_banker) { + banker = in_banker; + +} + +void Client::Bank(Spawn* banker, bool cancel) { + if (banker && banker->primary_command_list.size() > 0 && banker->primary_command_list[0]->command == "bank") { + if (!cancel) + SetBanker(banker); + else + SetBanker(0); + PacketStruct* packet = configReader.getStruct("WS_UpdateBank", GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", GetPlayer()->GetIDWithPlayerSpawn(banker)); + int64 coins = GetPlayer()->GetInfoStruct()->get_bank_coin_copper() + GetPlayer()->GetInfoStruct()->get_bank_coin_silver() * 100 + + GetPlayer()->GetInfoStruct()->get_bank_coin_gold() * 10000 + (int64)GetPlayer()->GetInfoStruct()->get_bank_coin_plat() * 1000000; + int32 coins1, coins2; + coins1 = ((int32*)&coins)[0]; + coins2 = ((int32*)&coins)[1]; + packet->setDataByName("bank_coins", coins1); + packet->setDataByName("bank_coins2", coins2); + packet->setDataByName("copper", GetPlayer()->GetInfoStruct()->get_coin_copper()); + packet->setDataByName("silver", GetPlayer()->GetInfoStruct()->get_coin_silver()); + packet->setDataByName("gold", GetPlayer()->GetInfoStruct()->get_coin_gold()); + packet->setDataByName("plat", GetPlayer()->GetInfoStruct()->get_coin_plat()); + if (!cancel) + packet->setDataByName("display", 1); + QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + +} + +bool Client::BankHasCoin(int64 amount){ + int32 tmp = 0; + + if(amount <= 0) + return 0; + + //plat + if (amount >= 1000000) { + tmp = amount / 1000000; + int32 bank_coins_plat = GetPlayer()->GetBankCoinsPlat(); + + if(bank_coins_plat >= tmp) + return 1; + } + //gold + if (amount >= 10000) { + tmp = amount / 10000; + int32 bank_coins_gold = GetPlayer()->GetBankCoinsGold(); + + if(bank_coins_gold >= tmp) + return 1; + } + //silver + if (amount >= 100) { + tmp = amount / 100; + int32 bank_coins_silver = GetPlayer()->GetBankCoinsSilver(); + + if(bank_coins_silver >= tmp) + return 1; + } + //copper + if (amount > 0) { + int32 bank_coins_copper = GetPlayer()->GetBankCoinsCopper(); + + if(bank_coins_copper >= amount) + return 1; + } + +return 0; +} + +bool Client::BankWithdrawalNoBanker(int64 amount) { + bool cheater = false; + if (amount > 0) { + string withdrawal = ""; + char withdrawal_data[512] = { 0 }; + int32 tmp = 0; + if (amount >= 1000000) { + tmp = amount / 1000000; + int32 bank_coins_plat = GetPlayer()->GetBankCoinsPlat(); + if (tmp > bank_coins_plat) + cheater = true; + else { + GetPlayer()->GetInfoStruct()->set_bank_coin_plat(bank_coins_plat - tmp); + GetPlayer()->GetInfoStruct()->add_coin_plat(tmp); + amount -= (int64)tmp * 1000000; + sprintf(withdrawal_data, "%u Platinum ", tmp); + withdrawal.append(withdrawal_data); + memset(withdrawal_data, 0, sizeof(withdrawal_data)); + } + } + if (!cheater && amount >= 10000) { + tmp = amount / 10000; + if (tmp > GetPlayer()->GetBankCoinsGold()) + cheater = true; + else { + int32 bank_coins_gold = GetPlayer()->GetInfoStruct()->get_bank_coin_gold(); + bank_coins_gold -= tmp; + if ((GetPlayer()->GetInfoStruct()->get_coin_gold() + tmp) > 100) { + GetPlayer()->GetInfoStruct()->set_coin_gold((GetPlayer()->GetInfoStruct()->get_coin_gold() + tmp) - 100); + GetPlayer()->GetInfoStruct()->add_coin_plat(1); + } + else + GetPlayer()->GetInfoStruct()->add_coin_gold(tmp); + amount -= tmp * 10000; + sprintf(withdrawal_data, "%u Gold ", tmp); + withdrawal.append(withdrawal_data); + memset(withdrawal_data, 0, sizeof(withdrawal_data)); + } + } + if (!cheater && amount >= 100) { + tmp = amount / 100; + int32 bank_coins_silver = GetPlayer()->GetBankCoinsSilver(); + if (tmp > bank_coins_silver) + cheater = true; + else { + GetPlayer()->GetInfoStruct()->set_bank_coin_silver(bank_coins_silver - tmp); + if ((GetPlayer()->GetInfoStruct()->get_coin_silver() + tmp) > 100) { + GetPlayer()->GetInfoStruct()->set_coin_silver((GetPlayer()->GetInfoStruct()->get_coin_silver() + tmp) - 100); + GetPlayer()->GetInfoStruct()->add_coin_gold(1); + } + else + GetPlayer()->GetInfoStruct()->add_coin_silver(tmp); + amount -= tmp * 100; + sprintf(withdrawal_data, "%u Silver ", tmp); + withdrawal.append(withdrawal_data); + memset(withdrawal_data, 0, sizeof(withdrawal_data)); + } + } + if (!cheater) { + if (amount > 0) { + sprintf(withdrawal_data, "%u Copper ", (int32)amount); + withdrawal.append(withdrawal_data); + int32 bank_coin_copper = GetPlayer()->GetInfoStruct()->get_bank_coin_copper(); + + GetPlayer()->GetInfoStruct()->set_bank_coin_copper(bank_coin_copper - amount); + if ((GetPlayer()->GetInfoStruct()->get_coin_copper() + amount) > 100) { + GetPlayer()->GetInfoStruct()->set_coin_copper((GetPlayer()->GetInfoStruct()->get_coin_copper() + amount) - 100); + GetPlayer()->GetInfoStruct()->add_coin_silver(1); + } + else + GetPlayer()->GetInfoStruct()->add_coin_copper(amount); + } + if (withdrawal.length() > 0) { + withdrawal.append("withdrawn "); + sprintf(withdrawal_data, "(%u Plat %u Gold %u Silver %u Copper in the bank now.)", GetPlayer()->GetInfoStruct()->get_bank_coin_plat(), + GetPlayer()->GetInfoStruct()->get_bank_coin_gold(), GetPlayer()->GetInfoStruct()->get_bank_coin_silver(), GetPlayer()->GetInfoStruct()->get_bank_coin_copper()); + withdrawal.append(withdrawal_data); + SimpleMessage(CHANNEL_NARRATIVE, withdrawal.c_str()); + return 1; + } + } + else + Message(CHANNEL_COLOR_RED, "Stop trying to cheat!"); + return 0; + } + return 0; +} + +void Client::BankWithdrawal(int64 amount) { + bool cheater = false; + if (GetBanker() && amount > 0) { + string withdrawal = ""; + char withdrawal_data[512] = { 0 }; + int32 tmp = 0; + if (amount >= 1000000) { + tmp = amount / 1000000; + int32 bank_coins_plat = GetPlayer()->GetBankCoinsPlat(); + if (tmp > bank_coins_plat) + cheater = true; + else { + GetPlayer()->GetInfoStruct()->set_bank_coin_plat(bank_coins_plat - tmp); + GetPlayer()->GetInfoStruct()->add_coin_plat(tmp); + amount -= (int64)tmp * 1000000; + sprintf(withdrawal_data, "%u Platinum ", tmp); + withdrawal.append(withdrawal_data); + memset(withdrawal_data, 0, sizeof(withdrawal_data)); + } + } + if (!cheater && amount >= 10000) { + tmp = amount / 10000; + if (tmp > GetPlayer()->GetBankCoinsGold()) + cheater = true; + else { + int32 bank_coins_gold = GetPlayer()->GetInfoStruct()->get_bank_coin_gold(); + bank_coins_gold -= tmp; + if ((GetPlayer()->GetInfoStruct()->get_coin_gold() + tmp) > 100) { + GetPlayer()->GetInfoStruct()->set_coin_gold((GetPlayer()->GetInfoStruct()->get_coin_gold() + tmp) - 100); + GetPlayer()->GetInfoStruct()->add_coin_plat(1); + } + else + GetPlayer()->GetInfoStruct()->add_coin_gold(tmp); + amount -= tmp * 10000; + sprintf(withdrawal_data, "%u Gold ", tmp); + withdrawal.append(withdrawal_data); + memset(withdrawal_data, 0, sizeof(withdrawal_data)); + } + } + if (!cheater && amount >= 100) { + tmp = amount / 100; + int32 bank_coins_silver = GetPlayer()->GetBankCoinsSilver(); + if (tmp > bank_coins_silver) + cheater = true; + else { + GetPlayer()->GetInfoStruct()->set_bank_coin_silver(bank_coins_silver - tmp); + if ((GetPlayer()->GetInfoStruct()->get_coin_silver() + tmp) > 100) { + GetPlayer()->GetInfoStruct()->set_coin_silver((GetPlayer()->GetInfoStruct()->get_coin_silver() + tmp) - 100); + GetPlayer()->GetInfoStruct()->add_coin_gold(1); + } + else + GetPlayer()->GetInfoStruct()->add_coin_silver(tmp); + amount -= tmp * 100; + sprintf(withdrawal_data, "%u Silver ", tmp); + withdrawal.append(withdrawal_data); + memset(withdrawal_data, 0, sizeof(withdrawal_data)); + } + } + if (!cheater) { + if (amount > 0) { + sprintf(withdrawal_data, "%u Copper ", (int32)amount); + withdrawal.append(withdrawal_data); + int32 bank_coin_copper = GetPlayer()->GetInfoStruct()->get_bank_coin_copper(); + + GetPlayer()->GetInfoStruct()->set_bank_coin_copper(bank_coin_copper - amount); + if ((GetPlayer()->GetInfoStruct()->get_coin_copper() + amount) > 100) { + GetPlayer()->GetInfoStruct()->set_coin_copper((GetPlayer()->GetInfoStruct()->get_coin_copper() + amount) - 100); + GetPlayer()->GetInfoStruct()->add_coin_silver(1); + } + else + GetPlayer()->GetInfoStruct()->add_coin_copper(amount); + } + if (withdrawal.length() > 0) { + withdrawal.append("withdrawn "); + sprintf(withdrawal_data, "(%u Plat %u Gold %u Silver %u Copper in the bank now.)", GetPlayer()->GetInfoStruct()->get_bank_coin_plat(), + GetPlayer()->GetInfoStruct()->get_bank_coin_gold(), GetPlayer()->GetInfoStruct()->get_bank_coin_silver(), GetPlayer()->GetInfoStruct()->get_bank_coin_copper()); + withdrawal.append(withdrawal_data); + SimpleMessage(CHANNEL_NARRATIVE, withdrawal.c_str()); + } + } + else + Message(CHANNEL_COLOR_RED, "Stop trying to cheat!"); + player->SetCharSheetChanged(true); + Bank(banker); + } + +} + +void Client::BankDeposit(int64 amount) { + bool cheater = false; + if (GetBanker() && amount > 0) { + int32 tmp = 0; + char deposit_data[512] = { 0 }; + string deposit = ""; + if (amount >= 1000000) { + tmp = amount / 1000000; + if (tmp > GetPlayer()->GetCoinsPlat()) + cheater = true; + else { + GetPlayer()->GetInfoStruct()->add_bank_coin_plat(tmp); + GetPlayer()->GetInfoStruct()->set_coin_plat(GetPlayer()->GetInfoStruct()->get_coin_plat() - tmp); + amount -= (int64)tmp * 1000000; + sprintf(deposit_data, "%u Platinum ", tmp); + deposit.append(deposit_data); + memset(deposit_data, 0, sizeof(deposit_data)); + } + } + if (!cheater && amount >= 10000) { + tmp = amount / 10000; + if (tmp > GetPlayer()->GetCoinsGold()) + cheater = true; + else { + if ((GetPlayer()->GetInfoStruct()->get_bank_coin_gold() + tmp) > 100) { + GetPlayer()->GetInfoStruct()->set_bank_coin_gold((GetPlayer()->GetInfoStruct()->get_bank_coin_gold() + tmp) - 100); + GetPlayer()->GetInfoStruct()->add_bank_coin_plat(1); + } + else + GetPlayer()->GetInfoStruct()->add_bank_coin_gold(tmp); + GetPlayer()->GetInfoStruct()->set_coin_gold(GetPlayer()->GetInfoStruct()->get_coin_gold() - tmp); + amount -= tmp * 10000; + sprintf(deposit_data, "%u Gold ", tmp); + deposit.append(deposit_data); + memset(deposit_data, 0, sizeof(deposit_data)); + } + } + if (!cheater && amount >= 100) { + tmp = amount / 100; + if (tmp > GetPlayer()->GetCoinsSilver()) + cheater = true; + else { + if ((GetPlayer()->GetInfoStruct()->get_bank_coin_silver() + tmp) > 100) { + GetPlayer()->GetInfoStruct()->set_bank_coin_silver((GetPlayer()->GetInfoStruct()->get_bank_coin_silver() + tmp) - 100); + GetPlayer()->GetInfoStruct()->add_bank_coin_gold(1); + } + else + GetPlayer()->GetInfoStruct()->add_bank_coin_silver(tmp); + GetPlayer()->GetInfoStruct()->set_coin_silver(GetPlayer()->GetInfoStruct()->get_coin_silver() - tmp); + amount -= tmp * 100; + sprintf(deposit_data, "%u Silver ", tmp); + deposit.append(deposit_data); + memset(deposit_data, 0, sizeof(deposit_data)); + } + } + if (!cheater) { + if (amount > 0) { + sprintf(deposit_data, "%u Copper ", (int32)amount); + deposit.append(deposit_data); + if ((GetPlayer()->GetInfoStruct()->get_bank_coin_copper() + amount) > 100) { + GetPlayer()->GetInfoStruct()->set_bank_coin_copper((GetPlayer()->GetInfoStruct()->get_bank_coin_copper() + amount) - 100); + GetPlayer()->GetInfoStruct()->add_bank_coin_silver(1); + } + else + GetPlayer()->GetInfoStruct()->add_bank_coin_copper(amount); + GetPlayer()->GetInfoStruct()->set_coin_copper(GetPlayer()->GetInfoStruct()->get_coin_copper() - amount); + } + if (deposit.length() > 0) { + deposit.append("deposited "); + sprintf(deposit_data, "(%u Plat %u Gold %u Silver %u Copper in the bank now.)", GetPlayer()->GetInfoStruct()->get_bank_coin_plat(), + GetPlayer()->GetInfoStruct()->get_bank_coin_gold(), GetPlayer()->GetInfoStruct()->get_bank_coin_silver(), GetPlayer()->GetInfoStruct()->get_bank_coin_copper()); + deposit.append(deposit_data); + SimpleMessage(CHANNEL_NARRATIVE, deposit.c_str()); + } + } + else + Message(CHANNEL_COLOR_RED, "Stop trying to cheat!"); + player->SetCharSheetChanged(true); + Bank(banker); + } + +} + +void Client::AddPendingQuestAcceptReward(Quest* quest) +{ + std::unique_lock lock(MPendingQuestAccept); + pending_quest_accept.push_back(quest->GetQuestID()); +} + +void Client::AddPendingQuestReward(Quest* quest, bool update, bool is_temporary, std::string description) { + QueueQuestReward(quest->GetQuestID(), is_temporary, false, false, (is_temporary ? quest->GetCoinTmpReward() : 0), + (is_temporary ? quest->GetStatusTmpReward() : 0), description, false, 0); + quest_updates = update; + if(quest_updates) { + SaveQuestRewardData(true); + } + +} + +void Client::QueueQuestReward(int32 quest_id, bool is_temporary, bool is_collection, bool has_displayed, int64 tmp_coin, int32 tmp_status, std::string description, bool db_saved, int32 index) { + if(HasQuestRewardQueued(quest_id, is_temporary, is_collection)) + return; + + QuestRewardData* data = new QuestRewardData; + data->quest_id = quest_id; + data->is_temporary = is_temporary; + data->is_collection = is_collection; + data->has_displayed = has_displayed; + data->tmp_coin = tmp_coin; + data->tmp_status = tmp_status; + data->description = std::string(description); + data->db_saved = db_saved; + data->db_index = index; + MQuestPendingUpdates.writelock(__FUNCTION__, __LINE__); + quest_pending_reward.push_back(data); + MQuestPendingUpdates.releasewritelock(__FUNCTION__, __LINE__); +} + +bool Client::HasQuestRewardQueued(int32 quest_id, bool is_temporary, bool is_collection) { + + bool success = false; + MQuestPendingUpdates.readlock(__FUNCTION__, __LINE__); + if (quest_pending_reward.size() > 0) { + vector::iterator itr; + + for (itr = quest_pending_reward.begin(); itr != quest_pending_reward.end(); itr++) { + int32 questID = (*itr)->quest_id; + bool temporary = (*itr)->is_temporary; + bool collection = (*itr)->is_collection; + if( questID == quest_id && is_temporary == temporary && is_collection == collection ) { + success = true; + break; + } + } + } + MQuestPendingUpdates.releasereadlock(__FUNCTION__, __LINE__); + + return success; +} + +void Client::RemoveQueuedQuestReward() { + MQuestPendingUpdates.writelock(__FUNCTION__, __LINE__); + if(quest_pending_reward.size() > 0) { + QuestRewardData* data = quest_pending_reward.at(0); + if(data->db_saved) { + Query query; + query.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete FROM character_quest_rewards where char_id = %u and indexed = %u", GetCharacterID(), data->db_index); + if(data->is_temporary && data->quest_id) { + query.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete FROM character_quest_temporary_rewards where char_id = %u and quest_id = %u", GetCharacterID(), data->quest_id); + } + } + quest_pending_reward.erase(quest_pending_reward.begin()); + } + MQuestPendingUpdates.releasewritelock(__FUNCTION__, __LINE__); + + SaveQuestRewardData(true); +} + +void Client::AddPendingQuestUpdate(int32 quest_id, int32 step_id, int32 progress) { + MQuestPendingUpdates.writelock(__FUNCTION__, __LINE__); + quest_pending_updates[quest_id][step_id] = progress; + quest_updates = true; + MQuestPendingUpdates.releasewritelock(__FUNCTION__, __LINE__); + +} + +void Client::ProcessQuestUpdates() { + if(!IsReadyForUpdates()) + return; + + if (quest_pending_updates.size() > 0) { + map > tmp_quest_updates; + MQuestPendingUpdates.writelock(__FUNCTION__, __LINE__); + tmp_quest_updates.insert(quest_pending_updates.begin(), quest_pending_updates.end()); + quest_pending_updates.clear(); + MQuestPendingUpdates.releasewritelock(__FUNCTION__, __LINE__); + map >::iterator quest_itr; + map::iterator step_itr; + for (quest_itr = tmp_quest_updates.begin(); quest_itr != tmp_quest_updates.end(); quest_itr++) { + for (step_itr = quest_itr->second.begin(); step_itr != quest_itr->second.end(); step_itr++) { + if (step_itr->second == 0xFFFFFFFF) { + SetStepComplete(quest_itr->first, step_itr->first); + player->SendQuestRequiredSpawns(quest_itr->first); + } + else + AddStepProgress(quest_itr->first, step_itr->first, step_itr->second); + } + } + } + MQuestPendingUpdates.readlock(__FUNCTION__, __LINE__); + if (quest_pending_reward.size() > 0) { + MQuestPendingUpdates.releasereadlock(__FUNCTION__, __LINE__); + + // only able to display one reward at a time + if(GetPlayer()->IsActiveReward()) + return; + + Query query; + vector::iterator itr; + vector tmp_quest_rewards; + MQuestPendingUpdates.writelock(__FUNCTION__, __LINE__); + tmp_quest_rewards.insert(tmp_quest_rewards.begin(), quest_pending_reward.begin(), quest_pending_reward.begin()+1); + MQuestPendingUpdates.releasewritelock(__FUNCTION__, __LINE__); + + bool delete_first = false; + for (itr = tmp_quest_rewards.begin(); itr != tmp_quest_rewards.end();) { + int32 questID = (*itr)->quest_id; + if((*itr)->is_collection && GetPlayer()->GetPendingCollectionReward()) { + DisplayCollectionComplete(GetPlayer()->GetPendingCollectionReward()); + GetPlayer()->SetActiveReward(true); + (*itr)->has_displayed = true; + + UpdateCharacterRewardData((*itr)); + break; + } + else if(questID > 0 && GetPlayer()->UpdateQuestReward(questID, (*itr))) { + (*itr)->has_displayed = true; + UpdateCharacterRewardData((*itr)); + // only able to display one reward at a time + break; + } else { + delete_first = true; + LogWrite(CCLIENT__ERROR, 0, "Client", "Quest ID %u missing for Player %s, deleting quest id from tmp_quest_rewards.", questID, GetPlayer()->GetName()); + break; + } + } + + if(delete_first) { + RemoveQueuedQuestReward(); + } + } else { + MQuestPendingUpdates.releasereadlock(__FUNCTION__, __LINE__); + } + + MQuestPendingUpdates.readlock(__FUNCTION__, __LINE__); + if (quest_pending_reward.size() > 0) { + quest_updates = true; + } + else { + quest_updates = false; + } + MQuestPendingUpdates.releasereadlock(__FUNCTION__, __LINE__); + +} + +void Client::CheckQuestQueue() { + MQuestQueue.writelock(); + last_update_time = 0; + vector::iterator itr; + for (itr = quest_queue.begin(); itr != quest_queue.end(); itr++) { + if(!GetPlayer()->SendQuestStepUpdate((*itr)->quest_id, (*itr)->step, (*itr)->display_quest_helper)) { + LogWrite(CCLIENT__ERROR, 0, "Client", "Queued Quest ID %u missing for Player %s, cannot send quest step update.", (*itr)->quest_id, GetPlayer()->GetName()); + } + safe_delete((*itr)); + } + quest_queue.clear(); + MQuestQueue.releasewritelock(); + +} + +void Client::SetStepComplete(int32 quest_id, int32 step) { + Quest* quest = player->SetStepComplete(quest_id, step); + if (quest) { + SendQuestUpdate(quest); + GetCurrentZone()->SendQuestUpdates(this); + } + +} + +void Client::AddStepProgress(int32 quest_id, int32 step, int32 progress) { + Quest* quest = player->AddStepProgress(quest_id, step, progress); + if (quest) { + SendQuestUpdate(quest); + GetCurrentZone()->SendQuestUpdates(this); + } +} + +void Client::CheckPlayerQuestsKillUpdate(Spawn* spawn) { + bool hadUpdates = false; + vector* quest_updates = player->CheckQuestsKillUpdate(spawn); + if (quest_updates) { + for (int32 i = 0; i < quest_updates->size(); i++) + { + SendQuestUpdate(quest_updates->at(i)); + hadUpdates = true; + } + } + safe_delete(quest_updates); + vector* quest_failures = player->CheckQuestsFailures(); + if (quest_failures) { + for (int32 i = 0; i < quest_failures->size(); i++) + { + SendQuestFailure(quest_failures->at(i)); + hadUpdates = true; + } + } + safe_delete(quest_failures); + + if (hadUpdates) + GetCurrentZone()->SendAllSpawnsForVisChange(this); +} + +void Client::CheckPlayerQuestsChatUpdate(Spawn* spawn) { + vector* quest_updates = player->CheckQuestsChatUpdate(spawn); + if (quest_updates) { + for (int32 i = 0; i < quest_updates->size(); i++) + SendQuestUpdate(quest_updates->at(i)); + GetCurrentZone()->SendQuestUpdates(this); + } + safe_delete(quest_updates); +} + +void Client::CheckPlayerQuestsItemUpdate(Item* item) { + vector* quest_updates = player->CheckQuestsItemUpdate(item); + if (quest_updates) { + for (int32 i = 0; i < quest_updates->size(); i++) + SendQuestUpdate(quest_updates->at(i)); + } + safe_delete(quest_updates); + vector* quest_failures = player->CheckQuestsFailures(); + if (quest_failures) { + for (int32 i = 0; i < quest_failures->size(); i++) + SendQuestFailure(quest_failures->at(i)); + } + safe_delete(quest_failures); +} + +void Client::CheckPlayerQuestsLocationUpdate() { + vector* quest_updates = player->CheckQuestsLocationUpdate(); + if (quest_updates) { + for (int32 i = 0; i < quest_updates->size(); i++) + SendQuestUpdate(quest_updates->at(i)); + } + safe_delete(quest_updates); +} + +void Client::CheckPlayerQuestsSpellUpdate(Spell* spell) { + vector* quest_updates = player->CheckQuestsSpellUpdate(spell); + if (quest_updates) { + for (int32 i = 0; i < quest_updates->size(); i++) + SendQuestUpdate(quest_updates->at(i)); + } + safe_delete(quest_updates); + vector* quest_failures = player->CheckQuestsFailures(); + if (quest_failures) { + for (int32 i = 0; i < quest_failures->size(); i++) + SendQuestFailure(quest_failures->at(i)); + } + safe_delete(quest_failures); +} + +void Client::AddPendingQuest(Quest* quest, bool forced) { + if (version <= 372 || forced) { //this client doesn't ask if you want the quest, so auto accept + MPendingQuestAccept.lock(); + player->pending_quests[quest->GetQuestID()] = quest; + MPendingQuestAccept.unlock(); + AcceptQuest(quest->GetQuestID()); + } + else { + MPendingQuestAccept.lock(); + player->pending_quests[quest->GetQuestID()] = quest; + MPendingQuestAccept.unlock(); + EQ2Packet* outapp = quest->OfferQuest(GetVersion(), player); + //DumpPacket(outapp); + QueuePacket(outapp); + } +} + +void Client::AcceptQuest(int32 quest_id) { + MPendingQuestAccept.lock_shared(); + if (player->pending_quests.count(quest_id) > 0) { + Quest* quest = player->pending_quests[quest_id]; + if(quest) { + MPendingQuestAccept.unlock_shared(); + MPendingQuestAccept.lock(); + player->pending_quests.erase(quest->GetQuestID()); + MPendingQuestAccept.unlock(); + AddPlayerQuest(quest); + GetCurrentZone()->SendQuestUpdates(this); + + GetPlayer()->UpdateQuestCompleteCount(quest_id); + return; // already unlocked mutex + } + } + MPendingQuestAccept.unlock_shared(); +} + +void Client::RemovePendingQuest(int32 quest_id) { + bool send_updates = false; + MPendingQuestAccept.lock_shared(); + + if (player->pending_quests.count(quest_id) > 0) { + Quest* quest = player->pending_quests[quest_id]; + MPendingQuestAccept.unlock_shared(); + MPendingQuestAccept.lock(); + player->pending_quests.erase(quest_id); + MPendingQuestAccept.unlock(); + + if(lua_interface) { + lua_interface->CallQuestFunction(quest, "Declined", GetPlayer()); + lua_interface->SetLuaUserDataStale(quest); + } + + safe_delete(quest); + + send_updates = true; + } + else { + MPendingQuestAccept.unlock_shared(); + } + + if(send_updates) { + GetCurrentZone()->SendQuestUpdates(this); + } +} + +void Client::SetPlayerQuest(Quest* quest, map* progress) { + if (!quest || !progress) { + return; + } + map::iterator itr; + QuestStep* step = 0; + for (itr = progress->begin(); itr != progress->end(); itr++) { + step = quest->GetQuestStep(itr->first); + if (step && itr->second > 0) { + step->SetStepProgress(itr->second); + if (lua_interface && step->GetQuestCurrentQuantity() >= step->GetQuestNeededQuantity()) + lua_interface->CallQuestFunction(quest, "Reload", player, step->GetStepID()); + } + } + if (lua_interface && step) + lua_interface->CallQuestFunction(quest, "CurrentStep", player, step->GetStepID()); + else if(!step) { + LogWrite(QUEST__ERROR, 0, "Client", "Missing step for quest %s (ID %u), cannot CallQuestFunction for CurrentStep", quest->GetName(), quest->GetQuestID()); + } +} + +void Client::AddPlayerQuest(Quest* quest, bool call_accepted, bool send_packets) { + bool lockCleared = false; + GetPlayer()->MPlayerQuests.writelock(__FUNCTION__, __LINE__); + if (player->player_quests.count(quest->GetQuestID()) > 0 && player->player_quests[quest->GetQuestID()]) { + if (player->player_quests[quest->GetQuestID()]->GetQuestFlags() > 0) + quest->SetQuestFlags(player->player_quests[quest->GetQuestID()]->GetQuestFlags()); + int32 questID = quest->GetQuestID(); + lockCleared = true; + GetPlayer()->MPlayerQuests.releasewritelock(__FUNCTION__, __LINE__); + RemovePlayerQuest(questID, false, false); + } + player->player_quests[quest->GetQuestID()] = quest; + + if(!lockCleared) + GetPlayer()->MPlayerQuests.releasewritelock(__FUNCTION__, __LINE__); + + quest->SetPlayer(player); + quest->SetSaveNeeded(true); + + current_quest_id = quest->GetQuestID(); + if (send_packets && quest->GetQuestGiver() > 0) + GetCurrentZone()->SendSpawnChangesByDBID(quest->GetQuestGiver(), this, false, true); + if (lua_interface && call_accepted) + lua_interface->CallQuestFunction(quest, "Accepted", player); + if (send_packets) { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Send Quest Journal..."); + //SendQuestJournal(); + SendQuestJournalUpdate(quest); + + // sent twice to match live + quest->SetTracked(false); + QueuePacket(quest->QuestJournalReply(GetVersion(), GetNameCRC(), player)); + quest->SetTracked(true); + QueuePacket(quest->QuestJournalReply(GetVersion(), GetNameCRC(), player)); + + GetCurrentZone()->SendAllSpawnsForVisChange(this); + } + //This isn't during a load screen, so update spawns with required quests + if (call_accepted) + player->SendQuestRequiredSpawns(quest->GetQuestID()); + +} + +void Client::RemovePlayerQuest(int32 id, bool send_update, bool delete_quest) { + if (current_quest_id == id) + current_quest_id = 0; + GetPlayer()->MPlayerQuests.writelock(__FUNCTION__, __LINE__); + if (player->player_quests.count(id) > 0 && player->player_quests[id]) { + if (delete_quest) { + player->player_quests[id]->SetDeleted(true); + database.DeleteCharacterQuest(id, GetCharacterID(), player->GetCompletedPlayerQuests()->count(id) > 0); + } + int32 quest_giver = player->player_quests[id]->GetQuestGiver(); + GetPlayer()->MPlayerQuests.releasewritelock(__FUNCTION__, __LINE__); + + if (send_update && quest_giver > 0) + GetCurrentZone()->SendSpawnChangesByDBID(quest_giver, this, false, true); + if (send_update) { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Send Quest Journal..."); + SendQuestJournal(false, 0, true); + } + player->RemoveQuest(id, delete_quest); + if (send_update) { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Send Quest Journal..."); + SendQuestJournal(false, 0, true); + GetCurrentZone()->SendAllSpawnsForVisChange(this); + } + } + else { + // if we don't have any quests to count then release the write lock + GetPlayer()->MPlayerQuests.releasewritelock(__FUNCTION__, __LINE__); + } + +} + +void Client::SendQuestUpdateStepImmediately(Quest* quest, int32 step, bool display_quest_helper) { + if (quest) { + QuestStep* quest_step = quest->GetQuestStep(step); + if (quest_step) { + QueuePacket(quest->QuestJournalReply(GetVersion(), GetNameCRC(), player, quest_step, 1, false, false, display_quest_helper)); + quest_step->WasUpdated(false); + } + } + +} + +void Client::SendQuestUpdateStep(Quest* quest, int32 step, bool display_quest_helper) { + QueuedQuest* item = new QueuedQuest; + item->quest_id = quest->GetQuestID(); + item->step = step; + item->display_quest_helper = display_quest_helper; + MQuestQueue.writelock(); + quest_queue.push_back(item); + last_update_time = Timer::GetCurrentTime2(); + MQuestQueue.releasewritelock(); + +} + +void Client::SendQuestFailure(Quest* quest) { + vector* failures = quest->GetQuestFailures(); + if (failures) { + QuestStep* step = 0; + for (int32 i = 0; i < failures->size(); i++) { + step = failures->at(i); + QueuePacket(quest->QuestJournalReply(GetVersion(), GetNameCRC(), player, step, 1, false, true)); + LogWrite(CCLIENT__DEBUG, 0, "Client", "Send Quest Journal..."); + SendQuestJournal(false, 0, true); + } + failures->clear(); + } + +} + +void Client::SendQuestUpdate(Quest* quest) { + vector* updates = quest->GetQuestUpdates(); + if (updates) { + QuestStep* step = 0; + bool updated = false; + for (int32 i = 0; i < updates->size(); i++) { + step = updates->at(i); + if (lua_interface && step->Complete() && quest->GetCompleteAction(step->GetStepID())) + { + lua_interface->CallQuestFunction(quest, quest->GetCompleteAction(step->GetStepID()), player); + SendQuestUpdateStep(quest, step->GetStepID()); + updated = true; + } + if (step->WasUpdated()) { + // reversing the order of SendQuestJournal and QueuePacket QuestJournalReply causes AoM client to crash! + SendQuestJournal(false, 0, true); + if(!updated) + QueuePacket(quest->QuestJournalReply(GetVersion(), GetNameCRC(), player, step)); + updated = true; + } + LogWrite(CCLIENT__DEBUG, 0, "Client", "Send Quest Journal..."); + + } + if (lua_interface && quest->GetCompleted() && quest->GetCompleteAction()) { + lua_interface->CallQuestFunction(quest, quest->GetCompleteAction(), player); + SendQuestJournalUpdate(quest, true); + } + if (quest->GetCompleted()) { + if (quest->GetQuestReturnNPC() > 0) + GetCurrentZone()->SendSpawnChangesByDBID(quest->GetQuestReturnNPC(), this, false, true); + if (quest->GetCompletedFlag()) + quest->SetCompletedFlag(false); + } + + updates->clear(); + } + +} + +void Client::SendQuestJournal(bool all_quests, Client* client, bool updated) { + if (!client) + client = this; + PacketStruct* packet = player->GetQuestJournalPacket(all_quests, GetVersion(), GetNameCRC(), current_quest_id, updated); + if (packet) { + EQ2Packet* outapp = packet->serialize(); + //DumpPacket(outapp); + client->QueuePacket(outapp); + safe_delete(packet); + } +} + +void Client::SendQuestJournalUpdate(Quest* quest, bool updated) { + PacketStruct* packet = player->GetQuestJournalPacket(quest, GetVersion(), GetNameCRC(), updated); + if (packet) { + QueuePacket(packet->serialize()); + safe_delete(packet); + } +} + +void Client::ReloadQuests() { + vector ids = player->GetQuestIDs(); + Quest* quest = 0; + for (int32 i = 0; i < ids.size(); i++) { + quest = master_quest_list.GetQuest(ids[i]); + if (quest) + AddPlayerQuest(quest, false); + else + RemovePlayerQuest(ids[i]); + } + +} + +Quest* Client::GetPendingQuestAcceptance(int32 item_id) { + std::unique_lock lock(MPendingQuestAccept); + bool found_quest = false; + vector::iterator itr; + int32 questID = 0; + Quest* quest = nullptr; + for (itr = pending_quest_accept.begin(); itr != pending_quest_accept.end();) { + questID = *itr; + + bool quest_exists = false; + quest = GetPlayer()->PendingQuestAcceptance(questID, item_id, &quest_exists); + + if(!quest_exists) { + LogWrite(CCLIENT__ERROR, 0, "Client", "Quest ID %u missing for Player %s, removing quest id from pending_quest_accept.", questID, GetPlayer()->GetName()); + itr = pending_quest_accept.erase(itr); + quest = nullptr; + continue; + } + else if (quest) { + pending_quest_accept.erase(itr); + break; + } + + itr++; + } + + return quest; +} + +void Client::AcceptQuestReward(Quest* quest, int32 item_id) { + int8 num_slots_needed = 0; + int16 free_slots = player->item_list.GetNumberOfFreeSlots(); + Item* master_item = 0; + if (item_id > 0) { + num_slots_needed++; + master_item = master_item_list.GetItem(item_id); + } + + int32 totalItems = 0; + + vector* items = 0; + vector* tmpItems = 0; + + bool isTempState = quest->GetQuestTemporaryState(); + + if(isTempState) + { + tmpItems = quest->GetTmpRewardItems(); + if (tmpItems && tmpItems->size() > 0) + { + num_slots_needed += tmpItems->size(); + totalItems += tmpItems->size(); + } + } + else + { + items = quest->GetRewardItems(); + if (items && items->size() > 0) + { + num_slots_needed += items->size(); + totalItems += items->size(); + } + } + + RemoveQueuedQuestReward(); + + GetPlayer()->SetActiveReward(false); + + if (free_slots >= num_slots_needed || (player->item_list.HasFreeBagSlot() && master_item && master_item->IsBag() && master_item->bag_info->num_slots >= totalItems)) { + if (master_item) + AddItem(item_id); + if (tmpItems && tmpItems->size() > 0) { + for (int32 i = 0; i < tmpItems->size(); i++) + AddItem(new Item(tmpItems->at(i))); + } + if (items && items->size() > 0) { + for (int32 i = 0; i < items->size(); i++) + AddItem(new Item(items->at(i))); + } + EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion()); + if (outapp) + QueuePacket(outapp); + map* reward_factions = quest->GetRewardFactions(); + map::iterator itr; + for (itr = reward_factions->begin(); itr != reward_factions->end(); itr++) { + int32 faction_id = itr->first; + sint32 amount = itr->second; + if (amount > 0) + player->GetFactions()->IncreaseFaction(faction_id, amount); + else + player->GetFactions()->DecreaseFaction(faction_id, (amount * -1)); + } + + if(quest->GetQuestTemporaryState()) + { + int64 total_coins = quest->GetCoinTmpReward(); + if (total_coins > 0) + AwardCoins(total_coins, std::string("for completing ").append(quest->GetName())); + + player->GetInfoStruct()->add_status_points(quest->GetStatusTmpReward()); + } + else { + player->GetInfoStruct()->add_status_points(quest->GetStatusPoints()); + } + + quest->SetQuestTemporaryState(false); + player->SetCharSheetChanged(true); + } + else { + GetPlayer()->SetActiveReward(true); + AddPendingQuestAcceptReward(quest); + SimpleMessage(CHANNEL_COLOR_RED, "You do not have enough free slots! Free some slots and try again."); + DisplayQuestComplete(quest, quest->GetQuestTemporaryState(), quest->GetQuestTemporaryDescription()); + } + +} + +void Client::DisplayQuestRewards(Quest* quest, int64 coin, vector* rewards, vector* selectable_rewards, map* factions, const char* header, int32 status_points, const char* text, bool was_displayed) { + if (coin == 0 && (!rewards || rewards->size() == 0) && (!selectable_rewards || selectable_rewards->size() == 0) && (!factions || factions->size() == 0) && status_points == 0 && text == 0 && (!quest || (quest->GetCoinsReward() == 0 && quest->GetCoinsRewardMax() == 0))) { + /*if (quest) + text = quest->GetName(); + else*/ + return;//nothing to give + } + + GetPlayer()->ClearPendingSelectableItemRewards(0, true); + GetPlayer()->ClearPendingItemRewards(); + + PacketStruct* packet2 = configReader.getStruct("WS_QuestRewardPackMsg", GetVersion()); + if (packet2) { + int32 source_id = 0; + if (quest) + source_id = quest->GetQuestID(); + int64 rewarded_coin = 0; + if (quest) { + if (quest->GetCoinsReward() > 0) { + if (quest->GetCoinsRewardMax() > 0) + rewarded_coin = MakeRandomInt(quest->GetCoinsReward(), quest->GetCoinsRewardMax()); + else + rewarded_coin = quest->GetCoinsReward(); + } + quest->SetGeneratedCoin(rewarded_coin); + } + if (rewarded_coin > coin) + coin = rewarded_coin; + if (!quest && !was_displayed) { //this entire function is either for version <=561 or for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards + if (coin > 0) { + player->AddCoins(coin); + PlaySound("coin_cha_ching"); + } + } + packet2->setSubstructDataByName("reward_data", "unknown1", 255); + packet2->setSubstructDataByName("reward_data", "reward", header); + packet2->setSubstructDataByName("reward_data", "max_coin", coin); + if (player->GetGuild() && !was_displayed) { + if (!quest) { //this entire function is either for version <=561 or for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards + player->GetInfoStruct()->add_status_points(status_points); + player->SetCharSheetChanged(true); + } + packet2->setSubstructDataByName("reward_data", "status_points", status_points); + } + if(text) + packet2->setSubstructDataByName("reward_data", "text", text); + + + std::vector items; + quest->GetTmpRewardItemsByID(&items); + if(rewards || items.size() > 0){ + int32 item_count = items.size(); + item_count += rewards ? rewards->size() : 0; + packet2->setSubstructArrayLengthByName("reward_data", "num_rewards", item_count); + int i = 0; + if(rewards) { + for (i = 0; i < rewards->size(); i++) { + Item* item = rewards->at(i); + if (item) { + packet2->setArrayDataByName("reward_id", item->details.item_id, i); + packet2->setItemArrayDataByName("item", item, player, i, 0, GetClientItemPacketOffset()); + } + if(!quest) //this entire function is either for version <=561 or for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards + player->AddPendingItemReward(item); //item reference will be deleted after the player accepts it + } + } + + for (int j = 0; j < items.size(); j++) { + Item* item = items.at(j); + if (item) { + packet2->setArrayDataByName("reward_id", item->details.item_id, i); + packet2->setItemArrayDataByName("item", item, player, i, 0, GetClientItemPacketOffset()); + } + if(!quest) //this entire function is either for version <=561 or for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards + player->AddPendingItemReward(item); //item reference will be deleted after the player accepts it + + i++; + } + } + if (selectable_rewards) { + packet2->setSubstructArrayLengthByName("reward_data", "num_select_rewards", selectable_rewards->size()); + for (int i = 0; i < selectable_rewards->size(); i++) { + Item* item = selectable_rewards->at(i); + if (item) { + packet2->setArrayDataByName("select_reward_id", item->details.item_id, i); + packet2->setItemArrayDataByName("select_item", item, player, i, 0, GetClientItemPacketOffset()); + if (!quest) //this entire function is either for version <=561 or for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards + player->AddPendingSelectableItemReward(source_id, item); //item reference will be deleted after the player selects one + } + } + } + if (factions) { + map::iterator itr; + map factions_map; + for (itr = factions->begin(); itr != factions->end(); itr++) { + Faction* faction = master_faction_list.GetFaction(itr->first); + if (faction) + factions_map[faction] = itr->second; + } + packet2->setSubstructArrayLengthByName("reward_data", "num_factions", factions_map.size()); + map::iterator faction_itr; + int8 i = 0; + for (faction_itr = factions_map.begin(); faction_itr != factions_map.end(); faction_itr++) { + packet2->setArrayDataByName("faction_name", faction_itr->first->name.c_str(), i); + sint32 amount = faction_itr->second; + packet2->setArrayDataByName("amount", amount, i); + if (!quest) { //this entire function is for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards + if (amount > 0) + player->GetFactions()->IncreaseFaction(faction_itr->first->id, amount); + else + player->GetFactions()->DecreaseFaction(faction_itr->first->id, (amount * -1)); + } + i++; + } + } + QueuePacket(packet2->serialize()); + safe_delete(packet2); + } +} + +void Client::PopulateQuestRewardItems(vector * items, PacketStruct* packet, + std::string num_rewards_str, std::string reward_id_str, std::string item_str) { + if(!items || !packet) + return; + + if (items) { + int32 total_item_count = 0; + for(int s=0;ssize();s++) { + Item* tmpItem = items->at(s); + if(tmpItem) { + if(tmpItem->details.count > 1) { + total_item_count += tmpItem->details.count; + } + else { + total_item_count += 1; + } + } + } + packet->setArrayLengthByName(num_rewards_str.c_str(), total_item_count); + int16 count = 0; + int16 pos = 0; + for (int32 i = 0; i < items->size();) { + packet->setArrayDataByName(reward_id_str.c_str(), items->at(i)->details.item_id, pos); + if (version < 860) + packet->setItemArrayDataByName(item_str.c_str(), items->at(i), player, pos, 0, GetClientItemPacketOffset()); + else if (version < 1193) + packet->setItemArrayDataByName(item_str.c_str(), items->at(i), player, pos); + else + packet->setItemArrayDataByName(item_str.c_str(), items->at(i), player, pos, 0, 2); + + pos++; + + if(count >= items->at(i)->details.count-1) { + count = 0; + } + else if(items->at(i)->details.count > 1) { + count++; + continue; + } + + i++; + } + } +} +void Client::DisplayQuestComplete(Quest* quest, bool tempReward, std::string customDescription, bool was_displayed) { + if (!quest) + return; + + if (GetVersion() <= 561) { + DisplayQuestRewards(quest, 0, quest->GetRewardItems(), quest->GetSelectableRewardItems(), quest->GetRewardFactions(), "Quest Complete!", quest->GetStatusPoints(), tempReward ? customDescription.c_str() : quest->GetCompletedDescription(), was_displayed); + return; + } + PacketStruct* packet = configReader.getStruct("WS_QuestComplete", GetVersion()); + if (packet) { + packet->setDataByName("title", "Quest Reward!"); + packet->setDataByName("name", quest->GetName()); + if(tempReward) + { + packet->setDataByName("description", customDescription.c_str()); + } + else + packet->setDataByName("description", quest->GetCompletedDescription()); + + packet->setDataByName("level", quest->GetLevel()); + packet->setDataByName("encounter_level", quest->GetEncounterLevel()); + int8 difficulty = 0; + if ((string)quest->GetType() == "Tradeskill") + difficulty = player->GetTSArrowColor(quest->GetLevel()); + else + difficulty = player->GetArrowColor(quest->GetLevel()); + packet->setDataByName("difficulty", difficulty); + + if(tempReward) + { + packet->setDataByName("max_coin", quest->GetCoinTmpReward()); + packet->setDataByName("min_coin", quest->GetCoinTmpReward()); + packet->setDataByName("status_points", quest->GetStatusPoints()); + } + else + { + int64 rewarded_coin = 0; + if (quest->GetCoinsReward() > 0) { + if (quest->GetCoinsRewardMax() > 0) + rewarded_coin = MakeRandomInt(quest->GetCoinsReward(), quest->GetCoinsRewardMax()); + else + rewarded_coin = quest->GetCoinsReward(); + } + quest->SetGeneratedCoin(rewarded_coin); + packet->setDataByName("max_coin", rewarded_coin); + packet->setDataByName("min_coin", rewarded_coin); + packet->setDataByName("status_points", quest->GetStatusPoints()); + } + + if(tempReward) { + PopulateQuestRewardItems(quest->GetTmpRewardItems(), packet); + } + else + { + vector* items2 = quest->GetSelectableRewardItems(); + PopulateQuestRewardItems(quest->GetRewardItems(), packet); + PopulateQuestRewardItems(quest->GetSelectableRewardItems(), packet, std::string("num_select_rewards"), + std::string("select_reward_id"), std::string("select_item")); + + map* reward_factions = quest->GetRewardFactions(); + if (reward_factions && reward_factions->size() > 0) { + packet->setArrayLengthByName("num_factions", reward_factions->size()); + map::iterator itr; + int16 index = 0; + for (itr = reward_factions->begin(); itr != reward_factions->end(); itr++) { + int32 faction_id = itr->first; + sint32 amount = itr->second; + const char* faction_name = master_faction_list.GetFactionNameByID(faction_id); + if (faction_name) { + packet->setArrayDataByName("faction_name", const_cast(faction_name), index); + packet->setArrayDataByName("amount", amount, index); + } + index++; + } + } + } + EQ2Packet* outapp = packet->serialize(); + // DumpPacket(outapp); + QueuePacket(outapp); + safe_delete(packet); + } + +} + +void Client::DisplayRandomizeFeatures(int32 flags) { + SimpleMessage(CHANNEL_NARRATIVE, "Showing Active Randomize Features:"); + if (flags > 0) { + if (flags & RANDOMIZE_GENDER) + SimpleMessage(CHANNEL_NARRATIVE, "- Gender"); + if (flags & RANDOMIZE_RACE) + SimpleMessage(CHANNEL_NARRATIVE, "- Race"); + if (flags & RANDOMIZE_MODEL_TYPE) + SimpleMessage(CHANNEL_NARRATIVE, "- Model"); + if (flags & RANDOMIZE_FACIAL_HAIR_TYPE) + SimpleMessage(CHANNEL_NARRATIVE, "- Facial Hair"); + if (flags & RANDOMIZE_HAIR_TYPE) + SimpleMessage(CHANNEL_NARRATIVE, "- Hair"); + if (flags & RANDOMIZE_WING_TYPE) + SimpleMessage(CHANNEL_NARRATIVE, "- Wing"); + if (flags & RANDOMIZE_CHEEK_TYPE) + SimpleMessage(CHANNEL_NARRATIVE, "- Cheek"); + if (flags & RANDOMIZE_CHIN_TYPE) + SimpleMessage(CHANNEL_NARRATIVE, "- Chin"); + if (flags & RANDOMIZE_EAR_TYPE) + SimpleMessage(CHANNEL_NARRATIVE, "- Ear"); + if (flags & RANDOMIZE_EYE_BROW_TYPE) + SimpleMessage(CHANNEL_NARRATIVE, "- Eyebrow"); + if (flags & RANDOMIZE_EYE_TYPE) + SimpleMessage(CHANNEL_NARRATIVE, "- Eye"); + if (flags & RANDOMIZE_LIP_TYPE) + SimpleMessage(CHANNEL_NARRATIVE, "- Lip"); + if (flags & RANDOMIZE_NOSE_TYPE) + SimpleMessage(CHANNEL_NARRATIVE, "- Nose"); + if (flags & RANDOMIZE_EYE_COLOR) + SimpleMessage(CHANNEL_NARRATIVE, "- Eye Color"); + if (flags & RANDOMIZE_HAIR_COLOR1) + SimpleMessage(CHANNEL_NARRATIVE, "- Hair Color1"); + if (flags & RANDOMIZE_HAIR_COLOR2) + SimpleMessage(CHANNEL_NARRATIVE, "- Hair Color2"); + if (flags & RANDOMIZE_HAIR_HIGHLIGHT) + SimpleMessage(CHANNEL_NARRATIVE, "- Hair Color Highlights"); + if (flags & RANDOMIZE_HAIR_FACE_COLOR) + SimpleMessage(CHANNEL_NARRATIVE, "- Facial Hair Color"); + if (flags & RANDOMIZE_HAIR_FACE_HIGHLIGHT_COLOR) + SimpleMessage(CHANNEL_NARRATIVE, "- Facial Hair Color Highlights"); + if (flags & RANDOMIZE_HAIR_TYPE_COLOR) + SimpleMessage(CHANNEL_NARRATIVE, "- Hair Type Color"); + if (flags & RANDOMIZE_HAIR_TYPE_HIGHLIGHT_COLOR) + SimpleMessage(CHANNEL_NARRATIVE, "- Hair Type Highlights"); + if (flags & RANDOMIZE_SKIN_COLOR) + SimpleMessage(CHANNEL_NARRATIVE, "- Skin Color"); + if (flags & RANDOMIZE_WING_COLOR1) + SimpleMessage(CHANNEL_NARRATIVE, "- Wing Color1"); + if (flags & RANDOMIZE_WING_COLOR2) + SimpleMessage(CHANNEL_NARRATIVE, "- Wing Color2"); + } + else + { + SimpleMessage(CHANNEL_NARRATIVE, "- No Randomization Set."); + } + +} + +void Client::GiveQuestReward(Quest* quest, bool has_displayed) { + current_quest_id = 0; + + if(!quest->GetQuestTemporaryState() && !has_displayed) + { + quest->IncrementCompleteCount(); + player->AddCompletedQuest(quest); + } + + AddPendingQuestAcceptReward(quest); + + DisplayQuestComplete(quest, quest->GetQuestTemporaryState(), quest->GetQuestTemporaryDescription()); + LogWrite(CCLIENT__DEBUG, 0, "Client", "Send Quest Journal..."); + SendQuestJournal(); + + if(quest->GetQuestTemporaryState()) { + return; + } + + if(!has_displayed) { + if (quest->GetExpReward() > 0) { + int32 xp = quest->GetExpReward(); + player->AddXP(xp); + } + if (quest->GetTSExpReward() > 0) { + int8 ts_level = player->GetTSLevel(); + int32 xp = quest->GetTSExpReward(); + if (player->AddTSXP(xp)) { + Message(CHANNEL_REWARD, "You gain %u tradeskill experience!", (int32)xp); + if (player->GetTSLevel() != ts_level) + ChangeTSLevel(ts_level, player->GetTSLevel()); + player->SetCharSheetChanged(true); + } + } + int64 total_coins = quest->GetGeneratedCoin(); + if (total_coins > 0) + AwardCoins(total_coins, std::string("for completing ").append(quest->GetName())); + + player->RemoveQuest(quest->GetQuestID(), false); + } + + if (quest->GetQuestGiver() > 0) + GetCurrentZone()->SendSpawnChangesByDBID(quest->GetQuestGiver(), this, false, true); + + if(!has_displayed) { + RemovePlayerQuest(quest->GetQuestID(), true, false); + } +} + +void Client::DisplayConversation(int32 conversation_id, int32 spawn_id, vector* conversations, const char* text, const char* mp3, int32 key1, int32 key2, int8 language, int8 can_close) { + std::unique_lock lock(MConversation); + PacketStruct* packet = configReader.getStruct("WS_DialogOpen", GetVersion()); + if (packet) { + packet->setDataByName("conversation_id", conversation_id); + packet->setDataByName("text", text); + packet->setDataByName("language", language); // default 0 + packet->setDataByName("enable_blue_ui", 0); // default 0 + packet->setDataByName("can_close", can_close); // default 1 + conversation_map[conversation_id].clear(); + if (conversations) { + packet->setArrayLengthByName("num_responses", conversations->size()); + for (int32 i = 0; i < conversations->size(); i++) { + packet->setArrayDataByName("response", conversations->at(i).option.c_str(), i); + if (conversations->at(i).function.length() > 0) + conversation_map[conversation_id][i] = conversations->at(i).function; + } + } + packet->setDataByName("spawn_id", spawn_id); + if (mp3) { + packet->setDataByName("voice", mp3); + packet->setDataByName("key1", key1); + packet->setDataByName("key2", key2); + } + QueuePacket(packet->serialize()); + safe_delete(packet); + } + +} + +void Client::DisplayConversation(Item* item, vector* conversations, const char* text, int8 type, const char* mp3, int32 key1, int32 key2, int8 language, int8 can_close) { + if (!item || !text || !conversations || conversations->size() == 0) { + return; + } + int32 conversation_id = GetConversationID(0, item); + if (conversation_id == 0) { + next_conversation_id++; + conversation_id = next_conversation_id; + } + MConversation.lock(); + conversation_items[conversation_id] = item; + MConversation.unlock(); + if (type == 4) + DisplayConversation(conversation_id, player->GetIDWithPlayerSpawn(player), conversations, text, mp3, key1, key2, language, can_close); + else + DisplayConversation(conversation_id, 0xFFFFFFFF, conversations, text, mp3, key1, key2, language, can_close); + +} + +void Client::DisplayConversation(Spawn* src, int8 type, vector* conversations, const char* text, const char* mp3, int32 key1, int32 key2, int8 language, int8 can_close) { + if (!src || !(type == 1 || type == 2 || type == 3) || !text /*|| !conversations || conversations->size() == 0*/) { + return; + } + int32 conversation_id = GetConversationID(src, 0); + if (conversation_id == 0) { + next_conversation_id++; + conversation_id = next_conversation_id; + } + MConversation.lock(); + conversation_spawns[conversation_id] = src->GetID(); + MConversation.unlock(); + + /* Spawns can start two different types of conversations. + * Type 1: The chat type with bubbles. + * Type 2: The dialog type with the blue box. */ + if (type == 1) + DisplayConversation(conversation_id, player->GetIDWithPlayerSpawn(src), conversations, text, mp3, key1, key2, language, can_close); + else if (type == 2) + DisplayConversation(conversation_id, 0xFFFFFFFF, conversations, text, mp3, key1, key2, language, can_close); + else //if (type == 3) + DisplayConversation(conversation_id, player->GetIDWithPlayerSpawn(player), conversations, text, mp3, key1, key2, language, can_close); + +} + +void Client::CloseDialog(int32 conversation_id) { + std::unique_lock lock(MConversation); + PacketStruct* packet = configReader.getStruct("WS_ServerDialogClose", GetVersion()); + if (packet) { + packet->setDataByName("conversation_id", conversation_id); + QueuePacket(packet->serialize()); + safe_delete(packet); + } + + std::map::iterator itr; + while((itr = conversation_items.find(conversation_id)) != conversation_items.end()) + { + conversation_items.erase(itr); + } + + std::map::iterator itr2 = conversation_spawns.find(conversation_id); + + while((itr2 = conversation_spawns.find(conversation_id)) != conversation_spawns.end()) + { + conversation_spawns.erase(itr2); + } +} + +int32 Client::GetConversationID(Spawn* spawn, Item* item) { + std::shared_lock lock(MConversation); + int32 conversation_id = 0; + if (spawn) { + map::iterator itr; + for (itr = conversation_spawns.begin(); itr != conversation_spawns.end(); itr++) { + if (itr->second == spawn->GetID()) { + conversation_id = itr->first; + break; + } + } + } + else if (item) { + map::iterator itr; + for (itr = conversation_items.begin(); itr != conversation_items.end(); itr++) { + if (itr->second == item) { + conversation_id = itr->first; + break; + } + } + } + + return conversation_id; +} + +Spawn* Client::GetCombineSpawn() { + return combine_spawn; +} + +bool Client::ShouldTarget() { + return should_target; +} + +void Client::TargetSpawn(Spawn* spawn) { + should_target = false; + PacketStruct* packet = configReader.getStruct("WS_ServerUpdateTarget", GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", GetPlayer()->GetIDWithPlayerSpawn(spawn)); + QueuePacket(packet->serialize()); + safe_delete(packet); + } + GetPlayer()->SetTarget(spawn); + GetPlayer()->info_changed = true; + GetPlayer()->changed = true; + GetPlayer()->AddChangedZoneSpawn(); +} + +void Client::CombineSpawns(float radius, Spawn* spawn) { + combine_spawn = spawn; + spawn->RemoveSpawnFromGroup(true); + if (!GetCurrentZone()->AddCloseSpawnsToSpawnGroup(combine_spawn, radius)) + SimpleMessage(CHANNEL_COLOR_YELLOW, "One or more spawns are in a spawn group and cannot be combined until they are removed from their group."); + GetCurrentZone()->RepopSpawns(this, combine_spawn); + should_target = true; + +} + +void Client::AddCombineSpawn(Spawn* spawn) { + if (combine_spawn && combine_spawn != spawn && spawn) { + combine_spawn->AddSpawnToGroup(spawn); + spawn->AddSpawnToGroup(combine_spawn); + GetCurrentZone()->RepopSpawns(this, combine_spawn); + } + else if (spawn) + combine_spawn = spawn; + should_target = true; + +} + +void Client::RemoveCombineSpawn(Spawn* spawn) { + if (combine_spawn && spawn) + spawn->RemoveSpawnFromGroup(); + if (combine_spawn == spawn) + combine_spawn->RemoveSpawnFromGroup(true); + GetCurrentZone()->RepopSpawns(this, combine_spawn); + if (combine_spawn == spawn) + combine_spawn = 0; + +} + +void Client::SaveCombineSpawns(const char* name) { + if (!combine_spawn) { + return; + } + vector* spawns = combine_spawn->GetSpawnGroup(); + if (!spawns) { + return; + } + int32 count = spawns->size(); + int32 spawnLocationID = 0; + + if (count == 1) + SimpleMessage(CHANNEL_COLOR_YELLOW, "Error: You only have a single Spawn in the group!"); + else if ((spawnLocationID = database.SaveCombinedSpawnLocation(GetCurrentZone(), combine_spawn, name)) > 0) { + Message(CHANNEL_COLOR_YELLOW, "Successfully combined %u spawns into spawn location: %u", count, spawnLocationID); + // we remove the spawn inside SaveCombinedSpawnLocation + //GetCurrentZone()->RemoveSpawn(combine_spawn); + } + else + SimpleMessage(CHANNEL_COLOR_YELLOW, "Error saving spawn group, check console for details."); + + safe_delete(spawns); + combine_spawn = 0; +} + +bool Client::AddItem(int32 item_id, int16 quantity, AddItemType type) { + Item* master_item = master_item_list.GetItem(item_id); + Item* item = 0; + if (master_item) + item = new Item(master_item); + if (item) { + if (quantity > 0) + item->details.count = quantity; + + return AddItem(item, nullptr, type); + } + else + Message(CHANNEL_COLOR_RED, "Could not find item with id of: %i", item_id); + + return false; +} + +bool Client::AddItem(Item* item, bool* item_deleted, AddItemType type) { + if (!item) { + return false; + } + if (player->AddItem(item, type)) { + EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion()); + if (outapp) { + //DumpPacket(outapp); + QueuePacket(outapp); + //resend bag desc with new item name added + outapp = player->SendBagUpdate(item->details.unique_id, GetVersion()); + if (outapp) { + //DumpPacket(outapp); + QueuePacket(outapp); + } + /*EQ2Packet* app = item->serialize(client->GetVersion(), false); + DumpPacket(app); + client->QueuePacket(app); + */ + } + CheckPlayerQuestsItemUpdate(item); + if (item->GetItemScript() && lua_interface) + lua_interface->RunItemScript(item->GetItemScript(), "obtained", item, player); + } + else { + lua_interface->SetLuaUserDataStale(item); + // likely lore conflict + + if(item_deleted) + *item_deleted = true; + + return false; + } + + return true; +} + +bool Client::AddItemToBank(int32 item_id, int16 quantity) { + Item* master_item = master_item_list.GetItem(item_id); + Item* item = 0; + if (master_item) + item = new Item(master_item); + if (item) { + if (quantity > 0) + item->details.count = quantity; + return AddItemToBank(item); + } + else + Message(CHANNEL_COLOR_RED, "Could not find item with id of: %i", item_id); + + return false; +} +bool Client::AddItemToBank(Item* item) { + if (!item) { + return false; + } + if (player->AddItemToBank(item)) { + EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion()); + if (outapp) { + QueuePacket(outapp); + //resend bag desc with new item name added + outapp = player->SendBagUpdate(item->details.inv_slot_id, GetVersion()); + if (outapp) + QueuePacket(outapp); + /*EQ2Packet* app = item->serialize(client->GetVersion(), false); + DumpPacket(app); + client->QueuePacket(app); + */ + } + CheckPlayerQuestsItemUpdate(item); + if (item->GetItemScript() && lua_interface) + lua_interface->RunItemScript(item->GetItemScript(), "obtained", item, player); + } + else { + lua_interface->SetLuaUserDataStale(item); + // likely lore conflict + safe_delete(item); + return false; + } + + return true; +} + +void Client::UnequipItem(int16 index, sint32 bag_id, int8 to_slot, int8 appearance_equip) { + vector packets = GetPlayer()->UnequipItem(index, bag_id, to_slot, GetVersion(), appearance_equip); + EQ2Packet* outapp = 0; + + for(int32 i=0;iUpdateWeapons(); + EQ2Packet* characterSheetPackets = GetPlayer()->GetPlayerInfo()->serialize(GetVersion()); + QueuePacket(characterSheetPackets); +} +bool Client::RemoveItem(Item* item, int16 quantity, bool force_override_no_delete) { + EQ2Packet* outapp; + bool delete_item = false; + + assert(item); + + if (quantity > 0 && !item->IsBag() && item->details.count > quantity) { + item->details.count -= quantity; + item->save_needed = true; + } + else { + database.DeleteItem(character_id, item, 0); + player->GetPlayerItemList()->RemoveItem(item, false); + delete_item = true; + } + + if(force_override_no_delete) + delete_item = false; + + if ((outapp = player->SendInventoryUpdate(version))) { + QueuePacket(outapp); + if (item->GetItemScript() && lua_interface) + lua_interface->RunItemScript(item->GetItemScript(), "removed", item, player); + if (delete_item) + { + PurgeItem(item); + lua_interface->SetLuaUserDataStale(item); + safe_delete(item); + } + + GetPlayer()->CalculateApplyWeight(); + return true; + } + + return false; +} + +void Client::SetLuaDebugClient(bool val) { + if (val) + lua_debug_timer.Start(); + lua_debug = val; + if (lua_interface && !val) { + lua_interface->RemoveDebugClients(this); + lua_debug_timer.Disable(); + } + +} + +void Client::SetMerchantTransaction(Spawn* spawn) { + merchant_transaction = spawn; + +} + +Spawn* Client::GetMerchantTransaction() { + return merchant_transaction; +} + +void Client::SetMailTransaction(Spawn* spawn) { + ResetSendMail(spawn ? false : true); + MMailWindowMutex.lock(); + mail_transaction = spawn; + MMailWindowMutex.unlock(); +} + +Spawn* Client::GetMailTransaction() { + return mail_transaction; +} + +void Client::PlaySound(const char* name) { + if (name) { + PacketStruct* packet = configReader.getStruct("WS_PlaySound", GetVersion()); + if (packet) { + packet->setMediumStringByName("name", name); + QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + +} + +float Client::CalculateBuyMultiplier(int32 merchant_id) { + /*MerchantFactionMultiplier* multiplier = world.GetMerchantMultiplier(merchant_id); + if(multiplier){ + sint32 faction_val = player->GetFactions()->GetFactionValue(multiplier->faction_id); + float diff_low = faction_val - multiplier->faction_min; + if(diff_low < 0) + diff_low*=-1; + float total_diff = multiplier->faction_max - multiplier->faction_min; + if(total_diff < 0) + total_diff*=-1; + float buy_multiplier = multiplier->high_buy_multiplier - multiplier->low_buy_multiplier; + float total1 = (diff_low/total_diff); + float final_buy_multiplier = total1*buy_multiplier + total1*multiplier->low_buy_multiplier; + return final_buy_multiplier; + }*/ + + return 1; +} + +float Client::CalculateSellMultiplier(int32 merchant_id) { + /*MerchantFactionMultiplier* multiplier = world.GetMerchantMultiplier(merchant_id); + if(multiplier){ + sint32 faction_val = player->GetFactions()->GetFactionValue(multiplier->faction_id); + float diff_low = faction_val - multiplier->faction_min; + if(diff_low < 0) + diff_low*=-1; + float total_diff = multiplier->faction_max - multiplier->faction_min; + if(total_diff < 0) + total_diff*=-1; + float sell_multiplier = multiplier->high_sell_multiplier - multiplier->low_sell_multiplier; + float total1 = (diff_low/total_diff); + float final_sell_multiplier = total1*sell_multiplier + total1*multiplier->low_sell_multiplier; + return final_sell_multiplier; + }*/ + + return 1; +} + +void Client::SellItem(int32 item_id, int16 quantity, int32 unique_id) { + Spawn* spawn = GetMerchantTransaction(); + Guild* guild = GetPlayer()->GetGuild(); + if (spawn && spawn->GetMerchantID() > 0 && (!(spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY)) && + spawn->IsClientInMerchantLevelRange(this)) { + int32 total_sell_price = 0; + int32 total_status_sell_price = 0; //for status + float multiplier = CalculateBuyMultiplier(spawn->GetMerchantID()); + int32 sell_price = 0; + int32 status_sell_price = 0; //for status + Item* master_item = master_item_list.GetItem(item_id); + Item* item = 0; + if (unique_id == 0) + item = player->item_list.GetItemFromID(item_id, quantity); + else + item = player->item_list.GetItemFromUniqueID(unique_id); + + if (!item) + item = player->item_list.GetItemFromID(item_id); + if (item && master_item) { + if(item->details.item_locked || item->details.equip_slot_id) + { + SimpleMessage(CHANNEL_COLOR_RED, "You cannot sell the item in use."); + return; + } + else if(item->CheckFlag(NO_VALUE)) + { + SimpleMessage(CHANNEL_COLOR_RED, "This item has no value."); + return; + } + else if (item->IsBag()) + { + int32 bagitemcount = player->GetPlayerItemList()->GetItemCountInBag(item); + if (bagitemcount > 0) { + SimpleMessage(CHANNEL_COLOR_RED, "You cannot sell a bag with items inside it."); + return; + } + } + + int32 sell_price = (int32)(master_item->sell_price * multiplier); + if (sell_price > item->sell_price) + sell_price = item->sell_price; + if (quantity > item->details.count) + quantity = item->details.count; + total_sell_price = sell_price * quantity; + + //------------------------------For Selling Status Items + status_sell_price = (int32)(master_item->sell_status * multiplier); + if (status_sell_price > item->sell_status) + status_sell_price = item->sell_status; + if (quantity > item->details.count) + quantity = item->details.count; + + total_status_sell_price = status_sell_price * quantity; + + if(total_status_sell_price > 0 && (!(spawn->GetMerchantType() & MERCHANT_TYPE_CITYMERCHANT))) + total_status_sell_price = 0; + + player->GetInfoStruct()->add_status_points(total_status_sell_price); + + int32 guildMaxLevel = 5 + item->details.recommended_level; // client hard codes +5 to the level + + if (player->GetGuild() && guild->GetLevel() < guildMaxLevel) { + guild->UpdateGuildStatus(GetPlayer(), total_status_sell_price / 10); + guild->SendGuildMemberList(); + guild->AddEXPCurrent((total_status_sell_price / 10), true); + } + if (quantity > 1) + { + if(total_status_sell_price) + Message(CHANNEL_MERCHANT_BUY_SELL, "You sell %i %s to %s for %s and %u Status Points.", quantity, master_item->CreateItemLink(GetVersion()).c_str(), spawn->GetName(), GetCoinMessage(total_sell_price).c_str(), status_sell_price); + else + Message(CHANNEL_MERCHANT_BUY_SELL, "You sell %i %s to %s for %s.", quantity, master_item->CreateItemLink(GetVersion()).c_str(), spawn->GetName(), GetCoinMessage(total_sell_price).c_str()); + } + else + { + if(total_status_sell_price) + Message(CHANNEL_MERCHANT_BUY_SELL, "You sell %s to %s for %s and %u Status Points.", master_item->CreateItemLink(GetVersion()).c_str(), spawn->GetName(), GetCoinMessage(total_sell_price).c_str(), status_sell_price); + else + Message(CHANNEL_MERCHANT_BUY_SELL, "You sell %s to %s for %s.", master_item->CreateItemLink(GetVersion()).c_str(), spawn->GetName(), GetCoinMessage(total_sell_price).c_str()); + } + player->AddCoins(total_sell_price); + + if(!item->no_buy_back && (total_status_sell_price == 0 || (total_status_sell_price > 0 && (!(spawn->GetMerchantType() & MERCHANT_TYPE_CITYMERCHANT))))) + AddBuyBack(unique_id, item_id, quantity, sell_price); + + if (quantity >= item->details.count) { + database.DeleteItem(GetCharacterID(), item, 0); + player->item_list.RemoveItem(item, true); + } + else { + item->details.count -= quantity; + item->save_needed = true; + } + EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion()); + if (outapp) + QueuePacket(outapp); + + if (!(spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY_BACK)) + SendBuyBackList(); + } + } + +} + +void Client::BuyBack(int32 item_id, int16 quantity) { + Spawn* spawn = GetMerchantTransaction(); + if (spawn && spawn->GetMerchantID() > 0 && (!(spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY_BACK)) && + spawn->IsClientInMerchantLevelRange(this)) { + deque::iterator itr; + BuyBackItem* buyback = 0; + BuyBackItem* closest = 0; + MBuyBack.readlock(__FUNCTION__, __LINE__); + for (itr = buy_back_items.begin(); itr != buy_back_items.end(); itr++) { + buyback = *itr; + if (buyback->unique_id == item_id) { + closest = buyback; + quantity = buyback->quantity; + break; + } + } + MBuyBack.releasereadlock(__FUNCTION__, __LINE__); + if (closest) { + Item* item = 0; + Item* master_item = master_item_list.GetItem(closest->item_id); + if (master_item) { + item = new Item(master_item); + if (closest->quantity >= quantity) + item->details.count = quantity; + else + item->details.count = closest->quantity; + } + bool itemDeleted = false; + bool itemAdded = false; + sint64 dispFlags = 0; + if (item && item->GetItemScript() && lua_interface && lua_interface->RunItemScript(item->GetItemScript(), "buyback_display_flags", item, player, nullptr, &dispFlags) && (dispFlags & DISPLAY_FLAG_NO_BUY)) + SimpleMessage(CHANNEL_NARRATIVE, "You do not meet all the requirements to buy this item."); + else if (!player->item_list.HasFreeSlot() && !player->item_list.CanStack(item)) + SimpleMessage(CHANNEL_COLOR_RED, "You do not have any slots available for this item."); + else if (player->RemoveCoins(closest->quantity * closest->price)) { + bool removed = false; + if (closest->quantity == quantity) { + MBuyBack.writelock(__FUNCTION__, __LINE__); + for (itr = buy_back_items.begin(); itr != buy_back_items.end(); itr++) { + if (*itr == closest) { + buy_back_items.erase(itr); + removed = true; + break; + } + } + MBuyBack.releasewritelock(__FUNCTION__, __LINE__); + } + else { + closest->quantity -= quantity; + closest->save_needed = true; + } + itemAdded = AddItem(item, &itemDeleted); + + if (removed) { + database.DeleteBuyBack(GetCharacterID(), closest->item_id, closest->quantity, closest->price); + safe_delete(closest); + } + + if (!(spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY_BACK)) + SendBuyBackList(); + } + else + SimpleMessage(CHANNEL_COLOR_RED, "You cannot afford this item."); + + if(!itemAdded && !itemDeleted) { + lua_interface->SetLuaUserDataStale(item); + safe_delete(item); + } + } + } + +} + +void Client::BuyItem(int32 item_id, int16 quantity) { + // Get the merchant we are buying from + Spawn* spawn = GetMerchantTransaction(); + // Make sure the spawn has a merchant list + if (spawn && spawn->GetMerchantID() > 0 && spawn->IsClientInMerchantLevelRange(this)) { + int32 total_buy_price = 0; + float multiplier = CalculateBuyMultiplier(spawn->GetMerchantID()); + int32 sell_price = 0; + Item* master_item = master_item_list.GetItem(item_id); + Item* item = 0; + int16 total_available = 0; + + vector* temp; + vector::iterator itr; + MerchantItemInfo* ItemInfo = 0; + temp = world.GetMerchantList(spawn->GetMerchantID()); + + for (itr = temp->begin(); itr != temp->end(); itr++) { + if ((*itr).item_id == item_id) { + ItemInfo = &(*itr); + break; + } + } + + if (master_item && ItemInfo) { + if (spawn->GetMerchantType() & MERCHANT_TYPE_LOTTO) { + quantity = 1; + total_available = 0xFFFF; + sell_price = master_item->sell_price; + } + else { + total_available = world.GetMerchantItemQuantity(spawn->GetMerchantID(), item_id); + sell_price = (int32)(master_item->sell_price * multiplier); + if (quantity > total_available) + quantity = total_available; + } + sint64 dispFlags = 0; + if (master_item->GetItemScript() && lua_interface && lua_interface->RunItemScript(master_item->GetItemScript(), "buy_display_flags", master_item, player, nullptr, &dispFlags) && (dispFlags & DISPLAY_FLAG_NO_BUY)) + { + SimpleMessage(CHANNEL_NARRATIVE, "You do not meet all the requirements to buy this item."); + return; + } + if(quantity < 1) + { + SimpleMessage(CHANNEL_COLOR_RED, "Merchant does not have item for purchase (quantity < 1)."); + return; + } + + total_buy_price = sell_price * quantity; + item = new Item(master_item); + item->details.count = quantity; + if (!player->item_list.HasFreeSlot() && !player->item_list.CanStack(item)) { + SimpleMessage(CHANNEL_COLOR_RED, "You do not have any slots available for this item."); + lua_interface->SetLuaUserDataStale(item); + safe_delete(item); + } + else { + // Price not set in the merchant_inventory table, use the old method + if (ItemInfo->price_item_id == 0 && ItemInfo->price_item2_id == 0 && ItemInfo->price_status == 0 && ItemInfo->price_stationcash == 0 && ItemInfo->price_coins == 0) { + if (player->RemoveCoins(total_buy_price)) { + item->SetMaxSellValue(sell_price); + if (quantity > 1) + Message(CHANNEL_MERCHANT_BUY_SELL, "You buy %i %s from %s for%s.", quantity, master_item->CreateItemLink(GetVersion()).c_str(), spawn->GetName(), GetCoinMessage(total_buy_price).c_str()); + else + Message(CHANNEL_MERCHANT_BUY_SELL, "You buy %s from %s for%s.", master_item->CreateItemLink(GetVersion()).c_str(), spawn->GetName(), GetCoinMessage(total_buy_price).c_str()); + bool itemDeleted = false; + AddItem(item, &itemDeleted); + if(!itemDeleted) { + CheckPlayerQuestsItemUpdate(item); + if (item && total_available < 0xFF) { + world.DecreaseMerchantQuantity(spawn->GetMerchantID(), item_id, quantity); + SendBuyMerchantList(); + } + + if (spawn->GetMerchantType() & MERCHANT_TYPE_LOTTO) + PlayLotto(total_buy_price, item->details.item_id); + } + } + else { + Message(CHANNEL_COLOR_RED, "You do not have enough coin to purchase %s.", master_item->CreateItemLink(GetVersion()).c_str()); + GetCurrentZone()->SendSpellFailedPacket(this, SPELL_ERROR_NOT_ENOUGH_COIN); + PlaySound("buy_failed"); + } + } + else { + // Price set in merchant_inventory table + + // Check if the player has enough status, coins and staion cash to buy the item before checking the items + // TODO: need to add support for station cash + if (player->GetInfoStruct()->get_status_points() >= (ItemInfo->price_status * quantity) && player->HasCoins(ItemInfo->price_coins * quantity)) { + // Check items + int16 item_quantity = 0; + // Default these to true in case price_item_id or price_item2_id was never set + bool hasItem1 = true; + bool hasItem2 = true; + Item* tempItem1 = 0; + Item* tempItem2 = 0; + if (ItemInfo->price_item_id != 0) { + // Same item for whatever reason lets add the quantities together + if (ItemInfo->price_item_id == ItemInfo->price_item2_id) + item_quantity = ItemInfo->price_item_qty + ItemInfo->price_item2_qty; + else + item_quantity = ItemInfo->price_item_qty; + + tempItem1 = player->item_list.GetItemFromID(ItemInfo->price_item_id); + if (tempItem1) { + if (tempItem1->details.count < item_quantity) + hasItem1 = false; + } + else { + hasItem1 = false; + } + } + + // Check item2, if item_quantity is greater then item1 quantity then item2 is the same item + // as item1 and we already checked for it so we can skip this check + if (ItemInfo->price_item2_id != 0 && item_quantity <= ItemInfo->price_item_qty) { + tempItem2 = player->item_list.GetItemFromID(ItemInfo->price_item2_id); + if (tempItem2) { + if (tempItem2->details.count < ItemInfo->price_item2_qty) + hasItem2 = false; + } + else { + hasItem2 = false; + } + } + // if we have every thing then remove the price and give the item + if (hasItem1 && hasItem2) { + player->GetInfoStruct()->set_status_points(player->GetInfoStruct()->get_status_points() - (ItemInfo->price_status * quantity)); + // TODO: station cash + + // The update that would normally be sent after modifing the players inventory is automatically sent in AddItem wich is called later + // so there is no need to send it more then that one time + if (tempItem1) { + if (tempItem1->details.count > item_quantity) { + tempItem1->details.count -= item_quantity; + tempItem1->save_needed = true; + } + else { + database.DeleteItem(GetCharacterID(), tempItem1, 0); + player->item_list.DestroyItem(tempItem1->details.index); + } + } + if (tempItem2) { + if (tempItem2->details.count > ItemInfo->price_item2_qty) { + tempItem2->details.count -= ItemInfo->price_item2_qty; + tempItem2->save_needed = true; + } + else { + database.DeleteItem(GetCharacterID(), tempItem2, 0); + player->item_list.DestroyItem(tempItem2->details.index); + } + } + + + // Checked to see if we had enough coins already so don't need to check the return type on RemoveCoins as it will always be true + player->RemoveCoins(ItemInfo->price_coins * quantity); + item->SetMaxSellValue(sell_price); + if (quantity > 1) + Message(CHANNEL_MERCHANT_BUY_SELL, "You buy %i %s from %s for%s.", quantity, master_item->CreateItemLink(GetVersion()).c_str(), spawn->GetName(), GetCoinMessage(ItemInfo->price_coins * quantity).c_str()); + else + Message(CHANNEL_MERCHANT_BUY_SELL, "You buy %s from %s for%s.", master_item->CreateItemLink(GetVersion()).c_str(), spawn->GetName(), GetCoinMessage(ItemInfo->price_coins * quantity).c_str()); + bool itemDeleted = false; + AddItem(item, &itemDeleted); + if(!itemDeleted) { + CheckPlayerQuestsItemUpdate(item); + if (item && total_available < 0xFF) { + world.DecreaseMerchantQuantity(spawn->GetMerchantID(), item_id, quantity); + SendBuyMerchantList(); + } + + SendSellMerchantList(); + if (spawn->GetMerchantType() & MERCHANT_TYPE_LOTTO) + PlayLotto(total_buy_price, item->details.item_id); + } + + } + else { + Message(CHANNEL_COLOR_RED, "You do not have enough coin to purchase %s.", master_item->CreateItemLink(GetVersion()).c_str()); + GetCurrentZone()->SendSpellFailedPacket(this, SPELL_ERROR_NOT_ENOUGH_COIN); + PlaySound("buy_failed"); + } + } + else { + Message(CHANNEL_COLOR_RED, "You do not have enough coin to purchase %s.", master_item->CreateItemLink(GetVersion()).c_str()); + GetCurrentZone()->SendSpellFailedPacket(this, SPELL_ERROR_NOT_ENOUGH_COIN); + PlaySound("buy_failed"); + } + } + } + } + } + +} + +void Client::RepairItem(int32 item_id) { + Spawn* spawn = GetMerchantTransaction(); + if (spawn) { + Item* item = player->item_list.GetItemFromID(item_id); + if (!item) + item = player->GetEquipmentList()->GetItemFromItemID(item_id); + if (item) { + if(item->CheckFlag2(NO_REPAIR)) { + Message(CHANNEL_MERCHANT, "The mender was unable to repair your items."); + PlaySound("buy_failed"); + } + else { + int32 repair_cost = item->CalculateRepairCost(); + if (player->RemoveCoins((int32)repair_cost)) { + item->generic_info.condition = 100; + item->save_needed = true; + QueuePacket(player->GetEquipmentList()->serialize(GetVersion(), player)); + QueuePacket(player->SendInventoryUpdate(GetVersion())); + QueuePacket(item->serialize(version, false, player)); + Message(CHANNEL_MERCHANT, "You give %s %s to repair your %s.", spawn->GetName(), GetCoinMessage(repair_cost).c_str(), item->CreateItemLink(GetVersion()).c_str()); + PlaySound("coin_cha_ching"); + if (spawn->GetMerchantType() & MERCHANT_TYPE_REPAIR) + SendRepairList(); + } + else { + string popup_text = "You do not have enough coin to repair "; + string popup_item = item->CreateItemLink(GetVersion()).c_str(); + popup_text.append(popup_item); + SendPopupMessage(10, popup_text.c_str(), "", 3, 0xFF, 0xFF, 0xFF); + Message(CHANNEL_MERCHANT, "You do not have enough coin to repair %s.", item->CreateItemLink(GetVersion()).c_str()); + PlaySound("buy_failed"); + } + } + } + } + +} + +void Client::RepairAllItems() { + Spawn* spawn = GetMerchantTransaction(); + if (spawn) { + vector* repairable_items = GetRepairableItems(); + if (repairable_items && repairable_items->size() > 0) { + vector::iterator itr; + int64 total_cost = 0; + for (itr = repairable_items->begin(); itr != repairable_items->end(); itr++) + total_cost += (*itr)->CalculateRepairCost(); + if (player->RemoveCoins((int32)total_cost)) { + Message(CHANNEL_MERCHANT, "You give %s to repair all of your items.", GetCoinMessage((int32)total_cost).c_str()); + for (itr = repairable_items->begin(); itr != repairable_items->end(); itr++) { + Item* item = *itr; + if (item) { + item->generic_info.condition = 100; + item->save_needed = true; + QueuePacket(item->serialize(version, false, player)); + Message(CHANNEL_COLOR_YELLOW, "Repaired: %s.", item->CreateItemLink(GetVersion()).c_str()); + } + } + QueuePacket(player->GetEquipmentList()->serialize(GetVersion(), player)); + QueuePacket(player->SendInventoryUpdate(GetVersion())); + PlaySound("coin_cha_ching"); + if (spawn->GetMerchantType() & MERCHANT_TYPE_REPAIR) + SendRepairList(); + } + else { + string popup_text = "You do not have enough coin to repair all of your items. "; + SendPopupMessage(10, popup_text.c_str(), "", 3, 0xFF, 0xFF, 0xFF); + SimpleMessage(CHANNEL_MERCHANT, "You do not have enough coin to repair all of your items."); + PlaySound("buy_failed"); + } + } + safe_delete(repairable_items); + } + +} + +void Client::SendAchievementsList() +{ + /*map *achievements = player->GetAchievementList()->GetAchievements(); + map::iterator itr; + Achievement *achievement; + vector *requirements = 0; + vector::iterator itr2; + AchievementRequirements *requirement; + vector *rewards = 0; + vector::iterator itr3; + AchievementRewards *reward; + PacketStruct *packet; + int16 i = 0; + int16 j = 0; + int16 k = 0; + + if (!(packet = configReader.getStruct("WS_CharacterAchievements", version))) { + return; + } + + packet->setArrayLengthByName("num_achievements" , achievements->size()); + for (itr = achievements->begin(); itr != achievements->end(); itr++) { + achievement = itr->second; + packet->setArrayDataByName("achievement_id", achievement->GetID(), i); + packet->setArrayDataByName("title", achievement->GetTitle(), i); + packet->setArrayDataByName("uncompleted_text", achievement->GetUncompletedText(), i); + packet->setArrayDataByName("completed_text", achievement->GetCompletedText(), i); + packet->setArrayDataByName("category", achievement->GetCategory(), i); + packet->setArrayDataByName("expansion", achievement->GetExpansion(), i); + packet->setArrayDataByName("icon", achievement->GetIcon(), i); + packet->setArrayDataByName("point_value", achievement->GetPointValue(), i); + packet->setArrayDataByName("qty_req", achievement->GetQtyReq(), i); + packet->setArrayDataByName("hide_achievement", achievement->GetHide(), i); + packet->setArrayDataByName("unknown3", achievement->GetUnknown3a(), i); + packet->setArrayDataByName("unknown3", achievement->GetUnknown3b(), i); + requirements = achievement->GetRequirements(); + rewards = achievement->GetRewards(); + j = 0; + k = 0; + packet->setSubArrayLengthByName("num_items", requirements->size(), i, j); + for (itr2 = requirements->begin(); itr2 != requirements->end(); itr2++) { + requirement = *itr2; + packet->setSubArrayDataByName("item_name", requirement->name.c_str(), i, j); + packet->setSubArrayDataByName("item_qty_req", requirement->qty_req, i, j); + j++; + } + packet->setSubArrayLengthByName("num_rewards", achievement->GetRewards()->size(), i, k); + for (itr3 = rewards->begin(); itr3 != rewards->end(); itr3++) { + reward = *itr3; + packet->setSubArrayDataByName("reward_item", reward->reward.c_str(), i, k); + k++; + } + i++; + } + + //packet->PrintPacket(); + EQ2Packet* data = packet->serialize(); + EQ2Packet* app = new EQ2Packet(OP_ClientCmdMsg, data->pBuffer, data->size); + safe_delete(packet); + safe_delete(data); + //DumpPacket(app); + QueuePacket(app);*/ + + QueuePacket(master_achievement_list.GetAchievementPacket()->Copy()); + SendAchievementUpdate(true); +} + +void Client::SendAchievementUpdate(bool first_login) { + map* updates = player->GetAchievementUpdateList()->GetAchievementUpdates(); + map::iterator itr; + AchievementUpdate* update; + vector* update_items = 0; + vector::iterator itr2; + AchievementUpdateItems* update_item; + + int16 i = 0; + int16 j = 0; + + PacketStruct* packet; + + if (!(packet = configReader.getStruct("WS_AchievementUpdate", version))) { + return; + } + + packet->setDataByName("unknown1", first_login ? 1 : 0); + packet->setArrayLengthByName("num_achievements", updates->size()); + for (itr = updates->begin(); itr != updates->end(); itr++) { + update = itr->second; + packet->setArrayDataByName("achievement_id", update->GetID(), i); + packet->setArrayDataByName("completed_date", update->GetCompletedDate(), i); + update_items = update->GetUpdateItems(); + j = 0; + packet->setSubArrayLengthByName("num_items", update_items->size(), i); + for (itr2 = update_items->begin(); itr2 != update_items->end(); itr2++) { + update_item = *itr2; + packet->setSubArrayDataByName("item_update", update_item->item_update, i, j); + j++; + } + i++; + } + + //packet->PrintPacket(); + EQ2Packet* data = packet->serialize(); + EQ2Packet* app = new EQ2Packet(OP_ClientCmdMsg, data->pBuffer, data->size); + safe_delete(packet); + safe_delete(data); + //DumpPacket(app); + QueuePacket(app); +} + +void Client::SendBuyMerchantList(bool sell) { + Spawn* spawn = GetMerchantTransaction(); + if (spawn && spawn->GetMerchantID() > 0 && spawn->IsClientInMerchantLevelRange(this)) { + vector* items = world.GetMerchantItemList(spawn->GetMerchantID(), spawn->GetMerchantType(), player); + if (items) { + PacketStruct* packet = configReader.getStruct("WS_UpdateMerchant", GetVersion()); + if (packet) { + float multiplier = CalculateBuyMultiplier(spawn->GetMerchantID()); + packet->setDataByName("spawn_id", player->GetIDWithPlayerSpawn(spawn)); + packet->setArrayLengthByName("num_items", items->size()); + vector::iterator itr; + sint8 item_difficulty = 0; + int32 sell_price = 0; + int i = 0; + int tmp_level = 0; + for (itr = items->begin(); itr != items->end(); itr++, i++) { + MerchantItemInfo ItemInfo = *itr; + Item* item = master_item_list.GetItem(ItemInfo.item_id); + if (!item) + continue; + + packet->setArrayDataByName("item_name", item->name.c_str(), i); + packet->setArrayDataByName("item_id", item->details.item_id, i); + packet->setArrayDataByName("stack_size", item->stack_count, i); + packet->setArrayDataByName("icon", item->GetIcon(GetVersion()), i); + if (item->generic_info.adventure_default_level > 0) + tmp_level = item->generic_info.adventure_default_level; + else + tmp_level = item->generic_info.tradeskill_default_level; + packet->setArrayDataByName("level", tmp_level, i); + if(rule_manager.GetGlobalRule(R_World, DisplayItemTiers)->GetBool()) { + packet->setArrayDataByName("tier", item->details.tier, i); + } + packet->setArrayDataByName("item_id2", item->details.item_id, i); + item_difficulty = player->GetArrowColor(tmp_level); + if (item_difficulty != ARROW_COLOR_WHITE && item_difficulty != ARROW_COLOR_RED && item_difficulty != ARROW_COLOR_GRAY) + item_difficulty = ARROW_COLOR_WHITE; + + sint64 overrideValue = 0; + if (item->GetItemScript() && lua_interface && lua_interface->RunItemScript(item->GetItemScript(), "item_difficulty", item, player, nullptr, &overrideValue)) + item_difficulty = (sint8)overrideValue; + + item_difficulty -= 6; + if (item_difficulty < 0) + item_difficulty *= -1; + + packet->setArrayDataByName("item_difficulty", item_difficulty, i); + packet->setArrayDataByName("quantity", ItemInfo.quantity, i); + packet->setArrayDataByName("unknown5", 255, i); + packet->setArrayDataByName("stack_size2", item->stack_count, i); + + sint64 dispFlags = 0; + if (item->GetItemScript() && lua_interface && lua_interface->RunItemScript(item->GetItemScript(), "buy_display_flags", item, player, nullptr, &dispFlags)) + packet->setArrayDataByName("display_flags", (int8)dispFlags, i); + + std::string overrideValueStr; + // classic client isn't properly tracking this field, DoF we don't have it identified yet, but no field to cause any issues (can add later if identified) + if (GetVersion() >= 546 && item->GetItemScript() && lua_interface && lua_interface->RunItemScriptWithReturnString(item->GetItemScript(), "item_description", item, player, &overrideValueStr)) + packet->setArrayDataByName("description", overrideValueStr.c_str(), i); + + // If no price set in the merchant_inventory table then use the old method + if (ItemInfo.price_item_id == 0 && ItemInfo.price_item2_id == 0 && ItemInfo.price_coins == 0 && ItemInfo.price_status == 0 && ItemInfo.price_stationcash == 0) { + sell_price = (int32)(item->sell_price * multiplier); + packet->setArrayDataByName("price", sell_price, i); + } + else { + int8 count = 0; + if (ItemInfo.price_item_id != 0 && ItemInfo.price_item_qty != 0) + count++; + if (ItemInfo.price_item2_id != 0 && ItemInfo.price_item2_qty != 0) + count++; + if (count != 0) { + packet->setSubArrayLengthByName("num_tokens", count, i); + int8 index = 0; + Item* token = 0; + if (ItemInfo.price_item_id != 0) { + token = master_item_list.GetItem(ItemInfo.price_item_id); + if (item) { + packet->setSubArrayDataByName("token_icon", token->GetIcon(GetVersion()), i, index); + packet->setSubArrayDataByName("token_qty", ItemInfo.price_item_qty, i, index); + packet->setSubArrayDataByName("token_id", ItemInfo.price_item_id, i, index); + packet->setSubArrayDataByName("token_name", token->name.c_str(), i, index); + } + token = 0; + index++; + } + if (ItemInfo.price_item2_id != 0) { + token = master_item_list.GetItem(ItemInfo.price_item2_id); + if (item) { + packet->setSubArrayDataByName("token_icon", token->GetIcon(GetVersion()), i, index); + packet->setSubArrayDataByName("token_qty", ItemInfo.price_item2_qty, i, index); + packet->setSubArrayDataByName("token_id", ItemInfo.price_item2_id, i, index); + packet->setSubArrayDataByName("token_name", token->name.c_str(), i, index); + } + } + } + packet->setArrayDataByName("price", ItemInfo.price_coins, i); + packet->setArrayDataByName("status2", ItemInfo.price_status, i); + packet->setArrayDataByName("station_cash", ItemInfo.price_stationcash, i); + } + } + if (GetVersion() < 561) { + //buy is 0 so dont need to set it + if (sell) + packet->setDataByName("type", 1); + } + else if (GetVersion() == 561) { + packet->setDataByName("type", 2); + } + else { + if (sell) + packet->setDataByName("type", 130); + else + packet->setDataByName("type", 2); + } + EQ2Packet* outapp = packet->serialize(); + QueuePacket(outapp); + safe_delete(packet); + } + safe_delete(items); + } + else { + // Need to send an empty packet in the event there is no item list, otherwise the + // last item list sent to the player will show for this merchant + PacketStruct* packet = configReader.getStruct("WS_UpdateMerchant", GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", player->GetIDWithPlayerSpawn(spawn)); + if (GetVersion() <= 561) { + //buy is 0 so dont need to set it + if (sell) + packet->setDataByName("type", 1); + } + else { + if (sell) + packet->setDataByName("type", 130); + else + packet->setDataByName("type", 2); + } + EQ2Packet* outapp = packet->serialize(); + QueuePacket(outapp); + safe_delete(packet); + } + } + } + +} + +void Client::SendSellMerchantList(bool sell) { + Spawn* spawn = GetMerchantTransaction(); + if (!spawn || (spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY) || (spawn->GetMerchantType() & MERCHANT_TYPE_LOTTO)) + return; + + if (spawn && spawn->GetMerchantID() > 0 && spawn->IsClientInMerchantLevelRange(this)) { + map* items = player->GetItemList(); + if (items) { + PacketStruct* packet = configReader.getStruct("WS_UpdateMerchant", GetVersion()); + if (packet) { + vector sellable_items; + map::iterator test_itr; + for (test_itr = items->begin(); test_itr != items->end(); test_itr++) { + bool isbagwithitems = false; + if (test_itr->second && test_itr->second->IsBag() && (test_itr->second->details.num_slots - test_itr->second->details.num_free_slots != test_itr->second->details.num_slots)) + isbagwithitems = true; + + if (test_itr->second && !test_itr->second->CheckFlag(NO_VALUE) && (isbagwithitems == false) && (test_itr->second->details.inv_slot_id != -3) && (test_itr->second->details.inv_slot_id != -4)) + sellable_items.push_back(test_itr->second); + } + packet->setDataByName("spawn_id", player->GetIDWithPlayerSpawn(spawn)); + packet->setArrayLengthByName("num_items", sellable_items.size()); + vector::iterator itr; + Item* item = 0; + sint8 item_difficulty = 0; + float multiplier = CalculateSellMultiplier(spawn->GetMerchantID()); + int32 sell_price = 0; + Item* master_item = 0; + int i = 0; + int tmp_level = 0; + for (itr = sellable_items.begin(); itr != sellable_items.end(); itr++, i++) { + item = *itr; + master_item = master_item_list.GetItem(item->details.item_id); + if (master_item) + sell_price = (int32)(master_item->sell_price * multiplier); + else + sell_price = 0; + if (sell_price > item->sell_price) + sell_price = item->sell_price; + packet->setArrayDataByName("item_name", item->name.c_str(), i); + string thename = item->name; + + packet->setArrayDataByName("price", sell_price, i); + packet->setArrayDataByName("status", 0, i);//additive to status 2 maybe for server bonus etc + + int8 dispFlags = 0; + + // only city merchants allow selling for status + if(item->sell_status > 0 && (spawn->GetMerchantType() & MERCHANT_TYPE_CITYMERCHANT)) + { + packet->setArrayDataByName("status2", item->sell_status, i); //this one is the main status + int32 guildMaxLevel = 5 + item->details.recommended_level; // client hard codes +5 to the level + if (GetPlayer()->GetGuild() && GetPlayer()->GetGuild()->GetLevel() >= guildMaxLevel) { + dispFlags += DISPLAY_FLAG_NO_GUILD_STATUS; + } + + } + if(item->no_buy_back || (item->sell_status > 0 && (spawn->GetMerchantType() & MERCHANT_TYPE_CITYMERCHANT))) + { + if(GetVersion() < 1188) + dispFlags += DISPLAY_FLAG_RED_TEXT; // for older clients it isn't "no buy back", you can either have 1 for red text or 255 for 'not for sale' to be checked + else + dispFlags += DISPLAY_FLAG_NO_BUYBACK; + } + + if(item->no_sale) + dispFlags += DISPLAY_FLAG_NOT_FOR_SALE; + + packet->setArrayDataByName("display_flags", dispFlags, i); + packet->setArrayDataByName("item_id", item->details.item_id, i); + packet->setArrayDataByName("unique_item_id", item->details.unique_id, i); + packet->setArrayDataByName("stack_size", item->details.count, i); + packet->setArrayDataByName("icon", item->GetIcon(GetVersion()), i); + if (item->generic_info.adventure_default_level > 0) + tmp_level = item->generic_info.adventure_default_level; + else + tmp_level = item->generic_info.tradeskill_default_level; + packet->setArrayDataByName("level", item->details.recommended_level, i); + + if(rule_manager.GetGlobalRule(R_World, DisplayItemTiers)->GetBool()) { + packet->setArrayDataByName("tier", item->details.tier, i); + } + packet->setArrayDataByName("item_id2", item->details.item_id, i); + item_difficulty = player->GetArrowColor(tmp_level); + if (item_difficulty != ARROW_COLOR_WHITE && item_difficulty != ARROW_COLOR_RED && item_difficulty != ARROW_COLOR_GRAY) + item_difficulty = ARROW_COLOR_WHITE; + + sint64 overrideValue = 0; + if (item->GetItemScript() && lua_interface && lua_interface->RunItemScript(item->GetItemScript(), "item_difficulty", item, player, nullptr, &overrideValue)) + item_difficulty = (sint8)overrideValue; + + item_difficulty -= 6; + if (item_difficulty < 0) + item_difficulty *= -1; + + packet->setArrayDataByName("item_difficulty", item_difficulty, i); + if (item->details.count == 1) + packet->setArrayDataByName("quantity", 0xFFFF, i); + else + packet->setArrayDataByName("quantity", item->details.count, i); + packet->setArrayDataByName("stack_size2", item->details.count, i); + if (GetVersion() <= 1096) + packet->setArrayDataByName("description", item->description.c_str(), i); + } + if (GetVersion() < 561) { + packet->setDataByName("type", 1); + } + else if(GetVersion() == 561) { + packet->setDataByName("type", 1); + } + else { + if (sell) + packet->setDataByName("type", 129); + else + packet->setDataByName("type", 1); + } + packet->setDataByName("unknown8a", 16256, 6); + packet->setDataByName("unknown8a", 16256, 10); + //packet->PrintPacket(); + EQ2Packet* outapp = packet->serialize(); + //DumpPacket(outapp); + QueuePacket(outapp); + safe_delete(packet); + } + safe_delete(items); + } + } + +} + +void Client::SendBuyBackList(bool sell) { + if (GetVersion() <= 561) //this wasn't added until LU37 on July 31st 2007, well after the DoF client + return; + Spawn* spawn = GetMerchantTransaction(); + if (spawn && spawn->GetMerchantID() > 0 && spawn->IsClientInMerchantLevelRange(this)) { + deque::iterator itr; + int i = 0; + Item* master_item = 0; + BuyBackItem* buyback = 0; + PacketStruct* packet = configReader.getStruct("WS_UpdateMerchant", GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", player->GetIDWithPlayerSpawn(spawn)); + packet->setArrayLengthByName("num_items", buy_back_items.size()); + sint8 item_difficulty = 0; + MBuyBack.readlock(__FUNCTION__, __LINE__); + int tmp_level = 0; + for (itr = buy_back_items.begin(); itr != buy_back_items.end(); itr++, i++) { + buyback = *itr; + master_item = master_item_list.GetItem(buyback->item_id); + if (master_item) { + packet->setArrayDataByName("item_name", master_item->name.c_str(), i); + packet->setArrayDataByName("price", buyback->price, i); + packet->setArrayDataByName("item_id", master_item->details.item_id, i); + packet->setArrayDataByName("unique_item_id", buyback->unique_id, i); + packet->setArrayDataByName("stack_size", buyback->quantity, i); + packet->setArrayDataByName("icon", master_item->GetIcon(GetVersion()), i); + if (master_item->generic_info.adventure_default_level > 0) + tmp_level = master_item->generic_info.adventure_default_level; + else + tmp_level = master_item->generic_info.tradeskill_default_level; + packet->setArrayDataByName("level", tmp_level, i); + if(rule_manager.GetGlobalRule(R_World, DisplayItemTiers)->GetBool()) { + packet->setArrayDataByName("tier", master_item->details.tier, i); + } + packet->setArrayDataByName("item_id2", master_item->details.item_id, i); + item_difficulty = player->GetArrowColor(tmp_level); + if (item_difficulty != ARROW_COLOR_WHITE && item_difficulty != ARROW_COLOR_RED && item_difficulty != ARROW_COLOR_GRAY) + item_difficulty = ARROW_COLOR_WHITE; + + sint64 overrideValue = 0; + if (master_item->GetItemScript() && lua_interface && lua_interface->RunItemScript(master_item->GetItemScript(), "item_difficulty", master_item, player, nullptr, &overrideValue)) + item_difficulty = (sint8)overrideValue; + + item_difficulty -= 6; + if (item_difficulty < 0) + item_difficulty *= -1; + packet->setArrayDataByName("item_difficulty", item_difficulty, i); + + sint64 dispFlags = 0; + if (master_item->GetItemScript() && lua_interface && lua_interface->RunItemScript(master_item->GetItemScript(), "buyback_display_flags", master_item, player, nullptr, &dispFlags)) + packet->setArrayDataByName("display_flags", (int8)dispFlags, i); + + if (buyback->quantity == 1) + packet->setArrayDataByName("quantity", 0xFFFF, i); + else + packet->setArrayDataByName("quantity", buyback->quantity, i); + packet->setArrayDataByName("stack_size2", buyback->quantity, i); + if (GetVersion() <= 1096) + packet->setArrayDataByName("description", master_item->description.c_str(), i); + } + } + MBuyBack.releasereadlock(__FUNCTION__, __LINE__); + if (sell) + packet->setDataByName("type", 640); + else + packet->setDataByName("type", 512); + EQ2Packet* outapp = packet->serialize(); + // DumpPacket(outapp); + QueuePacket(outapp); + safe_delete(packet); + } + } + +} + +void Client::SendRepairList() { + Spawn* spawn = GetMerchantTransaction(); + if (spawn) { + vector* repairable_items = GetRepairableItems(); + PacketStruct* packet = configReader.getStruct("WS_UpdateMerchant", GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", player->GetIDWithPlayerSpawn(spawn)); + packet->setArrayLengthByName("num_items", repairable_items->size()); + Item* item = 0; + sint8 item_difficulty = 0; + int32 i = 0; + vector::iterator itr; + for (itr = repairable_items->begin(); itr != repairable_items->end(); itr++, i++) { + item = *itr; + + packet->setArrayDataByName("item_name", item->name.c_str(), i); + packet->setArrayDataByName("price", item->CalculateRepairCost(), i); + packet->setArrayDataByName("item_id", item->details.item_id, i); + packet->setArrayDataByName("stack_size", item->details.count, i); + packet->setArrayDataByName("icon", item->GetIcon(GetVersion()), i); + + /*if (item->generic_info.adventure_default_level > 0) + tmp_level = item->generic_info.adventure_default_level; + else + tmp_level = item->generic_info.tradeskill_default_level; + packet->setArrayDataByName("level", tmp_level, i);*/ + packet->setArrayDataByName("level", item->generic_info.adventure_default_level, i); + + if(rule_manager.GetGlobalRule(R_World, DisplayItemTiers)->GetBool()) { + packet->setArrayDataByName("tier", item->details.tier, i); + } + packet->setArrayDataByName("item_id2", item->details.item_id, i); + item_difficulty = player->GetArrowColor(item->generic_info.adventure_default_level); + if (item_difficulty != ARROW_COLOR_WHITE && item_difficulty != ARROW_COLOR_RED && item_difficulty != ARROW_COLOR_GRAY) + item_difficulty = ARROW_COLOR_WHITE; + item_difficulty -= 6; + if (item_difficulty < 0) + item_difficulty *= -1; + packet->setArrayDataByName("item_difficulty", item_difficulty, i); + if (item->details.count == 1) + packet->setArrayDataByName("quantity", 0xFFFF, i); + else + packet->setArrayDataByName("quantity", item->details.count, i); + packet->setArrayDataByName("stack_size2", item->details.count, i); + if (GetVersion() <= 1096) + packet->setArrayDataByName("description", item->description.c_str(), i); + } + if (GetVersion() <= 561) { + packet->setDataByName("type", 112); + } + else { + packet->setDataByName("type", 96); + } + EQ2Packet* outapp = packet->serialize(); + + //DumpPacket(outapp); + QueuePacket(outapp); + + /*if (GetVersion() <= 561) { + packet->setDataByName("type", 16); + EQ2Packet* outapp2 = packet->serialize(); + QueuePacket(outapp2); + }*/ + + safe_delete(packet); + } + safe_delete(repairable_items); + } + +} + +void Client::ShowLottoWindow() { + if(GetVersion() <= 373) { + SimpleMessage(CHANNEL_COLOR_RED, "This client does not support the gambler UI, only Desert of Flames or later client."); + return; + } + Spawn* spawn = GetMerchantTransaction(); + if (spawn) { + + int32 item_id = rule_manager.GetGlobalRule(R_World, GamblingTokenItemID)->GetInt32(); + if (!item_id) + { + LogWrite(WORLD__ERROR, 0, "World", "No GamblingTokenItemID rule set!"); + SimpleMessage(CHANNEL_COLOR_RED, "The server admin has not setup a lotto item ticket."); + + return; + } + else if (item_id == 0) + { + LogWrite(WORLD__ERROR, 0, "World", "Error! Invalid GamblingTokenItemID value!"); + + return; + } + + Item* item = master_item_list.GetItem(item_id); + if (!item) { + LogWrite(WORLD__ERROR, 0, "World", "The 'GamblingTokenItemID' rule value %u is not a valid item id.", item_id); + + return; + } + + LogWrite(WORLD__DEBUG, 0, "World", "GamblingTokenItemID = '%s' (%u)", item->name.c_str(), item_id); + + PacketStruct* packet = configReader.getStruct("WS_UpdateMerchant", GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", player->GetIDWithPlayerSpawn(spawn)); + packet->setArrayLengthByName("num_items", 1); + packet->setArrayDataByName("item_name", item->name.c_str()); + packet->setArrayDataByName("price", item->sell_price); + packet->setArrayDataByName("item_id", item->details.item_id); + packet->setArrayDataByName("stack_size", item->details.count); + packet->setArrayDataByName("icon", item->GetIcon(GetVersion())); + packet->setArrayDataByName("level", item->generic_info.adventure_default_level); + + if(rule_manager.GetGlobalRule(R_World, DisplayItemTiers)->GetBool()) { + packet->setArrayDataByName("tier", item->details.tier); + } + packet->setArrayDataByName("item_id2", item->details.item_id); + int8 item_difficulty = player->GetArrowColor(item->generic_info.adventure_default_level); + if (item_difficulty != ARROW_COLOR_WHITE && item_difficulty != ARROW_COLOR_RED && item_difficulty != ARROW_COLOR_GRAY) + item_difficulty = ARROW_COLOR_WHITE; + item_difficulty -= 6; + if (item_difficulty < 0) + item_difficulty *= -1; + packet->setArrayDataByName("item_difficulty", item_difficulty); + //if(item->details.count == 1) + packet->setArrayDataByName("quantity", 0xFFFF); + //else + // packet->setArrayDataByName("quantity", item->details.count); + packet->setArrayDataByName("stack_size2", item->details.count); + packet->setArrayDataByName("description", item->description.c_str()); + if (GetVersion() <= 546) { + packet->setDataByName("type", 128); + } + else { + packet->setDataByName("type", 0x00000102); + } + QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + +} + +void Client::PlayLotto(int32 price, int32 ticket_item_id) { + PacketStruct* packet = configReader.getStruct("WS_Lottery", GetVersion()); + if (packet) { + world.AddLottoPlayer(GetCharacterID(), Timer::GetCurrentTime2() + 4500); + int32 rolls[6] = { 0 }; + int32 lottery_digits[6] = { 0 }; + int8 num_matches = 0; + int64 jackpot = 0; + Item* item = GetPlayer()->item_list.GetItemFromID(ticket_item_id); + if (!item) { + return; + } + database.DeleteItem(GetCharacterID(), item, 0); + GetPlayer()->item_list.RemoveItem(item, true); + QueuePacket(GetPlayer()->SendInventoryUpdate(GetVersion())); + Variable* winning_numbers = variables.FindVariable("gambling_winning_numbers"); + if (!winning_numbers) { + winning_numbers = new Variable("gambling_winning_numbers", "231205182236", "Current Gigglegibber Gambling Game winning numbers"); + variables.AddVariable(winning_numbers); + database.SaveVariable(winning_numbers->GetName(), winning_numbers->GetValue(), winning_numbers->GetComment()); + } + if (strlen(winning_numbers->GetValue()) != 12) { + winning_numbers->SetValue("231205182236"); + database.SaveVariable(winning_numbers->GetName(), winning_numbers->GetValue(), winning_numbers->GetComment()); + } + try { + for (int32 i = 0; i < 12; i += 2) { + char num[4]; + strncpy(num, winning_numbers->GetValue() + i, 2); + lottery_digits[i / 2] = atoi(num); + } + } + catch (...) { + LogWrite(WORLD__ERROR, 0, "World", "Error parsing 'gambling_winning_numbers' variable"); + + return; + } + Variable* jackpot_var = variables.FindVariable("gambling_current_jackpot"); + if (!jackpot_var) { + jackpot_var = new Variable("gambling_current_jackpot", "10000", "Current Gigglegibber Gambling Game Jackpot"); + variables.AddVariable(jackpot_var); + database.SaveVariable(jackpot_var->GetName(), jackpot_var->GetValue(), jackpot_var->GetComment()); + } + try { + jackpot = atoul(jackpot_var->GetValue()); + if (jackpot < 10000) + jackpot = 10000; + } + catch (...) { + jackpot = 10000; + } + char new_jackpot[128] = { 0 }; + sprintf(new_jackpot, "%llu", jackpot + price); + jackpot_var->SetValue(new_jackpot); + database.SaveVariable(jackpot_var->GetName(), jackpot_var->GetValue(), jackpot_var->GetComment()); + world.PickRandomLottoDigits(rolls); + packet->setDataByName("roll_digit1", rolls[0]); + packet->setDataByName("roll_digit2", rolls[1]); + packet->setDataByName("roll_digit3", rolls[2]); + packet->setDataByName("roll_digit4", rolls[3]); + packet->setDataByName("roll_digit5", rolls[4]); + packet->setDataByName("roll_digit6", rolls[5]); + packet->setDataByName("lottery_digit1", lottery_digits[0]); + packet->setDataByName("lottery_digit2", lottery_digits[1]); + packet->setDataByName("lottery_digit3", lottery_digits[2]); + packet->setDataByName("lottery_digit4", lottery_digits[3]); + packet->setDataByName("lottery_digit5", lottery_digits[4]); + packet->setDataByName("lottery_digit6", lottery_digits[5]); + QueuePacket(packet->serialize()); + safe_delete(packet); + for (int32 i = 0; i < 6; i++) { + for (int32 j = 0; j < 6; j++) { + if (rolls[i] == lottery_digits[j]) { + num_matches++; + break; + } + } + } + char new_jackpot_str[16]; + memset(new_jackpot_str, 0, sizeof(new_jackpot_str)); + world.SetLottoPlayerNumMatches(GetCharacterID(), num_matches); + if (num_matches == 6) { + world.PickRandomLottoDigits(lottery_digits); + for (int32 i = 0; i < 12; i += 2) + sprintf(new_jackpot_str + i, "%02d", lottery_digits[i / 2]); + winning_numbers->SetValue(new_jackpot_str); + jackpot_var->SetValue("10000"); + database.SaveVariable(winning_numbers->GetName(), winning_numbers->GetValue(), winning_numbers->GetComment()); + database.SaveVariable(jackpot_var->GetName(), jackpot_var->GetValue(), jackpot_var->GetComment()); + } + } + +} + +void Client::SendGuildCreateWindow() { + if (GetVersion() <= 561) { + SimpleMessage(0, "Not implemented on this client...yet?"); + } + else { + Spawn* spawn = GetPlayer()->GetTarget(); + if (spawn) { + PacketStruct* packet = configReader.getStruct("WS_UpdateMerchant", GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", player->GetIDWithPlayerSpawn(spawn)); + packet->setArrayLengthByName("num_items", 0); + packet->setDataByName("type", 0x00008000); + QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + } +} + +void Client::AddBuyBack(int32 unique_id, int32 item_id, int16 quantity, int32 price, bool save_needed) { + BuyBackItem* item = new BuyBackItem; + item->item_id = item_id; + item->unique_id = unique_id; + item->price = price; + item->quantity = quantity; + item->save_needed = save_needed; + MBuyBack.writelock(__FUNCTION__, __LINE__); + buy_back_items.push_back(item); + if (buy_back_items.size() > 10) { + safe_delete(buy_back_items.front()); + buy_back_items.pop_front(); + } + MBuyBack.releasewritelock(__FUNCTION__, __LINE__); + +} + +deque* Client::GetBuyBacks() { + return &buy_back_items; +} + +vector* Client::GetRepairableItems() { + vector* repairable_items = new vector; + vector* equipped_items = player->GetEquipmentList()->GetAllEquippedItems(); + map* items = player->GetItemList(); + if (equipped_items && equipped_items->size() > 0) { + for (int32 i = 0; i < equipped_items->size(); i++) { + Item* item = equipped_items->at(i); + if (item && !item->CheckFlag2(NO_REPAIR) && item->generic_info.condition < 100) + repairable_items->push_back(item); + } + } + if (items && items->size() > 0) { + map::iterator itr; + for (itr = items->begin(); itr != items->end(); itr++) { + Item* item = itr->second; + if (item && !item->CheckFlag2(NO_REPAIR) && item->generic_info.condition < 100) + repairable_items->push_back(item); + } + } + safe_delete(equipped_items); + safe_delete(items); + + return repairable_items; +} + + +vector* Client::GetItemsByEffectType(ItemEffectType type, ItemEffectType type2) { + if(type == NO_EFFECT_TYPE) + return nullptr; + + vector* return_items = new vector; + vector* equipped_items = player->GetEquipmentList()->GetAllEquippedItems(); + map* items = player->GetItemList(); + if (equipped_items && equipped_items->size() > 0) { + for (int32 i = 0; i < equipped_items->size(); i++) { + Item* item = equipped_items->at(i); + if (item && (item->effect_type == type || (type2 != NO_EFFECT_TYPE && item->effect_type == type2))) + return_items->push_back(item); + } + } + if (items && items->size() > 0) { + map::iterator itr; + for (itr = items->begin(); itr != items->end(); itr++) { + Item* item = itr->second; + if (item && (item->effect_type == type || (type2 != NO_EFFECT_TYPE && item->effect_type == type2))) + return_items->push_back(item); + } + } + safe_delete(equipped_items); + safe_delete(items); + + return return_items; +} + +void Client::SendMailList() { + int32 kiosk_id = player->GetIDWithPlayerSpawn(GetMailTransaction()); + if (kiosk_id > 0) { + PacketStruct* p = configReader.getStruct("WS_GetMailHeader", GetVersion()); + if (p) { + MutexMap* mail_list = player->GetMail(); + MutexMap::iterator itr = mail_list->begin(); + int32 i = 0; + p->setDataByName("kiosk_id", kiosk_id); + p->setArrayLengthByName("num_messages", (int16)mail_list->size()); + while (itr.Next()) { + Mail* mail = itr->second; + p->setArrayDataByName("mail_id", mail->mail_id, i); + p->setArrayDataByName("player_to_id", mail->player_to_id, i); + p->setArrayDataByName("player_from", mail->player_from.c_str(), i); + p->setArrayDataByName("subject", mail->subject.c_str(), i); + p->setArrayDataByName("already_read", mail->already_read, i); + if(mail->expire_time) + p->setArrayDataByName("mail_deletion", mail->expire_time - mail->time_sent, i); + else + p->setArrayDataByName("mail_deletion", 0, i); + + p->setArrayDataByName("mail_type", mail->mail_type, i); + p->setArrayDataByName("mail_expire", 0xFFFFFFFF, i); + p->setArrayDataByName("unknown1a", 0xFFFFFFFF, i); + p->setArrayDataByName("coin_copper", mail->coin_copper, i); + p->setArrayDataByName("coin_silver", mail->coin_silver, i); + p->setArrayDataByName("coin_gold", mail->coin_gold, i); + p->setArrayDataByName("coin_plat", mail->coin_plat, i); + + //p->setArrayDataByName("unknown2", 0, i); + + bool successItemAdd = false; + if(mail->stack || mail->char_item_id) + { + Item* item = master_item_list.GetItem(mail->char_item_id); + if(item) + { + item->stack_count = mail->stack > 1 ? mail->stack : 0; + if (version < 860) + p->setItemArrayDataByName("item", item, player, i, 0, GetClientItemPacketOffset()); + else if (version < 1193) + p->setItemArrayDataByName("item", item, player, i); + else + p->setItemArrayDataByName("item", item, player, i, 0, 2); + + successItemAdd = true; + } + } + + if(!successItemAdd) + { + p->setArrayDataByName("end_tag2", GetItemPacketType(GetVersion()), i); + p->setArrayDataByName("end_tag3", 0xFF, i); + } + i++; + } + + // GMs send mail for free! + if (GetAdminStatus() > 0) + { + p->setDataByName("postage_cost", 0); + p->setDataByName("attachment_cost", 0); + } + else + { + p->setDataByName("postage_cost", 10); + p->setDataByName("attachment_cost", 50); + } + p->setDataByName("unknown3", 0x01F4); + p->setDataByName("unknown4", 0x01000000); + EQ2Packet* pack = p->serialize(); + //DumpPacket(pack); + QueuePacket(pack); + safe_delete(p); + } + } + else + SimpleMessage(CHANNEL_NARRATIVE, "You are currently not in a mail transaction."); + +} + +void Client::DisplayMailMessage(int32 mail_id) { + Mail* mail = player->GetMail(mail_id); + if (mail) { + int32 kiosk_id = player->GetIDWithPlayerSpawn(GetMailTransaction()); + if (kiosk_id > 0) { + PacketStruct* update = configReader.getStruct("WS_UpdatePlayerMail", GetVersion()); + if (update) { + update->setDataByName("action", 0x03); + update->setDataByName("packettype", GetItemPacketType(GetVersion())); + update->setDataByName("packetsubtype", 0xFF); + QueuePacket(update->serialize()); + safe_delete(update); + } + if(!mail->already_read) { + mail->already_read = true; + SendMailList(); + } + PacketStruct* packet = configReader.getStruct("WS_MailGetMessage", GetVersion()); + if (packet) { + packet->setDataByName("kiosk_id", kiosk_id); + packet->setDataByName("mail_id", mail->mail_id); + packet->setDataByName("player_to_id", mail->player_to_id); + packet->setDataByName("player_from", mail->player_from.c_str()); + packet->setDataByName("subject", mail->subject.c_str()); + packet->setDataByName("mail_body", mail->mail_body.c_str()); + packet->setDataByName("unknown1", 1); + packet->setDataByName("unknown2", 0); + packet->setDataByName("lock_report_button", 1); + packet->setDataByName("unknown3", 0xFFFFFFFF); + packet->setDataByName("unknown3a", 0xFFFFFFFF); + packet->setDataByName("coin_copper", mail->coin_copper); + packet->setDataByName("coin_silver", mail->coin_silver); + packet->setDataByName("coin_gold", mail->coin_gold); + packet->setDataByName("coin_plat", mail->coin_plat); + if(mail->stack || mail->char_item_id) + { + Item* item = master_item_list.GetItem(mail->char_item_id); + item->stack_count = mail->stack > 1 ? mail->stack : 0; + if (version < 860) + packet->setItemByName("item", item, player, 0, version <= 373 ? -2 : -1); + else if (version < 1193) + packet->setItemByName("item", item, player, 0, 0); + else + packet->setItemByName("item", item, player, 0, 2); + } + else + { + packet->setDataByName("end_tag2", GetItemPacketType(GetVersion())); + packet->setDataByName("end_tag3", 0xFF); + } + mail->save_needed = true; + EQ2Packet* pack = packet->serialize(); + QueuePacket(pack); + safe_delete(packet); + // trying to update this causes the window not to open + //SendMailList(); + } + } + else + SimpleMessage(CHANNEL_NARRATIVE, "You are currently not in a mail transaction."); + } + +} + +/* This is called when the client sends a mail message. This determines whether or not the mail can be sent and must send the reply + packet back to the client before the mail actually sent. */ +void Client::HandleSentMail(EQApplicationPacket* app) { + PacketStruct* packet = configReader.getStruct("WS_MailSendMessage", GetVersion()); + if (packet) { + if(packet->LoadPacketData(app->pBuffer, app->size)) { + string player_to = packet->getType_EQ2_16BitString_ByName("player_to").data; + PacketStruct* reply_packet = configReader.getStruct("WS_MailSendMessageReply", GetVersion()); + vector* ids = 0; + MMailWindowMutex.lock(); + if (reply_packet) { + int8 reply_type = MAIL_SEND_RESULT_UNKNOWN_ERROR; + if (player_to.length() == 0) + reply_type = MAIL_SEND_RESULT_EMPTY_TO_LIST; + else if (player_to.compare(string(GetPlayer()->GetName())) == 0) + reply_type = MAIL_SEND_RESULT_CANNOT_SEND_TO_SELF; + else if (GetAdminStatus() == 0 && !player->RemoveCoins(10)) + reply_type = MAIL_SEND_RESULT_NOT_ENOUGH_COIN; + else { + if (GetAdminStatus() > 200 && player_to.compare("") == 0) { + if (mail_window.char_item_id == 0 && (mail_window.coin_copper + mail_window.coin_silver + mail_window.coin_gold + mail_window.coin_plat) == 0) + ids = database.GetAllPlayerIDs(); + else + SimpleMessage(CHANNEL_NARRATIVE, "You may not mail gifts to multiple players."); + } + else { + ids = new vector; + ids->push_back(database.GetCharacterID(player_to.c_str())); + } + if (ids) { + for (int32 i = 0; i < ids->size(); i++) { + int32 player_to_id = ids->at(i); + if (player_to_id > 0) { + reply_type = MAIL_SEND_RESULT_SUCCESS; + Mail* mail = new Mail; + mail->mail_id = 0; + mail->player_to_id = player_to_id; + mail->player_from = string(GetPlayer()->GetName()); + mail->subject = packet->getType_EQ2_16BitString_ByName("subject").data; + mail->mail_body = packet->getType_EQ2_16BitString_ByName("mail_body").data; + mail->already_read = 0; + mail->mail_type = MAIL_TYPE_REGULAR; + mail->coin_copper = mail_window.coin_copper; + mail->coin_silver = mail_window.coin_silver; + mail->coin_gold = mail_window.coin_gold; + mail->coin_plat = mail_window.coin_plat; + mail->char_item_id = mail_window.char_item_id; + mail->stack = mail_window.stack; + + // GM's send mail for free! + if (GetAdminStatus() > 0) + { + mail->postage_cost = 0; + mail->attachment_cost = 0; + } + else + { + mail->postage_cost = 10; + mail->attachment_cost = 50; + } + mail->time_sent = Timer::GetUnixTimeStamp(); + mail->expire_time = mail->time_sent + 2592000; //30 days in seconds + + mail->save_needed = false; + database.SavePlayerMail(mail); + Client* to_client = zone_list.GetClientByCharID(player_to_id); + if (to_client) { + to_client->GetPlayer()->AddMail(mail); + to_client->SimpleMessage(CHANNEL_NARRATIVE, "You have unread mail in your mailbox."); + string popup_text = "You have unread mail!"; + to_client->SendPopupMessage(10, popup_text.c_str(), "", 3, 0xFF, 0xFF, 0xFF); + } + else { + // don't need the pointer the client doesn't exist currently + safe_delete(mail); + } + ResetSendMail(false, false); + } + else + reply_type = MAIL_SEND_RESULT_UNKNOWN_PLAYER; + } + } + } + string players_to = ""; + if (ids) { + for (int32 i = 0; i < ids->size(); i++) { + if (ids->at(i) != 0) + players_to.append(database.GetCharacterName(ids->at(i))); + if (i < (ids->size() - 1)) + players_to.append(", "); + } + } + reply_packet->setDataByName("player_to", players_to.c_str()); + reply_packet->setDataByName("reply_type", reply_type); + QueuePacket(reply_packet->serialize()); + safe_delete(reply_packet); + safe_delete(ids); + } + MMailWindowMutex.unlock(); + } + safe_delete(packet); + } + +} + +void Client::DeleteMail(int32 mail_id, bool from_database) { + + player->DeleteMail(mail_id, from_database); + +} +bool Client::AddMailItem(Item* item) +{ + if(item && (item->CheckFlag(LORE) || item->CheckFlag(STACK_LORE))) { + Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "Lore items cannot be mailed."); + return false; + } + + bool ret = false; + if (GetMailTransaction()) { + MMailWindowMutex.lock(); + if(mail_window.char_item_id == 0) + { + mail_window.item = item; + mail_window.char_item_id = item->details.item_id; + mail_window.stack = item->details.count; + ret = true; + PacketStruct* packet = configReader.getStruct("WS_UpdatePlayerMail", GetVersion()); + packet->setDataByName("coin_copper", mail_window.coin_copper); + packet->setDataByName("coin_silver", mail_window.coin_silver); + packet->setDataByName("coin_gold", mail_window.coin_gold); + packet->setDataByName("coin_plat", mail_window.coin_plat); + + if(item) + { + packet->setDataByName("stack", mail_window.stack); + item->stack_count = mail_window.stack; + if (version < 860) + packet->setItemByName("item", item, player, 0, version <= 373 ? -2 : -1); + else if (version < 1193) + packet->setItemByName("item", item, player, 0, 0); + else + packet->setItemByName("item", item, player, 0, 2); + } + else + { + packet->setDataByName("end_tag2", GetItemPacketType(GetVersion())); + packet->setDataByName("end_tag3", 0xFF); + } + QueuePacket(packet->serialize()); + } + MMailWindowMutex.unlock(); + } + return ret; +} +bool Client::AddMailCoin(int32 copper, int32 silver, int32 gold, int32 plat) { + + bool ret = false; + if (GetMailTransaction()) { + MMailWindowMutex.lock(); + PacketStruct* packet = configReader.getStruct("WS_UpdatePlayerMail", GetVersion()); + if (packet) { + if (copper > 0) { + if (player->RemoveCoins(copper)) { + mail_window.coin_copper += copper; + Message(CHANNEL_NARRATIVE, "You add %u copper to the mail window.", copper); + ret = true; + } + } + else if (silver > 0) { + if (player->RemoveCoins(silver * 100)) { + mail_window.coin_silver += silver; + Message(CHANNEL_NARRATIVE, "You add %u silver to the mail window.", silver); + ret = true; + } + } + else if (gold > 0) { + if (player->RemoveCoins(gold * 10000)) { + mail_window.coin_gold += gold; + Message(CHANNEL_NARRATIVE, "You add %u gold to the mail window.", gold); + ret = true; + } + } + else if (plat > 0) { + if (player->RemoveCoins(plat * 1000000)) { + mail_window.coin_plat += plat; + Message(CHANNEL_NARRATIVE, "You add %u platinum to the mail window.", plat); + ret = true; + } + } + if (ret) { + packet->setDataByName("coin_copper", mail_window.coin_copper); + packet->setDataByName("coin_silver", mail_window.coin_silver); + packet->setDataByName("coin_gold", mail_window.coin_gold); + packet->setDataByName("coin_plat", mail_window.coin_plat); + Item* item = master_item_list.GetItem(mail_window.char_item_id); + if(item) + { + packet->setDataByName("stack", mail_window.stack); + item->stack_count = mail_window.stack; + if (version < 860) + packet->setItemByName("item", item, player, 0, version <= 373 ? -2 : -1); + else if (version < 1193) + packet->setItemByName("item", item, player, 0, 0); + else + packet->setItemByName("item", item, player, 0, 2); + } + else + { + packet->setDataByName("end_tag2", GetItemPacketType(GetVersion())); + packet->setDataByName("end_tag3", 0xFF); + } + //packet->PrintPacket(); + QueuePacket(packet->serialize()); + } + else + SimpleMessage(CHANNEL_NARRATIVE, "You don't have that much money."); + safe_delete(packet); + } + MMailWindowMutex.unlock(); + } + else + SimpleMessage(CHANNEL_NARRATIVE, "You are currently not in a mail transaction."); + + return ret; +} + +bool Client::RemoveMailCoin(int32 copper, int32 silver, int32 gold, int32 plat) { + bool ret = false; + if (GetMailTransaction()) { + MMailWindowMutex.lock(); + PacketStruct* packet = configReader.getStruct("WS_UpdatePlayerMail", GetVersion()); + if (packet) { + if (copper > 0) { + player->AddCoins(copper); + mail_window.coin_copper -= copper; + Message(CHANNEL_NARRATIVE, "You remove %u copper from the mail window.", copper); + ret = true; + } + else if (silver > 0) { + player->AddCoins(silver * 100); + mail_window.coin_silver -= silver; + Message(CHANNEL_NARRATIVE, "You remove %u silver from the mail window.", silver); + ret = true; + } + else if (gold > 0) { + player->AddCoins(gold * 10000); + mail_window.coin_gold -= gold; + Message(CHANNEL_NARRATIVE, "You remove %u gold from the mail window.", gold); + ret = true; + } + else if (plat > 0) { + player->AddCoins(plat * 1000000); + mail_window.coin_plat -= plat; + Message(CHANNEL_NARRATIVE, "You remove %u platinum from the mail window.", plat); + ret = true; + } + if (ret) { + packet->setDataByName("coin_copper", mail_window.coin_copper); + packet->setDataByName("coin_silver", mail_window.coin_silver); + packet->setDataByName("coin_gold", mail_window.coin_gold); + packet->setDataByName("coin_plat", mail_window.coin_plat); + packet->setDataByName("stack", 0); + packet->setDataByName("packettype", 0x2BFE); + packet->setDataByName("packetsubtype", 0xFF); + packet->setDataByName("unknown2", 0); + QueuePacket(packet->serialize()); + } + safe_delete(packet); + } + MMailWindowMutex.unlock(); + } + else + SimpleMessage(CHANNEL_NARRATIVE, "You are currently not in a mail transaction."); + + return ret; +} + +void Client::TakeMailAttachments(int32 mail_id) { + if (GetMailTransaction()) { + Mail* mail = player->GetMail(mail_id); + if (mail) { + int64 amount = 0; + if (mail->coin_copper > 0) { + amount += mail->coin_copper; + mail->coin_copper = 0; + } + if (mail->coin_silver > 0) { + amount += mail->coin_silver * 100; + mail->coin_silver = 0; + } + if (mail->coin_gold > 0) { + amount += mail->coin_gold * 10000; + mail->coin_gold = 0; + } + if (mail->coin_plat > 0) { + amount += mail->coin_plat * 1000000; + mail->coin_plat = 0; + } + if (mail->char_item_id > 0) { + AddItem(mail->char_item_id, mail->stack); + mail->char_item_id = 0; + mail->stack = 0; + } + /* Can't find the right packet to send to update the player's mail. This packet below updates the mail the player is sending, not + the mail the player is getting attachments from. There is an opcode OP_MailRemoveAttachFromMailMsg with opcode 328 but i can't + find it in any packet logs.*/ + /*PacketStruct* packet = configReader.getStruct("WS_UpdatePlayerMail", GetVersion()); + if (packet) { + packet->setDataByName("unknown", 0x03); + packet->setDataByName("coin_copper", mail->coin_copper); + packet->setDataByName("coin_silver", mail->coin_silver); + packet->setDataByName("coin_gold", mail->coin_gold); + packet->setDataByName("coin_plat", mail->coin_plat); + packet->setDataByName("stack", 0); + packet->setDataByName("packettype", 0x2BFE); + packet->setDataByName("packetsubtype", 0xFF); + packet->setDataByName("unknown2", 0); + packet->setDataByName("unknown3", 0x00000001);//0x00000016 + DumpPacket(packet->serialize()); + QueuePacket(packet->serialize()); + safe_delete(packet); + }*/ + database.SavePlayerMail(mail); + if (amount > 0) + player->AddCoins(amount); + SendMailList(); + } + } + else + SimpleMessage(CHANNEL_NARRATIVE, "You are currently not in a mail transaction."); + +} + +void Client::ResetSendMail(bool cancel, bool needslock) { + if(cancel && mail_transaction) + SimpleMessage(CHANNEL_NARRATIVE, "You cancel sending a letter."); + if(needslock) + MMailWindowMutex.lock(); + if(cancel) + player->AddCoins(mail_window.coin_copper + (mail_window.coin_silver * 100) + (mail_window.coin_gold * 10000) + (mail_window.coin_plat * 1000000)); + if(!cancel) + mail_transaction = 0; + mail_window.coin_copper = 0; + mail_window.coin_silver = 0; + mail_window.coin_gold = 0; + mail_window.coin_plat = 0; + mail_window.char_item_id = 0; + mail_window.stack = 0; + + if(mail_window.item){ + if(cancel) + AddItem(mail_window.item); + else + safe_delete(mail_window.item); + } + mail_window.item = nullptr; + if(needslock) + MMailWindowMutex.unlock(); +} + +bool Client::GateAllowed() { + ZoneServer* zone = GetCurrentZone(); + if (zone){ + bool cangate = zone->GetCanGate(); + return cangate; + } + + return false; +} + +bool Client::BindAllowed() { + ZoneServer* zone = GetCurrentZone(); + if (zone){ + bool canbind = zone->GetCanBind(); + return canbind; + } + return false; +} + +bool Client::Bind() { + int canbind = BindAllowed(); + + if(canbind == 0) { + Message(CHANNEL_MERCHANT, "You cannot bind at this location."); + return false; + } + player->GetPlayerInfo()->SetBindZone(GetCurrentZone()->GetZoneID()); + player->GetPlayerInfo()->SetBindX(player->GetX()); + player->GetPlayerInfo()->SetBindY(player->GetY()); + player->GetPlayerInfo()->SetBindZ(player->GetZ()); + player->GetPlayerInfo()->SetBindHeading(player->GetHeading()); + Message(CHANNEL_MERCHANT, "Your spirit has been bound to this location."); + return true; +} + +bool Client::Gate(bool is_spell) { + if (player->GetPlayerInfo()->GetBindZoneID() == 0) { + SimpleMessage(CHANNEL_MERCHANT, "You can not cast recall spells. You have no bind location set."); + return false; + } + + ZoneServer* zone = zone_list.Get(player->GetPlayerInfo()->GetBindZoneID()); + if (zone) { + int cangate = GateAllowed(); + if(cangate == 0) { + SimpleMessage(CHANNEL_MERCHANT, "You can not cast recall spells in this zone."); + return false; + } + player->SetX(player->GetPlayerInfo()->GetBindZoneX()); + player->SetY(player->GetPlayerInfo()->GetBindZoneY()); + player->SetZ(player->GetPlayerInfo()->GetBindZoneZ()); + player->SetHeading(player->GetPlayerInfo()->GetBindZoneHeading()); + Zone(zone, false, is_spell); + + return true; + } + + return false; +} + +void Client::ProcessTeleport(Spawn* spawn, vector* destinations, int32 transport_id, bool is_spell) { + if (!destinations || !spawn) { + return; + } + + bool has_map = false; + if (transport_id > 0) + has_map = GetCurrentZone()->TransportHasMap(transport_id); + + transport_spawn = spawn; + vector transport_list; + vector::iterator itr; + TransportDestination* destination = 0; + for (itr = destinations->begin(); itr != destinations->end(); itr++) { + destination = *itr; + if (has_map || (destination->type == TRANSPORT_TYPE_ZONE && ((destination->destination_zone_id != GetCurrentZone()->GetZoneID()) || GetPlayer()->GetDistance(destination->destination_x, destination->destination_y, destination->destination_z) > 100))) + transport_list.push_back(destination); + } + if (transport_list.size() == 0 && destination) { + if (destination->destination_zone_id == 0 || destination->destination_zone_id == GetCurrentZone()->GetZoneID()) { + + if (destination->type == TRANSPORT_TYPE_FLIGHT) + SendFlightAutoMount(destination->flight_path_id, destination->mount_id, destination->mount_red_color, destination->mount_green_color, destination->mount_blue_color); + else + { + EQ2Packet* app = GetPlayer()->Move(destination->destination_x, destination->destination_y, destination->destination_z, GetVersion()); + if (app) + QueuePacket(app); + } + } + else { + ZoneServer* new_zone = zone_list.Get(destination->destination_zone_id); + + // determine if this is an instanced zone that already exists + ZoneServer* instance_zone = GetPlayer()->GetGroupMemberInZone(destination->destination_zone_id); + + if (instance_zone || new_zone) { + GetPlayer()->SetX(destination->destination_x); + GetPlayer()->SetY(destination->destination_y); + GetPlayer()->SetZ(destination->destination_z); + GetPlayer()->SetHeading(destination->destination_heading); + if (instance_zone) + Zone(instance_zone->GetInstanceID(), false, true, is_spell); + else + Zone(new_zone, false, is_spell); + } + } + if (destination->message.length() > 0) + SimpleMessage(CHANNEL_COLOR_YELLOW, destination->message.c_str()); + } + else if (transport_list.size() > 0) { + if (!spawn->IsSoundsDisabled()) + PlaySound("mariner_bell"); + + PacketStruct* packet = configReader.getStruct("WS_TeleportList", GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", GetPlayer()->GetIDWithPlayerSpawn(spawn)); + + // Put all the destinations the player can go in a new vector + vector destinations; + for (int32 i = 0; i < transport_list.size(); i++) { + destination = transport_list.at(i); + + // Check min level + if (destination->min_level > 0 && GetPlayer()->GetLevel() < destination->min_level) + continue; + // Check max level + if (destination->max_level > 0 && GetPlayer()->GetLevel() > destination->max_level) + continue; + // Check quest complete + if (destination->req_quest_complete > 0 && GetPlayer()->HasQuestBeenCompleted(destination->req_quest_complete) == 0) + continue; + // Check req quest and step + if (destination->req_quest > 0 && destination->req_quest_step > 0 && GetPlayer()->GetQuestStep(destination->req_quest) != destination->req_quest_step) + continue; + // If we have a map and our current location is the same as the detination and player is within 20 units from the transport set the "current" elements but don't addt to the destination list + if (has_map && (destination->destination_zone_id == GetCurrentZone()->GetZoneID() && GetPlayer()->GetDistance(destination->destination_x, destination->destination_y, destination->destination_z) < 20)) { + packet->setDataByName("current_zone", destination->display_name.c_str()); + packet->setDataByName("current_map_x", destination->map_x); + packet->setDataByName("current_map_y", destination->map_y); + } + else { + destinations.push_back(destination); + } + } + + // Use the new vector to create the packet + destination = 0; + packet->setArrayLengthByName("num_destinations", destinations.size()); + for (int32 i = 0; i < destinations.size(); i++) { + destination = destinations.at(i); + + packet->setArrayDataByName("unique_id", destination->unique_id, i); + packet->setArrayDataByName("display_name", destination->display_name.c_str(), i); + packet->setArrayDataByName("zone_name", destination->display_name.c_str(), i); + packet->setArrayDataByName("zone_file_name", destination->display_name.c_str(), i); + packet->setArrayDataByName("cost", destination->cost, i); + + if (has_map) { + packet->setArrayDataByName("map_x", destination->map_x, i); + packet->setArrayDataByName("map_y", destination->map_y, i); + } + + } + + if (has_map) + packet->setDataByName("map_name", GetCurrentZone()->GetTransportMap(transport_id).c_str()); + EQ2Packet* app = packet->serialize(); + //DumpPacket(app); + if (destinations.size() > 0) + QueuePacket(app); + safe_delete(packet); + } + } + +} + +void Client::ProcessTeleportLocation(EQApplicationPacket* app) { + PacketStruct* packet = configReader.getStruct("WS_TeleportDestination", GetVersion()); + if (packet) { + if (packet->LoadPacketData(app->pBuffer, app->size)) { + Spawn* spawn = GetPlayer()->GetSpawnWithPlayerID(packet->getType_int32_ByName("spawn_id")); + int32 unique_id = packet->getType_int32_ByName("unique_id"); + string zone_name = packet->getType_EQ2_16BitString_ByName("zone_name").data; + int32 cost = packet->getType_int32_ByName("cost"); + vector destinations; + TransportDestination* destination = 0; + if (this->GetTemporaryTransportID() || (spawn && spawn == transport_spawn && spawn->GetTransporterID())) + GetCurrentZone()->GetTransporters(&destinations, this, this->GetTemporaryTransportID() ? this->GetTemporaryTransportID() : spawn->GetTransporterID()); + vector::iterator itr; + for (itr = destinations.begin(); itr != destinations.end(); itr++) { + if ((*itr)->unique_id == unique_id && (*itr)->display_name == zone_name && (*itr)->cost == cost) { + destination = *itr; + break; + } + } + + SetTemporaryTransportID(0); + + if (!destination) + SimpleMessage(CHANNEL_COLOR_RED, "Error processing transport."); + else { + if (cost == 0 || player->RemoveCoins(cost)) { + if (destination->destination_zone_id == 0 || destination->destination_zone_id == GetCurrentZone()->GetZoneID()) { + + if (destination->type == TRANSPORT_TYPE_FLIGHT) + SendFlightAutoMount(destination->flight_path_id, destination->mount_id, destination->mount_red_color, destination->mount_green_color, destination->mount_blue_color); + else + { + EQ2Packet* outapp = GetPlayer()->Move(destination->destination_x, destination->destination_y, destination->destination_z, GetVersion()); + if (outapp) + QueuePacket(outapp); + } + } + else { + GetPlayer()->SetX(destination->destination_x); + GetPlayer()->SetY(destination->destination_y); + GetPlayer()->SetZ(destination->destination_z); + GetPlayer()->SetHeading(destination->destination_heading); + + // Test if where we're going is an Instanced zone + if (!TryZoneInstance(destination->destination_zone_id, false)) { + LogWrite(INSTANCE__DEBUG, 0, "Instance", "Attempting to zone normally"); + ZoneServer* new_zone = zone_list.Get(destination->destination_zone_id); + Zone(new_zone, false); + } + } + if (destination->message.length() > 0) + SimpleMessage(CHANNEL_COLOR_YELLOW, destination->message.c_str()); + } + else + SimpleMessage(CHANNEL_COLOR_RED, "You do not have enough money to use this transport."); + } + } + safe_delete(packet); + } + +} + +void Client::SendNewSpells(int8 class_id) { + if (class_id > 0) { + vector* spells = master_spell_list.GetSpellListByAdventureClass(class_id, player->GetLevel(), 1); + AddSendNewSpells(spells); + safe_delete(spells); + } + +} + +void Client::SendNewTSSpells(int8 class_id) { + if (class_id > 0) { + vector* spells = master_spell_list.GetSpellListByTradeskillClass(class_id, player->GetLevel(), 1); + AddSendNewSpells(spells); + safe_delete(spells); + } +} + +void Client::AddSendNewSpells(vector* spells) { + Spell* spell = 0; + bool send_updates = false; + vector::iterator itr; + for (itr = spells->begin(); itr != spells->end(); itr++) { + spell = *itr; + if (spell && !player->HasSpell(spell->GetSpellID(), spell->GetSpellTier(), true) && spell->GetSpellData()->lua_script.length() > 0) { + send_updates = true; + SendSpellUpdate(spell); + player->AddSpellBookEntry(spell->GetSpellID(), spell->GetSpellTier(), player->GetFreeSpellBookSlot(spell->GetSpellData()->spell_book_type), spell->GetSpellData()->spell_book_type, spell->GetSpellData()->linked_timer, true); + player->UnlockSpell(spell); + } + } + if (send_updates) { + EQ2Packet* outapp = player->GetSpellBookUpdatePacket(GetVersion()); + if (outapp) + QueuePacket(outapp); + } +} + +void Client::SetItemSearch(vector* items) { + if (items) { + safe_delete(search_items); + search_items = items; + } + +} + +vector* Client::GetSearchItems() { + return search_items; +} + +void Client::SearchStore(int32 page) { + if (search_items) { + PacketStruct* packet = configReader.getStruct("WS_BrokerItems", GetVersion()); + if (packet) { + int32 x = page * 8; + if (search_items->size() > 8) { + if ((search_items->size() - x) > 8) + packet->setArrayLengthByName("num_items", 8); + else + packet->setArrayLengthByName("num_items", search_items->size() - x); + } + else + packet->setArrayLengthByName("num_items", search_items->size()); + if (search_items->size() > 0) { + packet->setArrayLengthByName("num_sellers", 1); + packet->setArrayDataByName("seller_seller_id", 1); + packet->setDataByName("per_page", 8); + packet->setDataByName("num_pages", search_items->size() / 8 + 1); + packet->setDataByName("page", page); + Item* item = 0; + int32 limit = search_items->size() > 8 ? 8 : search_items->size(); + for (int32 i = 0; i < limit; i++, x++) { + if (x >= search_items->size()) + break; + + item = search_items->at(x); + std::string teststr("test "); + teststr.append(std::to_string(i)); + packet->setArrayDataByName("string_one", teststr.c_str(), i); + packet->setArrayDataByName("string_two", "testtwo", i); + packet->setArrayDataByName("seller_name", "EQ2EMuDev", i); + packet->setArrayDataByName("item_id", item->details.item_id, i); + packet->setArrayDataByName("item_id2", item->details.item_id, i); + packet->setArrayDataByName("icon", item->GetIcon(GetVersion()), i); + //packet->setArrayDataByName("unknown2b", i, i); + packet->setArrayDataByName("item_seller_id", 1, i); + if (item->stack_count == 0) + packet->setArrayDataByName("quantity", 1, i); + else + packet->setArrayDataByName("quantity", item->stack_count, i); + packet->setArrayDataByName("stack_size", item->stack_count, i); + + packet->setArrayDataByName("sell_price", item->sell_price, i); + + + std::string tmpStr(""); + tmpStr.append(item->name.c_str()); + tmpStr.append(" ("); + tmpStr.append(std::to_string(item->details.item_id)); + tmpStr.append(")"); + + packet->setArrayDataByName("item_name", tmpStr.c_str(), i); + packet->setArrayDataByName("req_level", item->generic_info.adventure_default_level, i); + //QueuePacket(item->serialize(GetVersion(), false, GetPlayer())); + } + } + EQ2Packet* outapp = packet->serialize(); + DumpPacket(outapp); + QueuePacket(outapp); + safe_delete(packet); + } + } + +} + +void Client::SetReadyForUpdates() { + if (!ready_for_updates) { + database.loadCharacterProperties(this); + SendDefaultGroupOptions(); + } + + ready_for_updates = true; + + if(GetVersion() <= 561) { + SendRecipeList(); + } +} + +void Client::SetReadyForSpawns(bool val) { + ready_for_spawns = val; + if (GetPlayer()->GetActivityStatus() > 0) { + GetPlayer()->SetActivityStatus(0); + if (GetPlayer()->GetGroupMemberInfo()) { + world.GetGroupManager()->GroupMessage(GetPlayer()->GetGroupMemberInfo()->group_id, "%s has returned from Linkdead.", GetPlayer()->GetName()); + } + } + GetPlayer()->SetActiveReward(false); + zone_list.CheckFriendZoned(this); + +} + +void Client::SendChatRelationship(int8 type, const char* name) { + if (!name) { + return; + } + PacketStruct* packet = configReader.getStruct("WS_ChatRelationship", GetVersion()); + if (packet) { + packet->setDataByName("account_id", GetAccountID()); + packet->setDataByName("type", type); + packet->setArrayLengthByName("num_names", 1); + packet->setArrayDataByName("name", name); + if (type == 0) { + Client* client = zone_list.GetClientByCharName(name); + if (client) { + packet->setArrayDataByName("location", client->GetCurrentZone()->GetZoneName()); + packet->setArrayDataByName("class_name", classes.GetClassName(client->GetPlayer()->GetAdventureClass())); + } + } + QueuePacket(packet->serialize()); + safe_delete(packet); + } + +} + +void Client::SendFriendList() { + PacketStruct* packet = configReader.getStruct("WS_ChatRelationship", GetVersion()); + if (packet) { + packet->setDataByName("account_id", GetAccountID()); + map::iterator itr; + map* friends = player->GetFriends(); + if (friends && friends->size() > 0) { + Client* client = 0; + vector names; + for (itr = friends->begin(); itr != friends->end(); itr++) { + if (itr->second == 2) + continue; + names.push_back(itr->first); + } + packet->setArrayLengthByName("num_names", names.size()); + for (int32 i = 0; i < names.size(); i++) { + client = zone_list.GetClientByCharName(names[i]); + packet->setArrayDataByName("name", names[i].c_str(), i); + if (client) { + packet->setArrayDataByName("location", client->GetCurrentZone()->GetZoneName(), i); + packet->setArrayDataByName("class_name", classes.GetClassName(client->GetPlayer()->GetAdventureClass()), i); + } + } + } + QueuePacket(packet->serialize()); + safe_delete(packet); + } + +} + +void Client::SendIgnoreList() { + PacketStruct* packet = configReader.getStruct("WS_ChatRelationship", GetVersion()); + if (packet) { + packet->setDataByName("account_id", GetAccountID()); + packet->setDataByName("type", 2); + map::iterator itr; + map* ignored = player->GetIgnoredPlayers(); + if (ignored && ignored->size() > 0) { + vector names; + for (itr = ignored->begin(); itr != ignored->end(); itr++) { + if (itr->second == 2) + continue; + names.push_back(itr->first); + } + packet->setArrayLengthByName("num_names", names.size()); + for (int32 i = 0; i < names.size(); i++) + packet->setArrayDataByName("name", names[i].c_str(), i); + } + QueuePacket(packet->serialize()); + safe_delete(packet); + } + +} + +void Client::AddWaypoint(string name, int8 type) { + waypoint_id++; + WaypointInfo info; + info.id = waypoint_id; + info.type = type; + waypoints[name] = info; +} + +void Client::SendWaypoints() { + PacketStruct* packet = configReader.getStruct("WS_WaypointUpdate", GetVersion()); + if (packet) { + packet->setArrayLengthByName("num_updates", waypoints.size()); + map::iterator itr; + int16 i = 0; + for (itr = waypoints.begin(); itr != waypoints.end(); itr++) { + packet->setArrayDataByName("waypoint_name", itr->first.c_str(), i); + packet->setArrayDataByName("waypoint_category", itr->second.type, i); + packet->setArrayDataByName("spawn_id", itr->second.id, i); + i++; + } + packet->setDataByName("unknown", 0xFFFFFFFF); + QueuePacket(packet->serialize()); + safe_delete(packet); + } +} + +void Client::SelectWaypoint(int32 id) { + string found_name = ""; + map::iterator itr; + for (itr = waypoints.begin(); itr != waypoints.end(); itr++) { + if (itr->second.id == id) { + found_name = itr->first; + break; + } + } + if (found_name.length() > 0) { + Spawn* spawn = current_zone->FindSpawn(player, found_name.c_str()); + ShowPathToTarget(spawn); + } +} + +void Client::AddWaypoint(const char* waypoint_name, int8 waypoint_category, int32 spawn_id) { + if (waypoint_name) { + PacketStruct* packet = configReader.getStruct("WS_WaypointUpdate", GetVersion()); + if (packet) { + packet->setArrayLengthByName("num_updates", 1); + packet->setArrayDataByName("waypoint_name", waypoint_name, 0); + packet->setArrayDataByName("waypoint_category", waypoint_category, 0); + packet->setArrayDataByName("spawn_id", spawn_id, 0); + packet->setArrayDataByName("waypoint_category2", waypoint_category, 0); + packet->setArrayDataByName("spawn_id2", spawn_id, 0); + packet->setDataByName("unknown", 0xFFFFFFFF); + QueuePacket(packet->serialize()); + safe_delete(packet); + } + } +} + +void Client::ClearWaypoint() { + PacketStruct* packet = configReader.getStruct("WS_GlowPath", GetVersion()); + if (packet) { + QueuePacket(packet->serialize()); + safe_delete(packet); + } +} + +bool Client::ShowPathToTarget(float x, float y, float z, float y_offset) { + if (current_zone->pathing) { + if (GetPlayer()->GetMap()) { + if (x < GetPlayer()->GetMap()->GetMinX() || x > GetPlayer()->GetMap()->GetMaxX()) + return false; + if (z < GetPlayer()->GetMap()->GetMinZ() || z > GetPlayer()->GetMap()->GetMaxZ()) + return false; + auto loc = glm::vec3(x, z, y); + float new_z = GetPlayer()->FindBestZ(loc, nullptr); + if (new_z != BEST_Z_INVALID) //this is actually y + y = new_z; + } + bool partial = false; + bool stuck = false; + PathfinderOptions opts; + opts.smooth_path = true; + opts.step_size = 100.0f;//RuleR(Pathing, NavmeshStepSize); + opts.offset = y_offset + 1.0f; + opts.flags = PathingNotDisabled ^ PathingZoneLine; + PacketStruct* packet = configReader.getStruct("WS_GlowPath", GetVersion()); + if (packet) { + auto path = current_zone->pathing->FindPath(glm::vec3(player->GetX(), player->GetZ(), player->GetY()), glm::vec3(x, z, y), partial, stuck, opts); + packet->setArrayLengthByName("num_points", path.size()); + int i = 0; + for (auto& node : path) + { + packet->setArrayDataByName("x", node.pos.x, i); + packet->setArrayDataByName("y", node.pos.z, i); + packet->setArrayDataByName("z", node.pos.y, i); + packet->setDataByName("waypoint_x", x); + packet->setDataByName("waypoint_y", y); + packet->setDataByName("waypoint_z", z); + i++; + } + if (i > 0) + QueuePacket(packet->serialize()); + safe_delete(packet); + return (i > 0); + } + } + return false; +} + +bool Client::ShowPathToTarget(Spawn* spawn) { + if (spawn) { + return ShowPathToTarget(spawn->GetX(), spawn->GetY(), spawn->GetZ(), spawn->GetYOffset()); + } + return false; +} + +void Client::BeginWaypoint(const char* waypoint_name, float x, float y, float z) { + if (waypoint_name) { + PacketStruct* packet = configReader.getStruct("WS_GlowPath", GetVersion()); + if (packet) { + packet->setArrayLengthByName("num_points", 1); + packet->setArrayDataByName("x", x, 0); + packet->setArrayDataByName("y", y, 0); + packet->setArrayDataByName("z", z, 0); + packet->setDataByName("waypoint_x", x); + packet->setDataByName("waypoint_y", y); + packet->setDataByName("waypoint_z", z); + packet->setDataByName("waypoint_name", waypoint_name); + packet->setDataByName("unknown", 0); + QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + +} + +void Client::InspectPlayer(Player* player_to_inspect) { + int source_pvp_alignment = GetPlayer()->GetPVPAlignment(); + int target_pvp_alignment = player_to_inspect->GetPVPAlignment(); + bool pvp_allowed = rule_manager.GetGlobalRule(R_PVP, AllowPVP)->GetBool(); + + if(pvp_allowed == true){ + if(source_pvp_alignment != target_pvp_alignment){ + Message(CHANNEL_COLOR_RED, "You can not inspect players of different alignments."); + return; + } + } + + if (player_to_inspect && player_to_inspect->GetClient()) { + PacketStruct* packet = configReader.getStruct("WS_InspectPlayer", GetVersion()); + if (packet) { + packet->setDataByName("unknown", 0); + packet->setSmallStringByName("name", player_to_inspect->GetName()); + packet->setDataByName("race", player_to_inspect->GetRace()); + packet->setDataByName("gender", player_to_inspect->GetGender()); + packet->setDataByName("adventure_level", player_to_inspect->GetLevel()); + + int16 effective_level = player_to_inspect->GetInfoStruct()->get_effective_level() != 0 ? player_to_inspect->GetInfoStruct()->get_effective_level() : player_to_inspect->GetLevel(); + packet->setDataByName("adventure_level_effective", effective_level); + packet->setDataByName("adventure_class", player_to_inspect->GetAdventureClass()); + packet->setDataByName("tradeskill_level", player_to_inspect->GetTSLevel()); + packet->setDataByName("tradeskill_class", player_to_inspect->GetTradeskillClass()); + packet->setDataByName("health", player_to_inspect->GetHP()); + packet->setDataByName("health_max", player_to_inspect->GetTotalHP()); + packet->setDataByName("health_base", player_to_inspect->GetTotalHPBase()); + packet->setDataByName("power", player_to_inspect->GetPower()); + packet->setDataByName("power_max", player_to_inspect->GetTotalPower()); + packet->setDataByName("power_base", player_to_inspect->GetTotalPowerBase()); + packet->setDataByName("mitigation", player_to_inspect->GetInfoStruct()->get_cur_mitigation()); + packet->setDataByName("unknown1", 0); + packet->setDataByName("avoidance", player_to_inspect->GetInfoStruct()->get_avoidance_display()*10.0f); + packet->setDataByName("unknown2", 0); + packet->setDataByName("mitigation_percentage", 0); + packet->setDataByName("strength", player_to_inspect->GetStr()); + packet->setDataByName("strength_base", player_to_inspect->GetStrBase()); + packet->setDataByName("stamina", player_to_inspect->GetSta()); + packet->setDataByName("stamina_base", player_to_inspect->GetStaBase()); + packet->setDataByName("agility", player_to_inspect->GetAgi()); + packet->setDataByName("agility_base", player_to_inspect->GetAgiBase()); + packet->setDataByName("wisdom", player_to_inspect->GetWis()); + packet->setDataByName("wisdom_base", player_to_inspect->GetWisBase()); + packet->setDataByName("intelligence", player_to_inspect->GetInt()); + packet->setDataByName("intelligence_base", player_to_inspect->GetIntBase()); + packet->setDataByName("unknown4", 0); + packet->setDataByName("unknown5", 0); + packet->setDataByName("unknown6", 0); + packet->setDataByName("unknown7", 0); + packet->setDataByName("unknown8", 0); + packet->setDataByName("unknown9", 0); + packet->setDataByName("unknown10", 0); + packet->setDataByName("unknown11", 0); + packet->setDataByName("unknown12", 0); + packet->setDataByName("heat_resist", player_to_inspect->GetHeatResistance()); + packet->setDataByName("heat_resist_base", player_to_inspect->GetHeatResistanceBase()); + packet->setDataByName("heat_resist_percentage", 0); + packet->setDataByName("cold_resist", player_to_inspect->GetColdResistance()); + packet->setDataByName("cold_resist_base", player_to_inspect->GetColdResistanceBase()); + packet->setDataByName("cold_resist_percentage", 0); + packet->setDataByName("magic_resist", player_to_inspect->GetMagicResistance()); + packet->setDataByName("magic_resist_base", player_to_inspect->GetMagicResistanceBase()); + packet->setDataByName("magic_resist_percentage", 0); + packet->setDataByName("mental_resist", player_to_inspect->GetMentalResistance()); + packet->setDataByName("mental_resist_base", player_to_inspect->GetMentalResistanceBase()); + packet->setDataByName("mental_resist_percentage", 0); + packet->setDataByName("divine_resist", player_to_inspect->GetDivineResistance()); + packet->setDataByName("divine_resist_base", player_to_inspect->GetDivineResistanceBase()); + packet->setDataByName("divine_resist_percentage", 0); + packet->setDataByName("disease_resist", player_to_inspect->GetDiseaseResistance()); + packet->setDataByName("disease_resist_base", player_to_inspect->GetDiseaseResistanceBase()); + packet->setDataByName("disease_resist_percentage", 0); + packet->setDataByName("poison_resist", player_to_inspect->GetPoisonResistance()); + packet->setDataByName("poison_resist_base", player_to_inspect->GetPoisonResistanceBase()); + packet->setDataByName("poison_resist_percentage", 0); + packet->setArrayLengthByName("num_chars", 0x01FF); + string biography = player_to_inspect->GetBiography(); + for (size_t i = 0; i < biography.length(); i++) + packet->setArrayDataByName("biography_char", biography[i], i); + + if(GetVersion() <= 373) { + for(int32 s=0;s<20;s++) { + int32 slot = s; + + char item_slot_name[64], item_slot_name_appearance[64]; + _snprintf(item_slot_name,64,"slot_%u",slot); + Item* pw = player_to_inspect->GetEquipmentList()->GetItem(GetPlayer()->ConvertSlotFromClient(s, GetVersion())); + packet->setItemByName(item_slot_name, pw, this->GetPlayer(), 0, 7, true, true, true); + } + } + else if(GetVersion() <= 561) { + for(int32 s=0;s<22;s++) { + int32 slot = s; + + char item_slot_name[64], item_slot_name_appearance[64]; + _snprintf(item_slot_name,64,"slot_%u",slot); + Item* pw = player_to_inspect->GetEquipmentList()->GetItem(GetPlayer()->ConvertSlotFromClient(s, GetVersion())); + packet->setItemByName(item_slot_name, pw, this->GetPlayer(), 0, 5, true, true); + } + } + else { + for(int32 s=0;sGetEquipmentList()->GetItem(GetPlayer()->ConvertSlotFromClient(s, player_to_inspect->GetClient()->GetVersion())); + packet->setItemByName(item_slot_name, pw, this->GetPlayer(), 0, 13, false, true); + if(s <= EQ2_FEET_SLOT || s == EQ2_RANGE_SLOT || s == EQ2_CLOAK_SLOT) { + pw = player_to_inspect->GetAppearanceEquipmentList()->GetItem(s); + packet->setItemByName(item_slot_name_appearance, pw, this->GetPlayer(), 0, 13, false, true); + } + else { + packet->setItemByName(item_slot_name_appearance, nullptr, this->GetPlayer(), 0, 13, false, true); + } + } + } + QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + +} + +void Client::SetPendingGuildInvite(Guild* guild, Player* invited_by) { + pending_guild_invite.guild = guild; + pending_guild_invite.invited_by = invited_by; +} + +void Client::ShowClaimWindow() { + PacketStruct* packet = configReader.getStruct("WS_PromoFlagsDetails", GetVersion()); + if (packet) { + + int16 loaded = database.CountCharClaimItems(GetCharacterID()); + vector claim = database.LoadCharacterClaimItems(GetCharacterID()); + int32 account_age = database.GetAccountAge(GetAccountID()); + + //not sure if there is a message or not, but adding this and a return, so if we have nothing do nothing rather than display an empty window. + if (loaded == 0 || claim.empty()) { + Message(CHANNEL_COLOR_RED, "You have nothing to claim."); + return; + } + + packet->setArrayLengthByName("num_claim_items", loaded); + + int j = 0; //use this to track skipped vet items. + for (int i = 0; i < claim.size(); i++) + { + if (j == claim.size()) { + Message(CHANNEL_COLOR_RED, "You have nothing to claim."); + return; + } + + Item* item = master_item_list.GetItem(claim[i].item_id); + int16 claimed = 0; + + if (claim[i].curr_claim < claim[i].max_claim) { + claimed = claim[i].max_claim - claim[i].curr_claim; + } + else { + claimed = 0; + } + + //dont display vet rewards until they reach the age required + if (account_age < claim[i].vet_reward_time) { + j++; + continue; + } + + packet->setArrayDataByName("id", i, i); + packet->setArrayDataByName("not_yet_claimed", 1, i); + packet->setArrayDataByName("num_remaining", claim[i].curr_claim, i); + packet->setArrayDataByName("one_per_character", claim[i].one_per_char, i); + packet->setArrayDataByName("claimed_on_this_char", 0, i); + packet->setArrayDataByName("item_name", item->name.c_str(), i); + packet->setArrayDataByName("text", "If you ever see this text, let a developer know!", i); //I've not seen this! + //packet->setArrayDataByName("category", "Scott's Shit", i); //devn00b: not using so commenting out, leaving in case we ever do implement categories. + packet->setArrayDataByName("icon", item->GetIcon(GetVersion()), i); + packet->setArrayDataByName("item_id", item->details.item_id, i); + j++; + } + } + packet->setDataByName("unknown3", 1); + QueuePacket(packet->serialize()); + safe_delete(packet); +} + + +void Client::ShowGuildSearchWindow() { + PacketStruct* packet = configReader.getStruct("WS_GuildRecruiting", GetVersion()); + if (packet) { + MutexMap* guilds = guild_list.GetGuilds(); + MutexMap::iterator itr = guilds->begin(); + packet->setArrayLengthByName("num_guilds", guilds->size()); + int32 i = 0; + while (itr.Next()) { + Guild* guild = itr.second; + packet->setArrayDataByName("guild_id", guild->GetID(), i); + packet->setArrayDataByName("guild_name", guild->GetName(), i); + packet->setArrayDataByName("recruiting_short_description", guild->GetRecruitingShortDesc().c_str(), i); + packet->setArrayDataByName("descriptive_tag1", guild->GetRecruitingDescTag(0), i); + packet->setArrayDataByName("descriptive_tag2", guild->GetRecruitingDescTag(1), i); + packet->setArrayDataByName("descriptive_tag3", guild->GetRecruitingDescTag(2), i); + packet->setArrayDataByName("descriptive_tag4", guild->GetRecruitingDescTag(3), i); + packet->setArrayDataByName("playstyle", guild->GetRecruitingPlayStyle(), i); + packet->setArrayDataByName("looking_for", guild->GetRecruitingLookingForPacketValue(), i); //tradeskillers, fighters, new + packet->setArrayDataByName("unknown7", 0x02, i); + packet->setArrayDataByName("min_level", guild->GetRecruitingMinLevel(), i); + i++; + } + QueuePacket(packet->serialize()); + safe_delete(packet); + } + +} + +void Client::ShowDressingRoom(Item* item, sint32 crc) { + PacketStruct* packet; + vector* slot_data; + vector::iterator itr; + int32 slots = 0; + + assert(item); + + if (!(packet = configReader.getStruct("WS_DressingRoom", GetVersion()))) { + return; + } + + slot_data = &item->slot_data; + for (itr = slot_data->begin(); itr != slot_data->end(); itr++) { + if (version >= 1188) + slots = *itr; + else + slots += (int8)pow(2.0, *itr); + } + + packet->setDataByName("slot", slots); + packet->setDataByName("appearance_id", item->generic_info.appearance_id); + if (slots == 2) { + packet->setDataByName("rgb", item->generic_info.appearance_red, 2); + packet->setDataByName("rgb", item->generic_info.appearance_blue, 0); + } + else { + packet->setDataByName("rgb", item->generic_info.appearance_red, 0); + packet->setDataByName("rgb", item->generic_info.appearance_blue, 2); + } + packet->setDataByName("rgb", item->generic_info.appearance_green, 1); + if (slots == 4) { + packet->setDataByName("highlight_rgb", item->generic_info.appearance_highlight_red, 2); + packet->setDataByName("highlight_rgb", item->generic_info.appearance_highlight_green, 1); + packet->setDataByName("highlight_rgb", item->generic_info.appearance_highlight_blue, 0); + } + else if (slots == 7) { + packet->setDataByName("highlight_rgb", item->generic_info.appearance_highlight_red, 1); + packet->setDataByName("highlight_rgb", item->generic_info.appearance_highlight_green, 0); + packet->setDataByName("highlight_rgb", item->generic_info.appearance_highlight_blue, 2); + } + else { + packet->setDataByName("highlight_rgb", item->generic_info.appearance_highlight_red, 0); + packet->setDataByName("highlight_rgb", item->generic_info.appearance_highlight_green, 1); + packet->setDataByName("highlight_rgb", item->generic_info.appearance_highlight_blue, 2); + } + packet->setDataByName("icon", item->GetIcon(GetVersion())); + packet->setDataByName("item_id", item->details.item_id); + packet->setDataByName("item_crc", crc); + packet->setDataByName("unknown2", 0xFFFFFFFF); + packet->setDataByName("unknown4", 0xFFFFFFFF); + packet->setDataByName("unknown5", 0xFF, 9); + packet->setDataByName("unknown5", 0xFF, 10); + QueuePacket(packet->serialize()); + safe_delete(packet); + +} + +void Client::SendCollectionList() { + map* collections = player->GetCollectionList()->GetCollections(); + map::iterator itr; + vector* collection_items; + Collection* collection; + struct CollectionItem* collection_item; + PacketStruct* packet = 0; + int16 i = 0, j; + + if (!(packet = configReader.getStruct("WS_CollectionUpdate", version))) + return; + + packet->setArrayLengthByName("num_collections", collections->size()); + for (itr = collections->begin(); itr != collections->end(); itr++) { + collection = itr->second; + collection_items = collection->GetCollectionItems(); + + packet->setArrayDataByName("unknown", 1, i); + packet->setArrayDataByName("collection_name", collection->GetName(), i); + packet->setArrayDataByName("collection_category", collection->GetCategory(), i); + packet->setArrayDataByName("completed", collection->GetCompleted(), i); + packet->setArrayDataByName("collection_id", collection->GetID(), i); + packet->setArrayDataByName("level", collection->GetLevel(), i); + packet->setArrayDataByName("ready_to_turn_in", collection->GetIsReadyToTurnIn(), i); + packet->setSubArrayLengthByName("num_items", collection_items->size(), i); + + for (j = 0; j < collection_items->size(); j++) { + collection_item = collection_items->at(j); + Item* item = master_item_list.GetItem(collection_item->item); + if (item) { + packet->setSubArrayDataByName("item_icon", item->GetIcon(GetVersion()), i, j); + if (version < 955) + packet->setSubArrayDataByName("item_name", item->name.c_str(), i, j); + else + packet->setSubArrayDataByName("item_id", item->details.item_id, i, j); + } + packet->setSubArrayDataByName("item_flag", collection_item->found, i, j); + } + i++; + } + + packet->setDataByName("new_collection_flag", 1); + + QueuePacket(packet->serialize()); + safe_delete(packet); + +} + +bool Client::SendCollectionsForItem(Item* item) { + map collections_to_send; + map* collections; + map::iterator itr; + vector* collection_items; + Collection* collection; + struct CollectionItem* collection_item; + PacketStruct* packet; + int16 i; + + assert(item); + + /* get any collections required by this item that the player already has */ + collections = player->GetCollectionList()->GetCollections(); + for (itr = collections->begin(); itr != collections->end(); itr++) { + collection = itr->second; + if (!collection->GetCompleted() && !collection->GetIsReadyToTurnIn() && collection->NeedsItem(item)) { + LogWrite(COLLECTION__DEBUG, 0, "Collect", "Adding collection from player list %u\n", collection->GetID()); + collections_to_send[collection->GetID()] = collection; + } + } + + /* get any collections required by this item that the player does not have and send it to the client */ + collections = master_collection_list.GetCollections(); + for (itr = collections->begin(); itr != collections->end(); itr++) { + collection = itr->second; + if (collection->NeedsItem(item) && !player->GetCollectionList()->GetCollection(collection->GetID())) { + if (!(packet = configReader.getStruct("WS_CollectionUpdate", version))) { + return false; + } + + packet->setArrayLengthByName("num_collections", 1); + packet->setArrayDataByName("unknown", 1, 0); + packet->setArrayDataByName("collection_name", collection->GetName(), 0); + packet->setArrayDataByName("collection_category", collection->GetCategory(), 0); + packet->setArrayDataByName("completed", 0, 0); + packet->setArrayDataByName("collection_id", collection->GetID(), 0); + packet->setArrayDataByName("level", collection->GetLevel(), 0); + packet->setArrayDataByName("ready_to_turn_in", 0, 0); + packet->setArrayDataByName("unknown3", 0x28, 0); + + collection_items = collection->GetCollectionItems(); + packet->setSubArrayLengthByName("num_items", collection_items->size(), 0); + for (i = 0; i < collection_items->size(); i++) { + collection_item = collection_items->at(i); + Item* item2 = master_item_list.GetItem(collection_item->item); + if (item2) { + packet->setSubArrayDataByName("item_icon", item2->GetIcon(GetVersion()), 0, i); + if (version < 955) + packet->setSubArrayDataByName("item_name", item2->name.c_str(), 0, i); + else + packet->setSubArrayDataByName("item_id", item2->details.item_id, 0, i); + packet->setSubArrayDataByName("item_flag", collection_item->found, 0, i); + } + } + packet->setDataByName("new_collection_flag", 0); + + QueuePacket(packet->serialize()); + safe_delete(packet); + + LogWrite(COLLECTION__DEBUG, 0, "Collect", "Adding collection from master list %u\n", collection->GetID()); + collections_to_send[collection->GetID()] = collection; + } + } + + /* send the client a list of collections that should be filtered for this item */ + if (collections_to_send.size() > 0) { + if (!(packet = configReader.getStruct("WS_CollectionFilter", version))) { + return false; + } + + i = 0; + packet->setArrayLengthByName("num_filters", collections_to_send.size()); + for (itr = collections_to_send.begin(); itr != collections_to_send.end(); itr++) { + collection = itr->second; + + packet->setArrayDataByName("collection_id", collection->GetID(), i); + packet->setArrayDataByName("collection_item_num", collection->GetCollectionItemByItemID(item->details.item_id)->index, i); + i++; + } + + packet->setDataByName("item_icon", item->GetIcon(GetVersion())); + packet->setDataByName("item_name", item->name.c_str()); + packet->setDataByName("item_id", item->details.item_id); + packet->setDataByName("unknown3", player->GetCollectionList()->Size()); + + QueuePacket(packet->serialize()); + safe_delete(packet); + } + + return collections_to_send.size() > 0; +} + +void Client::HandleCollectionAddItem(int32 collection_id, Item* item) { + Collection* collection; + struct CollectionItem* collection_item; + PacketStruct* packet; + char tmp[200] = { 0 }; + + assert(item); + + /* first try to get the collection from the player's collection list. if it's not found, get it from the master list */ + if ((collection = player->GetCollectionList()->GetCollection(collection_id))) { + + /* get the item struct that represents the item for this collection */ + if (!(collection_item = collection->GetCollectionItemByItemID(item->details.item_id))) { + SendCollectionList(); + LogWrite(COLLECTION__ERROR, 0, "Collect", "Error in Client::HandleCollectionAddItem. Could not find item '%s' required by collection '%s'", item->name.c_str(), collection->GetName()); + return; + } + + /* sanity check */ + if (collection_item->found) { + SendCollectionList(); + LogWrite(COLLECTION__ERROR, 0, "Collect", "Error in Client::HandleCollectionAddItem. Player '%s' has already found item '%s' for collection '%s'", player->GetName(), item->name.c_str(), collection->GetName()); + return; + } + } + else if ((collection = master_collection_list.GetCollection(collection_id))) { + collection = new Collection(collection); + + /* get the item struct that represents the item for this collection */ + if (!(collection_item = collection->GetCollectionItemByItemID(item->details.item_id))) { + SendCollectionList(); + LogWrite(COLLECTION__ERROR, 0, "Collect", "Error in Client::HandleCollectionAddItem. Could not find item '%s' required by collection '%s'", item->name.c_str(), collection->GetName()); + safe_delete(collection); + return; + } + + /* add the new collection to the player's collection list */ + if (!player->GetCollectionList()->AddCollection(collection)) { + SendCollectionList(); + LogWrite(COLLECTION__ERROR, 0, "Collect", "Error in Client::HandleCollectionAddItem. Player '%s' already has collection '%s'", player->GetName(), collection->GetName()); + safe_delete(collection); + return; + } + } + else { + LogWrite(COLLECTION__ERROR, 0, "Collect", "Error in Client::HandleCollectionAddItem. Could not find collection with id %u", collection_id); + return; + } + + collection_item->found = 1; + collection->SetSaveNeeded(true); + + if (!(packet = configReader.getStruct("WS_CollectionItem", version))) { + SendCollectionList(); + LogWrite(COLLECTION__ERROR, 0, "Collect", "Error in Client::HandleCollectionAddItem. Could not find struct 'WS_CollectionItem'"); + return; + } + + packet->setDataByName("collection_id", collection_id); + packet->setDataByName("collection_item_num", collection_item->index); + packet->setDataByName("add", 1); + QueuePacket(packet->serialize()); + Item* item2 = master_item_list.GetItem(collection_item->item); + if (item2) { + Message(CHANNEL_COLOR_YELLOW, "You added: %s to %s", item2->name.c_str(), collection->GetName()); + sprintf(tmp, "You added: %s to %s", item2->name.c_str(), collection->GetName()); + SendPopupMessage(5, tmp, "quest_item", 3.5, 0x64, 0xFF, 0xFF); + } + safe_delete(packet); + + RemoveItem(item, 1); + SendCollectionList(); + +} + +void Client::DisplayCollectionComplete(Collection* collection) { + vector* reward_items; + vector* selectable_reward_items; + struct CollectionRewardItem* reward_item; + PacketStruct* packet; + int32 i; + + assert(collection); + + reward_items = collection->GetRewardItems(); + selectable_reward_items = collection->GetSelectableRewardItems(); + + if (GetVersion() <= 561) { + int32 source_id = collection->GetID(); + PacketStruct* packet2 = configReader.getStruct("WS_QuestRewardPackMsg", GetVersion()); + if (packet2) { + packet2->setSubstructDataByName("reward_data", "unknown1", 255); + packet2->setSubstructDataByName("reward_data", "reward", "Quest Reward!"); + packet2->setSubstructDataByName("reward_data", "max_coin", collection->GetRewardCoin()); + packet2->setSubstructDataByName("reward_data", "exp_bonus", collection->GetRewardXP()); + + if(reward_items){ + int32 item_count = reward_items->size(); + packet2->setSubstructArrayLengthByName("reward_data", "num_rewards", item_count); + i = 0; + if(reward_items) { + for (i = 0; i < reward_items->size(); i++) { + Item* item = reward_items->at(i)->item; + if (item) { + packet2->setArrayDataByName("reward_id", item->details.item_id, i); + packet2->setItemArrayDataByName("item", item, player, i, 0, GetClientItemPacketOffset()); + } + } + } + } + if (selectable_reward_items) { + packet2->setSubstructArrayLengthByName("reward_data", "num_select_rewards", selectable_reward_items->size()); + for (i = 0; i < selectable_reward_items->size(); i++) { + Item* item = selectable_reward_items->at(i)->item; + if (item) { + packet2->setArrayDataByName("select_reward_id", item->details.item_id, i); + packet2->setItemArrayDataByName("select_item", item, player, i, 0, GetClientItemPacketOffset()); + } + } + } + } + + EQ2Packet* app = packet2->serialize(); + QueuePacket(app); + safe_delete(packet2); + return; + } + + if (!(packet = configReader.getStruct("WS_QuestComplete", version))) { + return; + } + + packet->setDataByName("title", "Quest Reward!"); + packet->setDataByName("name", collection->GetName()); + packet->setDataByName("description", collection->GetCategory()); + packet->setDataByName("level", collection->GetLevel()); + packet->setDataByName("max_coin", collection->GetRewardCoin()); + packet->setDataByName("min_coin", collection->GetRewardCoin()); + //packet->setDataByName("status_points", quest->GetStatusPoints()); + packet->setArrayLengthByName("num_rewards", reward_items->size()); + for (i = 0; i < reward_items->size(); i++) { + reward_item = reward_items->at(i); + if(!reward_item->item) + { + LogWrite(DATABASE__ERROR, 0, "Database", "DisplayCollectionComplete Collection %s (%u) num_rewards has missing item in slot %u", collection->GetName(), collection->GetID(), i); + Message(CHANNEL_COLOR_RED, "DisplayCollectionComplete Collection %s (%u) num_rewards has missing item in slot %u", collection->GetName(), collection->GetID(), i); + continue; + } + + packet->setArrayDataByName("reward_id", reward_item->item->details.item_id, i); + if (version < 860) + packet->setItemArrayDataByName("item", reward_item->item, player, i, 0, GetClientItemPacketOffset()); + else if (version < 1193) + packet->setItemArrayDataByName("item", reward_item->item, player, i); + else + packet->setItemArrayDataByName("item", reward_item->item, player, i, 0, 2); + } + packet->setArrayLengthByName("num_select_rewards", selectable_reward_items->size()); + for (i = 0; i < selectable_reward_items->size(); i++) { + reward_item = selectable_reward_items->at(i); + if(!reward_item->item) + { + LogWrite(DATABASE__ERROR, 0, "Database", "DisplayCollectionComplete Collection %s (%u) num_select_rewards has missing item in slot %u", collection->GetName(), collection->GetID(), i); + Message(CHANNEL_COLOR_RED, "DisplayCollectionComplete Collection %s (%u) num_select_rewards has missing item in slot %u", collection->GetName(), collection->GetID(), i); + continue; + } + + packet->setArrayDataByName("select_reward_id", reward_item->item->details.item_id, i); + if (version < 860) + packet->setItemArrayDataByName("select_item", reward_item->item, player, i, 0, GetClientItemPacketOffset()); + else if (version < 1193) + packet->setItemArrayDataByName("select_item", reward_item->item, player, i); + else + packet->setItemArrayDataByName("select_item", reward_item->item, player, i, 0, 2); + } + + QueuePacket(packet->serialize()); + safe_delete(packet); + +} + +void Client::HandInCollections() { + map* collections; + map::iterator itr; + Collection* collection; + + /* only show 1 collection reward dialog at a time */ + if (player->GetPendingCollectionReward()) { + return; + } + + collections = player->GetCollectionList()->GetCollections(); + + /* we only want to display 1 collection reward dialog at a time. so once we find one to display, send it and return. once the player accepts the reward + for this collection, this function gets called again and the process repeats until there are no more collections to hand in */ + for (itr = collections->begin(); itr != collections->end(); itr++) { + collection = itr->second; + if (collection->GetIsReadyToTurnIn()) { + player->SetPendingCollectionReward(collection); + MQuestPendingUpdates.writelock(__FUNCTION__, __LINE__); + QuestRewardData* data = new QuestRewardData; + data->quest_id = 0; + data->is_temporary = false; + data->description = std::string(""); + data->is_collection = true; + data->has_displayed = false; + data->tmp_coin = 0; + data->tmp_status = 0; + data->db_saved = false; + data->db_index = 0; + quest_pending_reward.push_back(data); + MQuestPendingUpdates.releasewritelock(__FUNCTION__, __LINE__); + quest_updates = true; + break; + } + } + if(quest_updates) { + SaveQuestRewardData(true); + } +} + +void Client::AcceptCollectionRewards(Collection* collection, int32 selectable_item_id) { + vector* reward_items; + vector::iterator itr; + struct CollectionRewardItem* reward_item; + int16 num_slots_needed; + int16 num_slots; + + assert(collection); + + reward_items = collection->GetRewardItems(); + num_slots_needed = (int16)reward_items->size(); + if (selectable_item_id > 0) + num_slots_needed++; + + num_slots = player->GetPlayerItemList()->GetNumberOfFreeSlots(); + if (num_slots < num_slots_needed) { + SimpleMessage(CHANNEL_COLOR_RED, "You do not have enough free slots. Free up some slots and try again"); + DisplayCollectionComplete(collection); + return; + } + + /* add manditory items */ + for (itr = reward_items->begin(); itr != reward_items->end(); itr++) { + reward_item = *itr; + AddItem(reward_item->item->details.item_id, reward_item->quantity); + } + + /* find and add the selectable item if there's one */ + if (selectable_item_id > 0) { + reward_items = collection->GetSelectableRewardItems(); + for (itr = reward_items->begin(); itr != reward_items->end(); itr++) { + reward_item = *itr; + if (reward_item->item->details.item_id == selectable_item_id) { + AddItem(reward_item->item->details.item_id, reward_item->quantity); + break; + } + } + } + if (collection->GetRewardXP() > 0) { + player->AddXP((int32)collection->GetRewardXP()); + } + if (collection->GetRewardCoin() > 0) { + player->AddCoins(collection->GetRewardCoin()); + Message(CHANNEL_COLOR_YELLOW, "You receive %s", GetCoinMessage(collection->GetRewardCoin()).c_str()); + } + + collection->SetCompleted(true); + //update achievements for completeing collection here + collection->SetSaveNeeded(true); + SendCollectionList(); + + /* reset the pending collection reward and check for my collections that the player needs to hand in */ + player->SetPendingCollectionReward(0); + + RemoveQueuedQuestReward(); + + GetPlayer()->SetActiveReward(false); + + HandInCollections(); + + GetPlayer()->GetZone()->SendSubSpawnUpdates(SUBSPAWN_TYPES::COLLECTOR); +} + +void Client::SendRecipeList() { + if(GetRecipeListSent()) { + return; + } + + PacketStruct* packet = 0; + map* recipes = player->GetRecipeList()->GetRecipes(); + map::iterator itr; + int16 i = 0; + Recipe* recipe; + + if(version <= 561) { + PacketStruct* packet = 0; + if (!(packet = configReader.getStruct("WS_UpdateRecipeBook", GetVersion()))) { + return; + } + packet->setArrayLengthByName("recipe_count", recipes->size()); + for (itr = recipes->begin(); itr != recipes->end(); itr++) { + recipe = itr->second; + int32 recipe_id = recipe->GetID(); + packet->setArrayDataByName("recipe_id", recipe_id, i); + packet->setArrayDataByName("recipe_data_crc", GetRecipeCRC(recipe), i); + packet->setArrayDataByName("unknown", 0x7005BE3, i); //0x7005BE3 + i++; + } + EQ2Packet* ret = packet->serializeCountPacket(GetVersion(), 0, nullptr, nullptr); + QueuePacket(ret); + safe_delete(packet); + SetRecipeListSent(true); + return; + } + else if (!(packet = configReader.getStruct("WS_RecipeList", version))) { + return; + } + int8 level = player->GetTSLevel(); + int index = 0; + for (itr = recipes->begin(); itr != recipes->end(); itr++) { + recipe = itr->second; + auto res = std::find(devices.begin(), devices.end(), recipe->GetDevice()); + + if (res != devices.end()) + index = res - devices.begin(); + else + devices.push_back(recipe->GetDevice()); + } + + packet->setDataByName("command_type", 0); + packet->setArrayLengthByName("num_recipes", recipes->size()); + int stringsize = 0; + for (itr = recipes->begin(); itr != recipes->end(); itr++) { + recipe = itr->second; + int32 myid = recipe->GetID(); + int8 rlevel = recipe->GetLevel(); + int8 even = level - level * .05 + .5; + int8 easymin = level - level * .25 + .5; + int8 veasymin = level - level * .35 + .5; + if (rlevel > level ) + packet->setArrayDataByName("tier", 4, i); + else if ((rlevel <= level) & (rlevel >= even)) + packet->setArrayDataByName("tier", 3, i); + else if ((rlevel <= even) & (rlevel >= easymin)) + packet->setArrayDataByName("tier", 2, i); + else if ((rlevel <= easymin) & (rlevel >= veasymin)) + packet->setArrayDataByName("tier", 1, i); + else if ((rlevel <= veasymin) & (rlevel >= 0)) + packet->setArrayDataByName("tier", 0, i); + if (rlevel == 2) + int xxx = 1; + packet->setArrayDataByName("recipe_id", myid, i); + packet->setArrayDataByName("level", recipe->GetLevel(), i); + packet->setArrayDataByName("unknown1", recipe->GetLevel(), i); + packet->setArrayDataByName("icon", recipe->GetIcon(), i); + packet->setArrayDataByName("classes", recipe->GetClasses(), i); + packet->setArrayDataByName("technique", recipe->GetTechnique(), i); + packet->setArrayDataByName("knowledge", recipe->GetKnowledge(), i); + + + auto recipe_device = std::find(devices.begin(), devices.end(), recipe->GetDevice()); + if (recipe_device != devices.end()) + packet->setArrayDataByName("device_type", recipe_device - devices.begin(), i); + else + {//TODO error should never get here + } + packet->setArrayDataByName("device_sub_type", recipe->GetDevice_Sub_Type(), i); + packet->setArrayDataByName("recipe_name", recipe->GetName(), i); + packet->setArrayDataByName("recipe_book", recipe->GetBook(), i); + packet->setArrayDataByName("unknown3", recipe->GetUnknown3(), i); + i++; + } + //packet->PrintPacket(); + EQ2Packet* pack = packet->serialize(); + QueuePacket(pack); + safe_delete(packet); + SetRecipeListSent(true); +} + +void Client::ShowRecipeBook() { + PacketStruct* packet = 0; + Spawn* target = 0; + int index = 0; + if (!(target = player->GetTarget())) { + SimpleMessage(CHANNEL_COLOR_YELLOW, "You do not have a tradeskill device targeted"); + return; + } + if (!target->IsObject() || !((Object*)target)->GetDeviceID()) { + SimpleMessage(CHANNEL_COLOR_YELLOW, "You do not have a tradeskill device targeted"); + return; + } + + if (!(packet = configReader.getStruct("WS_ShowRecipeBook", version))) { + return; + } + + packet->setDataByName("device", target->GetName()); + packet->setDataByName("unknown1", 1); + auto res = std::find(devices.begin(), devices.end(), target->GetName()); + if (res != devices.end()){ + index = res - devices.begin(); + int32 deviceID = 0; + deviceID |= 1UL << index; + //LogWrite(TRADESKILL__ERROR, 0, "Tradeskills", "GetDeviceID() = %u, deviceID = %u", ((Object*)target)->GetDeviceID(), deviceID); + packet->setDataByName("unknown2", devices.size()); + packet->setDataByName("unknown3", deviceID); + } + else + packet->setDataByName("unknown2", devices.size()); + QueuePacket(packet->serialize()); + safe_delete(packet); +} + +void Client::SendTitleUpdate() { + // must call release read lock before leaving function on GetPlayerTitles + vector* titles = player->GetPlayerTitles()->GetAllTitles(); + vector::iterator itr; + Title* title; + sint32 i = 0; + sint32 prefix_index = database.GetCharPrefixIndex(GetCharacterID(), player); + sint32 suffix_index = database.GetCharSuffixIndex(GetCharacterID(), player); + PacketStruct* packet = configReader.getStruct("WS_TitleUpdate", GetVersion()); + if (packet) { + packet->setArrayLengthByName("num_titles", titles->size()); + for (itr = titles->begin(); itr != titles->end(); itr++) { + title = *itr; + packet->setArrayDataByName("title", title->GetName(), i); + packet->setArrayDataByName("prefix", title->GetPrefix(), i); + i++; + } + packet->setDataByName("current_prefix", prefix_index); + packet->setDataByName("current_suffix", suffix_index); + + LogWrite(CCLIENT__PACKET, 0, "Client", "Dump/Print Packet in func: %s, line: %i", __FUNCTION__, __LINE__); +#if EQDEBUG >= 9 + packet->PrintPacket(); +#endif + + QueuePacket(packet->serialize()); + safe_delete(packet); + SendUpdateTitles(prefix_index, suffix_index); + } + player->GetPlayerTitles()->ReleaseReadLock(); +} + +void Client::SendUpdateTitles(sint32 prefix, sint32 suffix) { + Title* suffix_title = 0; + Title* prefix_title = 0; + if (suffix != -1) { + suffix_title = player->GetPlayerTitles()->GetTitle(suffix); + if(suffix_title) + strcpy(player->appearance.suffix_title, suffix_title->GetName()); + } + else + memset(player->appearance.suffix_title, 0, strlen(player->appearance.suffix_title)); + if (prefix != -1) { + prefix_title = player->GetPlayerTitles()->GetTitle(prefix); + if(prefix_title) + strcpy(player->appearance.prefix_title, prefix_title->GetName()); + } + else + memset(player->appearance.prefix_title, 0, strlen(player->appearance.prefix_title)); + + current_zone->SendUpdateTitles(this, suffix_title, prefix_title); +} + +void Client::SendLanguagesUpdate(int32 id, bool setlang) { + list* languages = player->GetPlayerLanguages()->GetAllLanguages(); + list::iterator itr; + Language* language; + int32 i = 0; + + if(setlang==1){ + GetPlayer()->SetCurrentLanguage(id); + } + + PacketStruct* packet = configReader.getStruct("WS_Languages", GetVersion()); + if (packet) { + packet->setArrayLengthByName("num_languages", languages->size()); + for (itr = languages->begin(); itr != languages->end(); itr++) { + language = *itr; + packet->setArrayDataByName("language_id", language->GetID(), i); + i++; + } + packet->setDataByName("current_language", id); + + LogWrite(CCLIENT__PACKET, 0, "Client", "Dump/Print Packet in func: %s, line: %i", __FUNCTION__, __LINE__); +#if EQDEBUG >= 9 + packet->PrintPacket(); +#endif + QueuePacket(packet->serialize()); + safe_delete(packet); + } +} + +void Client::SendPetOptionsWindow(const char* pet_name, int8 type) { + PacketStruct* packet = configReader.getStruct("WS_PetOptions", GetVersion()); + if (packet) { + if (pet_name) + packet->setDataByName("pet_name", pet_name); + if (player->GetInfoStruct()->get_pet_behavior() & 1) + packet->setDataByName("protect_master", 1); + if (player->GetInfoStruct()->get_pet_behavior() & 2) + packet->setDataByName("protect_self", 1); + if (player->GetInfoStruct()->get_pet_movement() == 2) + packet->setDataByName("stay_follow_toggle", 1); + + packet->setDataByName("pet_type", type); + + QueuePacket(packet->serialize()); + } + + safe_delete(packet); +} + +bool Client::IsCrafting() { + return current_zone->GetTradeskillMgr()->IsClientCrafting(this); +} + +void Client::SendBiography() { + PacketStruct* packet = configReader.getStruct("WS_BioUpdate", GetVersion()); + if (packet) { + char biography[512]; + if(player->GetInfoStruct()->get_biography().size() < 1) + { + safe_delete(packet); + return; + } + else + { + int16 size = player->GetInfoStruct()->get_biography().size(); + if(size>511) + size = 511; + + strncpy(biography, player->GetInfoStruct()->get_biography().c_str(), player->GetInfoStruct()->get_biography().size()); + biography[player->GetInfoStruct()->get_biography().size()] = '\0'; + } + packet->setDataByName("biography", biography); + + QueuePacket(packet->serialize()); + } + + safe_delete(packet); +} + +PendingResurrection* Client::GetCurrentRez() { + return ¤t_rez; +} + +Mutex* Client::GetResurrectMutex() { + return &m_resurrect; +} + +void Client::SendResurrectionWindow() { + Spawn* caster = current_rez.caster; + if (!caster || !player) + return; + + PacketStruct* packet = configReader.getStruct("WS_ChoiceWindow", GetVersion()); + if (!packet) + return; + + char* tmp = new char[512]; + sprintf(tmp, "%s would like to cast '%s' on you. Do you accept?", caster->GetName(), current_rez.spell_name.c_str()); + + packet->setMediumStringByName("text", tmp); + packet->setMediumStringByName("accept_text", "Yes"); + packet->setMediumStringByName("cancel_text", "No"); + + sprintf(tmp, "accept_resurrection %u", player->GetID()); + packet->setMediumStringByName("accept_command", tmp); + + sprintf(tmp, "decline_resurrection %u", player->GetID()); + packet->setMediumStringByName("cancel_command", tmp); + packet->setDataByName("time", current_rez.expire_timer->GetRemainingTime() / 1000); + QueuePacket(packet->serialize()); + safe_delete(packet); + safe_delete_array(tmp); +} + +void Client::AcceptResurrection() { + Spawn* caster = current_rez.caster; + if (!player || !caster) + return; + + if (player->Alive()) + return; + + if ((caster->GetZone() != player->GetZone()) || (current_rez.range > 0 && player->GetDistance(caster) > current_rez.range)) { + SimpleMessage(CHANNEL_COLOR_YELLOW, "The caster must be nearby to complete the spell."); + SendResurrectionWindow(); + return; + } + + player->GetZone()->ResurrectSpawn(player, this); + current_rez.should_delete = true; +} + +void Client::SetPendingLastName(string last_name) { + pending_last_name = new string; + pending_last_name->assign(last_name); +} + +string* Client::GetPendingLastName() { + return pending_last_name; +} + +void Client::RemovePendingLastName() { + safe_delete(pending_last_name); +} + +void Client::SendLastNameConfirmation() { + if (!pending_last_name) + return; + + PacketStruct* packet = configReader.getStruct("WS_ChoiceWindow", GetVersion()); + if (packet) { + char* text = new char[128]; + sprintf(text, "Are you sure you want your last name to be \"%s\"?", pending_last_name->c_str()); + packet->setDataByName("text", text); + packet->setDataByName("accept_text", "Yes"); + packet->setDataByName("accept_command", "confirmedlastname"); + packet->setDataByName("cancel_text", "No"); + packet->setDataByName("max_length", 50); + packet->setDataByName("unknown4", 1); + packet->setDataByName("unknown5", 1); + QueuePacket(packet->serialize()); + safe_delete(packet); + safe_delete_array(text); + } +} + +void Client::AddQuestTimer(int32 quest_id) { + MQuestTimers.writelock(__FUNCTION__, __LINE__); + quest_timers.push_back(quest_id); + MQuestTimers.releasewritelock(__FUNCTION__, __LINE__); +} + +void Client::RemoveQuestTimer(int32 quest_id) { + MQuestTimers.writelock(__FUNCTION__, __LINE__); + vector::iterator itr; + for (itr = quest_timers.begin(); itr != quest_timers.end(); itr++) { + if ((*itr) == quest_id) { + quest_timers.erase(itr); + break; + } + } + MQuestTimers.releasewritelock(__FUNCTION__, __LINE__); +} + +void Client::SavePlayerImages() { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Saving %s image for player %s (%u)", (incoming_paperdoll.image_type == PAPERDOLL_TYPE_FULL ? "paperdoll" : "headshot"), GetPlayer()->GetName(), GetCharacterID()); + + // Save the paperdoll image if the server allows it + if (incoming_paperdoll.image_type == PAPERDOLL_TYPE_FULL && rule_manager.GetGlobalRule(R_World, SavePaperdollImage)->GetBool()) + database.SaveCharacterPicture(GetCharacterID(), incoming_paperdoll.image_type, incoming_paperdoll.image_bytes, incoming_paperdoll.current_size_bytes); + + if (incoming_paperdoll.image_type == PAPERDOLL_TYPE_HEAD) { + // Save the head shot if the server allows it + if (rule_manager.GetGlobalRule(R_World, SaveHeadshotImage)->GetBool()) + database.SaveCharacterPicture(GetCharacterID(), incoming_paperdoll.image_type, incoming_paperdoll.image_bytes, incoming_paperdoll.current_size_bytes); + + // Send the head shot to the login server + if (rule_manager.GetGlobalRule(R_World, SendPaperdollImagesToLogin)->GetBool()) { + int32 size = incoming_paperdoll.current_size_bytes + CHARPICSTRUCT_MINSIZE; + ServerPacket* packet = new ServerPacket(ServerOP_CharacterPicture, size); + memset(packet->pBuffer, 0, size); + + CharPictureUpdate_Struct* pic = (CharPictureUpdate_Struct*)packet->pBuffer; + pic->account_id = GetAccountID(); + pic->char_id = GetCharacterID(); + pic->pic_size = (int16)incoming_paperdoll.current_size_bytes; + memcpy(pic->pic, incoming_paperdoll.image_bytes, incoming_paperdoll.current_size_bytes); + + loginserver.SendPacket(packet); + safe_delete(packet); + } + } + safe_delete_array(incoming_paperdoll.image_bytes); + incoming_paperdoll.current_size_bytes = 0; +} + +void Client::EndAutoMount() { + PacketStruct* packet = configReader.getStruct("WS_ServerControlFlags", GetVersion()); + if (packet) { + packet->setDataByName("parameter1", 128); + packet->setDataByName("parameter2", 64); + packet->setDataByName("value", 1); + QueuePacket(packet->serialize()); + safe_delete(packet); + } + + packet = configReader.getStruct("WS_ServerControlFlags", GetVersion()); + if (packet) { + packet->setDataByName("parameter3", 4); + packet->setDataByName("parameter5", 2); + packet->setDataByName("value", 0); + QueuePacket(packet->serialize()); + safe_delete(packet); + } + + packet = configReader.getStruct("WS_ClearForLanding", GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", GetPlayer()->GetIDWithPlayerSpawn(GetPlayer())); + QueuePacket(packet->serialize()); + safe_delete(packet); + } + + on_auto_mount = false; + + player->SetMount(((Player*)player)->GetTempMount()); + EQ2_Color mount_color = player->GetTempMountColor(); + EQ2_Color saddle_color = player->GetTempMountSaddleColor(); + player->SetMountColor(&mount_color); + player->SetMountSaddleColor(&saddle_color); + player->SetTempMount(0); +} + +bool Client::EntityCommandPrecheck(Spawn* spawn, const char* command) { + const char* spawn_script = spawn->GetSpawnScript(); + bool should_use_spawn = true; + if (spawn_script) { + lua_State* state = lua_interface->GetSpawnScript(spawn_script); + if (state) { + Mutex* state_mutex = lua_interface->GetSpawnScriptMutex(spawn_script); + if (state_mutex) + state_mutex->writelock(__FUNCTION__, __LINE__); + lua_getglobal(state, "can_use_command"); + if (lua_isfunction(state, -1)) { + lua_interface->SetSpawnValue(state, spawn); + lua_interface->SetSpawnValue(state, GetPlayer()); + lua_interface->SetStringValue(state, command ? command : ""); + if (lua_pcall(state, 3, 1, 0) == 0) { + should_use_spawn = lua_interface->GetBooleanValue(state, 1); + } + } + lua_interface->ResetFunctionStack(state); + if (state_mutex) + state_mutex->releasewritelock(__FUNCTION__, __LINE__); + } + } + return should_use_spawn; +} + +bool Client::IsCurrentTransmuteID(int32 req_id) { + return req_id == transmuteID; +} + +void Client::SetTransmuteID(int32 trans_id) { + transmuteID = trans_id; +} + +int32 Client::GetTransmuteID() { + return transmuteID; +} + +bool Client::HandleNewLogin(int32 account_id, int32 access_code) +{ + printf("HandleNewLogin: AcctID: %i AccessCode: %i\n", account_id, access_code); + ZoneAuthRequest* zar = zone_auth.GetAuth(account_id, access_code); + + if (zar) + { + int32 charID = database.GetCharacterID(zar->GetCharacterName()); + if (database.IsActiveQuery(charID)) + { + delayedLogin = true; + delayedAccountID = account_id; + delayedAccessKey = access_code; + delayTimer.Start(500); + LogWrite(ZONE__INFO, 0, "ZoneAuth", "Attempt to Login must be delayed, async character save in progress! ... Access Key: %u, Character Name: %s, Account ID: %u, Client Data Version: %u", zar->GetAccessKey(), zar->GetCharacterName(), zar->GetAccountID(), version); + return true; + } + + delayedLogin = false; + delayTimer.Disable(); + + firstlogin = zar->isFirstLogin(); + SetReadyForSpawns(false); + ready_for_updates = false; + LogWrite(ZONE__INFO, 0, "ZoneAuth", "Access Key: %u, Character Name: %s, Account ID: %u, Client Data Version: %u", zar->GetAccessKey(), zar->GetCharacterName(), zar->GetAccountID(), version); + Client* client = zone_list.GetClientByCharName(zar->GetCharacterName()); + if (client || database.loadCharacter(zar->GetCharacterName(), zar->GetAccountID(), this)) { + GetPlayer()->CalculateOfflineDebtRecovery(GetLastSavedTimeStamp()); + GetPlayer()->vis_changed = false; + GetPlayer()->info_changed = false; + + bool pvp_allowed = rule_manager.GetGlobalRule(R_PVP, AllowPVP)->GetBool(); + if (pvp_allowed) + this->GetPlayer()->SetAttackable(1); + MDeletePlayer.writelock(__FUNCTION__, __LINE__); + if (client) { + if (client->getConnection()) + client->getConnection()->SendDisconnect(true); + + bool restore_ld_success = false; + if (client->GetCurrentZone() && !client->IsZoning()) { + //swap players, allowing the client to resume his LD'd player (ONLY if same version of the client) + if (client->GetVersion() == version) { + client->DisableSave(); + client->ReplaceGroupClient(this); + Player* current_player = GetPlayer(); + SetPlayer(client->GetPlayer()); + GetPlayer()->SetClient(this); + GetPlayer()->SetReturningFromLD(true); + + SetCharacterID(client->GetCharacterID()); + SetAccountID(client->GetAccountID()); + SetAdminStatus(client->GetAdminStatus()); + SetCurrentZone(GetPlayer()->GetZone()); + client->SetPlayer(current_player); + GetPlayer()->ResetSavedSpawns(); + restore_ld_success = true; + + char tmpldname[128]; + snprintf(tmpldname, 128, "%s Linkdead",GetPlayer()->GetName()); + client->GetPlayer()->SetName(tmpldname, false); + } + ZoneServer* tmpZone = client->GetCurrentZone(); + tmpZone->RemoveClientImmediately(client); + } + if(!restore_ld_success && !database.loadCharacter(zar->GetCharacterName(), zar->GetAccountID(), this)) { + LogWrite(ZONE__ERROR, 0, "Zone", "Error reloading LD character and loading DB character: %s", player->GetName()); + ClientPacketFunctions::SendLoginDenied(this); + Disconnect(); + return false; + } + } + MDeletePlayer.releasewritelock(__FUNCTION__, __LINE__); + if (!GetCurrentZone()) { + LogWrite(ZONE__ERROR, 0, "Zone", "Error loading zone for character: %s", player->GetName()); + ClientPacketFunctions::SendLoginDenied(this); + Disconnect(); + return false; + } + else if (EQOpcodeManager.count(GetOpcodeVersion(version)) > 0 && getConnection()) { + getConnection()->SetClientVersion(version); + GetCurrentZone()->SetSpawnStructs(this); + connected_to_zone = true; + client_list.Remove(this); //remove from master client list + GetCurrentZone()->AddClient(this); //add to zones client list + zone_list.AddClientToMap(player->GetName(), this); + // this initiates additional DB loading and client offloading within the Zone the player resides, need the client already added in zone and to the map above. + if(GetCurrentZone()->IsLoading()) { + new_client_login = NewLoginState::LOGIN_DELAYED; + } + else { + new_client_login = NewLoginState::LOGIN_ALLOWED; + } + + const char* zone_script = world.GetZoneScript(GetCurrentZone()->GetZoneID()); + if (zone_script && lua_interface) + lua_interface->RunZoneScript(zone_script, "new_client", GetCurrentZone(), GetPlayer()); + } + else { + LogWrite(WORLD__ERROR, 0, "World", "Incompatible version: %i", version); + version = 0; + ClientPacketFunctions::SendLoginDenied(this); + Disconnect(); + return false; + } + } + else { + LogWrite(WORLD__ERROR, 0, "World", "Could not load character '%s' with account id of: %u", zar->GetCharacterName(), zar->GetAccountID()); + ClientPacketFunctions::SendLoginDenied(this); + Disconnect(); + return false; + } + zone_auth.RemoveAuth(zar); + } + else + { + LogWrite(WORLD__ERROR, 0, "World", "Invalid ZoneAuthRequest, disconnecting client."); + Disconnect(); + return false; + } + + return true; +} + + +void Client::SendSpawnChanges(set& spawns) { + if (!IsReadyForUpdates()) + return; + + map info_changes; + map pos_changes; + map vis_changes; + + map empty_changes; + + int32 info_size = 0; + int32 pos_size = 0; + int32 vis_size = 0; + + int count = 0; + + for (const auto& spawn : spawns) { + int16 index = GetPlayer()->GetIndexForSpawn(spawn); + if (index == 0 || !GetPlayer()->WasSentSpawn(spawn->GetID())) + continue; + + if(GetPlayer() == spawn && GetVersion() <= 284) { // stopped self client/player warping in the EQ2 release disc (Beta), don't send yourself in bulk spawn updates + continue; + } + + int16 tmp_info_size = 0; + int16 tmp_pos_size = 0; + int16 tmp_vis_size = 0; + if (spawn->vis_changed) + { + auto vis_change = spawn->spawn_vis_changes_ex(GetPlayer(), GetVersion(), &tmp_vis_size); + if (vis_change) { + SpawnData data; + data.spawn = spawn; + data.data = vis_change; + data.size = tmp_vis_size; + map tmp_vis_changes; + tmp_vis_changes[index] = data; + + map tmp_info_changes; + map tmp_pos_changes; + if (spawn->info_changed) { + auto info_change = spawn->spawn_info_changes_ex(GetPlayer(), GetVersion(), &tmp_info_size); + + if (info_change) { + SpawnData data; + data.spawn = spawn; + data.data = info_change; + data.size = tmp_info_size; + tmp_info_changes[index] = data; + } + } + + if (spawn->position_changed) { + auto pos_change = spawn->spawn_pos_changes_ex(GetPlayer(), GetVersion(), &tmp_pos_size); + + if (pos_change) { + SpawnData data; + data.spawn = spawn; + data.data = pos_change; + data.size = tmp_pos_size; + tmp_pos_changes[index] = data; + } + } + + MakeSpawnChangePacket(tmp_info_changes, tmp_pos_changes, tmp_vis_changes, tmp_info_size, tmp_pos_size, data.size); + + for (auto& kv : tmp_info_changes) { + safe_delete_array(kv.second.data); + } + + for (auto& kv : tmp_pos_changes) { + safe_delete_array(kv.second.data); + } + + for (auto& kv : tmp_vis_changes) { + safe_delete_array(kv.second.data); + } + continue; + } + } + + if (spawn->info_changed) { + auto info_change = spawn->spawn_info_changes_ex(GetPlayer(), GetVersion(), &tmp_info_size); + + if (info_change) { + SpawnData data; + data.spawn = spawn; + data.data = info_change; + data.size = tmp_info_size; + info_size += tmp_info_size; + + info_changes[index] = data; + } + count++; + } + + if (spawn->position_changed) { + auto pos_change = spawn->spawn_pos_changes_ex(GetPlayer(), GetVersion(), &tmp_pos_size); + + if (pos_change) { + SpawnData data; + data.spawn = spawn; + data.data = pos_change; + data.size = tmp_pos_size; + pos_size += tmp_pos_size; + + pos_changes[index] = data; + } + count++; + } + + if (spawn->vis_changed) { + auto vis_change = spawn->spawn_vis_changes_ex(GetPlayer(), GetVersion(), &tmp_vis_size); + + if (vis_change) { + SpawnData data; + data.spawn = spawn; + data.data = vis_change; + data.size = tmp_vis_size; + vis_size += tmp_vis_size; + + vis_changes[index] = data; + } + count++; + } + + } + + if (info_size == 0 && pos_size == 0 && vis_size == 0) { + return; + } + + MakeSpawnChangePacket(info_changes, pos_changes, vis_changes, info_size, pos_size, vis_size); + + for (auto& kv : info_changes) { + safe_delete_array(kv.second.data); + } + + for (auto& kv : pos_changes) { + safe_delete_array(kv.second.data); + } + + for (auto& kv : vis_changes) { + safe_delete_array(kv.second.data); + } +} + +void Client::MakeSpawnChangePacket(map info_changes, map pos_changes, map vis_changes, int32 info_size, int32 pos_size, int32 vis_size) +{ + static const int8 oversized = 255; + int16 opcode_val = 0; + + if (EQOpcodeManager.count(GetOpcodeVersion(version)) != 0) { + opcode_val = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqUpdateGhostCmd); + } + int32 size = info_size + pos_size + vis_size + 8; + size += CheckOverLoadSize(info_size); + size += CheckOverLoadSize(pos_size); + size += CheckOverLoadSize(vis_size); + if (version <= 373 && size >= 255) {//1 byte to 3 for overloaded val + size += 2; + } + else if (version >= 374) + size += 3; + uchar* tmp = new uchar[size]; + uchar* ptr = tmp; + + memset(tmp, 0, size); + if (version <= 373) { + if (size >= 255) { + size -= 3; + memcpy(ptr, &oversized, sizeof(int8)); + ptr += sizeof(int8); + memcpy(ptr, &size, sizeof(int16)); + ptr += sizeof(int16); + size += 3; + } + else { + size -= 1; + memcpy(ptr, &size, sizeof(int8)); + ptr += sizeof(int8); + size += 1; + } + } + else { + size -= 4; + memcpy(ptr, &size, sizeof(int32)); + ptr += sizeof(int32); + size += 4; + } + memcpy(ptr, &oversized, sizeof(int8)); + ptr += sizeof(int8); + + memcpy(ptr, &opcode_val, sizeof(int16)); + ptr += sizeof(int16); + + int32 current_time = Timer::GetCurrentTime2(); + memcpy(ptr, ¤t_time, sizeof(int32)); + ptr += sizeof(int32); + + ptr += DoOverLoad(info_size, ptr); + + for (const auto& kv : info_changes) { + auto info = kv.second; + memcpy(ptr, info.data, info.size); + ptr += info.size; + } + + ptr += DoOverLoad(pos_size, ptr); + + for (const auto& kv : pos_changes) { + auto pos = kv.second; + memcpy(ptr, pos.data, pos.size); + ptr += pos.size; + } + + ptr += DoOverLoad(vis_size, ptr); + + for (const auto& kv : vis_changes) { + auto vis = kv.second; + memcpy(ptr, vis.data, vis.size); + ptr += vis.size; + } + + EQ2Packet* packet = new EQ2Packet(OP_ClientCmdMsg, tmp, size); + + if (packet) { + /*char blah[64]; + snprintf(blah, 64, "Sending %i", current_time); + SimpleMessage(4, blah);*/ + QueuePacket(packet); + } + + delete[] tmp; +} + +void Client::SendHailCommand(Spawn* spawn) +{ + // hardcoded 'hail' entity commands + switch (spawn->secondary_command_list_id) + { + case 9: + case 1262: + case 1265: + case 1267: + { + EQ2_16BitString* command = new EQ2_16BitString(); + command->data = ""; + command->size = 0; + commands.Process(COMMAND_HAIL, command, this, spawn); + safe_delete(command); + break; + } + } +} + +void Client::SendDefaultCommand(Spawn* spawn, const char* command, float distance) +{ + if (GetPlayer()->WasSentSpawn(spawn->GetID())) { + PacketStruct* packet = configReader.getStruct("WS_SetDefaultCommand", GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", GetPlayer()->GetIDWithPlayerSpawn(spawn)); + packet->setMediumStringByName("command_name", command); + packet->setDataByName("distance", distance); + QueuePacket(packet->serialize()); + safe_delete(packet); + } + } +} + +bool Client::HandleHouseEntityCommands(Spawn* spawn, int32 spawnid, string command) +{ + if (GetCurrentZone()->GetInstanceType() != PERSONAL_HOUSE_INSTANCE) + return false; + + if (command == "house_spawn_examine") + { + uint32 itemID = spawn->GetPickupItemID(); + if (itemID) + { + + Item* item = master_item_list.GetItem(itemID); + if (item) + { + EQ2Packet* app = item->serialize(GetVersion(), true, GetPlayer()); + //DumpPacket(app); + QueuePacket(app); + } + + } + return true; + } + + return false; +} + +bool Client::PopulateHouseSpawn(PacketStruct* place_object) +{ + if (GetTempPlacementSpawn()) + { + Spawn* tmp = GetTempPlacementSpawn(); + + int32 spawn_group_id = database.GetNextSpawnLocation(); + tmp->SetSpawnLocationID(spawn_group_id); + + float newHeading = place_object->getType_float_ByName("heading") + 180; + + int32 spawnDBID = 0; + if (GetCurrentZone()->house_object_database_lookup.count(tmp->GetModelType()) > 0) + { + spawnDBID = GetCurrentZone()->house_object_database_lookup.Get(tmp->GetModelType()); + tmp->SetDatabaseID(spawnDBID); + } + else + { + spawnDBID = database.FindHouseInstanceSpawn(tmp); + if (spawnDBID) + { + GetCurrentZone()->house_object_database_lookup.Put(tmp->GetModelType(), spawnDBID); + tmp->SetDatabaseID(spawnDBID); + } + } + + tmp->SetX(place_object->getType_float_ByName("x")); + tmp->SetY(place_object->getType_float_ByName("y")); + tmp->SetZ(place_object->getType_float_ByName("z")); + tmp->SetHeading(newHeading); + tmp->SetSpawnOrigX(tmp->GetX()); + tmp->SetSpawnOrigY(tmp->GetY()); + tmp->SetSpawnOrigZ(tmp->GetZ()); + tmp->SetSpawnOrigHeading(tmp->GetHeading()); + + database.SaveSpawnInfo(tmp); + database.SaveSpawnEntry(tmp, "houseplacement", 100, 0, 0, 0); + + if (!spawnDBID) + { + GetCurrentZone()->house_object_database_lookup.Put(tmp->GetModelType(), tmp->GetDatabaseID()); + // we need to copy as to not delete the ZoneServer object_list entry this on house item pickup + GetCurrentZone()->AddObject(tmp->GetDatabaseID(), ((Object*)tmp)->Copy()); + } + + return true; + } + + return false; +} + +bool Client::PopulateHouseSpawnFinalize() +{ + if (GetTempPlacementSpawn()) + { + Spawn* tmp = GetTempPlacementSpawn(); + GetCurrentZone()->AddSpawn(tmp); + GetCurrentZone()->SendSpawnChanges(tmp, this); + SetTempPlacementSpawn(nullptr); + int32 uniqueID = GetPlacementUniqueItemID(); + Item* uniqueItem = GetPlayer()->item_list.GetItemFromUniqueID(uniqueID); + tmp->SetPickupItemID(uniqueItem->details.item_id); + tmp->SetPickupUniqueItemID(uniqueID); + + if (uniqueItem) + { + if (GetCurrentZone()->GetInstanceType() == PERSONAL_HOUSE_INSTANCE) + { + Query query; + query.RunQuery2(Q_INSERT, "insert into spawn_instance_data set spawn_id = %u, spawn_location_id = %u, pickup_item_id = %u, pickup_unique_item_id = %u", tmp->GetDatabaseID(), tmp->GetSpawnLocationID(), tmp->GetPickupItemID(), uniqueID); + } + + if(uniqueItem->GetItemScript() && + lua_interface->RunItemScript(uniqueItem->GetItemScript(), "placed", uniqueItem, GetPlayer(), tmp)) + { + uniqueItem = GetPlayer()->item_list.GetItemFromUniqueID(uniqueID); + } + + if(uniqueItem) { + database.DeleteItem(GetCharacterID(), uniqueItem, 0); + GetPlayer()->item_list.RemoveItem(uniqueItem, true); + QueuePacket(GetPlayer()->SendInventoryUpdate(GetVersion())); + } + + SetPlacementUniqueItemID(0); + } + return true; + } + + return false; +} + +void Client::SendMoveObjectMode(Spawn* spawn, uint8 placementMode, float unknown2_3) +{ + PacketStruct* packet = configReader.getStruct("WS_MoveObjectMode", GetVersion()); + packet->setDataByName("placement_mode", placementMode); + packet->setDataByName("spawn_id", GetPlayer()->GetIDWithPlayerSpawn(spawn)); + packet->setDataByName("model_type", spawn->GetModelType()); + packet->setDataByName("unknown", 1); //size + packet->setDataByName("unknown2", 1); //size 2 + packet->setDataByName("unknown2", .5, 1); //size 3 + packet->setDataByName("unknown2", 3, 2); + packet->setDataByName("unknown2", unknown2_3, 3); + packet->setDataByName("max_distance", 500); + packet->setDataByName("CoEunknown", 0xFFFFFFFF); + QueuePacket(packet->serialize()); + safe_delete(packet); +} + +void Client::SendFlightAutoMount(int32 path_id, int16 mount_id, int8 mount_red_color, int8 mount_green_color, int8 mount_blue_color) +{ + SetPendingFlightPath(path_id); + + ((Player*)player)->SetTempMount(((Entity*)player)->GetMount()); + ((Player*)player)->SetTempMountColor(((Entity*)player)->GetMountColor()); + ((Player*)player)->SetTempMountSaddleColor(((Entity*)player)->GetMountSaddleColor()); + + PacketStruct* packet = configReader.getStruct("WS_ReadyForTakeOff", GetVersion()); + if (packet) { + QueuePacket(packet->serialize()); + safe_delete(packet); + } + + if (mount_id) + ((Entity*)GetPlayer())->SetMount(mount_id, mount_red_color, mount_green_color, mount_blue_color); +} + +void Client::SendShowBook(Spawn* sender, string title, int8 language, int8 num_pages, ...) +{ + if (!sender) + { + LogWrite(CCLIENT__ERROR, 0, "Client", "SendShowBook missing sender for Player %s, book title %s", GetPlayer()->GetName(), title); + return; + } + + PacketStruct* packet = configReader.getStruct("WS_EqShowBook", GetVersion()); + if (!packet) { + LogWrite(CCLIENT__ERROR, 0, "Client", "WS_EqShowBook missing for version %u", GetVersion()); + return; + } + packet->setDataByName("spawn_id", GetPlayer()->GetIDWithPlayerSpawn(sender)); + packet->setDataByName("book_title", title.c_str()); + packet->setDataByName("book_type", "simple"); + packet->setDataByName("unknown2", 1); + if(language > 0 && !GetPlayer()->HasLanguage(language)) + packet->setDataByName("language", language); + + if (GetVersion() > 561) + packet->setDataByName("unknown5", 1, 4); + + packet->setArrayLengthByName("num_pages", num_pages); + + va_list args; + va_start(args, num_pages); + std::string endString(""); + for (int8 p = 0; p < num_pages; p++) + { + std::string page = va_arg(args, string); + switch (GetVersion()) + { + // release client + case 283: + case 373: // trial isle client + { + endString.append(page); + break; + } + // DoF trial + case 546: + case 561: + { + if (p == 0) + packet->setDataByName("cover_page", page.c_str()); + else + packet->setArrayDataByName("page_text", page.c_str(), p - 1); + break; + } + // all other clients + default: + { + packet->setArrayDataByName("page_text", page.c_str(), p); + break; + } + } + } + + if (GetVersion() <= 373) + { + packet->setDataByName("page_text", endString.c_str()); + } + + va_end(args); + + QueuePacket(packet->serialize()); + safe_delete(packet); +} + +void Client::SendShowBook(Spawn* sender, string title, int8 language, vector pages) +{ + if (!sender) + { + LogWrite(CCLIENT__ERROR, 0, "Client", "SendShowBook missing sender for Player %s, book title %s", GetPlayer()->GetName(), title); + return; + } + + PacketStruct* packet = configReader.getStruct("WS_EqShowBook", GetVersion()); + if (!packet) { + LogWrite(CCLIENT__ERROR, 0, "Client", "WS_EqShowBook missing for version %u", GetVersion()); + return; + } + packet->setDataByName("spawn_id", GetPlayer()->GetIDWithPlayerSpawn(sender)); + packet->setDataByName("book_title", title.c_str()); + packet->setDataByName("book_type", "simple"); + packet->setDataByName("unknown2", 1); + + if(language > 0 && !GetPlayer()->HasLanguage(language)) + packet->setDataByName("language", language); + + if (GetVersion() > 561) + packet->setDataByName("unknown5", 1, 4); + + packet->setArrayLengthByName("num_pages", pages.size()); + + std::string endString(""); + for (int8 p = 0; p < pages.size(); p++) + { + Item::BookPage* page = pages[p]; + std::string pageText = string(page->page_text.data); + switch (GetVersion()) + { + // release client + case 283: + case 373: // trial isle client + { + endString.append(pageText); + break; + } + // DoF trial + case 546: + case 561: + { + if (p == 0) + packet->setDataByName("cover_page", pageText.c_str()); + else + packet->setArrayDataByName("page_text", pageText.c_str(), p - 1); + break; + } + // all other clients + default: + { + int8 valign = int8(page->valign); + int8 halign = int8(page->halign); + packet->setArrayDataByName("page_text", pageText.c_str(), p); + packet->setArrayDataByName("page_text_valign", valign, p); + packet->setArrayDataByName("page_text_halign", halign, p); + break; + } + } + } + + if (GetVersion() == 283) + { + packet->setDataByName("page_text", endString.c_str()); + } + + QueuePacket(packet->serialize()); + safe_delete(packet); +} + +void Client::ReplaceGroupClient(Client* new_client) +{ + if (this->GetPlayer()->GetGroupMemberInfo()) + { + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + PlayerGroup* group = world.GetGroupManager()->GetGroup(this->GetPlayer()->GetGroupMemberInfo()->group_id); + if(group) + { + group->MGroupMembers.writelock(); + rejoin_group_id = this->GetPlayer()->GetGroupMemberInfo()->group_id; + this->GetPlayer()->GetGroupMemberInfo()->client = new_client; + this->GetPlayer()->GetGroupMemberInfo()->member = GetPlayer(); + group->MGroupMembers.releasewritelock(); + } + else + { + this->GetPlayer()->GetGroupMemberInfo()->client = 0; + this->GetPlayer()->GetGroupMemberInfo()->member = 0; + this->GetPlayer()->SetGroupMemberInfo(0); + } + + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } +} + +void Client::TempRemoveGroup() +{ + if (this->GetPlayer()->GetGroupMemberInfo()) + { + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + PlayerGroup* group = world.GetGroupManager()->GetGroup(this->GetPlayer()->GetGroupMemberInfo()->group_id); + if(group) + { + group->MGroupMembers.writelock(); + rejoin_group_id = this->GetPlayer()->GetGroupMemberInfo()->group_id; + this->GetPlayer()->GetGroupMemberInfo()->client = 0; + this->GetPlayer()->GetGroupMemberInfo()->member = 0; + this->GetPlayer()->SetGroupMemberInfo(0); + group->MGroupMembers.releasewritelock(); + group->RemoveClientReference(this); + } + else + { + this->GetPlayer()->GetGroupMemberInfo()->client = 0; + this->GetPlayer()->GetGroupMemberInfo()->member = 0; + this->GetPlayer()->SetGroupMemberInfo(0); + } + + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } +} + +void Client::CreateMail(int32 charID, std::string fromName, std::string subjectName, std::string mailBody, +int8 mailType, int32 copper, int32 silver, int32 gold, int32 platinum, int32 item_id, int16 stack_size, int32 time_sent, int32 expire_time) +{ + Mail mail; + mail.mail_id = 0; + mail.already_read = 0; + mail.postage_cost = 0; + mail.attachment_cost = 0; + mail.save_needed = 1; + //uhh...mail has std::strings so + //memset(&mail,0,sizeof(Mail)); + mail.player_to_id = charID; + mail.player_from = fromName; + mail.subject = subjectName; + mail.mail_body = mailBody; + + mail.mail_type = mailType; + + mail.coin_copper = copper; + mail.coin_silver = silver; + mail.coin_gold = gold; + mail.coin_plat = platinum; + + mail.char_item_id = item_id; + mail.stack = stack_size; + + mail.time_sent = time_sent; + mail.expire_time = expire_time; + + database.SavePlayerMail(&mail); +} + +void Client::CreateAndUpdateMail(std::string fromName, std::string subjectName, std::string mailBody, +int8 mailType, int32 copper, int32 silver, int32 gold, int32 platinum, int32 item_id, int16 stack_size, int32 time_sent, int32 expire_time) +{ + Mail* mail = new Mail(); + mail->player_to_id = GetCharacterID(); + mail->player_from = fromName; + mail->subject = subjectName; + mail->mail_body = mailBody; + + mail->mail_type = mailType; + + mail->coin_copper = copper; + mail->coin_silver = silver; + mail->coin_gold = gold; + mail->coin_plat = platinum; + + mail->char_item_id = item_id; + mail->stack = stack_size; + + mail->time_sent = time_sent; + mail->expire_time = expire_time; + + mail->postage_cost = 0; + mail->save_needed = 1; + mail->already_read = 0; + database.SavePlayerMail(mail); + GetPlayer()->AddMail(mail); +} + +void Client::SendEquipOrInvUpdateBySlot(int8 slot) +{ + if(slot < NUM_SLOTS) + { + EQ2Packet* app = GetPlayer()->GetEquipmentList()->serialize(GetVersion(), GetPlayer()); + if (app) + QueuePacket(app); + } + else + { + EQ2Packet* outapp = GetPlayer()->SendInventoryUpdate(GetVersion()); + if (outapp) + QueuePacket(outapp); + } +} + +void Client::QueueStateCommand(int32 spawn_player_id, int32 state) +{ + if(spawn_player_id < 1) + return; + + MQueueStateCmds.writelock(); + queued_state_commands.insert(make_pair(spawn_player_id, state)); + MQueueStateCmds.releasewritelock(); +} + +void Client::ProcessStateCommands() +{ + if(!IsReadyForUpdates()) + return; + + MQueueStateCmds.writelock(); + map::iterator itr = queued_state_commands.begin(); + for(; itr != queued_state_commands.end(); itr++) + ClientPacketFunctions::SendStateCommand(this, itr->first, itr->second); + + queued_state_commands.clear(); + MQueueStateCmds.releasewritelock(); +} + +void Client::PurgeItem(Item* item) +{ + std::unique_lock lock(MConversation); + map::iterator itr; + for(itr = conversation_items.begin(); itr != conversation_items.end(); itr++) + { + if ( itr->second == item ) + { + conversation_items.erase(itr); + break; + } + } +} + +void Client::ConsumeFoodDrink(Item* item, int32 slot) +{ + if(GetPlayer()->StopSaveSpellEffects()) + return; + + if(item) { + LogWrite(MISC__INFO, 1, "Command", "ItemID: %u, ItemName: %s ItemCount: %i ", item->details.item_id, item->name.c_str(), item->details.count); + if(item->GetItemScript() && lua_interface){ + lua_interface->RunItemScript(item->GetItemScript(), "cast", item, GetPlayer()); + if (slot == 22) { + Message(CHANNEL_NARRATIVE, "You eat a %s.", item->name.c_str()); + GetPlayer()->SetActiveFoodUniqueID(item->details.unique_id); + } + else { + Message(CHANNEL_NARRATIVE, "You drink a %s.", item->name.c_str()); + GetPlayer()->SetActiveDrinkUniqueID(item->details.unique_id); + } + } + else { + Message(CHANNEL_NARRATIVE, "SERVER BUG! Item Script not assigned for consuming '%s'.", item->name.c_str()); + return; + } + + if (item->details.count > 1) { + item->details.count -= 1; + item->save_needed = true; + } + else { + database.DeleteItem(GetPlayer()->GetCharacterID(), item, "EQUIPPED"); + GetPlayer()->GetEquipmentList()->RemoveItem(slot, true); + + } + GetPlayer()->SetCharSheetChanged(true); + QueuePacket(player->GetEquipmentList()->serialize(GetVersion(), player)); + if(GetVersion() <= 373) { + EQ2Packet* outapp = GetPlayer()->SendInventoryUpdate(GetVersion()); + QueuePacket(outapp); + } + } +} + +void Client::AwardCoins(int64 total_coins, std::string reason) +{ + if (total_coins > 0) { + player->AddCoins(total_coins); + PlaySound("coin_cha_ching"); + char tmp[64] = { 0 }; + string message = "You receive "; + int32 val = 0; + if (total_coins >= 1000000) { + val = total_coins / 1000000; + total_coins -= 1000000 * val; + sprintf(tmp, "%u Platinum ", val); + message.append(tmp); + memset(tmp, 0, 64); + } + if (total_coins >= 10000) { + val = total_coins / 10000; + total_coins -= 10000 * val; + sprintf(tmp, "%u Gold ", val); + message.append(tmp); + memset(tmp, 0, 64); + } + if (total_coins >= 100) { + val = total_coins / 100; + total_coins -= 100 * val; + sprintf(tmp, "%u Silver ", val); + message.append(tmp); + memset(tmp, 0, 64); + } + if (total_coins > 0) { + sprintf(tmp, "%u Copper ", (int32)total_coins); + message.append(tmp); + } + message.append(reason); + int8 type = CHANNEL_LOOT; + SimpleMessage(type, message.c_str()); + } +} + +void Client::TriggerSpellSave() +{ + int32 interval = rule_manager.GetGlobalRule(R_Spells, PlayerSpellSaveStateWaitInterval)->GetInt32(); + // default to not have some bogus value in the rule + if(interval < 1) + interval = 100; + + MSaveSpellStateMutex.lock(); + if(!save_spell_state_timer.Enabled()) + { + save_spell_state_time_bucket = 0; + save_spell_state_timer.Start(interval, true); + } + else + { + int32 elapsed_time = save_spell_state_timer.GetElapsedTime(); + save_spell_state_time_bucket += elapsed_time; + + int32 save_wait_cap = rule_manager.GetGlobalRule(R_Spells, PlayerSpellSaveStateCap)->GetInt32(); + + // default to not have some bogus value in the rule + if(save_wait_cap < interval) + save_wait_cap = interval+1; + + if(save_spell_state_time_bucket >= save_wait_cap) + { + save_spell_state_timer.Trigger(); + } + } + MSaveSpellStateMutex.unlock(); +} + +void Client::UpdateSentSpellList() { + MSpellDetails.readlock(__FUNCTION__, __LINE__); + std::map::iterator itr; + for(itr = sent_spell_details.begin(); itr != sent_spell_details.end(); itr++) { + Spell* spell = master_spell_list.GetSpell(itr->first, itr->second); + EQ2Packet* app = spell->SerializeSpell(this, false, false); + QueuePacket(app); + } + MSpellDetails.releasereadlock(__FUNCTION__, __LINE__); +} + +void Client::SetTempPlacementSpawn(Spawn* tmp) { + tempPlacementSpawn = tmp; + hasSentTempPlacementSpawn = false; + if(tempPlacementSpawn) + temp_placement_timer.Start(); + else + temp_placement_timer.Disable(); +} + +void Client::SetPlayer(Player* new_player) { + if (player && player != new_player) + zone_list.RemoveClientFromMap(player->GetName(), this); + + player = new_player; + player->SetClient(this); +} + +bool Client::UseItem(Item* item, Spawn* target) { + if (item && item->GetItemScript()) { + int16 item_index = item->details.index; + if(!item->CheckFlag2(INDESTRUCTABLE) && item->generic_info.condition == 0) { + SimpleMessage(CHANNEL_COLOR_RED, "This item is broken and must be repaired at a mender before it can be used"); + } + else if (item->CheckFlag(EVIL_ONLY) && GetPlayer()->GetAlignment() != ALIGNMENT_EVIL) { + Message(0, "%s requires an evil race.", item->name.c_str()); + } + else if (item->CheckFlag(GOOD_ONLY) && GetPlayer()->GetAlignment() != ALIGNMENT_GOOD) { + Message(0, "%s requires a good race.", item->name.c_str()); + } + else if (item->generic_info.max_charges == 0 || item->generic_info.max_charges == 0xFFFF) { + lua_interface->RunItemScript(item->GetItemScript(), "used", item, player, target); + return true; + } + else { + if (item->details.count > 0) { + std::string itemName = string(item->name); + int32 item_id = item->details.item_id; + sint64 flags = 0; + if(lua_interface->RunItemScript(item->GetItemScript(), "used", item, player, target, &flags) && flags >= 0) + { + //reobtain item make sure it wasn't removed + item = player->item_list.GetItemFromIndex(item_index); + if(!item) { + LogWrite(PLAYER__WARNING, 0, "Command", "%s: Item %s (%i) was used, however after the item looks to be removed.", GetPlayer()->GetName(), itemName.c_str(), item_id); + return true; + } + else if(!item->generic_info.display_charges && item->generic_info.max_charges == 1) { + Message(CHANNEL_NARRATIVE, "%s is out of charges. It has been removed.", item->name.c_str()); + RemoveItem(item, 1); // end of a set of charges OR an item that uses a stack count of actual item quantity + return true; + } + else + { + item->details.count--; // charges + item->save_needed = true; + QueuePacket(item->serialize(GetVersion(), false, GetPlayer())); + if(!item->details.count) { + Message(CHANNEL_NARRATIVE, "%s is out of charges. It has been removed.", item->name.c_str()); + RemoveItem(item, 1); // end of a set of charges OR an item that uses a stack count of actual item quantity + } + return true; + } + } + else { + LogWrite(PLAYER__WARNING, 0, "Command", "%s: Item %s (%i) was used, after it returned %i, bypassing any removal/update of items.", GetPlayer()->GetName(), itemName.c_str(), item_id, flags); + return true; + } + } + else + { + //reobtain item make sure it wasn't removed + item = player->item_list.GetItemFromIndex(item_index); + SimpleMessage(CHANNEL_COLOR_YELLOW, "Item is out of charges."); + if(item) { + LogWrite(PLAYER__ERROR, 0, "Command", "%s: Item %s (%i) attempted to be used, however details.count is 0.", GetPlayer()->GetName(), item->name.c_str(), item->details.item_id); + } + } + } + } + return false; +} + +void Client::SendPlayFlavor(Spawn* spawn, int8 language, VoiceOverStruct* non_garble, + VoiceOverStruct* garble, bool success, bool garble_success) { + VoiceOverStruct* resStruct = nullptr; + + if(language == 0 || GetPlayer()->HasLanguage(language)) { + if(success) { + resStruct = non_garble; + } + } + else if(garble_success) { + resStruct = garble; + } + + if(resStruct) { + GetPlayer()->GetZone()->PlayFlavor(this, spawn, resStruct->mp3_string.c_str(), resStruct->text_string.c_str(), resStruct->emote_string.c_str(), resStruct->key1, resStruct->key2, language); + } +} + +void Client::SaveQuestRewardData(bool force_refresh) { + Query query; + if(force_refresh) { + query.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete from character_quest_rewards where char_id = %u", + GetCharacterID()); + + query.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete from character_quest_temporary_rewards where char_id = %u", + GetCharacterID()); + } + vector::iterator itr; + vector tmp_quest_rewards; + MQuestPendingUpdates.writelock(__FUNCTION__, __LINE__); + int index = 0; + for (itr = quest_pending_reward.begin(); itr != quest_pending_reward.end(); itr++) { + int32 questID = (*itr)->quest_id; + if(!(*itr)->db_saved || force_refresh) { + query.AddQueryAsync(GetCharacterID(), &database, Q_REPLACE, "replace into character_quest_rewards (char_id, indexed, quest_id, is_temporary, is_collection, has_displayed, tmp_coin, tmp_status, description) values(%u, %u, %u, %u, %u, %u, %llu, %u, '%s')", + GetCharacterID(), index, questID, (*itr)->is_temporary, (*itr)->is_collection, (*itr)->has_displayed, (*itr)->tmp_coin, (*itr)->tmp_status, database.getSafeEscapeString((*itr)->description.c_str()).c_str()); + (*itr)->db_saved = true; + (*itr)->db_index = index; + if(questID && (*itr)->is_temporary) { + std::vector items; + GetPlayer()->GetQuestTemporaryRewards(questID, &items); + if(!force_refresh && items.size() > 0) { + query.AddQueryAsync(GetCharacterID(), &database, Q_REPLACE, "delete from character_quest_temporary_rewards where char_id = %u and quest_id = %u", + GetCharacterID(), questID); + } + for(int i=0;idetails.item_id, items[i]->details.count); + } + } + } + index++; + } + MQuestPendingUpdates.releasewritelock(__FUNCTION__, __LINE__); +} + +void Client::UpdateCharacterRewardData(QuestRewardData* data) { + + if(!data) + return; + if(data->db_saved) { + Query query; + query.AddQueryAsync(GetCharacterID(), &database, Q_INSERT, "update character_quest_rewards set is_temporary = %u, is_collection = %u, has_displayed = %u, tmp_coin = %llu, tmp_status = %u, description = '%s' where char_id=%u and indexed=%u and quest_id=%u", + data->is_temporary, data->is_collection, data->has_displayed, data->tmp_coin, data->tmp_status, database.getSafeEscapeString(data->description.c_str()).c_str(), GetCharacterID(), data->db_index, data->quest_id); + } +} + +void Client::AddRecipeToPlayerPack(Recipe* recipe, PacketStruct* packet, int16* i) { + + + int index = 0; + if(recipe == nullptr) + return; + + PlayerRecipeList* prl = GetPlayer()->GetRecipeList(); + if (prl->GetRecipe(recipe->GetID())) { + delete recipe; + return; + } + auto res = std::find(devices.begin(), devices.end(), recipe->GetDevice()); + + if (res != devices.end()) + index = res - devices.begin(); + else + devices.push_back(recipe->GetDevice()); + + + prl->AddRecipe(recipe); + database.SavePlayerRecipe(GetPlayer(), recipe->GetID()); + Message(CHANNEL_NARRATIVE, "Recipe: \"%s\" put in recipe book.", recipe->GetName()); + + if (packet && GetRecipeListSent()) { + packet->setArrayDataByName("id", recipe->GetID(), *i); + packet->setArrayDataByName("tier", recipe->GetTier(), *i); + packet->setArrayDataByName("level", recipe->GetLevel(), *i); + packet->setArrayDataByName("icon", recipe->GetIcon(), *i); + packet->setArrayDataByName("classes", recipe->GetClasses(), *i); + //packet->setArrayDataByName("skill", recipe->GetSkill(), *i); + packet->setArrayDataByName("technique", recipe->GetTechnique(), *i); + packet->setArrayDataByName("knowledge", recipe->GetKnowledge(), *i); + auto recipe_device = std::find(devices.begin(), devices.end(), recipe->GetDevice()); + if (recipe_device != devices.end()) + packet->setArrayDataByName("device_type", recipe_device - devices.begin(), *i); + else + {//TODO error should never get here + } + packet->setArrayDataByName("device_sub_type", recipe->GetDevice_Sub_Type(), *i); + packet->setArrayDataByName("recipe_name", recipe->GetName(), *i); + packet->setArrayDataByName("recipe_book", recipe->GetBook(), *i); + packet->setArrayDataByName("unknown3", recipe->GetUnknown3(), *i); + if(i) { + (*i)++; + } + } +} + +bool Client::SetPlayerPOVGhost(Spawn* spawn) { + if(!spawn) { + pov_ghost_spawn_id = 0; + SendCharPOVGhost(); + return true; + } + + int32 ghost_id = player->GetIDWithPlayerSpawn(spawn); + if(ghost_id) { + pov_ghost_spawn_id = spawn->GetID(); + SendCharPOVGhost(); + return true; + } + + return false; +} + +void Client::HandleDialogSelectMsg(int32 conversation_id, int32 response_index) { + std::string conversation = ""; + bool conv_established = false; + MConversation.lock_shared(); + if (conversation_map.count(conversation_id) > 0 && conversation_map[conversation_id].count(response_index) > 0) { + conversation = std::string(conversation_map[conversation_id][response_index].c_str()); + conv_established = true; + } + + int32 spawn_id = conversation_spawns[conversation_id]; + + Item* item = conversation_items[conversation_id]; + MConversation.unlock_shared(); + + if (GetCurrentZone()) { + Spawn* spawn = nullptr; + if(spawn_id) { + spawn = GetCurrentZone()->GetSpawnByID(spawn_id); + } + + if (conv_established) { + if (spawn) { + if(conversation == "CloseItemConversation") { + LogWrite(LUA__ERROR, 0, "LUA", "CloseItemConversation is an invalid function call for this conversation with spawn id %u", spawn_id); + } + else { + GetCurrentZone()->CallSpawnScript(spawn, SPAWN_SCRIPT_CONVERSATION, player, conversation.c_str()); + } + } + else if (item && lua_interface && item->GetItemScript()) + lua_interface->RunItemScript(item->GetItemScript(), conversation.c_str(), item, player); + else + CloseDialog(conversation_id); + } + else + CloseDialog(conversation_id); + } +} + + +bool Client::SetPetName(const char* petName) { + int8 result = database.CheckNameFilter(petName,4,31); + if (result == BADNAMELENGTH_REPLY) { + SimpleMessage(CHANNEL_COLOR_YELLOW, "Name length is invalid, must be greater then 3 characters and less then 31."); + return false; + } + else if (result == NAMEINVALID_REPLY) { + SimpleMessage(CHANNEL_COLOR_YELLOW, "Name is invalid, can only contain letters."); + return false; + } + else if (result == NAMETAKEN_REPLY) { + SimpleMessage(CHANNEL_COLOR_YELLOW, "Name is already taken, please choose another."); + return false; + } + else if (result == NAMEFILTER_REPLY) { + SimpleMessage(CHANNEL_COLOR_YELLOW, "Name failed the filter check."); + return false; + } + else if (result == UNKNOWNERROR_REPLY) { + SimpleMessage(CHANNEL_COLOR_YELLOW, "Unknown error while checking the name."); + return false; + } + + GetPlayer()->GetInfoStruct()->set_pet_name(petName); + return true; +} + +bool Client::CheckConsumptionAllowed(int16 slot, bool send_message) { + switch(slot) { + case EQ2_FOOD_SLOT: { + if(GetPlayer()->GetSpellEffectBySpellType(SPELL_TYPE_FOOD)) { + if(send_message) { + Message(CHANNEL_NARRATIVE, "If you ate anymore you would explode!"); + } + return false; + } + break; + } + case EQ2_DRINK_SLOT: { + if (GetPlayer()->GetSpellEffectBySpellType(SPELL_TYPE_DRINK)) + { + if(send_message) { + Message(CHANNEL_NARRATIVE, "If you drank anymore you would explode!"); + } + return false; + } + break; + } + default: { + if (GetVersion() <= 373) { + Item* item = GetPlayer()->item_list.GetItemFromIndex(slot); + if(item->IsFood()) { + if(item->IsFoodFood()) { + if(GetPlayer()->GetSpellEffectBySpellType(SPELL_TYPE_FOOD)) { + if(send_message) { + Message(CHANNEL_NARRATIVE, "If you ate anymore you would explode!"); + } + return false; + } + return true; + } + else if(item->IsFoodDrink()) { + if (GetPlayer()->GetSpellEffectBySpellType(SPELL_TYPE_DRINK)) + { + if(send_message) { + Message(CHANNEL_NARRATIVE, "If you drank anymore you would explode!"); + } + return false; + } + return true; + } + } + } + return false; + break; + } + } + + return true; +} + +void Client::StartLinkdeadTimer() { + if(!linkdead_timer) { + int32 LD_Timer = rule_manager.GetGlobalRule(R_World, LinkDeadTimer)->GetInt32(); + LogWrite(CCLIENT__DEBUG, 0, "Client", "Starting linkdead timer for %s (timer %u seconds)", GetPlayer()->GetName(), (LD_Timer/1000)); + linkdead_timer = new Timer(LD_Timer); + linkdead_timer->Enable(); + + if(GetPlayer()->GetGroupMemberInfo()) { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Telling player %s group they are disconnecting", GetPlayer()->GetName()); + world.GetGroupManager()->GroupMessage(GetPlayer()->GetGroupMemberInfo()->group_id, "%s has gone Linkdead.", GetPlayer()->GetName()); + } + } +} + +bool Client::IsLinkdeadTimerEnabled() { + if(linkdead_timer) { + return linkdead_timer->Enabled(); + } + + return false; +} + +void Client::SendNewAdventureSpells() { + SendNewSpells(player->GetAdventureClass()); + int8 base_class = classes.GetBaseClass(player->GetAdventureClass()); + int secondary_class = classes.GetSecondaryBaseClass(player->GetAdventureClass()); + if(base_class != player->GetAdventureClass()) { + SendNewSpells(base_class); + } + if(secondary_class != player->GetAdventureClass() && secondary_class != base_class) { + SendNewSpells(secondary_class); + } +} + +void Client::SendNewTradeskillSpells() { + SendNewTSSpells(player->GetTradeskillClass()); + int8 secondary_class = classes.GetSecondaryTSBaseClass(player->GetTradeskillClass()); + if(secondary_class != player->GetTradeskillClass()) { + SendNewTSSpells(secondary_class); + } +} + +bool Client::AddRecipeBookToPlayer(int32 recipe_book_id, Item* item) { + Recipe* master_recipe = master_recipebook_list.GetRecipeBooks(recipe_book_id); + + if(master_recipe) { + Recipe* recipe_book = new Recipe(master_recipe); + // if valid recipe book and the player doesn't have it + if (recipe_book && recipe_book->GetLevel() > GetPlayer()->GetTSLevel()) { + if(item) { + Message(CHANNEL_NARRATIVE, "Your tradeskill level is not high enough to scribe this book."); + } + safe_delete(recipe_book); + } + else if(recipe_book && item && !recipe_book->CanUseRecipeByClass(item, GetPlayer()->GetTradeskillClass())) { + if(item) { + Message(CHANNEL_NARRATIVE, "Your tradeskill class cannot use this recipe."); + } + safe_delete(recipe_book); + } + else if (recipe_book && (!item || !(GetPlayer()->GetRecipeBookList()->HasRecipeBook(recipe_book_id)))){ + LogWrite(PLAYER__DEBUG, 0, "Recipe", "Valid recipe book that the player doesn't have"); + // Add recipe book to the players list + if(!GetPlayer()->GetRecipeBookList()->HasRecipeBook(recipe_book_id)) { + GetPlayer()->GetRecipeBookList()->AddRecipeBook(recipe_book); + } + + std::vector recipes; + // Get a list of all recipes this book contains + if (item && item->recipebook_info) { + //Backup I guess if the recipe book is empty for whatever reason? + for (auto& itr : item->recipebook_info->recipes) { + Recipe* r = master_recipe_list.GetRecipeByCRC(itr); //GetRecipeByName(itr.c_str()); + if (r) { + recipes.push_back(r); + } + } + LogWrite(PLAYER__DEBUG, 0, "Recipe", "%i recipes found for %s book", recipes.size(), recipe_book->GetBookName()); + } + else { + LogWrite(PLAYER__ERROR, 0, "Recipe", "no recipes found for %s book", recipe_book->GetBookName()); + } + + //Filter out duplicate recipes the player already has + for (auto itr = recipes.begin(); itr != recipes.end();) { + Recipe* recipe = *itr; + if (GetPlayer()->GetRecipeList()->GetRecipe(recipe->GetID())) { + itr = recipes.erase(itr); + } + else itr++; + } + + int16 i = 0; + // Create the packet to send to update the players recipe list + PacketStruct* packet = 0; + if (!recipes.empty() && GetRecipeListSent()) { + packet = configReader.getStruct("WS_RecipeList", GetVersion()); + if (packet) { + packet->setArrayLengthByName("num_recipes", recipes.size()); + } + } + + for (int32 r = 0; r < recipes.size(); r++) { + Recipe* recipe = recipes[r]; + if (recipe) { + Recipe* player_recipe = new Recipe(recipe); + AddRecipeToPlayerPack(player_recipe, packet, &i); + } + } + + LogWrite(TRADESKILL__DEBUG, 0, "Recipe", "Done adding recipes"); + database.SavePlayerRecipeBook(GetPlayer(), recipe_book->GetBookID()); + if(item) { + database.DeleteItem(GetCharacterID(), item, 0); + GetPlayer()->item_list.RemoveItem(item, true); + } + QueuePacket(GetPlayer()->SendInventoryUpdate(GetVersion())); + + SetRecipeListSent(false); + SendRecipeList(); + safe_delete(packet); + return true; + } + else { + if (recipe_book && item) { + Message(CHANNEL_NARRATIVE, "You have already learned all you can from this item."); + } + safe_delete(recipe_book); + } + } + else { + LogWrite(PLAYER__ERROR, 0, "Player", "%u recipe book id does not exist. Cannot AddRecipeToPlayer.", recipe_book_id); + } + return false; +} + + +bool Client::RemoveRecipeFromPlayer(int32 recipe_id) { + PlayerRecipeList* prl = GetPlayer()->GetRecipeList(); + + PacketStruct* packet = configReader.getStruct("WS_RecipeList", version); + Recipe* recipe = prl->GetRecipe(recipe_id); + int8 level = player->GetTSLevel(); + if(packet && recipe) { + packet->setDataByName("command_type", 1); + packet->setArrayLengthByName("num_recipes", 1); + int32 myid = recipe->GetID(); + int8 rlevel = recipe->GetLevel(); + int8 even = level - level * .05 + .5; + int8 easymin = level - level * .25 + .5; + int8 veasymin = level - level * .35 + .5; + if (rlevel > level ) + packet->setArrayDataByName("tier", 4, 0); + else if ((rlevel <= level) & (rlevel >= even)) + packet->setArrayDataByName("tier", 3, 0); + else if ((rlevel <= even) & (rlevel >= easymin)) + packet->setArrayDataByName("tier", 2, 0); + else if ((rlevel <= easymin) & (rlevel >= veasymin)) + packet->setArrayDataByName("tier", 1, 0); + else if ((rlevel <= veasymin) & (rlevel >= 0)) + packet->setArrayDataByName("tier", 0, 0); + if (rlevel == 2) + int xxx = 1; + packet->setArrayDataByName("recipe_id", myid, 0); + packet->setArrayDataByName("level", recipe->GetLevel(), 0); + packet->setArrayDataByName("unknown1", recipe->GetLevel(), 0); + packet->setArrayDataByName("icon", recipe->GetIcon(), 0); + packet->setArrayDataByName("classes", recipe->GetClasses(), 0); + packet->setArrayDataByName("technique", recipe->GetTechnique(), 0); + packet->setArrayDataByName("knowledge", recipe->GetKnowledge(), 0); + + + auto recipe_device = std::find(devices.begin(), devices.end(), recipe->GetDevice()); + if (recipe_device != devices.end()) + packet->setArrayDataByName("device_type", recipe_device - devices.begin(), 0); + else + {//TODO error should never get here + } + packet->setArrayDataByName("device_sub_type", recipe->GetDevice_Sub_Type(), 0); + packet->setArrayDataByName("recipe_name", recipe->GetName(), 0); + packet->setArrayDataByName("recipe_book", recipe->GetBook(), 0); + packet->setArrayDataByName("unknown3", recipe->GetUnknown3(), 0); + QueuePacket(packet->serialize()); + } + safe_delete(packet); + + bool res = prl->RemoveRecipe(recipe_id); + if(res) { + Query query; + query.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "DELETE FROM character_recipes where char_id=%u and recipe_id=%u", GetCharacterID(), recipe_id); + } + return res; +} + +void Client::SaveSpells() { + MSaveSpellStateMutex.lock(); + player->SaveSpellEffects(); + player->SetSaveSpellEffects(true); + MSaveSpellStateMutex.unlock(); +} + +void Client::SendReplaceWidget(int32 widget_id, bool delete_widget, float x, float y, float z, int32 grid_id) { + Widget* new_spawn = new Widget(); + new_spawn->SetWidgetID(widget_id); + new_spawn->SetLocation(grid_id); + new_spawn->SetWidgetX(x); + new_spawn->SetWidgetY(y); + new_spawn->SetWidgetZ(z); + new_spawn->SetX(x); + new_spawn->SetY(y); + new_spawn->SetZ(z); + + EQ2Packet* ret = new_spawn->serialize(GetPlayer(), GetVersion()); + QueuePacket(ret); + + // we have to delete spawn* references anyway, we don't keep this widget live in the spawn list + GetPlayer()->RemoveSpawn(new_spawn, delete_widget); + + safe_delete(new_spawn); +} + +void Client::ProcessZoneIgnoreWidgets() { + GetPlayer()->MIgnoredWidgets.lock_shared(); + std::map::iterator itr; + for(itr = GetPlayer()->ignored_widgets.begin(); itr != GetPlayer()->ignored_widgets.end(); itr++) { + SendReplaceWidget(itr->first, true); + } + GetPlayer()->MIgnoredWidgets.unlock_shared(); +} + +void Client::PopulateRecipeData(Recipe* recipe, PacketStruct* packet, int i) { + if(!recipe || !packet) + return; + + int8 level = player->GetTSLevel(); + int32 myid = recipe->GetID(); + int8 rlevel = recipe->GetLevel(); + int8 even = level - level * .05 + .5; + int8 easymin = level - level * .25 + .5; + int8 veasymin = level - level * .35 + .5; + if (rlevel > level ) + packet->setArrayDataByName("tier", 4, i); + else if ((rlevel <= level) & (rlevel >= even)) + packet->setArrayDataByName("tier", 3, i); + else if ((rlevel <= even) & (rlevel >= easymin)) + packet->setArrayDataByName("tier", 2, i); + else if ((rlevel <= easymin) & (rlevel >= veasymin)) + packet->setArrayDataByName("tier", 1, i); + else if ((rlevel <= veasymin) & (rlevel >= 0)) + packet->setArrayDataByName("tier", 0, i); + if (rlevel == 2) + int xxx = 1; + packet->setArrayDataByName("recipe_id", myid, i); + packet->setArrayDataByName("level", recipe->GetLevel(), i); + packet->setArrayDataByName("icon", recipe->GetIcon(), i); + packet->setArrayDataByName("classes", recipe->GetClasses(), i); + packet->setArrayDataByName("technique", recipe->GetTechnique(), i); + packet->setArrayDataByName("knowledge", recipe->GetKnowledge(), i); + packet->setArrayDataByName("device", recipe->GetDevice(), i); + packet->setArrayDataByName("device_sub_type", recipe->GetDevice_Sub_Type(), i); + packet->setArrayDataByName("recipe_name", recipe->GetName(), i); + packet->setArrayDataByName("recipe_book", recipe->GetBook(), i); + packet->setArrayDataByName("unknown3", recipe->GetUnknown3(), i); + + packet->setArrayDataByName("book_volume", 0x01, i); + packet->setArrayDataByName("device_id", 0x01, i); +} + +int32 Client::GetRecipeCRC(Recipe* recipe) { + + PacketStruct* packet = 0; + if (!(packet = configReader.getStruct("WS_RecipeDetailList", GetVersion()))) { + return 0; + } + packet->setArrayLengthByName("num_recipes", 1); + + PopulateRecipeData(recipe, packet); + + string* generic_string_data = packet->serializeString(); + int32 size = generic_string_data->length(); + + if(size < 5) + return 0; + + uchar* out_data = new uchar[size+1]; + uchar* out_ptr = out_data; + memcpy(out_ptr, (uchar*)generic_string_data->c_str()+4, generic_string_data->length()-4); + uint32 out_crc = GenerateCRCRecipe(0, (void*)out_ptr, size-4); + safe_delete(packet); + safe_delete_array(out_data); + + return out_crc; +} + +void Client::SendRecipeDetails(vector* recipes) { + if(!recipes || recipes->size() == 0) + return; + + PacketStruct* packet = 0; + if (!(packet = configReader.getStruct("WS_RecipeDetailList", GetVersion()))) { + return; + } + int32 recipe_size = player->GetRecipeList()->Size(); + packet->setArrayLengthByName("num_recipes", recipe_size > 100 ? 100 : recipe_size); + int16 i = 0; + int32 count = 0; + vector::iterator recipe_itr; + for(recipe_itr = recipes->begin(); recipe_itr != recipes->end(); recipe_itr++) { + Recipe* recipe = player->GetRecipeList()->GetRecipe(*recipe_itr); + if(!recipe) { + continue; + } + else if(i > 99) { + QueuePacket(packet->serialize()); + safe_delete(packet); + packet = configReader.getStruct("WS_RecipeDetailList", GetVersion()); + recipe_size -= i; + packet->setArrayLengthByName("num_recipes", recipe_size > 100 ? 100 : recipe_size); + i = 0; + } + count++; + PopulateRecipeData(recipe, packet, i); + i++; + } + + //packet->PrintPacket(); + + QueuePacket(packet->serialize()); + safe_delete(packet); +} + +ZoneServer* Client::GetHouseZoneServer(int32 spawn_id, int64 house_id) { + PlayerHouse* ph = nullptr; + HouseZone* hz = nullptr; + if(spawn_id) { + Spawn* houseWidget = GetPlayer()->GetSpawnByIndex(spawn_id); + if(houseWidget && houseWidget->IsWidget() && ((Widget*)houseWidget)->GetHouseID()) { + hz = world.GetHouseZone(((Widget*)houseWidget)->GetHouseID()); + if (hz) { + ph = world.GetPlayerHouseByHouseID(GetPlayer()->GetCharacterID(), hz->id); + } else { + Message(CHANNEL_COLOR_YELLOW, "HouseWidget spawn index %u house zone could not be found.", spawn_id); + } + } + } + + if(!ph && house_id) { + ph = world.GetPlayerHouseByUniqueID(house_id); + if (ph) { + hz = world.GetHouseZone(ph->house_id); + } + } + + if (ph && hz) { + ZoneServer* house = zone_list.GetByInstanceID(ph->instance_id, hz->zone_id, false, true); + return house; + } + + return nullptr; +} + +void Client::SendHearCast(Spawn* caster, Spawn* target, int32 spell_visual, int16 cast_time) { + PacketStruct* packet = configReader.getStruct("WS_HearCastSpell", GetVersion()); + if (packet) { + int32 caster_id = GetPlayer()->GetIDWithPlayerSpawn(caster); + int32 target_id = GetPlayer()->GetIDWithPlayerSpawn(target); + + packet->setDataByName("spawn_id", caster_id); + packet->setArrayLengthByName("num_targets", 1); + packet->setArrayDataByName("target", target_id); + packet->setDataByName("num_targets", 1); + + int32 visual = GetSpellVisualOverride(spell_visual); + + packet->setDataByName("spell_visual", visual); //result + packet->setDataByName("cast_time", cast_time*.01f); //delay + packet->setDataByName("spell_id", 1); + packet->setDataByName("spell_level", 1); + packet->setDataByName("spell_tier", 1); + EQ2Packet* outapp = packet->serialize(); + + DumpPacket(outapp); + QueuePacket(outapp); + safe_delete(packet); + } +} + +int32 Client::GetSpellVisualOverride(int32 spell_visual) { + int32 visual = spell_visual; + if(GetVersion() <= 561) { // spell's spell_visual field is based on newer clients, DoF has to convert + Emote* spellVisualEmote = visual_states.FindSpellVisualByID(visual, 60085); + if(spellVisualEmote != nullptr && spellVisualEmote->GetMessageString().size() > 0) { + spellVisualEmote = visual_states.FindSpellVisual(spellVisualEmote->GetMessageString(), GetVersion()); + if(spellVisualEmote) { + visual = (int32)spellVisualEmote->GetVisualState(); + } + } + } + + return visual; +} \ No newline at end of file diff --git a/source/WorldServer/client.h b/source/WorldServer/client.h new file mode 100644 index 0000000..805a35b --- /dev/null +++ b/source/WorldServer/client.h @@ -0,0 +1,767 @@ +/* + 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 . +*/ +#ifndef CLIENT_H +#define CLIENT_H + +#include +#include +#include +#include + +#include "../common/EQStream.h" +#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; +struct VoiceOverStruct; + +#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{ + int32 quest_id; + 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; + Item* item; + int32 char_item_id; + int32 stack; +}; + +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; +}; +struct WaypointInfo { + int32 id; + int8 type; +}; + + +class Client { +public: + Client(EQStream* ieqs); + ~Client(); + + void RemoveClientFromZone(); + 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(); + int8 GetMessageChannelColor(int8 channel_type); + void HandleTellMessage(Client* from, const char* message, const char* to, int32 current_language_id); + void SimpleMessage(int8 color, const char* message); + void Message(int8 type, const char* message, ...); + void SendSpellUpdate(Spell* spell, bool add_silently = false, bool add_to_hotbar = true); + void Zone(ZoneServer* new_zone, bool set_coords = true, bool is_spell = false); + void Zone(const char* new_zone, bool set_coords = true, bool is_spell = false); + void Zone(int32 instanceid, bool set_coords = true, bool byInstanceID=false, bool is_spell = false); + void SendZoneInfo(); + void SendZoneSpawns(); + void HandleVerbRequest(EQApplicationPacket* app); + void SendControlGhost(int32 send_id=0xFFFFFFFF, int8 unknown2=0); + void SendCharInfo(); + void SendLoginDeniedBadVersion(); + void SendCharPOVGhost(); + void SendPlayerDeathWindow(); + float DistanceFrom(Client* client); + void SendDefaultGroupOptions(); + bool HandleLootItemByID(Spawn* entity, int32 item_id, Spawn* target); + bool HandleLootItem(Spawn* entity, Item* item, Spawn* target=nullptr, bool overrideLootRestrictions = false); + void HandleLootItemRequestPacket(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); + std::string IdentifyInstanceLockout(int32 zoneID, bool displayClient = true); + ZoneServer* IdentifyInstance(int32 zoneID); + 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); + bool BankWithdrawalNoBanker(int64 amount); + bool BankHasCoin(int64 amount); + void BankDeposit(int64 amount); + Spawn* GetBanker(); + void SetBanker(Spawn* in_banker); + bool AddItem(int32 item_id, int16 quantity = 0, AddItemType type = AddItemType::NOT_SET); + bool AddItem(Item* item, bool* item_deleted = 0, AddItemType type = AddItemType::NOT_SET); + bool AddItemToBank(int32 item_id, int16 quantity = 0); + bool AddItemToBank(Item* item); + void UnequipItem(int16 index, sint32 bag_id = -999, int8 to_slot = 255, int8 appearance_equip = 0); + bool RemoveItem(Item *item, int16 quantity, bool force_override_no_delete = false); + void ProcessTeleport(Spawn* spawn, vector* destinations, int32 transport_id = 0, bool is_spell = false); + 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); + void SetZoningDestination(ZoneServer* zone) { + zoning_destination = zone; + } + ZoneServer* GetZoningDestination() { return zoning_destination; } + Player* GetPlayer(){ return player; } + EQStream* getConnection(){ return eqs; } + void setConnection(EQStream* ieqs){ eqs = ieqs; } + + 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(int32 spawn_id); + void SendLootResponsePacket(int32 total_coins, vector* items, Spawn* entity, bool ignore_loot_tier = false); + void LootSpawnRequest(Spawn* entity, bool attemptDisarm=true); + bool LootSpawnByMethod(Spawn* entity); + 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, bool forced = false); + void AcceptQuest(int32 quest_id); + void RemovePendingQuest(int32 quest_id); + void SetPlayerQuest(Quest* quest, map* 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, bool updated = true); + 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 DisplayQuestRewards(Quest* quest, int64 coin, vector* rewards=0, vector* selectable_rewards=0, map* factions=0, const char* header="Quest Reward!", int32 status_points=0, const char* text=0, bool was_displayed = false); + void PopulateQuestRewardItems(vector * items, PacketStruct* packet, std::string num_rewards_str = "num_rewards", std::string reward_id_str = "reward_id" , std::string item_str = "item"); + void DisplayQuestComplete(Quest* quest, bool tempReward = false, std::string customDescription = string(""), bool was_displayed = false); + void DisplayRandomizeFeatures(int32 features); + void AcceptQuestReward(Quest* quest, int32 item_id); + Quest* GetPendingQuestAcceptance(int32 item_id); + void DisplayConversation(int32 conversation_id, int32 spawn_id, vector* conversations, const char* text, const char* mp3, int32 key1, int32 key2, int8 language = 0, int8 can_close = 1); + void DisplayConversation(Item* item, vector* conversations, const char* text, int8 type, const char* mp3 = 0, int32 key1 = 0, int32 key2 = 0, int8 language = 0, int8 can_close = 1); + void DisplayConversation(Spawn* src, int8 type, vector* conversations, const char* text, const char* mp3 = 0, int32 key1 = 0, int32 key2 = 0, int8 language = 0, int8 can_close = 1); + 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* GetBuyBacks(); + vector* GetRepairableItems(); + vector* GetItemsByEffectType(ItemEffectType type, ItemEffectType secondary_effect = NO_EFFECT_TYPE); + void SendMailList(); + void DisplayMailMessage(int32 mail_id); + void HandleSentMail(EQApplicationPacket* app); + void DeleteMail(int32 mail_id, bool from_database = false); + bool AddMailItem(Item* item); + 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 ResetSendMail(bool cancel = true, bool needslock = true); + bool GateAllowed(); + bool BindAllowed(); + bool Bind(); + bool Gate(bool is_spell = false); + void SendChatRelationship(int8 type, const char* name); + void SendFriendList(); + void SendIgnoreList(); + void SendNewAdventureSpells(); + void SendNewTradeskillSpells(); + string GetCoinMessage(int32 total_coins); + void SetItemSearch(vector* items); + vector* GetSearchItems(); + void SearchStore(int32 page); + void SetPlayer(Player* new_player); + + void AddPendingQuestAcceptReward(Quest* quest); + void AddPendingQuestReward(Quest* quest, bool update=true, bool is_temporary = false, std::string description = std::string("")); + bool HasQuestRewardQueued(int32 quest_id, bool is_temporary, bool is_collection); + void QueueQuestReward(int32 quest_id, bool is_temporary, bool is_collection, bool has_displayed, int64 tmp_coin, int32 tmp_status, std::string description, bool db_saved=false, int32 index=0); + void RemoveQueuedQuestReward(); + 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 PopulateRecipeData(Recipe* recipe, PacketStruct* packet, int i=0); + int32 GetRecipeCRC(Recipe* recipe); + void SendRecipeDetails(vector* recipes); + void SendTitleUpdate(); + void SendUpdateTitles(sint32 prefix, sint32 suffix); + void SendLanguagesUpdate(int32 id, bool setlang = 1); + void SendAchievementsList(); + void SendAchievementUpdate(bool first_login = false); + + ///Send the pet options window to the client + ///Type of pet, 1 = combat 0 = non combat + 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, bool updated=true); + + 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& spawns); + void MakeSpawnChangePacket(map info_changes, map pos_changes, map 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); + + 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 language, int8 num_pages, ...); + void SendShowBook(Spawn* sender, string title, int8 language, vector pages); + + void SetTemporaryTransportID(int32 id) { temporary_transport_id = id; } + int32 GetTemporaryTransportID() { return temporary_transport_id; } + + void SetRejoinGroupID(int32 id) { rejoin_group_id = id; } + + void TempRemoveGroup(); + void ReplaceGroupClient(Client* new_client); + + void SendWaypoints(); + + void AddWaypoint(string name, int8 type); + void RemoveWaypoint(string name) { + if (waypoints.count(name) > 0){ + waypoints.erase(name); + } + } + void SelectWaypoint(int32 id); + void ClearWaypoint(); + bool ShowPathToTarget(float x, float y, float z, float y_offset); + bool ShowPathToTarget(Spawn* spawn); + + void SetRegionDebug(bool val) { regionDebugMessaging = val; } + + static void CreateMail(int32 charID, std::string fromName, std::string subjectName, std::string mailBody, + int8 mailType, int32 copper, int32 silver, int32 gold, int32 platinum, int32 item_id, int16 stack_size, int32 time_sent, int32 expire_time); + void CreateAndUpdateMail(std::string fromName, std::string subjectName, std::string mailBody, + int8 mailType, int32 copper, int32 silver, int32 gold, int32 platinum, int32 item_id, int16 stack_size, int32 time_sent, int32 expire_time); + + void SendEquipOrInvUpdateBySlot(int8 slot); + + void SetReloadingZone(bool val) { client_reloading_zone = val; } + bool IsReloadingZone() { return client_reloading_zone; } + + void QueueStateCommand(int32 spawn_player_id, int32 state); + void ProcessStateCommands(); + void PurgeItem(Item* item); + void ConsumeFoodDrink(Item* item, int32 slot); + void AwardCoins(int64 total_coins, std::string reason = string("")); + + void TriggerSpellSave(); + + void ClearSentItemDetails() { + MItemDetails.writelock(__FUNCTION__, __LINE__); + sent_item_details.clear(); + MItemDetails.releasewritelock(__FUNCTION__, __LINE__); + } + + bool IsPlayerLoadingComplete() { return player_loading_complete; } + + int32 GetRejoinGroupID() { return rejoin_group_id; } + + void ClearSentSpellList() { + MSpellDetails.writelock(__FUNCTION__, __LINE__); + sent_spell_details.clear(); + MSpellDetails.releasewritelock(__FUNCTION__, __LINE__); + } + + void UpdateSentSpellList(); + + bool CountSentSpell(int32 id, int32 tier) { + bool res = false; + MSpellDetails.readlock(__FUNCTION__, __LINE__); + std::map::iterator itr = sent_spell_details.find(id); + if(itr != sent_spell_details.end() && itr->second == tier) + res = true; + MSpellDetails.releasereadlock(__FUNCTION__, __LINE__); + return res; + } + + void SetSentSpell(int32 id, int32 tier) { + MSpellDetails.writelock(__FUNCTION__, __LINE__); + sent_spell_details[id] = tier; + MSpellDetails.releasewritelock(__FUNCTION__, __LINE__); + } + + void DisableSave() { disable_save = true; } + bool IsSaveDisabled() { return disable_save; } + void ResetZoningCoords() { + zoning_x = 0; + zoning_y = 0; + zoning_z = 0; + zoning_h = 0; + } + void SetZoningCoords(float x, float y, float z, float h) { + zoning_x = x; + zoning_y = y; + zoning_z = z; + zoning_h = h; + } + + bool UseItem(Item* item, Spawn* target = nullptr); + + void SendPlayFlavor(Spawn* spawn, int8 language, VoiceOverStruct* non_garble, VoiceOverStruct* garble, bool success = false, bool garble_success = false); + void SaveQuestRewardData(bool force_refresh = false); + void UpdateCharacterRewardData(QuestRewardData* data); + void SetQuestUpdateState(bool val) { quest_updates = val; } + + + bool SetPlayerPOVGhost(Spawn* spawn); + + int32 GetPlayerPOVGhostSpawnID() { return pov_ghost_spawn_id; } + + void HandleDialogSelectMsg(int32 conversation_id, int32 response_index); + bool SetPetName(const char* name); + + bool CheckConsumptionAllowed(int16 slot, bool send_message = true); + + void StartLinkdeadTimer(); + bool IsLinkdeadTimerEnabled(); + + bool AddRecipeBookToPlayer(int32 recipe_id, Item* item = nullptr); + bool RemoveRecipeFromPlayer(int32 recipe_id); + + void SaveSpells(); + + void GiveQuestReward(Quest* quest, bool has_displayed = false); + + void SendReplaceWidget(int32 widget_id, bool delete_widget, float x=0.0f, float y=0.0f, float z=0.0f, int32 grid_id=0); + void ProcessZoneIgnoreWidgets(); + + void SendHearCast(Spawn* caster, Spawn* target, int32 spell_visual, int16 cast_time); + int32 GetSpellVisualOverride(int32 spell_visual); + + sint16 GetClientItemPacketOffset() { sint16 offset = -1; if(GetVersion() <= 373) { offset = -2; } return offset; } +private: + void AddRecipeToPlayerPack(Recipe* recipe, PacketStruct* packet, int16* i); + void SavePlayerImages(); + void SkillChanged(Skill* skill, int16 previous_value, int16 new_value); + void SetStepComplete(int32 quest_id, int32 step); + void AddStepProgress(int32 quest_id, int32 step, int32 progress); + + void SendNewSpells(int8 class_id); + void SendNewTSSpells(int8 class_id); + void AddSendNewSpells(vector* spells); + + map > quest_pending_updates; + vector quest_queue; + vector quest_pending_reward; + volatile bool quest_updates; + Mutex MQuestPendingUpdates; + Mutex MQuestQueue; + Mutex MDeletePlayer; + vector* search_items; + int32 waypoint_id = 0; + map waypoints; + Spawn* transport_spawn; + Mutex MBuyBack; + deque buy_back_items; + Spawn* merchant_transaction; + Spawn* mail_transaction; + mutable std::shared_mutex MPendingQuestAccept; + vector pending_quest_accept; + bool lua_debug; + bool should_target; + Spawn* combine_spawn; + int8 num_active_failures; + int32 next_conversation_id; + map conversation_spawns; + map conversation_items; + mutable std::shared_mutex MConversation; + map > conversation_map; + int32 current_quest_id; + Spawn* banker; + map sent_spell_details; + map 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* linkdead_timer; + bool connected; + std::atomic ready_for_spawns; + std::atomic ready_for_updates; + + bool seencharsel; + bool connected_to_zone; + bool client_zoning; + int32 zoning_id; + int32 zoning_instance_id; + ZoneServer* zoning_destination; + float zoning_x; + float zoning_y; + float zoning_z; + float zoning_h; + bool firstlogin; + + enum NewLoginState { LOGIN_NONE, LOGIN_DELAYED, LOGIN_ALLOWED, LOGIN_INITIAL_LOAD, LOGIN_SEND }; + NewLoginState new_client_login; // 1 = delayed state, 2 = let client in + Timer underworld_cooldown_timer; + Timer pos_update; + Timer quest_pos_timer; + Timer lua_debug_timer; + Timer temp_placement_timer; + Timer spawn_removal_timer; + std::atomic player_pos_changed; + std::atomic player_pos_change_count; + int32 player_pos_timer; + bool enabled_player_pos_timer; + bool HandlePacket(EQApplicationPacket *app); + EQStream* eqs; + bool quickbar_changed; + ZoneServer* current_zone; + int32 name_crc; + MailWindow mail_window; + std::mutex MMailWindowMutex; + PendingGuildInvite pending_guild_invite; + PendingResurrection current_rez; + string* pending_last_name; + IncomingPaperdollImage incoming_paperdoll; + int32 transmuteID; + ZoneServer* GetHouseZoneServer(int32 spawn_id, int64 house_id); + + std::atomic m_recipeListSent; + bool initial_spawns_sent; + bool should_load_spells; + + // int32 = quest id + vector 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; + bool hasSentTempPlacementSpawn; + + int32 temporary_transport_id; + + int32 rejoin_group_id; + + int32 lastRegionRemapTime; + + bool regionDebugMessaging; + + bool client_reloading_zone; + + map queued_state_commands; + Mutex MQueueStateCmds; + Timer save_spell_state_timer; // will be the 're-trigger' to delay + int32 save_spell_state_time_bucket; // bucket as we collect over time when timer is reset by new spell effects being casted + std::mutex MSaveSpellStateMutex; + bool player_loading_complete; + Mutex MItemDetails; + Mutex MSpellDetails; + bool disable_save; + vector< string > devices; + + std::atomic pov_ghost_spawn_id; + Timer delay_msg_timer; + + uchar* recipe_orig_packet; + uchar* recipe_xor_packet; + int recipe_packet_count; + int recipe_orig_packet_size; +}; + +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_list; +}; +#endif diff --git a/source/WorldServer/makefile b/source/WorldServer/makefile new file mode 100644 index 0000000..7c963f2 --- /dev/null +++ b/source/WorldServer/makefile @@ -0,0 +1,100 @@ + + +# Programs +CC = gcc +CXX = g++ +LINKER = g++ + + +# Configuration +Build_Dir = build +Source_Dir = .. +#Conf_Dir = ../../conf +#Content_Dir = ../../../../vgocontent +APP = eq2world + + +# LUA flags +Lua_C_Flags = -DLUA_COMPAT_ALL -DLUA_USE_LINUX +Lua_W_Flags = -Wall + +C_Flags = -I/eq2emu/fmt/include -I/eq2emu/recastnavigation/Detour/Include -I/usr/include/mariadb -I/usr/local/include/boost -I/usr/include/glm -march=native -pipe -pthread -std=c++17 +LD_Flags = -L/usr/lib/x86_64-linux-gnu -lmariadb -lz -lpthread -L/recastnavigation/RecastDemo/Build/gmake/lib/Debug -lDebugUtils -lDetour -lDetourCrowd -lDetourTileCache -lRecast -L/usr/local/lib -rdynamic -lm -Wl,-E -ldl -lreadline -lssl -lcrypto -lboost_system -lboost_filesystem -lboost_iostreams -lboost_regex + +# World flags +W_Flags = -Wall -Wno-reorder +D_Flags = -DEQ2 -DWORLD -D_GNU_SOURCE + +# Setup Debug or Release build +ifeq ($(BUILD),debug) + # "Debug" build - minimum optimization, and debugging symbols + C_Flags += -O -ggdb + D_Flags += -DDEBUG + Current_Build_Dir := $(Build_Dir)/debug + App_Filename = $(APP)_debug +else + # "Release" build - optimization, and no debug symbols + C_Flags += -O2 -s -DNDEBUG + Current_Build_Dir := $(Build_Dir)/release + App_Filename = $(APP) +endif + + +# File lists +World_Source = $(wildcard $(Source_Dir)/WorldServer/*.cpp) $(wildcard $(Source_Dir)/WorldServer/*/*.cpp) +World_Objects = $(patsubst %.cpp,$(Current_Build_Dir)/%.o,$(subst $(Source_Dir)/,,$(World_Source))) +Common_Source = $(wildcard $(Source_Dir)/common/*.cpp) $(wildcard $(Source_Dir)/common/*/*.cpp) +Common_Objects = $(patsubst %.cpp,$(Current_Build_Dir)/%.o,$(subst $(Source_Dir)/,,$(Common_Source))) +Lua_Source = $(wildcard $(Source_Dir)/LUA/*.c) +Lua_Objects = $(patsubst %.c,$(Current_Build_Dir)/%.o,$(subst $(Source_Dir)/,,$(Lua_Source))) + + +# Receipes +all: $(APP) + +$(APP): $(Common_Objects) $(World_Objects) $(Lua_Objects) + @echo Linking... + @$(LINKER) $(W_Flags) $^ $(LD_Flags) -o $(App_Filename) + @test -e $(APP) || /bin/true + #@ln -s $(App_Filename) $(APP) || /bin/true + @echo Finished building world. + +$(Current_Build_Dir)/LUA/%.o: $(Source_Dir)/LUA/%.c + @mkdir -p $(dir $@) + $(CC) -c $(Lua_C_Flags) $(Lua_W_Flags) $< -o $@ + +$(Current_Build_Dir)/%.o: $(Source_Dir)/%.cpp + @mkdir -p $(dir $@) + $(CXX) -c $(C_Flags) $(D_Flags) $(W_Flags) $< -o $@ + +#setup: +# @test ! -e volumes.phys && ln -s $(Conf_Dir)/volumes.phys . || /bin/true +# @test ! -e vgemu-structs.xml && ln -s $(Conf_Dir)/vgemu-structs.xml . || /bin/true +# @$(foreach folder,$(wildcard $(Content_Dir)/scripts/*),test -d $(Content_Dir)/scripts && test ! -e $(notdir $(folder)) && ln -s $(folder) . || /bin/true) +# @echo "Symlinks have been created." +# @cp -n $(Conf_Dir)/vgemu-world.xml . +# @echo "You need to edit your config file: vgemu-world.xml" + +release: + @$(MAKE) "BUILD=release" + +debug: + @$(MAKE) "BUILD=debug" + +clean: + rm -rf $(filter-out %Lua,$(foreach folder,$(wildcard $(Current_Build_Dir)/*),$(folder))) $(App_Filename) $(APP) + +cleanlua: + rm -rf $(Current_Build_Dir)/Lua + +cleanall: + rm -rf $(Build_Dir) $(App_Filename) $(APP) + + +#cleansetup: +# rm volumes.phys vgemu-structs.xml $(foreach folder,$(wildcard $(Content_Dir)/scripts/*),$(notdir $(folder))) + +#docs: docs-world + +#docs-world: +# @cd ../../doc; doxygen Doxyfile-World diff --git a/source/WorldServer/makefile.discord b/source/WorldServer/makefile.discord new file mode 100644 index 0000000..739f4e0 --- /dev/null +++ b/source/WorldServer/makefile.discord @@ -0,0 +1,97 @@ +# Programs +CC = gcc +CXX = g++ +LINKER = g++ + + +# Configuration +Build_Dir = build +Source_Dir = .. +APP = eq2world + + +# LUA flags +Lua_C_Flags = -DLUA_COMPAT_ALL -DLUA_USE_LINUX +Lua_W_Flags = -Wall + + +# World flags +C_Flags = -I/usr/include/mariadb -I../depends/fmt/include -I../depends/recastnavigation/Detour/Include -I/usr/local/include/boost -I../depends/glm/ -march=native -pipe -pthread -std=c++17 +LD_Flags = -L/usr/lib/x86_64-linux-gnu -lz -lpthread -lmariadbclient -L../depends/recastnavigation/RecastDemo/Build/gmake/lib/Debug -lDebugUtils -lDetour -lDetourCrowd -lDetourTileCache -lRecast -L/usr/local/lib -rdynamic -lm -Wl,-E -ldl -lreadline -lboost_system -lboost_filesystem -lboost_iostreams -lboost_regex +W_Flags = -Wall -Wno-reorder +D_Flags = -DEQ2 -DWORLD -D_GNU_SOURCE + + +# Setup Debug or Release build +ifeq ($(BUILD),debug) + # "Debug" build - minimum optimization, and debugging symbols + C_Flags += -O -ggdb + D_Flags += -DDEBUG -DDISCORD + LD_Flags += -ldpp + Current_Build_Dir := $(Build_Dir)/debug + App_Filename = $(APP)_debug +else + # "Release" build - optimization, and no debug symbols + C_Flags += -O2 -s -DNDEBUG + Current_Build_Dir := $(Build_Dir)/release + App_Filename = $(APP) +endif + + +# File lists +World_Source = $(wildcard $(Source_Dir)/WorldServer/*.cpp) $(wildcard $(Source_Dir)/WorldServer/*/*.cpp) +World_Objects = $(patsubst %.cpp,$(Current_Build_Dir)/%.o,$(subst $(Source_Dir)/,,$(World_Source))) +Common_Source = $(wildcard $(Source_Dir)/common/*.cpp) +Common_Objects = $(patsubst %.cpp,$(Current_Build_Dir)/%.o,$(subst $(Source_Dir)/,,$(Common_Source))) +Lua_Source = $(wildcard $(Source_Dir)/LUA/*.c) +Lua_Objects = $(patsubst %.c,$(Current_Build_Dir)/%.o,$(subst $(Source_Dir)/,,$(Lua_Source))) + + +# Receipes +all: $(APP) + +$(APP): $(Common_Objects) $(World_Objects) $(Lua_Objects) + @echo Linking... + @$(LINKER) $(W_Flags) $^ $(LD_Flags) -o $(App_Filename) + @test -e $(APP) || /bin/true + #@ln -s $(App_Filename) $(APP) || /bin/true + @echo Finished building world. + +$(Current_Build_Dir)/LUA/%.o: $(Source_Dir)/LUA/%.c + @mkdir -p $(dir $@) + $(CC) -c $(Lua_C_Flags) $(Lua_W_Flags) $< -o $@ + +$(Current_Build_Dir)/%.o: $(Source_Dir)/%.cpp + @mkdir -p $(dir $@) + $(CXX) -c $(C_Flags) $(D_Flags) $(W_Flags) $< -o $@ + +#setup: +# @test ! -e volumes.phys && ln -s $(Conf_Dir)/volumes.phys . || /bin/true +# @test ! -e vgemu-structs.xml && ln -s $(Conf_Dir)/vgemu-structs.xml . || /bin/true +# @$(foreach folder,$(wildcard $(Content_Dir)/scripts/*),test -d $(Content_Dir)/scripts && test ! -e $(notdir $(folder)) && ln -s $(folder) . || /bin/true) +# @echo "Symlinks have been created." +# @cp -n $(Conf_Dir)/vgemu-world.xml . +# @echo "You need to edit your config file: vgemu-world.xml" + +release: + @$(MAKE) "BUILD=release" + +debug: + @$(MAKE) "BUILD=debug" + +clean: + rm -rf $(filter-out %Lua,$(foreach folder,$(wildcard $(Current_Build_Dir)/*),$(folder))) $(App_Filename) $(APP) + +cleanlua: + rm -rf $(Current_Build_Dir)/Lua + +cleanall: + rm -rf $(Build_Dir) $(App_Filename) $(APP) + +#cleansetup: +# rm volumes.phys vgemu-structs.xml $(foreach folder,$(wildcard $(Content_Dir)/scripts/*),$(notdir $(folder))) + +#docs: docs-world + +#docs-world: +# @cd ../../doc; doxygen Doxyfile-World diff --git a/source/WorldServer/net.cpp b/source/WorldServer/net.cpp new file mode 100644 index 0000000..c18082f --- /dev/null +++ b/source/WorldServer/net.cpp @@ -0,0 +1,948 @@ +/* + 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 . +*/ +#include "../common/debug.h" +#include "../common/Log.h" + +#include +using namespace std; +#include +#include +#include +#include +#include + +#include + +#include "../common/queue.h" +#include "../common/timer.h" +#include "../common/EQStreamFactory.h" +#include "../common/EQStream.h" +#include "net.h" + +#include "Variables.h" +#include "WorldDatabase.h" +#include "../common/seperator.h" +#include "../common/version.h" +#include "../common/EQEMuError.h" +#include "../common/opcodemgr.h" +#include "../common/Common_Defines.h" +#include "../common/JsonParser.h" +#include "../common/Common_Defines.h" + +#include "LoginServer.h" +#include "Commands/Commands.h" +#include "Factions.h" +#include "World.h" +#include "../common/ConfigReader.h" +#include "Skills.h" +#include "LuaInterface.h" +#include "Guilds/Guild.h" +#include "Commands/ConsoleCommands.h" +#include "Traits/Traits.h" +#include "Transmute.h" +#include "Zone/ChestTrap.h" + +//devn00b +#ifdef DISCORD + //linux only for the moment. + #ifndef WIN32 + #include + #include "Chat/Chat.h" + extern Chat chat; + #endif +#endif + +double frame_time = 0.0; + +#ifdef WIN32 + #include + #define strncasecmp _strnicmp + #define strcasecmp _stricmp + #include +#else + #include + #include "../common/unix.h" +#endif + +#ifdef PROFILER +#define SHINY_PROFILER TRUE +#include "../Profiler/src/Shiny.h" +#endif + +NetConnection net; +World world; +EQStreamFactory eqsf(LoginStream); +LoginServer loginserver; +LuaInterface* lua_interface = new LuaInterface(); +#include "MutexList.h" + +#include "Rules/Rules.h" +#include "Titles.h" +#include "Languages.h" +#include "Achievements/Achievements.h" + +volatile bool RunLoops = true; +sint32 numclients = 0; +sint32 numzones = 0; +extern ClientList client_list; +extern ZoneList zone_list; +extern MasterFactionList master_faction_list; +extern WorldDatabase database; +extern MasterSpellList master_spell_list; +extern MasterTraitList master_trait_list; +extern MasterSkillList master_skill_list; +extern MasterItemList master_item_list; +extern GuildList guild_list; +extern Variables variables; +ConfigReader configReader; +int32 MasterItemList::next_unique_id = 0; +int last_signal = 0; +RuleManager rule_manager; +MasterTitlesList master_titles_list; +MasterLanguagesList master_languages_list; +ChestTrapList chest_trap_list; +extern MasterAchievementList master_achievement_list; +extern map EQOpcodeVersions; + + +ThreadReturnType ItemLoad (void* tmp); +ThreadReturnType AchievmentLoad (void* tmp); +ThreadReturnType SpellLoad (void* tmp); +//devn00b +#ifdef DISCORD + #ifndef WIN32 + ThreadReturnType StartDiscord (void* tmp); + #endif +#endif + +int main(int argc, char** argv) { +#ifdef PROFILER + PROFILE_FUNC(); +#endif + int32 t_total = Timer::GetUnixTimeStamp(); + + LogStart(); + + LogParseConfigs(); + net.WelcomeHeader(); + + LogWrite(INIT__INFO, 0, "Init", "Starting EQ2Emulator WorldServer..."); + //int32 server_startup = time(NULL); + + //remove this when all database calls are using the new database class + if (!database.Init()) { + LogStop(); + return EXIT_FAILURE; + } + + if (!database.ConnectNewDatabase()) + return EXIT_FAILURE; + + #ifdef _DEBUG + _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); + #endif + + if (signal(SIGINT, CatchSignal) == SIG_ERR) { + LogWrite(INIT__ERROR, 0, "Init", "Could not set signal handler"); + return 0; + } + if (signal(SIGSEGV, CatchSignal) == SIG_ERR) { + LogWrite(INIT__ERROR, 0, "Init", "Could not set signal handler"); + return 0; + } + if (signal(SIGILL, CatchSignal) == SIG_ERR) { + LogWrite(INIT__ERROR, 0, "Init", "Could not set signal handler"); + return 0; + } + #ifndef WIN32 + if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) { + LogWrite(INIT__ERROR, 0, "Init", "Could not set signal handler"); + return 0; + } + #endif + + LogWrite(WORLD__DEBUG, 0, "World", "Randomizing World..."); + srand(time(NULL)); + + net.ReadLoginINI(); + + // JA: Grouping all System (core) data loads together for timing purposes + LogWrite(WORLD__INFO, 0, "World", "Loading System Data..."); + int32 t_now = Timer::GetUnixTimeStamp(); + + LogWrite(WORLD__DEBUG, 1, "World", "-Loading opcodes..."); + EQOpcodeVersions = database.GetVersions(); + map::iterator version_itr; + int16 version1 = 0; + int16 prevVersion = 0; + std::string prevString = std::string(""); + std::string builtString = std::string(""); + for (version_itr = EQOpcodeVersions.begin(); version_itr != EQOpcodeVersions.end(); version_itr++) { + version1 = version_itr->first; + EQOpcodeManager[version1] = new RegularOpcodeManager(); + map eq = database.GetOpcodes(version1); + std::string missingOpcodesList = std::string(""); + if(!EQOpcodeManager[version1]->LoadOpcodes(&eq, &missingOpcodesList)) { + LogWrite(INIT__ERROR, 0, "Init", "Loading opcodes failed. Make sure you have sourced the opcodes.sql file!"); + return false; + } + + if(version1 == 0) // we don't need to display version 0 + continue; + + if(prevString.size() > 0) { + if(prevString == missingOpcodesList) { + builtString += ", " + std::to_string(version1); + } + else { + LogWrite(OPCODE__WARNING, 1, "Opcode", "Opcodes %s.", builtString.c_str()); + builtString = std::string(""); + prevString = std::string(""); + } + } + if(prevString.size() < 1) { + prevString = std::string(missingOpcodesList); + builtString = std::string(missingOpcodesList + " are missing from the opcodes table for version(s) " + std::to_string(version1)); + } + } + + if(builtString.size() > 0) { + LogWrite(OPCODE__WARNING, 1, "Opcode", "Opcodes %s.", builtString.c_str()); + } + + LogWrite(WORLD__DEBUG, 1, "World", "-Loading structs..."); + if(!configReader.LoadFile("CommonStructs.xml") || !configReader.LoadFile("WorldStructs.xml") || !configReader.LoadFile("SpawnStructs.xml") || !configReader.LoadFile("ItemStructs.xml")) { + LogWrite(INIT__ERROR, 0, "Init", "Loading structs failed. Make sure you have CommonStructs.xml, WorldStructs.xml, SpawnStructs.xml, and ItemStructs.xml in the working directory!"); + return false; + } + + world.init(net.GetWebWorldAddress(), net.GetWebWorldPort(), net.GetWebCertFile(), net.GetWebKeyFile(), net.GetWebKeyPassword(), net.GetWebHardcodeUser(), net.GetWebHardcodePassword()); + + bool threadedLoad = rule_manager.GetGlobalRule(R_World, ThreadedLoad)->GetBool(); + + LogWrite(WORLD__DEBUG, 1, "World", "-Loading EQ2 time of day..."); + loginserver.InitLoginServerVariables(); + + LogWrite(WORLD__INFO, 0, "World", "Loaded System Data (took %u seconds)", Timer::GetUnixTimeStamp() - t_now); + // JA: End System Data loading functions + + if (threadedLoad) { + LogWrite(WORLD__WARNING, 0, "Threaded", "Using Threaded loading of static data..."); +#ifdef WIN32 + _beginthread(ItemLoad, 0, &world); + _beginthread(SpellLoad, 0, &world); + //_beginthread(AchievmentLoad, 0, &world); +#else + pthread_t thread; + pthread_create(&thread, NULL, ItemLoad, &world); + pthread_detach(thread); + pthread_t thread2; + pthread_create(&thread2, NULL, SpellLoad, &world); + pthread_detach(thread2); + //devn00b + #ifdef DISCORD + pthread_t thread3; + pthread_create(&thread3, NULL, StartDiscord, &world); + pthread_detach(thread3); + #endif +#endif + } + + // JA temp logger + LogWrite(MISC__TODO, 0, "Reformat", "JA: This is as far as I got reformatting the console logs."); + + if (!threadedLoad) { + // JA: Load all Item info + LogWrite(ITEM__INFO, 0, "Items", "Loading Items..."); + database.LoadItemList(); + MasterItemList::ResetUniqueID(database.LoadNextUniqueItemID()); + + LogWrite(SPELL__INFO, 0, "Spells", "Loading Spells..."); + database.LoadSpells(); + + LogWrite(SPELL__INFO, 0, "Spells", "Loading Spell Errors..."); + database.LoadSpellErrors(); + + // Jabantiz: Load traits + LogWrite(WORLD__INFO, 0, "Traits", "Loading Traits..."); + database.LoadTraits(); + + // JA: Load all Quest info + LogWrite(QUEST__INFO, 0, "Quests", "Loading Quests..."); + database.LoadQuests(); + + LogWrite(COLLECTION__INFO, 0, "Collect", "Loading Collections..."); + database.LoadCollections(); + + LogWrite(MISC__TODO, 1, "TODO", "TODO loading achievements\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__); + //LogWrite(ACHIEVEMENT__INFO, 0, "Achievements", "Loading Achievements..."); + //database.LoadAchievements(); + //master_achievement_list.CreateMasterAchievementListPacket(); + + LogWrite(MERCHANT__INFO, 0, "Merchants", "Loading Merchants..."); + database.LoadMerchantInformation(); + } + + LogWrite(GUILD__INFO, 0, "Guilds", "Loading Guilds..."); + database.LoadGuilds(); + + LogWrite(TRADESKILL__INFO, 0, "Recipes", "Loading Recipe Books..."); + database.LoadRecipeBooks(); + LogWrite(TRADESKILL__INFO, 0, "Recipes", "Loading Recipes..."); + database.LoadRecipes(); + LogWrite(TRADESKILL__INFO, 0, "Tradeskills", "Loading Tradeskill Events..."); + database.LoadTradeskillEvents(); + + LogWrite(SPELL__INFO, 0, "AA", "Loading Alternate Advancements..."); + database.LoadAltAdvancements(); + LogWrite(SPELL__INFO, 0, "AA", "Loading AA Tree Nodes..."); + database.LoadTreeNodes(); + LogWrite(WORLD__INFO, 0, "Titles", "Loading Titles..."); + database.LoadTitles(); + LogWrite(WORLD__INFO, 0, "Languages", "Loading Languages..."); + database.LoadLanguages(); + + LogWrite(CHAT__INFO, 0, "Chat", "Loading channels..."); + database.LoadChannels(); + + LogWrite(LUA__INFO, 0, "LUA", "Loading Spawn Scripts..."); + database.LoadSpawnScriptData(); + + LogWrite(LUA__INFO, 0, "LUA", "Loading Zone Scripts..."); + database.LoadZoneScriptData(); + + LogWrite(WORLD__INFO, 0, "World", "Loading House Zone Data..."); + database.LoadHouseZones(); + database.LoadPlayerHouses(); + + LogWrite(WORLD__INFO, 0, "World", "Loading Heroic OP Data..."); + database.LoadHOStarters(); + database.LoadHOWheel(); + + LogWrite(WORLD__INFO, 0, "World", "Loading Race Types Data..."); + database.LoadRaceTypes(); + + LogWrite(WORLD__INFO, 0, "World", "Loading Transmuting Data..."); + database.LoadTransmuting(); + + LogWrite(WORLD__INFO, 0, "World", "Loading Chest Trap Data..."); + database.LoadChestTraps(); + + LogWrite(WORLD__INFO, 0, "World", "Loading NPC Spells..."); + database.LoadNPCSpells(); + + if (threadedLoad) { + LogWrite(WORLD__INFO, 0, "World", "Waiting for load threads to finish."); + while (!world.items_loaded || !world.spells_loaded /*|| !world.achievments_loaded*/) + Sleep(10); + LogWrite(WORLD__INFO, 0, "World", "Load threads finished."); + } + + LogWrite(WORLD__INFO, 0, "World", "Total World startup time: %u seconds.", Timer::GetUnixTimeStamp() - t_total); + int ret_code = 0; + if (eqsf.Open(net.GetWorldPort())) { + if (strlen(net.GetWorldAddress()) == 0) + LogWrite(NET__INFO, 0, "Net", "World server listening on port %i", net.GetWorldPort()); + else + LogWrite(NET__INFO, 0, "Net", "World server listening on: %s:%i", net.GetWorldAddress(), net.GetWorldPort()); + + if(strlen(net.GetInternalWorldAddress())>0) + LogWrite(NET__INFO, 0, "Net", "World server listening on: %s:%i", net.GetInternalWorldAddress(), net.GetWorldPort()); + + world.world_loaded = true; + world.world_uptime = getCurrentTimestamp(); + } + else { + LogWrite(NET__ERROR, 0, "Net", "Failed to open port %i.", net.GetWorldPort()); + ret_code = 1; + } + Timer* TimeoutTimer = 0; + if (ret_code == 0) { + Timer InterserverTimer(INTERSERVER_TIMER); // does MySQL pings and auto-reconnect + InterserverTimer.Trigger(); + TimeoutTimer = new Timer(5000); + TimeoutTimer->Start(); + EQStream* eqs = 0; + UpdateWindowTitle(0); + LogWrite(ZONE__INFO, 0, "Zone", "Starting static zones..."); + database.LoadSpecialZones(); + map connecting_clients; + map::iterator cc_itr; + + LogWrite(WORLD__DEBUG, 0, "Thread", "Starting console command thread..."); +#ifdef WIN32 + _beginthread(EQ2ConsoleListener, 0, NULL); +#else + /*pthread_t thread; + pthread_create(&thread, NULL, &EQ2ConsoleListener, NULL); + pthread_detach(thread);*/ +#endif + // + + // just before starting loops, announce how to get console help (only to windows) +#ifdef WIN32 + LogWrite(WORLD__INFO, 0, "Console", "Type 'help' or '?' and press enter for menu options."); +#endif + + + std::chrono::time_point frame_prev = std::chrono::system_clock::now(); + while (RunLoops) { + Timer::SetCurrentTime(); + std::chrono::time_point frame_now = std::chrono::system_clock::now(); + frame_time = std::chrono::duration_cast>(frame_now - frame_prev).count(); + frame_prev = frame_now; + +#ifndef NO_CATCH + try + { +#endif + while ((eqs = eqsf.Pop())) { + struct in_addr in; + in.s_addr = eqs->GetRemoteIP(); + LogWrite(NET__DEBUG, 0, "Net", "New client from ip: %s port: %i", inet_ntoa(in), ntohs(eqs->GetRemotePort())); + + // JA: Check for BannedIPs + if (rule_manager.GetGlobalRule(R_World, UseBannedIPsTable)->GetInt8() == 1) + { + LogWrite(WORLD__DEBUG, 0, "World", "Checking inbound connection %s against BannedIPs table", inet_ntoa(in)); + if (database.CheckBannedIPs(inet_ntoa(in))) + { + LogWrite(WORLD__DEBUG, 0, "World", "Connection from %s FAILED banned IPs check. Closing connection.", inet_ntoa(in)); + eqs->Close(); // JA: If the inbound IP is on the banned table, close the EQStream. + } + } + + if (eqs && eqs->CheckActive() && client_list.ContainsStream(eqs) == false) { + LogWrite(NET__DEBUG, 0, "Net", "Adding new client..."); + Client* client = new Client(eqs); + client_list.Add(client); + } + else if (eqs && !client_list.ContainsStream(eqs)) { + LogWrite(NET__DEBUG, 0, "Net", "Adding client to waiting list..."); + connecting_clients[eqs] = Timer::GetCurrentTime2(); + } + } + if (connecting_clients.size() > 0) { + for (cc_itr = connecting_clients.begin(); cc_itr != connecting_clients.end(); cc_itr++) { + if (cc_itr->first && cc_itr->first->CheckActive() && client_list.ContainsStream(cc_itr->first) == false) { + LogWrite(NET__DEBUG, 0, "Net", "Removing client from waiting list..."); + Client* client = new Client(cc_itr->first); + client_list.Add(client); + connecting_clients.erase(cc_itr); + break; + } + else if (Timer::GetCurrentTime2() >= (cc_itr->second + 10000)) { + connecting_clients.erase(cc_itr); + break; + } + } + } + world.Process(); + client_list.Process(); + loginserver.Process(); + if (TimeoutTimer->Check()) { + eqsf.CheckTimeout(); + } + if (InterserverTimer.Check()) { + InterserverTimer.Start(); + database.ping(); + database.PingNewDB(); + database.PingAsyncDatabase(); + + if (net.LoginServerInfo && loginserver.Connected() == false && loginserver.CanReconnect()) { + LogWrite(WORLD__DEBUG, 0, "Thread", "Starting autoinit loginserver thread..."); +#ifdef WIN32 + _beginthread(AutoInitLoginServer, 0, NULL); +#else + pthread_t thread; + pthread_create(&thread, NULL, &AutoInitLoginServer, NULL); + pthread_detach(thread); +#endif + } + } +#ifndef NO_CATCH + } + catch (...) { + LogWrite(WORLD__ERROR, 0, "World", "Exception caught in net main loop!"); + } +#endif + if (numclients == 0) { + Sleep(10); + continue; + } + Sleep(1); + } + } + LogWrite(WORLD__DEBUG, 0, "World", "The world is ending!"); + + LogWrite(WORLD__DEBUG, 0, "World", "Shutting down zones..."); + zone_list.ShutDownZones(); + + LogWrite(WORLD__DEBUG, 0, "World", "Shutting down LUA interface..."); + safe_delete(lua_interface); + safe_delete(TimeoutTimer); + eqsf.Close(); + map::iterator opcode_itr; + for(opcode_itr=EQOpcodeManager.begin();opcode_itr!=EQOpcodeManager.end();opcode_itr++){ + safe_delete(opcode_itr->second); + } + CheckEQEMuErrorAndPause(); + +#ifdef PROFILER + PROFILER_UPDATE(); + PROFILER_OUTPUT(); +#endif + + LogWrite(WORLD__INFO, 0, "World", "Exiting... we hope you enjoyed your flight."); + LogStop(); + return ret_code; +} + +ThreadReturnType ItemLoad (void* tmp) +{ + LogWrite(WORLD__WARNING, 0, "Thread", "Item Loading Thread started."); +#ifdef WIN32 + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); +#endif + if (tmp == 0) { + ThrowError("ItemLoad(): tmp = 0!"); + THREAD_RETURN(NULL); + } + World* world = (World*) tmp; + WorldDatabase db; + db.Init(); + db.ConnectNewDatabase(); + + LogWrite(ITEM__INFO, 0, "Items", "Loading Items..."); + db.LoadItemList(); + MasterItemList::ResetUniqueID(db.LoadNextUniqueItemID()); + + // Relies on the item list so needs to be in the item thread + LogWrite(COLLECTION__INFO, 0, "Collect", "Loading Collections..."); + db.LoadCollections(); + + LogWrite(MERCHANT__INFO, 0, "Merchants", "Loading Merchants..."); + db.LoadMerchantInformation(); + + LogWrite(QUEST__INFO, 0, "Quests", "Loading Quests..."); + db.LoadQuests(); + + world->items_loaded = true; + LogWrite(WORLD__WARNING, 0, "Thread", "Item Loading Thread completed."); + + mysql_thread_end(); + THREAD_RETURN(NULL); +} + +ThreadReturnType SpellLoad (void* tmp) +{ + LogWrite(WORLD__WARNING, 0, "Thread", "Spell Loading Thread started."); +#ifdef WIN32 + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); +#endif + if (tmp == 0) { + ThrowError("ItemLoad(): tmp = 0!"); + THREAD_RETURN(NULL); + } + World* world = (World*) tmp; + WorldDatabase db; + db.Init(); + db.ConnectNewDatabase(); + LogWrite(SPELL__INFO, 0, "Spells", "Loading Spells..."); + db.LoadSpells(); + + LogWrite(SPELL__INFO, 0, "Spells", "Loading Spell Errors..."); + db.LoadSpellErrors(); + + LogWrite(WORLD__INFO, 0, "Traits", "Loading Traits..."); + db.LoadTraits(); + + world->spells_loaded = true; + LogWrite(WORLD__WARNING, 0, "Thread", "Spell Loading Thread completed."); + + mysql_thread_end(); + THREAD_RETURN(NULL); +} + +ThreadReturnType AchievmentLoad (void* tmp) +{ + LogWrite(WORLD__WARNING, 0, "Thread", "Achievement Loading Thread started."); +#ifdef WIN32 + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); +#endif + if (tmp == 0) { + ThrowError("ItemLoad(): tmp = 0!"); + THREAD_RETURN(NULL); + } + World* world = (World*) tmp; + WorldDatabase db; + db.Init(); + db.ConnectNewDatabase(); + + LogWrite(ACHIEVEMENT__INFO, 0, "Achievements", "Loading Achievements..."); + int32 t_now = Timer::GetUnixTimeStamp(); + db.LoadAchievements(); + master_achievement_list.CreateMasterAchievementListPacket(); + LogWrite(ACHIEVEMENT__INFO, 0, "Achievements", "Achievements loaded (took %u seconds)", Timer::GetUnixTimeStamp() - t_now); + + world->achievments_loaded = true; + LogWrite(WORLD__WARNING, 0, "Thread", "Achievement Loading Thread completed."); + + mysql_thread_end(); + THREAD_RETURN(NULL); +} + +ThreadReturnType EQ2ConsoleListener(void* tmp) +{ + char cmd[300]; + size_t i = 0; + size_t len; + + while( RunLoops ) + { + // Read in single line from "stdin" + memset( cmd, 0, sizeof( cmd ) ); + if( fgets( cmd, 300, stdin ) == NULL ) + continue; + + if( !RunLoops ) + break; + + len = strlen(cmd); + for( i = 0; i < len; ++i ) + { + if(cmd[i] == '\n' || cmd[i] == '\r') + cmd[i] = '\0'; + } + + ProcessConsoleInput(cmd); + } + THREAD_RETURN(NULL); +} + +#include +void CatchSignal(int sig_num) { + // In rare cases this can be called after the log system is shut down causing a deadlock or crash + // when the world shuts down, if this happens again comment out the LogWrite and uncomment the printf + if (last_signal != sig_num){ + static Mutex lock; + static std::ofstream signal_out; + + lock.lock(); + if (!signal_out.is_open()) + signal_out.open("signal_catching.log", ios::trunc); + if (signal_out){ + signal_out << "Caught signal " << sig_num << "\n"; + signal_out.flush(); + } + printf("Caught signal %i\n", sig_num); + lock.unlock(); + + last_signal = sig_num; + RunLoops = false; + } +} + +bool NetConnection::ReadLoginINI() { + JsonParser parser(MAIN_CONFIG_FILE); + if(!parser.IsLoaded()) { + LogWrite(INIT__ERROR, 0, "Init", "Failed to find %s in server directory..", MAIN_CONFIG_FILE); + return false; + } + std::string worldname_str = parser.getValue("loginserver.worldname"); + if(worldname_str.size() < 4) { + LogWrite(INIT__ERROR, 0, "Init", "loginserver.worldname was invalid or less than 4 characters.."); + return false; + } + + std::string worldaccount_str = parser.getValue("loginserver.account"); + std::string worldpassword_str = parser.getValue("loginserver.password"); + std::string worldaddress_str = parser.getValue("loginserver.worldaddress"); + + snprintf(worldname, sizeof(worldname), "%s", worldname_str.c_str()); + snprintf(worldaccount, sizeof(worldaccount), "%s", worldaccount_str.c_str()); + snprintf(worldpassword, sizeof(worldpassword), "%s", worldpassword_str.c_str()); + snprintf(worldaddress, sizeof(worldaddress), "%s", worldaddress_str.c_str()); + + std::string logstats_str = parser.getValue("loginserver.logstats"); + int16 logstats = 0; + parser.convertStringToUnsignedShort(logstats_str, logstats); + net.UpdateStats = logstats > 0 ? true : false; + + std::string locked_str = parser.getValue("loginserver.locked"); + int16 locked = 0; + parser.convertStringToUnsignedShort(locked_str, locked); + world_locked = locked > 0 ? true : false; + + std::string worldport_str = parser.getValue("loginserver.worldport"); + parser.convertStringToUnsignedShort(worldport_str, worldport); + + for(int i=-1;i<=3;i++) { + + std::string loginport_str = ""; + std::string loginaddress_str = ""; + + if(i==-1) { + loginport_str = parser.getValue("loginserver.loginport"); + loginaddress_str = parser.getValue("loginserver.loginserver"); + } + else { + loginport_str = parser.getValue("loginserver.loginport" + std::to_string(i)); + loginaddress_str = parser.getValue("loginserver.loginserver" + std::to_string(i)); + } + + if(loginport_str.size() < 1 || loginaddress_str.size() < 1) + continue; + + parser.convertStringToUnsignedShort(loginport_str, loginport[i+1]); + snprintf(loginaddress[i+1], sizeof(loginaddress[i+1]), "%s", loginaddress_str.c_str()); + LogWrite(INIT__INFO, 0, "Init", "Login Server %s:%u...", loginaddress[i+1], loginport[i+1]); + } + + if(!loginaddress[0][0]) { + LogWrite(INIT__ERROR, 0, "Init", "loginserver.loginserver was missing.."); + return false; + } + + web_worldaddress = parser.getValue("worldserver.webaddress"); + web_certfile = parser.getValue("worldserver.webcertfile"); + web_keyfile = parser.getValue("worldserver.webkeyfile"); + web_keypassword = parser.getValue("worldserver.webkeypassword"); + web_hardcodeuser = parser.getValue("worldserver.webhardcodeuser"); + web_hardcodepassword = parser.getValue("worldserver.webhardcodepassword"); + + std::string webloginport_str = parser.getValue("worldserver.webport"); + parser.convertStringToUnsignedShort(webloginport_str, web_worldport); + + std::string defaultstatus_str = parser.getValue("worldserver.defaultstatus"); + parser.convertStringToUnsignedChar(defaultstatus_str, DEFAULTSTATUS); + + LogWrite(INIT__DEBUG, 0, "Init", "%s read...", MAIN_CONFIG_FILE); + LoginServerInfo=1; + return true; +} + + +char* NetConnection::GetLoginInfo(int16* oPort) { + if (oPort == 0) + return 0; + if (loginaddress[0][0] == 0) + return 0; + + int8 tmp[4] = { 0, 0, 0 }; + int8 count = 0; + + for (int i=0; i<4; i++) { + if (loginaddress[i][0]) + tmp[count++] = i; + } + + int x = rand() % count; + + *oPort = loginport[tmp[x]]; + return loginaddress[tmp[x]]; +} + +void UpdateWindowTitle(char* iNewTitle) { + + char tmp[500]; + if (iNewTitle) { + snprintf(tmp, sizeof(tmp), "World: %s", iNewTitle); + } + else { + string servername = net.GetWorldName(); + snprintf(tmp, sizeof(tmp), "%s (%s), Version: %s: %i Clients(s) in %i Zones(s)", EQ2EMU_MODULE, servername.c_str(), CURRENT_VERSION, numclients, numzones); + } + // Zero terminate ([max - 1] = 0) the string to prevent a warning + tmp[499] = 0; + #ifdef WIN32 + SetConsoleTitle(tmp); + #else + printf("%c]0;%s%c", '\033', tmp, '\007'); + #endif +} + +ZoneAuthRequest::ZoneAuthRequest(int32 account_id, char* name, int32 access_key) { +accountid = account_id; +character_name = string(name); +accesskey = access_key; +timestamp = Timer::GetUnixTimeStamp(); +firstlogin = false; +} + +ZoneAuthRequest::~ZoneAuthRequest ( ) +{ +} + +void ZoneAuth::AddAuth(ZoneAuthRequest *zar) { + LogWrite(NET__DEBUG, 0, "Net", "AddAuth: %u Key: %u", zar->GetAccountID(), zar->GetAccessKey()); + list.Insert(zar); +} + +ZoneAuthRequest* ZoneAuth::GetAuth(int32 account_id, int32 access_key) { + LinkedListIterator iterator(list); + + iterator.Reset(); + while(iterator.MoreElements()) { + if (iterator.GetData()->GetAccountID() == account_id && iterator.GetData()->GetAccessKey() == access_key) { + ZoneAuthRequest* tmp = iterator.GetData(); + return tmp; + } + iterator.Advance(); + } + return 0; + +} + +void ZoneAuth::PurgeInactiveAuth() { + LinkedListIterator iterator(list); + + iterator.Reset(); + int32 current_time = Timer::GetUnixTimeStamp(); + while(iterator.MoreElements()) { + if ((iterator.GetData()->GetTimeStamp()+60) < current_time) { + iterator.RemoveCurrent(); + } + iterator.Advance(); + } +} + +void ZoneAuth::RemoveAuth(ZoneAuthRequest *zar) { + LinkedListIterator iterator(list); + + iterator.Reset(); + while(iterator.MoreElements()) { + if (iterator.GetData() == zar) { + iterator.RemoveCurrent(); + break; + } + iterator.Advance(); + } +} + +void NetConnection::WelcomeHeader() +{ +#ifdef _WIN32 + HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleTextAttribute(console, FOREGROUND_WHITE_BOLD); +#endif + printf("Module: %s, Version: %s", EQ2EMU_MODULE, CURRENT_VERSION); +#ifdef _WIN32 + SetConsoleTextAttribute(console, FOREGROUND_YELLOW_BOLD); +#endif + printf("\n\nCopyright (C) 2007-2022 EQ2Emulator. https://www.eq2emu.com \n\n"); + printf("EQ2Emulator is free software: you can redistribute it and/or modify\n"); + printf("it under the terms of the GNU General Public License as published by\n"); + printf("the Free Software Foundation, either version 3 of the License, or\n"); + printf("(at your option) any later version.\n\n"); + printf("EQ2Emulator is distributed in the hope that it will be useful,\n"); + printf("but WITHOUT ANY WARRANTY; without even the implied warranty of\n"); + printf("MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"); + printf("GNU General Public License for more details.\n\n"); +#ifdef _WIN32 + SetConsoleTextAttribute(console, FOREGROUND_GREEN_BOLD); +#endif + printf(" /$$$$$$$$ /$$$$$$ /$$$$$$ /$$$$$$$$ \n"); + printf("| $$_____/ /$$__ $$ /$$__ $$| $$_____/ \n"); + printf("| $$ | $$ \\ $$|__/ \\ $$| $$ /$$$$$$/$$$$ /$$ /$$\n"); + printf("| $$$$$ | $$ | $$ /$$$$$$/| $$$$$ | $$_ $$_ $$| $$ | $$\n"); + printf("| $$__/ | $$ | $$ /$$____/ | $$__/ | $$ \\ $$ \\ $$| $$ | $$\n"); + printf("| $$ | $$/$$ $$| $$ | $$ | $$ | $$ | $$| $$ | $$\n"); + printf("| $$$$$$$$| $$$$$$/| $$$$$$$$| $$$$$$$$| $$ | $$ | $$| $$$$$$/\n"); + printf("|________/ \\____ $$$|________/|________/|__/ |__/ |__/ \\______/ \n"); + printf(" \\__/ \n\n"); +#ifdef _WIN32 + SetConsoleTextAttribute(console, FOREGROUND_MAGENTA_BOLD); +#endif + printf(" Website : https://eq2emu.com \n"); + printf(" Wiki : https://wiki.eq2emu.com \n"); + printf(" Git : https://git.eq2emu.com \n"); + printf(" Discord : https://discord.gg/5Cavm9NYQf \n\n"); +#ifdef _WIN32 + SetConsoleTextAttribute(console, FOREGROUND_WHITE_BOLD); +#endif + printf("For more detailed logging, modify 'Level' param the log_config.xml file.\n\n"); +#ifdef _WIN32 + SetConsoleTextAttribute(console, FOREGROUND_WHITE); +#endif + + fflush(stdout); +} + +#ifdef DISCORD +ThreadReturnType StartDiscord(void* tmp) +{ +#ifndef DISCORD + THREAD_RETURN(NULL); +#endif + if (tmp == 0) { + ThrowError("StartDiscord: tmp = 0!"); + THREAD_RETURN(NULL); + } + +#ifdef WIN32 + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); +#endif + + bool enablediscord = rule_manager.GetGlobalRule(R_Discord, DiscordEnabled)->GetBool(); + + if(enablediscord == false) { + LogWrite(INIT__INFO, 0,"Discord","Bot Disabled By Rule..."); + THREAD_RETURN(NULL); + } + + LogWrite(INIT__INFO, 0, "Discord", "Starting Discord Bridge..."); + const char* bottoken = rule_manager.GetGlobalRule(R_Discord, DiscordBotToken)->GetString(); + + if(strlen(bottoken)== 0) { + LogWrite(INIT__INFO, 0,"Discord","Bot Token Was Empty..."); + THREAD_RETURN(NULL); + } + + dpp::cluster bot(bottoken, dpp::i_default_intents | dpp::i_message_content); + + //if we have debug on, go ahead and show DPP logs. + #ifdef DEBUG + bot.on_log([&bot](const dpp::log_t & event) { + std::cout << "[" << dpp::utility::loglevel(event.severity) << "] " << event.message << "\n"; + }); + #endif + + bot.on_message_create([&bot](const dpp::message_create_t& event) { + if (event.msg.author.is_bot() == false) { + std::string chanid = event.msg.channel_id.str(); + std::string listenchan = rule_manager.GetGlobalRule(R_Discord, DiscordListenChan)->GetString(); + + if(chanid.compare(listenchan) != 0 || !chanid.size() || !listenchan.size()) { + return; + } + chat.TellChannel(NULL, listenchan.c_str(), event.msg.content.c_str(), event.msg.author.username.c_str()); + } + }); + + while(true) { + bot.start(dpp::st_wait); + //wait 30s for reconnect. prevents hammering discord and a potential ban. + std::this_thread::sleep_for(std::chrono::milliseconds(30000)); + } + + THREAD_RETURN(NULL); +} +#endif \ No newline at end of file diff --git a/source/WorldServer/net.h b/source/WorldServer/net.h new file mode 100644 index 0000000..d3bad05 --- /dev/null +++ b/source/WorldServer/net.h @@ -0,0 +1,143 @@ +/* + 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 . +*/ +#ifndef __EQ2_NET__ +#define __EQ2_NET__ +#ifndef WIN32 + #include + #include + #include + #include + #include + #include + #include +#else + #include + #include + #include + #include +#endif + +#include "../common/linked_list.h" +#include "../common/types.h" + +ThreadReturnType EQ2ConsoleListener(void *tmp); +void CatchSignal(int sig_num); +void UpdateWindowTitle(char* iNewTitle); + +#define PORT 9000 +#define LOGIN_PORT 9100 + +class NetConnection +{ +public: + NetConnection() { + world_locked = false; + for (int i=0; i<4; i++) { + memset(loginaddress[i], 0, sizeof(loginaddress[i])); + loginport[i] = LOGIN_PORT; + } + listening_socket = 0; + memset(worldname, 0, sizeof(worldname)); + memset(worldaccount, 0, sizeof(worldaccount)); + memset(worldpassword, 0, sizeof(worldpassword)); + memset(worldaddress, 0, sizeof(worldaddress)); + memset(internalworldaddress, 0, sizeof(internalworldaddress)); + worldport = PORT; + DEFAULTSTATUS=0; + LoginServerInfo = 0;//ReadLoginINI(); + UpdateStats = false; + web_worldport = 0; + } + ~NetConnection() { } + + bool ReadLoginINI(); + void WelcomeHeader(); + + bool LoginServerInfo; + bool UpdateStats; + char* GetLoginInfo(int16* oPort); + inline char* GetLoginAddress(int8 i) { return loginaddress[i]; } + inline int16 GetLoginPort(int8 i) { return loginport[i]; } + inline char* GetWorldName() { return worldname; } + inline char* GetWorldAccount() { return worldaccount; } + inline char* GetWorldPassword() { return worldpassword; } + inline char* GetWorldAddress() { return worldaddress; } + inline char* GetInternalWorldAddress() { return internalworldaddress; } + inline int16 GetWorldPort() { return worldport; } + inline int8 GetDefaultStatus() { return DEFAULTSTATUS; } + std::string GetWebWorldAddress() { return web_worldaddress; } + inline int16 GetWebWorldPort() { return web_worldport; } + std::string GetWebCertFile() { return web_certfile; } + std::string GetWebKeyFile() { return web_keyfile; } + std::string GetWebKeyPassword() { return web_keypassword; } + std::string GetWebHardcodeUser() { return web_hardcodeuser; } + std::string GetWebHardcodePassword() { return web_hardcodepassword; } + bool world_locked; +private: + int listening_socket; + char loginaddress[4][255]; + int16 loginport[4]; + char worldname[201]; + char worldaccount[31]; + char worldpassword[31]; + char worldaddress[255]; + char internalworldaddress[21]; + int16 worldport; + int8 DEFAULTSTATUS; + std::string web_worldaddress; + std::string web_certfile; + std::string web_keyfile; + std::string web_keypassword; + std::string web_hardcodeuser; + std::string web_hardcodepassword; + int16 web_worldport; + +}; + +class ZoneAuthRequest +{ +public: + ZoneAuthRequest(int32 account_id, char* name, int32 access_key); + ~ZoneAuthRequest( ); + int32 GetAccountID() { return accountid; } + const char* GetCharacterName() { return character_name.c_str(); } + int32 GetAccessKey() { return accesskey; } + int32 GetTimeStamp() { return timestamp; } + void setFirstLogin(bool value) { firstlogin = value; } + bool isFirstLogin() { return firstlogin; } +private: + int32 accountid; + string character_name; + int32 accesskey; + int32 timestamp; + bool firstlogin; +}; + +class ZoneAuth +{ +public: + void AddAuth(ZoneAuthRequest* zar); + ZoneAuthRequest* GetAuth(int32 account_id, int32 access_key); + void PurgeInactiveAuth(); + void RemoveAuth(ZoneAuthRequest* zar); +private: + LinkedList list; +}; +#endif diff --git a/source/WorldServer/races.cpp b/source/WorldServer/races.cpp new file mode 100644 index 0000000..d2c7eb3 --- /dev/null +++ b/source/WorldServer/races.cpp @@ -0,0 +1,147 @@ +/* + 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 . +*/ +#include "../common/debug.h" +#include "races.h" +#include "../common/MiscFunctions.h" + +Races::Races(){ + race_map["BARBARIAN"] = 0; + race_map["DARKELF"] = 1; + race_map["DWARF"] = 2; + race_map["ERUDITE"] = 3; + race_map["FROGLOK"] = 4; + race_map["GNOME"] = 5; + race_map["HALFELF"] = 6; + race_map["HALFLING"] = 7; + race_map["HIGHELF"] = 8; + race_map["HUMAN"] = 9; + race_map["IKSAR"] = 10; + race_map["KERRA"] = 11; + race_map["OGRE"] = 12; + race_map["RATONGA"] = 13; + race_map["TROLL"] = 14; + race_map["WOODELF"] = 15; + race_map["FAE_LIGHT"] = 16; + race_map["FAE_DARK"] = 17; + race_map["SARNAK"] = 18; + race_map["VAMPIRE"] = 19; + race_map["AERAKYN"] = 20; + + race_map_friendly[0] = "Barbarian"; + race_map_friendly[1] = "Dark Elf"; + race_map_friendly[2] = "Dwarf"; + race_map_friendly[3] = "Erudite"; + race_map_friendly[4] = "Froglok"; + race_map_friendly[5] = "Gnome"; + race_map_friendly[6] = "Half Elf"; + race_map_friendly[7] = "Halfling"; + race_map_friendly[8] = "High Elf"; + race_map_friendly[9] = "Human"; + race_map_friendly[10] = "Iksar"; + race_map_friendly[11] = "Kerra"; + race_map_friendly[12] = "Ogre"; + race_map_friendly[13] = "Ratonga"; + race_map_friendly[14] = "Troll"; + race_map_friendly[15] = "Wood Elf"; + race_map_friendly[16] = "Fae"; + race_map_friendly[17] = "Arasai"; + race_map_friendly[18] = "Sarnak"; + race_map_friendly[19] = "Vampire"; + race_map_friendly[20] = "Aerakyn"; + + // "Neutral" races are in both lists - this is for /randomize RACE + race_map_good[0] = "DWARF"; + race_map_good[1] = "FAE_LIGHT"; + race_map_good[2] = "FROGLOK"; + race_map_good[3] = "HALFLING"; + race_map_good[4] = "HIGHELF"; + race_map_good[5] = "WOODELF"; + race_map_good[6] = "BARBARIAN"; + race_map_good[7] = "ERUDITE"; + race_map_good[8] = "GNOME"; + race_map_good[9] = "HALFELF"; + race_map_good[10] = "HUMAN"; + race_map_good[11] = "KERRA"; + race_map_good[12] = "VAMPIRE"; + race_map_good[13] = "AERAKYN"; + + race_map_evil[0] = "FAE_DARK"; + race_map_evil[1] = "DARKELF"; + race_map_evil[2] = "IKSAR"; + race_map_evil[3] = "OGRE"; + race_map_evil[4] = "RATONGA"; + race_map_evil[5] = "SARNAK"; + race_map_evil[6] = "TROLL"; + race_map_evil[7] = "BARBARIAN"; + race_map_evil[8] = "ERUDITE"; + race_map_evil[9] = "GNOME"; + race_map_evil[10] = "HALFELF"; + race_map_evil[11] = "HUMAN"; + race_map_evil[12] = "KERRA"; + race_map_evil[13] = "VAMPIRE"; + race_map_evil[14] = "AERAKYN"; +} + +sint8 Races::GetRaceID(const char* name){ + string race_name = string(name); + race_name = ToUpper(race_name); + if(race_map.count(race_name) == 1) + return race_map[race_name]; + else + return -1; +} + +const char* Races::GetRaceName(int8 race_id){ + map::iterator itr; + for(itr = race_map.begin(); itr != race_map.end(); itr++){ + if(itr->second == race_id) + return itr->first.c_str(); + } + return 0; +} + +const char* Races::GetRaceNameCase(int8 race_id) { + map::iterator itr; + for(itr = race_map_friendly.begin(); itr != race_map_friendly.end(); itr++){ + if(itr->first == race_id) + return itr->second.c_str(); + } + return 0; +} + +int8 Races::GetRaceNameGood() { + int8 random = MakeRandomInt(0,13); // 12 good races + map::iterator itr; + for(itr = race_map_good.begin(); itr != race_map_good.end(); itr++){ + if(itr->first == random) + return GetRaceID(itr->second.c_str()); + } + return 9; // default to Human race if error finding another +} + +int8 Races::GetRaceNameEvil() { + int8 random = MakeRandomInt(0,14); // 13 evil races + map::iterator itr; + for(itr = race_map_evil.begin(); itr != race_map_evil.end(); itr++){ + if(itr->first == random) + return GetRaceID(itr->second.c_str()); + } + return 9; // default to Human race if error finding another +} diff --git a/source/WorldServer/races.h b/source/WorldServer/races.h new file mode 100644 index 0000000..1764959 --- /dev/null +++ b/source/WorldServer/races.h @@ -0,0 +1,62 @@ +/* + 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 . +*/ +#ifndef RACES_H +#define RACES_H +#include "../common/types.h" +#include +using namespace std; + +#define BARBARIAN 0 +#define DARK_ELF 1 +#define DWARF 2 +#define ERUDITE 3 +#define FROGLOK 4 +#define GNOME 5 +#define HALF_ELF 6 +#define HALFLING 7 +#define HIGH_ELF 8 +#define HUMAN 9 +#define IKSAR 10 +#define KERRA 11 +#define OGRE 12 +#define RATONGA 13 +#define TROLL 14 +#define WOOD_ELF 15 +#define FAE 16 +#define ARASAI 17 +#define SARNAK 18 +#define VAMPIRE 19 +#define AERAKYN 20 + +class Races { +public: + Races(); + const char* GetRaceName(int8 race_id); + const char* GetRaceNameCase(int8 race_id); + int8 GetRaceNameGood(); + int8 GetRaceNameEvil(); + sint8 GetRaceID(const char* name); +private: + map race_map; + map race_map_friendly; + map race_map_good; + map race_map_evil; +}; +#endif diff --git a/source/WorldServer/zoneserver.cpp b/source/WorldServer/zoneserver.cpp new file mode 100644 index 0000000..61b1200 --- /dev/null +++ b/source/WorldServer/zoneserver.cpp @@ -0,0 +1,9104 @@ +/* +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 . +*/ + +#include "../common/debug.h" +#include +using namespace std; +#include +#include "../common/misc.h" +#include +#include +#include +#include +#include +#include "Commands/Commands.h" +#include "Zone/pathfinder_interface.h" +#include "NPC_AI.h" + +#ifdef WIN32 +#include +#include +#include +#pragma comment(lib,"imagehlp.lib") +#else +#include +#include +#ifdef FREEBSD //Timothy Whitman - January 7, 2003 +#include +#endif +#include +#include +#include +#include +#include + +#include "../common/unix.h" + +#define SOCKET_ERROR -1 +#define INVALID_SOCKET -1 + +extern int errno; +#endif + +#include "../common/servertalk.h" +#include "../common/packet_dump.h" +#include "WorldDatabase.h" +#include "races.h" +#include "classes.h" +#include "../common/seperator.h" +#include "../common/EQStream.h" +#include "../common/EQStreamFactory.h" +#include "../common/opcodemgr.h" +#include "zoneserver.h" +#include "client.h" +#include "LoginServer.h" +#include "World.h" +#include +#include +#include "LuaInterface.h" +#include "Factions.h" +#include "VisualStates.h" +#include "ClientPacketFunctions.h" +#include "SpellProcess.h" +#include "../common/Log.h" +#include "Rules/Rules.h" +#include "Chat/Chat.h" +#include "Tradeskills/Tradeskills.h" +#include "RaceTypes/RaceTypes.h" +#include +#include + +#include "Bots/Bot.h" + +#ifdef WIN32 +#define strncasecmp _strnicmp +#define strcasecmp _stricmp +#endif + +// int32 numplayers = 0; // never used? +// extern bool GetZoneLongName(char* short_name, char** long_name); // never used? +// extern bool holdzones; // never used? +// extern volatile bool RunLoops; // never used in the zone server? +// extern Classes classes; // never used in the zone server? + +#define NO_CATCH 1 + +extern WorldDatabase database; +extern sint32 numzones; +extern ClientList client_list; +extern LoginServer loginserver; +extern ZoneList zone_list; +extern World world; +extern ConfigReader configReader; +extern Commands commands; +extern LuaInterface* lua_interface; +extern MasterFactionList master_faction_list; +extern VisualStates visual_states; +extern RuleManager rule_manager; +extern Chat chat; +extern MasterRaceTypeList race_types_list; +extern MasterSpellList master_spell_list; // temp - remove later +extern MasterSkillList master_skill_list; + + +int32 MinInstanceID = 1000; + +// JA: Moved most default values to Rules and risky initializers to ZoneServer::Init() - 2012.12.07 +ZoneServer::ZoneServer(const char* name) { + incoming_clients = 0; + default_zone_map = nullptr; + + MIncomingClients.SetName("ZoneServer::MIncomingClients"); + + depop_zone = false; + repop_zone = false; + respawns_allowed = true; + instanceID = 0; + strcpy(zone_name, name); + zoneID = 0; + rain = 0; + cityzone = false; + always_loaded = false; + locked = false; // JA: implementing /zone lock|unlock commands + pNumPlayers = 0; + LoadingData = true; + zoneShuttingDown = false; + ++numzones; + revive_points = 0; + zone_motd = ""; + finished_depop = true; + initial_spawn_threads_active = 0; + minimumStatus = 0; + minimumLevel = 0; + maximumLevel = 0; + minimumVersion = 0; + weather_current_severity = 0; + weather_signaled = false; + xp_mod = 0; + isDusk = false; + dusk_hour = 0; + dusk_minute = 0; + dawn_hour = 0; + dawn_minute = 0; + reloading_spellprocess = false; + expansion_flag = 0; + holiday_flag = 0; + can_bind = 1; + can_gate = 1; + MMasterZoneLock = new CriticalSection(MUTEX_ATTRIBUTE_RECURSIVE); + + pathing = nullptr; + strcpy(zonesky_file,""); + + reloading = true; + watchdogTimestamp = Timer::GetCurrentTime2(); + + MPendingSpawnRemoval.SetName("ZoneServer::MPendingSpawnRemoval"); + + lifetime_client_count = 0; +} + +typedef map ChangedSpawnMapType; +ZoneServer::~ZoneServer() { + zoneShuttingDown = true; //ensure other threads shut down too + //allow other threads to properly shut down + LogWrite(ZONE__INFO, 0, "Zone", "Initiating zone shutdown of '%s'", zone_name); + int32 disp_count = 0; + int32 next_disp_count = 100; + while (spawnthread_active || initial_spawn_threads_active > 0){ + bool disp = false; + if ( disp_count == 0 ) { + disp = true; + } + else if(disp_count >= next_disp_count) { + disp_count = 0; + disp = true; + } + + disp_count++; + if (spawnthread_active && disp) + LogWrite(ZONE__DEBUG, 7, "Zone", "Zone shutdown waiting on spawn thread"); + if (initial_spawn_threads_active > 0 && disp) + LogWrite(ZONE__DEBUG, 7, "Zone", "Zone shutdown waiting on initial spawn thread"); + Sleep(10); + } + + MChangedSpawns.lock(); + changed_spawns.clear(); + MChangedSpawns.unlock(); + + transport_spawns.clear(); + safe_delete(tradeskillMgr); + MMasterZoneLock->lock(); + MMasterSpawnLock.writelock(__FUNCTION__, __LINE__); + DeleteData(true); + RemoveLocationProximities(); + RemoveLocationGrids(); + DeleteSpawns(true); + + DeleteGlobalSpawns(); + + DeleteFlightPaths(); + + MMasterSpawnLock.releasewritelock(__FUNCTION__, __LINE__); + MMasterZoneLock->unlock(); + world.UpdateServerStatistic(STAT_SERVER_NUM_ACTIVE_ZONES, -1); + + // If lockout, public, tradeskill, or quest instance delete from db when zone shuts down + if (InstanceType == SOLO_LOCKOUT_INSTANCE || InstanceType == GROUP_LOCKOUT_INSTANCE || InstanceType == RAID_LOCKOUT_INSTANCE || + InstanceType == PUBLIC_INSTANCE || InstanceType == TRADESKILL_INSTANCE || InstanceType == QUEST_INSTANCE) { + LogWrite(INSTANCE__DEBUG, 0, "Instance", "Non persistent instance shutting down, deleting instance"); + database.DeleteInstance(instanceID); + } + + if (pathing != nullptr) + delete pathing; + + if (movementMgr != nullptr) + delete movementMgr; + + // moved to the bottom as we want spawns deleted first, this used to be above Spawn deletion which is a big no no + safe_delete(spellProcess); + + + MGridMaps.lock(); + std::map::iterator grids; + for(grids = grid_maps.begin(); grids != grid_maps.end(); grids++) { + GridMap* gm = grids->second; + safe_delete(gm); + } + grid_maps.clear(); + MGridMaps.unlock(); + + LogWrite(ZONE__INFO, 0, "Zone", "Completed zone shutdown of '%s'", zone_name); + --numzones; + UpdateWindowTitle(0); + zone_list.Remove(this); + zone_list.RemoveClientZoneReference(this); + safe_delete(MMasterZoneLock); +} + +void ZoneServer::IncrementIncomingClients() { + MIncomingClients.writelock(__FUNCTION__, __LINE__); + incoming_clients++; + LogWrite(ZONE__INFO, 0, "Zone", "Increment incoming clients of '%s' zoneid %u (instance id: %u). Current incoming client count: %u", zone_name, zoneID, instanceID, incoming_clients); + MIncomingClients.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::DecrementIncomingClients() { + MIncomingClients.writelock(__FUNCTION__, __LINE__); + bool zeroed = false; + if(incoming_clients) + incoming_clients--; + else + zeroed = true; + LogWrite(ZONE__INFO, 0, "Zone", "Decrement incoming clients of '%s' zoneid %u (instance id: %u). Current incoming client count: %u (was client count previously zero: %u)", zone_name, zoneID, instanceID, incoming_clients, zeroed); + MIncomingClients.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::Init() +{ + LogWrite(ZONE__INFO, 0, "Zone", "Loading new Zone '%s'", zone_name); + zone_list.Add(this); + + spellProcess = new SpellProcess(); + tradeskillMgr = new TradeskillMgr(); + + /* Dynamic Timers */ + regenTimer.Start(rule_manager.GetGlobalRule(R_Zone, RegenTimer)->GetInt32()); + client_save.Start(rule_manager.GetGlobalRule(R_Zone, ClientSaveTimer)->GetInt32()); + shutdownTimer.Disable(); + spawn_range.Start(rule_manager.GetGlobalRule(R_Zone, CheckAttackPlayer)->GetInt32()); + aggro_timer.Start(rule_manager.GetGlobalRule(R_Zone, CheckAttackNPC)->GetInt32()); + /* Weather stuff */ + InitWeather(); + + /* Static Timers */ + // JA - haven't decided yet if these should remain hard-coded. Changing them could break EQ2Emu functionality + spawn_check_add.Start(1000); + spawn_check_remove.Start(30000); + spawn_expire_timer.Start(10000); + respawn_timer.Start(10000); + // there was never a starter for these? + widget_timer.Start(5000); + + tracking_timer.Start(5000); + + movement_timer.Start(100); + location_prox_timer.Start(1000); + location_grid_timer.Start(1000); + + charsheet_changes.Start(500); + + // Send game time packet every in game hour (180 sec) + sync_game_time_timer.Start(180000); + + // Get the dusk and dawn time from the rule manager and store it in the correct variables + sscanf (rule_manager.GetGlobalRule(R_World, DuskTime)->GetString(), "%d:%d", &dusk_hour, &dusk_minute); + sscanf (rule_manager.GetGlobalRule(R_World, DawnTime)->GetString(), "%d:%d", &dawn_hour, &dawn_minute); + + spawn_update.Start(rule_manager.GetGlobalRule(R_Zone, SpawnUpdateTimer)->GetInt16()); + LogWrite(ZONE__DEBUG, 0, "Zone", "SpawnUpdateTimer: %ims", rule_manager.GetGlobalRule(R_Zone, SpawnUpdateTimer)->GetInt16()); + + queue_updates.Start(rule_manager.GetGlobalRule(R_Zone, SpawnUpdateTimer)->GetInt16()); + LogWrite(ZONE__DEBUG, 0, "Zone", "QueueUpdateTimer(inherits SpawnUpdateTimer): %ims", rule_manager.GetGlobalRule(R_Zone, SpawnUpdateTimer)->GetInt16()); + + spawn_delete_timer = rule_manager.GetGlobalRule(R_Zone, SpawnDeleteTimer)->GetInt32(); + LogWrite(ZONE__DEBUG, 0, "Zone", "SpawnDeleteTimer: %ums", spawn_delete_timer); + + LogWrite(ZONE__DEBUG, 0, "Zone", "Loading zone flight paths"); + database.LoadZoneFlightPaths(this); + + world.UpdateServerStatistic(STAT_SERVER_NUM_ACTIVE_ZONES, 1); + UpdateWindowTitle(0); + + string zoneName(GetZoneFile()); + + world.LoadRegionMaps(zoneName); + + world.LoadMaps(zoneName); + + pathing = IPathfinder::Load(zoneName); + movementMgr = new MobMovementManager(); + + MMasterSpawnLock.SetName("ZoneServer::MMasterSpawnLock"); + m_npc_faction_list.SetName("ZoneServer::npc_faction_list"); + m_enemy_faction_list.SetName("ZoneServer::enemy_faction_list"); + m_reverse_enemy_faction_list.SetName("ZoneServer::reverse_enemy_faction_list"); + MDeadSpawns.SetName("ZoneServer::dead_spawns"); + MTransportSpawns.SetName("ZoneServer::transport_spawns"); + MSpawnList.SetName("ZoneServer::spawn_list"); + MTransporters.SetName("ZoneServer::m_transportMaps"); + MSpawnGroupAssociation.SetName("ZoneServer::spawn_group_associations"); + MSpawnGroupLocations.SetName("ZoneServer::spawn_group_locations"); + MSpawnLocationGroups.SetName("ZoneServer::spawn_location_groups"); + MSpawnGroupChances.SetName("ZoneServer::spawn_group_chances"); + MTransportLocations.SetName("ZoneServer::transporter_locations"); + MSpawnLocationList.SetName("ZoneServer::spawn_location_list"); + MSpawnDeleteList.SetName("ZoneServer::spawn_delete_list"); + MSpawnScriptTimers.SetName("ZoneServer::spawn_script_timers"); + MRemoveSpawnScriptTimersList.SetName("ZoneServer::remove_spawn_script_timers_list"); + MClientList.SetName("ZoneServer::clients"); + MWidgetTimers.SetName("ZoneServer::widget_timers"); +#ifdef WIN32 + _beginthread(ZoneLoop, 0, this); + _beginthread(SpawnLoop, 0, this); +#else + pthread_create(&ZoneThread, NULL, ZoneLoop, this); + pthread_detach(ZoneThread); + pthread_create(&SpawnThread, NULL, SpawnLoop, this); + pthread_detach(SpawnThread); +#endif +} + +void ZoneServer::CancelThreads() { +#ifdef WIN32 + LogWrite(WORLD__ERROR, 1, "World", "Zone %s is hung, however CancelThreads is unsupported for WIN32.", GetZoneName()); +#else + pthread_cancel(ZoneThread); + pthread_cancel(SpawnThread); +#endif +} + +void ZoneServer::InitWeather() +{ + weather_enabled = rule_manager.GetGlobalRule(R_Zone, WeatherEnabled)->GetBool(); + if( weather_enabled && isWeatherAllowed()) + { + string tmp; + // set up weather system when zone starts up + weather_type = rule_manager.GetGlobalRule(R_Zone, WeatherType)->GetInt8(); + switch(weather_type) + { + case 3: tmp = "Chaotic"; break; + case 2: tmp = "Random"; break; + case 1: tmp = "Dynamic"; break; + default: tmp = "Normal"; break; + } + LogWrite(ZONE__DEBUG, 0, "Zone", "%s: Setting up '%s' weather", zone_name, tmp.c_str()); + + weather_frequency = rule_manager.GetGlobalRule(R_Zone, WeatherChangeFrequency)->GetInt32(); + LogWrite(ZONE__DEBUG, 1, "Zone", "%s: Change weather every %u seconds", zone_name, weather_frequency); + + weather_change_chance = rule_manager.GetGlobalRule(R_Zone, WeatherChangeChance)->GetInt8(); + LogWrite(ZONE__DEBUG, 1, "Zone", "%s: Chance of weather change: %i%%", zone_name, weather_change_chance); + + weather_min_severity = rule_manager.GetGlobalRule(R_Zone, MinWeatherSeverity)->GetFloat(); + weather_max_severity = rule_manager.GetGlobalRule(R_Zone, MaxWeatherSeverity)->GetFloat(); + LogWrite(ZONE__DEBUG, 1, "Zone", "%s: Weather Severity min/max is %.2f - %.2f", zone_name, weather_min_severity, weather_max_severity); + // Allow a random roll to determine if weather should start out severe or calm + if( MakeRandomInt(1, 100) > 50) + { + weather_pattern = 1; // default weather to increase in severity initially + weather_current_severity = weather_min_severity; + } + else + { + weather_pattern = 0; // default weather to decrease in severity initially + weather_current_severity = weather_max_severity; + } + LogWrite(ZONE__DEBUG, 1, "Zone", "%s: Weather Severity set to %.2f, pattern: %i", zone_name, weather_current_severity, weather_pattern); + + weather_change_amount = rule_manager.GetGlobalRule(R_Zone, WeatherChangePerInterval)->GetFloat(); + LogWrite(ZONE__DEBUG, 1, "Zone", "%s: Weather change by %.2f each interval", zone_name, weather_change_amount); + + if( weather_type > 0 ) + { + weather_dynamic_offset = rule_manager.GetGlobalRule(R_Zone, WeatherDynamicMaxOffset)->GetFloat(); + LogWrite(ZONE__DEBUG, 1, "Zone", "%s: Weather Max Offset changes no more than %.2f each interval", zone_name, weather_dynamic_offset); + } + else + weather_dynamic_offset = 0; + + SetRain(weather_current_severity); + weather_last_changed_time = Timer::GetUnixTimeStamp(); + weatherTimer.Start(rule_manager.GetGlobalRule(R_Zone, WeatherTimer)->GetInt32()); + } +} +void ZoneServer::DeleteSpellProcess(){ + //Just get a lock to make sure we aren't already looping the spawnprocess or clientprocess if this is different than the calling thread + MMasterSpawnLock.writelock(__FUNCTION__, __LINE__); + MMasterZoneLock->lock(); + reloading_spellprocess = true; + // Remove spells from NPC's + Spawn* spawn = 0; + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + spawn = itr->second; + if(spawn && spawn->IsNPC()) + ((NPC*)spawn)->SetSpells(0); + + if(spawn->IsEntity()) + ((Entity*)spawn)->RemoveSpellBonus(nullptr, true); + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + + MMasterZoneLock->unlock(); + MMasterSpawnLock.releasewritelock(__FUNCTION__, __LINE__); + DismissAllPets(); + spellProcess->RemoveAllSpells(true); + safe_delete(spellProcess); +} + +void ZoneServer::LoadSpellProcess(){ + spellProcess = new SpellProcess(); + reloading_spellprocess = false; + + // Reload NPC's spells + Spawn* spawn = 0; + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + spawn = itr->second; + if(spawn && spawn->IsNPC()) + ((NPC*)spawn)->SetSpells(world.GetNPCSpells(((NPC*)spawn)->GetPrimarySpellList(), ((NPC*)spawn)->GetSecondarySpellList())); + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::LockAllSpells(Player* player) { + if (player && spellProcess) { + Client* client = ((Player*)player)->GetClient(); + if (client) + spellProcess->LockAllSpells(client); + } +} + +void ZoneServer::UnlockAllSpells(Player* player) { + if (player && spellProcess) { + Client* client = ((Player*)player)->GetClient(); + if (client) + spellProcess->UnlockAllSpells(client); + } +} + +void ZoneServer::DeleteFactionLists() { + map *>::iterator faction_itr; + map *>::iterator spawn_itr; + + m_enemy_faction_list.writelock(__FUNCTION__, __LINE__); + for (faction_itr = enemy_faction_list.begin(); faction_itr != enemy_faction_list.end(); faction_itr++) + safe_delete(faction_itr->second); + enemy_faction_list.clear(); + m_enemy_faction_list.releasewritelock(__FUNCTION__, __LINE__); + + m_reverse_enemy_faction_list.writelock(__FUNCTION__, __LINE__); + for (faction_itr = reverse_enemy_faction_list.begin(); faction_itr != reverse_enemy_faction_list.end(); faction_itr++) + safe_delete(faction_itr->second); + reverse_enemy_faction_list.clear(); + m_reverse_enemy_faction_list.releasewritelock(__FUNCTION__, __LINE__); + + m_npc_faction_list.writelock(__FUNCTION__, __LINE__); + for (spawn_itr = npc_faction_list.begin(); spawn_itr != npc_faction_list.end(); spawn_itr++) + safe_delete(spawn_itr->second); + npc_faction_list.clear(); + m_npc_faction_list.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::DeleteData(bool boot_clients){ + Spawn* spawn = 0; + vector tmp_player_list; // changed to a vector from a MutexList as this is a local variable and don't need mutex stuff for the list + + // Clear spawn groups + spawn_group_map.clear(); + + // Loop through the spawn list and set the spawn for deletion + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + spawn = itr->second; + if(spawn){ + if(!boot_clients && (spawn->IsPlayer() || spawn->IsBot())) + tmp_player_list.push_back(spawn); + else if(spawn->IsPlayer()){ + Client* client = ((Player*)spawn)->GetClient(); + if(client) + client->Disconnect(); + } + else{ + RemoveSpawnSupportFunctions(spawn, boot_clients, true); + RemoveSpawnFromGrid(spawn, spawn->GetLocation()); + AddPendingDelete(spawn); + } + } + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + + // Quick hack to prevent a deadlock, RemoveSpawnSupportFunctions() will cancel spells and result in zone->GetSpawnByID() + // being called which read locks the spawn list and caused a dead lock as the above mutex's were write locked + MSpawnList.writelock(__FUNCTION__, __LINE__); + // Clear the spawn list, this was in the mutex above, moved it down so the above mutex could be a read lock + spawn_list.clear(); + + // Moved this up so we only read lock the list once in this list + vector::iterator spawn_iter2; + for (spawn_iter2 = tmp_player_list.begin(); spawn_iter2 != tmp_player_list.end(); spawn_iter2++) { + spawn_list[(*spawn_iter2)->GetID()] = (*spawn_iter2); + } + MSpawnList.releasewritelock(__FUNCTION__, __LINE__); + + // Clear player proximities + RemovePlayerProximity(0, true); + + spawn_range_map.clear(true); + if(boot_clients) { + // Refactor + vector::iterator itr; + + MClientList.writelock(__FUNCTION__, __LINE__); + for (itr = clients.begin(); itr != clients.end(); itr++) + safe_delete(*itr); + + clients.clear(); + MClientList.releasewritelock(__FUNCTION__, __LINE__); + } + + // Clear and delete spawn locations + MSpawnLocationList.writelock(__FUNCTION__, __LINE__); + map::iterator spawn_location_iter; + for (spawn_location_iter = spawn_location_list.begin(); spawn_location_iter != spawn_location_list.end(); spawn_location_iter++) + safe_delete(spawn_location_iter->second); + + spawn_location_list.clear(); + MSpawnLocationList.releasewritelock(__FUNCTION__, __LINE__); + + // If we allow clients to stay in the zone we need to preserve the revive_points, otherwise if the player dies they will crash + if(revive_points && boot_clients){ + vector::iterator revive_iter; + for(revive_iter=revive_points->begin(); revive_iter != revive_points->end(); revive_iter++){ + safe_delete(*revive_iter); + } + safe_delete(revive_points); + } + + MSpawnGroupAssociation.writelock(__FUNCTION__, __LINE__); + map*>::iterator assoc_itr; + for (assoc_itr = spawn_group_associations.begin(); assoc_itr != spawn_group_associations.end(); assoc_itr++) + safe_delete(assoc_itr->second); + + spawn_group_associations.clear(); + MSpawnGroupAssociation.releasewritelock(__FUNCTION__, __LINE__); + + MSpawnGroupLocations.writelock(__FUNCTION__, __LINE__); + map*>::iterator loc_itr; + for (loc_itr = spawn_group_locations.begin(); loc_itr != spawn_group_locations.end(); loc_itr++) + safe_delete(loc_itr->second); + + spawn_group_locations.clear(); + MSpawnGroupLocations.releasewritelock(__FUNCTION__, __LINE__); + + MSpawnLocationGroups.writelock(__FUNCTION__, __LINE__); + map*>::iterator group_itr; + for (group_itr = spawn_location_groups.begin(); group_itr != spawn_location_groups.end(); group_itr++) + safe_delete(group_itr->second); + + spawn_location_groups.clear(); + MSpawnLocationGroups.releasewritelock(__FUNCTION__, __LINE__); + + // Clear lists that need more then just a Clear() + DeleteFactionLists(); + DeleteSpawnScriptTimers(0, true); + DeleteSpawnScriptTimers(); + ClearDeadSpawns(); + + // Clear lists + movement_spawns.clear(); + respawn_timers.clear(); + transport_spawns.clear(); + quick_database_id_lookup.clear(); + + MWidgetTimers.writelock(__FUNCTION__, __LINE__); + widget_timers.clear(); + MWidgetTimers.releasewritelock(__FUNCTION__, __LINE__); + + map::iterator struct_itr; + for (struct_itr = versioned_info_structs.begin(); struct_itr != versioned_info_structs.end(); struct_itr++) + safe_delete(struct_itr->second); + versioned_info_structs.clear(); + + for (struct_itr = versioned_pos_structs.begin(); struct_itr != versioned_pos_structs.end(); struct_itr++) + safe_delete(struct_itr->second); + versioned_pos_structs.clear(); + + for (struct_itr = versioned_vis_structs.begin(); struct_itr != versioned_vis_structs.end(); struct_itr++) + safe_delete(struct_itr->second); + versioned_vis_structs.clear(); +} + +void ZoneServer::RemoveLocationProximities() { + MutexList::iterator itr = location_proximities.begin(); + while(itr.Next()){ + safe_delete(itr->value); + } + location_proximities.clear(); +} + +RevivePoint* ZoneServer::GetRevivePoint(int32 id){ + vector::iterator revive_iter; + for(revive_iter=revive_points->begin(); revive_iter != revive_points->end(); revive_iter++){ + if((*revive_iter)->id == id) + return *revive_iter; + } + return 0; +} + +vector* ZoneServer::GetRevivePoints(Client* client) +{ + vector* points = new vector; + RevivePoint* closest_point = 0; + + // we should not check for revive points if this is null + if ( revive_points != NULL ) + { + LogWrite(ZONE__DEBUG, 0, "Zone", "Got revive point in %s!", __FUNCTION__); + + float closest = 100000; + float test_closest = 0; + RevivePoint* test_point = 0; + vector::iterator revive_iter; + for(revive_iter=revive_points->begin(); revive_iter != revive_points->end(); revive_iter++) + { + test_point = *revive_iter; + if(test_point) + { + test_closest = client->GetPlayer()->GetDistance(test_point->x, test_point->y, test_point->z); + + // should this be changed to list all revive points within max distance or just the closest + if(test_closest < closest) + { + LogWrite(ZONE__DEBUG, 0, "Zone", "test_closest: %.2f, closest: %.2f", test_closest, closest); + closest = test_closest; + closest_point = test_point; + } + if(test_point->always_included ) { + points->push_back(test_point); + if(closest_point == test_point) { + closest_point = nullptr; + closest = 100000; + } + } + } + } + if(closest_point) { + points->push_back(closest_point); + } + } + + if(closest_point && points->size() == 0 && closest_point->zone_id == GetZoneID()) + { + LogWrite(ZONE__WARNING, 0, "Zone", "Nearest Revive Point too far away. Add another!"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "The closest revive point is quite far away, you might want to ask the server admin for a closer one."); + points->push_back(closest_point); + } + else if(points->size() == 0) + { + LogWrite(ZONE__WARNING, 0, "Zone", "No Revive Points set for zoneID %u. Add some!", GetZoneID()); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "There are no revive points for this zone, you might want to ask the server admin for one."); + closest_point = new RevivePoint; + closest_point->heading = GetSafeHeading(); + closest_point->id = 0xFFFFFFFF; + closest_point->location_name = "Zone Safe Point"; + closest_point->zone_id = GetZoneID(); + closest_point->x = GetSafeX(); + closest_point->y = GetSafeY(); + closest_point->z = GetSafeZ(); + closest_point->always_included = true; + points->push_back(closest_point); + } + return points; +} + +void ZoneServer::TriggerCharSheetTimer(){ + charsheet_changes.Trigger(); +} + +void ZoneServer::RegenUpdate(){ + if(damaged_spawns.size(true) == 0) + return; + + Spawn* spawn = 0; + MutexList::iterator spawn_iter = damaged_spawns.begin(); + while(spawn_iter.Next()){ + spawn = GetSpawnByID(spawn_iter->value); + if(spawn && (((spawn->GetHP() < spawn->GetTotalHP()) && spawn->GetHP()>0) || (spawn->GetPower() < spawn->GetTotalPower()))){ + if(spawn->IsEntity()) + ((Entity*)spawn)->DoRegenUpdate(); + if(spawn->IsPlayer()){ + Client* client = ((Player*)spawn)->GetClient(); + if(client && client->IsReadyForUpdates()) + client->QueuePacket(client->GetPlayer()->GetPlayerInfo()->serialize(client->GetVersion())); + } + } + else + RemoveDamagedSpawn(spawn); + //Spawn no longer valid, remove it from the list + if (!spawn) + damaged_spawns.Remove(spawn_iter->value); + } +} + +void ZoneServer::ClearDeadSpawns(){ + MDeadSpawns.writelock(__FUNCTION__, __LINE__); + dead_spawns.clear(); + MDeadSpawns.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::ProcessDepop(bool respawns_allowed, bool repop) { + vector::iterator client_itr; + Client* client = 0; + Spawn* spawn = 0; + PacketStruct* packet = 0; + int16 packet_version = 0; + spawn_expire_timers.clear(); + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(!client) + continue; + client->GetPlayer()->SetTarget(0); + if(repop) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Zone Repop in progress..."); + else{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Zone Depop in progress..."); + if(respawns_allowed) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Spawns will respawn according to their respawn timers."); + } + if(!packet || packet_version != client->GetVersion()){ + safe_delete(packet); + packet_version = client->GetVersion(); + packet = configReader.getStruct("WS_DestroyGhostCmd", packet_version); + } + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + spawn = itr->second; + if(spawn && !spawn->IsPlayer() && !spawn->IsBot()){ + bool dispatched = false; + if(spawn->IsPet()) + { + Entity* owner = ((Entity*)spawn)->GetOwner(); + if(owner) + { + owner->DismissPet((Entity*)spawn); + dispatched = true; + } + } + + spawn->SetDeletedSpawn(true); + + if(!dispatched) + SendRemoveSpawn(client, spawn, packet); + } + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + + DeleteTransporters(); + safe_delete(packet); + if(!repop && respawns_allowed){ + spawn_range_map.clear(true); + MutexList tmp_player_list; // Local variable, never be on another thread so probably don't need the extra mutex code that comes with a MutexList + ClearDeadSpawns(); + + map::iterator itr; + MSpawnList.writelock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + spawn = itr->second; + if (spawn) { + if(spawn->GetRespawnTime() > 0 && spawn->GetSpawnLocationID() > 0) + respawn_timers.Put(spawn->GetSpawnLocationID(), Timer::GetCurrentTime2() + spawn->GetRespawnTime()*1000); + if(spawn->IsPlayer() || spawn->IsBot()) + tmp_player_list.Add(spawn); + else { + RemoveSpawnSupportFunctions(spawn, true); + RemoveSpawnFromGrid(spawn, spawn->GetLocation()); + AddPendingDelete(spawn); + } + } + } + spawn_list.clear(); + //add back just the clients + MutexList::iterator spawn_iter2 = tmp_player_list.begin(); + while(spawn_iter2.Next()) { + spawn_list[spawn_iter2->value->GetID()] = spawn_iter2->value; + } + MSpawnList.releasewritelock(__FUNCTION__, __LINE__); + } + else { + DeleteData(false); + } + + if(repop) + { + // reload spirit shards for the current zone + database.LoadSpiritShards(this); + + LoadingData = true; + } +} + +void ZoneServer::Depop(bool respawns, bool repop) { + respawns_allowed = respawns; + repop_zone = repop; + finished_depop = false; + depop_zone = true; +} + +bool ZoneServer::AddCloseSpawnsToSpawnGroup(Spawn* spawn, float radius){ + if(!spawn) + return false; + + Spawn* close_spawn = 0; + bool ret = true; + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + close_spawn = itr->second; + if(close_spawn && close_spawn != spawn && !close_spawn->IsPlayer() && close_spawn->GetDistance(spawn) <= radius){ + if((spawn->IsNPC() && close_spawn->IsNPC()) || (spawn->IsGroundSpawn() && close_spawn->IsGroundSpawn()) || (spawn->IsObject() && close_spawn->IsObject()) || (spawn->IsWidget() && close_spawn->IsWidget()) || (spawn->IsSign() && close_spawn->IsSign())){ + if(close_spawn->GetSpawnGroupID() == 0){ + spawn->AddSpawnToGroup(close_spawn); + close_spawn->AddSpawnToGroup(spawn); + } + else + ret = false; + } + } + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +void ZoneServer::RepopSpawns(Client* client, Spawn* in_spawn){ + vector* spawns = in_spawn->GetSpawnGroup(); + PacketStruct* packet = configReader.getStruct("WS_DestroyGhostCmd", client->GetVersion()); + if(spawns){ + if(!packet) + return; + + Spawn* spawn = 0; + vector::iterator itr; + for(itr = spawns->begin(); itr != spawns->end(); itr++){ + spawn = *itr; + spawn->SetDeletedSpawn(true); + SendRemoveSpawn(client, spawn, packet); + } + } + safe_delete(spawns); + if(in_spawn) + in_spawn->SetDeletedSpawn(true); + + SendRemoveSpawn(client, in_spawn, packet); + spawn_check_add.Trigger(); + safe_delete(packet); +} + +bool ZoneServer::AggroVictim(NPC* npc, Spawn* victim, Client* client) +{ + bool isEntity = victim->IsEntity(); + if(isEntity && !npc->AttackAllowed((Entity*)victim)) + return false; + + if (npc->HasSpawnGroup()) { + vector* groupVec = npc->GetSpawnGroup(); + for (int32 i = 0; i < groupVec->size(); i++) { + Spawn* group_member = groupVec->at(i); + if (group_member && !group_member->EngagedInCombat() && group_member->Alive()) { + CallSpawnScript(group_member, SPAWN_SCRIPT_AGGRO, victim); + if (isEntity) + ((NPC*)group_member)->AddHate((Entity*)victim, 50); + else + ((NPC*)group_member)->InCombat(true); + } + } + safe_delete(groupVec); + } + else + { + if (isEntity) + { + CallSpawnScript(victim, SPAWN_SCRIPT_AGGRO, victim); + npc->AddHate((Entity*)victim, 50); + } + else + npc->InCombat(true); + } + + victim->CheckEncounterState((Entity*)npc, true); + return true; +} + +bool ZoneServer::CheckNPCAttacks(NPC* npc, Spawn* victim, Client* client){ + if(!npc || !victim) + return true; + + if (client) { + int8 arrow = 0; + if (client->IsReadyForUpdates() && npc->CanSeeInvis(client->GetPlayer()) && client->GetPlayer()->GetFactions()->ShouldAttack(npc->GetFactionID()) && npc->AttackAllowed((Entity*)victim, false)) { + if (!npc->EngagedInCombat()) { + if(client->GetPlayer()->GetArrowColor(npc->GetLevel()) != ARROW_COLOR_GRAY) { + AggroVictim(npc, victim, client); + } + else if(npc->IsScaredByStrongPlayers() && + !client->GetPlayer()->IsSpawnInRangeList(npc->GetID())) { + SendSpawnChanges(npc, client, true, true); + client->GetPlayer()->SetSpawnInRangeList(npc->GetID(), true); + } + } + } + } + else{ + AggroVictim(npc, victim, client); + } + return true; +} + +bool ZoneServer::CheckEnemyList(NPC* npc) { + vector *factions; + vector::iterator faction_itr; + vector *spawns; + vector::iterator spawn_itr; + map attack_spawns; + map reverse_attack_spawns; + map::iterator itr; + int32 faction_id = npc->GetFactionID(); + float distance; + + if (faction_id == 0) + return true; + + m_enemy_faction_list.readlock(__FUNCTION__, __LINE__); + if (enemy_faction_list.count(faction_id) > 0) { + factions = enemy_faction_list[faction_id]; + + for (faction_itr = factions->begin(); faction_itr != factions->end(); faction_itr++) { + m_npc_faction_list.readlock(__FUNCTION__, __LINE__); + if (npc_faction_list.count(*faction_itr) > 0) { + spawns = npc_faction_list[*faction_itr]; + spawn_itr = spawns->begin(); + + for (spawn_itr = spawns->begin(); spawn_itr != spawns->end(); spawn_itr++) { + Spawn* spawn = GetSpawnByID(*spawn_itr); + if (spawn) { + if ((!npc->IsPrivateSpawn() || npc->AllowedAccess(spawn)) && (distance = spawn->GetDistance(npc)) <= npc->GetAggroRadius() && npc->CheckLoS(spawn)) + attack_spawns[distance] = spawn; + } + } + } + m_npc_faction_list.releasereadlock(__FUNCTION__, __LINE__); + } + } + m_enemy_faction_list.releasereadlock(__FUNCTION__, __LINE__); + + m_reverse_enemy_faction_list.readlock(__FUNCTION__, __LINE__); + if (reverse_enemy_faction_list.count(faction_id) > 0) { + factions = reverse_enemy_faction_list[faction_id]; + + for (faction_itr = factions->begin(); faction_itr != factions->end(); faction_itr++) { + m_npc_faction_list.readlock(__FUNCTION__, __LINE__); + if (npc_faction_list.count(*faction_itr) > 0) { + spawns = npc_faction_list[*faction_itr]; + spawn_itr = spawns->begin(); + + for (spawn_itr = spawns->begin(); spawn_itr != spawns->end(); spawn_itr++) { + Spawn* spawn = GetSpawnByID(*spawn_itr); + if (spawn) { + if ((!npc->IsPrivateSpawn() || npc->AllowedAccess(spawn)) && (distance = spawn->GetDistance(npc)) <= npc->GetAggroRadius() && npc->CheckLoS(spawn)) + reverse_attack_spawns[distance] = spawn; + } + } + } + m_npc_faction_list.releasereadlock(__FUNCTION__, __LINE__); + } + } + m_reverse_enemy_faction_list.releasereadlock(__FUNCTION__, __LINE__); + + if (attack_spawns.size() > 0) { + for (itr = attack_spawns.begin(); itr != attack_spawns.end(); itr++) + CheckNPCAttacks(npc, itr->second); + } + if (reverse_attack_spawns.size() > 0) { + for (itr = reverse_attack_spawns.begin(); itr != reverse_attack_spawns.end(); itr++) + CheckNPCAttacks((NPC*)itr->second, npc); + } + + return attack_spawns.size() == 0; +} + +void ZoneServer::RemoveDeadEnemyList(Spawn *spawn) +{ + int32 faction_id = spawn->GetFactionID(); + vector *spawns; + vector::iterator itr; + + m_npc_faction_list.writelock(__FUNCTION__, __LINE__); + if (npc_faction_list.count(faction_id) > 0) { + spawns = npc_faction_list[faction_id]; + + for (itr = spawns->begin(); itr != spawns->end(); itr++) { + if (*itr == spawn->GetID()) { + spawns->erase(itr); + break; + } + } + } + m_npc_faction_list.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::AddEnemyList(NPC* npc){ + int32 faction_id = npc->GetFactionID(); + vector *hostile_factions; + vector::iterator itr; + + if(faction_id <= 9) + return; + + if(!rule_manager.GetGlobalRule(R_Faction, AllowFactionBasedCombat)->GetBool()) { + LogWrite(FACTION__WARNING, 0, "Faction", "Faction Combat is DISABLED via R_Faction::AllowFactionBasedCombat rule!"); + return; + } + + m_npc_faction_list.readlock(__FUNCTION__, __LINE__); + if (npc_faction_list.count(faction_id) == 0) { + if(faction_id > 10) { + if ((hostile_factions = master_faction_list.GetHostileFactions(faction_id)) != NULL) { + itr = hostile_factions->begin(); + + for (itr = hostile_factions->begin(); itr != hostile_factions->end(); itr++) { + m_enemy_faction_list.writelock(__FUNCTION__, __LINE__); + if (enemy_faction_list.count(faction_id) == 0) + enemy_faction_list[faction_id] = new vector; + enemy_faction_list[faction_id]->push_back(*itr); + m_enemy_faction_list.releasewritelock(__FUNCTION__, __LINE__); + + m_reverse_enemy_faction_list.writelock(__FUNCTION__, __LINE__); + if(reverse_enemy_faction_list.count(*itr) == 0) + reverse_enemy_faction_list[*itr] = new vector; + reverse_enemy_faction_list[*itr]->push_back(faction_id); + m_reverse_enemy_faction_list.releasewritelock(__FUNCTION__, __LINE__); + } + } + } + + /*m_enemy_faction_list.writelock(__FUNCTION__, __LINE__); + if(enemy_faction_list.count(1) == 0) + enemy_faction_list[1] = new vector; + enemy_faction_list[1]->push_back(faction_id); + m_enemy_faction_list.releasewritelock(__FUNCTION__, __LINE__);*/ + } + m_npc_faction_list.releasereadlock(__FUNCTION__, __LINE__); + + m_npc_faction_list.writelock(__FUNCTION__, __LINE__); + if(npc_faction_list.count(faction_id) == 0) + npc_faction_list[faction_id] = new vector; + npc_faction_list[faction_id]->push_back(npc->GetID()); + m_npc_faction_list.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::CheckSpawnRange(Client* client, Spawn* spawn, bool initial_login){ + if(client && spawn && (initial_login || client->IsConnected())) { + if(spawn != client->GetPlayer()) { + if(spawn_range_map.count(client) == 0) + spawn_range_map.Put(client, new MutexMap()); + float curDist = spawn->GetDistance(client->GetPlayer()); + + int32 ghost_spawn_id = client->GetPlayerPOVGhostSpawnID(); + Spawn* otherSpawn = GetSpawnByID(ghost_spawn_id); + + if (!client->GetPlayer()->WasSentSpawn(spawn->GetID()) + && (!otherSpawn || otherSpawn->GetDistance(spawn) > SEND_SPAWN_DISTANCE) && curDist > SEND_SPAWN_DISTANCE) + { + return; + } + + spawn_range_map.Get(client)->Put(spawn->GetID(), curDist); + + if(!initial_login && client && spawn->IsNPC() && (!spawn->IsPrivateSpawn() || spawn->AllowedAccess(client->GetPlayer())) + && curDist <= ((NPC*)spawn)->GetAggroRadius() && !client->GetPlayer()->GetInvulnerable()) + CheckNPCAttacks((NPC*)spawn, client->GetPlayer(), client); + } + + if(!initial_login) + CheckPlayerProximity(spawn, client); + } +} + +void ZoneServer::CheckSpawnRange(Spawn* spawn){ + vector::iterator client_itr; + Client* client = 0; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(client && client->IsReadyForSpawns()) + CheckSpawnRange(client, spawn); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +bool ZoneServer::PrepareSpawnID(Player* player, Spawn* spawn){ + return player->SetSpawnMap(spawn); +} + +void ZoneServer::CheckSendSpawnToClient(Client* client, bool initial_login) { + if (!client) { + LogWrite(ZONE__ERROR, 0, "Zone", "CheckSendSpawnToClient called with an invalid client"); + return; + } + + if (!initial_login && !client->GetInitialSpawnsSent() || (!initial_login && !client->IsReadyForSpawns())) + return; + + Spawn* spawn = 0; + map* > closest_spawns; + if (spawn_range_map.count(client) > 0) { + if (initial_login || client->IsConnected()) { + MutexMap::iterator spawn_iter = spawn_range_map.Get(client)->begin(); + while (spawn_iter.Next()) { + spawn = GetSpawnByID(spawn_iter->first, true); + if (spawn && spawn->GetPrivateQuestSpawn()) { + if (!spawn->IsPrivateSpawn()) + spawn->AddAllowAccessSpawn(spawn); + if (spawn->MeetsSpawnAccessRequirements(client->GetPlayer())) { + if (spawn->IsPrivateSpawn() && !spawn->AllowedAccess(client->GetPlayer())) + spawn->AddAllowAccessSpawn(client->GetPlayer()); + } + else if (spawn->AllowedAccess(client->GetPlayer())) + spawn->RemoveSpawnAccess(client->GetPlayer()); + } + if (spawn && spawn != client->GetPlayer() && client->GetPlayer()->ShouldSendSpawn(spawn)) { + if ((!initial_login && spawn_iter->second <= SEND_SPAWN_DISTANCE) || (initial_login && (spawn_iter->second <= (SEND_SPAWN_DISTANCE / 2) || spawn->IsWidget()))) { + if(PrepareSpawnID(client->GetPlayer(), spawn)) { + if (closest_spawns.count(spawn_iter->second) == 0) + closest_spawns[spawn_iter->second] = new vector(); + closest_spawns[spawn_iter->second]->push_back(spawn); + } + } + } + } + } + vector::iterator spawn_iter2; + map* >::iterator itr; + for (itr = closest_spawns.begin(); itr != closest_spawns.end(); ) { + for (spawn_iter2 = itr->second->begin(); spawn_iter2 != itr->second->end(); spawn_iter2++) { + spawn = *spawn_iter2; + + if(!client->IsReloadingZone() || (client->IsReloadingZone() && spawn != client->GetPlayer())) + SendSpawn(spawn, client); + + if (client->ShouldTarget() && client->GetCombineSpawn() == spawn) + client->TargetSpawn(spawn); + } + vector* vect = itr->second; + map* >::iterator tmpitr = itr; + itr++; + closest_spawns.erase(tmpitr); + safe_delete(vect); + } + } + + if (initial_login) + client->SetInitialSpawnsSent(true); +} + +void ZoneServer::CheckSendSpawnToClient(){ + vector::iterator itr; + Client* client = 0; + + MClientList.readlock(__FUNCTION__, __LINE__); + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = clients.begin(); itr != clients.end(); itr++) { + client = *itr; + if(client->IsReadyForSpawns()) + CheckSendSpawnToClient(client); + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::CheckRemoveSpawnFromClient(Spawn* spawn) { + vector::iterator itr; + Client* client = 0; + PacketStruct* packet = 0; + int16 packet_version = 0; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (itr = clients.begin(); itr != clients.end(); itr++) { + client = *itr; + if(client){ + int32 ghost_spawn_id = client->GetPlayerPOVGhostSpawnID(); + Spawn* otherSpawn = GetSpawnByID(ghost_spawn_id); + if(!packet || packet_version != client->GetVersion()){ + safe_delete(packet); + packet_version = client->GetVersion(); + packet = configReader.getStruct("WS_DestroyGhostCmd", packet_version); + } + + if(spawn && spawn != client->GetPlayer() && + client->GetPlayer()->WasSentSpawn(spawn->GetID()) && + !client->GetPlayer()->IsRemovingSpawn(spawn->GetID()) && + client->GetPlayer()->WasSpawnRemoved(spawn) == false && + (ghost_spawn_id == 0 || (ghost_spawn_id != spawn->GetID() && otherSpawn && otherSpawn->GetDistance(spawn) > REMOVE_SPAWN_DISTANCE)) && + (spawn_range_map.Get(client)->Get(spawn->GetID()) > REMOVE_SPAWN_DISTANCE && + !spawn->IsSign() && !spawn->IsObject() && !spawn->IsWidget() && !spawn->IsTransportSpawn())){ + SendRemoveSpawn(client, spawn, packet); + spawn_range_map.Get(client)->erase(spawn->GetID()); + } + + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + safe_delete(packet); +} + +bool ZoneServer::CombatProcess(Spawn* spawn) { + bool ret = true; + + if (spawn && spawn->IsEntity()) + ((Entity*)spawn)->ProcessCombat(); + if (spawn && !spawn->Alive() && !spawn->IsLootDispensed()) { + LootProcess(spawn); + } + + return ret; +} + +void ZoneServer::LootProcess(Spawn* spawn) { + if (spawn->GetLootMethod() == GroupLootMethod::METHOD_ROUND_ROBIN) { + spawn->LockLoot(); + if (spawn->CheckLootTimer() || spawn->IsLootWindowComplete()) { + LogWrite(LOOT__INFO, 0, "Loot", "%s: Dispensing loot, loot window was completed? %s.", spawn->GetName(), spawn->IsLootWindowComplete() ? "YES" : "NO"); + spawn->DisableLootTimer(); + spawn->SetLootDispensed(); + Spawn* looter = nullptr; + if (spawn->GetLootGroupID() < 1 && spawn->GetLootWindowList()->size() > 0) { + std::map::iterator itr; + + for (itr = spawn->GetLootWindowList()->begin(); itr != spawn->GetLootWindowList()->end(); itr++) { + Spawn* entry = GetSpawnByID(itr->first, true); + if (entry->IsPlayer()) { + looter = entry; + break; + } + } + + int32 item_id = 0; + std::vector item_list; + spawn->GetLootItemsList(&item_list); + spawn->UnlockLoot(); + + std::vector::iterator item_itr; + + for (item_itr = item_list.begin(); item_itr != item_list.end(); item_itr++) { + int32 item_id = *item_itr; + Item* tmpItem = master_item_list.GetItem(item_id); + + bool skipItem = spawn->IsItemInLootTier(tmpItem); + + if (skipItem) + continue; + + if (looter) { + if (looter->IsPlayer()) { + + Item* item = spawn->LootItem(item_id); + bool success = false; + success = ((Player*)looter)->GetClient()->HandleLootItem(spawn, item, ((Player*)looter)); + + if (!success) + spawn->AddLootItem(item); + } + else { + Item* item = spawn->LootItem(item_id); + safe_delete(item); + } + } + } + } + else if (spawn->GetLootGroupID() > 0) { + int32 item_id = 0; + std::vector item_list; + spawn->GetLootItemsList(&item_list); + spawn->UnlockLoot(); + spawn->DistributeGroupLoot_RoundRobin(&item_list); + } + + if (!spawn->HasLoot()) { + if (spawn->IsNPC()) + RemoveDeadSpawn(spawn); + } + else { + spawn->LockLoot(); + spawn->SetLootMethod(GroupLootMethod::METHOD_FFA, 0, 0); + spawn->SetLooterSpawnID(0); + spawn->UnlockLoot(); + } + } + else { + spawn->UnlockLoot(); + } + } + else if ((spawn->GetLootMethod() == GroupLootMethod::METHOD_LOTTO || spawn->GetLootMethod() == GroupLootMethod::METHOD_NEED_BEFORE_GREED) && spawn->IsLootTimerRunning()) { + spawn->LockLoot(); + if (spawn->CheckLootTimer() || spawn->IsLootWindowComplete()) { + LogWrite(LOOT__INFO, 0, "Loot", "%s: Dispensing loot, loot window was completed? %s.", spawn->GetName(), spawn->IsLootWindowComplete() ? "YES" : "NO"); + spawn->DisableLootTimer(); + spawn->SetLootDispensed(); + + // identify any clients that still have the loot window open, close it out + CloseSpawnLootWindow(spawn); + + // lotto items while we have loot items in the list + int32 item_id = 0; + std::vector item_list; + spawn->GetLootItemsList(&item_list); + spawn->UnlockLoot(); + + std::vector::iterator item_itr; + + for (item_itr = item_list.begin(); item_itr != item_list.end(); item_itr++) { + int32 item_id = *item_itr; + Item* tmpItem = master_item_list.GetItem(item_id); + + bool skipItem = spawn->IsItemInLootTier(tmpItem); + + if (skipItem) + continue; + + std::map out_entries; + std::map::iterator out_itr; + bool itemNeed = true; + switch (spawn->GetLootMethod()) { + case GroupLootMethod::METHOD_LOTTO: { + spawn->GetSpawnLottoEntries(item_id, &out_entries); + break; + } + case GroupLootMethod::METHOD_NEED_BEFORE_GREED: { + spawn->GetSpawnNeedGreedEntries(item_id, true, &out_entries); + if (out_entries.size() < 1) { + spawn->GetSpawnNeedGreedEntries(item_id, false, &out_entries); + itemNeed = false; + } + break; + } + } + if (out_entries.size() < 1) { + LogWrite(LOOT__INFO, 0, "Loot", "%s: No spawns matched for loot attempt of %s (%u), skip item.", spawn->GetName(), tmpItem ? tmpItem->name.c_str() : "Unknown", item_id); + continue; + } + Spawn* looter = nullptr; + int32 curWinValue = 0; + for (out_itr = out_entries.begin(); out_itr != out_entries.end(); out_itr++) { + Spawn* entry = GetSpawnByID(out_itr->first, true); + if ((out_itr->second > curWinValue) || looter == nullptr) { + curWinValue = out_itr->second; + looter = entry; + } + if (spawn->GetLootMethod() == GroupLootMethod::METHOD_LOTTO) { + world.GetGroupManager()->SendGroupMessage(spawn->GetLootGroupID(), CHANNEL_LOOT_ROLLS, "%s rolled %u on %s.", entry->GetName(), out_itr->second, tmpItem ? tmpItem->name.c_str() : "Unknown"); + } + else { + world.GetGroupManager()->SendGroupMessage(spawn->GetLootGroupID(), CHANNEL_LOOT_ROLLS, "%s rolled %s (%u) on %s.", entry->GetName(), itemNeed ? "NEED" : "GREED", out_itr->second, tmpItem ? tmpItem->name.c_str() : "Unknown"); + } + } + + if (looter) { + if (looter->IsPlayer()) { + Item* item = spawn->LootItem(item_id); + bool success = false; + success = ((Player*)looter)->GetClient()->HandleLootItem(spawn, item, ((Player*)looter)); + + if (!success) + spawn->AddLootItem(item); + } + else { + Item* item = spawn->LootItem(item_id); + safe_delete(item); + } + } + } + + if (!spawn->HasLoot()) { + if (spawn->IsNPC()) + RemoveDeadSpawn(spawn); + } + else { + spawn->LockLoot(); + spawn->SetLootMethod(GroupLootMethod::METHOD_FFA, 0, 0); + spawn->SetLooterSpawnID(0); + spawn->UnlockLoot(); + } + } + else { + spawn->UnlockLoot(); + } + } +} + +void ZoneServer::CloseSpawnLootWindow(Spawn* spawn) { + if (spawn->GetLootWindowList()->size() > 0) { + std::map::iterator itr; + for (itr = spawn->GetLootWindowList()->begin(); itr != spawn->GetLootWindowList()->end(); itr++) { + if (itr->second) + continue; + + itr->second = true; + Spawn* looter = GetSpawnByID(itr->first, true); + if (looter && looter->IsPlayer() && ((Player*)looter)->GetClient()) { + LogWrite(LOOT__DEBUG, 0, "Loot", "%s: Close loot for player %s.", spawn->GetName(), looter->GetName()); + ((Player*)looter)->GetClient()->CloseLoot(spawn->GetID()); + } + } + } +} +void ZoneServer::AddPendingDelete(Spawn* spawn) { + MSpawnDeleteList.writelock(__FUNCTION__, __LINE__); + spawn->SetDeletedSpawn(true); + if (spawn_delete_list.count(spawn) == 0) + spawn_delete_list.insert(make_pair(spawn, Timer::GetCurrentTime2() + spawn_delete_timer)); //give other threads up to 30 seconds to stop using this spawn reference + MSpawnDeleteList.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::DeleteSpawns(bool delete_all) { + MSpawnDeleteList.writelock(__FUNCTION__, __LINE__); + MPendingSpawnRemoval.readlock(__FUNCTION__, __LINE__); + if(spawn_delete_list.size() > 0){ + map::iterator itr; + map::iterator erase_itr; + int32 current_time = Timer::GetCurrentTime2(); + Spawn* spawn = 0; + for (itr = spawn_delete_list.begin(); itr != spawn_delete_list.end(); ) { + if (delete_all || current_time >= itr->second){ + // we haven't removed it from the spawn list yet.. + if(!delete_all && m_pendingSpawnRemove.count(itr->first->GetID())) + continue; + + spawn = itr->first; + if(movementMgr != nullptr) { + movementMgr->RemoveMob((Entity*)spawn); + } + + // delete brain if it has one + if(spawn->IsNPC()) { + NPC* tmpNPC = (NPC*)spawn; + if(tmpNPC->Brain()) + tmpNPC->SetBrain(nullptr); + } + + erase_itr = itr++; + spawn_delete_list.erase(erase_itr); + + MSpawnList.writelock(__FUNCTION__, __LINE__); + lua_interface->SetLuaUserDataStale(spawn); + + std::map::iterator sitr = spawn_list.find(spawn->GetID()); + if(sitr != spawn_list.end()) { + spawn_list.erase(sitr); + } + + if(spawn->IsCollector()) { + std::map::iterator subitr = subspawn_list[SUBSPAWN_TYPES::COLLECTOR].find(spawn->GetID()); + if(subitr != subspawn_list[SUBSPAWN_TYPES::COLLECTOR].end()) { + subspawn_list[SUBSPAWN_TYPES::COLLECTOR].erase(subitr); + } + } + + if(spawn->GetPickupItemID()) { + std::map::iterator subitr = subspawn_list[SUBSPAWN_TYPES::HOUSE_ITEM_SPAWN].find(spawn->GetPickupItemID()); + if(subitr != subspawn_list[SUBSPAWN_TYPES::HOUSE_ITEM_SPAWN].end() && subitr->second == spawn) { + subspawn_list[SUBSPAWN_TYPES::HOUSE_ITEM_SPAWN].erase(subitr); + } + housing_spawn_map.erase(spawn->GetID()); + } + MSpawnList.releasewritelock(__FUNCTION__, __LINE__); + spellProcess->RemoveCaster(spawn); + safe_delete(spawn); + } + else + itr++; + } + } + MPendingSpawnRemoval.releasereadlock(__FUNCTION__, __LINE__); + MSpawnDeleteList.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::AddDamagedSpawn(Spawn* spawn){ + if (spawn) + damaged_spawns.Add(spawn->GetID()); +} + +void ZoneServer::RemoveDamagedSpawn(Spawn* spawn){ + if (spawn) + damaged_spawns.Remove(spawn->GetID()); +} + +bool ZoneServer::Process() +{ + MMasterZoneLock->lock(); //Changing this back to a recursive lock to fix a possible /reload spells crash with multiple zones running - Foof + SetWatchdogTime(Timer::GetCurrentTime2()); +#ifndef NO_CATCH + try + { +#endif + while (zoneID == 0) { //this is loaded by world + SetWatchdogTime(Timer::GetCurrentTime2()); + Sleep(10); + } + + if (LoadingData) { + if (lua_interface) { + string tmpScript("ZoneScripts/"); + tmpScript.append(GetZoneName()); + tmpScript.append(".lua"); + struct stat buffer; + bool fileExists = (stat(tmpScript.c_str(), &buffer) == 0); + if (fileExists) + lua_interface->RunZoneScript(tmpScript.c_str(), "preinit_zone_script", this); + } + + if (reloading) { + LogWrite(COMMAND__DEBUG, 0, "Command", "-Loading Entity Commands..."); + database.LoadEntityCommands(this); + LogWrite(NPC__INFO, 0, "NPC", "-Loading Spirit Shard data..."); + database.LoadSpiritShards(this); + LogWrite(NPC__INFO, 0, "NPC", "-Load Spirit Shard data complete!"); + + LogWrite(NPC__INFO, 0, "NPC", "-Loading NPC data..."); + database.LoadNPCs(this); + LogWrite(NPC__INFO, 0, "NPC", "-Load NPC data complete!"); + + LogWrite(OBJECT__INFO, 0, "Object", "-Loading Object data..."); + database.LoadObjects(this); + LogWrite(OBJECT__INFO, 0, "Object", "-Load Object data complete!"); + + LogWrite(SIGN__INFO, 0, "Sign", "-Loading Sign data..."); + database.LoadSigns(this); + LogWrite(SIGN__INFO, 0, "Sign", "-Load Sign data complete!"); + + LogWrite(WIDGET__INFO, 0, "Widget", "-Loading Widget data..."); + database.LoadWidgets(this); + LogWrite(WIDGET__INFO, 0, "Widget", "-Load Widget data complete!"); + + LogWrite(GROUNDSPAWN__INFO, 0, "GSpawn", "-Loading Groundspawn data..."); + database.LoadGroundSpawns(this); + database.LoadGroundSpawnEntries(this); + LogWrite(GROUNDSPAWN__INFO, 0, "GSpawn", "-Load Groundspawn data complete!"); + + LogWrite(PET__INFO, 0, "Pet", "-Loading Pet data..."); + database.GetPetNames(this); + LogWrite(PET__INFO, 0, "Pet", "-Load Pet data complete!"); + + LogWrite(LOOT__INFO, 0, "Loot", "-Loading Spawn loot data..."); + database.LoadLoot(this); + LogWrite(LOOT__INFO, 0, "Loot", "-Loading Spawn loot data complete!"); + + LogWrite(TRANSPORT__INFO, 0, "Transport", "-Loading Transporters..."); + database.LoadTransporters(this); + LogWrite(TRANSPORT__INFO, 0, "Transport", "-Loading Transporters complete!"); + reloading = false; + world.RemoveReloadingSubSystem("Spawns"); + } + + MSpawnGroupAssociation.writelock(__FUNCTION__, __LINE__); + spawn_group_associations.clear(); + MSpawnGroupAssociation.releasewritelock(__FUNCTION__, __LINE__); + + MSpawnGroupLocations.writelock(__FUNCTION__, __LINE__); + spawn_group_locations.clear(); + MSpawnGroupLocations.releasewritelock(__FUNCTION__, __LINE__); + + MSpawnLocationGroups.writelock(__FUNCTION__, __LINE__); + spawn_location_groups.clear(); + MSpawnLocationGroups.releasewritelock(__FUNCTION__, __LINE__); + + MSpawnGroupChances.writelock(__FUNCTION__, __LINE__); + spawn_group_chances.clear(); + MSpawnGroupChances.releasewritelock(__FUNCTION__, __LINE__); + Map* zonemap = world.GetMap(std::string(GetZoneFile()),0); + while (zonemap != nullptr && zonemap->IsMapLoading()) + { + SetWatchdogTime(Timer::GetCurrentTime2()); + // Client loop + ClientProcess(true); + Sleep(10); + } + + default_zone_map = world.GetMap(std::string(GetZoneFile()),0); + + DeleteTransporters(); + ReloadTransporters(); + + database.LoadSpawns(this); + ProcessSpawnLocations(); + + if (!revive_points) + revive_points = new vector; + else { + while (!revive_points->empty()) { + safe_delete(revive_points->back()); + revive_points->pop_back(); + } + } + database.LoadRevivePoints(revive_points, GetZoneID()); + + RemoveLocationGrids(); + database.LoadLocationGrids(this); + + + MMasterZoneLock->unlock(); + + while(true) { + ProcessPendingSpawns(); + Sleep(20); + MPendingSpawnListAdd.readlock(__FUNCTION__, __LINE__); + int32 count = pending_spawn_list_add.size(); + MPendingSpawnListAdd.releasereadlock(__FUNCTION__, __LINE__); + if(count < 1) + break; + } + + startupDelayTimer.Start(60000); // this is hard coded for 60 seconds after the zone is loaded to allow a client to at least add itself to the list before we start zone shutdown timer + + MMasterZoneLock->lock(); + + LoadingData = false; + + const char* zone_script = world.GetZoneScript(this->GetZoneID()); + if (lua_interface && zone_script) { + RemoveLocationProximities(); + lua_interface->RunZoneScript(zone_script, "init_zone_script", this); + } + + spawn_range.Trigger(); + spawn_check_add.Trigger(); + } + + if (reloading_spellprocess){ + MMasterZoneLock->unlock(); + return !zoneShuttingDown; + } + + if(shutdownTimer.Enabled() && shutdownTimer.Check() && connected_clients.size(true) == 0) { + //if(lifetime_client_count) + zoneShuttingDown = true; + /*else { // allow up to 120 seconds then timeout + LogWrite(ZONE__WARNING, 0, "Zone", "No clients have connected to zone '%s' and the shutdown timer has counted down -- will delay shutdown for 120 seconds.", GetZoneName()); + shutdownTimer.Start(120000, true); + lifetime_client_count = 1; + }*/ + MMasterZoneLock->unlock(); + return false; + } + + // client loop + if(charsheet_changes.Check()) + SendCharSheetChanges(); + + // Client loop + ClientProcess(startupDelayTimer.Enabled()); + if(startupDelayTimer.Check()) + startupDelayTimer.Disable(); + + if(!reloading && spellProcess) + spellProcess->Process(); + if (tradeskillMgr) + tradeskillMgr->Process(); + + // Client loop + if(client_save.Check()) + SaveClients(); + + // Possibility to do a client loop + if(weather_enabled && weatherTimer.Check()) + ProcessWeather(); + + // client related loop, move to main thread? + if(!zoneShuttingDown) + ProcessDrowning(); + + // client than location_proximities loop, move to main thread + if (location_prox_timer.Check() && !zoneShuttingDown) + CheckLocationProximity(); + + // client than location_grid loop, move to main thread + if (location_grid_timer.Check() && !zoneShuttingDown) + CheckLocationGrids(); + + if (sync_game_time_timer.Check() && !zoneShuttingDown) + SendTimeUpdateToAllClients(); + + world.MWorldTime.readlock(__FUNCTION__, __LINE__); + int hour = world.GetWorldTimeStruct()->hour; + int minute = world.GetWorldTimeStruct()->minute; + world.MWorldTime.releasereadlock(__FUNCTION__, __LINE__); + + if (!isDusk && (hour >= 19 || hour < 8)) {//((hour > dusk_hour || hour < dawn_hour) || ((dusk_hour == hour && minute >= dusk_minute) || (hour == dawn_hour && minute < dawn_minute)))) { + isDusk = true; + const char* zone_script = world.GetZoneScript(GetZoneID()); + if (lua_interface && zone_script) + lua_interface->RunZoneScript(zone_script, "dusk", this); + + ProcessSpawnConditional(SPAWN_CONDITIONAL_NIGHT); + } + else if (isDusk && hour >= 8 && hour < 19) {//((hour > dawn_hour && hour < dusk_hour) || ((hour == dawn_hour && minute >= dawn_minute) || (hour == dusk_hour && minute < dusk_minute)))) { + isDusk = false; + const char* zone_script = world.GetZoneScript(GetZoneID()); + if (lua_interface && zone_script) + lua_interface->RunZoneScript(zone_script, "dawn", this); + + ProcessSpawnConditional(SPAWN_CONDITIONAL_DAY); + } + + // damaged spawns loop, spawn related, move to spawn thread? + if(regenTimer.Check()) + RegenUpdate(); + + // respawn_timers loop + if(respawn_timer.Check() && !zoneShuttingDown) + CheckRespawns(); + + // spawn_expire_timers loop + if (spawn_expire_timer.Check() && !zoneShuttingDown) + CheckSpawnExpireTimers(); + + // widget_timers loop + if(widget_timer.Check() && !zoneShuttingDown) + CheckWidgetTimers(); + + // spawn_script_timers loop + if(!reloading && !zoneShuttingDown) + CheckSpawnScriptTimers(); + + // Check to see if a dead spawn needs to be removed + CheckDeadSpawnRemoval(); +#ifndef NO_CATCH + } + catch(...) + { + LogWrite(ZONE__ERROR, 0, "Zone", "Exception while running '%s'", GetZoneName()); + zoneShuttingDown = true; + MMasterZoneLock->unlock(); + return false; + } +#endif + MMasterZoneLock->unlock(); + return (zoneShuttingDown == false); +} + +bool ZoneServer::SpawnProcess(){ + if(depop_zone) { + depop_zone = false; + ProcessDepop(respawns_allowed, repop_zone); + finished_depop = true; + } + + MMasterSpawnLock.writelock(__FUNCTION__, __LINE__); + // If the zone is loading data or shutting down don't do anything + if(!LoadingData && !zoneShuttingDown && !reloading_spellprocess) { + // Set some bool's for timers + bool movement = movement_timer.Check(); + bool spawnRange = spawn_range.Check(); + bool checkRemove = spawn_check_remove.Check(); + bool aggroCheck = aggro_timer.Check(); + vector pending_spawn_list_remove; + + // Check to see if there are any spawn id's that need to be removed from the spawn list, if so remove them all + ProcessSpawnRemovals(); + + map::iterator itr; + if (spawnRange || checkRemove) + { + // Loop through the spawn list + MSpawnList.readlock(__FUNCTION__, __LINE__); + // Loop throught the list to set up spawns to be sent to clients + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + // if zone is shutting down kill the loop + if (zoneShuttingDown) + break; + + Spawn* spawn = itr->second; + if (spawn) { + // Checks the range to all clients in the zone + if (spawnRange) + CheckSpawnRange(spawn); + + // Checks to see if the spawn needs to be removed from a client + if (checkRemove) + CheckRemoveSpawnFromClient(spawn); + } + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + } + + // Broke the spawn loop into 2 so spawns are sent to the client faster, send the spawns to clients now then resume the spawn loop + + // client loop, move to main thread? + // moved this back to the spawn thread as on the main thread during a depop, done on the spawn thread, spawns would start to pop again + // might be an issue with other functions moved from the spawn thread to the main thread? + if(spawn_check_add.Check() && !zoneShuttingDown) + CheckSendSpawnToClient(); + + + // send spawn changes, changed_spawns loop + if (spawn_update.Check() && !zoneShuttingDown) { //check for changed spawns every {Rule:SpawnUpdateTimer} milliseconds (default: 200ms) + SendSpawnChanges(); + } + + if (movement || aggroCheck) + { + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + // Break the loop if the zone is shutting down + if (zoneShuttingDown) + break; + + Spawn* spawn = itr->second; + if (spawn) { + // Process spawn movement + if (movement) { + spawn->ProcessMovement(true); + // update last_movement_update for all spawns (used for time_step) + spawn->last_movement_update = Timer::GetCurrentTime2(); + if (!aggroCheck) + CombatProcess(spawn); + } + + // Makes NPC's KOS to other NPC's or players + if (aggroCheck) + { + ProcessAggroChecks(spawn); + CombatProcess(spawn); + } + } + else { + // unable to get a valid spawn, lets add the id to another list to remove from the spawn list after this loop is finished + pending_spawn_list_remove.push_back(itr->first); + } + + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + } + + // Check to see if there are any spawn id's that need to be removed from the spawn list, if so remove them all + if (pending_spawn_list_remove.size() > 0) { + MSpawnList.writelock(__FUNCTION__, __LINE__); + vector::iterator itr2; + for (itr2 = pending_spawn_list_remove.begin(); itr2 != pending_spawn_list_remove.end(); itr2++) { + spawn_list.erase(*itr2); + subspawn_list[SUBSPAWN_TYPES::COLLECTOR].erase(*itr2); + + std::map::iterator hsmitr = housing_spawn_map.find(*itr2); + if(hsmitr != housing_spawn_map.end()) { + subspawn_list[SUBSPAWN_TYPES::HOUSE_ITEM_SPAWN].erase(hsmitr->second); + housing_spawn_map.erase(hsmitr); + } + } + + pending_spawn_list_remove.clear(); + MSpawnList.releasewritelock(__FUNCTION__, __LINE__); + } + + // Double Check to see if there are any spawn id's that need to be removed from the spawn list, if so remove them before we replace with pending spawns + // and also potentially further down when we delete the Spawn* in DeleteSpawns(false) + ProcessSpawnRemovals(); + + // Check to see if there are spawns waiting to be added to the spawn list, if so add them all + if (pending_spawn_list_add.size() > 0) { + ProcessPendingSpawns(); + } + + MSpawnList.readlock(__FUNCTION__, __LINE__); + if (movementMgr != nullptr) + movementMgr->Process(); + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + + if(queue_updates.Check()) + ProcessQueuedStateCommands(); + // Do other loops for spawns + // tracking, client loop with spawn loop for each client that is tracking, change to a spawn_range_map loop instead of using the main spawn list? + //if (tracking_timer.Check()) + //ProcessTracking(); // probably doesn't work as spawn loop iterator is never set + + + // Delete unused spawns, do this last + if(!zoneShuttingDown) + DeleteSpawns(false); + + // Nothing should come after this + + + //LogWrite(PLAYER__ERROR, 0, "Debug", "Spawn loop time %u", Timer::GetCurrentTime2() - time); + } + + MMasterSpawnLock.releasewritelock(__FUNCTION__, __LINE__); + + return (zoneShuttingDown == false); +} + +void ZoneServer::CheckDeadSpawnRemoval() { + MDeadSpawns.writelock(__FUNCTION__, __LINE__); + if(dead_spawns.size() > 0){ + vector tmp_dead_list; + int32 current_time = Timer::GetCurrentTime2(); + Spawn* spawn = 0; + map::iterator itr = dead_spawns.begin(); + map::iterator itr_delete; + while (itr != dead_spawns.end()) { + spawn = GetSpawnByID(itr->first); + if (spawn) { + if(current_time >= itr->second) + tmp_dead_list.push_back(spawn); + itr++; + } + else { + itr_delete = itr++; + dead_spawns.erase(itr_delete); + } + } + for(int i=tmp_dead_list.size()-1;i>=0;i--){ + spawn = tmp_dead_list[i]; + if (!spawn->IsPlayer()) + { + dead_spawns.erase(spawn->GetID()); + MDeadSpawns.releasewritelock(__FUNCTION__, __LINE__); + RemoveSpawn(spawn, true, true, true, true, true); + MDeadSpawns.writelock(__FUNCTION__, __LINE__); + } + } + } + MDeadSpawns.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::CheckRespawns(){ + vector tmp_respawn_list; + MutexMap::iterator itr = respawn_timers.begin(); + while(itr.Next()){ + if(Timer::GetCurrentTime2() >= itr->second) + tmp_respawn_list.push_back(itr->first); + } + for(int i=tmp_respawn_list.size()-1;i>=0;i--){ + if ( IsInstanceZone() ) + { + if ( database.DeleteInstanceSpawnRemoved(GetInstanceID(),tmp_respawn_list[i]) ) + { + } + else + { + } + } + + ProcessSpawnLocation(tmp_respawn_list[i], true); + respawn_timers.erase(tmp_respawn_list[i]); + } +} + +void ZoneServer::CheckSpawnExpireTimers() { + MutexMap::iterator itr = spawn_expire_timers.begin(); + while (itr.Next()) { + Spawn* spawn = GetSpawnByID(itr->first); + if (spawn) { + if (Timer::GetCurrentTime2() >= itr.second) { + spawn_expire_timers.erase(itr.first); + Despawn(spawn, spawn->GetRespawnTime()); + } + } + else + spawn_expire_timers.erase(itr->first); + } +} + +void ZoneServer::AddSpawnExpireTimer(Spawn* spawn, int32 expire_time, int32 expire_offset) { + if (spawn) { + int32 actual_expire_time = expire_time; + if (expire_offset > 0) { + int32 low = expire_time; + int32 high = expire_time + expire_offset; + if (expire_offset < expire_time) + low = expire_time - expire_offset; + int32 range = (high - low) + 1; + actual_expire_time = (low + (int32)((range * rand()) / (RAND_MAX + 1.0))); + } + actual_expire_time *= 1000; + spawn_expire_timers.Put(spawn->GetID(), Timer::GetCurrentTime2() + actual_expire_time); + } +} + +void ZoneServer::SaveClient(Client* client){ + client->Save(); +} + +void ZoneServer::SaveClients(){ + vector::iterator itr; + Client* client = 0; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (itr = clients.begin(); itr != clients.end(); itr++) { + client = *itr; + if(client->IsConnected() && client->IsReadyForUpdates()){ + SaveClient(client); + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::SendSpawnVisualState(Spawn* spawn, int16 type){ + if(!spawn) + return; + + vector::iterator itr; + spawn->SetTempVisualState(type); + Client* client = 0; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (itr = clients.begin(); itr != clients.end(); itr++) { + client = *itr; + if(client && client->GetPlayer() != spawn) + AddChangedSpawn(spawn); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::SendSpawnChangesByDBID(int32 db_id, Client* client, bool override_changes, bool override_vis_changes){ + Spawn* spawn = GetSpawnByDatabaseID(db_id); + if(spawn && (spawn->changed || override_changes || override_vis_changes)) + SendSpawnChanges(spawn, client, override_changes, override_vis_changes); +} + +void ZoneServer::SendSpawnChanges(Spawn* spawn, Client* client, bool override_changes, bool override_vis_changes){ + if(client && client->IsConnected() && client->IsReadyForUpdates() && client->GetPlayer()->WasSentSpawn(spawn->GetID()) && (spawn->IsTransportSpawn() || client->GetPlayer()->GetDistance(spawn) < SEND_SPAWN_DISTANCE)){ + EQ2Packet* outapp = spawn->spawn_update_packet(client->GetPlayer(), client->GetVersion(), override_changes, override_vis_changes); + if(outapp) + client->QueuePacket(outapp); + } +} + +void ZoneServer::SendSpawnChanges(Spawn* spawn){ + MClientList.readlock(__FUNCTION__, __LINE__); + MSpawnList.readlock(); + if(spawn && spawn->changed){ + if(!spawn->IsPlayer() || (spawn->IsPlayer() && (spawn->info_changed || spawn->vis_changed))){ + vector::iterator itr; + Client* client = 0; + + // MClientList locked at a higher level + for (itr = clients.begin(); itr != clients.end(); itr++) { + client = *itr; + SendSpawnChanges(spawn, client); + } + } + spawn->changed = false; + spawn->info_changed = false; + if(spawn->IsPlayer() == false) + spawn->position_changed = false; + spawn->vis_changed = false; + } + MSpawnList.releasereadlock(); + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +Spawn* ZoneServer::FindSpawn(Player* searcher, const char* name){ + if(!searcher || !name) + return 0; + + Spawn* spawn = 0; + vector find_spawn_list; + vector::iterator fspawn_iter; + int8 name_size = strlen(name); + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + spawn = itr->second; + if(spawn && !strncasecmp(spawn->GetName(), name, name_size)) + find_spawn_list.push_back(spawn); + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + Spawn* closest = 0; + float distance = 0; + float test_distance = 0; + for(fspawn_iter=find_spawn_list.begin(); fspawn_iter!=find_spawn_list.end(); fspawn_iter++){ + spawn = *fspawn_iter; + if(spawn && ((((test_distance = searcher->GetDistance(spawn)) < distance)) || !closest)){ + distance = test_distance; + closest = spawn; + } + } + return closest; +} + +void ZoneServer::AddChangedSpawn(Spawn* spawn) { + if (!spawn || (spawn->IsPlayer() && !spawn->info_changed && !spawn->vis_changed) || (spawn->IsPlayer() && ((Player*)spawn)->GetClient() && ((Player*)spawn)->GetClient()->IsReadyForUpdates() == false)) + return; + + MChangedSpawns.lock_shared(); + ChangedSpawnMapType::iterator it = changed_spawns.find(spawn->GetID()); + if (it != changed_spawns.end()) { + it->second = true; + MChangedSpawns.unlock_shared(); + } + else { + MChangedSpawns.unlock_shared(); + MChangedSpawns.lock(); + changed_spawns.insert(make_pair(spawn->GetID(),true)); + MChangedSpawns.unlock(); + } +} + +void ZoneServer::RemoveChangedSpawn(Spawn* spawn){ + if(!spawn) + return; + + MChangedSpawns.lock(); + ChangedSpawnMapType::iterator it = changed_spawns.find(spawn->GetID()); + if (it != changed_spawns.end()) { + it->second = false; + } + MChangedSpawns.unlock(); +} + +void ZoneServer::AddDrowningVictim(Player* player){ + Client* client = ((Player*)player)->GetClient(); + if(client && drowning_victims.count(client) == 0) + drowning_victims.Put(client, Timer::GetCurrentTime2()); +} + +void ZoneServer::RemoveDrowningVictim(Player* player){ + Client* client = ((Player*)player)->GetClient(); + if(client) + drowning_victims.erase(client); +} + +Client* ZoneServer::GetDrowningVictim(Player* player){ + Client* client = ((Player*)player)->GetClient(); + if(client && drowning_victims.count(client) > 0) + return(client); + return 0; +} + +void ZoneServer::ProcessDrowning(){ + vector dead_list; + if(drowning_victims.size(true) > 0){ + sint32 damage = 0; + int32 current_time = Timer::GetCurrentTime2(); + MutexMap::iterator itr = drowning_victims.begin(); + while(itr.Next()){ + if(current_time >= itr->second) { + Client* client = itr->first; + Player* player = client->GetPlayer(); + drowning_victims.Get(client) = Timer::GetCurrentTime2() + 2000; + damage = player->GetTotalHP()/20 + player->GetInfoStruct()->get_hp_regen(); + player->TakeDamage(damage); + if(!player->Alive()) + dead_list.push_back(client); + player->SetCharSheetChanged(true); + SendCharSheetChanges(client); + SendDamagePacket(0, player, DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE, DAMAGE_PACKET_RESULT_SUCCESSFUL, DAMAGE_PACKET_DAMAGE_TYPE_DROWN, damage, 0); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You are drowning!"); + } + } + } + if(dead_list.size() > 0){ + vector::iterator itr; + for(itr = dead_list.begin(); itr != dead_list.end(); itr++){ + RemoveDrowningVictim((*itr)->GetPlayer()); + KillSpawn(false, (*itr)->GetPlayer(), nullptr, true, 0, 0, 10); // kill blow type 10 means death by WATER! (glug glug!) + } + } +} + +void ZoneServer::SendSpawnChanges(){ + std::shared_lock lock(MChangedSpawns); + if (changed_spawns.size() < 1) + return; + + set spawns_to_send; + Spawn* spawn = 0; + + int count = 0; + + MSpawnList.readlock(__FUNCTION__, __LINE__); + + for( ChangedSpawnMapType::iterator it = changed_spawns.begin(); it != changed_spawns.end(); ++it ) { + if(!it->second) + continue; + + spawn = GetSpawnByID(it->first); + if(spawn){ + spawns_to_send.insert(spawn); + count++; + } + } + + vector::iterator client_itr; + Client* client = 0; + + MClientList.readlock(__FUNCTION__, __LINE__); + if(clients.size()) + { + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(client) + client->SendSpawnChanges(spawns_to_send); + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + + for (const auto& spawn : spawns_to_send) { + spawn->changed = false; + spawn->position_changed = false; + spawn->vis_changed = false; + spawn->info_changed = false; + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::SendPlayerPositionChanges(Player* player){ + if(player){ + player->position_changed = false; + Client* client = 0; + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(player != client->GetPlayer() && client->GetPlayer()->WasSentSpawn(player->GetID())){ + if(client->GetVersion() > 373) { + EQ2Packet* outapp = player->player_position_update_packet(client->GetPlayer(), client->GetVersion(), true); + if(outapp) + client->QueuePacket(outapp); + } + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + } +} + +void ZoneServer::SendCharSheetChanges(){ + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) + SendCharSheetChanges(*client_itr); + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::SendCharSheetChanges(Client* client){ + if(client && client->IsConnected() && client->GetPlayer()->GetCharSheetChanged()){ + client->GetPlayer()->SetCharSheetChanged(false); + ClientPacketFunctions::SendCharacterSheet(client); + } +} + +int32 ZoneServer::CalculateSpawnGroup(SpawnLocation* spawnlocation, bool respawn) +{ + int32 group = 0; + list* groups_at_location = GetSpawnGroupsByLocation(spawnlocation->placement_id); + + LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__); + + if(groups_at_location){ + list::iterator group_location_itr; + float chance = 0; + float total_chance = 0; + map tmp_chances; + set* associated_groups = 0; + for (group_location_itr = groups_at_location->begin(); group_location_itr != groups_at_location->end(); group_location_itr++) { + if(tmp_chances.count(*group_location_itr) > 0) + continue; + associated_groups = GetAssociatedGroups(*group_location_itr); + if(associated_groups){ + set::iterator group_itr; + for (group_itr = associated_groups->begin(); group_itr != associated_groups->end(); group_itr++) { + chance = GetSpawnGroupChance(*group_itr); + if(chance > 0){ + total_chance += chance; + tmp_chances[*group_itr] = chance; + } + else + tmp_chances[*group_itr] = 0; + } + } + else{ //single group, no associations + chance = GetSpawnGroupChance(*group_location_itr); + total_chance += chance; + tmp_chances[*group_location_itr] = chance; + } + } + if(tmp_chances.size() > 1){ + //set the default for any chances not set + map::iterator itr2; + for(itr2 = tmp_chances.begin(); itr2 != tmp_chances.end(); itr2++){ + if(itr2->second == 0){ + total_chance += 100/tmp_chances.size(); + tmp_chances[itr2->first] = (float)(100 / tmp_chances.size()); + } + } + } + if(tmp_chances.size() > 1){ + float roll = (float)(rand()%((int32)total_chance)); + map::iterator itr3; + for (itr3 = tmp_chances.begin(); itr3 != tmp_chances.end(); itr3++){ + if(itr3->second >= roll){ + group = itr3->first; + break; + } + else + roll -= itr3->second; + } + } + else if(tmp_chances.size() == 1) + group = tmp_chances.begin()->first; + } + if(group > 0){ + map* locations = GetSpawnLocationsByGroup(group); + if(locations){ + map::iterator itr; + Spawn* spawn = 0; + Spawn* leader = 0; + + MSpawnLocationList.readlock(__FUNCTION__, __LINE__); + for (itr = locations->begin(); itr != locations->end(); itr++) { + if(spawn_location_list.count(itr->second) > 0){ + spawn = ProcessSpawnLocation(spawn_location_list[itr->second], respawn); + if(!leader && spawn) + leader = spawn; + if(leader) + leader->AddSpawnToGroup(spawn); + if(spawn){ + //if(spawn_group_map.count(group) == 0) + // spawn_group_map.Put(group, new MutexList()); + MutexList* groupList = &spawn_group_map.Get(group); + groupList->Add(spawn->GetID()); + spawn->SetSpawnGroupID(group); + } + } + } + MSpawnLocationList.releasereadlock(__FUNCTION__, __LINE__); + } + } + + LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit %s", __FUNCTION__); + + return group; +} + +void ZoneServer::ProcessSpawnLocation(int32 location_id, bool respawn) +{ + LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__); + + MSpawnLocationList.readlock(__FUNCTION__, __LINE__); + + if(spawn_location_list.count(location_id) > 0) + { + if(respawn) //see if there are any spawns still in game associated with this spawn's group, if so, dont spawn this + { + list* groups = GetSpawnGroupsByLocation(spawn_location_list[location_id]->placement_id); + + if(groups) + { + set* associated_groups = 0; + bool should_spawn = true; + list::iterator itr; + for (itr = groups->begin(); itr != groups->end(); itr++) { + associated_groups = GetAssociatedGroups(*itr); + + if(associated_groups) + { + set::iterator assoc_itr; + for (assoc_itr = associated_groups->begin(); assoc_itr != associated_groups->end(); assoc_itr++) { + if(spawn_group_map.count(*assoc_itr) > 0 && spawn_group_map.Get(*assoc_itr).size() > 0) + should_spawn = false; + } + } + } + + if(should_spawn) + CalculateSpawnGroup(spawn_location_list[location_id]); + + LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit %s", __FUNCTION__); + // need to unlock the list before we exit the function + MSpawnLocationList.releasereadlock(__FUNCTION__, __LINE__); + return; + } + } + + LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit %s", __FUNCTION__); + + ProcessSpawnLocation(spawn_location_list[location_id], respawn); + } + MSpawnLocationList.releasereadlock(__FUNCTION__, __LINE__); +} + +Spawn* ZoneServer::ProcessSpawnLocation(SpawnLocation* spawnlocation, bool respawn) +{ + LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__); + + if(!spawnlocation) + return 0; + + Spawn* spawn = 0; + float rand_number = MakeRandomFloat(0, spawnlocation->total_percentage); + + for(int32 i=0;ientities.size();i++) + { + if(spawnlocation->entities[i]->spawn_percentage == 0) + continue; + + if (spawnlocation->conditional > 0) { + if ((spawnlocation->conditional & SPAWN_CONDITIONAL_DAY) == SPAWN_CONDITIONAL_DAY && isDusk) + continue; + + if ((spawnlocation->conditional & SPAWN_CONDITIONAL_NIGHT) == SPAWN_CONDITIONAL_NIGHT && !isDusk) + continue; + + if ((spawnlocation->conditional & SPAWN_CONDITIONAL_DAY) == SPAWN_CONDITIONAL_NOT_RAINING && rain >= 0.75f) + continue; + + if ((spawnlocation->conditional & SPAWN_CONDITIONAL_DAY) == SPAWN_CONDITIONAL_RAINING && rain < 0.75f) + continue; + } + + if (spawnlocation->entities[i]->spawn_percentage >= rand_number) { + if (spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_NPC) + spawn = AddNPCSpawn(spawnlocation, spawnlocation->entities[i]); + else if (spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_GROUNDSPAWN) + spawn = AddGroundSpawn(spawnlocation, spawnlocation->entities[i]); + else if (spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_OBJECT) + spawn = AddObjectSpawn(spawnlocation, spawnlocation->entities[i]); + else if (spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_WIDGET) + spawn = AddWidgetSpawn(spawnlocation, spawnlocation->entities[i]); + else if (spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_SIGN) + spawn = AddSignSpawn(spawnlocation, spawnlocation->entities[i]); + + if (GetInstanceType() == PERSONAL_HOUSE_INSTANCE) + database.GetHouseSpawnInstanceData(this, spawn); + + if(spawn && spawn->IsOmittedByDBFlag()) + { + LogWrite(SPAWN__WARNING, 0, "Spawn", "Spawn (%u) in spawn location id %u was skipped due to a missing expansion / holiday flag being met (ZoneServer::ProcessSpawnLocation)", spawnlocation->entities[i]->spawn_id, spawnlocation->entities[i]->spawn_location_id); + safe_delete(spawn); + spawn = 0; + continue; + } + else if (!spawn) + { + LogWrite(ZONE__ERROR, 0, "Zone", "Error adding spawn by spawn location to zone %s with location id %u, spawn id %u, spawn type %u.", GetZoneName(), spawnlocation->entities[i]->spawn_location_id, spawnlocation->entities[i]->spawn_id, spawnlocation->entities[i]->spawn_type); + continue; + } + + if (spawn) + { + if(respawn) + CallSpawnScript(spawn, SPAWN_SCRIPT_RESPAWN); + else + CallSpawnScript(spawn, SPAWN_SCRIPT_SPAWN); + } + break; + } + else + rand_number -= spawnlocation->entities[i]->spawn_percentage; + } + + LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit %s", __FUNCTION__); + + return spawn; +} + + +Spawn* ZoneServer::ProcessInstanceSpawnLocation(SpawnLocation* spawnlocation, map* instNPCs, map* instGroundSpawns, map* instObjSpawns, map* instWidgetSpawns, map* instSignSpawns, bool respawn) +{ + if(!spawnlocation) + return 0; + + LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__); + + Spawn* spawn = 0; + float rand_number = MakeRandomFloat(0, spawnlocation->total_percentage); + + for(int32 i=0;ientities.size();i++) + { + if(spawnlocation->entities[i]->spawn_percentage == 0) + continue; + + int32 spawnTime = 0; + + if(spawnlocation->entities[i]->spawn_percentage >= rand_number) + { + if(spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_NPC && + (spawnTime = database.CheckSpawnRemoveInfo(instNPCs,spawnlocation->entities[i]->spawn_location_id)) > 0) + spawn = AddNPCSpawn(spawnlocation, spawnlocation->entities[i]); + else if(spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_GROUNDSPAWN && + (spawnTime = database.CheckSpawnRemoveInfo(instGroundSpawns,spawnlocation->entities[i]->spawn_location_id)) > 0) + spawn = AddGroundSpawn(spawnlocation, spawnlocation->entities[i]); + else if(spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_OBJECT && + (spawnTime = database.CheckSpawnRemoveInfo(instObjSpawns,spawnlocation->entities[i]->spawn_location_id)) > 0) + spawn = AddObjectSpawn(spawnlocation, spawnlocation->entities[i]); + else if(spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_WIDGET && + (spawnTime = database.CheckSpawnRemoveInfo(instWidgetSpawns,spawnlocation->entities[i]->spawn_location_id)) > 0) + spawn = AddWidgetSpawn(spawnlocation, spawnlocation->entities[i]); + else if(spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_SIGN && + (spawnTime = database.CheckSpawnRemoveInfo(instSignSpawns,spawnlocation->entities[i]->spawn_location_id)) > 0) + spawn = AddSignSpawn(spawnlocation, spawnlocation->entities[i]); + + if(spawn && spawn->IsOmittedByDBFlag()) + { + LogWrite(SPAWN__WARNING, 0, "Spawn", "Spawn (%u) in spawn location id %u was skipped due to a missing expansion / holiday flag being met (ZoneServer::ProcessInstanceSpawnLocation)", spawnlocation->entities[i]->spawn_id, spawnlocation->entities[i]->spawn_location_id); + safe_delete(spawn); + spawn = 0; + continue; + } + + if (GetInstanceType() == PERSONAL_HOUSE_INSTANCE) + database.GetHouseSpawnInstanceData(this, spawn); + + const char* script = 0; + + for(int x=0;x<3;x++) + { + switch(x) + { + case 0: + script = world.GetSpawnEntryScript(spawnlocation->entities[i]->spawn_entry_id); + break; + case 1: + script = world.GetSpawnLocationScript(spawnlocation->entities[i]->spawn_location_id); + break; + case 2: + script = world.GetSpawnScript(spawnlocation->entities[i]->spawn_id); + break; + } + + if(spawn && script && lua_interface->GetSpawnScript(script) != 0) + { + spawn->SetSpawnScript(string(script)); + break; + } + } + + if(spawn) + { + if (respawn) + CallSpawnScript(spawn, SPAWN_SCRIPT_RESPAWN); + else + CallSpawnScript(spawn, SPAWN_SCRIPT_SPAWN); + + if ( spawnTime > 1 ) + { + spawn->SetRespawnTime(spawnTime); + } + } + break; + } + else + rand_number -= spawnlocation->entities[i]->spawn_percentage; + } + + LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit %s", __FUNCTION__); + + return spawn; +} + +void ZoneServer::ProcessSpawnLocations() +{ + LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__); + + map* instNPCs = NULL; + map* instGroundSpawns = NULL; + map* instObjSpawns = NULL; + map* instWidgetSpawns = NULL; + map* instSignSpawns = NULL; + + if ( this->IsInstanceZone() ) + { + LogWrite(SPAWN__DEBUG, 0, "Spawn", "Processing Instance Removed Spawns..."); + instNPCs = database.GetInstanceRemovedSpawns(this->GetInstanceID() , SPAWN_ENTRY_TYPE_NPC ); + instGroundSpawns = database.GetInstanceRemovedSpawns(this->GetInstanceID() , SPAWN_ENTRY_TYPE_GROUNDSPAWN ); + instObjSpawns = database.GetInstanceRemovedSpawns(this->GetInstanceID() , SPAWN_ENTRY_TYPE_OBJECT ); + instWidgetSpawns = database.GetInstanceRemovedSpawns(this->GetInstanceID() , SPAWN_ENTRY_TYPE_WIDGET ); + instSignSpawns = database.GetInstanceRemovedSpawns(this->GetInstanceID() , SPAWN_ENTRY_TYPE_SIGN ); + } + + map processed_spawn_locations; + map::iterator itr; + MSpawnLocationList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_location_list.begin(); itr != spawn_location_list.end(); itr++) { + LogWrite(SPAWN__TRACE, 0, "Spawn", "while spawn_location_list itr (#%u)", spawn_location_list.size()); + + if(itr->second && processed_spawn_locations.count(itr->second->placement_id) > 0) //if we processed one spawn in a spawn group, we processed them all for that group + continue; + + if(itr->second && spawn_location_groups.count(itr->second->placement_id) > 0) + { + int32 group_id = CalculateSpawnGroup(itr->second); + + if(group_id) + { + LogWrite(SPAWN__TRACE, 0, "Spawn", "is group_id"); + set* associated_groups = GetAssociatedGroups(group_id); + + if(associated_groups) + { + LogWrite(SPAWN__TRACE, 0, "Spawn", "is associated_groups"); + vector* associated_locations = GetAssociatedLocations(associated_groups); + + if(associated_locations) + { + LogWrite(SPAWN__TRACE, 0, "Spawn", "is associated_locations"); + for(int32 i=0;isize();i++) + { + LogWrite(SPAWN__DEBUG, 5, "Spawn", "Loading processed_spawn_locations..."); + processed_spawn_locations[associated_locations->at(i)] = true; + } + + safe_delete(associated_locations); + } + } + } + } + else + { + if ( this->IsInstanceZone() ) + { + //LogWrite(SPAWN__DEBUG, 5, "Spawn", "ProcessInstanceSpawnLocation (%u)...", itr->second->placement_id); + ProcessInstanceSpawnLocation(itr->second,instNPCs,instGroundSpawns,instObjSpawns,instWidgetSpawns,instSignSpawns); + } + else + { + //LogWrite(SPAWN__DEBUG, 5, "Spawn", "ProcessSpawnLocation (%u)...", itr->second->placement_id); + ProcessSpawnLocation(itr->second); + } + } + } + MSpawnLocationList.releasereadlock(__FUNCTION__, __LINE__); + + safe_delete(instNPCs); + safe_delete(instGroundSpawns); + safe_delete(instObjSpawns); + safe_delete(instWidgetSpawns); + safe_delete(instSignSpawns); + + LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit %s", __FUNCTION__); +} + +void ZoneServer::AddLoot(NPC* npc, Spawn* killer, GroupLootMethod loot_method, int8 item_rarity, int32 group_id){ + // this function is ran twice, first on spawn of mob, then at death of mob (gray mob check and no_drop_quest_completed_id check) + + // first we see if the skipping of gray mobs loot is enabled, then we move all non body drops + if(killer) + { + npc->SetLootMethod(loot_method, item_rarity, group_id); + int8 skip_loot_gray_mob_flag = rule_manager.GetGlobalRule(R_Loot, SkipLootGrayMob)->GetInt8(); + if(skip_loot_gray_mob_flag) { + Player* player = 0; + if(killer->IsPlayer()) + player = (Player*)killer; + else if(killer->IsPet()) { + Spawn* owner = ((Entity*)killer)->GetOwner(); + if(owner->IsPlayer()) + player = (Player*)owner; + } + if(player) { + int8 difficulty = player->GetArrowColor(npc->GetLevel()); + if(difficulty == ARROW_COLOR_GRAY) { + npc->ClearNonBodyLoot(); + } + } + } + } + + // check for starting loot of Spawn and death of Spawn loot (no_drop_quest_completed_id) + vector loot_tables = GetSpawnLootList(npc->GetDatabaseID(), GetZoneID(), npc->GetLevel(), race_types_list.GetRaceType(npc->GetModelType()), npc); + if(loot_tables.size() > 0){ + vector* loot_drops = 0; + vector::iterator loot_drop_itr; + LootTable* table = 0; + vector::iterator loot_list_itr; + float chancecoin = 0; + float chancetable = 0; + float chancedrop = 0; + float chancetally = 0; + float droptally = 0; + // the following loop,loops through each table + for(loot_list_itr = loot_tables.begin(); loot_list_itr != loot_tables.end(); loot_list_itr++){ + table = GetLootTable(*loot_list_itr); + // if killer is assigned this is on-death, we already assigned coin + if(!killer && table && table->maxcoin > 0){ + chancecoin = rand()%100; + if(table->coin_probability >= chancecoin){ + if(table->maxcoin > table->mincoin) + npc->AddLootCoins(table->mincoin + rand()%(table->maxcoin - table->mincoin)); + } + } + int numberchances = 1; + + //if (table->lootdrop_probability == 100){ } + //else + //chancetally += table->lootdrop_probability; + int maxchance = 0; + if (table) { + maxchance = table->maxlootitems; + for (numberchances; numberchances <= maxchance; numberchances++) { + chancetable = static_cast (rand()) / (static_cast (RAND_MAX / 100)); + //LogWrite(PLAYER__DEBUG, 0, "Player", "Table Chance: '%f'", chancetable); + float droppercenttotal = 0; + //-------------------------------------------------------------------------------------------------------------------------------------------------------------- + if (table->lootdrop_probability == 100 || table->lootdrop_probability >= chancetable) { + + //LogWrite(PLAYER__DEBUG, 0, "Player", "Probability:%f Table Chance: '%f'", table->lootdrop_probability, chancetable); + loot_drops = GetLootDrops(*loot_list_itr); + if (loot_drops && loot_drops->size() > 0) { + LootDrop* drop = 0; + int16 count = 0; + + std::shuffle(loot_drops->begin(), loot_drops->end(), std::default_random_engine(Timer::GetCurrentTime2())); + + int16 IC = 0; + for (loot_drop_itr = loot_drops->begin(); loot_drop_itr != loot_drops->end(); loot_drop_itr++) { + drop = *loot_drop_itr; + droppercenttotal += drop->probability; + } + + + int droplistsize = loot_drops->size(); + float chancedroptally = 0; + bool breakIterMaxLoot = false; + chancedrop = static_cast (rand()) / (static_cast (RAND_MAX / 100)); + for (loot_drop_itr = loot_drops->begin(); loot_drop_itr != loot_drops->end(); loot_drop_itr++) { + drop = *loot_drop_itr; + + // if no killer is provided, then we are instantiating the spawn loot, quest related loot should be added on death of the spawn to check against spawn/group members + if(drop->no_drop_quest_completed_id && killer == nullptr) + continue; + else if(!drop->no_drop_quest_completed_id && killer) // skip since this doesn't have a quest id attached and we are doing after-math of death loot additions + continue; + else if(killer && drop->no_drop_quest_completed_id) // check if the player already completed quest related to item + { + Player* player = nullptr; + if(killer->IsPlayer()) + { + player = (Player*)killer; + // player has already completed the quest + if(player->HasQuestBeenCompleted(drop->no_drop_quest_completed_id) && !player->GetGroupMemberInfo()) + { + LogWrite(PLAYER__DEBUG, 0, "Player", "%s: Player has completed quest %u, skipping loot item %u", npc->GetName(), drop->no_drop_quest_completed_id, drop->item_id); + continue; + } + else if(player->GetGroupMemberInfo() && world.GetGroupManager()->HasGroupCompletedQuest(player->GetGroupMemberInfo()->group_id, drop->no_drop_quest_completed_id)) + { + LogWrite(PLAYER__DEBUG, 0, "Player", "%s: Group %u has completed quest %u, skipping loot item %u", npc->GetName(), player->GetGroupMemberInfo()->group_id, drop->no_drop_quest_completed_id, drop->item_id); + continue; + } + } + else + { + LogWrite(PLAYER__DEBUG, 0, "Player", "%s: Killer is not a player, skipping loot item %u", npc->GetName(), drop->item_id); + continue; + } + } + + if (npc->HasLootItemID(drop->item_id)) + continue; + + if (droppercenttotal >= 100) + droppercenttotal = 100; + chancedroptally += 100 / droppercenttotal * drop->probability; + //chancedrop = static_cast (rand()) / (static_cast (RAND_MAX / 100)); + //LogWrite(PLAYER__DEBUG, 0, "Player", "Loot drop: '%i' Chance: %f Prob tally: %f min: %f", drop, chancedrop, chancedroptally, chancedroptally - drop->probability); + if ((chancedroptally == 100) || ((chancedroptally >= chancedrop) && (chancedroptally - (100 / droppercenttotal * drop->probability)) <= chancedrop)) { + + //LogWrite(PLAYER__DEBUG, 0, "Player", "Loot drop: '%i' Chance: %f Prob: %f We have a loot drop winner", drop, chancedrop, chancedroptally); + count++; + npc->AddLootItem(drop->item_id, drop->item_charges); + //LogWrite(PLAYER__DEBUG, 0, "Player", "loot Count: '%i'",count); + //LogWrite(MISC__TODO, 1, "TODO", "Auto-Equip new looted items\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__); + //if(drop->equip_item) + + } + // so many items on this table break out and cap it! + if (table->maxlootitems > 0 && count >= table->maxlootitems) { + breakIterMaxLoot = true; + break; + } + } + // hit our max item drop for this table already, break out of numberchances + if(breakIterMaxLoot) { + break; + } + } + } + } + } + } + } +} + +void ZoneServer::DeterminePosition(SpawnLocation* spawnlocation, Spawn* spawn){ + if(!spawn || !spawnlocation) + return; + + int offset = 0; + if(spawnlocation->x_offset > 0){ + //since rand() deals with integers only, we are going to divide by 1000 later so that we can use fractions of integers + offset = (int)((spawnlocation->x_offset*1000)+1); + spawn->SetX(spawnlocation->x + ((float)(rand()%offset - rand()%offset))/1000); + } + else + spawn->SetX(spawnlocation->x); + if(spawnlocation->y_offset > 0){ + //since rand() deals with integers only, we are going to divide by 1000 later so that we can use fractions of integers + offset = (int)((spawnlocation->y_offset*1000)+1); + spawn->SetY(spawnlocation->y + ((float)(rand()%offset - rand()%offset))/1000); + } + else + spawn->SetY(spawnlocation->y, true, true); + if(spawnlocation->z_offset > 0){ + //since rand() deals with integers only, we are going to divide by 1000 later so that we can use fractions of integers + offset = (int)((spawnlocation->z_offset*1000)+1); + spawn->SetZ(spawnlocation->z + ((float)(rand()%offset - rand()%offset))/1000); + } + else + spawn->SetZ(spawnlocation->z); + spawn->SetHeading(spawnlocation->heading); + spawn->SetPitch(spawnlocation->pitch); + spawn->SetRoll(spawnlocation->roll); + spawn->SetSpawnOrigX(spawn->GetX()); + spawn->SetSpawnOrigY(spawn->GetY()); + spawn->SetSpawnOrigZ(spawn->GetZ()); + spawn->SetSpawnOrigHeading(spawn->GetHeading()); + spawn->SetSpawnOrigPitch(spawnlocation->pitch); + spawn->SetSpawnOrigRoll(spawnlocation->roll); + spawn->SetLocation(spawnlocation->grid_id); + spawn->SetSpawnLocationPlacementID(spawnlocation->placement_id); +} + +NPC* ZoneServer::AddNPCSpawn(SpawnLocation* spawnlocation, SpawnEntry* spawnentry){ + LogWrite(SPAWN__TRACE, 1, "Spawn", "Enter %s", __FUNCTION__); + NPC* npc = GetNewNPC(spawnentry->spawn_id); + if(npc && !npc->IsOmittedByDBFlag()){ + InfoStruct* info = npc->GetInfoStruct(); + DeterminePosition(spawnlocation, npc); + npc->SetDatabaseID(spawnentry->spawn_id); + npc->SetSpawnLocationID(spawnentry->spawn_location_id); + npc->SetSpawnEntryID(spawnentry->spawn_entry_id); + npc->SetRespawnTime(spawnentry->respawn); + npc->SetExpireTime(spawnentry->expire_time); + + //devn00b add overrides for some spawns + if(spawnentry->hp_override > 0){ + npc->SetHP(spawnentry->hp_override); + } + if(spawnentry->lvl_override > 0){ + npc->SetLevel(spawnentry->lvl_override); + } + if(spawnentry->mp_override > 0){ + npc->SetPower(spawnentry->mp_override); + } + if(spawnentry->str_override > 0){ + info->set_str_base(spawnentry->str_override); + info->set_str(spawnentry->str_override); + } + if(spawnentry->sta_override > 0){ + info->set_sta_base(spawnentry->sta_override); + info->set_sta(spawnentry->sta_override); + } + if(spawnentry->wis_override > 0){ + info->set_wis_base(spawnentry->wis_override); + info->set_wis(spawnentry->wis_override); + } + if(spawnentry->int_override > 0){ + info->set_intel_base(spawnentry->int_override); + info->set_intel(spawnentry->int_override); + } + if(spawnentry->agi_override > 0){ + info->set_agi_base(spawnentry->agi_override); + info->set_agi(spawnentry->agi_override); + } + if(spawnentry->heat_override > 0){ + info->set_heat_base(spawnentry->heat_override); + info->set_heat(spawnentry->heat_override); + } + if(spawnentry->cold_override > 0){ + info->set_cold_base(spawnentry->cold_override); + info->set_cold(spawnentry->cold_override); + } + if(spawnentry->magic_override > 0){ + info->set_magic_base(spawnentry->magic_override); + info->set_magic(spawnentry->magic_override); + } + if(spawnentry->mental_override > 0){ + info->set_mental_base(spawnentry->mental_override); + info->set_mental(spawnentry->mental_override); + } + if(spawnentry->divine_override > 0){ + info->set_divine_base(spawnentry->divine_override); + info->set_divine(spawnentry->divine_override); + } + if(spawnentry->disease_override > 0){ + info->set_disease_base(spawnentry->disease_override); + info->set_disease(spawnentry->disease_override); + } + if(spawnentry->poison_override > 0){ + info->set_poison_base(spawnentry->poison_override); + info->set_poison(spawnentry->poison_override); + } + if(spawnentry->difficulty_override > 0){ + npc->SetDifficulty(spawnentry->difficulty_override, 1); + } + if (spawnentry->expire_time > 0) + AddSpawnExpireTimer(npc, spawnentry->expire_time, spawnentry->expire_offset); + AddLoot(npc); + + SetSpawnScript(spawnentry, npc); + + CallSpawnScript(npc, SPAWN_SCRIPT_PRESPAWN); + + AddSpawn(npc); + } + LogWrite(SPAWN__TRACE, 1, "Spawn", "Exit %s", __FUNCTION__); + return npc; +} + +vector* ZoneServer::GetAssociatedLocations(set* groups){ + vector* ret = 0; + LogWrite(SPAWN__TRACE, 1, "Spawn", "Enter %s", __FUNCTION__); + if(groups){ + int32 group_id = 0; + set::iterator group_itr; + for (group_itr = groups->begin(); group_itr != groups->end(); group_itr++) { + if(!ret) + ret = new vector(); + group_id = *group_itr; + + MSpawnGroupLocations.readlock(__FUNCTION__, __LINE__); + if(spawn_group_locations.count(group_id) > 0){ + map::iterator itr; + for (itr = spawn_group_locations[group_id]->begin(); itr != spawn_group_locations[group_id]->end(); itr++) { + ret->push_back(itr->first); + } + } + MSpawnGroupLocations.releasereadlock(__FUNCTION__, __LINE__); + } + } + LogWrite(SPAWN__TRACE, 1, "Spawn", "Exit %s", __FUNCTION__); + return ret; +} + +set* ZoneServer::GetAssociatedGroups(int32 group_id) { + set* ret = 0; + MSpawnGroupAssociation.readlock(__FUNCTION__, __LINE__); + LogWrite(SPAWN__TRACE, 1, "Spawn", "Enter %s", __FUNCTION__); + if(spawn_group_associations.count(group_id) > 0) + ret = spawn_group_associations[group_id]; + LogWrite(SPAWN__TRACE, 1, "Spawn", "Exit %s", __FUNCTION__); + MSpawnGroupAssociation.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +map* ZoneServer::GetSpawnLocationsByGroup(int32 group_id) { + map* ret = 0; + + MSpawnGroupLocations.readlock(__FUNCTION__, __LINE__); + if(spawn_group_locations.count(group_id) > 0) + ret = spawn_group_locations[group_id]; + MSpawnGroupLocations.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +list* ZoneServer::GetSpawnGroupsByLocation(int32 location_id){ + list* ret = 0; + + MSpawnLocationGroups.readlock(__FUNCTION__, __LINE__); + if(spawn_location_groups.count(location_id) > 0) + ret = spawn_location_groups[location_id]; + MSpawnLocationGroups.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +float ZoneServer::GetSpawnGroupChance(int32 group_id){ + float ret = -1; + + MSpawnGroupChances.readlock(__FUNCTION__, __LINE__); + if(spawn_group_chances.count(group_id) > 0) + ret = spawn_group_chances[group_id]; + MSpawnGroupChances.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +void ZoneServer::AddSpawnGroupChance(int32 group_id, float percent){ + MSpawnGroupChances.writelock(__FUNCTION__, __LINE__); + spawn_group_chances[group_id] = percent; + MSpawnGroupChances.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::AddSpawnGroupAssociation(int32 group_id1, int32 group_id2) { + MSpawnGroupAssociation.writelock(__FUNCTION__, __LINE__); + //Check if we already have containers for these group ids, if not create them + if (spawn_group_associations.count(group_id1) == 0) + spawn_group_associations[group_id1] = new set; + if (spawn_group_associations.count(group_id2) == 0) + spawn_group_associations[group_id2] = new set; + + //Associate groups 1 and 2 now + set* group_1 = spawn_group_associations.find(group_id1)->second; + set* group_2 = spawn_group_associations.find(group_id2)->second; + group_1->insert(group_id2); + group_2->insert(group_id1); + + //Associate the remaining groups together + set::iterator itr; + for (itr = group_1->begin(); itr != group_1->end(); itr++){ + group_2->insert(*itr); + map*>::iterator assoc_itr = spawn_group_associations.find(*itr); + if (assoc_itr != spawn_group_associations.end()) + assoc_itr->second->insert(group_id2); + else { + set* new_set = new set; + spawn_group_associations[*itr] = new_set; + new_set->insert(group_id2); + } + } + for (itr = group_2->begin(); itr != group_2->end(); itr++){ + group_1->insert(*itr); + map*>::iterator assoc_itr = spawn_group_associations.find(*itr); + if (assoc_itr != spawn_group_associations.end()) + assoc_itr->second->insert(group_id1); + else { + set* new_set = new set; + spawn_group_associations[*itr] = new_set; + new_set->insert(group_id1); + } + } + MSpawnGroupAssociation.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::AddSpawnGroupLocation(int32 group_id, int32 location_id, int32 spawn_location_id) { + MSpawnGroupLocations.writelock(__FUNCTION__, __LINE__); + if(spawn_group_locations.count(group_id) == 0) + spawn_group_locations[group_id] = new map(); + (*spawn_group_locations[group_id])[location_id] = spawn_location_id; + MSpawnGroupLocations.releasewritelock(__FUNCTION__, __LINE__); + + MSpawnLocationGroups.writelock(__FUNCTION__, __LINE__); + if(spawn_location_groups.count(location_id) == 0) + spawn_location_groups[location_id] = new list(); + spawn_location_groups[location_id]->push_back(group_id); + MSpawnLocationGroups.releasewritelock(__FUNCTION__, __LINE__); + + MSpawnGroupAssociation.writelock(__FUNCTION__, __LINE__); + if(spawn_group_associations.count(group_id) == 0) + spawn_group_associations[group_id] = new set(); + spawn_group_associations[group_id]->insert(group_id); + MSpawnGroupAssociation.releasewritelock(__FUNCTION__, __LINE__); +} + +bool ZoneServer::CallSpawnScript(Spawn* npc, int8 type, Spawn* spawn, const char* message, bool is_door_open, sint32 input_value, sint32* return_value){ + + LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__); + if(!npc) + return false; + + const char* script = npc->GetSpawnScript(); + if ( script == nullptr || strlen(script) < 1 ) + { + if (!npc->IsPet() && npc->GetZone() != nullptr) + { + string tmpScript; + tmpScript.append("SpawnScripts/"); + tmpScript.append(npc->GetZone()->GetZoneName()); + tmpScript.append("/"); + int count = 0; + for (int s = 0; s < strlen(npc->GetName()); s++) + { + if (isalnum((unsigned char)npc->GetName()[s])) + { + tmpScript += npc->GetName()[s]; + count++; + } + } + + tmpScript.append(".lua"); + + if (count < 1) + { + LogWrite(SPAWN__TRACE, 0, "Spawn", "Could not form script name %s..", __FUNCTION__); + } + else + { + struct stat buffer; + bool fileExists = (stat(tmpScript.c_str(), &buffer) == 0); + if (fileExists) + { + LogWrite(SPAWN__WARNING, 0, "Spawn", "No script file described in the database, overriding with SpawnScript at %s", (char*)tmpScript.c_str()); + npc->SetSpawnScript(tmpScript); + script = npc->GetSpawnScript(); + } + } + } + } + + bool result = false; + if(lua_interface && script){ + result = true; // default to true, if we don't match a switch case, return false in default case + switch(type){ + case SPAWN_SCRIPT_SPAWN:{ + lua_interface->RunSpawnScript(script, "spawn", npc); + break; + } + case SPAWN_SCRIPT_RESPAWN:{ + lua_interface->RunSpawnScript(script, "respawn", npc); + break; + } + case SPAWN_SCRIPT_ATTACKED:{ + lua_interface->RunSpawnScript(script, "attacked", npc, spawn); + break; + } + case SPAWN_SCRIPT_TARGETED:{ + lua_interface->RunSpawnScript(script, "targeted", npc, spawn); + break; + } + case SPAWN_SCRIPT_HAILED:{ + result = lua_interface->RunSpawnScript(script, "hailed", npc, spawn); + break; + } + case SPAWN_SCRIPT_HAILED_BUSY:{ + lua_interface->RunSpawnScript(script, "hailed_busy", npc, spawn); + break; + } + case SPAWN_SCRIPT_DEATH:{ + lua_interface->RunSpawnScript(script, "death", npc, spawn); + break; + } + case SPAWN_SCRIPT_KILLED:{ + lua_interface->RunSpawnScript(script, "killed", npc, spawn); + break; + } + case SPAWN_SCRIPT_AGGRO:{ + lua_interface->RunSpawnScript(script, "aggro", npc, spawn); + break; + } + case SPAWN_SCRIPT_HEALTHCHANGED:{ + result = lua_interface->RunSpawnScript(script, "healthchanged", npc, spawn, 0, false, input_value, return_value); + break; + } + case SPAWN_SCRIPT_RANDOMCHAT:{ + lua_interface->RunSpawnScript(script, "randomchat", npc, 0, message); + break; + } + case SPAWN_SCRIPT_CUSTOM: + case SPAWN_SCRIPT_TIMER: + case SPAWN_SCRIPT_CONVERSATION:{ + lua_interface->RunSpawnScript(script, message, npc, spawn); + break; + } + case SPAWN_SCRIPT_CASTED_ON: { + lua_interface->RunSpawnScript(script, "casted_on", npc, spawn, message); + break; + } + case SPAWN_SCRIPT_AUTO_ATTACK_TICK: { + lua_interface->RunSpawnScript(script, "auto_attack_tick", npc, spawn); + break; + } + case SPAWN_SCRIPT_COMBAT_RESET: { + lua_interface->RunSpawnScript(script, "CombatReset", npc); + break; + } + case SPAWN_SCRIPT_GROUP_DEAD: { + lua_interface->RunSpawnScript(script, "group_dead", npc, spawn); + break; + } + case SPAWN_SCRIPT_HEAR_SAY: { + lua_interface->RunSpawnScript(script, "hear_say", npc, spawn, message); + break; + } + case SPAWN_SCRIPT_PRESPAWN: { + lua_interface->RunSpawnScript(script, "prespawn", npc); + break; + } + case SPAWN_SCRIPT_USEDOOR: { + result = lua_interface->RunSpawnScript(script, "usedoor", npc, spawn, "", is_door_open); + break; + } + case SPAWN_SCRIPT_BOARD: { + result = lua_interface->RunSpawnScript(script, "board", npc, spawn); + break; + } + case SPAWN_SCRIPT_DEBOARD: { + result = lua_interface->RunSpawnScript(script, "deboard", npc, spawn); + break; + } + default: + { + result = false; + break; + } + } + } + LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit %s", __FUNCTION__); + + return result; +} + +void ZoneServer::DeleteTransporters() { + MTransportLocations.writelock(__FUNCTION__, __LINE__); + transporter_locations.clear(); //world takes care of actually deleting the data + MTransportLocations.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::ReloadTransporters(){ + MutexList* locations = GetLocationTransporters(GetZoneID()); + if(locations){ + MutexList::iterator itr = locations->begin(); + while(itr.Next()) + AddTransporter(itr->value); + } +} + +void ZoneServer::CheckTransporters(Client* client) { + MTransportLocations.readlock(__FUNCTION__, __LINE__); + if(transporter_locations.size() > 0){ + LocationTransportDestination* loc = 0; + list::iterator itr; + for (itr = transporter_locations.begin(); itr != transporter_locations.end(); itr++) { + loc = *itr; + if(client->GetPlayer()->GetDistance(loc->trigger_x, loc->trigger_y, loc->trigger_z) <= loc->trigger_radius){ + if(loc->destination_zone_id == 0 || loc->destination_zone_id == GetZoneID()){ + EQ2Packet* packet = client->GetPlayer()->Move(loc->destination_x, loc->destination_y, loc->destination_z, client->GetVersion()); + if(packet) + client->QueuePacket(packet); + } + else{ + ZoneServer* new_zone = zone_list.Get(loc->destination_zone_id); + if(new_zone){ + client->GetPlayer()->SetX(loc->destination_x); + client->GetPlayer()->SetY(loc->destination_y); + client->GetPlayer()->SetZ(loc->destination_z); + client->GetPlayer()->SetHeading(loc->destination_heading); + client->Zone(new_zone, false); + } + } + break; + } + } + } + MTransportLocations.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::AddTransporter(LocationTransportDestination* loc) { + MTransportLocations.writelock(__FUNCTION__, __LINE__); + transporter_locations.push_back(loc); + MTransportLocations.releasewritelock(__FUNCTION__, __LINE__); +} + +Sign* ZoneServer::AddSignSpawn(SpawnLocation* spawnlocation, SpawnEntry* spawnentry){ + LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__); + Sign* sign = GetNewSign(spawnentry->spawn_id); + if(sign && !sign->IsOmittedByDBFlag()){ + DeterminePosition(spawnlocation, sign); + sign->SetDatabaseID(spawnentry->spawn_id); + sign->SetSpawnLocationID(spawnentry->spawn_location_id); + sign->SetSpawnEntryID(spawnentry->spawn_entry_id); + sign->SetRespawnTime(spawnentry->respawn); + sign->SetExpireTime(spawnentry->expire_time); + if (spawnentry->expire_time > 0) + AddSpawnExpireTimer(sign, spawnentry->expire_time, spawnentry->expire_offset); + + SetSpawnScript(spawnentry, sign); + + CallSpawnScript(sign, SPAWN_SCRIPT_PRESPAWN); + + AddSpawn(sign); + } + LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit %s", __FUNCTION__); + return sign; +} + +Widget* ZoneServer::AddWidgetSpawn(SpawnLocation* spawnlocation, SpawnEntry* spawnentry){ + LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__); + Widget* widget = GetNewWidget(spawnentry->spawn_id); + if(widget && !widget->IsOmittedByDBFlag()){ + DeterminePosition(spawnlocation, widget); + widget->SetDatabaseID(spawnentry->spawn_id); + widget->SetSpawnLocationID(spawnentry->spawn_location_id); + widget->SetSpawnEntryID(spawnentry->spawn_entry_id); + if(!widget->GetIncludeLocation()){ + widget->SetX(widget->GetWidgetX()); + if(widget->GetCloseY() != 0) + widget->SetY(widget->GetCloseY()); + widget->SetZ(widget->GetWidgetZ()); + } + widget->SetRespawnTime(spawnentry->respawn); + widget->SetExpireTime(spawnentry->expire_time); + widget->SetSpawnOrigHeading(widget->GetHeading()); + if (spawnentry->expire_time > 0) + AddSpawnExpireTimer(widget, spawnentry->expire_time, spawnentry->expire_offset); + + SetSpawnScript(spawnentry, widget); + + CallSpawnScript(widget, SPAWN_SCRIPT_PRESPAWN); + + AddSpawn(widget); + } + LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit %s", __FUNCTION__); + return widget; +} + +Object* ZoneServer::AddObjectSpawn(SpawnLocation* spawnlocation, SpawnEntry* spawnentry){ + LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__); + Object* object = GetNewObject(spawnentry->spawn_id); + if(object && !object->IsOmittedByDBFlag()){ + DeterminePosition(spawnlocation, object); + object->SetDatabaseID(spawnentry->spawn_id); + object->SetSpawnLocationID(spawnentry->spawn_location_id); + object->SetSpawnEntryID(spawnentry->spawn_entry_id); + object->SetRespawnTime(spawnentry->respawn); + object->SetExpireTime(spawnentry->expire_time); + if (spawnentry->expire_time > 0) + AddSpawnExpireTimer(object, spawnentry->expire_time, spawnentry->expire_offset); + + SetSpawnScript(spawnentry, object); + + CallSpawnScript(object, SPAWN_SCRIPT_PRESPAWN); + + AddSpawn(object); + } + LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit %s", __FUNCTION__); + return object; +} + +GroundSpawn* ZoneServer::AddGroundSpawn(SpawnLocation* spawnlocation, SpawnEntry* spawnentry){ + LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__); + GroundSpawn* spawn = GetNewGroundSpawn(spawnentry->spawn_id); + if(spawn && !spawn->IsOmittedByDBFlag()){ + DeterminePosition(spawnlocation, spawn); + spawn->SetDatabaseID(spawnentry->spawn_id); + spawn->SetSpawnLocationID(spawnentry->spawn_location_id); + spawn->SetSpawnEntryID(spawnentry->spawn_entry_id); + spawn->SetRespawnTime(spawnentry->respawn); + spawn->SetExpireTime(spawnentry->expire_time); + + if(spawn->GetRandomizeHeading()) { + float rand_heading = MakeRandomFloat(0.0f, 360.0f); + spawn->SetHeading(rand_heading); + } + else { + spawn->SetHeading(spawnlocation->heading); + } + + if (spawnentry->expire_time > 0) + AddSpawnExpireTimer(spawn, spawnentry->expire_time, spawnentry->expire_offset); + + SetSpawnScript(spawnentry, spawn); + + CallSpawnScript(spawn, SPAWN_SCRIPT_PRESPAWN); + + AddSpawn(spawn); + } + LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit %s", __FUNCTION__); + return spawn; +} + +void ZoneServer::AddSpawn(Spawn* spawn) { + if(!spawn->IsPlayer()) // we already set it on loadCharacter + spawn->SetZone(this); + + MIgnoredWidgets.lock_shared(); + std::map::iterator itr; + for(itr = ignored_widgets.begin(); itr != ignored_widgets.end(); itr++) { + spawn->AddIgnoredWidget(itr->first); + } + MIgnoredWidgets.unlock_shared(); + spawn->position_changed = false; + spawn->info_changed = false; + spawn->vis_changed = false; + spawn->changed = false; + + // Write locking the spawn list here will cause deadlocks, so instead add it to a temp list that the + // main spawn thread will put into the spawn_list when ever it has a chance. + MPendingSpawnListAdd.writelock(__FUNCTION__, __LINE__); + pending_spawn_list_add.push_back(spawn); + MPendingSpawnListAdd.releasewritelock(__FUNCTION__, __LINE__); + + spawn_range.Trigger(); + + if (GetInstanceType() == PERSONAL_HOUSE_INSTANCE && spawn->IsObject()) + { + spawn->AddSecondaryEntityCommand("Examine", 20, "house_spawn_examine", "", 0, 0); + spawn->AddSecondaryEntityCommand("Move", 20, "move_item", "", 0, 0); + spawn->AddSecondaryEntityCommand("Pack in Moving Crate", 20, "house_spawn_pack_in_moving_crate", "", 0, 0); + spawn->AddSecondaryEntityCommand("Pick Up", 20, "pickup", "", 0, 0); + spawn->SetShowCommandIcon(1); + } + + if(spawn->IsNPC()) + AddEnemyList((NPC*)spawn); + if(spawn->IsPlayer() && ((Player*)spawn)->GetGroupMemberInfo()) + spawn->SendGroupUpdate(); + if (spawn->IsPlayer()) { + ((Player*)spawn)->GetInfoStruct()->set_rain(rain); + ((Player*)spawn)->SetCharSheetChanged(true); + } + + if (movementMgr != nullptr && spawn->IsEntity()) { + movementMgr->AddMob((Entity*)spawn); + } + + AddSpawnProximities(spawn); + + AddSpawnToGrid(spawn, spawn->GetLocation()); + spawn->SetAddedToWorldTimestamp(Timer::GetCurrentTime2()); +} + +void ZoneServer::AddClient(Client* client){ + MClientList.writelock(__FUNCTION__, __LINE__); + lifetime_client_count++; + DecrementIncomingClients(); + clients.push_back(client); + MClientList.releasewritelock(__FUNCTION__, __LINE__); + + connected_clients.Add(client); +} + +void ZoneServer::RemoveClient(Client* client) +{ + Guild *guild; + + bool dismissPets = false; + if(client) + { + if (client->GetPlayer()) + client_list.RemovePlayerFromInvisHistory(client->GetPlayer()->GetID()); + + LogWrite(ZONE__DEBUG, 0, "Zone", "Sending login equipment appearance updates..."); + loginserver.SendImmediateEquipmentUpdatesForChar(client->GetPlayer()->GetCharacterID()); + + if (!client->IsZoning()) + { + client->SaveSpells(); + + client->GetPlayer()->DeleteSpellEffects(true); + + if ((guild = client->GetPlayer()->GetGuild()) != NULL) + guild->GuildMemberLogoff(client->GetPlayer()); + + chat.LeaveAllChannels(client); + } + + if(!zoneShuttingDown && !client->IsZoning()) + { + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo(); + int32 group_id = 0; + if (gmi) { + group_id = gmi->group_id; + } + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + if (group_id) { + int32 size = world.GetGroupManager()->GetGroupSize(group_id); + if (size > 1) { + bool send_left_message = size > 2; + // removegroupmember can delete the gmi, so make sure we still have a group_id after + world.GetGroupManager()->RemoveGroupMember(group_id, client->GetPlayer()); + if (send_left_message) + world.GetGroupManager()->GroupMessage(group_id, "%s has left the group.", client->GetPlayer()->GetName()); + } + } + if( (client->GetPlayer()->GetActivityStatus() & ACTIVITY_STATUS_LINKDEAD) > 0) + { + LogWrite(ZONE__DEBUG, 0, "Zone", "Removing client '%s' (%u) due to LD/Exit...", client->GetPlayer()->GetName(), client->GetPlayer()->GetCharacterID()); + } + else + { + LogWrite(ZONE__DEBUG, 0, "Zone", "Removing client '%s' (%u) due to Camp/Quit...", client->GetPlayer()->GetName(), client->GetPlayer()->GetCharacterID()); + } + + dismissPets = true; + //} + } + else + { + LogWrite(ZONE__DEBUG, 0, "Zone", "Removing client '%s' (%u) due to some client zoning...", client->GetPlayer()->GetName(), client->GetPlayer()->GetCharacterID()); + } + + map::iterator itr; + for (itr = client->GetPlayer()->SpawnedBots.begin(); itr != client->GetPlayer()->SpawnedBots.end(); itr++) { + Spawn* spawn = GetSpawnByID(itr->second); + if (spawn) + ((Bot*)spawn)->Camp(); + } + + if(dismissPets) { + ((Entity*)client->GetPlayer())->DismissAllPets(); + } + + MClientList.writelock(__FUNCTION__, __LINE__); + LogWrite(ZONE__DEBUG, 0, "Zone", "Calling clients.Remove(client)..."); + + std::vector::iterator itr2 = find(clients.begin(), clients.end(), client); + if (itr2 != clients.end()) + clients.erase(itr2); + MClientList.releasewritelock(__FUNCTION__, __LINE__); + + LogWrite(ZONE__INFO, 0, "Zone", "Scheduling client '%s' for removal.", client->GetPlayer()->GetName()); + database.ToggleCharacterOnline(client, 0); + + RemoveSpawn(client->GetPlayer(), false, true, true, true, true); + + int32 DisconnectClientTimer = rule_manager.GetGlobalRule(R_World, RemoveDisconnectedClientsTimer)->GetInt32(); + connected_clients.Remove(client, true, DisconnectClientTimer); // changed from a hardcoded 30000 (30 sec) to the DisconnectClientTimer rule + } +} + +void ZoneServer::RemoveClientImmediately(Client* client) { + Guild *guild; + + if(client) + { + if(client->GetPlayer()) { + if((client->GetPlayer()->GetActivityStatus() & ACTIVITY_STATUS_LINKDEAD) > 0) { + client->GetPlayer()->SetActivityStatus(client->GetPlayer()->GetActivityStatus() - ACTIVITY_STATUS_LINKDEAD); + } + if ((client->GetPlayer()->GetActivityStatus() & ACTIVITY_STATUS_CAMPING) == 0) { + client->GetPlayer()->SetActivityStatus(client->GetPlayer()->GetActivityStatus() + ACTIVITY_STATUS_CAMPING); + } + client->Disconnect(); + } + MClientList.writelock(__FUNCTION__, __LINE__); + std::vector::iterator itr = find(clients.begin(), clients.end(), client); + if (itr != clients.end()) + clients.erase(itr); + MClientList.releasewritelock(__FUNCTION__, __LINE__); + //clients.Remove(client, true); + } +} + +void ZoneServer::ClientProcess(bool ignore_shutdown_timer) +{ + if(!ignore_shutdown_timer && connected_clients.size(true) == 0) + { + MIncomingClients.readlock(__FUNCTION__, __LINE__); + bool shutdownDelayCheck = shutdownDelayTimer.Check(); + if((!AlwaysLoaded() && !shutdownTimer.Enabled()) || shutdownDelayCheck) + { + if(incoming_clients && !shutdownDelayTimer.Enabled()) { + LogWrite(ZONE__INFO, 0, "Zone", "Incoming clients (%u) expected for %s, delaying shutdown timer...", incoming_clients, GetZoneName()); + int32 timerDelay = rule_manager.GetGlobalRule(R_Zone, ShutdownDelayTimer)->GetInt32(); + + if(timerDelay < 10) { + LogWrite(ZONE__INFO, 0, "Zone", "Overriding %s shutdown delay timer as other clients are incoming, value %u too short, setting to 10...", GetZoneName(), timerDelay); + timerDelay = 10; + } + shutdownDelayTimer.Start(timerDelay, true); + } + else if(!incoming_clients || shutdownDelayCheck) { + if(!shutdownTimer.Enabled()) { + LogWrite(ZONE__INFO, 0, "Zone", "Starting zone shutdown timer for %s...", GetZoneName()); + shutdownTimer.Start(); + } + else { + LogWrite(ZONE__INFO, 0, "Zone", "zone shutdown timer for %s has %u remaining...", GetZoneName(), shutdownTimer.GetRemainingTime()); + } + } + } + MIncomingClients.releasereadlock(__FUNCTION__, __LINE__); + return; + } + + shutdownTimer.Disable(); + shutdownDelayTimer.Disable(); + Client* client = 0; + MutexList::iterator iterator = connected_clients.begin(); + + while(iterator.Next()) + { + client = iterator->value; +#ifndef NO_CATCH + try + { +#endif + if(zoneShuttingDown || !client->Process(true)) + { + if(!zoneShuttingDown && !client->IsZoning()) + { + // avoid spam of messages while we await linkdead to complete + if(!client->IsLinkdeadTimerEnabled()) { + bool camping = (client->GetPlayer()->GetActivityStatus() & ACTIVITY_STATUS_CAMPING); + LogWrite(ZONE__DEBUG, 0, "Zone", "Client is disconnecting in %s (camping = %s)", __FUNCTION__, camping ? "true" : "false"); + if(!camping) { + client->setConnection(nullptr); + } + } + + if((client->GetPlayer()->GetActivityStatus() & ACTIVITY_STATUS_LINKDEAD) > 0) { + client->StartLinkdeadTimer(); + } + else if( (client->GetPlayer()->GetActivityStatus() & ACTIVITY_STATUS_CAMPING) == 0 ) + { + //only set LD flag if we're disconnecting but not camping/quitting + client->GetPlayer()->SetActivityStatus(client->GetPlayer()->GetActivityStatus() + ACTIVITY_STATUS_LINKDEAD); + client->StartLinkdeadTimer(); + } + else { + // camp timer completed, remove client + RemoveClient(client); + client->Disconnect(); + } + } + else { + // force boot all players or clients zoning + RemoveClient(client); + client->Disconnect(); + } + } +#ifndef NO_CATCH + } + catch(...) + { + LogWrite(ZONE__ERROR, 0, "Zone", "Exception caught when in ZoneServer::ClientProcess() for zone '%s'!\n%s, %i", GetZoneName(), __FUNCTION__, __LINE__); + try{ + bool isLinkdead = false; + if(!client->IsZoning()) + { + if( (client->GetPlayer()->GetActivityStatus() & ACTIVITY_STATUS_CAMPING) == 0 ) + { + client->GetPlayer()->SetActivityStatus(client->GetPlayer()->GetActivityStatus() + ACTIVITY_STATUS_LINKDEAD); + client->StartLinkdeadTimer(); + isLinkdead = true; + if(client->GetPlayer()->GetGroupMemberInfo()) + world.GetGroupManager()->GroupMessage(client->GetPlayer()->GetGroupMemberInfo()->group_id, "%s has gone Linkdead.", client->GetPlayer()->GetName()); + } + } + + if(!isLinkdead) { + RemoveClient(client); + client->Disconnect(); + } + } + catch(...){ + LogWrite(ZONE__ERROR, 0, "Zone", "Exception caught when in ZoneServer::ClientProcess(), second try\n%s, %i", __FUNCTION__, __LINE__); + } + } +#endif + } +} + +void ZoneServer::SimpleMessage(int8 type, const char* message, Spawn* from, float distance, bool send_to_sender){ + Client* client = 0; + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(from && client && client->IsConnected() && (send_to_sender || from != client->GetPlayer()) && from->GetDistance(client->GetPlayer()) <= distance){ + client->SimpleMessage(type, message); + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::HandleChatMessage(Client* client, Spawn* from, const char* to, int16 channel, const char* message, float distance, const char* channel_name, bool show_bubble, int32 language) { + if ((!distance || from->GetDistance(client->GetPlayer()) <= distance) && (!from || !client->GetPlayer()->IsIgnored(from->GetName()))) { + PacketStruct* packet = configReader.getStruct("WS_HearChat", client->GetVersion()); + if (packet) { + if (from) + packet->setMediumStringByName("from", from->GetName()); + if (client->GetPlayer() != from) + packet->setMediumStringByName("to", client->GetPlayer()->GetName()); + int8 clientchannel = client->GetMessageChannelColor(channel); + packet->setDataByName("channel", client->GetMessageChannelColor(channel)); + if (from && ((from == client->GetPlayer()) || (client->GetPlayer()->WasSentSpawn(from->GetID())))) + packet->setDataByName("from_spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(from)); + else + packet->setDataByName("from_spawn_id", 0xFFFFFFFF); + packet->setDataByName("to_spawn_id", 0xFFFFFFFF); + packet->setMediumStringByName("message", message); + packet->setDataByName("language", language); + + bool hasLanguage = client->GetPlayer()->HasLanguage(language); + if (language > 0 && !hasLanguage) + packet->setDataByName("understood", 0); + else + packet->setDataByName("understood", 1); + + show_bubble == true ? packet->setDataByName("show_bubble", 1) : packet->setDataByName("show_bubble", 0); + if (channel_name) + packet->setMediumStringByName("channel_name", channel_name); + EQ2Packet* outapp = packet->serialize(); + //DumpPacket(outapp); + client->QueuePacket(outapp); + safe_delete(packet); + } + } +} + +void ZoneServer::HandleChatMessage(Spawn* from, const char* to, int16 channel, const char* message, float distance, const char* channel_name, bool show_bubble, int32 language){ + vector::iterator client_itr; + Client* client = 0; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(client && client->IsConnected()) + HandleChatMessage(client, from, to, channel, message, distance, channel_name, show_bubble, language); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::HandleBroadcast(const char* message) { + vector::iterator client_itr; + Client* client = 0; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(client && client->IsConnected()) + client->SimpleMessage(CHANNEL_BROADCAST, message); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::HandleAnnouncement(const char* message) { + vector::iterator client_itr; + Client* client = 0; + int32 words = ::CountWordsInString(message); + if (words < 5) + words = 5; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(client && client->IsConnected()) { + client->SimpleMessage(CHANNEL_BROADCAST, message); + client->SendPopupMessage(10, message, "ui_harvest_normal", words, 0xFF, 0xFF, 0x00); + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::SendTimeUpdate(Client* client){ + if(client){ + PacketStruct* packet = world.GetWorldTime(client->GetVersion()); + if(packet){ + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } +} + +void ZoneServer::SendTimeUpdateToAllClients(){ + Client* client = 0; + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(client && client->IsConnected()) + SendTimeUpdate(client); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::UpdateVitality(float amount){ + Client* client = 0; + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(client && client->GetPlayer()->GetInfoStruct()->get_xp_vitality() < 100){ + if((client->GetPlayer()->GetInfoStruct()->get_xp_vitality() + amount) > 100) + client->GetPlayer()->GetInfoStruct()->set_xp_vitality(100); + else + client->GetPlayer()->GetInfoStruct()->add_xp_vitality(amount); + client->GetPlayer()->SetCharSheetChanged(true); + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::SendSpawn(Spawn* spawn, Client* client){ + EQ2Packet* outapp = spawn->serialize(client->GetPlayer(), client->GetVersion()); + + if(!client->GetPlayer()->IsSendingSpawn(spawn->GetID())) { + safe_delete(outapp); + } + else { + LogWrite(ZONE__DEBUG, 7, "Zone", "%s: Processing SendSpawn for spawn index %u (%s)...", client->GetPlayer()->GetName(), client->GetPlayer()->GetIndexForSpawn(spawn), spawn->GetName()); + if(outapp) + client->QueuePacket(outapp, true); + + client->GetPlayer()->SetSpawnSentState(spawn, SpawnState::SPAWN_STATE_SENT_WAIT); + } + + /* + vis flags: + 2 = show icon + 4 = targetable + 16 = show name + 32 = show level/border + activity_status: + 4 - linkdead + 8 - camping + 16 - LFG + 32 - LFW + 2048 - mentoring + 4096 - displays shield + 8192 - immunity gained + 16384 - immunity remaining + attackable_status + 1 - no_hp_bar + 4 - not attackable + npc_con + -4 = scowls + -3 = threatening + -2 = dubiously + -1 = apprehensively + 0 = indifferent + 1 = amiably + 2 = kindly + 3 = warmly + 4 = ally + quest_flag + 1 = new quest + 2 = update and new quest + 3 = update + */ + if(spawn->IsEntity() && spawn->HasTrapTriggered()) + client->QueueStateCommand(client->GetPlayer()->GetIDWithPlayerSpawn(spawn), spawn->GetTrapState()); +} + +Client* ZoneServer::GetClientByName(char* name) { + Client* ret = 0; + vector::iterator itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (itr = clients.begin(); itr != clients.end(); itr++) { + if ((*itr)->GetPlayer()) { + if (strncmp((*itr)->GetPlayer()->GetName(), name, strlen(name)) == 0) { + ret = *itr; + break; + } + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +Client* ZoneServer::GetClientByCharID(int32 charid) { + Client* ret = 0; + vector::iterator itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (itr = clients.begin(); itr != clients.end(); itr++) { + if ((*itr)->GetCharacterID() == charid) { + ret = *itr; + break; + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +void ZoneServer::AddMovementNPC(Spawn* spawn){ + if (spawn) + movement_spawns.Put(spawn->GetID(), 1); +} + +void ZoneServer::RemoveMovementNPC(Spawn* spawn){ + if (spawn) + remove_movement_spawns.Add(spawn->GetID()); +} + +void ZoneServer::PlayFlavor(Client* client, Spawn* spawn, const char* mp3, const char* text, const char* emote, int32 key1, int32 key2, int8 language){ + if(!client || !spawn) + return; + + PacketStruct* packet = configReader.getStruct("WS_PlayFlavor", client->GetVersion()); + if(packet){ + packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(spawn)); + packet->setDataByName("unknown1", 0xFFFFFFFF); + packet->setDataByName("unknown5", 1, 1); + packet->setDataByName("unknown5", 1, 6); + if(mp3){ + packet->setMediumStringByName("mp3", mp3); + packet->setDataByName("key", key1); + packet->setDataByName("key", key2, 1); + } + packet->setMediumStringByName("name", spawn->GetName()); + if(text) + packet->setMediumStringByName("text", text); + if(emote) { + if(client->GetVersion() > 561) { + packet->setMediumStringByName("emote", emote); + } + else { + HandleEmote(spawn, std::string(emote)); + } + } + if (language != 0) + packet->setDataByName("language", language); + + //We should probably add Common = language id 0 or 0xFF so admins can customize more.. + if (language == 0 || client->GetPlayer()->HasLanguage(language)) + packet->setDataByName("understood", 1); + + EQ2Packet* app = packet->serialize(); + //DumpPacket(app); + client->QueuePacket(app); + safe_delete(packet); + } +} + +void ZoneServer::PlayVoice(Client* client, Spawn* spawn, const char* mp3, int32 key1, int32 key2){ + if(!client || !spawn) + return; + + PacketStruct* packet = configReader.getStruct("WS_PlayVoice", client->GetVersion()); + if(packet){ + packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(spawn)); + packet->setMediumStringByName("mp3", mp3); + packet->setDataByName("key", key1); + packet->setDataByName("key", key2, 1); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } +} + +void ZoneServer::PlayFlavor(Spawn* spawn, const char* mp3, const char* text, const char* emote, int32 key1, int32 key2, int8 language){ + if(!spawn) + return; + + Client* client = 0; + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(!client || !client->IsReadyForUpdates() || !client->GetPlayer()->WasSentSpawn(spawn->GetID()) || client->GetPlayer()->GetDistance(spawn) > 30) + continue; + PlayFlavor(client, spawn, mp3, text, emote, key1, key2, language); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::PlayFlavorID(Spawn* spawn, int8 type, int32 id, int16 index, int8 language){ + if(!spawn) + return; + + Client* client = 0; + vector::iterator client_itr; + + VoiceOverStruct non_garble, garble; + bool garble_success = false; + bool success = world.FindVoiceOver(type, id, index, &non_garble, &garble_success, &garble); + + VoiceOverStruct* resStruct = nullptr; + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(!client || !client->IsReadyForUpdates() || !client->GetPlayer()->WasSentSpawn(spawn->GetID()) || client->GetPlayer()->GetDistance(spawn) > 30) + continue; + + client->SendPlayFlavor(spawn, language, &non_garble, &garble, success, garble_success); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::PlayVoice(Spawn* spawn, const char* mp3, int32 key1, int32 key2){ + if(!spawn || !mp3) + return; + + Client* client = 0; + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(!client || !client->IsReadyForUpdates() || !client->GetPlayer()->WasSentSpawn(spawn->GetID())) + continue; + PlayVoice(client, spawn, mp3, key1, key2); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::PlaySoundFile(Client* client, const char* name, float origin_x, float origin_y, float origin_z){ + if(!name) + return; + + PacketStruct* packet = 0; + if(client){ + packet = configReader.getStruct("WS_Play3DSound", client->GetVersion()); + if(packet){ + packet->setMediumStringByName("name", name); + packet->setDataByName("x", origin_x); + packet->setDataByName("y", origin_y); + packet->setDataByName("z", origin_z); + packet->setDataByName("unknown1", 1); + packet->setDataByName("unknown2", 2.5); + packet->setDataByName("unknown3", 15); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + else{ + EQ2Packet* outapp = 0; + int16 packet_version = 0; + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(client && (!packet || packet_version != client->GetVersion())){ + safe_delete(packet); + safe_delete(outapp); + packet_version = client->GetVersion(); + packet = configReader.getStruct("WS_Play3DSound", packet_version); + if(packet){ + packet->setMediumStringByName("name", name); + packet->setDataByName("x", origin_x); + packet->setDataByName("y", origin_y); + packet->setDataByName("z", origin_z); + packet->setDataByName("unknown1", 1); + packet->setDataByName("unknown2", 2.5); + packet->setDataByName("unknown3", 15); + outapp = packet->serialize(); + } + } + if(outapp && client && client->IsReadyForUpdates()) + client->QueuePacket(outapp->Copy()); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + safe_delete(packet); + safe_delete(outapp); + } +} + +bool ZoneServer::HasWidgetTimer(Spawn* widget){ + bool ret = false; + if (widget) { + int32 id = widget->GetID(); + map::iterator itr; + MWidgetTimers.readlock(__FUNCTION__, __LINE__); + for (itr = widget_timers.begin(); itr != widget_timers.end(); itr++) { + if(itr->first == id){ + ret = true; + break; + } + } + MWidgetTimers.releasereadlock(__FUNCTION__, __LINE__); + } + return ret; +} + +void ZoneServer::CheckWidgetTimers(){ + vector remove_list; + map::iterator itr; + + MWidgetTimers.readlock(__FUNCTION__, __LINE__); + for (itr = widget_timers.begin(); itr != widget_timers.end(); itr++) { + if(Timer::GetCurrentTime2() >= itr->second){ + /*Spawn* widget = GetSpawnByID(itr->first); + if (widget && widget->IsWidget()) + ((Widget*)widget)->HandleTimerUpdate();*/ + + remove_list.push_back(itr->first); + } + } + MWidgetTimers.releasereadlock(__FUNCTION__, __LINE__); + + for (int32 i = 0; i < remove_list.size(); i++) { + Spawn* widget = GetSpawnByID(remove_list[i]); + if (widget && widget->IsWidget()) + ((Widget*)widget)->HandleTimerUpdate(); + } + + MWidgetTimers.writelock(__FUNCTION__, __LINE__); + for(int32 i=0;iIsWidget()) { + MWidgetTimers.writelock(__FUNCTION__, __LINE__); + widget_timers[widget->GetID()] = ((int32)(time * 1000)) + Timer::GetCurrentTime2(); + MWidgetTimers.releasewritelock(__FUNCTION__, __LINE__); + } +} + +Spawn* ZoneServer::GetSpawnGroup(int32 id){ + Spawn* ret = 0; + Spawn* spawn = 0; + + if(id < 1) + return 0; + + bool lookup = false; + if(quick_group_id_lookup.count(id) > 0) { + ret = GetSpawnByID(quick_group_id_lookup.Get(id)); + lookup = true; + } + if(ret == NULL) { + if(lookup) + quick_group_id_lookup.erase(id); + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + spawn = itr->second; + if(spawn){ + if(spawn->GetSpawnGroupID() == id){ + ret = spawn; + quick_group_id_lookup.Put(id, spawn->GetID()); + break; + } + } + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + } + return ret; +} + +Spawn* ZoneServer::GetSpawnByLocationID(int32 location_id) { + Spawn* ret = 0; + Spawn* current_spawn = 0; + + if(location_id < 1) + return 0; + + bool lookup = false; + if(quick_location_id_lookup.count(location_id) > 0) { + ret = GetSpawnByID(quick_location_id_lookup.Get(location_id)); + lookup = true; + } + if(ret == NULL) { + if(lookup) + quick_location_id_lookup.erase(location_id); + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + current_spawn = itr->second; + if (current_spawn && current_spawn->GetSpawnLocationID() == location_id) { + ret = current_spawn; + quick_location_id_lookup.Put(location_id, ret->GetID()); + break; + } + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + } + return ret; +} + +Spawn* ZoneServer::GetSpawnByDatabaseID(int32 id){ + Spawn* ret = 0; + + if(id < 1) + return 0; + + bool lookup = false; + + if(quick_database_id_lookup.count(id) > 0) { + ret = GetSpawnByID(quick_database_id_lookup.Get(id)); + lookup = true; + } + if(ret == NULL){ + if(lookup) + quick_database_id_lookup.erase(id); + Spawn* spawn = 0; + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++){ + spawn = itr->second; + if(spawn){ + if(spawn->GetDatabaseID() == id){ + quick_database_id_lookup.Put(id, spawn->GetID()); + ret = spawn; + break; + } + } + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + } + return ret; +} + +Spawn* ZoneServer::GetSpawnByID(int32 id, bool spawnListLocked) { + Spawn* ret = 0; + if (!spawnListLocked ) + MSpawnList.readlock(__FUNCTION__, __LINE__); + + if (spawn_list.count(id) > 0) + ret = spawn_list[id]; + + if (!spawnListLocked) + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +bool ZoneServer::SendRemoveSpawn(Client* client, Spawn* spawn, PacketStruct* packet, bool delete_spawn) +{ + if(!client || !spawn || (client && client->GetPlayer() == spawn)) + return false; + + if(client->GetPlayerPOVGhostSpawnID() == spawn->GetID()) { + client->SetPlayerPOVGhost(nullptr); + } + + spawn->RemoveSpawnFromPlayer(client->GetPlayer()); + return true; +} + +void ZoneServer::SetSpawnCommand(Spawn* spawn, int8 type, char* value, Client* client){ + //commands + LogWrite(MISC__TODO, 1, "TODO", "%s does nothing!\n%s, %i", __FUNCTION__, __FILE__, __LINE__); +} + +void ZoneServer::SetSpawnCommand(int32 spawn_id, int8 type, char* value, Client* client){ + LogWrite(MISC__TODO, 1, "TODO", "%s does nothing!\n%s, %i", __FUNCTION__, __FILE__, __LINE__); +} + +void ZoneServer::ApplySetSpawnCommand(Client* client, Spawn* target, int8 type, const char* value){ + // This will apply the /spawn set command to all the spawns in the zone with the same DB ID, we do not want to set + // location values (x, y, z, heading, grid) for all spawns in the zone with the same DB ID, only the targeted spawn + if(type == SPAWN_SET_VALUE_SPAWNENTRY_SCRIPT || type == SPAWN_SET_VALUE_SPAWNLOCATION_SCRIPT || (type >= SPAWN_SET_VALUE_X && type <= SPAWN_SET_VALUE_LOCATION) || + type == SPAWN_SET_VALUE_PITCH || type == SPAWN_SET_VALUE_ROLL) + return; + + Spawn* tmp = 0; + if(target->IsNPC()) + tmp = GetNPC(target->GetDatabaseID()); + else if(target->IsObject()) + tmp = GetObject(target->GetDatabaseID()); + else if(target->IsGroundSpawn()) + tmp = GetGroundSpawn(target->GetDatabaseID()); + else if(target->IsSign()) + tmp = GetSign(target->GetDatabaseID()); + else if(target->IsWidget()) + tmp = GetWidget(target->GetDatabaseID()); + if(tmp && type == SPAWN_SET_VALUE_SPAWN_SCRIPT) + tmp->SetSpawnScript(value); + else if(tmp) + commands.SetSpawnCommand(client, tmp, type, value); // set the master spawn + Spawn* spawn = 0; + + // this check needs to be here otherwise every spawn with 0 will be set + if ( target->GetDatabaseID ( ) > 0 ) + { + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + spawn = itr->second; + if(spawn && spawn->GetDatabaseID() == target->GetDatabaseID()){ + if(type == SPAWN_SET_VALUE_SPAWN_SCRIPT) + spawn->SetSpawnScript(value); + else + commands.SetSpawnCommand(client, spawn, type, value); + } + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + } +} + +void ZoneServer::StopSpawnScriptTimer(Spawn* spawn, std::string functionName){ + MSpawnScriptTimers.writelock(__FUNCTION__, __LINE__); + MRemoveSpawnScriptTimersList.writelock(__FUNCTION__, __LINE__); + if(spawn_script_timers.size() > 0){ + set::iterator itr; + SpawnScriptTimer* timer = 0; + for (itr = spawn_script_timers.begin(); itr != spawn_script_timers.end(); ) { + timer = *itr; + if(timer->spawn == spawn->GetID() && (functionName == "" || timer->function == functionName) && remove_spawn_script_timers_list.count(timer) == 0) { + itr = spawn_script_timers.erase(itr); + safe_delete(timer); + } + else + itr++; + } + } + MRemoveSpawnScriptTimersList.releasewritelock(__FUNCTION__, __LINE__); + MSpawnScriptTimers.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::DeleteSpawnScriptTimers(Spawn* spawn, bool all){ + MSpawnScriptTimers.writelock(__FUNCTION__, __LINE__); + MRemoveSpawnScriptTimersList.writelock(__FUNCTION__, __LINE__); + if(spawn_script_timers.size() > 0){ + set::iterator itr; + SpawnScriptTimer* timer = 0; + for (itr = spawn_script_timers.begin(); itr != spawn_script_timers.end(); itr++) { + timer = *itr; + if((all || timer->spawn == spawn->GetID()) && remove_spawn_script_timers_list.count(timer) == 0) + remove_spawn_script_timers_list.insert(timer); + } + + if(all) + spawn_script_timers.clear(); + } + MRemoveSpawnScriptTimersList.releasewritelock(__FUNCTION__, __LINE__); + MSpawnScriptTimers.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::DeleteSpawnScriptTimers() { + MSpawnScriptTimers.writelock(__FUNCTION__, __LINE__); + MRemoveSpawnScriptTimersList.writelock(__FUNCTION__, __LINE__); + if(remove_spawn_script_timers_list.size() > 0){ + set::iterator itr; + SpawnScriptTimer* timer = 0; + + for (itr = remove_spawn_script_timers_list.begin(); itr != remove_spawn_script_timers_list.end(); itr++) { + timer = *itr; + set::iterator itr2; + + itr2 = spawn_script_timers.find(timer); + + if(itr2 != spawn_script_timers.end()) + spawn_script_timers.erase(itr2); + + safe_delete(timer); + } + remove_spawn_script_timers_list.clear(); + } + MRemoveSpawnScriptTimersList.releasewritelock(__FUNCTION__, __LINE__); + MSpawnScriptTimers.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::CheckSpawnScriptTimers(){ + DeleteSpawnScriptTimers(); + SpawnScriptTimer* timer = 0; + vector call_timers; + MSpawnScriptTimers.readlock(__FUNCTION__, __LINE__); + MRemoveSpawnScriptTimersList.writelock(__FUNCTION__, __LINE__); + if(spawn_script_timers.size() > 0){ + int32 current_time = Timer::GetCurrentTime2(); + set::iterator itr; + for (itr = spawn_script_timers.begin(); itr != spawn_script_timers.end(); itr++) { + timer = *itr; + if(remove_spawn_script_timers_list.count(timer) == 0 && + timer->current_count < timer->max_count && current_time >= timer->timer){ + timer->current_count++; + SpawnScriptTimer tmpTimer; + tmpTimer.current_count = timer->current_count; + tmpTimer.function = timer->function; + tmpTimer.player = timer->player; + tmpTimer.spawn = timer->spawn; + tmpTimer.max_count = timer->max_count; + call_timers.push_back(tmpTimer); + } + if(timer->current_count >= timer->max_count && remove_spawn_script_timers_list.count(timer) == 0) + remove_spawn_script_timers_list.insert(timer); + } + } + MRemoveSpawnScriptTimersList.releasewritelock(__FUNCTION__, __LINE__); + MSpawnScriptTimers.releasereadlock(__FUNCTION__, __LINE__); + if(call_timers.size() > 0){ + vector::iterator itr; + for(itr = call_timers.begin(); itr != call_timers.end(); itr++){ + SpawnScriptTimer tmpTimer = (SpawnScriptTimer)*itr; + CallSpawnScript(GetSpawnByID(tmpTimer.spawn), SPAWN_SCRIPT_TIMER, tmpTimer.player > 0 ? GetSpawnByID(tmpTimer.player) : 0, tmpTimer.function.c_str()); + } + } +} + +void ZoneServer::KillSpawnByDistance(Spawn* spawn, float max_distance, bool include_players, bool send_packet){ + if(!spawn) + return; + + auto loc = glm::vec3(spawn->GetX(), spawn->GetZ(), spawn->GetY()); + std::vector grids_by_radius; + if(spawn->GetMap()) { + grids_by_radius = GetGridsByLocation(spawn, loc, max_distance); + } + else { + grids_by_radius.push_back(spawn->GetLocation()); + } + + Spawn* test_spawn = 0; + MGridMaps.lock_shared(); + std::vector::iterator grid_radius_itr; + for(grid_radius_itr = grids_by_radius.begin(); grid_radius_itr != grids_by_radius.end(); grid_radius_itr++) { + std::map::iterator grids = grid_maps.find((*grid_radius_itr)); + if(grids != grid_maps.end()) { + grids->second->MSpawns.lock_shared(); + typedef map SpawnMapType; + for( SpawnMapType::iterator it = grids->second->spawns.begin(); it != grids->second->spawns.end(); ++it ) { + test_spawn = it->second; + if(test_spawn && test_spawn->Alive() && test_spawn->GetID() > 0 && test_spawn->GetID() != spawn->GetID() && test_spawn->IsEntity() && + (!test_spawn->IsPlayer() || include_players)){ + if(test_spawn->GetDistance(spawn) < max_distance) + KillSpawn(true, test_spawn, spawn, send_packet); + } + } + grids->second->MSpawns.unlock_shared(); + } + } + MGridMaps.unlock_shared(); +} + +void ZoneServer::SpawnSetByDistance(Spawn* spawn, float max_distance, string field, string value){ + if(!spawn) + return; + + Spawn* test_spawn = 0; + int32 type = commands.GetSpawnSetType(field); + if(type == 0xFFFFFFFF) + return; + + auto loc = glm::vec3(spawn->GetX(), spawn->GetZ(), spawn->GetY()); + std::vector grids_by_radius; + if(spawn->GetMap()) { + grids_by_radius = GetGridsByLocation(spawn, loc, max_distance); + } + else { + grids_by_radius.push_back(spawn->GetLocation()); + } + + MGridMaps.lock_shared(); + std::vector::iterator grid_radius_itr; + for(grid_radius_itr = grids_by_radius.begin(); grid_radius_itr != grids_by_radius.end(); grid_radius_itr++) { + std::map::iterator grids = grid_maps.find((*grid_radius_itr)); + grids->second->MSpawns.lock_shared(); + typedef map SpawnMapType; + for( SpawnMapType::iterator it = grids->second->spawns.begin(); it != grids->second->spawns.end(); ++it ) { + test_spawn = it->second; + if(test_spawn && test_spawn->GetID() > 0 && test_spawn->GetID() != spawn->GetID() && !test_spawn->IsPlayer()){ + if(test_spawn->GetDistance(spawn) < max_distance){ + commands.SetSpawnCommand(0, test_spawn, type, value.c_str()); + } + } + } + grids->second->MSpawns.unlock_shared(); + } + MGridMaps.unlock_shared(); +} + +void ZoneServer::AddSpawnScriptTimer(SpawnScriptTimer* timer){ + MSpawnScriptTimers.writelock(__FUNCTION__, __LINE__); + spawn_script_timers.insert(timer); + MSpawnScriptTimers.releasewritelock(__FUNCTION__, __LINE__); +} + +/* +void ZoneServer::RemoveFromRangeMap(Client* client){ + spawn_range_map.erase(client); +} +*/ + +void ZoneServer::RemoveSpawn(Spawn* spawn, bool delete_spawn, bool respawn, bool lock, bool erase_from_spawn_list, bool lock_spell_process) +{ + LogWrite(ZONE__DEBUG, 3, "Zone", "Processing RemoveSpawn function for %s (%i)...", spawn->GetName(),spawn->GetID()); + + PacketStruct* packet = 0; + int16 packet_version = 0; + Client* client = 0; + + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + + if (client && (client->GetVersion() > 373 || !client->IsZoning() || client->GetPlayer() != spawn)) { //don't send destroy ghost of 283 client when zoning + if (client->GetPlayer()->HasTarget() && client->GetPlayer()->GetTarget() == spawn) + client->GetPlayer()->SetTarget(0); + if(client->GetPlayer()->WasSentSpawn(spawn->GetID()) || client->GetPlayer()->IsSendingSpawn(spawn->GetID())) + SendRemoveSpawn(client, spawn, packet, delete_spawn); + if (spawn_range_map.count(client) > 0) + spawn_range_map.Get(client)->erase(spawn->GetID()); + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + + safe_delete(packet); + + spawn->RemoveSpawnProximities(); + RemoveSpawnProximities(spawn); + + if (movementMgr != nullptr && spawn->IsEntity()) { + movementMgr->RemoveMob((Entity*)spawn); + } + + RemoveSpawnSupportFunctions(spawn, lock_spell_process); + if (reloading) + RemoveDeadEnemyList(spawn); + + if (lock) + MDeadSpawns.writelock(__FUNCTION__, __LINE__); + + if (dead_spawns.count(spawn->GetID()) > 0) + dead_spawns.erase(spawn->GetID()); + if (lock) + MDeadSpawns.releasewritelock(__FUNCTION__, __LINE__); + + if (spawn_expire_timers.count(spawn->GetID()) > 0) + spawn_expire_timers.erase(spawn->GetID()); + + spawn->SetDeletedSpawn(true); + + // we will remove the spawn ptr and entry in the spawn_list later.. it is not safe right now (lua? client process? spawn process? etc? too many factors) + if(erase_from_spawn_list) + AddPendingSpawnRemove(spawn->GetID()); + + if(respawn && !spawn->IsPlayer() && spawn->GetRespawnTime() > 0 && spawn->GetSpawnLocationID() > 0) + { + LogWrite(ZONE__DEBUG, 3, "Zone", "Handle NPC Respawn for '%s'.", spawn->GetName()); + + // handle instance spawn db info + // we don't care if a NPC or a client kills the spawn, we could have events that cause NPCs to kill NPCs. + if(spawn->GetZone()->GetInstanceID() > 0 && spawn->GetSpawnLocationID() > 0) + { + LogWrite(ZONE__DEBUG, 3, "Zone", "Handle NPC Respawn in an Instance."); + // use respawn time to either insert/update entry (likely insert in this situation) + if ( spawn->IsNPC() ) + { + database.CreateInstanceSpawnRemoved(spawn->GetSpawnLocationID(),SPAWN_ENTRY_TYPE_NPC, + spawn->GetRespawnTime(),spawn->GetZone()->GetInstanceID()); + } + else if ( spawn->IsObject ( ) ) + { + database.CreateInstanceSpawnRemoved(spawn->GetSpawnLocationID(),SPAWN_ENTRY_TYPE_OBJECT, + spawn->GetRespawnTime(),spawn->GetZone()->GetInstanceID()); + } + } + else + { + int32 spawnLocationID = spawn->GetSpawnLocationID(); + int32 spawnRespawnTime = spawn->GetRespawnTime(); + respawn_timers.Put(spawnLocationID, Timer::GetCurrentTime2() + spawnRespawnTime * 1000); + } + } + + RemoveSpawnFromGrid(spawn, spawn->GetLocation()); + + // Do we really need the mutex locks and check to dead_spawns as we remove it from dead spawns at the start of this function + if (lock && !respawn) + MDeadSpawns.readlock(__FUNCTION__, __LINE__); + if(delete_spawn && dead_spawns.count(spawn->GetID()) == 0) + AddPendingDelete(spawn); + if (lock && !respawn) + MDeadSpawns.releasereadlock(__FUNCTION__, __LINE__); + + LogWrite(ZONE__DEBUG, 3, "Zone", "Done processing RemoveSpawn function..."); +} + +Spawn* ZoneServer::GetClosestSpawn(Spawn* spawn, int32 spawn_id){ + Spawn* closest_spawn = 0; + Spawn* test_spawn = 0; + float closest_distance = 1000000; + float test_distance = 0; + + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + test_spawn = itr->second; + if(test_spawn && test_spawn != spawn && test_spawn->GetDatabaseID() == spawn_id){ + test_distance = test_spawn->GetDistance(spawn); + if(test_distance < closest_distance){ + closest_distance = test_distance; + closest_spawn = test_spawn; + } + } + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + + return closest_spawn; +} + +int32 ZoneServer::GetClosestLocation(Spawn* spawn){ + Spawn* closest_spawn = 0; + Spawn* test_spawn = 0; + float closest_distance = 1000000; + float test_distance = 0; + + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + test_spawn = itr->second; + if(test_spawn){ + test_distance = test_spawn->GetDistance(spawn); + if(test_distance < closest_distance){ + closest_distance = test_distance; + closest_spawn = test_spawn; + if(closest_distance < 10) + break; + } + } + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + + if(closest_spawn) + return closest_spawn->GetLocation(); + return 0; +} + +void ZoneServer::SendQuestUpdates(Client* client, Spawn* spawn){ + if(!client) + return; + + if(spawn){ + if(client->GetPlayer()->WasSentSpawn(spawn->GetID())) + SendSpawnChanges(spawn, client, false, true); + } + else{ + client->GetCurrentZone()->SendAllSpawnsForVisChange(client); + } +} + +void ZoneServer::SendAllSpawnsForLevelChange(Client* client) { + Spawn* spawn = 0; + if (spawn_range_map.count(client) > 0) { + MutexMap::iterator itr = spawn_range_map.Get(client)->begin(); + while (itr.Next()) { + spawn = GetSpawnByID(itr->first); + if (spawn && client->GetPlayer()->WasSentSpawn(spawn->GetID())) { + SendSpawnChanges(spawn, client, false, true); + // Attempt to slow down the packet spam sent to the client + // who the bloody fuck put a Sleep here + //Sleep(5); + } + } + } +} + + +void ZoneServer::SendAllSpawnsForSeeInvisChange(Client* client) { + Spawn* spawn = 0; + if (spawn_range_map.count(client) > 0) { + MutexMap::iterator itr = spawn_range_map.Get(client)->begin(); + while (itr.Next()) { + spawn = GetSpawnByID(itr->first); + if (spawn && spawn->IsEntity() && (((Entity*)spawn)->IsInvis() || ((Entity*)spawn)->IsStealthed()) && client->GetPlayer()->WasSentSpawn(spawn->GetID())) { + SendSpawnChanges(spawn, client, true, true); + } + } + } +} + + +void ZoneServer::SendAllSpawnsForVisChange(Client* client, bool limitToEntities) { + Spawn* spawn = 0; + if (spawn_range_map.count(client) > 0) { + MutexMap::iterator itr = spawn_range_map.Get(client)->begin(); + while (itr.Next()) { + spawn = GetSpawnByID(itr->first); + if (spawn && (!limitToEntities || (limitToEntities && spawn->IsEntity())) && client->GetPlayer()->WasSentSpawn(spawn->GetID())) { + SendSpawnChanges(spawn, client, false, true); + } + } + } +} + +void ZoneServer::StartZoneSpawnsForLevelThread(Client* client){ + if(zoneShuttingDown) + return; + +#ifdef WIN32 + _beginthread(SendLevelChangedSpawns, 0, client); +#else + pthread_t thread; + pthread_create(&thread, NULL, SendLevelChangedSpawns, client); + pthread_detach(thread); +#endif +} + +void ZoneServer::ReloadClientQuests(){ + Client* client = 0; + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(client) + client->ReloadQuests(); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::SendCalculatedXP(Player* player, Spawn* victim){ + if (player && victim) { + if (player->GetGroupMemberInfo()) { + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + + PlayerGroup* group = world.GetGroupManager()->GetGroup(player->GetGroupMemberInfo()->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + deque::iterator itr; + bool skipGrayMob = false; + + for (itr = members->begin(); itr != members->end(); itr++) { + GroupMemberInfo* gmi = *itr; + if (gmi->client) { + Player* group_member = gmi->client->GetPlayer(); + if(group_member && group_member->GetArrowColor(victim->GetLevel()) == ARROW_COLOR_GRAY) { + skipGrayMob = true; + break; + } + } + } + + for (itr = members->begin(); !skipGrayMob && itr != members->end(); itr++) { + GroupMemberInfo* gmi = *itr; + if (gmi->client) { + Player* group_member = gmi->client->GetPlayer(); + if(group_member) { + float xp = group_member->CalculateXP(victim) / members->size(); + if (xp > 0) { + group_member->AddXP((int32)xp); + } + } + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } + else { + float xp = player->CalculateXP(victim); + if (xp > 0) { + Client* client = ((Player*)player)->GetClient(); + if(!client) + return; + player->AddXP((int32)xp); + } + } + } +} + +void ZoneServer::ProcessFaction(Spawn* spawn, Client* client) +{ + if(client && !spawn->IsPlayer() && spawn->GetFactionID() > 10) + { + bool update_result = false; + Faction* faction = 0; + vector* factions = 0; + Player* player = client->GetPlayer(); + + bool hasfaction = database.VerifyFactionID(player->GetCharacterID(), spawn->GetFactionID()); + //if 0 they dont have an entry in the db for this faction. if one they do and we can skip it. + if(hasfaction == 0) { + //Find out the default for this faction + sint32 defaultfaction = master_faction_list.GetDefaultFactionValue(spawn->GetFactionID()); + //add the default faction for the player. + player->SetFactionValue(spawn->GetFactionID(), defaultfaction); + //save the character so the new default gets written to the db. + client->Save(); + } + + if(player->GetFactions()->ShouldDecrease(spawn->GetFactionID())) + { + + update_result = player->GetFactions()->DecreaseFaction(spawn->GetFactionID()); + faction = master_faction_list.GetFaction(spawn->GetFactionID()); + + if(faction && update_result) + client->Message(CHANNEL_FACTION, "Your faction standing with %s got worse.", faction->name.c_str()); + else if(faction) + client->Message(CHANNEL_FACTION, "Your faction standing with %s could not possibly get any worse.", faction->name.c_str()); + + factions = master_faction_list.GetHostileFactions(spawn->GetFactionID()); + + if(factions) + { + vector::iterator itr; + + for(itr = factions->begin(); itr != factions->end(); itr++) + { + if(player->GetFactions()->ShouldIncrease(*itr)) + { + + update_result = player->GetFactions()->IncreaseFaction(*itr); + faction = master_faction_list.GetFaction(*itr); + + if(faction && update_result) + client->Message(CHANNEL_FACTION, "Your faction standing with %s got better.", faction->name.c_str()); + else if(faction) + client->Message(CHANNEL_FACTION, "Your faction standing with %s could not possibly get any better.", faction->name.c_str()); + } + } + } + } + + factions = master_faction_list.GetFriendlyFactions(spawn->GetFactionID()); + + if(factions) + { + vector::iterator itr; + + for(itr = factions->begin(); itr != factions->end(); itr++) + { + if(player->GetFactions()->ShouldDecrease(*itr)) + { + bool hasfaction = database.VerifyFactionID(player->GetCharacterID(),spawn->GetFactionID()); + if(hasfaction == 0) { + //they do not have the faction. Lets get the default value and feed it in. + sint32 defaultfaction = master_faction_list.GetDefaultFactionValue(spawn->GetFactionID()); + //add the default faction for the player. + player->SetFactionValue(spawn->GetFactionID(), defaultfaction); + } + + update_result = player->GetFactions()->DecreaseFaction(*itr); + faction = master_faction_list.GetFaction(*itr); + + if(faction && update_result) + client->Message(CHANNEL_FACTION, "Your faction standing with %s got worse.", faction->name.c_str()); + else if(faction) + client->Message(CHANNEL_FACTION, "Your faction standing with %s could not possibly get any worse.", faction->name.c_str()); + } + } + } + + EQ2Packet* outapp = client->GetPlayer()->GetFactions()->FactionUpdate(client->GetVersion()); + + if(outapp) + client->QueuePacket(outapp); + } +} + +void ZoneServer::Despawn(Spawn* spawn, int32 timer){ + if (spawn && movementMgr != nullptr) { + movementMgr->RemoveMob((Entity*)spawn); + } + if(!spawn || spawn->IsPlayer()) + return; + if(spawn->IsEntity()) + ((Entity*)spawn)->InCombat(false); + if(timer == 0) + timer = 1; + AddDeadSpawn(spawn, timer); +} + +void ZoneServer::KillSpawn(bool spawnListLocked, Spawn* dead, Spawn* killer, bool send_packet, int8 type, int8 damage_type, int16 kill_blow_type) +{ + bool isSpell = (type == DAMAGE_PACKET_TYPE_SIPHON_SPELL || type == DAMAGE_PACKET_TYPE_SIPHON_SPELL2 || + type == DAMAGE_PACKET_TYPE_SPELL_DAMAGE || type == DAMAGE_PACKET_TYPE_SPELL_CRIT_DMG || + type == DAMAGE_PACKET_TYPE_SPELL_DAMAGE2 || type == DAMAGE_PACKET_TYPE_SPELL_DAMAGE3); + + MDeadSpawns.readlock(__FUNCTION__, __LINE__); + if(!dead || this->dead_spawns.count(dead->GetID()) > 0) { + MDeadSpawns.releasereadlock(__FUNCTION__, __LINE__); + return; + } + MDeadSpawns.releasereadlock(__FUNCTION__, __LINE__); + + PacketStruct* packet = 0; + Client* client = 0; + vector* encounter = 0; + int32 encounter_player_bot_count = 1; + bool killer_in_encounter = false; + int8 loot_state = dead->GetLockedNoLoot(); + + if(dead->IsEntity()) + { + // add any special quest related loot (no_drop_quest_completed) + if(dead->IsNPC() && ((NPC*)dead)->Brain()) { + if(!((NPC*)dead)->Brain()->PlayerInEncounter() || (loot_state != ENCOUNTER_STATE_LOCKED && loot_state != ENCOUNTER_STATE_OVERMATCHED)) { + LogWrite(LOOT__DEBUG, 0, "Loot", "NPC %s bypassed loot drop due to no player in encounter, or encounter state not locked.", ((NPC*)dead)->GetName()); + } + else { + Entity* hated = ((NPC*)dead)->Brain()->GetMostHated(); + if(hated) { + GroupLootMethod loot_method = GroupLootMethod::METHOD_FFA; + int8 item_rarity = 0; + if(hated->GetGroupMemberInfo()) { + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + PlayerGroup* group = world.GetGroupManager()->GetGroup(hated->GetGroupMemberInfo()->group_id); + if (group) { + loot_method = (GroupLootMethod)group->GetGroupOptions()->loot_method; + item_rarity = group->GetGroupOptions()->loot_items_rarity; + LogWrite(LOOT__DEBUG, 0, "Loot", "%s: Loot method set to %u.", dead->GetName(), loot_method); + } + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } + AddLoot((NPC*)dead, hated, loot_method, item_rarity, hated->GetGroupMemberInfo() ? hated->GetGroupMemberInfo()->group_id : 0); + } + } + } + ((Entity*)dead)->InCombat(false); + dead->SetInitialState(16512, false); // This will make aerial npc's fall after death + dead->SetHP(0); + dead->SetSpawnType(3); + dead->appearance.attackable = 0; + + + // Remove hate towards dead from all npc's in the zone + ClearHate((Entity*)dead); + + // Check kill and death procs + if (killer && dead != killer){ + if (dead->IsEntity()) + ((Entity*)dead)->CheckProcs(PROC_TYPE_DEATH, killer); + if (killer->IsEntity()) + ((Entity*)killer)->CheckProcs(PROC_TYPE_KILL, dead); + } + + //Check if caster is alive after death proc called, incase of deathsave + if (dead->Alive()) + return; + + RemoveSpellTimersFromSpawn(dead, true, !dead->IsPlayer(), true, !isSpell); + ((Entity*)dead)->IsCasting(false); + + if(dead->IsPlayer()) + { + ((Player*)dead)->UpdatePlayerStatistic(STAT_PLAYER_TOTAL_DEATHS, 1); + client = ((Player*)dead)->GetClient(); + + ((Entity*)dead)->HandleDeathExperienceDebt(killer); + + if(client) { + + if(client->GetPlayer()->DamageEquippedItems(10, client)) + client->QueuePacket(client->GetPlayer()->GetEquipmentList()->serialize(client->GetVersion(), client->GetPlayer())); + + client->DisplayDeadWindow(); + } + } + else if (dead->IsNPC()) { + encounter = ((NPC*)dead)->Brain()->GetEncounter(); + encounter_player_bot_count = ((NPC*)dead)->Brain()->CountPlayerBotInEncounter(); + if(encounter_player_bot_count < 1) + encounter_player_bot_count = 1; + } + + } + + dead->SetActionState(0); + dead->SetTempActionState(0); + + // Needs npc to have access to the encounter list for who is allowed to loot + NPC* chest = 0; + + if (dead->IsNPC() && !((NPC*)dead)->Brain()->PlayerInEncounter()) { + dead->SetLootCoins(0); + dead->ClearLoot(); + } + + Spawn* groupMemberAlive = nullptr; + // If dead has loot attempt to drop a chest + if (dead->HasLoot()) { + if(!(groupMemberAlive = dead->IsSpawnGroupMembersAlive(dead))) { + chest = ((Entity*)dead)->DropChest(); + } + else { + switch(dead->GetLootDropType()) { + case 0: + // default drop all chest type as a group + dead->TransferLoot(groupMemberAlive); + break; + case 1: + // this is a primary mob it drops its own loot + chest = ((Entity*)dead)->DropChest(); + break; + } + } + } + // If dead is an npc get the encounter and loop through it giving out the rewards, no rewards for pets + if (dead->IsNPC() && !dead->IsPet() && !dead->IsBot()) { + Spawn* spawn = 0; + int8 size = encounter->size(); + + for (int8 i = 0; i < encounter->size(); i++) { + spawn = GetSpawnByID(encounter->at(i), spawnListLocked); + // set a flag to let us know if the killer is in the encounter + if (!killer_in_encounter && spawn == killer) + killer_in_encounter = true; + + if (spawn && spawn->IsPlayer()) { + // Update players total kill count + ((Player*)spawn)->UpdatePlayerStatistic(STAT_PLAYER_TOTAL_NPC_KILLS, 1); + + // If this was an epic mob kill send the announcement for this player + if (dead->GetDifficulty() >= 10) + SendEpicMobDeathToGuild((Player*)spawn, dead); + + // Clear hostile spells from the players spell queue + spellProcess->RemoveSpellFromQueue((Player*)spawn, true); + + // Get the client of the player + client = ((Player*)spawn)->GetClient(); + // valid client? + if (client) { + // Check for quest kill updates + if(!dead->IsNPC() || loot_state != ENCOUNTER_STATE_BROKEN) { + client->CheckPlayerQuestsKillUpdate(dead); + } + // If the dead mob is not a player and if it had a faction with an ID greater or equal to 10 the send faction changes + if (!dead->IsPlayer() && dead->GetFactionID() > 10) + ProcessFaction(dead, client); + + // Send xp...this is currently wrong fix it + if (spawn != dead && ((Player*)spawn)->GetArrowColor(dead->GetLevel()) >= ARROW_COLOR_GREEN) { + //SendCalculatedXP((Player*)spawn, dead); + + float xp = ((Player*)spawn)->CalculateXP(dead) / encounter_player_bot_count; + if (xp > 0) { + ((Player*)spawn)->AddXP((int32)xp); + } + } + } + } + + // If a chest is being dropped add this spawn to the chest's encounter so they can loot it + if (chest && spawn && spawn->IsEntity()) + chest->Brain()->AddToEncounter((Entity*)spawn); + } + } + + // If a chest is being dropped add it to the world and set the timer to remove it. + if (chest) { + AddSpawn(chest); + AddDeadSpawn(chest, 0xFFFFFFFF); + LogWrite(LOOT__DEBUG, 0, "Loot", "Adding a chest to the world..."); + } + + // Reset client pointer + client = 0; + + // Killer was not in the encounter, give them the faction hit but no xp + if (!killer_in_encounter) { + // make sure the killer is a player and the dead spawn had a faction and wasn't a player + if (killer && killer->IsPlayer()) { + if (!dead->IsPlayer() && dead->GetFactionID() > 10) { + client = ((Player*)killer)->GetClient(); + if (client) + ProcessFaction(dead, client); + } + + // Clear hostile spells from the killers spell queue + spellProcess->RemoveSpellFromQueue((Player*)killer, true); + } + } + + // Reset client pointer + client = 0; + + + vector* group = dead->GetSpawnGroup(); + if (group && group->size() == 1) + CallSpawnScript(dead, SPAWN_SCRIPT_GROUP_DEAD, killer); + safe_delete(group); + + + // Remove the support functions for the dead spawn + RemoveSpawnSupportFunctions(dead, !isSpell); + + // Erase the expire timer if it has one + if (spawn_expire_timers.count(dead->GetID()) > 0) + spawn_expire_timers.erase(dead->GetID()); + + // If dead is an npc or object call the spawn scrip and handle instance stuff + if(dead->IsNPC() || dead->IsObject()) + { + // handle instance spawn db info + // we don't care if a NPC or a client kills the spawn, we could have events that cause NPCs to kill NPCs. + if(dead->GetZone()->GetInstanceID() > 0 && dead->GetSpawnLocationID() > 0) + { + // use respawn time to either insert/update entry (likely insert in this situation) + if(dead->IsNPC()) + database.CreateInstanceSpawnRemoved(dead->GetSpawnLocationID(),SPAWN_ENTRY_TYPE_NPC, dead->GetRespawnTime(),dead->GetZone()->GetInstanceID()); + else if ( dead->IsObject ( ) ) + database.CreateInstanceSpawnRemoved(dead->GetSpawnLocationID(),SPAWN_ENTRY_TYPE_OBJECT, dead->GetRespawnTime(),dead->GetZone()->GetInstanceID()); + } + + // Call the spawn scripts death() function + CallSpawnScript(dead, SPAWN_SCRIPT_DEATH, killer); + const char* zone_script = world.GetZoneScript(this->GetZoneID()); + if (zone_script && lua_interface) + lua_interface->RunZoneScript(zone_script, "spawn_killed", this, dead, 0, 0, killer); + } + + int32 victim_id = dead->GetID(); + int32 attacker_id = 0xFFFFFFFF; + + if(killer) + attacker_id = killer->GetID(); + + if(send_packet) + { + vector::iterator client_itr; + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(!client->GetPlayer()->WasSentSpawn(victim_id) || (attacker_id != 0xFFFFFFFF && !client->GetPlayer()->WasSentSpawn(attacker_id)) ) + continue; + else if(killer && killer->GetDistance(client->GetPlayer()) > HEAR_SPAWN_DISTANCE) + continue; + + packet = configReader.getStruct("WS_HearDeath", client->GetVersion()); + if(packet) + { + if(killer) + packet->setDataByName("attacker", client->GetPlayer()->GetIDWithPlayerSpawn(killer)); + else + packet->setDataByName("attacker", 0xFFFFFFFF); + + packet->setDataByName("defender", client->GetPlayer()->GetIDWithPlayerSpawn(dead)); + packet->setDataByName("damage_type", damage_type); + packet->setDataByName("blow_type", kill_blow_type); + + client->QueuePacket(packet->serialize()); + LogWrite(COMBAT__DEBUG, 0, "Combat", "Zone Killing of '%s' by '%s' damage type %u, blow type %u", dead->GetName(), killer ? killer->GetName() : "", damage_type, kill_blow_type); + safe_delete(packet); + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + } + + + int32 pop_timer = 0xFFFFFFFF; + if(killer && killer->IsNPC()) + { + // Call the spawn scripts killed() function + CallSpawnScript(killer, SPAWN_SCRIPT_KILLED, dead); + + if(!dead->IsPlayer()) + { + LogWrite(MISC__TODO, 1, "TODO", "Whenever pets are added, check for pet kills\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__); + // Set the time for the corpse to linger to 5 sec + //pop_timer = 5000; + // commented out the timer so in the event the killer is not a player (pet, guard, something else i haven't thought of) + // the corpse doesn't vanish sooner then it should if it had loot for the players. AddDeadSpawn() will set timers based on if + // the corpse has loot or not if the timer value (pop_timer) is 0xFFFFFFFF + } + } + + // If the dead spawns was not a player add it to the dead spawn list + if (!dead->IsPlayer() && !dead->IsBot()) + AddDeadSpawn(dead, pop_timer); + + // if dead was a player clear hostile spells from its spell queue + if (dead->IsPlayer()) + spellProcess->RemoveSpellFromQueue((Player*)dead, true); + + if (dead->IsNPC()) + ((NPC*)dead)->Brain()->ClearHate(); + + safe_delete(encounter); +} + +void ZoneServer::SendDamagePacket(Spawn* attacker, Spawn* victim, int8 type1, int8 type2, int8 damage_type, int16 damage, const char* spell_name) { + //Scat: was set but never being used anywhere. i see references to 0xFFFFFFFF below so could be old code not used anymore + //int32 attacker_id = 0xFFFFFFFF; + //if(attacker) + // attacker_id = attacker->GetID(); + PacketStruct* packet = 0; + Client* client = 0; + if (attacker && victim && victim->IsPlayer() && victim->GetTarget() == 0) { + client = ((Player*)victim)->GetClient(); + if (client) + client->TargetSpawn(attacker); + } + + if(damage_type == DAMAGE_PACKET_DAMAGE_TYPE_FOCUS) { + damage_type = 0; + type2 = DAMAGE_PACKET_RESULT_FOCUS; + } + + vector::iterator client_itr; + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if (!client || (client->GetPlayer() != attacker && client->GetPlayer() != victim && ((attacker && client->GetPlayer()->WasSentSpawn(attacker->GetID()) == false) || (victim && client->GetPlayer()->WasSentSpawn(victim->GetID()) == false)))) + continue; + if (attacker && attacker->GetDistance(client->GetPlayer()) > 50) + continue; + if (victim && victim->GetDistance(client->GetPlayer()) > 50) + continue; + + int8 mod_type1 = type1; + if(client->GetVersion() <= 561) { + mod_type1 = DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE; + } + + switch (mod_type1) { + case DAMAGE_PACKET_TYPE_SIPHON_SPELL: + case DAMAGE_PACKET_TYPE_SIPHON_SPELL2: + packet = configReader.getStruct("WS_HearSiphonSpellDamage", client->GetVersion()); + break; + case DAMAGE_PACKET_TYPE_MULTIPLE_DAMAGE: + if (client->GetVersion() > 561) + packet = configReader.getStruct("WS_HearMultipleDamage", client->GetVersion()); + else + packet = configReader.getStruct("WS_HearSimpleDamage", client->GetVersion()); + break; + case DAMAGE_PACKET_TYPE_SIMPLE_CRIT_DMG: + case DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE: + packet = configReader.getStruct("WS_HearSimpleDamage", client->GetVersion()); + break; + case DAMAGE_PACKET_TYPE_SPELL_DAMAGE2: + case DAMAGE_PACKET_TYPE_SPELL_DAMAGE3: + case DAMAGE_PACKET_TYPE_SPELL_CRIT_DMG: + case DAMAGE_PACKET_TYPE_SPELL_DAMAGE: + if (client->GetVersion() > 561) + packet = configReader.getStruct("WS_HearSpellDamage", client->GetVersion()); + else + packet = configReader.getStruct("WS_HearSimpleDamage", client->GetVersion()); + if (packet) + packet->setSubstructDataByName("header", "unknown", 5); + break; + case DAMAGE_PACKET_TYPE_RANGE_DAMAGE: + packet = configReader.getStruct("WS_HearRangeDamage", client->GetVersion()); + break; + case DAMAGE_PACKET_TYPE_RANGE_SPELL_DMG: + case DAMAGE_PACKET_TYPE_RANGE_SPELL_DMG2: + packet = configReader.getStruct("WS_HearRangeDamage", client->GetVersion()); + break; + default: + LogWrite(ZONE__ERROR, 0, "Zone", "Unknown Damage Packet type: %i in ZoneServer::SendDamagePacket.", type1); + MClientList.releasereadlock(__FUNCTION__, __LINE__); + return; + } + + if (packet) { + if (client->GetVersion() > 561) { + packet->setSubstructDataByName("header", "packet_type", type1); + packet->setSubstructDataByName("header", "result_type", type2); + packet->setDataByName("damage_type", damage_type); + packet->setDataByName("damage", damage); + } + else { + switch (type2) { + case DAMAGE_PACKET_RESULT_MISS: + packet->setSubstructDataByName("header", "result_type", 1); + break; + case DAMAGE_PACKET_RESULT_DODGE: + packet->setSubstructDataByName("header", "result_type", 2); + break; + case DAMAGE_PACKET_RESULT_PARRY: + packet->setSubstructDataByName("header", "result_type", 3); + break; + case DAMAGE_PACKET_RESULT_RIPOSTE: + packet->setSubstructDataByName("header", "result_type", 4); + break; + case DAMAGE_PACKET_RESULT_BLOCK: + packet->setSubstructDataByName("header", "result_type", 5); + break; + case DAMAGE_PACKET_RESULT_INVULNERABLE: + packet->setSubstructDataByName("header", "result_type", 7); + break; + case DAMAGE_PACKET_RESULT_RESIST: + packet->setSubstructDataByName("header", "result_type", 9); + break; + case DAMAGE_PACKET_RESULT_REFLECT: + packet->setSubstructDataByName("header", "result_type", 10); + break; + case DAMAGE_PACKET_RESULT_IMMUNE: + packet->setSubstructDataByName("header", "result_type", 11); + break; + } + packet->setArrayLengthByName("num_dmg", 1); + packet->setSubstructDataByName("header", "defender_proxy", client->GetPlayer()->GetIDWithPlayerSpawn(victim)); + packet->setArrayDataByName("damage_type", damage_type); + packet->setArrayDataByName("damage", damage); + } + + if (!attacker) + packet->setSubstructDataByName("header", "attacker", 0xFFFFFFFF); + else + packet->setSubstructDataByName("header", "attacker", client->GetPlayer()->GetIDWithPlayerSpawn(attacker)); + packet->setSubstructDataByName("header", "defender", client->GetPlayer()->GetIDWithPlayerSpawn(victim)); + if (spell_name) { + packet->setDataByName("spell", 1); + packet->setDataByName("spell_name", spell_name); + } + EQ2Packet* app = packet->serialize(); + //DumpPacket(app); + client->QueuePacket(app); + safe_delete(packet); + packet = 0; + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::SendHealPacket(Spawn* caster, Spawn* target, int16 heal_type, int32 heal_amt, const char* spell_name){ + Client* client = 0; + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(!client || (client->GetPlayer() != caster && ((caster && client->GetPlayer()->WasSentSpawn(caster->GetID()) == false) || (target && client->GetPlayer()->WasSentSpawn(target->GetID()) == false)))) + continue; + if(caster && caster->GetDistance(client->GetPlayer()) > 50) + continue; + if(target && target->GetDistance(client->GetPlayer()) > 50) + continue; + + + PacketStruct* packet = configReader.getStruct("WS_HearHeal", client->GetVersion()); + if (packet) { + packet->setDataByName("caster", client->GetPlayer()->GetIDWithPlayerSpawn(caster)); + packet->setDataByName("target", client->GetPlayer()->GetIDWithPlayerSpawn(target)); + packet->setDataByName("heal_amt", heal_amt); + packet->setDataByName("spellname", spell_name); + packet->setDataByName("type", heal_type); + packet->setDataByName("unknown2", 1); + EQ2Packet* app = packet->serialize(); + client->QueuePacket(app); + safe_delete(packet); + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::SendThreatPacket(Spawn* caster, Spawn* target, int32 threat_amt, const char* spell_name) { + Client* client = 0; + + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(!client || (client->GetPlayer() != caster && ((caster && client->GetPlayer()->WasSentSpawn(caster->GetID()) == false) || (target && client->GetPlayer()->WasSentSpawn(target->GetID()) == false)))) + continue; + if(caster && caster->GetDistance(client->GetPlayer()) > 50) + continue; + if(target && target->GetDistance(client->GetPlayer()) > 50) + continue; + + if(client->GetVersion() <= 561) { + int8 channel = 46; + + if(client->GetPlayer() == caster || client->GetPlayer() == target) + channel = 42; + + client->Message(channel, "%s increases %s hate with %s for %u threat.", spell_name, (client->GetPlayer() == caster) ? "YOUR" : caster->GetName(), (client->GetPlayer() == target) ? "YOU" : target->GetName(), threat_amt); + } + else { + PacketStruct* packet = configReader.getStruct("WS_HearThreatCmd", client->GetVersion()); + if (packet) { + packet->setDataByName("spell_name", spell_name); + packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(caster)); + packet->setDataByName("target", client->GetPlayer()->GetIDWithPlayerSpawn(target)); + packet->setDataByName("threat_amount", threat_amt); + + client->QueuePacket(packet->serialize()); + } + safe_delete(packet); + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + + +void ZoneServer::SendYellPacket(Spawn* yeller, float max_distance) { + Client* client = 0; + + + string yellMsg = std::string(yeller->GetName()) + " yelled for help!"; + vector::iterator client_itr; + PacketStruct* packet = nullptr; + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(!client || !client->GetPlayer() || client->GetPlayer()->WasSentSpawn(yeller->GetID()) == false) + continue; + if(client->GetPlayer()->GetDistance(yeller) > max_distance) + continue; + + if(packet && packet->GetVersion() == client->GetVersion()) { + client->QueuePacket(packet->serialize()); + } + else { + safe_delete(packet); + packet = configReader.getStruct("WS_EncounterBroken", client->GetVersion()); + if (packet) { + packet->setDataByName("message", yellMsg.c_str()); + /* none of the other data seems necessary, keeping for reference for future disassembly + packet2->setDataByName("unknown2", 0x40); + packet2->setDataByName("unknown3", 0x40); + packet2->setDataByName("unknown4", 0xFF); + packet2->setDataByName("unknown5", 0xFF); + packet2->setDataByName("unknown6", 0xFF);*/ + client->QueuePacket(packet->serialize()); + } + } + } + safe_delete(packet); + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::SendSpellFailedPacket(Client* client, int16 error){ + if(!client) + return; + + PacketStruct* packet = configReader.getStruct("WS_DisplaySpellFailed", client->GetVersion()); + if(packet){ + /* Temp solution, need to modify the error code before this function and while we still have access to the spell/combat art */ + error = master_spell_list.GetSpellErrorValue(client->GetVersion(), error); + + if(client->GetVersion() > 373 && client->GetVersion() <= 561 && error) { + error += 1; + } + + packet->setDataByName("error_code", error); + //packet->PrintPacket(); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } +} + +void ZoneServer::SendInterruptPacket(Spawn* interrupted, LuaSpell* spell, bool fizzle){ + if(!interrupted || !spell) + return; + + EQ2Packet* outapp = 0; + PacketStruct* packet = 0; + Client* client = 0; + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(!client || !client->GetPlayer()->WasSentSpawn(interrupted->GetID())) + continue; + packet = configReader.getStruct(fizzle ? "WS_SpellFizzle" : "WS_Interrupt", client->GetVersion()); + if(packet){ + packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(interrupted)); + packet->setArrayLengthByName("num_targets", spell->targets.size()); + for (int32 i = 0; i < spell->targets.size(); i++) + packet->setArrayDataByName("target_id", client->GetPlayer()->GetIDWithPlayerSpawn(client->GetPlayer()->GetZone()->GetSpawnByID(spell->targets[i])), i); + packet->setDataByName("spell_id", spell->spell->GetSpellID()); + outapp = packet->serialize(); + client->QueuePacket(outapp); + safe_delete(packet); + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + safe_delete(packet); +} + +void ZoneServer::SendCastSpellPacket(LuaSpell* spell, Entity* caster, int32 spell_visual_override, int16 casttime_override){ + EQ2Packet* outapp = 0; + PacketStruct* packet = 0; + Client* client = 0; + if(!caster || !spell || !spell->spell || spell->interrupted) + return; + + if(spell->is_damage_spell && (!spell->has_damaged || spell->resisted)) { + // we did not successfully hit target, so we should not send the visual + return; + } + + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(!client) + continue; + + packet = configReader.getStruct("WS_HearCastSpell", client->GetVersion()); + if(packet){ + int32 caster_id = client->GetPlayer()->GetIDWithPlayerSpawn(caster); + + if(!caster_id) { + safe_delete(packet); + continue; + } + + packet->setDataByName("spawn_id", caster_id); + packet->setArrayLengthByName("num_targets", spell->targets.size()); + for (int32 i = 0; i < spell->targets.size(); i++) { + int32 target_id = client->GetPlayer()->GetIDWithPlayerSpawn(spell->caster->GetZone()->GetSpawnByID(spell->targets[i])); + if(target_id) { + packet->setArrayDataByName("target", target_id, i); + } + else { + packet->setArrayDataByName("target", 0xFFFFFFFF, i); + } + } + + int32 visual_to_use = spell_visual_override > 0 ? spell_visual_override : spell->spell->GetSpellData()->spell_visual; + int32 visual = client->GetSpellVisualOverride(visual_to_use); + + packet->setDataByName("spell_visual", visual); //result + if(casttime_override != 0xFFFF) { + packet->setDataByName("cast_time", casttime_override*.01f); //delay + } + else { + packet->setDataByName("cast_time", spell->spell->GetSpellData()->cast_time*.01f); //delay + } + packet->setDataByName("spell_id", spell->spell->GetSpellID()); + packet->setDataByName("spell_level", 1); + packet->setDataByName("spell_tier", spell->spell->GetSpellData()->tier); + outapp = packet->serialize(); + client->QueuePacket(outapp); + safe_delete(packet); + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + safe_delete(packet); +} + +void ZoneServer::SendCastSpellPacket(int32 spell_visual, Spawn* target, Spawn* caster) { + if (target) { + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + Client* client = *client_itr; + if (!client) + continue; + PacketStruct* packet = configReader.getStruct("WS_HearCastSpell", client->GetVersion()); + if (packet) { + + int32 target_id = client->GetPlayer()->GetIDWithPlayerSpawn(target); + if(!target_id) { // client is not aware of spawn + safe_delete(packet); + continue; + } + + if (!caster) { + packet->setDataByName("spawn_id", 0xFFFFFFFF); + } + else { + int32 caster_id = client->GetPlayer()->GetIDWithPlayerSpawn(caster); + + if(!caster_id) { // client is not aware of spawn + safe_delete(packet); + continue; + } + packet->setDataByName("spawn_id", caster_id); + } + packet->setArrayLengthByName("num_targets", 1); + packet->setArrayDataByName("target", target_id); + + int32 visual = client->GetSpellVisualOverride(spell_visual); + + packet->setDataByName("spell_visual", visual); + packet->setDataByName("cast_time", 0); + packet->setDataByName("spell_id", 0); + packet->setDataByName("spell_level", 0); + packet->setDataByName("spell_tier", 1); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + } +} + +void ZoneServer::SendCastEntityCommandPacket(EntityCommand* entity_command, int32 spawn_id, int32 target_id) { + if (entity_command) { + Spawn* spawn = GetSpawnByID(spawn_id); + Spawn* target = GetSpawnByID(target_id); + if (!spawn || !target) + return; + + Client* client = 0; + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if (!client || !client->GetPlayer()->WasSentSpawn(spawn_id) || !client->GetPlayer()->WasSentSpawn(target_id)) + continue; + PacketStruct* packet = configReader.getStruct("WS_HearCastSpell", client->GetVersion()); + if (packet) { + int32 caster_id = client->GetPlayer()->GetIDWithPlayerSpawn(spawn); + int32 target_id = client->GetPlayer()->GetIDWithPlayerSpawn(target); + + if(!caster_id || !target_id) + continue; + + packet->setDataByName("spawn_id", caster_id); + packet->setArrayLengthByName("num_targets", 1); + packet->setArrayDataByName("target", target_id); + packet->setDataByName("num_targets", 1); + packet->setDataByName("spell_visual", entity_command->spell_visual); //result + packet->setDataByName("cast_time", entity_command->cast_time * 0.01); //delay + packet->setDataByName("spell_id", 1); + packet->setDataByName("spell_level", 1); + packet->setDataByName("spell_tier", 1); + EQ2Packet* outapp = packet->serialize(); + client->QueuePacket(outapp); + safe_delete(packet); + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + } +} + +void ZoneServer::StartZoneInitialSpawnThread(Client* client){ + if(zoneShuttingDown) + return; + +#ifdef WIN32 + _beginthread(SendInitialSpawns, 0, client); +#else + pthread_t thread; + pthread_create(&thread, NULL, SendInitialSpawns, client); + pthread_detach(thread); +#endif +} + +void ZoneServer::SendZoneSpawns(Client* client){ + int8 count = 0; + while (LoadingData && count <= 6000) { //sleep for max of 60 seconds (60000ms) while the maps are loading + count++; + Sleep(10); + } + count = 0; + int16 size = 0; + //give the spawn thread a tad bit of time to add the pending_spawns to spawn_list (up to 10 seconds) + while (count < 1000) { + MPendingSpawnListAdd.readlock(__FUNCTION__, __LINE__); + size = pending_spawn_list_add.size(); + MPendingSpawnListAdd.releasereadlock(__FUNCTION__, __LINE__); + if (size == 0) + break; + Sleep(10); + count++; + } + initial_spawn_threads_active++; + + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + Spawn* spawn = itr->second; + if (spawn) { + if(spawn == client->GetPlayer() && (client->IsReloadingZone() || client->GetPlayer()->IsReturningFromLD())) + { + if(!client->GetPlayer()->SetSpawnMap(spawn)) + continue; + } + + CheckSpawnRange(client, spawn, true); + } + } + + CheckSendSpawnToClient(client, true); + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + client->SetConnected(true); + ClientPacketFunctions::SendFinishedEntitiesList(client); + initial_spawn_threads_active--; +} + +vector ZoneServer::GetPlayers(){ + vector ret; + Client* client = 0; + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + ret.push_back(client->GetPlayer()); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +int16 ZoneServer::SetSpawnTargetable(Spawn* spawn, float distance){ + Spawn* test_spawn = 0; + int16 ret_val = 0; + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + test_spawn = itr->second; + if(test_spawn){ + if(test_spawn->GetDistance(spawn) <= distance){ + test_spawn->SetTargetable(1); + ret_val++; + } + } + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + return ret_val; +} + +int16 ZoneServer::SetSpawnTargetable(int32 spawn_id){ + Spawn* spawn = 0; + int16 ret_val = 0; + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + spawn = itr->second; + if(spawn){ + if(spawn->GetDatabaseID() == spawn_id){ + spawn->SetTargetable(1); + ret_val++; + } + } + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + return ret_val; +} + +ZoneInfoSlideStruct* ZoneServer::GenerateSlideStruct(float unknown1a, float unknown1b, int32 unknown2a, int32 unknown2b, int32 unknown3, int32 unknown4, const char* slide, const char* voiceover, int32 key1, int32 key2) { + ZoneInfoSlideStructInfo* info = new ZoneInfoSlideStructInfo(); + memset(info, 0, sizeof(ZoneInfoSlideStructInfo)); + info->unknown1[0] = unknown1a; + info->unknown1[1] = unknown1b; + info->unknown2[0] = unknown2a; + info->unknown2[1] = unknown2b; + info->unknown3 = unknown3; + info->unknown4 = unknown4; + int8 length = strlen(slide); + if (length >= 128) + length = 127; + strncpy(info->slide, slide, length); + length = strlen(voiceover); + if (length >= 128) + length = 127; + strncpy(info->voiceover, voiceover, length); + info->key1 = key1; + info->key2 = key2; + ZoneInfoSlideStruct* ret = new ZoneInfoSlideStruct(); + ret->info = info; + return ret; +} + +void ZoneServer::AddZoneInfoSlideStructTransitionInfo(ZoneInfoSlideStruct* info, int32 x, int32 y, float zoom, float transition_time) { + ZoneInfoSlideStructTransitionInfo* transition_info = new ZoneInfoSlideStructTransitionInfo(); + transition_info->transition_x = x; + transition_info->transition_y = y; + transition_info->transition_zoom = zoom; + transition_info->transition_time = transition_time; + info->slide_transition_info.push_back(transition_info); +} + +vector* ZoneServer::GenerateTutorialSlides() { + vector* slides = new vector(); + ZoneInfoSlideStruct* slide = GenerateSlideStruct(0.5, 0.5, 15, 15, 1842, 1012, "images/slideshows/boat_06p_tutorial02/lore_chapt01_final001.dds", "voiceover/english/antonia_intro/antonia_intro_001_64.mp3", 2519553957, 1010319376); + AddZoneInfoSlideStructTransitionInfo(slide, 920, 495, 1, 0); + AddZoneInfoSlideStructTransitionInfo(slide, 920, 495, 1.5, 16); + slides->push_back(slide); + + slide = GenerateSlideStruct(0.5, 0.5, 15, 15, 1842, 1012, "images/slideshows/boat_06p_tutorial02/lore_chapt02_final001.dds", "voiceover/english/antonia_intro/antonia_intro_002_64.mp3", 567178266, 3055063399); + AddZoneInfoSlideStructTransitionInfo(slide, 600, 365, 1.60000002384186, 0); + AddZoneInfoSlideStructTransitionInfo(slide, 800, 370, 1.39999997615814, 9); + AddZoneInfoSlideStructTransitionInfo(slide, 920, 420, 1.20000004768372, 8); + slides->push_back(slide); + + slide = GenerateSlideStruct(0.5, 0.5, 15, 15, 1842, 1012, "images/slideshows/boat_06p_tutorial02/lore_chapt03_final001.dds", "voiceover/english/antonia_intro/antonia_intro_003_64.mp3", 3171561318, 593374281); + AddZoneInfoSlideStructTransitionInfo(slide, 920, 420, 1.20000004768372, 0); + AddZoneInfoSlideStructTransitionInfo(slide, 750, 320, 1.60000002384186, 10); + AddZoneInfoSlideStructTransitionInfo(slide, 575, 265, 2.29999995231628, 10); + slides->push_back(slide); + + slide = GenerateSlideStruct(0.5, 0.5, 15, 15, 1842, 1012, "images/slideshows/boat_06p_tutorial02/lore_chapt04_final001.dds", "voiceover/english/antonia_intro/antonia_intro_004_64.mp3", 1959944485, 4285605574); + AddZoneInfoSlideStructTransitionInfo(slide, 920, 420, 2.5, 0); + AddZoneInfoSlideStructTransitionInfo(slide, 920, 420, 1.70000004768372, 8); + AddZoneInfoSlideStructTransitionInfo(slide, 675, 390, 2.20000004768372, 11); + slides->push_back(slide); + + slide = GenerateSlideStruct(0.5, 0.5, 15, 15, 1842, 1012, "images/slideshows/boat_06p_tutorial02/lore_chapt05_final001.dds", "voiceover/english/antonia_intro/antonia_intro_005_64.mp3", 609693392, 260295215); + AddZoneInfoSlideStructTransitionInfo(slide, 750, 500, 2.79999995231628, 0); + AddZoneInfoSlideStructTransitionInfo(slide, 720, 300, 2.5, 9); + AddZoneInfoSlideStructTransitionInfo(slide, 975, 270, 2.20000004768372, 9); + slides->push_back(slide); + + slide = GenerateSlideStruct(0.5, 0.5, 15, 15, 1842, 1012, "images/slideshows/boat_06p_tutorial02/lore_chapt06_final001.dds", "voiceover/english/antonia_intro/antonia_intro_006_64.mp3", 3056613203, 775201556); + AddZoneInfoSlideStructTransitionInfo(slide, 920, 495, 1.89999997615814, 0); + AddZoneInfoSlideStructTransitionInfo(slide, 920, 475, 1, 24); + slides->push_back(slide); + + slide = GenerateSlideStruct(0.5, 0.5, 15, 15, 1842, 1012, "images/slideshows/boat_06p_tutorial02/lore_chapt07_final001.dds", "voiceover/english/antonia_intro/antonia_intro_007_64.mp3", 3113327662, 1299367895); + AddZoneInfoSlideStructTransitionInfo(slide, 1400, 420, 2.40000009536743, 0); + AddZoneInfoSlideStructTransitionInfo(slide, 1200, 375, 1.70000004768372, 7); + AddZoneInfoSlideStructTransitionInfo(slide, 800, 225, 2.29999995231628, 7); + slides->push_back(slide); + + slide = GenerateSlideStruct(0.5, 0.5, 15, 15, 1842, 1012, "images/slideshows/boat_06p_tutorial02/lore_chapt08_final001.dds", "voiceover/english/antonia_intro/antonia_intro_008_64.mp3", 2558791235, 2674773065); + AddZoneInfoSlideStructTransitionInfo(slide, 920, 495, 1, 0); + AddZoneInfoSlideStructTransitionInfo(slide, 920, 495, 1.5, 27); + slides->push_back(slide); + + slide = GenerateSlideStruct(0.5, 0.5, 15, 15, 1842, 1012, "images/slideshows/boat_06p_tutorial02/lore_chapt09_final001.dds", "voiceover/english/antonia_intro/antonia_intro_009_64.mp3", 4029296401, 1369011033); + AddZoneInfoSlideStructTransitionInfo(slide, 715, 305, 2.40000009536743, 0); + AddZoneInfoSlideStructTransitionInfo(slide, 730, 325, 1.79999995231628, 6); + AddZoneInfoSlideStructTransitionInfo(slide, 920, 395, 1.5, 5); + AddZoneInfoSlideStructTransitionInfo(slide, 1360, 330, 1.79999995231628, 9); + slides->push_back(slide); + + slide = GenerateSlideStruct(0.5, 0.5, 15, 15, 1842, 1012, "images/slideshows/boat_06p_tutorial02/lore_chapt10_final001.dds", "voiceover/english/antonia_intro/antonia_intro_010_64.mp3", 3055524517, 3787058332); + AddZoneInfoSlideStructTransitionInfo(slide, 670, 675, 2.20000004768372, 0); + AddZoneInfoSlideStructTransitionInfo(slide, 710, 390, 1.79999995231628, 7); + AddZoneInfoSlideStructTransitionInfo(slide, 920, 415, 1.60000002384186, 5.5); + AddZoneInfoSlideStructTransitionInfo(slide, 1250, 675, 1.79999995231628, 8); + slides->push_back(slide); + + slide = GenerateSlideStruct(0.5, 0.5, 15, 15, 1842, 1012, "images/slideshows/boat_06p_tutorial02/lore_chapt11_final001.dds", "voiceover/english/antonia_intro/antonia_intro_011_64.mp3", 3525586740, 812068950); + AddZoneInfoSlideStructTransitionInfo(slide, 920, 495, 1, 0); + AddZoneInfoSlideStructTransitionInfo(slide, 920, 495, 2, 19); + slides->push_back(slide); + + slide = GenerateSlideStruct(0.5, 0.5, 15, 15, 1842, 1012, "images/slideshows/boat_06p_tutorial02/lore_chapt12_final001.dds", "voiceover/english/antonia_intro/antonia_intro_012_64.mp3", 3493874350, 2037661816); + AddZoneInfoSlideStructTransitionInfo(slide, 920, 495, 2, 0); + AddZoneInfoSlideStructTransitionInfo(slide, 920, 495, 1, 43); + slides->push_back(slide); + + return slides; +} + +EQ2Packet* ZoneServer::GetZoneInfoPacket(Client* client){ + PacketStruct* packet = configReader.getStruct("WS_ZoneInfo", client->GetVersion()); + packet->setSmallStringByName("server1",net.GetWorldName()); + packet->setSmallStringByName("server2",net.GetWorldName()); + packet->setDataByName("unknown1", 1, 1);//1, 1 + int32 expansions = EXPANSION_UNKNOWN + EXPANSION_DOF + EXPANSION_KOS + EXPANSION_EOF + EXPANSION_ROK + EXPANSION_TSO + EXPANSION_DOV; + //packet->setDataByName("expansions_enabled", 82313211);//expansions 63181 + //packet->setDataByName("expansions_enabled", 552075103);//expansions 63182 + packet->setDataByName("expansions_enabled", 4294967295);//expansions 1096 //612499455 works + if (client->GetVersion() >= 1193) { + packet->setDataByName("unknown3", 4294967295, 0); // DOV and down + packet->setDataByName("unknown3", 4294967295, 1); //COE and up + packet->setDataByName("unknown3", 4294967295, 2); + } + else + packet->setDataByName("unknown3", 4294967295, 0); // DOV and down + + packet->setSmallStringByName("auction_website", "eq2emulator.net"); + packet->setDataByName("auction_port", 80); + packet->setSmallStringByName("upload_page", "test_upload.m"); + packet->setSmallStringByName("upload_key", "dsya987yda9"); + packet->setSmallStringByName("zone", GetZoneFile()); + //packet->setSmallStringByName("zone2", GetZoneName()); + + //if ( strlen(GetZoneSkyFile()) > 0 ) + // packet->setSmallStringByName("zone_unknown2", GetZoneSkyFile()); // used for the sky map + + packet->setSmallStringByName("zone_desc", GetZoneDescription()); + packet->setSmallStringByName("char_name", client->GetPlayer()->GetName()); + + packet->setDataByName("x", client->GetPlayer()->GetX()); + packet->setDataByName("y", client->GetPlayer()->GetY()); + packet->setDataByName("z", client->GetPlayer()->GetZ()); + + if ((GetZoneFile() && strcmp("boat_06p_tutorial02", GetZoneFile()) == 0) && client->GetPlayer()->GetX() == this->GetSafeX() && client->GetPlayer()->GetY() == this->GetSafeY() && client->GetPlayer()->GetZ() == this->GetSafeZ()) { //basically the only time the player will see this is if their zone in coords are the exact same as the safe coords (they haven't moved) + vector* slides = GenerateTutorialSlides(); + if (slides) { + packet->setArrayLengthByName("num_slides", slides->size()); + ZoneInfoSlideStruct* slide = 0; + for (int8 i = 0; i < slides->size(); i++) { + slide = slides->at(i); + packet->setArrayDataByName("unknown1", slide->info->unknown1[0], i, 0); + packet->setArrayDataByName("unknown1", slide->info->unknown1[1], i, 1); + packet->setArrayDataByName("unknown2", slide->info->unknown2[0], i, 0); + packet->setArrayDataByName("unknown2", slide->info->unknown2[1], i, 1); + packet->setArrayDataByName("unknown3", slide->info->unknown3, i); + packet->setArrayDataByName("unknown4", slide->info->unknown4, i); + packet->setArrayDataByName("slide", slide->info->slide, i); + packet->setArrayDataByName("voiceover", slide->info->voiceover, i); + packet->setArrayDataByName("key1", slide->info->key1, i); + packet->setArrayDataByName("key2", slide->info->key2, i); + packet->setSubArrayLengthByName("num_transitions", slide->slide_transition_info.size(), i); + for (int8 x = 0; x < slide->slide_transition_info.size(); x++) { + packet->setSubArrayDataByName("transition_x", slide->slide_transition_info[x]->transition_x, i, x); + packet->setSubArrayDataByName("transition_y", slide->slide_transition_info[x]->transition_y, i, x); + packet->setSubArrayDataByName("transition_zoom", slide->slide_transition_info[x]->transition_zoom, i, x); + packet->setSubArrayDataByName("transition_time", slide->slide_transition_info[x]->transition_time, i, x); + safe_delete(slide->slide_transition_info[x]); + } + safe_delete(slide->info); + safe_delete(slide); + } + } + safe_delete(slides); + } + + if(rule_manager.GetGlobalRule(R_Zone, UseMapUnderworldCoords)->GetBool() && client->GetPlayer()->GetMap()) { + packet->setDataByName("underworld", client->GetPlayer()->GetMap()->GetMinY() + rule_manager.GetGlobalRule(R_Zone, MapUnderworldCoordOffset)->GetFloat()); + } + else { + packet->setDataByName("underworld", underworld); + } + + // unknown3 can prevent screen shots from being taken if + //packet->setDataByName("unknown3", 2094661567, 1); // Screenshots allowed with this value + //packet->setDataByName("unknown3", 3815767999, 1); // Screenshots disabled with this value + //packet->setDataByName("unknown3", 1, 2); + + /*if (client->GetVersion() >= 63587) { + packet->setArrayLengthByName("num_exp_feature_bytes", 9); + packet->setArrayDataByName("exp_feature_bytes", 95, 0);//kos and dof + packet->setArrayDataByName("exp_feature_bytes", 255, 1);//eof rok tso sf dov coe tov + packet->setArrayDataByName("exp_feature_bytes", 247, 2);//aom tot ka exp14 + packet->setArrayDataByName("exp_feature_bytes", 32, 3);//rum cellar + packet->setArrayDataByName("exp_feature_bytes", 140, 4); + packet->setArrayDataByName("exp_feature_bytes", 62, 5); + packet->setArrayDataByName("exp_feature_bytes", 0, 6); + packet->setArrayDataByName("exp_feature_bytes", 45, 7); + packet->setArrayDataByName("exp_feature_bytes", 128, 8); + + packet->setArrayLengthByName("num_unknown3b_bytes", 9); + packet->setArrayDataByName("unknown3b_bytes", 95, 0); + packet->setArrayDataByName("unknown3b_bytes", 255, 1); + packet->setArrayDataByName("unknown3b_bytes", 247, 2); + packet->setArrayDataByName("unknown3b_bytes", 237, 3); + packet->setArrayDataByName("unknown3b_bytes", 143, 4); + packet->setArrayDataByName("unknown3b_bytes", 255, 5); + packet->setArrayDataByName("unknown3b_bytes", 255, 6); + packet->setArrayDataByName("unknown3b_bytes", 255, 7); + packet->setArrayDataByName("unknown3b_bytes", 128, 8); + } + else if (client->GetVersion() >= 63214) { + packet->setArrayLengthByName("num_exp_feature_bytes", 9); + packet->setArrayDataByName("exp_feature_bytes", 95, 0);//kos and dof + packet->setArrayDataByName("exp_feature_bytes", 255, 1);//eof rok tso sf dov coe tov + packet->setArrayDataByName("exp_feature_bytes", 247, 2);//aom tot ka exp14 + packet->setArrayDataByName("exp_feature_bytes", 32, 3);//rum cellar + packet->setArrayDataByName("exp_feature_bytes", 140, 4); + packet->setArrayDataByName("exp_feature_bytes", 62, 5); + packet->setArrayDataByName("exp_feature_bytes", 0, 6); + packet->setArrayDataByName("exp_feature_bytes", 45, 7); + packet->setArrayDataByName("exp_feature_bytes", 128, 8); + + packet->setArrayLengthByName("num_unknown3b_bytes", 9); + packet->setArrayDataByName("unknown3b_bytes", 95, 0); + packet->setArrayDataByName("unknown3b_bytes", 255, 1); + packet->setArrayDataByName("unknown3b_bytes", 247, 2); + packet->setArrayDataByName("unknown3b_bytes", 237, 3); + packet->setArrayDataByName("unknown3b_bytes", 143, 4); + packet->setArrayDataByName("unknown3b_bytes", 255, 5); + packet->setArrayDataByName("unknown3b_bytes", 255, 6); + packet->setArrayDataByName("unknown3b_bytes", 255, 7); + packet->setArrayDataByName("unknown3b_bytes", 128, 8); + }*/ + if (client->GetVersion() >= 64644) { + packet->setDataByName("unknown3a", 12598924); + packet->setDataByName("unknown3b", 3992452959); + packet->setDataByName("unknown3c", 4294967183); + packet->setDataByName("unknown2a", 9); + packet->setDataByName("unknown2b", 9); + } + else if (client->GetVersion() >= 63181) { + packet->setDataByName("unknown3a", 750796556);//63182 73821356 + packet->setDataByName("unknown3b", 3991404383);// 63182 3991404383 + packet->setDataByName("unknown3c", 4278189967);// 63182 4278189967 + packet->setDataByName("unknown2a", 8);// 63182 + packet->setDataByName("unknown2b", 8);// 63182 + + } + else{ + //packet->setDataByName("unknown3", 872447025,0);//63181 + //packet->setDataByName("unknown3", 3085434875,1);// 63181 + //packet->setDataByName("unknown3", 2147483633,2);// 63181 + } + + packet->setDataByName("year", world.GetWorldTimeStruct()->year); + packet->setDataByName("month", world.GetWorldTimeStruct()->month); + packet->setDataByName("day", world.GetWorldTimeStruct()->day); + packet->setDataByName("hour", world.GetWorldTimeStruct()->hour); + packet->setDataByName("minute", world.GetWorldTimeStruct()->minute); + packet->setDataByName("unknown", 0); + packet->setDataByName("unknown7", 1); + packet->setDataByName("unknown7", 1, 1); + + packet->setDataByName("unknown9", 13); + //packet->setDataByName("unknown10", 25188959);4294967295 + //packet->setDataByName("unknown10", 25190239); + packet->setDataByName("unknown10", 25191524);//25191524 + packet->setDataByName("unknown10b", 1); + packet->setDataByName("permission_level",3);// added on 63182 for now till we figur it out 0=none,1=visitor,2=friend,3=trustee,4=owner + packet->setDataByName("num_adv", 9); + + packet->setArrayDataByName("adv_name", "adv02_dun_drowned_caverns", 0); + packet->setArrayDataByName("adv_id", 6, 0); + packet->setArrayDataByName("adv_name", "adv02_dun_sundered_splitpaw_hub", 1); + packet->setArrayDataByName("adv_id", 5, 1); + packet->setArrayDataByName("adv_name", "exp03_rgn_butcherblock", 2); + packet->setArrayDataByName("adv_id", 8, 2); + packet->setArrayDataByName("adv_name", "exp03_rgn_greater_faydark", 3); + packet->setArrayDataByName("adv_id", 7, 3); + packet->setArrayDataByName("adv_name", "mod01_dun_crypt_of_thaen", 4); + packet->setArrayDataByName("adv_id", 3, 4); + packet->setArrayDataByName("adv_name", "mod01_dun_tombs_of_night", 5); + packet->setArrayDataByName("adv_id", 4, 5); + packet->setArrayDataByName("adv_name", "nektulos_mini01", 6); + packet->setArrayDataByName("adv_id", 0, 6); + packet->setArrayDataByName("adv_name", "nektulos_mini02", 7); + packet->setArrayDataByName("adv_id", 1, 7); + packet->setArrayDataByName("adv_name", "nektulos_mini03", 8); + packet->setArrayDataByName("adv_id", 2, 8); + + + + + LogWrite(MISC__TODO, 0, "TODO", "Put cl_ client commands back in variables (not Rules) so they can be dynamically maintained"); + vector* variables = world.GetClientVariables(); + packet->setArrayLengthByName("num_client_setup", variables->size()); + for(int i=variables->size()-1;i>=0;i--) + packet->setArrayDataByName("client_cmds", variables->at(i)->GetNameValuePair().c_str(), i); + + // For AoM clients so item link work + if (client->GetVersion() >= 60114) + packet->setArrayDataByName("client_cmds", "chat_linkbrackets_item 1", variables->size()); + + safe_delete(variables); + //packet->setDataByName("unknown8", ); story? + // AA Tabs for 1193+ clients + if (client->GetVersion() >= 1193) { + packet->setArrayLengthByName("tab_count", 48); + int8 i = 0; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":es24a58bd8fcaac8c2:All", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c727bd47a6:Racial Innate", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c75a96e23c:Tradeskill Advancement", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c744f1fd99:Focus Effects", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c71edd2a66:Heroic", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c76ee6239f:Shadows", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7e678b977:Prestige", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c77ee422d7:Animalist", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7f165af77:Bard", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7421b9375:Brawler", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7a03ae7d1:Cleric", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7c9605e9f:Crusader", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7f9424168:Druid", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c79cb9556c:Enchanter", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c70c8b6aa4:Predator", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c73a43b6dd:Rogue", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c759fe7d15:Sorcerer", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7ad610aca:Summoner", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c71e056728:Warrior", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7ba864c0b:Assassin", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7b8116aad:Beastlord", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7f53feb7b:Berserker", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c73d8a70e2:Brigand", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c770c766d6:Bruiser", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c79226984b:Coercer", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c70c58bb30:Conjurer", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c73dfe68d0:Defiler", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c792919a6b:Dirge", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7062e5f55:Fury", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c762c1fdfc:Guardian", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c78addfbf4:Illusionist", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7ece054a7:Inquisitor", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7d550d2e7:Monk", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c743cfeaa2:Mystic", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7f63c9c8c:Necromancer", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c70c5de0ae:Paladin", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c79bc97b3a:Ranger", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c78fbd2256:Shadowknight", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7781cc625:Shaman", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c77eecdcdb:Swashbuckler", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7648d181e:Templar", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c78df47d77:Troubador", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7c78ce0b8:Warden", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c76290dcfa:Warlock", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7d1d52cf5:Wizard", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c71c8f6f4d:Shaper", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c72f6e354d:Channeler", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7df8bd37d:Dragon", i); + } + packet->setDataByName("unknown_mj", 1);//int8 + packet->setDataByName("unknown_mj1", 335544320);//int32 + packet->setDataByName("unknown_mj2", 4);//int32 + packet->setDataByName("unknown_mj3", 3962504088);//int32 + packet->setDataByName("unknown_mj4", 3985947216);//int32 + packet->setDataByName("unknown_mj5", 1);//int32 + packet->setDataByName("unknown_mj6", 386);//int32 + packet->setDataByName("unknown_mj7", 4294967295);//int32 + packet->setDataByName("unknown_mj8", 2716312211);//int32 + packet->setDataByName("unknown_mj9", 1774338333);//int32 + packet->setDataByName("unknown_mj10", 1);//int32 + packet->setDataByName("unknown_mj11", 391);//int32 + packet->setDataByName("unknown_mj12", 4294967295);//int32 + packet->setDataByName("unknown_mj13", 3168965163);//int32 + packet->setDataByName("unknown_mj14", 4117025286);//int32 + packet->setDataByName("unknown_mj15", 1);//int32 + packet->setDataByName("unknown_mj16", 394);//int32 + packet->setDataByName("unknown_mj17", 4294967295);//int32 + packet->setDataByName("unknown_mj18", 1790669110);//int32 + packet->setDataByName("unknown_mj19", 107158108);//int32 + packet->setDataByName("unknown_mj20", 1);//int32 + packet->setDataByName("unknown_mj21", 393);//int32 + packet->setDataByName("unknown_mj22", 4294967295);//int32 + + EQ2Packet* outapp = packet->serialize(); + //packet->PrintPacket(); + //DumpPacket(outapp); + safe_delete(packet); + return outapp; +} + +void ZoneServer::SendUpdateDefaultCommand(Spawn* spawn, const char* command, float distance, Spawn* toPlayer){ + if (spawn == nullptr || command == nullptr) + return; + + if (toPlayer) + { + if (!toPlayer->IsPlayer()) + return; + + Client* client = ((Player*)toPlayer)->GetClient(); + if (client) + { + client->SendDefaultCommand(spawn, command, distance); + } + // we don't override the primary command cause that would change ALL clients + return; + } + + QueueDefaultCommand(spawn->GetID(), std::string(command), distance); + + if (strlen(command)>0) + spawn->SetPrimaryCommand(command, command, distance); +} + +void ZoneServer::CheckPlayerProximity(Spawn* spawn, Client* client){ + if (player_proximities.size() < 1) + return; + + if(player_proximities.count(spawn->GetID()) > 0){ + PlayerProximity* prox = player_proximities.Get(spawn->GetID()); + if(prox->clients_in_proximity.count(client) == 0 && spawn_range_map.count(client) > 0 && spawn_range_map.Get(client)->count(spawn->GetID()) > 0 && spawn_range_map.Get(client)->Get(spawn->GetID()) < prox->distance){ + prox->clients_in_proximity[client] = true; + CallSpawnScript(spawn, SPAWN_SCRIPT_CUSTOM, client->GetPlayer(), prox->in_range_lua_function.c_str()); + } + else if(prox->clients_in_proximity.count(client) > 0 && spawn_range_map.count(client) > 0 && spawn_range_map.Get(client)->count(spawn->GetID()) > 0 && spawn_range_map.Get(client)->Get(spawn->GetID()) > prox->distance){ + if(prox->leaving_range_lua_function.length() > 0) + CallSpawnScript(spawn, SPAWN_SCRIPT_CUSTOM, client->GetPlayer(), prox->leaving_range_lua_function.c_str()); + prox->clients_in_proximity.erase(client); + } + } +} + +void ZoneServer::AddPlayerProximity(Spawn* spawn, float distance, string in_range_function, string leaving_range_function){ + RemovePlayerProximity(spawn); + PlayerProximity* prox = new PlayerProximity; + prox->distance = distance; + prox->in_range_lua_function = in_range_function; + prox->leaving_range_lua_function = leaving_range_function; + player_proximities.Put(spawn->GetID(), prox); +} + +void ZoneServer::RemovePlayerProximity(Client* client){ + PlayerProximity* prox = 0; + MutexMap::iterator itr = player_proximities.begin(); + while(itr.Next()){ + prox = itr->second; + if(prox->clients_in_proximity.count(client) > 0) + prox->clients_in_proximity.erase(client); + } +} + +void ZoneServer::RemovePlayerProximity(Spawn* spawn, bool all){ + if(all){ + MutexMap::iterator itr = player_proximities.begin(); + while(itr.Next()){ + player_proximities.erase(itr->first, false, true, 10000); + } + } + else if(player_proximities.count(spawn->GetID()) > 0){ + player_proximities.erase(spawn->GetID(), false, true, 10000); + } +} + +void ZoneServer::AddLocationProximity(float x, float y, float z, float max_variation, string in_range_function, string leaving_range_function) { + LocationProximity* prox = new LocationProximity; + prox->x = x; + prox->y = y; + prox->z = z; + prox->max_variation = max_variation; + prox->in_range_lua_function = in_range_function; + prox->leaving_range_lua_function = leaving_range_function; + location_proximities.Add(prox); +} + +void ZoneServer::CheckLocationProximity() { + const char* zone_script = world.GetZoneScript(this->GetZoneID()); + if (!zone_script) + return; + + if (location_proximities.size() > 0 && connected_clients.size() > 0) { + Client* client = 0; + MutexList::iterator iterator = connected_clients.begin(); + while(iterator.Next()){ + client = iterator->value; + if (client->IsConnected() && client->IsReadyForUpdates() && !client->IsZoning()) { + try { + MutexList::iterator itr = location_proximities.begin(); + LocationProximity* prox = 0; + while(itr.Next()){ + prox = itr->value; + bool in_range = false; + float char_x = client->GetPlayer()->GetX(); + float char_y = client->GetPlayer()->GetY(); + float char_z = client->GetPlayer()->GetZ(); + float x = prox->x; + float y = prox->y; + float z = prox->z; + float max_variation = prox->max_variation; + float total_diff = 0; + float diff = x - char_x; //Check X + if(diff < 0) + diff *= -1; + if(diff <= max_variation) { + total_diff += diff; + diff = z - char_z; //Check Z (we check Z first because it is far more likely to be a much greater variation than y) + if(diff < 0) + diff *= -1; + if(diff <= max_variation) { + total_diff += diff; + if(total_diff <= max_variation) { //Check Total + diff = y - char_y; //Check Y + if(diff < 0) + diff *= -1; + if(diff <= max_variation) { + total_diff += diff; + if(total_diff <= max_variation) { + in_range = true; + if(lua_interface && prox->in_range_lua_function.length() > 0 && prox->clients_in_proximity.count(client) == 0) { //Check Total + prox->clients_in_proximity[client] = true; + lua_interface->RunZoneScript(zone_script, prox->in_range_lua_function.c_str(), this, client->GetPlayer()); + } + } + } + } + } + } + if (!in_range) { + if(lua_interface && prox->leaving_range_lua_function.length() > 0 && prox->clients_in_proximity.count(client) > 0) { + lua_interface->RunZoneScript(zone_script, prox->leaving_range_lua_function.c_str(), this, client->GetPlayer()); + prox->clients_in_proximity.erase(client); + } + } + } + } + catch (...) { + LogWrite(ZONE__ERROR, 0, "Zone", "Except caught in ZoneServer::CheckLocationProximity"); + return; + } + } + } + } +} + +void ZoneServer::CheckLocationGrids() { + if (connected_clients.size() > 0 && location_grids.size() > 0) { + MutexList::iterator client_itr = connected_clients.begin(); + while (client_itr.Next()) { + Client* client = client_itr.value; + if (!client) + continue; + Player* player = client->GetPlayer(); + float x = player->GetX(); + float y = player->GetY(); + float z = player->GetZ(); + int32 grid_id = player->GetLocation(); + MutexList::iterator location_grid_itr = location_grids.begin(); + while (location_grid_itr.Next()) { + LocationGrid* grid = location_grid_itr.value; + bool playerInGrid = false; + if (grid->locations.size() > 0 || (playerInGrid = (grid->grid_id > 0 && grid->grid_id == grid_id)) || grid->players.count(player) > 0) { + float x_small = 0; + float x_large = 0; + float y_small = 0; + float y_large = 0; + float z_small = 0; + float z_large = 0; + bool first = true; + bool in_grid = false; + if(grid->locations.size() == 0 && playerInGrid) { // no locations, we presume player is in grid + in_grid = true; + } + else { + MutexList::iterator location_itr = grid->locations.begin(); + while (location_itr.Next()) { + Location* location = location_itr.value; + if (first) { + x_small = location->x; + x_large = location->x; + if (grid->include_y) { + y_small = location->y; + y_large = location->y; + } + z_small = location->z; + z_large = location->z; + first = false; + } + else { + if (location->x < x_small) + x_small = location->x; + else if (location->x > x_large) + x_large = location->x; + if (grid->include_y) { + if (location->y < y_small) + y_small = location->y; + else if (location->y > y_large) + y_large = location->y; + } + if (location->z < z_small) + z_small = location->z; + else if (location->z > z_large) + z_large = location->z; + } + } + if (grid->include_y && (x >= x_small && x <= x_large && y >= y_small && y <= y_large && z >= z_small && z <= z_large)) + in_grid = true; + else if (x >= x_small && x <= x_large && z >= z_small && z <= z_large) + in_grid = true; + } + if (in_grid && grid->players.count(player) == 0) { + grid->players.Put(player, true); + + bool show_enter_location_popup = true; + bool discovery_enabled = rule_manager.GetGlobalRule(R_World, EnablePOIDiscovery)->GetBool(); + + if( grid->discovery && discovery_enabled && !player->DiscoveredLocation(grid->id) ) + { + // check if player has already discovered this location + + // if not, process new discovery + char tmp[200] = {0}; + sprintf(tmp, "\\#FFE400You have discovered\12\\#FFF283%s", grid->name.c_str()); + client->SendPopupMessage(11, tmp, "ui_discovery", 2.25, 0xFF, 0xFF, 0xFF); + LogWrite(ZONE__DEBUG, 0, "Zone", "Player '%s' discovered location '%s' (%u)", player->GetName(), grid->name.c_str(), grid->id); + + player->UpdatePlayerHistory(HISTORY_TYPE_DISCOVERY, HISTORY_SUBTYPE_LOCATION, grid->id); + show_enter_location_popup = false; + + // else, print standard location entry + } + + if( show_enter_location_popup ) + { + LogWrite(ZONE__DEBUG, 0, "Zone", "Player '%s' entering location '%s' (%u)", player->GetName(), grid->name.c_str(), grid->id); + client->SendPopupMessage(10, grid->name.c_str(), 0, 2.5, 255, 255, 0); + } + } + else if (!in_grid && grid->players.count(player) > 0) { + LogWrite(ZONE__DEBUG, 0, "Zone", "Player '%s' leaving location '%s' (%u)", player->GetName(), grid->name.c_str(), grid->id); + grid->players.erase(player); + } + } + } + } + } +} + +// Called from a command (client, main zone thread) and the main zone thread +// so no need for a mutex container +void ZoneServer::AddLocationGrid(LocationGrid* grid) { + if (grid) + location_grids.Add(grid); +} + +void ZoneServer::RemoveLocationGrids() { + MutexList::iterator itr = location_grids.begin(); + while (itr.Next()) + itr.value->locations.clear(true); + location_grids.clear(true); +} + +void ZoneServer::RemoveSpellTimersFromSpawn(Spawn* spawn, bool remove_all, bool delete_recast, bool call_expire_function, bool lock_spell_process){ + if(spellProcess) + spellProcess->RemoveSpellTimersFromSpawn(spawn, remove_all, delete_recast, call_expire_function, lock_spell_process); +} + +void ZoneServer::Interrupted(Entity* caster, Spawn* interruptor, int16 error_code, bool cancel, bool from_movement){ + if(spellProcess) + spellProcess->Interrupted(caster, interruptor, error_code, cancel, from_movement); +} + +Spell* ZoneServer::GetSpell(Entity* caster){ + Spell* spell = 0; + if(spellProcess) + spell = spellProcess->GetSpell(caster); + return spell; +} + +void ZoneServer::ProcessSpell(Spell* spell, Entity* caster, Spawn* target, bool lock, bool harvest_spell, LuaSpell* customSpell, int16 custom_cast_time, bool in_heroic_opp){ + if(spellProcess) + spellProcess->ProcessSpell(this, spell, caster, target, lock, harvest_spell, customSpell, custom_cast_time, in_heroic_opp); +} + +void ZoneServer::ProcessEntityCommand(EntityCommand* entity_command, Entity* caster, Spawn* target, bool lock) { + if (target && target->GetSpawnScript()) { + Player* player = 0; + if (caster && caster->IsPlayer()) + player = (Player*)caster; + CallSpawnScript(target, SPAWN_SCRIPT_CUSTOM, player, entity_command->command.c_str()); + } + if (spellProcess) + spellProcess->ProcessEntityCommand(this, entity_command, caster, target, lock); +} + +void ZoneServer::RemoveSpawnSupportFunctions(Spawn* spawn, bool lock_spell_process, bool shutdown) { + if(!spawn) + return; + + // remove entity* in trade class + if(spawn->IsEntity()) { + ((Entity*)spawn)->TerminateTrade(); + } + + if(spawn->IsPlayer() && spawn->GetZone()) { + spawn->GetZone()->RemovePlayerPassenger(((Player*)spawn)->GetCharacterID()); + if(((Player*)spawn)->GetClient()) { + GetTradeskillMgr()->StopCrafting(((Player*)spawn)->GetClient()); + } + } + if(spawn->IsEntity()) + RemoveSpellTimersFromSpawn((Entity*)spawn, true, true, true, lock_spell_process); + + if(!shutdown) { // in case of shutdown, DeleteData(true) handles the cleanup later via DeleteSpawnScriptTimers + StopSpawnScriptTimer(spawn, ""); + } + + if(spawn->IsEntity()) { + ClearHate((Entity*)spawn); + } + + RemoveDamagedSpawn(spawn); + spawn->SendSpawnChanges(false); + RemoveChangedSpawn(spawn); + + // Everything inside this if will be nuked during a reload in other spots, no need to do it twice + if (!reloading) { + RemoveDeadEnemyList(spawn); + + spawn->changed = true; + spawn->info_changed = true; + spawn->vis_changed = true; + spawn->position_changed = true; + SendSpawnChanges(spawn); + + if (spawn->GetSpawnGroupID() > 0) { + int32 group_id = spawn->GetSpawnGroupID(); + spawn->RemoveSpawnFromGroup(); + if (spawn_group_map.count(group_id) > 0) + spawn_group_map.Get(group_id).Remove(spawn->GetID()); + } + + if (!spawn->IsPlayer()) { + if(quick_database_id_lookup.count(spawn->GetDatabaseID()) > 0) + quick_database_id_lookup.erase(spawn->GetDatabaseID()); + + if(spawn->GetSpawnLocationID() > 0 && quick_location_id_lookup.count(spawn->GetSpawnLocationID()) > 0 && quick_location_id_lookup.Get(spawn->GetSpawnLocationID()) == spawn->GetID()) + quick_location_id_lookup.erase(spawn->GetSpawnLocationID()); + + if(spawn->GetSpawnGroupID() > 0 && quick_group_id_lookup.count(spawn->GetSpawnGroupID()) > 0 && quick_group_id_lookup.Get(spawn->GetSpawnGroupID()) == spawn->GetID()) + quick_group_id_lookup.erase(spawn->GetSpawnGroupID()); + } + + DeleteSpawnScriptTimers(spawn); + RemovePlayerProximity(spawn); + } + + // We don't use RemoveMovementNPC() here as it caused a hell of a delay during reloads + // instead we remove it from the list directly + if (spawn->IsNPC()) + movement_spawns.erase(spawn->GetID()); +} + +void ZoneServer::HandleEmote(Spawn* originator, string name) { + if (!originator) { + LogWrite(ZONE__ERROR, 0, "Zone", "HandleEmote called with an invalid client"); + return; + } + + Client* orig_client = (originator->IsPlayer() && ((Player*)originator)->GetClient()) ? ((Player*)originator)->GetClient() : nullptr; + Client* client = 0; + int32 cur_client_version = orig_client ? orig_client->GetVersion() : 546; + Emote* origEmote = visual_states.FindEmote(name, cur_client_version); + if(!origEmote){ + if(orig_client) { + orig_client->Message(CHANNEL_COLOR_YELLOW, "Unable to find emote '%s'. If this should be a valid emote be sure to submit a /bug report.", name.c_str()); + } + return; + } + Emote* emote = origEmote; + + PacketStruct* packet = 0; + char* emoteResponse = 0; + vector::iterator client_itr; + + map emote_version_range; + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(!client || (client && originator->IsPlayer() && client->GetPlayer()->IsIgnored(originator->GetName()))) + continue; + + // establish appropriate emote for the version used by the client + if (client->GetVersion() != cur_client_version) + { + map::iterator rangeitr = emote_version_range.find(client->GetVersion()); + if (rangeitr == emote_version_range.end()) + { + Emote* tmp_new_emote = visual_states.FindEmote(name, client->GetVersion()); + if (tmp_new_emote) + { + emote_version_range.insert(make_pair(client->GetVersion(), tmp_new_emote)); + emote = tmp_new_emote; + } // else its missing just use the current clients default + } + else // we have an existing emote already cached + emote = rangeitr->second; + } + else // since the client and originator client match use the original emote + emote = origEmote; + + packet = configReader.getStruct("WS_CannedEmote", client->GetVersion()); + if(packet){ + packet->setDataByName("spawn_id" , client->GetPlayer()->GetIDWithPlayerSpawn(originator)); + if(!emoteResponse){ + string message; + if(originator->GetTarget() && originator->GetTarget()->GetID() != originator->GetID()){ + message = emote->GetTargetedMessageString(); + if(message.find("%t") < 0xFFFFFFFF) + message.replace(message.find("%t"), 2, originator->GetTarget()->GetName()); + } + if(message.length() == 0) + message = emote->GetMessageString(); + if(message.find("%g1") < 0xFFFFFFFF){ + if(originator->GetGender() == 1) + message.replace(message.find("%g1"), 3, "his"); + else + message.replace(message.find("%g1"), 3, "her"); + } + if(message.find("%g2") < 0xFFFFFFFF){ + if(originator->GetGender() == 1) + message.replace(message.find("%g2"), 3, "him"); + else + message.replace(message.find("%g2"), 3, "her"); + } + if(message.find("%g3") < 0xFFFFFFFF){ + if(originator->GetGender() == 1) + message.replace(message.find("%g3"), 3, "he"); + else + message.replace(message.find("%g3"), 3, "she"); + } + if(message.length() > 0){ + emoteResponse = new char[message.length() + strlen(originator->GetName()) + 10]; + sprintf(emoteResponse,"%s %s", originator->GetName(), message.c_str()); + } + } + if(originator->IsPlayer()) { + packet->setMediumStringByName("emote_msg", emoteResponse ? emoteResponse : ""); + } + packet->setDataByName("anim_type", emote->GetVisualState()); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + safe_delete_array(emoteResponse); + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + + +void ZoneServer::SetupInstance(int32 createdInstanceID) { + if ( createdInstanceID == 0 ) // if this happens that isn't good! + instanceID = ++MinInstanceID; + else // db should pass the good ID + instanceID = createdInstanceID; +} + +void ZoneServer::RemoveDeadSpawn(Spawn* spawn){ + AddDeadSpawn(spawn, 0); +} + +void ZoneServer::AddDeadSpawn(Spawn* spawn, int32 timer){ + MDeadSpawns.writelock(__FUNCTION__, __LINE__); + if (dead_spawns.count(spawn->GetID()) > 0) + dead_spawns[spawn->GetID()] = Timer::GetCurrentTime2() + timer; + else if(timer != 0xFFFFFFFF) + dead_spawns.insert(make_pair(spawn->GetID(), Timer::GetCurrentTime2() + timer)); + else{ + if(spawn->IsEntity() && spawn->HasLoot()){ + dead_spawns.insert(make_pair(spawn->GetID(), Timer::GetCurrentTime2() + (15000 * spawn->GetLevel() + 240000))); + SendUpdateDefaultCommand(spawn, "loot", 10); + } + else + dead_spawns.insert(make_pair(spawn->GetID(), Timer::GetCurrentTime2() + 10000)); + } + MDeadSpawns.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::WritePlayerStatistics() { + MutexList::iterator client_itr = connected_clients.begin(); + while(client_itr.Next()) + client_itr->value->GetPlayer()->WritePlayerStatistics(); +} + +bool ZoneServer::SendRadiusSpawnInfo(Client* client, float radius) { + if (!client) + return false; + + Spawn* spawn = 0; + bool ret = false; + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + spawn = itr->second; + if (spawn && spawn != client->GetPlayer() && !spawn->IsPlayer() && spawn->GetDistance(client->GetPlayer()) <= radius) { + const char* type = "NPC"; + const char* specialTypeID = "N/A"; + int32 specialID = 0, spawnEntryID = spawn->GetSpawnEntryID(); + if (spawn->IsObject()) + { + Object* obj = (Object*)spawn; + specialID = obj->GetID(); + specialTypeID = "GetID"; + type = "Object"; + } + else if (spawn->IsSign()) + { + Sign* sign = (Sign*)spawn; + specialID = sign->GetWidgetID(); + specialTypeID = "WidgetID"; + type = "Sign"; + } + else if (spawn->IsWidget()) + { + Widget* widget = (Widget*)spawn; + specialID = widget->GetWidgetID(); + specialTypeID = "WidgetID"; + if ( specialID == 0xFFFFFFFF ) + specialTypeID = "WidgetID(spawn_widgets entry missing)"; + + type = "Widget"; + } + else if (spawn->IsGroundSpawn()) + { + GroundSpawn* gs = (GroundSpawn*)spawn; + specialID = gs->GetGroundSpawnEntryID(); + specialTypeID = "GroundSpawnEntryID"; + type = "GroundSpawn"; + } + client->Message(CHANNEL_COLOR_RED, "Name: %s (%s), Spawn Table ID: %u, %s: %u", spawn->GetName(), type, spawn->GetDatabaseID(), specialTypeID, specialID); + client->Message(CHANNEL_COLOR_RED, "Spawn Location ID: %u, Spawn Group ID: %u, SpawnEntryID: %u, Grid ID: %u", spawn->GetSpawnLocationID(), spawn->GetSpawnGroupID(), spawnEntryID, spawn->GetLocation()); + client->Message(CHANNEL_COLOR_RED, "Respawn Time: %u (sec), X: %f, Y: %f, Z: %f Heading: %f", spawn->GetRespawnTime(), spawn->GetX(), spawn->GetY(), spawn->GetZ(), spawn->GetHeading()); + client->Message(CHANNEL_COLOR_YELLOW, "============================="); + ret = true; + } + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +void ZoneServer::FindSpawn(Client* client, char* regSearchStr) +{ + if (!regSearchStr || strlen(regSearchStr) < 1) + { + client->SimpleMessage(CHANNEL_COLOR_RED, "Bad ZoneServer::FindSpawn(Client*, char* regSearchStr) attempt, regSearchStr is NULL or empty."); + return; + } + + string resString = string(regSearchStr); + try + { + std::regex pre_re_check("^[a-zA-Z0-9_ ]+$"); + bool output = std::regex_match(resString, pre_re_check); + if (output) + { + string newStr(".*"); + newStr.append(regSearchStr); + newStr.append(".*"); + resString = newStr; + } + } + catch (...) + { + client->SimpleMessage(CHANNEL_COLOR_RED, "Try/Catch ZoneServer::FindSpawn(Client*, char* regSearchStr) failure."); + return; + } + std::regex re; + try { + re = std::regex(resString, std::regex_constants::icase); + } + catch(...) { + client->SimpleMessage(CHANNEL_COLOR_RED, "Invalid regex for FindSpawn."); + return; + } + + client->Message(CHANNEL_NARRATIVE, "RegEx Search Spawn List: %s", regSearchStr); + client->Message(CHANNEL_NARRATIVE, "Database ID | Spawn Name | X , Y , Z"); + client->Message(CHANNEL_NARRATIVE, "========================"); + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + int32 spawnsFound = 0; + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + Spawn* spawn = itr->second; + if (!spawn || !spawn->GetName()) + continue; + bool output = false; + try { + output = std::regex_match(string(spawn->GetName()), re); + } + catch (...) + { + continue; + } + + if (output) + { + client->Message(CHANNEL_NARRATIVE, "%i | %s | %f , %f , %f", spawn->GetDatabaseID(), spawn->GetName(), spawn->GetX(), spawn->GetY(), spawn->GetZ()); + spawnsFound++; + } + } + client->Message(CHANNEL_NARRATIVE, "========================", spawnsFound); + client->Message(CHANNEL_NARRATIVE, "%u Results Found.", spawnsFound); + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); +} + + +void ZoneServer::AddPlayerTracking(Player* player) { + if (player && !player->GetIsTracking() && players_tracking.count(player->GetDatabaseID()) == 0) { + Client* client = ((Player*)player)->GetClient(); + if (client) { + PacketStruct* packet = configReader.getStruct("WS_TrackingUpdate", client->GetVersion()); + if (packet) { + player->SetIsTracking(true); + players_tracking.Put(client->GetCharacterID(), player); + packet->setDataByName("mode", TRACKING_START); + packet->setDataByName("type", TRACKING_TYPE_ENTITIES); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + } +} + +void ZoneServer::RemovePlayerTracking(Player* player, int8 mode) { + if (player && player->GetIsTracking()) { + Client* client = ((Player*)player)->GetClient(); + if (client) { + PacketStruct* packet = configReader.getStruct("WS_TrackingUpdate", client->GetVersion()); + if (packet) { + player->SetIsTracking(false); + players_tracking.erase(client->GetCharacterID()); + packet->setDataByName("mode", mode); + packet->setDataByName("type", TRACKING_TYPE_ENTITIES); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + } +} + +void ZoneServer::ProcessTracking() { + MutexMap::iterator itr = players_tracking.begin(); + while (itr.Next()) { + Player* player = itr->second; + if(player->GetClient()) { + ProcessTracking(player->GetClient()); + } + } +} + +void ZoneServer::ProcessTracking(Client* client) { + if (!client) + return; + + Player* player = client->GetPlayer(); + if (player && player->GetIsTracking()) { + MutexMap::iterator spawn_itr; + PacketStruct* packet = configReader.getStruct("WS_TrackingUpdate", client->GetVersion()); + if (packet) { + packet->setDataByName("mode", TRACKING_UPDATE); + packet->setDataByName("type", TRACKING_TYPE_ENTITIES); + vector spawns_tracked; + while (spawn_itr.Next()) { + Spawn* spawn = spawn_itr->second; + float distance = player->GetDistance(spawn); + if (spawn->IsEntity() && distance <= 80 && spawn != player) { + TrackedSpawn* ts = new TrackedSpawn; + ts->spawn = spawn; + ts->distance = distance; + + /* Add spawns in ascending order from closest to furthest */ + if (spawns_tracked.empty()) + spawns_tracked.push_back(ts); + else { + vector::iterator tracked_itr; + bool added = false; + for (tracked_itr = spawns_tracked.begin(); tracked_itr != spawns_tracked.end(); tracked_itr++) { + TrackedSpawn* cur_ts = *tracked_itr; + if (ts->distance <= cur_ts->distance) { + spawns_tracked.insert(tracked_itr, ts); + added = true; + break; + } + } + if (!added) + spawns_tracked.push_back(ts); + } + } + } + packet->setArrayLengthByName("num_spawns", spawns_tracked.size()); + for (int32 i = 0; i < spawns_tracked.size(); i++) { + TrackedSpawn* ts = spawns_tracked[i]; + + LogWrite(ZONE__DEBUG, 0, "Zone", "%s (%f)", ts->spawn->GetName(), ts->distance); + + packet->setArrayDataByName("spawn_id", player->GetIDWithPlayerSpawn(ts->spawn), i); + packet->setArrayDataByName("spawn_name", ts->spawn->GetName(), i); + if (ts->spawn->IsPlayer()) + packet->setArrayDataByName("spawn_type", TRACKING_SPAWN_TYPE_PC, i); + else + packet->setArrayDataByName("spawn_type", TRACKING_SPAWN_TYPE_NPC, i); + packet->setArrayDataByName("spawn_con_color", player->GetArrowColor(ts->spawn->GetLevel()), i); + } + packet->setArrayLengthByName("num_array1", 0); + //for (int32 i = 0; i < spawns_tracked.size(); i++) { + //} + packet->setArrayLengthByName("num_spawns2", spawns_tracked.size()); + for (int32 i = 0; i < spawns_tracked.size(); i++) { + TrackedSpawn* ts = spawns_tracked[i]; + packet->setArrayDataByName("list_spawn_id", player->GetIDWithPlayerSpawn(ts->spawn), i); + packet->setArrayDataByName("list_number", i, i); + } + client->QueuePacket(packet->serialize()); + safe_delete(packet); + for (int32 i = 0; i < spawns_tracked.size(); i++) + safe_delete(spawns_tracked[i]); + } + } +} + +void ZoneServer::SendEpicMobDeathToGuild(Player* killer, Spawn* victim) { + if (killer && victim) { + + LogWrite(MISC__TODO, 1, "TODO" , "Check if player is in raid\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__); + + if (killer->GetGroupMemberInfo()) { + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + + deque::iterator itr; + + + PlayerGroup* group = world.GetGroupManager()->GetGroup(killer->GetGroupMemberInfo()->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + for (itr = members->begin(); itr != members->end(); itr++) { + GroupMemberInfo* gmi = *itr; + if (gmi->client) { + Player* group_member = gmi->client->GetPlayer(); + if (group_member && group_member->GetGuild()) { + Guild* guild = group_member->GetGuild(); + string message = Guild::GetEpicMobDeathMessage(group_member->GetName(), victim->GetName()); + guild->AddNewGuildEvent(GUILD_EVENT_KILLS_EPIC_MONSTER, message.c_str(), Timer::GetUnixTimeStamp()); + guild->SendMessageToGuild(GUILD_EVENT_KILLS_EPIC_MONSTER, message.c_str()); + } + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } + else if (killer->GetGuild()) { + Guild* guild = killer->GetGuild(); + string message = Guild::GetEpicMobDeathMessage(killer->GetName(), victim->GetName()); + guild->AddNewGuildEvent(GUILD_EVENT_KILLS_EPIC_MONSTER, message.c_str(), Timer::GetUnixTimeStamp()); + guild->SendMessageToGuild(GUILD_EVENT_KILLS_EPIC_MONSTER, message.c_str()); + } + } +} + +void ZoneServer::ProcessAggroChecks(Spawn* spawn) { + if (spawn->GetFactionID() < 1 || spawn->EngagedInCombat()) + return; + // If faction based combat is not allowed then no need to run the loops so just return out + if(!rule_manager.GetGlobalRule(R_Faction, AllowFactionBasedCombat)->GetBool()) + return; + + if (spawn && spawn->IsNPC() && spawn->Alive()) + CheckEnemyList((NPC*)spawn); +} + +void ZoneServer::SendUpdateTitles(Client* client, Title* suffix, Title* prefix) { + assert(client); + if (client->GetVersion() > 561) + SendUpdateTitles(client->GetPlayer(), suffix, prefix); +} + +void ZoneServer::SendUpdateTitles(Spawn *spawn, Title *suffix, Title *prefix) { + if (!spawn) + return; + + vector::iterator itr; + PacketStruct *packet; + Client* current_client; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (itr = clients.begin(); itr != clients.end(); itr++) { + current_client = *itr; + + if (current_client->GetVersion() <= 561) + continue; + + if (!(packet = configReader.getStruct("WS_UpdateTitle", current_client->GetVersion()))) + continue; + + packet->setDataByName("player_id", current_client->GetPlayer()->GetIDWithPlayerSpawn(spawn)); + packet->setDataByName("player_name", spawn->GetName()); + packet->setDataByName("unknown1", 1, 1); + if(suffix) + packet->setDataByName("suffix_title", suffix->GetName()); + else + packet->setDataByName("suffix_title", spawn->GetSuffixTitle()); + if(prefix) + packet->setDataByName("prefix_title", prefix->GetName()); + else + packet->setDataByName("prefix_title", spawn->GetPrefixTitle()); + packet->setDataByName("last_name", spawn->GetLastName()); + packet->setDataByName("sub_title", spawn->GetSubTitle()); + current_client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::AddTransportSpawn(Spawn* spawn){ + if(!spawn) + return; + MTransportSpawns.writelock(__FUNCTION__, __LINE__); + transport_spawns.push_back(spawn->GetID()); + spawn->SetTransportSpawn(true); + MTransportSpawns.releasewritelock(__FUNCTION__, __LINE__); +} + +Spawn* ZoneServer::GetClosestTransportSpawn(float x, float y, float z){ + Spawn* spawn = 0; + Spawn* closest_spawn = 0; + float closest_distance = 0.0; + MTransportSpawns.writelock(__FUNCTION__, __LINE__); + vector::iterator itr = transport_spawns.begin(); + while(itr != transport_spawns.end()){ + spawn = GetSpawnByID(*itr); + if(spawn){ + if(closest_distance == 0.0){ + closest_spawn = spawn; + closest_distance = spawn->GetDistance(x, y, z); + } + else if(spawn->GetDistance(x, y, z) < closest_distance){ + closest_spawn = spawn; + closest_distance = spawn->GetDistance(x, y, z); + } + itr++; + } + else + itr = transport_spawns.erase(itr); + } + MTransportSpawns.releasewritelock(__FUNCTION__, __LINE__); + + return closest_spawn; +} + +Spawn* ZoneServer::GetTransportByRailID(sint64 rail_id){ + Spawn* spawn = 0; + Spawn* closest_spawn = 0; + MTransportSpawns.readlock(__FUNCTION__, __LINE__); + vector::iterator itr = transport_spawns.begin(); + while(itr != transport_spawns.end()){ + spawn = GetSpawnByID(*itr); + //printf("Rail id: %i vs %i\n", spawn ? spawn->GetRailID() : 0, rail_id); + if(spawn && spawn->GetRailID() == rail_id){ + closest_spawn = spawn; + break; + } + itr++; + } + MTransportSpawns.releasereadlock(__FUNCTION__, __LINE__); + + return closest_spawn; +} + +void ZoneServer::SetRain(float val) { + rain = val; + vector::iterator itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (itr = clients.begin(); itr != clients.end(); itr++) { + Client* client = *itr; + client->GetPlayer()->GetInfoStruct()->set_rain(val); + client->GetPlayer()->SetCharSheetChanged(true); + if( val >= 0.75 && !weather_signaled ) + { + client->SimpleMessage(CHANNEL_NARRATIVE, "It starts to rain."); + } + else if( val < 0.75 && weather_signaled ) + { + client->SimpleMessage(CHANNEL_NARRATIVE, "It stops raining."); + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + + if (val >= 0.75 && !weather_signaled) { + weather_signaled = true; + ProcessSpawnConditional(SPAWN_CONDITIONAL_RAINING); + } + else if (val < 0.75 && weather_signaled) { + weather_signaled = false; + ProcessSpawnConditional(SPAWN_CONDITIONAL_NOT_RAINING); + } +} + +void ZoneServer::SetWind(float val) { + vector::iterator itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (itr = clients.begin(); itr != clients.end(); itr++) { + Client* client = *itr; + client->GetPlayer()->GetInfoStruct()->set_wind(val); + client->GetPlayer()->SetCharSheetChanged(true); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::ProcessWeather() +{ + // if the global rule to disable weather is set, or if the `weather_allowed` field in the zone record == 0, do not process weather + if( !weather_enabled || !isWeatherAllowed() ) + return; + + LogWrite(ZONE__DEBUG, 1, "Zone", "%s: Processing weather changes", zone_name); + float new_weather = 0; + float weather_offset = 0; + bool change_weather = false; + + // check to see if it is time to change the weather according to weather_frequency (time between changes) + if( weather_last_changed_time <= (Timer::GetUnixTimeStamp() - weather_frequency) ) + { + LogWrite(ZONE__DEBUG, 2, "Zone", "%s: Checking for weather changes", zone_name); + // reset last changed time (frequency check) + weather_last_changed_time = Timer::GetUnixTimeStamp(); + + // this is the chance a weather change occurs at all at the expired interval + int8 weather_random = MakeRandomInt(1, 100); + LogWrite(ZONE__DEBUG, 2, "Zone", "%s: Chance to change weather: %i%%, rolled: %i%% - Change weather: %s", zone_name, weather_change_chance, weather_random, weather_random <= weather_change_chance ? "True" : "False"); + + if( weather_random <= weather_change_chance ) + { + change_weather = true; + weather_offset = weather_change_amount; + + if( weather_type == 3 ) // chaotic weather patterns, random weather between min/max + { + new_weather = MakeRandomFloat(weather_min_severity, weather_max_severity); + LogWrite(ZONE__DEBUG, 3, "Zone", "%s: Chaotic weather severity changed to %.2f", zone_name, new_weather); + weather_pattern = 2; + } + else if( weather_type == 2 ) // random weather patterns, combination of normal + dynamic + max_offset + { + weather_offset = MakeRandomFloat(weather_change_amount, weather_dynamic_offset); + LogWrite(ZONE__DEBUG, 3, "Zone", "%s: Random weather severity changed by %.2f", zone_name, weather_offset); + + int8 weather_alter = weather_change_chance / 10; // the divide is to prevent too many direction changes in a cycle + weather_random = MakeRandomInt(1, 100); // chance that the weather changes direction (weather_pattern) + + if( weather_random <= weather_alter ) + weather_pattern = ( weather_pattern == 0 ) ? 1 : 0; + } + else if( weather_type == 1 ) // dynamic weather patterns, weather may not reach min/max + { + int8 weather_alter = weather_change_chance / 10; // the divide is to prevent too many direction changes in a cycle + weather_random = MakeRandomInt(1, 100); // chance that the weather changes direction (weather_pattern) + + if( weather_random <= weather_alter ) + { + weather_pattern = ( weather_pattern == 0 ) ? 1 : 0; + LogWrite(ZONE__DEBUG, 3, "Zone", "%s: Dynamic weather pattern changed to %i", zone_name, weather_pattern); + } + } + else // normal weather patterns, weather starts at min, goes to max, then back down again + { + // do nothing (processed below) + LogWrite(ZONE__DEBUG, 3, "Zone", "%s: Normal weather severity changed by %.2f", zone_name, weather_offset); + } + + // when all done, change the weather + if( change_weather ) + { + if( weather_pattern == 1 ) + { + // weather is getting worse, til it reaches weather_max_severity + new_weather = ( weather_current_severity <= weather_max_severity ) ? weather_current_severity + weather_offset : weather_max_severity; + LogWrite(ZONE__DEBUG, 3, "Zone", "%s: Increased weather severity by %.2f", zone_name, weather_offset); + + if(new_weather > weather_max_severity) + { + new_weather = weather_max_severity - weather_offset; + weather_pattern = 0; + } + } + else if( weather_pattern == 0 ) + { + // weather is clearing up, til it reaches weather_min_severity + new_weather = ( weather_current_severity >= weather_min_severity ) ? weather_current_severity - weather_offset : weather_min_severity; + LogWrite(ZONE__DEBUG, 3, "Zone", "%s: Decreased weather severity by %.2f", zone_name, weather_offset); + + if(new_weather < weather_min_severity) + { + new_weather = weather_min_severity + weather_offset; + weather_pattern = 1; + } + } + + LogWrite(ZONE__DEBUG, 1, "Zone", "%s: Weather change triggered from %.2f to %.2f", zone_name, weather_current_severity, new_weather); + this->SetRain(new_weather); + weather_current_severity = new_weather; + } + } + } + else + LogWrite(ZONE__DEBUG, 1, "Zone", "%s: Not time to change weather yet", zone_name); +} + +void ZoneServer::HidePrivateSpawn(Spawn* spawn) { + if (!spawn->IsPrivateSpawn()) + return; + + Client* client = 0; + Player* player = 0; + PacketStruct* packet = 0; + int32 packet_version = 0; + MutexList::iterator itr = connected_clients.begin(); + while (itr->Next()) { + client = itr->value; + player = client->GetPlayer(); + if (player->WasSentSpawn(spawn->GetID()) || client->GetPlayer()->IsSendingSpawn(spawn->GetID())) { + if (!packet || packet_version != client->GetVersion()) { + safe_delete(packet); + packet_version = client->GetVersion(); + packet = configReader.getStruct("WS_DestroyGhostCmd", packet_version); + } + + SendRemoveSpawn(client, spawn, packet); + if(spawn_range_map.count(client) > 0) + spawn_range_map.Get(client)->erase(spawn->GetID()); + + if(player->GetTarget() == spawn) + player->SetTarget(0); + } + } + + safe_delete(packet); +} + +SpawnLocation* ZoneServer::GetSpawnLocation(int32 id) { + SpawnLocation* ret = 0; + MSpawnLocationList.readlock(__FUNCTION__, __LINE__); + if (spawn_location_list.count(id) > 0) + ret = spawn_location_list[id]; + MSpawnLocationList.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +void ZoneServer::PlayAnimation(Spawn* spawn, int32 visual_state, Spawn* spawn2, int8 hide_type){ + Client* client = 0; + PacketStruct* packet = 0; + Spawn* exclude_spawn = 0; + if (!spawn) + return; + if (spawn2){ + if(hide_type == 1){ + if(spawn2->IsPlayer()) { + client = ((Player*)spawn2)->GetClient(); + if(client){ + packet = configReader.getStruct("WS_CannedEmote", client->GetVersion()); + packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(spawn)); + packet->setDataByName("anim_type", visual_state); + client->QueuePacket(packet->serialize()); + } + safe_delete(packet); + return; + } + } + if(hide_type == 2) + exclude_spawn = spawn2; + } + + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(spawn->GetDistance(client->GetPlayer()) > 50) + continue; + if(exclude_spawn == client->GetPlayer()) + continue; + if(!client->IsReadyForUpdates()) // client is not in world yet so we shouldn't be sending animations of spawns yet + continue; + + if(!packet || packet->GetVersion() != client->GetVersion()) { + safe_delete(packet); + packet = configReader.getStruct("WS_CannedEmote", client->GetVersion()); + } + if (packet) { + int32 spawn_id = client->GetPlayer()->GetIDWithPlayerSpawn(spawn); + if(spawn_id) { + packet->setDataByName("spawn_id", spawn_id); + packet->setDataByName("anim_type", visual_state); + client->QueuePacket(packet->serialize()); + } + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + safe_delete(packet); +} + +vector ZoneServer::GetSpawnsByID(int32 id) { + vector tmp_list; + Spawn* spawn; + + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + spawn = itr->second; + if (spawn && (spawn->GetDatabaseID() == id)) + tmp_list.push_back(spawn); + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + + return tmp_list; +} + + +vector ZoneServer::GetSpawnsByRailID(sint64 rail_id) { + vector tmp_list; + Spawn* spawn; + MTransportSpawns.readlock(__FUNCTION__, __LINE__); + vector::iterator itr = transport_spawns.begin(); + while(itr != transport_spawns.end()){ + spawn = GetSpawnByID(*itr); + if(spawn && spawn->GetRailID() == rail_id){ + tmp_list.push_back(spawn); + } + itr++; + } + MTransportSpawns.releasereadlock(__FUNCTION__, __LINE__); + return tmp_list; +} + +void ZoneServer::RemovePlayerPassenger(int32 char_id) { + vector tmp_list; + Spawn* spawn; + MTransportSpawns.readlock(__FUNCTION__, __LINE__); + vector::iterator itr = transport_spawns.begin(); + while(itr != transport_spawns.end()){ + spawn = GetSpawnByID(*itr); + if(spawn) { + spawn->RemoveRailPassenger(char_id); + } + itr++; + } + MTransportSpawns.releasereadlock(__FUNCTION__, __LINE__); +} + +bool ZoneServer::SetPlayerTargetByName(Client* originator, char* targetName, float distance) { + if(!targetName || !originator->GetPlayer()) { + return false; + } + int32 name_size = strlen(targetName); + if(name_size < 1 || name_size > 127) { + return false; + } + + auto loc = glm::vec3(originator->GetPlayer()->GetX(), originator->GetPlayer()->GetZ(), originator->GetPlayer()->GetY()); + std::vector grids_by_radius; + if(originator->GetPlayer()->GetMap()) { + grids_by_radius = GetGridsByLocation(originator->GetPlayer(), loc, distance); + } + else { + grids_by_radius.push_back(originator->GetPlayer()->GetLocation()); + } + + bool found_target = false; + float spawn_dist = 999999.0f; + Spawn* target_spawn = nullptr; + float tmp_dist = 0.0f; + MGridMaps.lock_shared(); + std::vector::iterator grid_radius_itr; + for(grid_radius_itr = grids_by_radius.begin(); grid_radius_itr != grids_by_radius.end(); grid_radius_itr++) { + std::map::iterator grids = grid_maps.find((*grid_radius_itr)); + if(grids != grid_maps.end()) { + grids->second->MSpawns.lock_shared(); + typedef map SpawnMapType; + for( SpawnMapType::iterator it = grids->second->spawns.begin(); it != grids->second->spawns.end(); ++it ) { + Spawn* spawn = it->second; + + bool inSameGroup = false; + if(spawn->IsEntity()) { + GroupMemberInfo* gmi = originator->GetPlayer()->GetGroupMemberInfo(); + inSameGroup = (((Entity*)spawn)->GetGroupMemberInfo() && originator->GetPlayer()->GetGroupMemberInfo() && ((Entity*)spawn)->GetGroupMemberInfo()->group_id == originator->GetPlayer()->GetGroupMemberInfo()->group_id); + } + if (spawn && spawn->appearance.targetable > 0 && !strncasecmp(spawn->GetName(), targetName, name_size) && (inSameGroup || (((tmp_dist = spawn->GetDistance(originator->GetPlayer(), true)) <= distance) && tmp_dist < spawn_dist && originator->GetPlayer()->CheckLoS(spawn)))) { + spawn_dist = tmp_dist; + target_spawn = spawn; + } + } + grids->second->MSpawns.unlock_shared(); + } + } + MGridMaps.unlock_shared(); + + if(target_spawn != nullptr) { + originator->TargetSpawn(target_spawn); + } + + return (target_spawn != nullptr); +} + +std::vector ZoneServer::GetGridsByLocation(Spawn* originator, glm::vec3 loc, float distance) { + std::vector grids_by_radius; + if(originator == nullptr) + return grids_by_radius; + + if(originator->GetMap()) { + grids_by_radius = originator->GetMap()->GetGridsByPoint(loc, distance); + if(default_zone_map && default_zone_map != originator->GetMap()) { + std::vector default_grids = default_zone_map->GetGridsByPoint(loc, distance); + + std::unordered_set elements(grids_by_radius.begin(), grids_by_radius.end()); + + for (const auto& elem : default_grids) { + if (elements.find(elem) == elements.end()) { + grids_by_radius.push_back(elem); + elements.insert(elem); + } + } + } + } + + return grids_by_radius; +} + +std::vector> ZoneServer::GetAttackableSpawnsByDistance(Spawn* caster, float distance) { + std::vector> spawns_by_distance; + Spawn* spawn = 0; + auto loc = glm::vec3(caster->GetX(), caster->GetZ(), caster->GetY()); + std::vector grids_by_radius; + if(caster->GetMap()) { + grids_by_radius = GetGridsByLocation(caster, loc, distance); + } + else { + grids_by_radius.push_back(caster->GetLocation()); + } + + float tmp_dist = 0.0f; + MGridMaps.lock_shared(); + std::vector::iterator grid_radius_itr; + for(grid_radius_itr = grids_by_radius.begin(); grid_radius_itr != grids_by_radius.end(); grid_radius_itr++) { + std::map::iterator grids = grid_maps.find((*grid_radius_itr)); + if(grids != grid_maps.end()) { + grids->second->MSpawns.lock_shared(); + typedef map SpawnMapType; + for( SpawnMapType::iterator it = grids->second->spawns.begin(); it != grids->second->spawns.end(); ++it ) { + Spawn* spawn = it->second; + if (spawn && spawn->IsNPC() && spawn->appearance.attackable > 0 && spawn->GetID() > 0 && spawn->GetID() != caster->GetID() && + spawn->Alive() && ((tmp_dist = spawn->GetDistance(caster, true)) <= distance)) { + spawns_by_distance.push_back({spawn->GetID(), tmp_dist}); + } + } + grids->second->MSpawns.unlock_shared(); + } + } + MGridMaps.unlock_shared(); + std::sort(spawns_by_distance.begin(), spawns_by_distance.end(), compareByValue); + + return spawns_by_distance; +} + +void ZoneServer::ResurrectSpawn(Spawn* spawn, Client* client) { + if(!client || !spawn) + return; + PendingResurrection* rez = client->GetCurrentRez(); + if(!rez || !rez->caster) + return; + + PacketStruct* packet = 0; + float power_perc = rez->mp_perc; + float health_perc = rez->hp_perc; + Spawn* caster_spawn = rez->caster; + sint32 heal_amt = 0; + sint32 power_amt = 0; + bool no_calcs = rez->no_calcs; + int8 crit_mod = rez->crit_mod; + Entity* caster = 0; + InfoStruct* info = 0; + bool crit = false; + string heal_spell = rez->heal_name; + int16 heal_packet_type = 0; + int16 power_packet_type = 0; + + //Calculations for how much to heal the spawn + if(health_perc > 0) + heal_amt = (spawn->GetTotalHP() * (health_perc / 100)); + if(power_perc > 0) + power_amt = (spawn->GetTotalPower() * (power_perc / 100)); + + if(caster_spawn->IsEntity()){ + caster = ((Entity*)caster_spawn); + info = caster->GetInfoStruct(); + } + + if(!no_calcs && caster){ + heal_amt = caster->CalculateHealAmount(spawn, heal_amt, crit_mod, &crit); + power_amt = caster->CalculateHealAmount(spawn, power_amt, crit_mod, &crit); + } + + //Set this rez as a crit to be passed to subspell (not yet used) + rez->crit = true; + + //Set Heal amt to 1 if 0 now so the player has health + if(heal_amt == 0) + heal_amt = 1; + + if(heal_amt > spawn->GetTotalHP()) + heal_amt = spawn->GetTotalHP(); + if(power_amt > spawn->GetTotalPower()) + power_amt = spawn->GetTotalPower(); + + spawn->SetAlive(true); + spawn->SetHP(heal_amt); + if(power_amt > 0) + spawn->SetPower(power_amt); + + if(client && caster){ + EQ2Packet* move = ((Player*)spawn)->Move(caster->GetX(), caster->GetY(), caster->GetZ(), client->GetVersion()); + if(move) + client->QueuePacket(move); + } + + if(crit){ + power_packet_type = HEAL_PACKET_TYPE_CRIT_MANA; + heal_packet_type = HEAL_PACKET_TYPE_CRIT_HEAL; + } + else { + power_packet_type = HEAL_PACKET_TYPE_SIMPLE_MANA; + heal_packet_type = HEAL_PACKET_TYPE_SIMPLE_HEAL; + } + + SendHealPacket(caster, spawn, heal_packet_type, heal_amt, heal_spell.c_str()); + if(power_amt > 0) + SendHealPacket(caster, spawn, power_packet_type, power_amt, heal_spell.c_str()); + + //The following code sets the spawn as alive + if(dead_spawns.count(spawn->GetID()) > 0) + dead_spawns.erase(spawn->GetID()); + + if(spawn->IsPlayer()){ + spawn->SetSpawnType(4); + client = ((Player*)spawn)->GetClient(); + if(client){ + packet = configReader.getStruct("WS_Resurrected", client->GetVersion()); + if(packet){ + client->QueuePacket(packet->serialize()); + } + safe_delete(packet); + + if(client->GetVersion() <= 561) { + ClientPacketFunctions::SendServerControlFlagsClassic(client, 8, 0); + ClientPacketFunctions::SendServerControlFlagsClassic(client, 16, 0); + } + else { + ClientPacketFunctions::SendServerControlFlags(client, 1, 8, 0); + ClientPacketFunctions::SendServerControlFlags(client, 1, 16, 0); + } + client->SimpleMessage(CHANNEL_NARRATIVE, "You regain consciousness!"); + } + } + spawn->SendSpawnChanges(true); + spawn->SetTempActionState(-1); + spawn->appearance.attackable = 1; +} + +void ZoneServer::SendDispellPacket(Entity* caster, Spawn* target, string dispell_name, string spell_name, int8 dispell_type){ + if(!caster || !target) + return; + + Client* client = 0; + Player* player = 0; + PacketStruct* packet = 0; + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++){ + client = *client_itr; + if(!client || !(player = client->GetPlayer()) || (player != caster && ((caster && player->WasSentSpawn(caster->GetID()) == false) || (target && player->WasSentSpawn(target->GetID()) == false)))) + continue; + if(caster && caster->GetDistance(player) > 50) + continue; + if(target && target->GetDistance(player) > 50) + continue; + + packet = configReader.getStruct("WS_HearDispell", client->GetVersion()); + if(packet){ + packet->setDataByName("spell_name", spell_name.c_str()); + packet->setDataByName("dispell_name", dispell_name.c_str()); + packet->setDataByName("caster", player->GetIDWithPlayerSpawn(caster)); + packet->setDataByName("target", player->GetIDWithPlayerSpawn(target)); + packet->setDataByName("type", dispell_type); + client->QueuePacket(packet->serialize()); + } + safe_delete(packet); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::DismissAllPets() { + Spawn* spawn = 0; + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + spawn = itr->second; + if (spawn && spawn->IsEntity()) + ((Entity*)spawn)->DismissAllPets(); + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::RemoveTargetFromSpell(LuaSpell* spell, Spawn* target, bool remove_caster){ + if (spellProcess) + spellProcess->RemoveTargetFromSpell(spell, target, remove_caster); +} + +void ZoneServer::ClearHate(Entity* entity) { + Spawn* spawn = 0; + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + spawn = itr->second; + if (spawn && spawn->IsNPC() && ((NPC*)spawn)->Brain()) + ((NPC*)spawn)->Brain()->ClearHate(entity); + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); +} + +ThreadReturnType ZoneLoop(void* tmp) { +#ifdef WIN32 + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); +#endif + if (tmp == 0) { + ThrowError("ZoneLoop(): tmp = 0!"); + THREAD_RETURN(NULL); + } + ZoneServer* zs = (ZoneServer*) tmp; + while (zs->Process()) { + if(zs->GetClientCount() == 0) + Sleep(1000); + else + Sleep(10); + } + // we failed, time to disappear, no more processing period + safe_delete(zs); + THREAD_RETURN(NULL); +} + +ThreadReturnType SpawnLoop(void* tmp) { +#ifdef WIN32 + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); +#endif + if (tmp == 0) { + ThrowError("SpawnLoop(): tmp = 0!"); + THREAD_RETURN(NULL); + } + ZoneServer* zs = (ZoneServer*) tmp; +#ifndef NO_CATCH + try { +#endif + zs->spawnthread_active = true; + while (zs->SpawnProcess()) { + if(zs->GetClientCount() == 0) + Sleep(1000); + else + Sleep(20); + } + zs->spawnthread_active = false; +#ifndef NO_CATCH + } + catch(...) { + zs->spawnthread_active = false; + zs->initial_spawn_threads_active = 0; + LogWrite(ZONE__ERROR, 0, "Zone", "Error Processing SpawnLoop, shutting down zone '%s'...", zs->GetZoneName()); + try{ + zs->Shutdown(); + } + catch(...){ + LogWrite(ZONE__ERROR, 0, "Zone", "Error Processing SpawnLoop while shutting down zone '%s'...", zs->GetZoneName()); + throw; + } + throw; + } +#endif + THREAD_RETURN(NULL); +} + +ThreadReturnType SendInitialSpawns(void* tmp) { +#ifdef WIN32 + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL); +#endif + if (tmp == 0) { + ThrowError("SendInitialSpawns(): tmp = 0!"); + THREAD_RETURN(NULL); + } + Client* client = (Client*) tmp; + client->GetCurrentZone()->SendZoneSpawns(client); + THREAD_RETURN(NULL); +} + +ThreadReturnType SendLevelChangedSpawns(void* tmp) { +#ifdef WIN32 + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); +#endif + if (tmp == 0) { + ThrowError("SendLevelChangedSpawns(): tmp = 0!"); + THREAD_RETURN(NULL); + } + Client* client = (Client*)tmp; + client->GetCurrentZone()->SendAllSpawnsForLevelChange(client); + THREAD_RETURN(NULL); +} + +void ZoneServer::SetSpawnStructs(Client* client) { + int16 client_ver = client->GetVersion(); + Player* player = client->GetPlayer(); + + //Save a copy of the correct spawn substructs for the client's player, save here a copy if we don't have one + PacketStruct* pos = configReader.getStruct("Substruct_SpawnPositionStruct", client_ver); + player->SetSpawnPosStruct(pos); + if (versioned_pos_structs.count(pos->GetVersion()) == 0) + versioned_pos_structs[pos->GetVersion()] = new PacketStruct(pos, true); + + PacketStruct* vis = configReader.getStruct("Substruct_SpawnVisualizationInfoStruct", client_ver); + player->SetSpawnVisStruct(vis); + if (versioned_vis_structs.count(vis->GetVersion()) == 0) + versioned_vis_structs[vis->GetVersion()] = new PacketStruct(vis, true); + + PacketStruct* info = configReader.getStruct("Substruct_SpawnInfoStruct", client_ver); + player->SetSpawnInfoStruct(info); + if (versioned_info_structs.count(info->GetVersion()) == 0) + versioned_info_structs[info->GetVersion()] = new PacketStruct(info, true); + + PacketStruct* header = configReader.getStruct("WS_SpawnStruct_Header", client_ver); + player->SetSpawnHeaderStruct(header); + + PacketStruct* footer = configReader.getStruct("WS_SpawnStruct_Footer", client_ver); + player->SetSpawnFooterStruct(footer); + + PacketStruct* sfooter = configReader.getStruct("WS_SignWidgetSpawnStruct_Footer", client_ver); + player->SetSignFooterStruct(sfooter); + + PacketStruct* wfooter = configReader.getStruct("WS_WidgetSpawnStruct_Footer", client_ver); + player->SetWidgetFooterStruct(wfooter); +} + +Spawn* ZoneServer::GetSpawn(int32 id){ + Spawn* ret = 0; + + if(GetNPC(id)) + ret = GetNewNPC(id); + else if(this->GetObject(id)) + ret = GetNewObject(id); + else if(GetWidget(id)) + ret = GetNewWidget(id); + else if(GetSign(id)) + ret = GetNewSign(id); + else if(GetGroundSpawn(id)) + ret = GetNewGroundSpawn(id); + // Unable to find the spawn in the list lets attempt to add it if we are not currently reloading + else if (!reloading && database.LoadNPC(this, id)) { + if (GetNPC(id)) + ret = GetNewNPC(id); + else + LogWrite(NPC__ERROR, 0, "NPC", "Database inserted npc (%u) but was still unable to retrieve it!", id); + } + else if (!reloading && database.LoadObject(this, id)) { + if (this->GetObject(id)) + ret = GetNewObject(id); + else + LogWrite(OBJECT__ERROR, 0, "Object", "Database inserted object (%u) but was still unable to retrieve it!", id); + } + else if (!reloading && database.LoadWidget(this, id)) { + if (GetWidget(id)) + ret = GetNewWidget(id); + else + LogWrite(WIDGET__ERROR, 0, "Widget", "Database inserted widget (%u) but was still unable to retrieve it!", id); + } + else if (!reloading && database.LoadSign(this, id)) { + if (GetSign(id)) + ret = GetNewSign(id); + else + LogWrite(SIGN__ERROR, 0, "Sign", "Database inserted sign (%u) but was still unable to retrieve it!", id); + } + else if (!reloading && database.LoadGroundSpawn(this, id)) { + if (GetGroundSpawn(id)) + ret = GetNewGroundSpawn(id); + else + LogWrite(GROUNDSPAWN__ERROR, 0, "GSpawn", "Database inserted ground spawn (%u) but was still unable to retrieve it!", id); + } + + if(ret && ret->IsOmittedByDBFlag()) + { + LogWrite(SPAWN__WARNING, 0, "Spawn", "Spawn (%u) was skipped due to a missing expansion / holiday flag being met.", id); + safe_delete(ret); + ret = 0; + } + + if(ret) + ret->SetID(Spawn::NextID()); + return ret; +} + + + + +vector* ZoneServer::GetEntityCommandList(int32 id){ + if(entity_command_list.count(id) > 0) + return entity_command_list[id]; + else + return 0; +} + +void ZoneServer::SetEntityCommandList(int32 id, EntityCommand* command) { + if (entity_command_list.count(id) == 0) + entity_command_list[id] = new vector; + + entity_command_list[id]->push_back(command); +} + +EntityCommand* ZoneServer::GetEntityCommand(int32 id, string name) { + EntityCommand* ret = 0; + if (entity_command_list.count(id) == 0) + return ret; + + vector::iterator itr; + for (itr = entity_command_list[id]->begin(); itr != entity_command_list[id]->end(); itr++) { + if ((*itr)->name == name) { + ret = (*itr); + break; + } + } + + return ret; +} + +void ZoneServer::ClearEntityCommands() { + if (entity_command_list.size() > 0) { + map* >::iterator itr; + for (itr = entity_command_list.begin(); itr != entity_command_list.end(); itr++) { + vector* entity_commands = itr->second; + if (entity_commands && entity_commands->size() > 0) { + vector::iterator v_itr; + for (v_itr = entity_commands->begin(); v_itr != entity_commands->end(); v_itr++) + safe_delete(*v_itr); + entity_commands->clear(); + } + safe_delete(entity_commands); + } + entity_command_list.clear(); + } +} + +void ZoneServer::AddNPCSkill(int32 list_id, int32 skill_id, int16 value){ + npc_skill_list[list_id][skill_id] = value; +} + +map* ZoneServer::GetNPCSkills(int32 primary_list, int32 secondary_list){ + map* ret = 0; + if(npc_skill_list.count(primary_list) > 0){ + ret = new map(); + map::iterator itr; + Skill* tmpSkill = 0; + for(itr = npc_skill_list[primary_list].begin(); itr != npc_skill_list[primary_list].end(); itr++){ + tmpSkill = master_skill_list.GetSkill(itr->first); + if(tmpSkill){ + tmpSkill = new Skill(tmpSkill); + tmpSkill->current_val = itr->second; + tmpSkill->max_val = tmpSkill->current_val+5; + (*ret)[tmpSkill->name.data] = tmpSkill; + } + } + } + if(npc_skill_list.count(secondary_list) > 0){ + if(!ret) + ret = new map(); + map::iterator itr; + Skill* tmpSkill = 0; + for(itr = npc_skill_list[secondary_list].begin(); itr != npc_skill_list[secondary_list].end(); itr++){ + tmpSkill = master_skill_list.GetSkill(itr->first); + if(tmpSkill){ + tmpSkill = new Skill(tmpSkill); + tmpSkill->current_val = itr->second; + tmpSkill->max_val = tmpSkill->current_val+5; + (*ret)[tmpSkill->name.data] = tmpSkill; + } + } + } + if(ret && ret->size() == 0){ + safe_delete(ret); + ret = 0; + } + return ret; +} + +void ZoneServer::AddNPCEquipment(int32 list_id, int32 item_id){ + npc_equipment_list[list_id].push_back(item_id); +} + +void ZoneServer::SetNPCEquipment(NPC* npc) { + if(npc_equipment_list.count(npc->GetEquipmentListID()) > 0){ + Item* tmpItem = 0; + int8 slot = 0; + vector::iterator itr; + for(itr = npc_equipment_list[npc->GetEquipmentListID()].begin(); itr != npc_equipment_list[npc->GetEquipmentListID()].end(); itr++){ + tmpItem = master_item_list.GetItem(*itr); + if(tmpItem){ + slot = npc->GetEquipmentList()->GetFreeSlot(tmpItem); + if(slot < 255){ + tmpItem = new Item(tmpItem); + npc->GetEquipmentList()->SetItem(slot, tmpItem); + } + } + } + } +} + +void ZoneServer::AddNPC(int32 id, NPC* npc) { + npc_list[id] = npc; +} + +void ZoneServer::AddWidget(int32 id, Widget* widget) { + widget_list[id] = widget; +} + +Widget* ZoneServer::GetWidget(int32 id, bool override_loading) { + if((!reloading || override_loading) && widget_list.count(id) > 0) + return widget_list[id]; + else + return 0; +} + +Widget* ZoneServer::GetNewWidget(int32 id) { + if(!reloading && widget_list.count(id) > 0) + return widget_list[id]->Copy(); + else + return 0; +} + + +void ZoneServer::LoadGroundSpawnEntries(){ + MGroundSpawnItems.lock(); + database.LoadGroundSpawnEntries(this); + MGroundSpawnItems.unlock(); +} + +void ZoneServer::LoadGroundSpawnItems() { +} + +void ZoneServer::AddGroundSpawnEntry(int32 groundspawn_id, int16 min_skill_level, int16 min_adventure_level, int8 bonus_table, float harvest1, float harvest3, float harvest5, float harvest_imbue, float harvest_rare, float harvest10, int32 harvest_coin) { + GroundSpawnEntry* entry = new GroundSpawnEntry; + entry->min_skill_level = min_skill_level; + entry->min_adventure_level = min_adventure_level; + entry->bonus_table = bonus_table; + entry->harvest1 = harvest1; + entry->harvest3 = harvest3; + entry->harvest5 = harvest5; + entry->harvest_imbue = harvest_imbue; + entry->harvest_rare = harvest_rare; + entry->harvest10 = harvest10; + entry->harvest_coin = harvest_coin; + groundspawn_entries[groundspawn_id].push_back(entry); +} + +void ZoneServer::AddGroundSpawnItem(int32 groundspawn_id, int32 item_id, int8 is_rare, int32 grid_id) { + GroundSpawnEntryItem* entry = new GroundSpawnEntryItem; + entry->item_id = item_id; + entry->is_rare = is_rare; + entry->grid_id = grid_id; + groundspawn_items[groundspawn_id].push_back(entry); + +} + +vector* ZoneServer::GetGroundSpawnEntries(int32 id){ + vector* ret = 0; + MGroundSpawnItems.lock(); + if(groundspawn_entries.count(id) > 0) + ret = &groundspawn_entries[id]; + MGroundSpawnItems.unlock(); + return ret; +} + +vector* ZoneServer::GetGroundSpawnEntryItems(int32 id){ + vector* ret = 0; + if(groundspawn_items.count(id) > 0) + ret = &groundspawn_items[id]; + return ret; +} + +// TODO - mis-named, should be DeleteGroundSpawnEntries() but this is ok for now :) +void ZoneServer::DeleteGroundSpawnItems() +{ + MGroundSpawnItems.lock(); + + map >::iterator groundspawnentry_map_itr; + vector::iterator groundspawnentry_itr; + for(groundspawnentry_map_itr = groundspawn_entries.begin(); groundspawnentry_map_itr != groundspawn_entries.end(); groundspawnentry_map_itr++) + { + for(groundspawnentry_itr = groundspawnentry_map_itr->second.begin(); groundspawnentry_itr != groundspawnentry_map_itr->second.end(); groundspawnentry_itr++) + { + safe_delete(*groundspawnentry_itr); + } + } + groundspawn_entries.clear(); + + map >::iterator groundspawnitem_map_itr; + vector::iterator groundspawnitem_itr; + for(groundspawnitem_map_itr = groundspawn_items.begin(); groundspawnitem_map_itr != groundspawn_items.end(); groundspawnitem_map_itr++) + { + for(groundspawnitem_itr = groundspawnitem_map_itr->second.begin(); groundspawnitem_itr != groundspawnitem_map_itr->second.end(); groundspawnitem_itr++) + { + safe_delete(*groundspawnitem_itr); + } + } + groundspawn_items.clear(); + + MGroundSpawnItems.unlock(); +} + +void ZoneServer::AddGroundSpawn(int32 id, GroundSpawn* spawn) { + groundspawn_list[id] = spawn; +} + +GroundSpawn* ZoneServer::GetGroundSpawn(int32 id, bool override_loading) { + if((!reloading || override_loading) && groundspawn_list.count(id) > 0) + return groundspawn_list[id]; + else + return 0; +} + +GroundSpawn* ZoneServer::GetNewGroundSpawn(int32 id) { + if(!reloading && groundspawn_list.count(id) > 0) + return groundspawn_list[id]->Copy(); + else + return 0; +} + +void ZoneServer::AddLootTable(int32 id, LootTable* table){ + loot_tables[id] = table; +} + +void ZoneServer::AddLootDrop(int32 id, LootDrop* drop){ + loot_drops[id].push_back(drop); +} + +void ZoneServer::AddSpawnLootList(int32 spawn_id, int32 id) { + spawn_loot_list[spawn_id].push_back(id); +} + +void ZoneServer::ClearSpawnLootList(int32 spawn_id) { + spawn_loot_list[spawn_id].clear(); +} + +void ZoneServer::AddLevelLootList(GlobalLoot* loot) { + level_loot_list.push_back(loot); +} + +void ZoneServer::AddRacialLootList(int16 racial_id, GlobalLoot* loot) { + racial_loot_list[racial_id].push_back(loot); +} + +void ZoneServer::AddZoneLootList(int32 zone, GlobalLoot* loot) { + zone_loot_list[zone].push_back(loot); +} + +void ZoneServer::ClearLootTables(){ + map::iterator table_itr; + for(table_itr = loot_tables.begin(); table_itr != loot_tables.end(); table_itr++){ + safe_delete(table_itr->second); + } + + map >::iterator drop_itr; + vector::iterator drop_itr2; + for(drop_itr = loot_drops.begin(); drop_itr != loot_drops.end(); drop_itr++){ + for(drop_itr2 = drop_itr->second.begin(); drop_itr2 != drop_itr->second.end(); drop_itr2++){ + safe_delete(*drop_itr2); + } + } + + vector::iterator level_itr; + for (level_itr = level_loot_list.begin(); level_itr != level_loot_list.end(); level_itr++) { + safe_delete(*level_itr); + } + + + map >::iterator race_itr; + vector::iterator race_itr2; + for (race_itr = racial_loot_list.begin(); race_itr != racial_loot_list.end(); race_itr++) { + for (race_itr2 = race_itr->second.begin(); race_itr2 != race_itr->second.end(); race_itr2++) { + safe_delete(*race_itr2); + } + } + + map >::iterator zone_itr; + vector::iterator zone_itr2; + for(zone_itr = zone_loot_list.begin(); zone_itr != zone_loot_list.end(); zone_itr++) { + for (zone_itr2 = zone_itr->second.begin(); zone_itr2 != zone_itr->second.end(); zone_itr2++) { + safe_delete(*zone_itr2); + } + } + + loot_tables.clear(); + loot_drops.clear(); + spawn_loot_list.clear(); + level_loot_list.clear(); + racial_loot_list.clear(); + zone_loot_list.clear(); +} + +vector ZoneServer::GetSpawnLootList(int32 spawn_id, int32 zone_id, int8 spawn_level, int16 racial_id, Spawn* spawn) { + vector ret; + int32 returnValue = 0; + + if(reloading) + return ret; + + if (spawn_loot_list.count(spawn_id) > 0) + ret.insert(ret.end(), spawn_loot_list[spawn_id].begin(), spawn_loot_list[spawn_id].end()); + + if (level_loot_list.size() > 0) { + vector::iterator itr; + for (itr = level_loot_list.begin(); itr != level_loot_list.end(); itr++) { + GlobalLoot* loot = *itr; + const char* zone_script = world.GetZoneScript(this->GetZoneID()); + returnValue = 0; // reset since this can override the database setting + if(zone_script) + { + if(lua_interface->RunZoneScriptWithReturn(zone_script, "loot_criteria_level", spawn->GetZone(), spawn, loot->table_id, loot->minLevel, loot->maxLevel, &returnValue) && returnValue == 0) + continue; + } + bool entryAdded = false; + if (loot->minLevel == 0 && loot->maxLevel == 0 && (!loot->loot_tier || spawn->GetLootTier() >= loot->loot_tier) && (entryAdded = true)) // successful plan to add set entryAdded to true + ret.push_back(loot->table_id); + else { + if (spawn_level >= loot->minLevel && spawn_level <= loot->maxLevel && (!loot->loot_tier || spawn->GetLootTier() >= loot->loot_tier) && (entryAdded = true)) // successful plan to add set entryAdded to true + ret.push_back(loot->table_id); + } + + if(!entryAdded && returnValue) // DB override via LUA scripting + ret.push_back(loot->table_id); + } + } + + if (racial_loot_list.count(racial_id) > 0) { + vector::iterator itr; + for (itr = racial_loot_list[racial_id].begin(); itr != racial_loot_list[racial_id].end(); itr++) { + GlobalLoot* loot = *itr; + const char* zone_script = world.GetZoneScript(this->GetZoneID()); + returnValue = 0; // reset since this can override the database setting + if(zone_script) + { + if(lua_interface->RunZoneScriptWithReturn(zone_script, "loot_criteria_racial", spawn->GetZone(), spawn, loot->table_id, loot->minLevel, loot->maxLevel, &returnValue) && returnValue == 0) + continue; + } + bool entryAdded = false; + if (loot->minLevel == 0 && loot->maxLevel == 0 && (!loot->loot_tier || spawn->GetLootTier() >= loot->loot_tier) && (entryAdded = true)) // successful plan to add set entryAdded to true + ret.push_back(loot->table_id); + else { + if (spawn_level >= loot->minLevel && spawn_level <= loot->maxLevel && (!loot->loot_tier || spawn->GetLootTier() >= loot->loot_tier) && (entryAdded = true)) // successful plan to add set entryAdded to true + ret.push_back(loot->table_id); + } + + if(!entryAdded && returnValue) // DB override via LUA scripting + ret.push_back(loot->table_id); + } + } + + if (zone_loot_list.count(zone_id) > 0) { + vector::iterator itr; + for (itr = zone_loot_list[zone_id].begin(); itr != zone_loot_list[zone_id].end(); itr++) { + GlobalLoot* loot = *itr; + const char* zone_script = world.GetZoneScript(this->GetZoneID()); + returnValue = 0; // reset since this can override the database setting + if(zone_script) + { + if(lua_interface->RunZoneScriptWithReturn(zone_script, "loot_criteria_zone", spawn->GetZone(), spawn, loot->table_id, loot->minLevel, loot->maxLevel, &returnValue) && returnValue == 0) + continue; + } + bool entryAdded = false; + if (loot->minLevel == 0 && loot->maxLevel == 0 && (!loot->loot_tier || spawn->GetLootTier() >= loot->loot_tier) && (entryAdded = true)) // successful plan to add set entryAdded to true + ret.push_back(loot->table_id); + else { + if (spawn_level >= loot->minLevel && spawn_level <= loot->maxLevel && (!loot->loot_tier || spawn->GetLootTier() >= loot->loot_tier) && (entryAdded = true)) // successful plan to add set entryAdded to true + ret.push_back(loot->table_id); + } + + if(!entryAdded && returnValue) // DB override via LUA scripting + ret.push_back(loot->table_id); + } + } + + return ret; +} + +vector* ZoneServer::GetLootDrops(int32 table_id){ + if(!reloading && loot_drops.count(table_id) > 0) + return &(loot_drops[table_id]); + else + return 0; +} + +LootTable* ZoneServer::GetLootTable(int32 table_id){ + return loot_tables[table_id]; +} + +void ZoneServer::AddLocationTransporter(int32 zone_id, string message, float trigger_x, float trigger_y, float trigger_z, float trigger_radius, int32 destination_zone_id, float destination_x, float destination_y, float destination_z, float destination_heading, int32 cost, int32 unique_id){ + LocationTransportDestination* loc = new LocationTransportDestination; + loc->message = message; + loc->trigger_x = trigger_x; + loc->trigger_y = trigger_y; + loc->trigger_z = trigger_z; + loc->trigger_radius = trigger_radius; + loc->destination_zone_id = destination_zone_id; + loc->destination_x = destination_x; + loc->destination_y = destination_y; + loc->destination_z = destination_z; + loc->destination_heading = destination_heading; + loc->cost = cost; + loc->unique_id = unique_id; + MTransporters.lock(); + if(location_transporters.count(zone_id) == 0) + location_transporters[zone_id] = new MutexList(); + location_transporters[zone_id]->Add(loc); + MTransporters.unlock(); +} + +void ZoneServer::AddTransporter(int32 transport_id, int8 type, string name, string message, int32 destination_zone_id, float destination_x, float destination_y, float destination_z, float destination_heading, + int32 cost, int32 unique_id, int8 min_level, int8 max_level, int32 quest_req, int16 quest_step_req, int32 quest_complete, int32 map_x, int32 map_y, int32 expansion_flag, int32 holiday_flag, int32 min_client_version, + int32 max_client_version, int32 flight_path_id, int16 mount_id, int8 mount_red_color, int8 mount_green_color, int8 mount_blue_color){ + TransportDestination* transport = new TransportDestination; + transport->type = type; + transport->display_name = name; + transport->message = message; + transport->destination_zone_id = destination_zone_id; + transport->destination_x = destination_x; + transport->destination_y = destination_y; + transport->destination_z = destination_z; + transport->destination_heading = destination_heading; + transport->cost = cost; + transport->unique_id = unique_id; + + transport->min_level = min_level; + transport->max_level = max_level; + transport->req_quest = quest_req; + transport->req_quest_step = quest_step_req; + transport->req_quest_complete = quest_complete; + + transport->map_x = map_x; + transport->map_y = map_y; + + transport->expansion_flag = expansion_flag; + transport->holiday_flag = holiday_flag; + + transport->min_client_version = min_client_version; + transport->max_client_version = max_client_version; + + transport->flight_path_id = flight_path_id; + + transport->mount_id = mount_id; + transport->mount_red_color = mount_red_color; + transport->mount_green_color = mount_green_color; + transport->mount_blue_color = mount_blue_color; + + MTransporters.lock(); + transporters[transport_id].push_back(transport); + MTransporters.unlock(); +} + +void ZoneServer::GetTransporters(vector* returnList, Client* client, int32 transport_id){ + if (!returnList) + return; + + MTransporters.lock(); + if (transporters.count(transport_id) > 0) + { + vector list; + for (int i = 0; i < transporters[transport_id].size(); i++) + { + if (transporters[transport_id][i]->min_client_version && client->GetVersion() < transporters[transport_id][i]->min_client_version) + continue; + else if (transporters[transport_id][i]->max_client_version && client->GetVersion() > transporters[transport_id][i]->max_client_version) + continue; + + if (database.CheckExpansionFlags(this, transporters[transport_id][i]->expansion_flag) && database.CheckHolidayFlags(this, transporters[transport_id][i]->holiday_flag)) + { + returnList->push_back(transporters[transport_id][i]); + } + } + } + MTransporters.unlock(); +} + +MutexList* ZoneServer::GetLocationTransporters(int32 zone_id){ + MutexList* ret = 0; + MTransporters.lock(); + if(location_transporters.count(zone_id) > 0) + ret = location_transporters[zone_id]; + MTransporters.unlock(); + return ret; +} + +void ZoneServer::DeleteGlobalTransporters(){ + MTransporters.lock(); + map >::iterator itr; + vector::iterator transport_vector_itr; + for(itr = transporters.begin(); itr != transporters.end(); itr++){ + for(transport_vector_itr = itr->second.begin(); transport_vector_itr != itr->second.end(); transport_vector_itr++){ + safe_delete(*transport_vector_itr); + } + } + map* >::iterator itr2; + for(itr2 = location_transporters.begin(); itr2 != location_transporters.end(); itr2++){ + itr2->second->clear(true); + delete itr2->second; + } + transporters.clear(); + location_transporters.clear(); + MTransporters.unlock(); +} + +void ZoneServer::DeleteGlobalSpawns() { + ClearLootTables(); + + map::iterator npc_list_iter; + for(npc_list_iter=npc_list.begin();npc_list_iter!=npc_list.end();npc_list_iter++) { + safe_delete(npc_list_iter->second); + } + npc_list.clear(); + map::iterator object_list_iter; + for(object_list_iter=object_list.begin();object_list_iter!=object_list.end();object_list_iter++) { + safe_delete(object_list_iter->second); + } + object_list.clear(); + map::iterator groundspawn_list_iter; + for(groundspawn_list_iter=groundspawn_list.begin();groundspawn_list_iter!=groundspawn_list.end();groundspawn_list_iter++) { + safe_delete(groundspawn_list_iter->second); + } + groundspawn_list.clear(); + map::iterator widget_list_iter; + for(widget_list_iter=widget_list.begin();widget_list_iter!=widget_list.end();widget_list_iter++) { + safe_delete(widget_list_iter->second); + } + widget_list.clear(); + map::iterator sign_list_iter; + for(sign_list_iter=sign_list.begin();sign_list_iter!=sign_list.end();sign_list_iter++) { + safe_delete(sign_list_iter->second); + } + sign_list.clear(); + + /*map::iterator appearance_list_iter; + for(appearance_list_iter=npc_appearance_list.begin();appearance_list_iter!=npc_appearance_list.end();appearance_list_iter++) { + safe_delete(appearance_list_iter->second); + } + npc_appearance_list.clear();*/ + + + ClearEntityCommands(); + + DeleteGroundSpawnItems(); + DeleteGlobalTransporters(); + DeleteTransporterMaps(); +} + +void ZoneServer::AddTransportMap(int32 id, string name) { + MTransportMaps.writelock(__FUNCTION__, __LINE__); + m_transportMaps[id] = name; + MTransportMaps.releasewritelock(__FUNCTION__, __LINE__); +} + +bool ZoneServer::TransportHasMap(int32 id) { + bool ret = false; + + MTransportMaps.readlock(__FUNCTION__, __LINE__); + ret = m_transportMaps.count(id) > 0; + MTransportMaps.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +string ZoneServer::GetTransportMap(int32 id) { + string ret; + + MTransportMaps.readlock(__FUNCTION__, __LINE__); + if (m_transportMaps.count(id) > 0) + ret = m_transportMaps[id]; + MTransportMaps.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +void ZoneServer::DeleteTransporterMaps() { + MTransportMaps.writelock(__FUNCTION__, __LINE__); + m_transportMaps.clear(); + MTransportMaps.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::ReloadSpawns() { + if (reloading) + return; + + reloading = true; + world.SetReloadingSubsystem("Spawns"); + // Let every one in the zone know what is happening + HandleBroadcast("Reloading all spawns for this zone."); + DeleteGlobalSpawns(); + Depop(false, true); +} + +void ZoneServer::SendStateCommand(Spawn* spawn, int32 state) { + vector::iterator itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (itr = clients.begin(); itr != clients.end(); itr++) { + Client* client = *itr; + if (client && client->GetPlayer()->WasSentSpawn(spawn->GetID())) + ClientPacketFunctions::SendStateCommand(client, client->GetPlayer()->GetIDWithPlayerSpawn(spawn), state); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::AddFlightPath(int32 id, FlightPathInfo* info) { + if (m_flightPaths.count(id) > 0) { + LogWrite(ZONE__ERROR, 0, "Zone", "Duplicate flight path (%u)", id); + safe_delete(info); + return; + } + + m_flightPaths[id] = info; +} + +void ZoneServer::AddFlightPathLocation(int32 id, FlightPathLocation* location) { + if (m_flightPaths.count(id) == 0) { + LogWrite(ZONE__ERROR, 0, "Zone", "There is no flight info for this route (%u)", id); + safe_delete(location); + return; + } + + m_flightPathRoutes[id].push_back(location); +} + +void ZoneServer::DeleteFlightPaths() { + map >::iterator itr; + vector::iterator itr2; + map::iterator itr3; + + for (itr = m_flightPathRoutes.begin(); itr != m_flightPathRoutes.end(); itr++) { + for (itr2 = itr->second.begin(); itr2 != itr->second.end(); itr2++) { + safe_delete(*itr2); + } + + itr->second.clear(); + } + m_flightPathRoutes.clear(); + + for (itr3 = m_flightPaths.begin(); itr3 != m_flightPaths.end(); itr3++) { + safe_delete(itr3->second); + } + m_flightPaths.clear(); +} + +void ZoneServer::SendFlightPathsPackets(Client* client) { + // Only send a packet if there are flight paths + if (m_flightPathRoutes.size() > 0) { + PacketStruct* packet = configReader.getStruct("WS_FlightPathsMsg", client->GetVersion()); + if (packet) { + + int32 num_routes = m_flightPaths.size(); + packet->setArrayLengthByName("number_of_routes", num_routes); + packet->setArrayLengthByName("number_of_routes2", num_routes); + packet->setArrayLengthByName("number_of_routes3", num_routes); + packet->setArrayLengthByName("number_of_routes4", num_routes); + + map::iterator itr; + int32 i = 0; + for (itr = m_flightPaths.begin(); itr != m_flightPaths.end(); itr++, i++) { + packet->setArrayDataByName("route_length", m_flightPathRoutes[itr->first].size(), i); + packet->setArrayDataByName("ground_mount", itr->second->flying ? 0 : 1, i); + packet->setArrayDataByName("allow_dismount", itr->second->dismount ? 1 : 0, i); + + + packet->setSubArrayLengthByName("route_length2", m_flightPathRoutes[itr->first].size(), i); + vector::iterator itr2; + int32 j = 0; + for (itr2 = m_flightPathRoutes[itr->first].begin(); itr2 != m_flightPathRoutes[itr->first].end(); itr2++, j++) { + packet->setSubArrayDataByName("x", (*itr2)->X, i, j); + packet->setSubArrayDataByName("y", (*itr2)->Y, i, j); + packet->setSubArrayDataByName("z", (*itr2)->Z, i, j); + } + } + + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } +} + +int32 ZoneServer::GetFlightPathIndex(int32 id) { + int32 index = 0; + map::iterator itr; + for (itr = m_flightPaths.begin(); itr != m_flightPaths.end(); itr++, index++) { + if (itr->first == id) + return index; + } + + return -1; +} + +float ZoneServer::GetFlightPathSpeed(int32 id) { + float speed = 1; + + if (m_flightPaths.count(id) > 0) + speed = m_flightPaths[id]->speed; + + return speed; +} + +void ZoneServer::ProcessSpawnConditional(int8 condition) { + MSpawnLocationList.readlock(__FUNCTION__, __LINE__); + MSpawnList.readlock(__FUNCTION__, __LINE__); + map::iterator itr; + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + if (itr->second != NULL) // null itr->second still coming into ProcessSpawnConditional + { + SpawnLocation* loc = spawn_location_list[itr->second->GetSpawnLocationID()]; + if (loc && loc->conditional > 0) { + if ((loc->conditional & condition) != condition) { + Despawn(itr->second, 0); + } + } + } + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + + map::iterator itr2; + for (itr2 = spawn_location_list.begin(); itr2 != spawn_location_list.end(); itr2++) { + SpawnLocation* loc = itr2->second; + if (loc && loc->conditional > 0 && ((loc->conditional & condition) == condition)) + if (GetSpawnByLocationID(loc->placement_id) == NULL) + ProcessSpawnLocation(loc); + } + + MSpawnLocationList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::AddSpawnProximities(Spawn* newSpawn) { + Spawn* spawn = 0; + map::iterator itr; + MPendingSpawnListAdd.readlock(__FUNCTION__, __LINE__); + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + spawn = itr->second; + if (spawn && spawn != newSpawn) { + if (newSpawn->GetDatabaseID()) + spawn->AddSpawnToProximity(newSpawn->GetDatabaseID(), Spawn::SpawnProximityType::SPAWNPROXIMITY_DATABASE_ID); + if (newSpawn->GetSpawnLocationID()) + spawn->AddSpawnToProximity(newSpawn->GetSpawnLocationID(), Spawn::SpawnProximityType::SPAWNPROXIMITY_LOCATION_ID); + + if (spawn->GetDatabaseID()) + newSpawn->AddSpawnToProximity(spawn->GetDatabaseID(), Spawn::SpawnProximityType::SPAWNPROXIMITY_DATABASE_ID); + if (spawn->GetSpawnLocationID()) + newSpawn->AddSpawnToProximity(spawn->GetSpawnLocationID(), Spawn::SpawnProximityType::SPAWNPROXIMITY_LOCATION_ID); + } + } + + list::iterator itr2; + for (itr2 = pending_spawn_list_add.begin(); itr2 != pending_spawn_list_add.end(); itr2++) { + spawn = *itr2; + if (spawn && spawn != newSpawn) { + if (newSpawn->GetDatabaseID()) + spawn->AddSpawnToProximity(newSpawn->GetDatabaseID(), Spawn::SpawnProximityType::SPAWNPROXIMITY_DATABASE_ID); + if (newSpawn->GetSpawnLocationID()) + spawn->AddSpawnToProximity(newSpawn->GetSpawnLocationID(), Spawn::SpawnProximityType::SPAWNPROXIMITY_LOCATION_ID); + + if (spawn->GetDatabaseID()) + newSpawn->AddSpawnToProximity(spawn->GetDatabaseID(), Spawn::SpawnProximityType::SPAWNPROXIMITY_DATABASE_ID); + if (spawn->GetSpawnLocationID()) + newSpawn->AddSpawnToProximity(spawn->GetSpawnLocationID(), Spawn::SpawnProximityType::SPAWNPROXIMITY_LOCATION_ID); + } + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + MPendingSpawnListAdd.releasereadlock(__FUNCTION__, __LINE__); +} + +// we only call this inside a write lock +void ZoneServer::RemoveSpawnProximities(Spawn* oldSpawn) { + Spawn* spawn = 0; + map::iterator itr; + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + spawn = itr->second; + if (spawn && spawn != oldSpawn) { + if (oldSpawn->GetDatabaseID()) + spawn->RemoveSpawnFromProximity(oldSpawn->GetDatabaseID(), Spawn::SpawnProximityType::SPAWNPROXIMITY_DATABASE_ID); + if (oldSpawn->GetSpawnLocationID()) + spawn->RemoveSpawnFromProximity(oldSpawn->GetSpawnLocationID(), Spawn::SpawnProximityType::SPAWNPROXIMITY_LOCATION_ID); + + // don't need to remove oldSpawn proximities, we clear them all out + } + } +} + +void ZoneServer::SetSpawnScript(SpawnEntry* entry, Spawn* spawn) +{ + if (!entry || !spawn) + return; + + const char* script = 0; + + for (int x = 0; x < 3; x++) + { + switch (x) + { + case 0: + script = world.GetSpawnEntryScript(entry->spawn_entry_id); + break; + case 1: + script = world.GetSpawnLocationScript(entry->spawn_location_id); + break; + case 2: + script = world.GetSpawnScript(entry->spawn_id); + break; + } + + if (script && lua_interface && lua_interface->GetSpawnScript(script) != 0) + { + spawn->SetSpawnScript(string(script)); + break; + } + } +} + +vector ZoneServer::GetHouseItems(Client* client) +{ + if (!client->GetCurrentZone()->GetInstanceID() || !client->HasOwnerOrEditAccess()) + return std::vector(); + + PacketStruct* packet = configReader.getStruct("WS_HouseItemsList", client->GetVersion()); + + std::vector items; + map::iterator itr; + Spawn* spawn = 0; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + spawn = itr->second; + if (spawn && spawn->IsObject() && spawn->GetPickupItemID()) + { + HouseItem tmpItem; + tmpItem.item_id = spawn->GetPickupItemID(); + tmpItem.unique_id = spawn->GetPickupUniqueItemID(); + tmpItem.spawn_id = spawn->GetID(); + tmpItem.item = master_item_list.GetItem(spawn->GetPickupItemID()); + + if (!tmpItem.item) + continue; + + items.push_back(tmpItem); + } + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + + return items; +} + +void ZoneServer::SendHouseItems(Client* client) +{ + if (!client->GetCurrentZone()->GetInstanceID() || !client->HasOwnerOrEditAccess()) + return; + + PacketStruct* packet = configReader.getStruct("WS_HouseItemsList", client->GetVersion()); + + if(!packet) { + return; + } + + std::vector items = GetHouseItems(client); + + // setting this to 1 puts it on the door widget + packet->setDataByName("is_widget_door", 1); + packet->setArrayLengthByName("num_items", items.size()); + for (int i = 0; i < items.size(); i++) + { + HouseItem tmpItem = items[i]; + packet->setArrayDataByName("unique_id", tmpItem.unique_id, i); // unique_id is in fact the item_id... + packet->setArrayDataByName("item_name", tmpItem.item->name.c_str(), i); + packet->setArrayDataByName("status_reduction", tmpItem.item->houseitem_info->status_rent_reduction, i); + + // location, 0 = floor, 1 = ceiling + //packet->setArrayDataByName("location", 1, i, 0); + + // item_state int8 + // 0 = normal (cannot pick up item / move item / toggle visibility) + // 1 = virtual (toggle visibility available, no move item) + // 2 = hidden (cannot pick up item / move item / toggle visibility) + // 3 = virtual/hidden/toggle visibility + // 4 = none (cannot pick up item / move item / toggle visibility) + // 5 = none, toggle visibility (cannot pick up item / move item) + // 8 = none (cannot pick up item / move item / toggle visibility) + //packet->setArrayDataByName("item_state", tmpvalue, i, 0); + + // makes it so we don't have access to move item/retrieve item + // cannot use in conjunction with ui_tab_flag1/ui_tab_flag2 + //packet->setArrayDataByName("tradeable", 1, i); + //packet->setArrayDataByName("item_description", "failboat", i); + + // access to move item/retrieve item, do not use in conjunction with tradeable + packet->setArrayDataByName("ui_tab_flag1", 1, i, 0); + packet->setArrayDataByName("ui_tab_flag2", 1, i, 0); + + // both of these can serve as description fields (only one should be used they populate the same area below the item name) + //packet->setArrayDataByName("first_item_description", "test", i); + //packet->setArrayDataByName("second_item_description", "Description here!", i); + + packet->setArrayDataByName("icon", tmpItem.item->GetIcon(client->GetVersion()), i); + } + + EQ2Packet* pack = packet->serialize(); + client->QueuePacket(pack); + safe_delete(packet); +} + +Spawn* ZoneServer::GetSpawnFromUniqueItemID(int32 unique_id) +{ + if (!GetInstanceID() || GetInstanceType() != Instance_Type::PERSONAL_HOUSE_INSTANCE) + return nullptr; + + map::iterator itr; + Spawn* spawn = 0; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + spawn = itr->second; + if (spawn && spawn->IsObject() && spawn->GetPickupUniqueItemID() == unique_id) + { + Spawn* tmpSpawn = spawn; + MSpawnList.releasereadlock(); + return tmpSpawn; + } + } + MSpawnList.releasereadlock(); + + return nullptr; +} + +void ZoneServer::AddPendingSpawnRemove(int32 id) +{ + MPendingSpawnRemoval.writelock(__FUNCTION__, __LINE__); + m_pendingSpawnRemove.insert(make_pair(id,true)); + MPendingSpawnRemoval.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::ProcessSpawnRemovals() +{ + MSpawnList.writelock(__FUNCTION__, __LINE__); + MPendingSpawnRemoval.writelock(__FUNCTION__, __LINE__); + if (m_pendingSpawnRemove.size() > 0) { + map::iterator itr2; + for (itr2 = m_pendingSpawnRemove.begin(); itr2 != m_pendingSpawnRemove.end(); itr2++) { + spawn_list.erase(itr2->first); + subspawn_list[SUBSPAWN_TYPES::COLLECTOR].erase(itr2->first); + + std::map::iterator hsmitr = housing_spawn_map.find(itr2->first); + if(hsmitr != housing_spawn_map.end()) { + subspawn_list[SUBSPAWN_TYPES::HOUSE_ITEM_SPAWN].erase(hsmitr->second); + housing_spawn_map.erase(hsmitr); + } + } + + m_pendingSpawnRemove.clear(); + } + MPendingSpawnRemoval.releasewritelock(__FUNCTION__, __LINE__); + MSpawnList.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::AddSpawnToGroup(Spawn* spawn, int32 group_id) +{ + if( spawn->GetSpawnGroupID() > 0 ) + spawn->RemoveSpawnFromGroup(); + MutexList* groupList = &spawn_group_map.Get(group_id); + MutexList::iterator itr2 = groupList->begin(); + + while(itr2.Next()) + { + Spawn* groupSpawn = GetSpawnByID(itr2.value); + if(groupSpawn) + { + // found existing group member to add it in + spawn->AddSpawnToGroup(groupSpawn); + break; + } + } + groupList->Add(spawn->GetID()); + spawn->SetSpawnGroupID(group_id); +} + +void ZoneServer::QueueStateCommandToClients(int32 spawn_id, int32 state) +{ + if(spawn_id < 1) + return; + + MLuaQueueStateCmd.lock(); + lua_queued_state_commands.insert(make_pair(spawn_id, state)); + MLuaQueueStateCmd.unlock(); +} + +void ZoneServer::QueueDefaultCommand(int32 spawn_id, std::string command, float distance) +{ + if(spawn_id < 1) + return; + + MLuaQueueStateCmd.lock(); + lua_spawn_update_command[spawn_id].insert(make_pair(command,distance)); + MLuaQueueStateCmd.unlock(); +} + +void ZoneServer::ProcessQueuedStateCommands() // in a client list lock only +{ + vector::iterator itr; + + MLuaQueueStateCmd.lock(); + + if(lua_queued_state_commands.size() > 0) + { + std::map::iterator statecmds; + for(statecmds = lua_queued_state_commands.begin(); statecmds != lua_queued_state_commands.end(); statecmds++) + { + Spawn* spawn = GetSpawnByID(statecmds->first, false); + if(!spawn) + continue; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (itr = clients.begin(); itr != clients.end(); itr++) { + Client* client = *itr; + if (client && client->GetPlayer()->WasSentSpawn(spawn->GetID())) + ClientPacketFunctions::SendStateCommand(client, client->GetPlayer()->GetIDWithPlayerSpawn(spawn), statecmds->second); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + } + lua_queued_state_commands.clear(); + } + + if(lua_spawn_update_command.size() > 0) + { + std::map>::iterator updatecmds; + for(updatecmds = lua_spawn_update_command.begin(); updatecmds != lua_spawn_update_command.end(); updatecmds++) + { + Spawn* spawn = GetSpawnByID(updatecmds->first, false); + if(!spawn) + continue; + + std::map::iterator innermap; + for(innermap = lua_spawn_update_command[updatecmds->first].begin(); innermap != lua_spawn_update_command[updatecmds->first].end(); innermap++) + { + MClientList.readlock(__FUNCTION__, __LINE__); + for (itr = clients.begin(); itr != clients.end(); itr++) { + Client* client = *itr; + if (client && client->GetPlayer()->WasSentSpawn(spawn->GetID())) + client->SendDefaultCommand(spawn, innermap->first.c_str(), innermap->second); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + } + lua_spawn_update_command[updatecmds->first].clear(); + } + lua_spawn_update_command.clear(); + } + MLuaQueueStateCmd.unlock(); +} + +void ZoneServer::RemoveClientsFromZone(ZoneServer* zone) { + vector::iterator itr; + MClientList.readlock(__FUNCTION__, __LINE__); + for (itr = clients.begin(); itr != clients.end(); itr++) { + Client* client = *itr; + if(client->GetCurrentZone() == zone) { + client->SetCurrentZone(nullptr); + } + if(client->GetZoningDestination() == zone) { + client->SetZoningDestination(nullptr); + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::SendSubSpawnUpdates(SUBSPAWN_TYPES subtype) { + std::map::iterator subitr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for(subitr = subspawn_list[subtype].begin(); subitr != subspawn_list[subtype].end(); subitr++) { + subitr->second->changed = true; + subitr->second->info_changed = true; + AddChangedSpawn(subitr->second); + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); +} + +bool ZoneServer::HouseItemSpawnExists(int32 item_id) { + bool exists = false; + std::map::iterator subitr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + subitr = subspawn_list[SUBSPAWN_TYPES::HOUSE_ITEM_SPAWN].find(item_id); + if(subitr != subspawn_list[SUBSPAWN_TYPES::HOUSE_ITEM_SPAWN].end()) { + exists = true; + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + return exists; +} + +void ZoneServer::ProcessPendingSpawns() { + MPendingSpawnListAdd.writelock(__FUNCTION__, __LINE__); + list::iterator itr2; + for (itr2 = pending_spawn_list_add.begin(); itr2 != pending_spawn_list_add.end(); itr2++) { + Spawn* spawn = *itr2; + + MSpawnList.writelock(__FUNCTION__, __LINE__); + if (spawn) + spawn_list[spawn->GetID()] = spawn; + + if(spawn->IsCollector()) { + subspawn_list[SUBSPAWN_TYPES::COLLECTOR].insert(make_pair(spawn->GetID(),spawn)); + } + if(spawn->GetPickupItemID()) { + subspawn_list[SUBSPAWN_TYPES::HOUSE_ITEM_SPAWN].insert(make_pair(spawn->GetPickupItemID(),spawn)); + housing_spawn_map.insert(make_pair(spawn->GetID(), spawn->GetPickupItemID())); + } + MSpawnList.releasewritelock(__FUNCTION__, __LINE__); + + CheckSpawnRange(spawn); + } + + pending_spawn_list_add.clear(); + MPendingSpawnListAdd.releasewritelock(__FUNCTION__, __LINE__); + spawn_check_add.Trigger(); +} + +void ZoneServer::AddSpawnToGrid(Spawn* spawn, int32 grid_id) { + if(spawn->GetID() == 0 || spawn->IsDeletedSpawn()) + return; + + MGridMaps.lock_shared(); + std::map::iterator grids = grid_maps.find(grid_id); + if(grids != grid_maps.end()) { + grids->second->MSpawns.lock(); + grids->second->spawns.insert(make_pair(spawn->GetID(), spawn)); + grids->second->MSpawns.unlock(); + } + else { + MGridMaps.unlock_shared(); + MGridMaps.lock(); + GridMap* gm = new GridMap; + gm->grid_id = grid_id; + gm->spawns.insert(make_pair(spawn->GetID(), spawn)); + grid_maps.insert(make_pair(grid_id, gm)); + MGridMaps.unlock(); + return; + } + + MGridMaps.unlock_shared(); +} + +void ZoneServer::RemoveSpawnFromGrid(Spawn* spawn, int32 grid_id) { + std::shared_lock lock(MGridMaps); + std::map::iterator grids = grid_maps.find(grid_id); + if(grids != grid_maps.end()) { + grids->second->MSpawns.lock(); + if(grids->second->spawns.count(spawn->GetID()) > 0) { + grids->second->spawns.erase(spawn->GetID()); + } + grids->second->MSpawns.unlock(); + } +} + +int32 ZoneServer::GetSpawnCountInGrid(int32 grid_id) { + int32 count = 0; + std::shared_lock lock(MGridMaps); + std::map::iterator grids = grid_maps.find(grid_id); + if(grids != grid_maps.end()) { + grids->second->MSpawns.lock_shared(); + count = grids->second->spawns.size(); + grids->second->MSpawns.unlock_shared(); + } + + return count; +} + +void ZoneServer::SendClientSpawnListInGrid(Client* client, int32 grid_id){ + std::shared_lock lock(MGridMaps); + + Spawn* spawn = nullptr; + client->Message(CHANNEL_COLOR_RED, "Grid ID %u has %u spawns.", grid_id, GetSpawnCountInGrid(grid_id)); + std::map::iterator grids = grid_maps.find(grid_id); + if(grids != grid_maps.end()) { + grids->second->MSpawns.lock_shared(); + typedef map SpawnMapType; + for( SpawnMapType::iterator it = grids->second->spawns.begin(); it != grids->second->spawns.end(); ++it ) { + spawn = it->second; + client->Message(CHANNEL_COLOR_YELLOW, "Spawn %s (%u), Loc X/Y/Z: %f/%f/%f.", spawn->GetName(), spawn->GetID(), spawn->GetX(), spawn->GetY(), spawn->GetZ()); + } + grids->second->MSpawns.unlock_shared(); + } +} + +void ZoneServer::AddIgnoredWidget(int32 id) { + std::unique_lock lock(MIgnoredWidgets); + if(ignored_widgets.find(id) == ignored_widgets.end()) { + ignored_widgets.insert(make_pair(id,true)); + } +} diff --git a/source/WorldServer/zoneserver.h b/source/WorldServer/zoneserver.h new file mode 100644 index 0000000..047b8e0 --- /dev/null +++ b/source/WorldServer/zoneserver.h @@ -0,0 +1,1168 @@ +/* + 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 . +*/ +#ifndef ZONESERVER_H +#define ZONESERVER_H + +#include +#include + +#include "../common/linked_list.h" +#include "../common/timer.h" +#include "../common/queue.h" +#include "../common/servertalk.h" +#include "../common/TCPConnection.h" +#include "WorldTCPConnection.h" +#include "../common/Mutex.h" +#include "../common/DataBuffer.h" +#include "net.h" +#include "Player.h" +#include "Combat.h" +#include +#include +#include +#include "MutexList.h" +#include "MutexMap.h" +#include "MutexVector.h" +#include "NPC.h" +#include "Widget.h" +#include "Object.h" +#include "GroundSpawn.h" +#include "Sign.h" +#include "Zone/map.h" +#include "Zone/pathfinder_interface.h" +#include "Zone/mob_movement_manager.h" +#include "Zone/region_map.h" + +extern NetConnection net; // needs to be here or compile errors in commands.cpp +class SpellProcess; +class TradeskillMgr; +class Bot; + +#define EXPANSION_UNKNOWN 1 +#define EXPANSION_UNKNOWN2 64 +#define EXPANSION_UNKNOWN3 128 +#define EXPANSION_UNKNOWN4 256 +#define EXPANSION_UNKNOWN5 512 +#define EXPANSION_DOF 1024 +#define EXPANSION_KOS 2048 +#define EXPANSION_EOF 4096 +#define EXPANSION_ROK 8192 +#define EXPANSION_TSO 16384 +#define EXPANSION_DOV 65536 // This enables DoV and CoE AA tree's lower values disable both trees +// Can't verify these 3 values +// 32768 - SF +// 131072 - AoD + +#define SPAWN_SCRIPT_SPAWN 0 +#define SPAWN_SCRIPT_RESPAWN 1 +#define SPAWN_SCRIPT_ATTACKED 2 +#define SPAWN_SCRIPT_TARGETED 3 +#define SPAWN_SCRIPT_HAILED 4 +#define SPAWN_SCRIPT_DEATH 5 +#define SPAWN_SCRIPT_KILLED 6 +#define SPAWN_SCRIPT_AGGRO 7 +#define SPAWN_SCRIPT_HEALTHCHANGED 8 +#define SPAWN_SCRIPT_RANDOMCHAT 9 +#define SPAWN_SCRIPT_CONVERSATION 10 +#define SPAWN_SCRIPT_TIMER 11 +#define SPAWN_SCRIPT_CUSTOM 12 +#define SPAWN_SCRIPT_HAILED_BUSY 13 +#define SPAWN_SCRIPT_CASTED_ON 14 +#define SPAWN_SCRIPT_AUTO_ATTACK_TICK 15 +#define SPAWN_SCRIPT_COMBAT_RESET 16 +#define SPAWN_SCRIPT_GROUP_DEAD 17 +#define SPAWN_SCRIPT_HEAR_SAY 18 +#define SPAWN_SCRIPT_PRESPAWN 19 +#define SPAWN_SCRIPT_USEDOOR 20 +#define SPAWN_SCRIPT_BOARD 21 +#define SPAWN_SCRIPT_DEBOARD 22 + +#define SPAWN_CONDITIONAL_NONE 0 +#define SPAWN_CONDITIONAL_DAY 1 +#define SPAWN_CONDITIONAL_NIGHT 2 +#define SPAWN_CONDITIONAL_NOT_RAINING 4 +#define SPAWN_CONDITIONAL_RAINING 8 + +#define MAX_REVIVEPOINT_DISTANCE 1000 + +/* JA: TODO Turn into R_World Rules */ +#define SEND_SPAWN_DISTANCE 250 /* when spawns appear visually to the client */ +#define HEAR_SPAWN_DISTANCE 30 /* max distance a client can be from a spawn to 'hear' it */ +#define MAX_CHASE_DISTANCE 80 +#define REMOVE_SPAWN_DISTANCE 300 // increased distance between send/remove is ideal, this makes sure there is no overlap if a 'fast' client (AKA GM warp speed) + +#define TRACKING_STOP 0 +#define TRACKING_START 1 +#define TRACKING_UPDATE 2 +#define TRACKING_CLOSE_WINDOW 3 + +#define TRACKING_TYPE_ENTITIES 1 +#define TRACKING_TYPE_HARVESTABLES 2 + +#define TRACKING_SPAWN_TYPE_PC 0 +#define TRACKING_SPAWN_TYPE_NPC 1 + +#define WAYPOINT_CATEGORY_GROUP 0 +#define WAYPOINT_CATEGORY_QUESTS 1 +#define WAYPOINT_CATEGORY_PEOPLE 2 +#define WAYPOINT_CATEGORY_PLACES 3 +#define WAYPOINT_CATEGORY_USER 4 +#define WAYPOINT_CATEGORY_DIRECTIONS 5 +#define WAYPOINT_CATEGORY_TRACKING 6 +#define WAYPOINT_CATEGORY_HOUSES 7 +#define WAYPOINT_CATEGORY_MAP 8 + +struct PlayerProximity{ + float distance; + string in_range_lua_function; + string leaving_range_lua_function; + map clients_in_proximity; +}; + +struct LocationProximity { + float x; + float y; + float z; + float max_variation; + string in_range_lua_function; + string leaving_range_lua_function; + map clients_in_proximity; +}; + +struct LocationGrid { + int32 id; + int32 grid_id; + string name; + bool include_y; + bool discovery; + MutexList locations; + MutexMap players; +}; + +struct GridMap { + int32 grid_id; + std::map spawns; + mutable std::shared_mutex MSpawns; +}; + +struct TrackedSpawn { + Spawn* spawn; + float distance; +}; + +struct HouseItem { + int32 spawn_id; + int32 item_id; + int32 unique_id; + Item* item; +}; + +class Widget; +class Client; +class Sign; +class Object; +class GroundSpawn; +struct GroundSpawnEntry; +struct GroundSpawnEntryItem; +struct LootTable; +struct LootDrop; +struct GlobalLoot; +struct TransportDestination; +struct LocationTransportDestination; + +#ifdef WIN32 + void ZoneLoop(void *tmp); + void SpawnLoop(void *tmp); + void SendInitialSpawns(void *tmp); + void SendLevelChangedSpawns(void*tmp); +#else + void *ZoneLoop(void *tmp); + void *SpawnLoop(void *tmp); + void *SendInitialSpawns(void *tmp); + void *SendLevelChangedSpawns(void *tmp); +#endif +using namespace std; +struct RevivePoint{ + int32 id; + int32 zone_id; //usually this zone, but not always + string location_name; + float x; + float y; + float z; + float heading; + bool always_included; +}; + +struct SpawnScriptTimer { + int32 timer; + int32 spawn; + int32 player; + string function; + int32 current_count; + int32 max_count; +}; + +enum Instance_Type { + NONE, + GROUP_LOCKOUT_INSTANCE, + GROUP_PERSIST_INSTANCE, + RAID_LOCKOUT_INSTANCE, + RAID_PERSIST_INSTANCE, + SOLO_LOCKOUT_INSTANCE, + SOLO_PERSIST_INSTANCE, + TRADESKILL_INSTANCE, // allows anyone to enter, server searches for the first instance that is available + PUBLIC_INSTANCE, // same as tradeskill, except dead spawns are tracked + PERSONAL_HOUSE_INSTANCE, + GUILD_HOUSE_INSTANCE, + QUEST_INSTANCE +}; + +struct FlightPathInfo { + float speed; + bool flying; + bool dismount; +}; + +struct FlightPathLocation { + float X; + float Y; + float Z; +}; + +struct ZoneInfoSlideStructInfo { + float unknown1[2]; + int32 unknown2[2]; + int32 unknown3; + int32 unknown4; + char slide[128]; + char voiceover[128]; + int32 key1; + int32 key2; +}; +struct ZoneInfoSlideStructTransitionInfo { + int32 transition_x; + int32 transition_y; + float transition_zoom; + float transition_time; +}; +struct ZoneInfoSlideStruct { + ZoneInfoSlideStructInfo* info; + vector slide_transition_info; +}; + +enum SUBSPAWN_TYPES { + COLLECTOR = 0, + HOUSE_ITEM_SPAWN = 1, + MAX_SUBSPAWN_TYPE = 20 +}; + +// need to attempt to clean this up and add xml comments, remove unused code, find a logical way to sort the functions maybe by get/set/process/add etc... +class ZoneServer { +public: + ZoneServer(const char* file); + ~ZoneServer(); + + void IncrementIncomingClients(); + void DecrementIncomingClients(); + void Init(); + bool Process(); + bool SpawnProcess(); + + ZoneInfoSlideStruct* GenerateSlideStruct(float unknown1a, float unknown1b, int32 unknown2a, int32 unknown2b, int32 unknown3, int32 unknown4, const char* slide, const char* voiceover, int32 key1, int32 key2); + void AddZoneInfoSlideStructTransitionInfo(ZoneInfoSlideStruct* info, int32 x, int32 y, float zoom, float transition_time); + vector* GenerateTutorialSlides(); + + void LoadRevivePoints(vector* revive_points); + vector* GetRevivePoints(Client* client); + RevivePoint* GetRevivePoint(int32 id); + + void AddClient(Client* client); + + void SimpleMessage(int8 type, const char* message, Spawn* from, float distance, bool send_to_sender = true); + void HandleChatMessage(Spawn* from, const char* to, int16 channel, const char* message, float distance = 0, const char* channel_name = 0, bool show_bubble = true, int32 language = 0); + void HandleChatMessage(Client* client, Spawn* from, const char* to, int16 channel, const char* message, float distance = 0, const char* channel_name = 0, bool show_bubble = true, int32 language = 0); + void HandleBroadcast(const char* message); + void HandleAnnouncement(const char* message); + + int16 SetSpawnTargetable(Spawn* spawn, float distance); + int16 SetSpawnTargetable(int32 spawn_id); + void ApplySetSpawnCommand(Client* client, Spawn* target, int8 type, const char* value); + void SetSpawnCommand(Spawn* spawn, int8 type, char* value, Client* client = 0); + void SetSpawnCommand(int32 spawn_id, int8 type, char* value, Client* client = 0); + void AddLoot(NPC* npc, Spawn* killer = nullptr, GroupLootMethod loot_method = GroupLootMethod::METHOD_FFA, int8 item_rarity = 0, int32 group_id = 0); + + NPC* AddNPCSpawn(SpawnLocation* spawnlocation, SpawnEntry* spawnentry); + Object* AddObjectSpawn(SpawnLocation* spawnlocation, SpawnEntry* spawnentry); + GroundSpawn* AddGroundSpawn(SpawnLocation* spawnlocation, SpawnEntry* spawnentry); + Widget* AddWidgetSpawn(SpawnLocation* spawnlocation, SpawnEntry* spawnentry); + Sign* AddSignSpawn(SpawnLocation* spawnlocation, SpawnEntry* spawnentry); + void AddSpawn(Spawn* spawn); + void RemoveDeadEnemyList(Spawn* spawn); + void RemoveDeadSpawn(Spawn* spawn); + + void AddSpawnGroupLocation(int32 group_id, int32 location_id, int32 spawn_location_id); + void AddSpawnGroupAssociation(int32 group_id1, int32 group_id2); + + void AddSpawnGroupChance(int32 group_id, float percent); + + void RemoveSpawn(Spawn* spawn, bool delete_spawn = true, bool respawn = true, bool lock = true, bool erase_from_spawn_list = true, bool lock_spell_process = false); + void ProcessSpawnLocations(); + void SendQuestUpdates(Client* client, Spawn* spawn = 0); + + EQ2Packet* GetZoneInfoPacket(Client* client); + Spawn* FindSpawn(Player* searcher, const char* name); + bool CallSpawnScript(Spawn* npc, int8 type, Spawn* spawn = 0, const char* message = 0, bool is_door_open = false, sint32 input_value = 0, sint32* return_value = 0); + void SendSpawnVisualState(Spawn* spawn, int16 type); + void SendSpellFailedPacket(Client* client, int16 error); + void SendInterruptPacket(Spawn* interrupted, LuaSpell* spell, bool fizzle=false); + void HandleEmote(Spawn* originator, string name); + Spawn* GetSpawnByDatabaseID(int32 id); + Spawn* GetSpawnByID(int32 id, bool spawnListLocked=false); + + void PlaySoundFile(Client* client, const char* name, float origin_x, float origin_y, float origin_z); + void SendZoneSpawns(Client* client); + void StartZoneInitialSpawnThread(Client* client); + void SendSpawnChanges(); + void SendSpawnChanges(Spawn* spawn); + void SendSpawnChanges(Spawn* spawn, Client* client, bool override_changes = false, bool override_vis_changes = false); + void SendSpawnChangesByDBID(int32 spawn_id, Client* client, bool override_changes = false, bool override_vis_changes = false); + void SendPlayerPositionChanges(Player* player); + + void UpdateVitality(float amount); + + vector GetPlayers(); + + void KillSpawn(bool spawnListLocked, Spawn* dead, Spawn* killer, bool send_packet = true, int8 type = 0, int8 damage_type = 0, int16 kill_blow_type = 0); + + void SendDamagePacket(Spawn* attacker, Spawn* victim, int8 type1, int8 type2, int8 damage_type, int16 damage, const char* spell_name); + void SendHealPacket(Spawn* caster, Spawn* target, int16 type, int32 heal_amt, const char* spell_name); + + void SendCastSpellPacket(LuaSpell* spell, Entity* caster, int32 spell_visual_override = 0, int16 casttime_override = 0xFFFF); + void SendCastSpellPacket(int32 spell_visual, Spawn* target, Spawn* caster = 0); + void SendCastEntityCommandPacket(EntityCommand* entity_command, int32 spawn_id, int32 target_id); + void TriggerCharSheetTimer(); + + /// Sends the game time packet to all connected clients + void SendTimeUpdateToAllClients(); + void AddWidgetTimer(Spawn* widget, float time); + bool HasWidgetTimer(Spawn* widget); + + void Despawn(Spawn* spawn, int32 timer); + + void RepopSpawns(Client* client, Spawn* spawn); + bool AddCloseSpawnsToSpawnGroup(Spawn* spawn, float radius); + void Depop(bool respawns = false, bool repop = false); + + Spawn* GetSpawnGroup(int32 id); + + void AddEnemyList(NPC* npc); + + void ReloadClientQuests(); + void SendAllSpawnsForLevelChange(Client* client); + void SendAllSpawnsForSeeInvisChange(Client* client); + void SendAllSpawnsForVisChange(Client* client, bool limitToEntities=true); + + void AddLocationGrid(LocationGrid* grid); + void RemoveLocationGrids(); + + void DeleteTransporters(); + + void CheckTransporters(Client* client); + + void WritePlayerStatistics(); + + bool SendRadiusSpawnInfo(Client* client, float radius); + void FindSpawn(Client* client, char* regSearchStr); + + volatile bool spawnthread_active; + volatile bool combatthread_active; + volatile int8 initial_spawn_threads_active; + volatile bool client_thread_active; + void AddChangedSpawn(Spawn* spawn); + + void AddDamagedSpawn(Spawn* spawn); + + void AddDrowningVictim(Player* player); + void RemoveDrowningVictim(Player* player); + Client* GetDrowningVictim(Player* player); + + void DeleteSpellProcess(); + void LoadSpellProcess(); + void LockAllSpells(Player* player); + void UnlockAllSpells(Player* player); + void RemoveSpellTimersFromSpawn(Spawn* spawn, bool remove_all, bool delete_recast = true, bool call_expire_function = true, bool lock_spell_process = false); + void Interrupted(Entity* caster, Spawn* interruptor, int16 error_code, bool cancel = false, bool from_movement = false); + Spell* GetSpell(Entity* caster); + void ProcessSpell(Spell* spell, Entity* caster, Spawn* target = 0, bool lock = true, bool harvest_spell = false, LuaSpell* customSpell = 0, int16 custom_cast_time = 0, bool in_heroic_opp = false); + void ProcessEntityCommand(EntityCommand* entity_command, Entity* caster, Spawn* target, bool lock = true); + void AddPlayerTracking(Player* player); + void RemovePlayerTracking(Player* player, int8 mode); + + void SendUpdateTitles(Client *client, Title *suffix = 0, Title *prefix = 0); + void SendUpdateTitles(Spawn *spawn, Title *suffix = 0, Title *prefix = 0); + + void RemoveTargetFromSpell(LuaSpell* spell, Spawn* target, bool remove_caster = false); + + /// Set the rain levl in the zone + /// Level of rain in the zone 0.0 - 1.1 (rain starts at 0.76) + void SetRain(float val); + + /// Sets the wind direction + /// Direction in degrees to set the wind + void SetWind(float val); + + /// Handles zone-wide weather changes + void ProcessWeather(); + + Spawn* GetClosestTransportSpawn(float x, float y, float z); + Spawn* GetTransportByRailID(sint64 rail_id); + + void ResurrectSpawn(Spawn* spawn, Client* client); + + void HidePrivateSpawn(Spawn* spawn); + Client* GetClientByName(char* name); + Client* GetClientByCharID(int32 charid); + + bool SetPlayerTargetByName(Client* originator, char* targetName, float distance); + std::vector GetGridsByLocation(Spawn* originator, glm::vec3 loc, float distance); + /// Gets spawns for a true AoE spell + std::vector> GetAttackableSpawnsByDistance(Spawn* spawn, float distance); + + // Comparator function to sort by the value (second element of the pair) + static bool compareByValue(const std::pair& a, const std::pair& b) { + return a.second < b.second; + } + + void StartZoneSpawnsForLevelThread(Client* client); + + void SendDispellPacket(Entity* caster, Spawn* target, string dispell_name, string spell_name, int8 dispell_type); + + void SetupInstance(int32 createdInstanceID=0); + void SendUpdateDefaultCommand(Spawn* spawn, const char* command, float distance, Spawn* toplayer = NULL); + + map* GetSpawnLocationsByGroup(int32 group_id); + + IPathfinder* pathing; + MobMovementManager* movementMgr; + + /**************************************************** + Following functions are only used for LUA commands + ****************************************************/ + + int32 GetClosestLocation(Spawn* spawn); + Spawn* GetClosestSpawn(Spawn* spawn, int32 spawn_id); + SpawnLocation* GetSpawnLocation(int32 id); + void PlayFlavor(Client* client, Spawn* spawn, const char* mp3, const char* text, const char* emote, int32 key1, int32 key2, int8 language); + void PlayVoice(Client* client, Spawn* spawn, const char* mp3, int32 key1, int32 key2); + void PlayFlavor(Spawn* spawn, const char* mp3, const char* text, const char* emote, int32 key1, int32 key2, int8 language); + void PlayFlavorID(Spawn* spawn, int8 type, int32 id, int16 index, int8 language); + void PlayVoice(Spawn* spawn, const char* mp3, int32 key1, int32 key2); + void SendThreatPacket(Spawn* caster, Spawn* target, int32 threat_amt, const char* spell_name); + void SendYellPacket(Spawn* yeller, float max_distance=50.0f); + void KillSpawnByDistance(Spawn* spawn, float max_distance, bool include_players = false, bool send_packet = false); + void SpawnSetByDistance(Spawn* spawn, float max_distance, string field, string value); + void AddSpawnScriptTimer(SpawnScriptTimer* timer); + Spawn* GetSpawnByLocationID(int32 location_id); + void AddMovementNPC(Spawn* spawn); + void AddPlayerProximity(Spawn* spawn, float distance, string in_range_function, string leaving_range_function); + void AddLocationProximity(float x, float y, float z, float max_variation, string in_range_function, string leaving_range_function); + void PlayAnimation(Spawn* spawn, int32 visual_state, Spawn* spawn2 = 0, int8 type = 1); + void AddTransportSpawn(Spawn* spawn); + vector GetSpawnsByID(int32 id); + vector GetSpawnsByRailID(sint64 rail_id); + void RemovePlayerPassenger(int32 char_id); + bool IsDusk() { return isDusk; } // never used, probably meant for lua though + + + /**************************************************** + Following functions are all contained in the header + ****************************************************/ + + inline const char* GetZoneName() { return zone_name; } + void SetZoneName(char* new_zone) { + if( strlen(new_zone) >= sizeof zone_name ) + return; + strcpy(zone_name, new_zone); + } + inline const char* GetZoneFile() { return zone_file; } + void SetZoneFile(char* zone) { + if (strlen(zone) >= sizeof zone_file) + return; + strcpy(zone_file, zone); + } + inline const char* GetZoneSkyFile() { return zonesky_file; } + void SetZoneSkyFile(char* zone) { + if (strlen(zone) >= sizeof zonesky_file) + return; + strcpy(zonesky_file, zone); + } + inline const char* GetZoneDescription() { return zone_description; } + void SetZoneDescription(char* desc) { + if( strlen(desc) >= sizeof zone_description ) + return; + strcpy(zone_description, desc); + } + + void SetUnderWorld(float under){ underworld = under; } + float GetUnderWorld(){ return underworld; } + + inline int32 GetZoneID() { return zoneID; } + void SetZoneID(int32 new_id){ zoneID = new_id; } + + inline bool IsCityZone() { return cityzone; } + inline bool AlwaysLoaded() { return always_loaded; } + void SetCityZone(bool val) { cityzone = val; } + void SetAlwaysLoaded(bool val) { always_loaded = val; } + inline int32& NumPlayers() { return pNumPlayers; } + void SetMinimumStatus(sint16 minStatus) { minimumStatus = minStatus; } + sint16 GetMinimumStatus() { return minimumStatus; } + void SetMinimumLevel(int16 minLevel) { minimumLevel = minLevel; } + void SetMaximumLevel(int16 maxLevel) { maximumLevel = maxLevel; } + void SetMinimumVersion(int16 minVersion) { minimumVersion = minVersion; } + int16 GetMinimumLevel() { return minimumLevel; } + int16 GetMaximumLevel() { return maximumLevel; } + int16 GetMinimumVersion() { return minimumVersion; } + inline bool GetZoneLockState() { return locked; } // JA: /zone lock|unlock + void SetZoneLockState(bool lock_state) { locked = lock_state; } // JA: /zone lock|unlock + int32 GetInstanceID() { return instanceID; } + bool IsInstanceZone() { return isInstance; } + + void SetShutdownTimer(int val){ + shutdownTimer.SetTimer(val*1000); + } + + void AddSpawnLocation(int32 id, SpawnLocation* spawnlocation) { + MSpawnLocationList.writelock(__FUNCTION__, __LINE__); + if (spawn_location_list.count(id) > 0) + safe_delete(spawn_location_list[id]); + spawn_location_list[id] = spawnlocation; + MSpawnLocationList.releasewritelock(__FUNCTION__, __LINE__); + } + + void SetInstanceType(int16 type) { InstanceType = (Instance_Type)type; if(type>0)isInstance=true; else isInstance=false; } + Instance_Type GetInstanceType() { return InstanceType; } + float GetSafeX(){ return safe_x; } + float GetSafeY(){ return safe_y; } + float GetSafeZ(){ return safe_z; } + float GetSafeHeading() { return safe_heading; } + void SetSafeX(float val){ safe_x = val; } + void SetSafeY(float val){ safe_y = val; } + void SetSafeZ(float val){ safe_z = val; } + void SetSafeHeading(float val) { safe_heading = val; } + float GetXPModifier() { return xp_mod; } + void SetXPModifier(float val) { xp_mod = val; } + void SetZoneMOTD(string z_motd) { zone_motd = z_motd; } + string GetZoneMOTD() { return zone_motd; } + bool isZoneShuttingDown ( ) { return zoneShuttingDown; } + void Shutdown(){ zoneShuttingDown = true; } + int32 GetClientCount(){ return clients.size(); } + int32 GetDefaultLockoutTime() { return def_lockout_time; } + int32 GetDefaultReenterTime() { return def_reenter_time; } + int32 GetDefaultResetTime() { return def_reset_time; } + int8 GetForceGroupZoneOption() { return group_zone_option; } + void SetDefaultLockoutTime(int32 val) { def_lockout_time = val; } + void SetDefaultReenterTime(int32 val) { def_reenter_time = val; } + void SetDefaultResetTime(int32 val) { def_reset_time = val; } + void SetForceGroupZoneOption(int8 val) { group_zone_option = val; } + SpellProcess* GetSpellProcess() {return spellProcess;} + bool FinishedDepop(){ return finished_depop; } + + /// Returns the Tradeskill Manager for this zone + TradeskillMgr* GetTradeskillMgr() { return tradeskillMgr; } + + + // had to add these to access weather from Commands + bool isWeatherEnabled() { return weather_enabled; } + void SetWeatherEnabled(bool val) { weather_enabled = val; } + bool isWeatherAllowed() { return weather_allowed; } + void SetWeatherAllowed(bool val) { weather_allowed = val; } + int8 GetWeatherType() { return weather_type; } + void SetWeatherType(int8 val) { weather_type = val; } + int32 GetWeatherFrequency() { return weather_frequency; } + void SetWeatherFrequency(int32 val) { weather_frequency = val; } + float GetWeatherMinSeverity() { return weather_min_severity; } + void SetWeatherMinSeverity(float val) { weather_min_severity = val; } + float GetWeatherMaxSeverity() { return weather_max_severity; } + void SetWeatherMaxSeverity(float val) { weather_max_severity = val; } + float GetWeatherChangeAmount() { return weather_change_amount; } + void SetWeatherChangeAmount(float val) { weather_change_amount = val; } + float GetWeatherDynamicOffset() { return weather_dynamic_offset; } + void SetWeatherDynamicOffset(float val) { weather_dynamic_offset = val; } + int8 GetWeatherChance() { return weather_change_chance; } + void SetWeatherChance(int8 val) { weather_change_chance = val; } + float GetCurrentWeather() { return weather_current_severity; } + void SetCurrentWeather(float val) { weather_current_severity = val; } + int8 GetWeatherPattern() { return weather_pattern; } + void SetWeatherPattern(int8 val) { weather_pattern = val; } + void SetWeatherLastChangedTime(int32 val) { weather_last_changed_time = val; } + + int32 GetExpansionFlag() { return expansion_flag; } + void SetExpansionFlag(int32 val) { expansion_flag = val; } + + int32 GetHolidayFlag() { return holiday_flag; } + void SetHolidayFlag(int32 val) { holiday_flag = val; } + + int32 GetCanBind() { return can_bind; } + void SetCanBind(int32 val) { can_bind = val; } + + bool GetCanGate() { return can_gate; } + void SetCanGate(int32 val) { can_gate = val; } + + bool GetCanEvac() { return can_evac; } + void SetCanEvac(int32 val) { can_evac = val; } + + void RemoveClientImmediately(Client* client); + + void ClearHate(Entity* entity); + + + + /**************************************************** + Following functions are pending deletion, left in for + now just to make sure one won't be of future use. + ****************************************************/ + //void RemoveFromRangeMap(Client* client); // never used? + //void AddSpawnAssociatedGroup(vector* ret, int32 group_id); // never used, not even any code for it + //inline const char* GetCAddress() { return clientaddress; } // never used? + //inline int16 GetCPort() { return clientport; } // never used? + //inline bool IsBootingUp() { return BootingUp; } // never used? + //int32 GetShutdownTimer() {return shutdownTimer.GetTimerTime();} // never used + + // Following were private + + //char clientaddress[250]; // never used + //int16 clientport; // never used + //bool BootingUp; // never used + //bool authenticated; // never used? + //int16 next_index; // never used + + + + + + + + + + + + + + + + + + void AddFlightPath(int32 id, FlightPathInfo* info); + void AddFlightPathLocation(int32 id, FlightPathLocation* location); + void DeleteFlightPaths(); + void SendFlightPathsPackets(Client* client); + int32 GetFlightPathIndex(int32 id); + float GetFlightPathSpeed(int32 id); + + + void SendSpawn(Spawn* spawn, Client* client); // moved from private to public for bots + + void ProcessSpawnConditional(int8 condition); + + void SetSpawnStructs(Client* client); + + void AddSpawnProximities(Spawn* spawn); + void RemoveSpawnProximities(Spawn* spawn); + void SetSpawnScript(SpawnEntry* entry, Spawn* spawn); + bool IsLoading() { + return LoadingData; + } + + vector GetHouseItems(Client* client); + Spawn* GetSpawnFromUniqueItemID(int32 unique_id); + void SendHouseItems(Client* client); + + MutexMap house_object_database_lookup; // 1st int32 = model type, 2nd int32 = spawn id + + int32 GetWatchdogTime() { return watchdogTimestamp; } + void SetWatchdogTime(int32 time) { watchdogTimestamp = time; } + void CancelThreads(); + + void AddPendingSpawnRemove(int32 id); + void ProcessSpawnRemovals(); + + bool SendRemoveSpawn(Client* client, Spawn* spawn, PacketStruct* packet = 0, bool delete_spawn = false); + + void AddSpawnToGroup(Spawn* spawn, int32 group_id); + + void QueueStateCommandToClients(int32 spawn_id, int32 state); + void QueueDefaultCommand(int32 spawn_id, std::string command, float distance); + void ProcessQueuedStateCommands(); + void RemoveClientsFromZone(ZoneServer* zone); + + void WorldTimeUpdateTrigger() { sync_game_time_timer.Trigger(); } + void StopSpawnScriptTimer(Spawn* spawn, std::string functionName); + + Client* RemoveZoneServerFromClient(ZoneServer* zone); + + void SendSubSpawnUpdates(SUBSPAWN_TYPES subtype); + bool HouseItemSpawnExists(int32 item_id); + void ProcessPendingSpawns(); + void AddSpawnToGrid(Spawn* spawn, int32 grid_id); + void RemoveSpawnFromGrid(Spawn* spawn, int32 grid_id); + int32 GetSpawnCountInGrid(int32 grid_id); + void SendClientSpawnListInGrid(Client* client, int32 grid_id); + + void AddIgnoredWidget(int32 id); + +private: +#ifndef WIN32 + pthread_t ZoneThread; + pthread_t SpawnThread; +#endif + + /* Private Functions */ + void AddTransporter(LocationTransportDestination* loc); + void CheckDeadSpawnRemoval(); + void DeleteData(bool boot_clients = true); + void DeleteFactionLists(); + void ProcessDepop(bool respawns_allowed = false, bool repop = false); + + /* + Following functions were public but never used outside the zone server so moved them to private + */ + void ClientProcess(bool ignore_shutdown_timer = false); // never used outside zone server + void RemoveClient(Client* client); // never used outside zone server + void DeterminePosition(SpawnLocation* spawnlocation, Spawn* spawn); // never used outside zone server + void AddDeadSpawn(Spawn* spawn, int32 timer = 0xFFFFFFFF); // never used outside zone server + int32 CalculateSpawnGroup(SpawnLocation* spawnlocation, bool respawn = false); // never used outside zone server + float GetSpawnGroupChance(int32 group_id); // never used outside zone server + vector* GetAssociatedLocations(set* groups); // never used outside zone server + set* GetAssociatedGroups(int32 group_id); // never used outside zone server + list* GetSpawnGroupsByLocation(int32 location_id); // never used outside zone server + void ProcessSpawnLocation(int32 location_id, bool respawn = false); // never used outside zone server + Spawn* ProcessSpawnLocation(SpawnLocation* spawnlocation, bool respawn = false); // never used outside zone server + Spawn* ProcessInstanceSpawnLocation(SpawnLocation* spawnlocation, map* instNPCs, map* instGroundSpawns, map* instObjSpawns, map* instWidgetSpawns, map* instSignSpawns, bool respawn = false); // never used outside zone server + void SendCharSheetChanges(); // never used outside zone server + void SendCharSheetChanges(Client* client); // never used outside zone server + void SaveClients(); // never used outside zone server + void CheckSendSpawnToClient(); // never used outside zone server + void CheckSendSpawnToClient(Client* client, bool initial_login = false); // never used outside zone server + void CheckRemoveSpawnFromClient(Spawn* spawn); // never used outside zone server + void SaveClient(Client* client); // never used outside zone server + void ProcessFaction(Spawn* spawn, Client* client); // never used outside zone server + void RegenUpdate(); // never used outside zone server + void SendCalculatedXP(Player* player, Spawn* victim); // never used outside zone server, might not be used at all any more + void SendTimeUpdate(Client* client); // never used outside zone server + void CheckWidgetTimers(); // never used outside zone server + void CheckRespawns(); // never used outside zone server + void CheckSpawnExpireTimers(); // never used outside zone server + void AddSpawnExpireTimer(Spawn* spawn, int32 expire_time, int32 expire_offset = 0); // never used outside zone server + void CheckSpawnRange(Client* client, Spawn* spawn, bool initial_login = false); // never used outside zone server + void CheckSpawnRange(Spawn* spawn); // never used outside zone server + void DeleteSpawnScriptTimers(Spawn* spawn, bool all = false); // never used outside zone server + void DeleteSpawnScriptTimers(); // never used outside zone server + void CheckSpawnScriptTimers(); // never used outside zone server + bool PrepareSpawnID(Player* player, Spawn* spawn); // never used outside zone server + void RemoveMovementNPC(Spawn* spawn); // never used outside zone server + bool CheckNPCAttacks(NPC* npc, Spawn* victim, Client* client = 0); // never used outside zone server + bool AggroVictim(NPC* npc, Spawn* victim, Client* client = 0); // never used outside zone server + bool CheckEnemyList(NPC* npc); // never used outside zone server + void RemovePlayerProximity(Spawn* spawn, bool all = false); // never used outside zone server + void RemovePlayerProximity(Client* client); // never used outside zone server + void CheckPlayerProximity(Spawn* spawn, Client* client); // never used outside zone server + void RemoveLocationProximities(); // never used outside zone server + void CheckLocationProximity(); // never used outside zone server + void CheckLocationGrids(); // never used outside zone server + void RemoveSpawnSupportFunctions(Spawn* spawn, bool lock_spell_process = false, bool shutdown = false); // never used outside zone server + void ReloadTransporters(); // never used outside zone server + void DeleteSpawns(bool delete_all); // never used outside zone server + void AddPendingDelete(Spawn* spawn); // never used outside zone server + void ClearDeadSpawns(); // never used outside zone server + void RemoveChangedSpawn(Spawn* spawn); // never used outside zone server + void ProcessDrowning(); // never used outside zone server + void RemoveDamagedSpawn(Spawn* spawn); // never used outside zone server + void ProcessTracking(); // never used outside zone server + void ProcessTracking(Client* client); // never used outside zone server + void SendEpicMobDeathToGuild(Player* killer, Spawn* victim); // never used outside zone server + void ProcessAggroChecks(Spawn* spawn); // never used outside zone server + /// Checks to see if it is time to remove a spawn and removes it + /// Forces all spawns scheduled to be removed regardless of time + bool CombatProcess(Spawn* spawn); // never used outside zone server + void LootProcess(Spawn* spawn); + void CloseSpawnLootWindow(Spawn* spawn); + void InitWeather(); // never used outside zone server + ///Dismiss all pets in the zone, useful when the spell process needs to be reloaded + void DismissAllPets(); // never used outside zone server + + /* Mutex Lists */ + std::map changed_spawns; // int32 = spawn id + vector clients; + MutexList connected_clients; // probably remove this list so we are not maintaining 2 client lists + MutexList damaged_spawns; // int32 = spawn id + MutexList location_proximities; + MutexList location_grids; + MutexList remove_movement_spawns; // int32 = spawn id + set spawn_script_timers; + Mutex MSpawnScriptTimers; + set remove_spawn_script_timers_list; + Mutex MRemoveSpawnScriptTimersList; + list transporter_locations; + + /* Mutex Maps */ + MutexMap drowning_victims; + MutexMap movement_spawns; // 1st int32 = spawn id + MutexMap player_proximities; // 1st int32 = spawn id + MutexMap players_tracking; + MutexMap quick_database_id_lookup; // 1st int32 = database id, 2nd int32 = spawn id + MutexMap quick_location_id_lookup; // 1st int32 = location id, 2nd int32 = spawn id + MutexMap quick_group_id_lookup; // 1st int32 = group id, 2nd int32 = spawn id + MutexMap respawn_timers; + map spawn_delete_list; + MutexMap spawn_expire_timers; // 1st int32 = spawn id + map* > spawn_group_associations; + map spawn_group_chances; + map* > spawn_group_locations; + MutexMap > spawn_group_map; // MutexList is a list of spawn id's + map* > spawn_location_groups; + map spawn_location_list; + MutexMap* > spawn_range_map; // int32 in the MutexMap* = spawn id, float = distance + Mutex MWidgetTimers; + map widget_timers; // 1st int32 = spawn id + + std::map grid_maps; + + /* Mutexs */ + mutable std::shared_mutex MGridMaps; + mutable std::shared_mutex MChangedSpawns; + + Mutex m_enemy_faction_list; + Mutex m_npc_faction_list; + Mutex m_reverse_enemy_faction_list; + Mutex MDeadSpawns; + CriticalSection* MMasterZoneLock; //This needs to be a recursive lock to fix a possible /reload spells crash with multiple zones loaded - Foof + Mutex MMasterSpawnLock; + Mutex MPendingSpawnListAdd; + Mutex MSpawnList; + Mutex MTransportSpawns; + Mutex MSpawnGroupAssociation; + Mutex MSpawnGroupLocations; + Mutex MSpawnLocationGroups; + Mutex MSpawnGroupChances; + Mutex MTransportLocations; + Mutex MSpawnLocationList; + Mutex MSpawnDeleteList; + Mutex MClientList; + Mutex MIncomingClients; + + /* Maps */ + map dead_spawns; + map* > enemy_faction_list; + map* > npc_faction_list; + map* > reverse_enemy_faction_list; + map spawn_list; + map m_flightPaths; + map > m_flightPathRoutes; + + /* Lists */ + list pending_spawn_list_add; + + /* Specialized Lists to update specific scenarios */ + std::map subspawn_list[SUBSPAWN_TYPES::MAX_SUBSPAWN_TYPE]; + std::map housing_spawn_map; + + /* Vectors */ + vector* revive_points; + vector transport_spawns; + + /* Classes */ + SpellProcess* spellProcess; + TradeskillMgr* tradeskillMgr; + + /* Timers */ + Timer aggro_timer; + Timer charsheet_changes; + Timer client_save; + Timer location_prox_timer; + Timer location_grid_timer; + Timer movement_timer; + Timer regenTimer; + Timer respawn_timer; + Timer shutdownTimer; + Timer startupDelayTimer; + Timer spawn_check_add; + Timer spawn_check_remove; + Timer spawn_expire_timer; + Timer spawn_range; + Timer spawn_update; + Timer sync_game_time_timer; + Timer tracking_timer; + Timer weatherTimer; + Timer widget_timer; + Timer queue_updates; + Timer shutdownDelayTimer; + + /* Enums */ + Instance_Type InstanceType; + + /* Variables */ + volatile bool finished_depop; + volatile bool depop_zone; + volatile bool repop_zone; + volatile bool respawns_allowed; + volatile bool LoadingData; + std::atomic reloading_spellprocess; + std::atomic zoneShuttingDown; + bool cityzone; + bool always_loaded; + bool isInstance; + + int32 pNumPlayers; + sint16 minimumStatus; + int16 minimumLevel; + int16 maximumLevel; + int16 minimumVersion; + char zone_name[64]; + char zonesky_file[64]; + char zone_file[64]; + char zone_description[255]; + float underworld; + float safe_x; + float safe_y; + float safe_z; + float safe_heading; + float xp_mod; + volatile int32 zoneID; + bool locked; // JA: implementing /zone lock|unlock commands + int32 instanceID; + string zone_motd; + int32 def_reenter_time; + int32 def_reset_time; + int32 def_lockout_time; + int8 group_zone_option; + float rain; + bool isDusk; + int dusk_hour; + int dawn_hour; + int dusk_minute; + int dawn_minute; + int32 spawn_delete_timer; + int32 expansion_flag; + int32 holiday_flag; + //devn00b:test + int can_bind; + bool can_gate; + bool can_evac; + + map versioned_pos_structs; + map versioned_info_structs; + map versioned_vis_structs; + + /* Weather Stuff */ + bool weather_enabled; // false = disabled, true = enabled + int8 weather_type; // 0 = normal, 1 = dynamic, 2 = random, 3 = chaotic + int32 weather_frequency; // how often weather changes + float weather_min_severity; // minimum weather severity in a zone + float weather_max_severity; // maximum weather severity in a zone + float weather_change_amount; // how much does the weather change each interval (normal weather conditions) + float weather_dynamic_offset; // max amount the weather change each interval (dynamic weather conditions) + int8 weather_change_chance; // percentage chance the weather will change + int8 weather_pattern; // 0 = decreasing severity, 1 = increasing severity, 2 = random severity + int32 weather_last_changed_time; // last time weather changed (used with weather_frequency) + float weather_current_severity; // current weather conditions in a zone + bool weather_allowed; // from zones.weather_allowed field in database + bool weather_signaled; // whether or not we told the client "it begins to rain" + + + + + + + + + bool reloading; + map* > entity_command_list; + map > npc_skill_list; + map > npc_equipment_list; + map npc_list; + map object_list; + map sign_list; + map widget_list; + map > groundspawn_entries; + map > groundspawn_items; + Mutex MGroundSpawnItems; + map groundspawn_list; + map loot_tables; + map > loot_drops; + map > spawn_loot_list; + vector level_loot_list; + map > racial_loot_list; + map > zone_loot_list; + map > transporters; + map* > location_transporters; + Mutex MTransporters; + Mutex MTransportMaps; + // Map + map m_transportMaps; + + int32 watchdogTimestamp; + + std::map m_pendingSpawnRemove; + Mutex MPendingSpawnRemoval; + + std::map lua_queued_state_commands; + std::map> lua_spawn_update_command; + std::mutex MLuaQueueStateCmd; + + mutable std::shared_mutex MIgnoredWidgets; + std::map ignored_widgets; + Map* default_zone_map; // this is the map that npcs, ground spawns, so on use. May not be the same as the clients! +public: + Spawn* GetSpawn(int32 id); + + /* Entity Commands */ + map*>* GetEntityCommandListAll() {return &entity_command_list;} + vector* GetEntityCommandList(int32 id); + void SetEntityCommandList(int32 id, EntityCommand* command); + void ClearEntityCommands(); + EntityCommand* GetEntityCommand(int32 id, string name); + + /* NPC's */ + void AddNPC(int32 id, NPC* npc); + NPC* GetNPC(int32 id, bool override_loading = false) { + if((!reloading || override_loading) && npc_list.count(id) > 0) + return npc_list[id]; + else + return 0; + } + NPC* GetNewNPC(int32 id) { + if(!reloading && npc_list.count(id) > 0) + return new NPC(npc_list[id]); + else + return 0; + } + + /* NPC Skills */ + void AddNPCSkill(int32 list_id, int32 skill_id, int16 value); + map* GetNPCSkills(int32 primary_list, int32 secondary_list); + + /* NPC Equipment */ + void AddNPCEquipment(int32 list_id, int32 item_id); + void SetNPCEquipment(NPC* npc); + + /* Objects */ + void AddObject(int32 id, Object* object){ object_list[id] = object; } + Object* GetObject(int32 id, bool override_loading = false) { + if((!reloading || override_loading) && object_list.count(id) > 0) + return object_list[id]; + else + return 0; + } + Object* GetNewObject(int32 id) { + if(!reloading && object_list.count(id) > 0) + return object_list[id]->Copy(); + else + return 0; + } + + /* Signs */ + void AddSign(int32 id, Sign* sign){ sign_list[id] = sign; } + Sign* GetSign(int32 id, bool override_loading = false) { + if((!reloading || override_loading) && sign_list.count(id) > 0) + return sign_list[id]; + else + return 0; + } + Sign* GetNewSign(int32 id) { + if(!reloading && sign_list.count(id) > 0) + return sign_list[id]->Copy(); + else + return 0; + } + + /* Widgets */ + void AddWidget(int32 id, Widget* widget); + Widget* GetWidget(int32 id, bool override_loading = false); + Widget* GetNewWidget(int32 id); + + /* Groundspawns */ + // JA: groundspawn revamp + void AddGroundSpawnEntry(int32 groundspawn_id, int16 min_skill_level, int16 min_adventure_level, int8 bonus_table, float harvest1, float harvest3, float harvest5, float harvest_imbue, float harvest_rare, float harvest10, int32 harvest_coin); + void AddGroundSpawnItem(int32 groundspawn_id, int32 item_id, int8 is_rare, int32 grid_id); + vector* GetGroundSpawnEntries(int32 id); + vector* GetGroundSpawnEntryItems(int32 id); + void LoadGroundSpawnEntries(); + void LoadGroundSpawnItems(); + // + void DeleteGroundSpawnItems(); + + void AddGroundSpawn(int32 id, GroundSpawn* spawn); + GroundSpawn* GetGroundSpawn(int32 id, bool override_loading = false); + GroundSpawn* GetNewGroundSpawn(int32 id); + + /* Pet names */ + vector pet_names; + + /* Loot */ + void AddLootTable(int32 id, LootTable* table); + void AddLootDrop(int32 id, LootDrop* drop); + void AddSpawnLootList(int32 spawn_id, int32 id); + void ClearSpawnLootList(int32 spawn_id); + void AddLevelLootList(GlobalLoot* loot); + void AddRacialLootList(int16 racial_id, GlobalLoot* loot); + void AddZoneLootList(int32 zone, GlobalLoot* loot); + void ClearLootTables(); + vector GetSpawnLootList(int32 spawn_id, int32 zone_id, int8 spawn_level, int16 racial_id, Spawn* spawn = 0); + vector* GetLootDrops(int32 table_id); + LootTable* GetLootTable(int32 table_id); + + /* Transporters */ + void AddLocationTransporter(int32 zone_id, string message, float trigger_x, float trigger_y, float trigger_z, float trigger_radius, int32 destination_zone_id, float destination_x, float destination_y, float destination_z, float destination_heading, int32 cost, int32 unique_id); + void AddTransporter(int32 transport_id, int8 type, string name, string message, int32 destination_zone_id, float destination_x, float destination_y, float destination_z, float destination_heading, + int32 cost, int32 unique_id, int8 min_level, int8 max_level, int32 quest_req, int16 quest_step_req, int32 quest_complete, int32 map_x, int32 map_y, int32 expansion_flag, int32 holiday_flag, int32 min_client_version, + int32 max_client_version, int32 flight_path_id, int16 mount_id, int8 mount_red_color, int8 mount_green_color, int8 mount_blue_color); + void GetTransporters(vector* returnList, Client* client, int32 transport_id); + MutexList* GetLocationTransporters(int32 zone_id); + void DeleteGlobalTransporters(); + /// + ///The transport id + ///Name of the map + void AddTransportMap(int32 id, string name); + + ///Checks to see if the transport has a map + ///The transport id we want to check + ///True if the transport id has a map + bool TransportHasMap(int32 id); + + ///Gets the map name for the given transport id + ///The transport id that we want a map for + ///Map name + string GetTransportMap(int32 id); + + ///Clears the list of transporter maps + void DeleteTransporterMaps(); + + + void DeleteGlobalSpawns(); + + void ReloadSpawns(); + + void SendStateCommand(Spawn* spawn, int32 state); + + int32 lifetime_client_count; + int32 incoming_clients; +}; + +#endif diff --git a/source/common/CRC16.cpp b/source/common/CRC16.cpp new file mode 100644 index 0000000..f7b43ad --- /dev/null +++ b/source/common/CRC16.cpp @@ -0,0 +1,328 @@ +/* + 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 . +*/ +#include + +unsigned long IntArray[]={ +0x00000000, +0x77073096, +0xEE0E612C, +0x990951BA, +0x076DC419, +0x706AF48F, +0xE963A535, +0x9E6495A3, +0x0EDB8832, +0x79DCB8A4, +0xE0D5E91E, +0x97D2D988, +0x09B64C2B, +0x7EB17CBD, +0xE7B82D07, +0x90BF1D91, +0x1DB71064, +0x6AB020F2, +0xF3B97148, +0x84BE41DE, +0x1ADAD47D, +0x6DDDE4EB, +0xF4D4B551, +0x83D385C7, +0x136C9856, +0x646BA8C0, +0xFD62F97A, +0x8A65C9EC, +0x14015C4F, +0x63066CD9, +0xFA0F3D63, +0x8D080DF5, +0x3B6E20C8, +0x4C69105E, +0xD56041E4, +0xA2677172, +0x3C03E4D1, +0x4B04D447, +0xD20D85FD, +0xA50AB56B, +0x35B5A8FA, +0x42B2986C, +0xDBBBC9D6, +0xACBCF940, +0x32D86CE3, +0x45DF5C75, +0xDCD60DCF, +0xABD13D59, +0x26D930AC, +0x51DE003A, +0xC8D75180, +0xBFD06116, +0x21B4F4B5, +0x56B3C423, +0xCFBA9599, +0xB8BDA50F, +0x2802B89E, +0x5F058808, +0xC60CD9B2, +0xB10BE924, +0x2F6F7C87, +0x58684C11, +0xC1611DAB, +0xB6662D3D, +0x76DC4190, +0x01DB7106, +0x98D220BC, +0xEFD5102A, +0x71B18589, +0x06B6B51F, +0x9FBFE4A5, +0xE8B8D433, +0x7807C9A2, +0x0F00F934, +0x9609A88E, +0xE10E9818, +0x7F6A0DBB, +0x086D3D2D, +0x91646C97, +0xE6635C01, +0x6B6B51F4, +0x1C6C6162, +0x856530D8, +0xF262004E, +0x6C0695ED, +0x1B01A57B, +0x8208F4C1, +0xF50FC457, +0x65B0D9C6, +0x12B7E950, +0x8BBEB8EA, +0xFCB9887C, +0x62DD1DDF, +0x15DA2D49, +0x8CD37CF3, +0xFBD44C65, +0x4DB26158, +0x3AB551CE, +0xA3BC0074, +0xD4BB30E2, +0x4ADFA541, +0x3DD895D7, +0xA4D1C46D, +0xD3D6F4FB, +0x4369E96A, +0x346ED9FC, +0xAD678846, +0xDA60B8D0, +0x44042D73, +0x33031DE5, +0xAA0A4C5F, +0xDD0D7CC9, +0x5005713C, +0x270241AA, +0xBE0B1010, +0xC90C2086, +0x5768B525, +0x206F85B3, +0xB966D409, +0xCE61E49F, +0x5EDEF90E, +0x29D9C998, +0xB0D09822, +0xC7D7A8B4, +0x59B33D17, +0x2EB40D81, +0xB7BD5C3B, +0xC0BA6CAD, +0xEDB88320, +0x9ABFB3B6, +0x03B6E20C, +0x74B1D29A, +0xEAD54739, +0x9DD277AF, +0x04DB2615, +0x73DC1683, +0xE3630B12, +0x94643B84, +0x0D6D6A3E, +0x7A6A5AA8, +0xE40ECF0B, +0x9309FF9D, +0x0A00AE27, +0x7D079EB1, +0xF00F9344, +0x8708A3D2, +0x1E01F268, +0x6906C2FE, +0xF762575D, +0x806567CB, +0x196C3671, +0x6E6B06E7, +0xFED41B76, +0x89D32BE0, +0x10DA7A5A, +0x67DD4ACC, +0xF9B9DF6F, +0x8EBEEFF9, +0x17B7BE43, +0x60B08ED5, +0xD6D6A3E8, +0xA1D1937E, +0x38D8C2C4, +0x4FDFF252, +0xD1BB67F1, +0xA6BC5767, +0x3FB506DD, +0x48B2364B, +0xD80D2BDA, +0xAF0A1B4C, +0x36034AF6, +0x41047A60, +0xDF60EFC3, +0xA867DF55, +0x316E8EEF, +0x4669BE79, +0xCB61B38C, +0xBC66831A, +0x256FD2A0, +0x5268E236, +0xCC0C7795, +0xBB0B4703, +0x220216B9, +0x5505262F, +0xC5BA3BBE, +0xB2BD0B28, +0x2BB45A92, +0x5CB36A04, +0xC2D7FFA7, +0xB5D0CF31, +0x2CD99E8B, +0x5BDEAE1D, +0x9B64C2B0, +0xEC63F226, +0x756AA39C, +0x026D930A, +0x9C0906A9, +0xEB0E363F, +0x72076785, +0x05005713, +0x95BF4A82, +0xE2B87A14, +0x7BB12BAE, +0x0CB61B38, +0x92D28E9B, +0xE5D5BE0D, +0x7CDCEFB7, +0x0BDBDF21, +0x86D3D2D4, +0xF1D4E242, +0x68DDB3F8, +0x1FDA836E, +0x81BE16CD, +0xF6B9265B, +0x6FB077E1, +0x18B74777, +0x88085AE6, +0xFF0F6A70, +0x66063BCA, +0x11010B5C, +0x8F659EFF, +0xF862AE69, +0x616BFFD3, +0x166CCF45, +0xA00AE278, +0xD70DD2EE, +0x4E048354, +0x3903B3C2, +0xA7672661, +0xD06016F7, +0x4969474D, +0x3E6E77DB, +0xAED16A4A, +0xD9D65ADC, +0x40DF0B66, +0x37D83BF0, +0xA9BCAE53, +0xDEBB9EC5, +0x47B2CF7F, +0x30B5FFE9, +0xBDBDF21C, +0xCABAC28A, +0x53B39330, +0x24B4A3A6, +0xBAD03605, +0xCDD70693, +0x54DE5729, +0x23D967BF, +0xB3667A2E, +0xC4614AB8, +0x5D681B02, +0x2A6F2B94, +0xB40BBE37, +0xC30C8EA1, +0x5A05DF1B, +0x2D02EF8D, +}; + +unsigned long CRC16(const unsigned char *buf, int size, int key) +{ + unsigned long ecx = key; //mov ecx, [esp+arg_8] + unsigned long eax = ecx; //mov eax, ecx + unsigned long edi; + + eax = ~ eax; //not eax + eax&=0xFF; //and eax, 0FFh + eax=IntArray[eax]; //mov eax, dword_0_10115D38[eax*4] IntArray + eax ^= 0x00FFFFFF; //xor eax, 0FFFFFFh + int edx = ecx; //mov edx, ecx + edx = edx >> 8; //sar edx, 8 + edx = edx ^ eax; //xor edx, eax + eax = eax >> 8; //sar eax, 8 + edx &= 0xFF; //and edx, 0FFh + eax &= 0x00FFFFFF; //and eax, 0FFFFFFh + eax ^= IntArray[edx]; //xor eax, dword_0_10115D38[edx*4] + edx = ecx; //mov edx, ecx + edx = edx >> 0x10; //sar edx, 10h + edx ^= eax; //xor edx, eax + eax = eax >> 8; //sar eax, 8 + edx &= 0xFF; //and edx, 0FFh + int esi = IntArray[edx]; //mov esi, dword_0_10115D38[edx*4] + edx = size; //mov edx, [esp+4+arg_4] + eax &= 0x00FFFFFF; //and eax, 0FFFFFFh + eax ^= esi; //xor eax, esi + ecx = ecx >> 0x18; //sar ecx, 18h + ecx ^= eax; //xor ecx, eax + ecx &= 0xFF; //and ecx, 0FFh + esi = IntArray[ecx]; //mov esi, dword_0_10115D38[ecx*4] + ecx = (int)*buf; //mov ecx, [esp+4+arg_0] + eax = eax >> 8; //sar eax, 8 + eax &= 0x00FFFFFF; //and eax, 0FFFFFFh + eax ^= esi; //xor eax, esi + for(int x = 0; x < size; x++) + { //eax is the crc, ecx is the current part of the buffer + int edx = 0; //xor edx, edx + edx = buf[x] & 0x00FF; //mov dl, [ecx] + + edx ^= eax; //xor edx, eax + eax = eax >> 8; //sar eax, 8 + edx &= 0xFF; //and edx, 0FFh + edi = IntArray[edx]; //mov edi, dword_0_10115D38[edx*4] + eax &= 0x00FFFFFF; //and eax, 0FFFFFFh + eax ^= edi; //xor eax, edi + } + return ~eax; +} diff --git a/source/common/CRC16.h b/source/common/CRC16.h new file mode 100644 index 0000000..7aacd36 --- /dev/null +++ b/source/common/CRC16.h @@ -0,0 +1,25 @@ +/* + 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 . +*/ +#ifndef _CRC16_H +#define _CRC16_H + +unsigned long CRC16(const unsigned char *buf, int size, int key); + +#endif diff --git a/source/common/Common_Defines.h b/source/common/Common_Defines.h new file mode 100644 index 0000000..4600d97 --- /dev/null +++ b/source/common/Common_Defines.h @@ -0,0 +1,32 @@ +/* + 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 . +*/ +#define BASEDIR "./" + +#ifndef DB_INI_FILE + #ifdef LOGIN + #define DB_INI_FILE BASEDIR "login_db.ini" + #else + #define DB_INI_FILE BASEDIR "world_db.ini" + #endif +#endif + +#ifndef MAIN_CONFIG_FILE + #define MAIN_CONFIG_FILE BASEDIR "server_config.json" +#endif \ No newline at end of file diff --git a/source/common/Condition.cpp b/source/common/Condition.cpp new file mode 100644 index 0000000..348c90a --- /dev/null +++ b/source/common/Condition.cpp @@ -0,0 +1,133 @@ +/* + 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 . +*/ +#include "debug.h" +#include "Condition.h" + +#ifdef WIN32 +#else +#include +#include +#include +#endif + +#ifdef WIN32 +/* + + Windows does not support condition variables by default. + So we use a simple hack of sleeping in wait() and doing + nothing anywhere else. + + some possible places to look for ways to do this: + http://www.cs.wustl.edu/~schmidt/win32-cv-1.html + + http://sources.redhat.com/pthreads-win32/ + http://sources.redhat.com/cgi-bin/cvsweb.cgi/pthreads/pthread_cond_signal.c?rev=1.7&content-type=text/x-cvsweb-markup&cvsroot=pthreads-win32 + +*/ + +#define CONDITION_HACK_GRANULARITY 4 + + +Condition::Condition() +{ +} + +void Condition::Signal() +{ +} + +void Condition::SignalAll() +{ +} + +void Condition::Wait() +{ + Sleep(CONDITION_HACK_GRANULARITY); +} + +Condition::~Condition() +{ +} + + +#else //!WIN32 + +Condition::Condition() +{ + pthread_cond_init(&cond,NULL); + pthread_mutex_init(&mutex,NULL); +} + +void Condition::Signal() +{ + pthread_mutex_lock(&mutex); + pthread_cond_signal(&cond); + pthread_mutex_unlock(&mutex); +} + +void Condition::SignalAll() +{ + pthread_mutex_lock(&mutex); + pthread_cond_broadcast(&cond); + pthread_mutex_unlock(&mutex); +} + +void Condition::Wait() +{ + pthread_mutex_lock(&mutex); + pthread_cond_wait(&cond,&mutex); + pthread_mutex_unlock(&mutex); +} + +/* +I commented this specifically because I think it might be very +difficult to write a windows counterpart to it, so I would like +to discourage its use until we can confirm that it can be reasonably +implemented on windows. + +bool Condition::TimedWait(unsigned long usec) +{ +struct timeval now; +struct timespec timeout; +int retcode=0; + pthread_mutex_lock(&mutex); + gettimeofday(&now,NULL); + now.tv_usec+=usec; + timeout.tv_sec = now.tv_sec + (now.tv_usec/1000000); + timeout.tv_nsec = (now.tv_usec%1000000) *1000; + //cout << "now=" << now.tv_sec << "."<. +*/ +#ifndef __CONDITION_H +#define __CONDITION_H + +#ifndef WIN32 +#include +#endif + +//Sombody, someday needs to figure out how to implement a condition +//system on windows... + + +class Condition { + private: +#ifndef WIN32 + pthread_cond_t cond; + pthread_mutex_t mutex; +#endif + public: + Condition(); + void Signal(); + void SignalAll(); + void Wait(); +// bool TimedWait(unsigned long usec); + ~Condition(); +}; + +#endif + + diff --git a/source/common/ConfigReader.cpp b/source/common/ConfigReader.cpp new file mode 100644 index 0000000..33869f4 --- /dev/null +++ b/source/common/ConfigReader.cpp @@ -0,0 +1,302 @@ +/* + 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 . +*/ +#include "ConfigReader.h" +#include "Log.h" + +ConfigReader::~ConfigReader(){ + MStructs.lock(); + DestroyStructs(); + MStructs.unlock(); +} +PacketStruct* ConfigReader::getStructByVersion(const char* name, int16 version){ + PacketStruct* packet = 0; + PacketStruct* newpacket = 0; + MStructs.lock(); + vector* struct_versions = structs[string(name)]; + if(struct_versions){ + vector::iterator iter; + for(iter = struct_versions->begin(); iter != struct_versions->end(); iter++){ + packet = *iter; + if(packet && packet->GetVersion() == version){ + newpacket = new PacketStruct(packet, version); + break; + } + } + } + MStructs.unlock(); + if(!newpacket) + LogWrite(PACKET__ERROR, 0, "Packet", "Could not find struct named '%s' with version: %i", name, version); + return newpacket; +} +void ConfigReader::ReloadStructs(){ + MStructs.lock(); + DestroyStructs(); + for(int32 i=0;i*>::iterator struct_iterator; + for(struct_iterator=structs.begin();struct_iterator!=structs.end();struct_iterator++) { + vector* versions = struct_iterator->second; + vector::iterator version_iter; + if(versions){ + for(version_iter = versions->begin(); version_iter != versions->end(); version_iter++){ + safe_delete(*version_iter); + } + } + safe_delete(versions); + } + structs.clear(); +} +PacketStruct* ConfigReader::getStruct(const char* name, int16 version){ + PacketStruct* latest_version = 0; + PacketStruct* new_latest_version = 0; + MStructs.lock(); + vector* struct_versions = structs[string(name)]; + if(struct_versions){ + vector::iterator iter; + for(iter = struct_versions->begin(); iter != struct_versions->end(); iter++){ + if((*iter)->GetVersion() <= version && (!latest_version || (*iter)->GetVersion() > latest_version->GetVersion())) + latest_version = *iter; + } + if (latest_version) { + if (latest_version->GetOpcode() != OP_Unknown && (latest_version->GetOpcodeValue(version) == 0xFFFF || latest_version->GetOpcodeValue(version)==0xCDCD)) { + LogWrite(PACKET__ERROR, 0, "Packet", "Could not find valid opcode for Packet Struct '%s' and client version %d", latest_version->GetName(), version); + } + else if(strlen(latest_version->GetOpcodeType()) == 0 || latest_version->GetOpcode() != OP_Unknown) + new_latest_version = new PacketStruct(latest_version, version); + } + + } + MStructs.unlock(); + if(!new_latest_version && !latest_version) + LogWrite(PACKET__ERROR, 0, "Packet", "Could not find struct named '%s'", name); + return new_latest_version; +} +int16 ConfigReader::GetStructVersion(const char* name, int16 version){ + MStructs.lock(); + vector* struct_versions = structs[string(name)]; + int16 ret = 0; + if(struct_versions){ + vector::iterator iter; + PacketStruct* latest_version = 0; + for(iter = struct_versions->begin(); iter != struct_versions->end(); iter++){ + if(!latest_version || ( (*iter)->GetVersion() > latest_version->GetVersion() && (*iter)->GetVersion() <= version) ) + latest_version = *iter; + } + if(latest_version) + ret = latest_version->GetVersion(); + } + MStructs.unlock(); + return ret; +} +void ConfigReader::addStruct(const char* name, int16 version, PacketStruct* new_struct){ + string strname(name); + vector* struct_versions = structs[strname]; + if(struct_versions) + struct_versions->push_back(new_struct); + else{ + struct_versions = new vector; + struct_versions->push_back(new_struct); + structs[strname] = struct_versions; + } +} +bool ConfigReader::LoadFile(const char* name){ + load_files.push_back(name); + return processXML_Elements(name); +} +bool ConfigReader::processXML_Elements(const char* fileName){ + XMLNode xMainNode=XMLNode::openFileHelper(fileName,"EQ2Emulator"); + if(xMainNode.isEmpty()) + return false; + for(int i=0;iSetName(struct_name); + if(opcode_type) + new_struct->SetOpcodeType(opcode_type); + if(opcode_name){ + if(!new_struct->SetOpcode(opcode_name)){ + safe_delete(new_struct); + continue; + } + } + new_struct->SetVersion(version); + loadDataStruct(new_struct, xMainNode.getChildNode("Struct", i)); + addStruct(struct_name, version, new_struct); + } + return true; +} +void ConfigReader::loadDataStruct(PacketStruct* packet, XMLNode parentNode, bool array_packet){ + for(int x=0;xGetVersion()); + if(substruct_packet){ + vector::iterator itr; + vector* structs = substruct_packet->getStructs(); + DataStruct* ds = 0; + int i = 0; + char tmp[12] = {0}; + for(i=0;ibegin();itr!=structs->end();itr++) { + ds = *itr; + string new_name; + if(array_packet) + new_name = string(name).append("_").append(ds->GetStringName()); + else + new_name = string(name).append("_").append(ds->GetStringName()).append("_").append(tmp); + + DataStruct* ds2 = new DataStruct(new_name.c_str(), ds->GetType(),ds->GetLength(), ds->GetType2()); + + if(!array_packet && strlen(ds->GetArraySizeVariable()) > 1) + ds2->SetArraySizeVariable(string(name).append("_").append(ds->GetArraySizeVariable()).append("_").append(tmp).c_str()); + ds2->SetOversized(ds->GetOversized()); + ds2->SetOversizedByte(ds->GetOversizedByte()); + ds2->SetDefaultValue(ds->GetDefaultValue()); + ds2->SetMaxArraySize(ds->GetMaxArraySize()); + ds2->SetIfSetVariable(ds->GetIfSetVariable() ? ds->GetIfSetVariable() : if_variable); + ds2->SetIfNotSetVariable(ds->GetIfSetVariable() ? ds->GetIfNotSetVariable() : if_not_variable); + ds2->SetIfNotEqualsVariable(ds->GetIfNotEqualsVariable()); + ds2->SetIfFlagNotSetVariable(ds->GetIfFlagNotSetVariable()); + ds2->SetIfFlagSetVariable(ds->GetIfFlagSetVariable()); + ds2->SetIsOptional(ds->IsOptional()); + ds2->AddIfSetVariable(if_variable); //add this if the modifier is on the piece that is including the substruct + ds2->AddIfNotSetVariable(if_not_variable); //add this if the modifier is on the piece that is including the substruct + packet->add(ds2); + } + } + if(!array_packet){ + i--; + substruct_packet->renameSubstructArray(name, i); + //ds2->SetArraySizeVariable((char*)string(name).append("_").append(ds->GetArraySizeVariable()).append("_").append(tmp).c_str()); + packet->addPacketArrays(substruct_packet); + } + + safe_delete(substruct_packet); + } + continue; + } + else if(type && strncasecmp(type,"Array", 5)==0 && array_size){ + PacketStruct* new_packet = new PacketStruct; + new_packet->SetName(name); + new_packet->IsSubPacket(true); + new_packet->SetVersion(packet->GetVersion()); + loadDataStruct(new_packet, parentNode.getChildNode("Data", x), true); + packet->add(new_packet); + } + if(!name || !type) + { + LogWrite(MISC__WARNING, 0, "Misc", "Ignoring invalid Data Element, all elements must include at least an ElementName and Type!"); + LogWrite(MISC__WARNING, 0, "Misc", "\tStruct: '%s', version: %i", parentNode.getAttribute("Name"), parentNode.getAttribute("ClientVersion")); + continue; + } + DataStruct* ds = new DataStruct(name, type, num_size, type2); + int8 oversized_value = 0; + int8 oversized_byte_value = 255; + if(oversized){ + try{ + oversized_value = atoi(oversized); + } + catch(...){} + } + if(oversized_byte){ + try{ + oversized_byte_value = atoi(oversized_byte); + } + catch(...){} + } + ds->SetOversizedByte(oversized_byte_value); + ds->SetOversized(oversized_value); + ds->SetMaxArraySize(max_array_size); + if(array_size) + ds->SetArraySizeVariable(array_size); + ds->SetDefaultValue(byte_val); + ds->SetIfSetVariable(if_variable); + ds->SetIfNotSetVariable(if_not_variable); + ds->SetIfEqualsVariable(if_equals_variable); + ds->SetIfNotEqualsVariable(if_not_equals_variable); + ds->SetIfFlagNotSetVariable(if_flag_not_set_variable); + ds->SetIfFlagSetVariable(if_flag_set_variable); + if (optional && strlen(optional) > 0 && (strcmp("true", optional) == 0 || strcmp("TRUE", optional) == 0 || strcmp("True", optional) == 0)) + ds->SetIsOptional(true); + packet->add(ds); + } +} + diff --git a/source/common/ConfigReader.h b/source/common/ConfigReader.h new file mode 100644 index 0000000..cd34ae5 --- /dev/null +++ b/source/common/ConfigReader.h @@ -0,0 +1,52 @@ +/* + 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 . +*/ +#ifndef __CONFIG_READER__ +#define __CONFIG_READER__ +#include +#include "PacketStruct.h" +#include +#include +#include +#include "xmlParser.h" +#include "Mutex.h" + +using namespace std; + +class ConfigReader{ +public: + ~ConfigReader(); + + void addStruct(const char* name, int16 version, PacketStruct* new_struct); + PacketStruct* getStruct(const char* name, int16 version); + PacketStruct* getStructByVersion(const char* name, int16 version); + void loadDataStruct(PacketStruct* packet, XMLNode parentNode, bool array_packet = false); + bool processXML_Elements(const char* fileName); + int16 GetStructVersion(const char* name, int16 version); + void DestroyStructs(); + void ReloadStructs(); + bool LoadFile(const char* name); +private: + Mutex MStructs; + vector load_files; + map*> structs; + //vector structs; +}; +#endif + diff --git a/source/common/Crypto.cpp b/source/common/Crypto.cpp new file mode 100644 index 0000000..369390e --- /dev/null +++ b/source/common/Crypto.cpp @@ -0,0 +1,47 @@ +/* + 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 . +*/ +#include "Crypto.h" +#include +#include "../common/packet_dump.h" + +using namespace std; +void test(); +int64 Crypto::RSADecrypt(uchar* text, int16 size){ + int64 ret = 0; + uchar* buffer = new uchar[8]; + for(int i=7;i>=0;i--) + buffer[7-i] = text[i]; + memcpy(&ret, buffer, 8); + safe_delete_array(buffer); + return ret; +} + +void Crypto::RC4Decrypt(uchar* text, int32 size){ + MCrypto.lock(); + client->Cypher(text, size); + MCrypto.unlock(); +} + +void Crypto::RC4Encrypt(uchar* text, int32 size){ + MCrypto.lock(); + server->Cypher(text, size); + MCrypto.unlock(); +} + diff --git a/source/common/Crypto.h b/source/common/Crypto.h new file mode 100644 index 0000000..d2c478b --- /dev/null +++ b/source/common/Crypto.h @@ -0,0 +1,66 @@ +/* + 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 . +*/ +#ifndef _CRYPTO_H +#define _CRYPTO_H +#include +#include +#include "RC4.h" +#include "../common/types.h" + +using namespace std; +class Crypto { +public: + ~Crypto(){ safe_delete(client); safe_delete(server); } + Crypto() { rc4_key = 0; encrypted = false; client = 0; server = 0; }; + + static int64 RSADecrypt(uchar* text, int16 size); + void RC4Encrypt(uchar* text, int32 size); + void RC4Decrypt(uchar* text, int32 size); + int64 getRC4Key() { return rc4_key; } + void setRC4Key(int64 key) { + rc4_key = key; + if(key > 0){ + encrypted = true; + client = new RC4(~key); + server = new RC4(key); + uchar temp[20]; + client->Cypher(temp, 20); + server->Cypher(temp, 20); + } + else{ + encrypted = false; + safe_delete(client); + safe_delete(server); + } + } + bool isEncrypted(){ return encrypted; } + void setEncrypted(bool in_val){ encrypted = in_val; } + + +private: + RC4* server; + RC4* client; + bool encrypted; + int64 rc4_key; + mutex MCrypto; +}; + +#endif + diff --git a/source/common/DataBuffer.h b/source/common/DataBuffer.h new file mode 100644 index 0000000..f40486d --- /dev/null +++ b/source/common/DataBuffer.h @@ -0,0 +1,207 @@ +/* + 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 . +*/ +#ifndef __EQ2_DATABUFFER_ +#define __EQ2_DATABUFFER_ +#include +#include "../common/types.h" +#include "../common/EQPacket.h" +#include "../common/EQ2_Common_Structs.h" + +#ifdef WORLD + #include "../WorldServer/SpawnLists.h" +#endif + +using namespace std; + +class DataBuffer{ +public: + bool changed; + uchar* getData(){ return (uchar*)buffer.c_str(); } + int32 getDataSize(){ return buffer.length(); } + string* getDataString(){ return &buffer; } + void CreateEQ2Color(EQ2_Color& color){ + CreateEQ2Color(&color); + } + uchar* GetLoadBuffer(){ + return load_buffer; + } + int32 GetLoadPos(){ + return load_pos; + } + int32 GetLoadLen(){ + return load_len; + } + void SetLoadPos(int32 new_pos){ + load_pos = new_pos; + } + void CreateEQ2Color(EQ2_Color* color){ + int8 rgb[3]; + float* tmp = 0; + for(int i=0;i<3;i++){ + tmp = (float*)(load_buffer + load_pos); + rgb[i] = (int8)((*tmp)*255); + load_pos += sizeof(float); + } + color->red = rgb[0]; + color->green = rgb[1]; + color->blue = rgb[2]; + } + + template void MakeEQ2_Int8(Type& output){ + MakeEQ2_Int8(&output); + } + template void MakeEQ2_Int8(Type* output){ + float* tmp = (float*)(load_buffer + load_pos); + if(*tmp < 0) + *tmp *= -1; + sint8 result = (sint8)((*tmp)*100); + memcpy(output, &result, sizeof(sint8)); + load_pos += sizeof(float); + } + void InitializeGetData(){ + get_buffer = (uchar*)buffer.c_str(); + get_len = buffer.length(); + get_pos = 0; + } + void InitializeLoadData(uchar* input, int32 size){ + buffer = string((char*)input, size); + load_buffer = (uchar*)buffer.c_str(); + load_len = size; + load_pos = 0; + } + template void LoadDataString(String& output){ + LoadDataString(&output); + } + template void LoadDataString(String* output){ + if((sizeof(output->size) + load_pos) <= load_len){ + memcpy(&output->size, load_buffer + load_pos, sizeof(output->size)); + load_pos += sizeof(output->size); + } + if((output->size + load_pos) <= load_len){ + output->data = string((char*)(load_buffer + load_pos), output->size); + load_pos += output->size; + } + } + template void LoadData(Type& output){ + LoadData(&output); + } + template void LoadData(Type* output, int32 array_size){ + if(array_size<=1){ + LoadData(output); + } + else{ + for(int32 i=0;i void LoadData(Type* output){ + if((sizeof(Type) + load_pos) <= load_len){ + memcpy(output, load_buffer + load_pos, sizeof(Type)); + load_pos += sizeof(Type); + } + } + template void LoadData(Type& output, int32 array_size){ + LoadData(&output, array_size); + } + void LoadSkip(int8 bytes){ + load_pos += bytes; + } + template void LoadSkip(Type& skip){ + LoadSkip(&skip); + } + template void LoadSkip(Type* skip){ + load_pos += sizeof(Type); + } + template void GetData(Type* output){ + if((sizeof(Type) + get_pos) <= get_len){ + *output = (Type*)get_buffer; + get_pos += sizeof(output); + } + } + void AddZeros(int16 num){ + int8* data = new int8[num]; + memset(data, 0, num); + AddData(*data); + safe_delete_array(data); + } + template void StructAddData(Type input, int16 size, string* datastring){ + if(datastring) + datastring->append((char*)&input, size); + else + buffer.append((char*)&input, size); + } + template void StructAddData(Type input, int32 array_size, int16 size, string* datastring){ + if(array_size>0){ + for(int32 i=0;i void AddData(Type input, string* datastring = 0){ + if(!datastring) + datastring = &buffer; + datastring->append((char*)&input, sizeof(input)); + } + template void AddData(Type input, int32 array_size, string* datastring = 0){ + if(array_size>0){ + for(int32 i=0;i void AddDataString(String* input, string* datastring = 0){ + AddDataString(*input, datastring); + } + template void AddDataString(String input, string* datastring = 0){ + input.size = input.data.length(); + if(!datastring) + datastring = &buffer; + datastring->append((char*)&input.size, sizeof(input.size)); + datastring->append(input.data); + } + void AddCharArray(char* array, string* datastring = 0){ + if(!datastring) + datastring = &buffer; + datastring->append(array); + } + void AddCharArray(char* array, int16 size, string* datastring = 0){ + if(!datastring) + datastring = &buffer; + datastring->append(array, size); + } + void AddData(string data, string* datastring = 0){ + if(!datastring) + datastring = &buffer; + datastring->append(data); + } + void Clear() { buffer.clear(); } +private: + string buffer; + uchar* get_buffer; + uchar* load_buffer; + int32 get_len; + int32 get_pos; + int32 load_len; + int32 load_pos; +}; +#endif + diff --git a/source/common/DatabaseNew.cpp b/source/common/DatabaseNew.cpp new file mode 100644 index 0000000..e1e2450 --- /dev/null +++ b/source/common/DatabaseNew.cpp @@ -0,0 +1,422 @@ +/* + 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 . +*/ +#include +#include +#include +#include +#include "Log.h" +#include "DatabaseNew.h" +#include + +//increase this if large queries are being run frequently to make less calls to malloc() +#define QUERY_INITIAL_SIZE 512 + +#if defined WORLD +#define DB_INI "world_db.ini" +#elif defined LOGIN +#define DB_INI "login_db.ini" +#elif defined PARSER +#define DB_INI "parser_db.ini" +#endif + +DatabaseNew::DatabaseNew() { + mysql_init(&mysql); + int timeout = 10; + mysql_options(&mysql, MYSQL_OPT_CONNECT_TIMEOUT, &timeout); + MMysql.SetName("DatabaseNew::mysql"); +} + +DatabaseNew::~DatabaseNew() { + mysql_close(&mysql); +#if MYSQL_VERSION_ID >= 50003 + mysql_library_end(); +#else + mysql_server_end(); +#endif +} + +bool DatabaseNew::Connect() { + char line[256], *key, *val; + char host[256], user[64], password[64], database[64], port[64]; + bool found_section = false; + FILE *f; + + if ((f = fopen(DB_INI, "r")) == NULL) { + LogWrite(DATABASE__ERROR, 0, "Database", "Unable to read %s\n", DB_INI); + return false; + } + + memset(host, 0, sizeof(host)); + memset(user, 0, sizeof(user)); + memset(password, 0, sizeof(password)); + memset(database, 0, sizeof(database)); + memset(port, 0, sizeof(port)); + + while (fgets(line, sizeof(line), f) != NULL) { + if (line[0] == '#' || line[0] == '\n' || line[0] == '\r') + continue; + + if (!found_section) { + if (strncasecmp(line, "[Database]", 10) == 0) + found_section = true; + } + else { + if ((key = strtok(line, "=")) != NULL) { + if ((val = strtok(NULL, "\r\n")) != NULL) { + if (strncasecmp(line, "host", 4) == 0) + strncpy(host, val, sizeof(host) - 1); + else if (strncasecmp(line, "user", 4) == 0) + strncpy(user, val, sizeof(user) - 1); + else if (strncasecmp(line, "password", 8) == 0) + strncpy(password, val, sizeof(password) - 1); + else if (strncasecmp(line, "database", 8) == 0) + strncpy(database, val, sizeof(database) - 1); + else if (strncasecmp(line, "port", 4) == 0) + strncpy(port, val, sizeof(port) - 1); + } + } + } + } + + fclose(f); + + if (host[0] == '\0') { + LogWrite(DATABASE__ERROR, 0, "Database", "Unknown 'host' in '%s'\n", DB_INI); + return false; + } + if (user[0] == '\0') { + LogWrite(DATABASE__ERROR, 0, "Database", "Unknown 'user' in '%s'\n", DB_INI); + return false; + } + if (password[0] == '\0') { + LogWrite(DATABASE__ERROR, 0, "Database", "Unknown 'password' in '%s'\n", DB_INI); + return false; + } + if (database[0] == '\0') { + LogWrite(DATABASE__ERROR, 0, "Database", "Unknown 'database' in '%s'\n", DB_INI); + return false; + } + + unsigned int portnum = atoul(port); + return Connect(host, user, password, database, portnum); +} + +bool DatabaseNew::Connect(const char *host, const char *user, const char *password, const char *database, unsigned int port) { + if (mysql_real_connect(&mysql, host, user, password, database, port, NULL, 0) == NULL) { + LogWrite(DATABASE__ERROR, 0, "Database", "Unable to connect to MySQL server at %s:%u: %s\n", host, port, mysql_error(&mysql)); + return false; + } + + return true; +} + +bool DatabaseNew::Query(const char *query, ...) { + char *buf; + size_t size = QUERY_INITIAL_SIZE; + int num_chars; + va_list args; + bool ret = true; + + MMysql.writelock(__FUNCTION__, __LINE__); + while (true) { + if ((buf = (char *)malloc(size)) == NULL) { + LogWrite(DATABASE__ERROR, 0, "Database", "Out of memory trying to allocate database query of %u bytes\n", size); + MMysql.releasewritelock(__FUNCTION__, __LINE__); + return false; + } + + va_start(args, query); + num_chars = vsnprintf(buf, size, query, args); + va_end(args); + + if (num_chars > -1 && (size_t)num_chars < size) + break; + + if (num_chars > -1) + size = num_chars + 1; + else + size *= 2; + + free(buf); + } + + if (mysql_real_query(&mysql, buf, num_chars) != 0) { + + if (mysql_errno(&mysql) == CR_SERVER_LOST || mysql_errno(&mysql) == CR_SERVER_GONE_ERROR) { + LogWrite(DATABASE__ERROR, 0, "Database", "Lost connection, attempting to recover and retry query..."); + Connect(); + + // retry attempt of previous query (1 try and we give up) + if (mysql_real_query(&mysql, buf, num_chars) != 0) { + ret = false; + } + } + else if (!IsIgnoredErrno(mysql_errno(&mysql))) { + LogWrite(DATABASE__ERROR, 0, "Database", "Error %i running MySQL query: %s\n%s\n", mysql_errno(&mysql), mysql_error(&mysql), buf); + ret = false; + } + } + free(buf); + + MMysql.releasewritelock(__FUNCTION__, __LINE__); + return ret; +} + +bool DatabaseNew::Select(DatabaseResult *result, const char *query, ...) { + char *buf; + size_t size = QUERY_INITIAL_SIZE; + int num_chars; + va_list args; + MYSQL_RES *res; + bool ret = true; + + MMysql.writelock(__FUNCTION__, __LINE__); + while (true) { + if ((buf = (char *)malloc(size)) == NULL) { + LogWrite(DATABASE__ERROR, 0, "Database", "Out of memory trying to allocate database query of %u bytes\n", size); + MMysql.releasewritelock(__FUNCTION__, __LINE__); + return false; + } + + va_start(args, query); + num_chars = vsnprintf(buf, size, query, args); + va_end(args); + + if (num_chars > -1 && (size_t)num_chars < size) + break; + + if (num_chars > -1) + size = num_chars + 1; + else + size *= 2; + + free(buf); + } + + if (mysql_real_query(&mysql, buf, (unsigned long)num_chars) != 0) { + + if (mysql_errno(&mysql) == CR_SERVER_LOST || mysql_errno(&mysql) == CR_SERVER_GONE_ERROR) { + LogWrite(DATABASE__ERROR, 0, "Database", "Lost connection, attempting to recover and retry query..."); + + mysql_close(&mysql); + Connect(); + + // retry attempt of previous query (1 try and we give up) + if (mysql_real_query(&mysql, buf, (unsigned long)num_chars) != 0) { + ret = false; + } + } + else if (!IsIgnoredErrno(mysql_errno(&mysql))) { + LogWrite(DATABASE__ERROR, 0, "Database", "Error %i running MySQL query: %s\n%s\n", mysql_errno(&mysql), mysql_error(&mysql), buf); + ret = false; + } + } + + if (ret && !IsIgnoredErrno(mysql_errno(&mysql))) { + res = mysql_store_result(&mysql); + + if (res != NULL) + { + // Grab number of rows and number of fields from the query + uint8 num_rows = mysql_affected_rows(&mysql); + uint8 num_fields = mysql_field_count(&mysql); + + ret = result->StoreResult(res, num_fields, num_rows); + } + else { + LogWrite(DATABASE__ERROR, 0, "Database", "Error storing MySql query result (%d): %s\n%s", mysql_errno(&mysql), mysql_error(&mysql), buf); + ret = false; + } + } + + free(buf); + + MMysql.releasewritelock(__FUNCTION__, __LINE__); + return ret; +} + +int32 DatabaseNew::LastInsertID() +{ + return (int32)mysql_insert_id(&mysql); +} + +long DatabaseNew::AffectedRows() +{ + return mysql_affected_rows(&mysql); +} + +char * DatabaseNew::Escape(const char *str, size_t len) { + char *buf = (char *)malloc(len * 2 + 1); + + if (buf == NULL) { + LogWrite(DATABASE__ERROR, 0, "Database", "Out of memory trying to allocate %u bytes in %s:%u\n", len * 2 + 1, __FUNCTION__, __LINE__); + return NULL; + } + + mysql_real_escape_string(&mysql, buf, str, len); + return buf; +} + +char * DatabaseNew::Escape(const char *str) { + return Escape(str, strlen(str)); +} + +string DatabaseNew::EscapeStr(const char *str, size_t len) { + char *buf = (char *)malloc(len * 2 + 1); + string ret; + + if (buf == NULL) { + LogWrite(DATABASE__ERROR, 0, "Database", "Out of memory trying to allocate %u bytes in %s:%u\n", len * 2 + 1, __FUNCTION__, __LINE__); + return NULL; + } + + mysql_real_escape_string(&mysql, buf, str, len); + ret.append(buf); + free(buf); + + return ret; +} + +string DatabaseNew::EscapeStr(const char *str) { + return EscapeStr(str, strlen(str)); +} + +string DatabaseNew::EscapeStr(string str) { + return EscapeStr(str.c_str(), str.length()); +} + +bool DatabaseNew::QueriesFromFile(const char * file) { + bool success = true; + long size; + char *buf; + int ret; + MYSQL_RES *res; + FILE *f; + + f = fopen(file, "rb"); + if (f == NULL) { + LogWrite(DATABASE__ERROR, 0, "Database", "Unable to open '%s' for reading: %s", file, strerror(errno)); + return false; + } + + fseek(f, 0, SEEK_END); + size = ftell(f); + fseek(f, 0, SEEK_SET); + + buf = (char *)malloc(size + 1); + if (buf == NULL) { + fclose(f); + LogWrite(DATABASE__ERROR, 0, "Database", "Out of memory trying to allocate %u bytes in %s:%u\n", size + 1, __FUNCTION__, __LINE__); + return false; + } + + if (fread(buf, sizeof(*buf), size, f) != (size_t)size) { + LogWrite(DATABASE__ERROR, 0, "Database", "Failed to read from '%s': %s", file, strerror(errno)); + fclose(f); + free(buf); + return false; + } + + buf[size] = '\0'; + fclose(f); + + mysql_set_server_option(&mysql, MYSQL_OPTION_MULTI_STATEMENTS_ON); + ret = mysql_real_query(&mysql, buf, size); + free(buf); + + if (ret != 0) { + LogWrite(DATABASE__ERROR, 0, "Database", "Error running MySQL queries from file '%s' (%d): %s", file, mysql_errno(&mysql), mysql_error(&mysql)); + success = false; + } + else { + //all results must be processed + do { + res = mysql_store_result(&mysql); + if (res != NULL) + mysql_free_result(res); + ret = mysql_next_result(&mysql); + + if (ret > 0) { + LogWrite(DATABASE__ERROR, 0, "Database", "Error running MySQL queries from file '%s' (%d): %s", file, mysql_errno(&mysql), mysql_error(&mysql)); + success = false; + } + + } while (ret == 0); + } + mysql_set_server_option(&mysql, MYSQL_OPTION_MULTI_STATEMENTS_OFF); + + return success; +} + +void DatabaseNew::SetIgnoredErrno(unsigned int db_errno) { + vector::iterator itr; + + for (itr = ignored_errnos.begin(); itr != ignored_errnos.end(); itr++) { + if ((*itr) == db_errno) + return; + } + + ignored_errnos.push_back(db_errno); +} + +void DatabaseNew::RemoveIgnoredErrno(unsigned int db_errno) { + vector::iterator itr; + + for (itr = ignored_errnos.begin(); itr != ignored_errnos.end(); itr++) { + if ((*itr) == db_errno) { + ignored_errnos.erase(itr); + break; + } + } +} + +bool DatabaseNew::IsIgnoredErrno(unsigned int db_errno) { + vector::iterator itr; + + for (itr = ignored_errnos.begin(); itr != ignored_errnos.end(); itr++) { + if ((*itr) == db_errno) + return true; + } + + return false; +} + +// Sends the MySQL server a keepalive +void DatabaseNew::PingNewDB() { + MMysql.writelock(__FUNCTION__, __LINE__); + mysql_ping(&mysql); + + int32* errnum = new int32; + *errnum = mysql_errno(&mysql); + + switch (*errnum) + { + case CR_COMMANDS_OUT_OF_SYNC: + case CR_SERVER_GONE_ERROR: + case CR_UNKNOWN_ERROR: + { + LogWrite(DATABASE__ERROR, 0, "Database", "[Database] We lost connection to the database., errno: %i", errno); + break; + } + } + + safe_delete(errnum); + MMysql.releasewritelock(__FUNCTION__, __LINE__); +} \ No newline at end of file diff --git a/source/common/DatabaseNew.h b/source/common/DatabaseNew.h new file mode 100644 index 0000000..2e2e002 --- /dev/null +++ b/source/common/DatabaseNew.h @@ -0,0 +1,48 @@ +#ifndef COMMON_DATABASE_H_ +#define COMMON_DATABASE_H_ + +#include +#include "DatabaseResult.h" + +using namespace std; + +class DatabaseNew { +public: + DatabaseNew(); + virtual ~DatabaseNew(); + + unsigned int GetError() {return mysql_errno(&mysql);} + const char * GetErrorMsg() {return mysql_error(&mysql);} + + bool Connect(); + bool Connect(const char *host, const char *user, const char *password, const char *database, unsigned int port = 3306); + + bool Query(const char *query, ...); + bool Select(DatabaseResult *result, const char *query, ...); + + int32 LastInsertID(); + long AffectedRows(); + + //these two must free() the return char* after it's used in a query + char * Escape(const char *str, size_t len); + char * Escape(const char *str); + + //does not need free() + string EscapeStr(const char *str, size_t len); + string EscapeStr(const char *str); + string EscapeStr(string str); + + bool QueriesFromFile(const char *file); + void SetIgnoredErrno(unsigned int db_errno); + void RemoveIgnoredErrno(unsigned int db_errno); + bool IsIgnoredErrno(unsigned int db_errno); + + void PingNewDB(); +private: + MYSQL mysql; + Mutex MMysql; + + vector ignored_errnos; +}; + +#endif diff --git a/source/common/DatabaseResult.cpp b/source/common/DatabaseResult.cpp new file mode 100644 index 0000000..05df1a8 --- /dev/null +++ b/source/common/DatabaseResult.cpp @@ -0,0 +1,234 @@ +/* + 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 . +*/ +#include +#include +#include "Log.h" +#include "DatabaseResult.h" + +//enforced by MySQL...couldn't find a #define in their headers though +#define FIELD_NAME_MAX 64 + +//return this instead of NULL for certain functions to prevent crashes from coding errors +static const char *empty_str = ""; + +DatabaseResult::DatabaseResult(): field_map(), result(0), num_fields(0), row(0) { +} + +DatabaseResult::~DatabaseResult() { + unsigned int i; + + if (result != NULL) + mysql_free_result(result); + + if (field_map.size()) { + field_map.clear(); + } +} + +bool DatabaseResult::StoreResult(MYSQL_RES* res, uint8 field_count, uint8 row_count) { + + //clear any previously stored result + if (result != NULL) + mysql_free_result(result); + + //clear any field names from a previous result + if (field_map.size()) { + field_map.clear(); + } + + result = res; + num_rows = row_count; + num_fields = field_count; + + // No rows or fields then we don't care + if (!num_rows || !num_fields) { + mysql_free_result(res); + result = NULL; + return false; + } + + + const MYSQL_FIELD* fields = mysql_fetch_fields(result); + + for (uint8 i = 0; i < num_fields; ++i) { + field_map.emplace(std::make_pair(std::string_view(fields[i].name), i)); + } + + return true; +} + +const char * DatabaseResult::GetFieldValue(unsigned int index) { + if (index >= num_fields) { + LogWrite(DATABASE__ERROR, 0, "Database Result", "Attempt to access field at index %u but there %s only %u field%s", index, num_fields == 1 ? "is" : "are", num_fields, num_fields == 1 ? "" : "s"); + return NULL; + } + + return row[index]; +} + +const char * DatabaseResult::GetFieldValueStr(const char *field_name) { + const auto& map_iterator = field_map.find(std::string_view(field_name)); + if (map_iterator != field_map.end()) { + return row[map_iterator->second]; + } + + LogWrite(DATABASE__ERROR, 0, "Database Result", "Unknown field name '%s'", field_name); + return NULL; +} + +bool DatabaseResult::Next() { + return (result != NULL && (row = mysql_fetch_row(result)) != NULL); +} + +bool DatabaseResult::IsNull(unsigned int index) { + const char *value = GetFieldValue(index); + return value == NULL; +} + +bool DatabaseResult::IsNullStr(const char *field_name) { + const char *value = GetFieldValueStr(field_name); + return value == NULL; +} + +int8 DatabaseResult::GetInt8(unsigned int index) { + const char *value = GetFieldValue(index); + return value == NULL ? 0 : atoi(value); +} + +int8 DatabaseResult::GetInt8Str(const char *field_name) { + const char *value = GetFieldValueStr(field_name); + return value == NULL ? 0 : atoi(value); +} + +sint8 DatabaseResult::GetSInt8(unsigned int index) { + const char *value = GetFieldValue(index); + return value == NULL ? 0 : atoi(value); +} + +sint8 DatabaseResult::GetSInt8Str(const char *field_name) { + const char *value = GetFieldValueStr(field_name); + return value == NULL ? 0 : atoi(value); +} + +int16 DatabaseResult::GetInt16(unsigned int index) { + const char *value = GetFieldValue(index); + return value == NULL ? 0 : atoi(value); +} + +int16 DatabaseResult::GetInt16Str(const char *field_name) { + const char *value = GetFieldValueStr(field_name); + return value == NULL ? 0 : atoi(value); +} + +sint16 DatabaseResult::GetSInt16(unsigned int index) { + const char *value = GetFieldValue(index); + return value == NULL ? 0 : atoi(value); +} + +sint16 DatabaseResult::GetSInt16Str(const char *field_name) { + const char *value = GetFieldValueStr(field_name); + return value == NULL ? 0 : atoi(value); +} + +int32 DatabaseResult::GetInt32(unsigned int index) { + const char *value = GetFieldValue(index); + return value == NULL ? 0U : strtoul(value, NULL, 10); +} + +int32 DatabaseResult::GetInt32Str(const char *field_name) { + const char *value = GetFieldValueStr(field_name); + return value == NULL ? 0U : strtoul(value, NULL, 10); +} + +sint32 DatabaseResult::GetSInt32(unsigned int index) { + const char *value = GetFieldValue(index); + return value == NULL ? 0 : atoi(value); +} + +sint32 DatabaseResult::GetSInt32Str(const char *field_name) { + const char *value = GetFieldValueStr(field_name); + return value == NULL ? 0 : atoi(value); +} + +uint64 DatabaseResult::GetInt64(unsigned int index) { + const char *value = GetFieldValue(index); +#ifdef _WIN32 + return value == NULL ? 0UL : _strtoui64(value, NULL, 10); +#else + return value == NULL ? 0UL : strtoull(value, NULL, 10); +#endif +} + +uint64 DatabaseResult::GetInt64Str(const char *field_name) { + const char *value = GetFieldValueStr(field_name); +#ifdef _WIN32 + return value == NULL ? 0UL : _strtoui64(value, NULL, 10); +#else + return value == NULL ? 0UL : strtoull(value, NULL, 10); +#endif +} + +sint64 DatabaseResult::GetSInt64(unsigned int index) { + const char *value = GetFieldValue(index); +#ifdef _WIN32 + return value == NULL ? 0L : _strtoi64(value, NULL, 10); +#else + return value == NULL ? 0L : strtoll(value, NULL, 10); +#endif +} + +sint64 DatabaseResult::GetSInt64Str(const char *field_name) { + const char *value = GetFieldValueStr(field_name); +#ifdef _WIN32 + return value == NULL ? 0L : _strtoi64(value, NULL, 10); +#else + return value == NULL ? 0L : strtoll(value, NULL, 10); +#endif +} + +float DatabaseResult::GetFloat(unsigned int index) { + const char *value = GetFieldValue(index); + return value == NULL ? 0.0F : atof(value); +} + +float DatabaseResult::GetFloatStr(const char *field_name) { + const char *value = GetFieldValueStr(field_name); + return value == NULL ? 0.0F : atof(value); +} + +char DatabaseResult::GetChar(unsigned int index) { + const char *value = GetFieldValue(index); + return value == NULL ? '\0' : value[0]; +} + +char DatabaseResult::GetCharStr(const char *field_name) { + const char *value = GetFieldValueStr(field_name); + return value == NULL ? '\0' : value[0]; +} + +const char * DatabaseResult::GetString(unsigned int index) { + const char *value = GetFieldValue(index); + return value == NULL ? empty_str : value; +} + +const char * DatabaseResult::GetStringStr(const char *field_name) { + const char *value = GetFieldValueStr(field_name); + return value == NULL ? empty_str : value; +} diff --git a/source/common/DatabaseResult.h b/source/common/DatabaseResult.h new file mode 100644 index 0000000..36b3c3e --- /dev/null +++ b/source/common/DatabaseResult.h @@ -0,0 +1,57 @@ +#ifndef COMMON_DATABASERESULT_H_ +#define COMMON_DATABASERESULT_H_ + +#include "types.h" +#ifdef _WIN32 +#include //#include when we/if we go to winsock2 :/ +#endif +#include +#include + +class DatabaseResult { +public: + DatabaseResult(); + virtual ~DatabaseResult(); + + bool StoreResult(MYSQL_RES* result, uint8 field_count, uint8 row_count); + bool Next(); + + bool IsNull(unsigned int index); + bool IsNullStr(const char *field_name); + int8 GetInt8(unsigned int index); + int8 GetInt8Str(const char *field_name); + sint8 GetSInt8(unsigned int index); + sint8 GetSInt8Str(const char *field_name); + int16 GetInt16(unsigned int index); + int16 GetInt16Str(const char *field_name); + sint16 GetSInt16(unsigned int index); + sint16 GetSInt16Str(const char *field_name); + int32 GetInt32(unsigned int index); + int32 GetInt32Str(const char *field_name); + sint32 GetSInt32(unsigned int index); + sint32 GetSInt32Str(const char *field_name); + int64 GetInt64(unsigned int index); + int64 GetInt64Str(const char *field_name); + sint64 GetSInt64(unsigned int index); + sint64 GetSInt64Str(const char *field_name); + float GetFloat(unsigned int index); + float GetFloatStr(const char *field_name); + char GetChar(unsigned int index); + char GetCharStr(const char *field_name); + const char * GetString(unsigned int index); + const char * GetStringStr(const char *field_name); + + const unsigned int GetNumRows() { return num_rows; } + + const char * GetFieldValue(unsigned int index); + const char * GetFieldValueStr(const char *field_name); +private: + MYSQL_RES *result; + MYSQL_ROW row; + unsigned int num_rows; + unsigned int num_fields; + + std::map field_map; +}; + +#endif diff --git a/source/common/EQ2_Common_Structs.h b/source/common/EQ2_Common_Structs.h new file mode 100644 index 0000000..e6db286 --- /dev/null +++ b/source/common/EQ2_Common_Structs.h @@ -0,0 +1,361 @@ +/* + 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 . +*/ +#ifndef _EQ2COMMON_STRUCTS_ +#define _EQ2COMMON_STRUCTS_ + +#define SPAWN_PACKET_SIZE 895 +#define EQUIPMENT_L_WEAPON_INDEX 0 //chars left hand weapon +#define EQUIPMENT_R_WEAPON_INDEX 1 //chars right hand weapon +#define EQUIPMENT_HELMET 2 + +#pragma pack(1) +struct KeyGen_Struct{ + int32 size; +}; +struct KeyGen_End_Struct{ + int32 exponent_len; + int8 exponent; +}; +struct LoginByNumRequest_Struct{ + int32 account_id; + int32 access_code; + int16 version; + int32 unknown2[5]; +}; +struct LS_LoginResponse{ + int8 reply_code; // 0 granted, 1 denied + int16 unknown01; + int8 unknown02; + sint32 unknown03; // -1 denied, 0 granted + sint32 unknown04; + sint32 unknown05; + sint32 unknown06; + int8 unknown07; + int8 unknown08; + int8 unknown09; + int8 unknown10; + sint32 unknown11; + int32 accountid; + int16 unknown12; +}; +#pragma pack() +enum EQ2_EquipmentSlot { + slot_primary=0, + slot_secondary=1, + slot_head=2, + slot_chest=3, + slot_shoulders=4, + slot_forearms=5, + slot_hands=6, + slot_legs=7, + slot_feet=8, + slot_left_ring=9, + slot_right_ring=10, + slot_ears=11, + slot_neck=12, + slot_left_wrist=13, + slot_right_wrist=14, + slot_ranged=15, + slot_ammo=16, + slot_waist=17, + slot_activate1=18, + slot_activate2=19, + slot_textures=20, + slot_hair=21, + slot_beard=22, + slot_naked_chest=23, + slot_naked_legs=24 +}; +struct EQ2_EquipmentItem{ + int16 type; + EQ2_Color color; + EQ2_Color highlight; +}; +struct EQ2_Equipment{ + int16 equip_id[25]; + EQ2_Color color[25]; + EQ2_Color highlight[25]; +}; +#pragma pack(1) +struct CharFeatures{ + int16 hair_type; + int16 hair_face_type; + int16 wing_type; + int16 chest_type; + int16 legs_type; + sint8 eye_type[3]; + sint8 ear_type[3]; + sint8 eye_brow_type[3]; + sint8 cheek_type[3]; + sint8 lip_type[3]; + sint8 chin_type[3]; + sint8 nose_type[3]; + sint8 body_size; + sint8 body_age; + sint8 soga_eye_type[3]; + sint8 soga_ear_type[3]; + sint8 soga_eye_brow_type[3]; + sint8 soga_cheek_type[3]; + int16 soga_chest_type; + int16 soga_legs_type; + sint8 soga_lip_type[3]; + sint8 soga_chin_type[3]; + sint8 soga_nose_type[3]; + sint8 soga_body_size; + sint8 soga_body_age; + int16 soga_hair_type; + int16 soga_hair_face_type; + int16 combat_voice; + int16 emote_voice; + int16 mount_model_type; + + EQ2_Color mount_saddle_color; + EQ2_Color mount_color; + EQ2_Color skin_color; + EQ2_Color eye_color; + EQ2_Color hair_type_color; + EQ2_Color hair_type_highlight_color; + EQ2_Color hair_face_color; + EQ2_Color hair_face_highlight_color; + EQ2_Color hair_highlight_color; + EQ2_Color wing_color1; + EQ2_Color wing_color2; + EQ2_Color shirt_color; + EQ2_Color pants_color; + EQ2_Color hair_color1; + EQ2_Color hair_color2; + EQ2_Color soga_skin_color; + EQ2_Color soga_eye_color; + EQ2_Color soga_hair_color1; + EQ2_Color soga_hair_color2; + EQ2_Color soga_hair_type_color; + EQ2_Color soga_hair_type_highlight_color; + EQ2_Color soga_hair_face_color; + EQ2_Color soga_hair_face_highlight_color; + EQ2_Color soga_hair_highlight_color; + + EQ2_Color model_color; + EQ2_Color soga_model_color; +}; +struct PositionData{ + int32 grid_id; + int32 bad_grid_id; + sint8 Speed1; + sint8 Speed2; + sint16 Dir1; + sint16 Dir2; + sint16 Pitch1; + sint16 Pitch2; + sint16 Roll; + float X; + float Y; + float Z; + float X2; + float Y2; + float Z2; + float X3; + float Y3; + float Z3; + float SpawnOrigX; + float SpawnOrigY; + float SpawnOrigZ; + float SpawnOrigHeading; + float SpawnOrigPitch; + float SpawnOrigRoll; + float SpeedX; + float SpeedY; + float SpeedZ; + float SideSpeed; + float VertSpeed; + float ClientHeading1; + float ClientHeading2; + float ClientPitch; + int16 collision_radius; + int16 state; +}; +struct AppearanceData { + PositionData pos; + int16 model_type; + int16 soga_model_type; + int16 activity_status; + int16 visual_state; + int16 action_state; + int16 mood_state; + int16 emote_state; + int8 attackable; + int8 icon; + int8 hide_hood; + int8 show_level; + + int8 locked_no_loot; + int8 quest_flag; + int8 heroic_flag; + int8 show_command_icon; + int8 display_hand_icon; + int8 player_flag; + int8 targetable; + int8 display_name; + char sub_title[255]; //Guild + int32 display_hp;//0 = 100 percent + int32 power_left; //bar not shown if >=100 + int8 adventure_class; + int8 tradeskill_class; + int8 level; + int8 tradeskill_level; + int8 min_level; + int8 max_level; + int8 difficulty; + int16 visible; // 02 = normal, 15 = shadow + char name[128]; //size around here somewhere + char last_name[64]; + char prefix_title[128]; + char suffix_title[128]; + int8 race; + int8 gender; + int32 randomize; + int8 lua_race_id; +}; +struct Player_Update{ +/*0000*/ int32 activity; +/*0004*/ float unknown2; // 1 +/*0008*/ float direction1; +/*0012*/ float unknown3[8]; +/*0044*/ float speed; +/*0048*/ float side_speed; +/*0052*/ float vert_speed; +/*0056*/ float orig_x; +/*0060*/ float orig_y; +/*0064*/ float orig_z; +/*0068*/ float orig_x2; +/*0072*/ float orig_y2; +/*0076*/ float orig_z2; +/*0080*/ float unknown5[3]; +/*0092*/ int32 unknown6; +/*0096*/ float unknown7[3]; +/*0108*/ int32 unknown8; +/*0112*/ int32 grid_location; +/*0116*/ float x; +/*0120*/ float y; +/*0124*/ float z; +/*0128*/ float direction2; +/*0132*/ float pitch; +/*0136*/ float unknown10; +/*0140*/ float speed_x; +/*0144*/ float speed_y; +/*0148*/ float speed_z; +}; +struct Player_Update283 { + /*0000*/ int32 activity; + /*0004*/ int32 movement_mode; // 1 + /*0008*/ float direction1; + /*0012*/ float desiredpitch; + /*0016*/ float desired_heading_speed; + /*0020*/ float desired_pitch_speed; + /*0024*/ float collision_radius; + /*0028*/ float collision_scale; + /*0032*/ float temp_scale; + /*0036*/ float speed_modifier; + /*0040*/ float swim_speed_modifier; + /*0044*/ float speed; + /*0048*/ float side_speed; + /*0052*/ float vert_speed; + /*0056*/ float orig_x; + /*0060*/ float orig_y; + /*0064*/ float orig_z; + /*0068*/ float orig_x2; + /*0072*/ float orig_y2; + /*0076*/ float orig_z2; + /*0080*/ int32 face_actor_id; + /*0084*/ int32 face_actor_range; + /*0088*/ int32 grid_location; + /*0092*/ float x; + /*0096*/ float y; + /*0100*/ float z; + /*0104*/ float direction2; + /*0108*/ float pitch; + /*0112*/ float roll; + /*0116*/ float speed_x; + /*0120*/ float speed_y; + /*0124*/ float speed_z; +};//0128 +struct Player_Update1096{ +/*0000*/ int32 activity; +/*0004*/ float unknown2; // 1 +/*0008*/ float direction1; +/*0012*/ float unknown3[8]; +/*0044*/ float unk_speed; +/*0048*/ float speed; +/*0052*/ float side_speed; +/*0056*/ float vert_speed; +/*0060*/ float orig_x; +/*0064*/ float orig_y; +/*0068*/ float orig_z; +/*0072*/ float orig_x2; +/*0076*/ float orig_y2; +/*0080*/ float orig_z2; +/*0092*/ float unknown5[3]; +/*0096*/ int32 unknown6; +/*0108*/ float unknown7[3]; +/*0112*/ int32 unknown8; +/*0116*/ int32 grid_location; +/*0120*/ float x; +/*0124*/ float y; +/*0128*/ float z; +/*0132*/ float direction2; +/*0136*/ float pitch; +/*0140*/ float unknown10; +/*0144*/ float speed_x; +/*0148*/ float speed_y; +/*0152*/ float speed_z; +}; + +struct Player_Update1144{ +/*0000*/ int32 activity; +/*0004*/ float unknown2; // 1 +/*0008*/ float direction1; +/*0012*/ float unknown3[12]; +/*0044*/ float unk_speed; +/*0048*/ float speed; +/*0052*/ float side_speed; +/*0056*/ float vert_speed; +/*0060*/ float orig_x; +/*0064*/ float orig_y; +/*0068*/ float orig_z; +/*0072*/ float orig_x2; +/*0076*/ float orig_y2; +/*0080*/ float orig_z2; +/*0092*/ float unknown5[3]; +/*0096*/ int32 unknown6; +/*0108*/ float unknown7[3]; +/*0112*/ int32 unknown8; +/*0116*/ int32 grid_location; +/*0120*/ float x; +/*0124*/ float y; +/*0128*/ float z; +/*0132*/ float direction2; +/*0136*/ float pitch; +/*0140*/ float unknown10; +/*0144*/ float speed_x; +/*0148*/ float speed_y; +/*0152*/ float speed_z; +}; +#pragma pack() +#endif + diff --git a/source/common/EQEMuError.cpp b/source/common/EQEMuError.cpp new file mode 100644 index 0000000..323463a --- /dev/null +++ b/source/common/EQEMuError.cpp @@ -0,0 +1,131 @@ +/* + 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 . +*/ +#ifdef WIN32 +#include +#include +#endif +#include "EQEMuError.h" +#include "linked_list.h" +#include "Mutex.h" +#include "MiscFunctions.h" +#include +#include +#ifdef WIN32 + #include +#endif + +void CatchSignal(int sig_num); + +const char* EQEMuErrorText[EQEMuError_MaxErrorID] = { "ErrorID# 0, No Error", + "MySQL Error #1405 or #2001 means your mysql server rejected the username and password you presented it.", + "MySQL Error #2003 means you were unable to connect to the mysql server.", + "MySQL Error #2005 means you there are too many connections on the mysql server. The server is overloaded.", + "MySQL Error #2007 means you the server is out of memory. The server is overloaded.", + }; + +LinkedList* EQEMuErrorList; +Mutex* MEQEMuErrorList; +AutoDelete< LinkedList > ADEQEMuErrorList(&EQEMuErrorList); +AutoDelete ADMEQEMuErrorList(&MEQEMuErrorList); + +const char* GetErrorText(int32 iError) { + if (iError >= EQEMuError_MaxErrorID) + return "ErrorID# out of range"; + else + return EQEMuErrorText[iError]; +} + +void AddEQEMuError(eEQEMuError iError, bool iExitNow) { + if (!iError) + return; + if (!EQEMuErrorList) { + EQEMuErrorList = new LinkedList; + MEQEMuErrorList = new Mutex; + } + LockMutex lock(MEQEMuErrorList); + + LinkedListIterator iterator(*EQEMuErrorList); + iterator.Reset(); + while (iterator.MoreElements()) { + if (iterator.GetData()[0] == 1) { + if (*((eEQEMuError*) &(iterator.GetData()[1])) == iError) + return; + } + iterator.Advance(); + } + + char* tmp = new char[6]; + tmp[0] = 1; + tmp[5] = 0; + *((int32*) &tmp[1]) = iError; + EQEMuErrorList->Append(tmp); + + if (iExitNow) + CatchSignal(2); +} + +void AddEQEMuError(char* iError, bool iExitNow) { + if (!iError) + return; + if (!EQEMuErrorList) { + EQEMuErrorList = new LinkedList; + MEQEMuErrorList = new Mutex; + } + LockMutex lock(MEQEMuErrorList); + char* tmp = strcpy(new char[strlen(iError) + 1], iError); + EQEMuErrorList->Append(tmp); + + if (iExitNow) + CatchSignal(2); +} + +int32 CheckEQEMuError() { + if (!EQEMuErrorList) + return 0; + int32 ret = 0; + char* tmp = 0; + bool HeaderPrinted = false; + LockMutex lock(MEQEMuErrorList); + + while ((tmp = EQEMuErrorList->Pop() )) { + if (!HeaderPrinted) { + fprintf(stdout, "===============================\nRuntime errors:\n\n"); + HeaderPrinted = true; + } + if (tmp[0] == 1) { + fprintf(stdout, "%s\n", GetErrorText(*((int32*) &tmp[1]))); + } + else { + fprintf(stdout, "%s\n\n", tmp); + } + safe_delete(tmp); + ret++; + } + return ret; +} + +void CheckEQEMuErrorAndPause() { + if (CheckEQEMuError()) { + fprintf(stdout, "Hit any key to exit\n"); + getchar(); + } +} + + diff --git a/source/common/EQEMuError.h b/source/common/EQEMuError.h new file mode 100644 index 0000000..03b364c --- /dev/null +++ b/source/common/EQEMuError.h @@ -0,0 +1,39 @@ +/* + 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 . +*/ +#ifndef EQEMuError_H +#define EQEMuError_H + +#include "../common/types.h" + +enum eEQEMuError { EQEMuError_NoError, + EQEMuError_Mysql_1405, + EQEMuError_Mysql_2003, + EQEMuError_Mysql_2005, + EQEMuError_Mysql_2007, + EQEMuError_MaxErrorID }; + +void AddEQEMuError(eEQEMuError iError, bool iExitNow = false); +void AddEQEMuError(char* iError, bool iExitNow = false); +int32 CheckEQEMuError(); +void CheckEQEMuErrorAndPause(); + +#endif + + diff --git a/source/common/EQPacket.cpp b/source/common/EQPacket.cpp new file mode 100644 index 0000000..499ac90 --- /dev/null +++ b/source/common/EQPacket.cpp @@ -0,0 +1,652 @@ +/* + 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 . +*/ +#include "debug.h" +#include +#include +#include +#include +#include "EQPacket.h" +#include "misc.h" +#include "op_codes.h" +#include "CRC16.h" +#include "opcodemgr.h" +#include "packet_dump.h" +#include +#include "Log.h" +#include + +using namespace std; +extern mapEQOpcodeManager; + +uint8 EQApplicationPacket::default_opcode_size=2; + +EQPacket::EQPacket(const uint16 op, const unsigned char *buf, uint32 len) +{ + this->opcode=op; + this->pBuffer=NULL; + this->size=0; + version = 0; + setTimeInfo(0,0); + if (len>0) { + this->size=len; + pBuffer= new unsigned char[this->size]; + if (buf) { + memcpy(this->pBuffer,buf,this->size); + } else { + memset(this->pBuffer,0,this->size); + } + } +} + +const char* EQ2Packet::GetOpcodeName() { + int16 OpcodeVersion = GetOpcodeVersion(version); + if (EQOpcodeManager.count(OpcodeVersion) > 0) + return EQOpcodeManager[OpcodeVersion]->EmuToName(login_op); + else + return NULL; +} + +int8 EQ2Packet::PreparePacket(int16 MaxLen) { + int16 OpcodeVersion = GetOpcodeVersion(version); + + // stops a crash for incorrect version + if (EQOpcodeManager.count(OpcodeVersion) == 0) + { + LogWrite(PACKET__ERROR, 0, "Packet", "Version %i is not listed in the opcodes table.", version); + return -1; + } + + packet_prepared = true; + + int16 login_opcode = EQOpcodeManager[OpcodeVersion]->EmuToEQ(login_op); + if (login_opcode == 0xcdcd) + { + LogWrite(PACKET__ERROR, 0, "Packet", "Version %i is not listed in the opcodes table for opcode %s", version, EQOpcodeManager[OpcodeVersion]->EmuToName(login_op)); + return -1; + } + + int16 orig_opcode = login_opcode; + int8 offset = 0; + //one of the int16s is for the seq, other is for the EQ2 opcode and compressed flag (OP_Packet is the header, not the opcode) + int32 new_size = size + sizeof(int16) + sizeof(int8); + bool oversized = false; + if (login_opcode != 2) { + new_size += sizeof(int8); //for opcode + if (login_opcode >= 255) { + new_size += sizeof(int16); + oversized = true; + } + else + login_opcode = ntohs(login_opcode); + } + uchar* new_buffer = new uchar[new_size]; + memset(new_buffer, 0, new_size); + uchar* ptr = new_buffer + sizeof(int16); // sequence is first + if (login_opcode != 2) { + if (oversized) { + ptr += sizeof(int8); //compressed flag + int8 addon = 0xff; + memcpy(ptr, &addon, sizeof(int8)); + ptr += sizeof(int8); + } + memcpy(ptr, &login_opcode, sizeof(int16)); + ptr += sizeof(int16); + } + else { + memcpy(ptr, &login_opcode, sizeof(int8)); + ptr += sizeof(int8); + } + memcpy(ptr, pBuffer, size); + + safe_delete_array(pBuffer); + pBuffer = new_buffer; + offset = new_size - size - 1; + size = new_size; + + return offset; +} + +uint32 EQProtocolPacket::serialize(unsigned char *dest, int8 offset) const +{ + if (opcode>0xff) { + *(uint16 *)dest=opcode; + } else { + *(dest)=0; + *(dest+1)=opcode; + } + memcpy(dest+2,pBuffer+offset,size-offset); + + return size+2; +} + +uint32 EQApplicationPacket::serialize(unsigned char *dest) const +{ + uint8 OpCodeBytes = app_opcode_size; + + if (app_opcode_size==1) + *(unsigned char *)dest=opcode; + else + { + // Application opcodes with a low order byte of 0x00 require an extra 0x00 byte inserting prior to the opcode. + if ((opcode & 0x00ff) == 0) + { + *(uint8*)dest = 0; + *(uint16*)(dest + 1) = opcode; + ++OpCodeBytes; + } + else + *(uint16*)dest = opcode; + } + + memcpy(dest+app_opcode_size,pBuffer,size); + + return size+ OpCodeBytes; +} + +EQPacket::~EQPacket() +{ + safe_delete_array(pBuffer); + pBuffer=NULL; +} + + +void EQPacket::DumpRawHeader(uint16 seq, FILE* to) const +{ + /*if (timestamp.tv_sec) { + char temp[20]; + tm t; + const time_t sec = timestamp.tv_sec; + localtime_s(&t, &sec); + strftime(temp, 20, "%F %T", &t); + fprintf(to, "%s.%06lu ", temp, timestamp.tv_usec); + }*/ + + DumpRawHeaderNoTime(seq, to); +} + +const char* EQPacket::GetOpcodeName(){ + int16 OpcodeVersion = GetOpcodeVersion(version); + if(EQOpcodeManager.count(OpcodeVersion) > 0) + return EQOpcodeManager[OpcodeVersion]->EQToName(opcode); + else + return NULL; +} +void EQPacket::DumpRawHeaderNoTime(uint16 seq, FILE *to) const +{ + if (src_ip) { + string sIP,dIP;; + sIP=long2ip(src_ip); + dIP=long2ip(dst_ip); + fprintf(to, "[%s:%d->%s:%d] ",sIP.c_str(),src_port,dIP.c_str(),dst_port); + } + if (seq != 0xffff) + fprintf(to, "[Seq=%u] ",seq); + + string name; + int16 OpcodeVersion = GetOpcodeVersion(version); + if(EQOpcodeManager.count(OpcodeVersion) > 0) + name = EQOpcodeManager[OpcodeVersion]->EQToName(opcode); + + fprintf(to, "[OpCode 0x%04x (%s) Size=%u]\n",opcode,name.c_str(),size); +} + +void EQPacket::DumpRaw(FILE *to) const +{ + DumpRawHeader(); + if (pBuffer && size) + dump_message_column(pBuffer, size, " ", to); + fprintf(to, "\n"); +} + +EQProtocolPacket::EQProtocolPacket(const unsigned char *buf, uint32 len, int in_opcode) +{ + uint32 offset = 0; + if(in_opcode>=0) + opcode = in_opcode; + else{ + offset=2; + opcode=ntohs(*(const uint16 *)buf); + } + + if (len-offset) { + pBuffer= new unsigned char[len-offset]; + size=len-offset; + if(buf) + memcpy(pBuffer,buf+offset,len-offset); + else + memset(pBuffer,0,size); + + } else { + pBuffer=NULL; + size=0; + } + version = 0; + eq2_compressed = false; + packet_prepared = false; + packet_encrypted = false; + sent_time = 0; + attempt_count = 0; + sequence = 0; +} + +bool EQ2Packet::AppCombine(EQ2Packet* rhs){ + bool result = false; + uchar* tmpbuffer = 0; + bool over_sized_packet = false; + int32 new_size = 0; + //bool whee = false; +// DumpPacket(this); +// DumpPacket(rhs); + /*if(rhs->size >= 255){ + DumpPacket(this); + DumpPacket(rhs); + whee = true; + }*/ + if (opcode==OP_AppCombined && ((size + rhs->size + 3) < 255)){ + int16 tmp_size = rhs->size - 2; + if(tmp_size >= 255){ + new_size = size+tmp_size+3; + over_sized_packet = true; + } + else + new_size = size+tmp_size+1; + tmpbuffer = new uchar[new_size]; + uchar* ptr = tmpbuffer; + memcpy(ptr, pBuffer, size); + ptr += size; + if(over_sized_packet){ + memset(ptr, 255, sizeof(int8)); + ptr += sizeof(int8); + tmp_size = htons(tmp_size); + memcpy(ptr, &tmp_size, sizeof(int16)); + ptr += sizeof(int16); + } + else{ + memcpy(ptr, &tmp_size, sizeof(int8)); + ptr += sizeof(int8); + } + memcpy(ptr, rhs->pBuffer+2, rhs->size-2); + delete[] pBuffer; + size = new_size; + pBuffer=tmpbuffer; + safe_delete(rhs); + result=true; + } + else if (rhs->size > 2 && size > 2 && (size + rhs->size + 6) < 255) { + int32 tmp_size = size - 2; + int32 tmp_size2 = rhs->size - 2; + opcode=OP_AppCombined; + bool over_sized_packet2 = false; + new_size = size; + if(tmp_size >= 255){ + new_size += 5; + over_sized_packet = true; + } + else + new_size += 3; + if(tmp_size2 >= 255){ + new_size += tmp_size2+3; + over_sized_packet2 = true; + } + else + new_size += tmp_size2+1; + tmpbuffer = new uchar[new_size]; + tmpbuffer[2]=0; + tmpbuffer[3]=0x19; + uchar* ptr = tmpbuffer+4; + if(over_sized_packet){ + memset(ptr, 255, sizeof(int8)); + ptr += sizeof(int8); + tmp_size = htons(tmp_size); + memcpy(ptr, &tmp_size, sizeof(int16)); + ptr += sizeof(int16); + } + else{ + memcpy(ptr, &tmp_size, sizeof(int8)); + ptr += sizeof(int8); + } + memcpy(ptr, pBuffer+2, size-2); + ptr += (size-2); + if(over_sized_packet2){ + memset(ptr, 255, sizeof(int8)); + ptr += sizeof(int8); + tmp_size2 = htons(tmp_size2); + memcpy(ptr, &tmp_size2, sizeof(int16)); + ptr += sizeof(int16); + } + else{ + memcpy(ptr, &tmp_size2, sizeof(int8)); + ptr += sizeof(int8); + } + memcpy(ptr, rhs->pBuffer+2, rhs->size-2); + size = new_size; + delete[] pBuffer; + pBuffer=tmpbuffer; + safe_delete(rhs); + result=true; + } + /*if(whee){ + DumpPacket(this); + cout << "fsdfsdf"; + }*/ + //DumpPacket(this); + return result; +} + +bool EQProtocolPacket::combine(const EQProtocolPacket *rhs) +{ + bool result=false; + //if(dont_combine) + // return false; + //if (opcode==OP_Combined && size+rhs->size+5<256) { + if (opcode == OP_Combined && size + rhs->size + 5 < 256) { + auto tmpbuffer = new unsigned char[size + rhs->size + 3]; + memcpy(tmpbuffer, pBuffer, size); + uint32 offset = size; + tmpbuffer[offset++] = rhs->Size(); + offset += rhs->serialize(tmpbuffer + offset); + size = offset; + delete[] pBuffer; + pBuffer = tmpbuffer; + result = true; + } + else if (size + rhs->size + 7 < 256) { + auto tmpbuffer = new unsigned char[size + rhs->size + 6]; + uint32 offset = 0; + tmpbuffer[offset++] = Size(); + offset += serialize(tmpbuffer + offset); + tmpbuffer[offset++] = rhs->Size(); + offset += rhs->serialize(tmpbuffer + offset); + size = offset; + delete[] pBuffer; + pBuffer = tmpbuffer; + opcode = OP_Combined; + result = true; + } + return result; +} + +EQApplicationPacket::EQApplicationPacket(const unsigned char *buf, uint32 len, uint8 opcode_size) +{ +uint32 offset=0; + app_opcode_size=(opcode_size==0) ? EQApplicationPacket::default_opcode_size : opcode_size; + + if (app_opcode_size==1) { + opcode=*(const unsigned char *)buf; + offset++; + } else { + opcode=*(const uint16 *)buf; + offset+=2; + } + + if ((len-offset)>0) { + pBuffer=new unsigned char[len-offset]; + memcpy(pBuffer,buf+offset,len-offset); + size=len-offset; + } else { + pBuffer=NULL; + size=0; + } + + emu_opcode = OP_Unknown; +} + +bool EQApplicationPacket::combine(const EQApplicationPacket *rhs) +{ +cout << "CALLED AP COMBINE!!!!\n"; + return false; +} + +void EQApplicationPacket::SetOpcode(EmuOpcode emu_op) { + if(emu_op == OP_Unknown) { + opcode = 0; + emu_opcode = OP_Unknown; + return; + } + + opcode = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(emu_op); + + if(opcode == OP_Unknown) { + LogWrite(PACKET__DEBUG, 0, "Packet", "Unable to convert Emu opcode %s (%d) into an EQ opcode.", OpcodeNames[emu_op], emu_op); + } + + //save the emu opcode we just set. + emu_opcode = emu_op; +} + +const EmuOpcode EQApplicationPacket::GetOpcodeConst() const { + if(emu_opcode != OP_Unknown) { + return(emu_opcode); + } + if(opcode == 10000) { + return(OP_Unknown); + } + + EmuOpcode emu_op; + emu_op = EQOpcodeManager[GetOpcodeVersion(version)]->EQToEmu(opcode); + if(emu_op == OP_Unknown) { + LogWrite(PACKET__DEBUG, 1, "Packet", "Unable to convert EQ opcode 0x%.4X (%i) to an emu opcode (%s)", opcode, opcode, __FUNCTION__); + } + + return(emu_op); +} + +EQApplicationPacket *EQProtocolPacket::MakeApplicationPacket(uint8 opcode_size) const { + EQApplicationPacket *res = new EQApplicationPacket; + res->app_opcode_size=(opcode_size==0) ? EQApplicationPacket::default_opcode_size : opcode_size; + if (res->app_opcode_size==1) { + res->pBuffer= new unsigned char[size+1]; + memcpy(res->pBuffer+1,pBuffer,size); + *(res->pBuffer)=htons(opcode)&0xff; + res->opcode=opcode&0xff; + res->size=size+1; + } else { + res->pBuffer= new unsigned char[size]; + memcpy(res->pBuffer,pBuffer,size); + res->opcode=opcode; + res->size=size; + } + res->copyInfo(this); + return(res); +} +bool EQProtocolPacket::ValidateCRC(const unsigned char *buffer, int length, uint32 Key) +{ +bool valid=false; + // OP_SessionRequest, OP_SessionResponse, OP_OutOfSession are not CRC'd + if (buffer[0]==0x00 && (buffer[1]==OP_SessionRequest || buffer[1]==OP_SessionResponse || buffer[1]==OP_OutOfSession)) { + valid=true; + } else if(buffer[2] == 0x00 && buffer[3] == 0x19){ + valid = true; + } + else { + uint16 comp_crc=CRC16(buffer,length-2,Key); + uint16 packet_crc=ntohs(*(const uint16 *)(buffer+length-2)); +#ifdef EQN_DEBUG + if (packet_crc && comp_crc != packet_crc) { + cout << "CRC mismatch: comp=" << hex << comp_crc << ", packet=" << packet_crc << dec << endl; + } +#endif + valid = (!packet_crc || comp_crc == packet_crc); + } + return valid; +} + +uint32 EQProtocolPacket::Decompress(const unsigned char *buffer, const uint32 length, unsigned char *newbuf, uint32 newbufsize) +{ +uint32 newlen=0; +uint32 flag_offset=0; + newbuf[0]=buffer[0]; + if (buffer[0]==0x00) { + flag_offset=2; + newbuf[1]=buffer[1]; + } else + flag_offset=1; + + if (length>2 && buffer[flag_offset]==0x5a) { + LogWrite(PACKET__DEBUG, 0, "Packet", "In Decompress 1"); + newlen=Inflate(const_cast(buffer+flag_offset+1),length-(flag_offset+1)-2,newbuf+flag_offset,newbufsize-flag_offset)+2; + + // something went bad with zlib + if (newlen == -1) + { + LogWrite(PACKET__ERROR, 0, "Packet", "Debug Bad Inflate!"); + DumpPacket(buffer, length); + memcpy(newbuf, buffer, length); + return length; + } + + newbuf[newlen++]=buffer[length-2]; + newbuf[newlen++]=buffer[length-1]; + } else if (length>2 && buffer[flag_offset]==0xa5) { + LogWrite(PACKET__DEBUG, 0, "Packet", "In Decompress 2"); + memcpy(newbuf+flag_offset,buffer+flag_offset+1,length-(flag_offset+1)); + newlen=length-1; + } else { + memcpy(newbuf,buffer,length); + newlen=length; + } + + return newlen; +} + +uint32 EQProtocolPacket::Compress(const unsigned char *buffer, const uint32 length, unsigned char *newbuf, uint32 newbufsize) { +uint32 flag_offset=1,newlength; + //dump_message_column(buffer,length,"Before: "); + newbuf[0]=buffer[0]; + if (buffer[0]==0) { + flag_offset=2; + newbuf[1]=buffer[1]; + } + if (length>30) { + newlength=Deflate(const_cast(buffer+flag_offset),length-flag_offset,newbuf+flag_offset+1,newbufsize); + *(newbuf+flag_offset)=0x5a; + newlength+=flag_offset+1; + } else { + memmove(newbuf+flag_offset+1,buffer+flag_offset,length-flag_offset); + *(newbuf+flag_offset)=0xa5; + newlength=length+1; + } + //dump_message_column(newbuf,length,"After: "); + + return newlength; +} + +void EQProtocolPacket::ChatDecode(unsigned char *buffer, int size, int DecodeKey) +{ + if (buffer[1]!=0x01 && buffer[0]!=0x02 && buffer[0]!=0x1d) { + int Key=DecodeKey; + unsigned char *test=(unsigned char *)malloc(size); + buffer+=2; + size-=2; + + int i; + for (i = 0 ; i+4 <= size ; i+=4) + { + int pt = (*(int*)&buffer[i])^(Key); + Key = (*(int*)&buffer[i]); + *(int*)&test[i]=pt; + } + unsigned char KC=Key&0xFF; + for ( ; i < size ; i++) + { + test[i]=buffer[i]^KC; + } + memcpy(buffer,test,size); + free(test); + } +} + +void EQProtocolPacket::ChatEncode(unsigned char *buffer, int size, int EncodeKey) +{ + if (buffer[1]!=0x01 && buffer[0]!=0x02 && buffer[0]!=0x1d) { + int Key=EncodeKey; + char *test=(char*)malloc(size); + int i; + buffer+=2; + size-=2; + for ( i = 0 ; i+4 <= size ; i+=4) + { + int pt = (*(int*)&buffer[i])^(Key); + Key = pt; + *(int*)&test[i]=pt; + } + unsigned char KC=Key&0xFF; + for ( ; i < size ; i++) + { + test[i]=buffer[i]^KC; + } + memcpy(buffer,test,size); + free(test); + } +} + +bool EQProtocolPacket::IsProtocolPacket(const unsigned char* in_buff, uint32_t len, bool bTrimCRC) { + bool ret = false; + uint16_t opcode = ntohs(*(uint16_t*)in_buff); + uint32_t offset = 2; + + switch (opcode) { + case OP_SessionRequest: + case OP_SessionDisconnect: + case OP_KeepAlive: + case OP_SessionStatResponse: + case OP_Packet: + case OP_Combined: + case OP_Fragment: + case OP_Ack: + case OP_OutOfOrderAck: + case OP_OutOfSession: + { + ret = true; + break; + } + } + + return ret; +} + + + +void DumpPacketHex(const EQApplicationPacket* app) +{ + DumpPacketHex(app->pBuffer, app->size); +} + +void DumpPacketAscii(const EQApplicationPacket* app) +{ + DumpPacketAscii(app->pBuffer, app->size); +} +void DumpPacket(const EQProtocolPacket* app) { + DumpPacketHex(app->pBuffer, app->size); +} +void DumpPacket(const EQApplicationPacket* app, bool iShowInfo) { + if (iShowInfo) { + cout << "Dumping Applayer: 0x" << hex << setfill('0') << setw(4) << app->GetOpcode() << dec; + cout << " size:" << app->size << endl; + } + DumpPacketHex(app->pBuffer, app->size); +// DumpPacketAscii(app->pBuffer, app->size); +} + +void DumpPacketBin(const EQApplicationPacket* app) { + DumpPacketBin(app->pBuffer, app->size); +} + + diff --git a/source/common/EQPacket.h b/source/common/EQPacket.h new file mode 100644 index 0000000..455a72c --- /dev/null +++ b/source/common/EQPacket.h @@ -0,0 +1,209 @@ +/* + 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 . +*/ +#ifndef _EQPACKET_H +#define _EQPACKET_H + +#include "types.h" +#include +#include + +#ifdef WIN32 + #include + #include +#else + #include + #include +#endif + +#include "emu_opcodes.h" +#include "op_codes.h" +#include "packet_dump.h" + +class OpcodeManager; + +class EQStream; + +class EQPacket { + friend class EQStream; +public: + unsigned char *pBuffer; + uint32 size; + uint32 src_ip,dst_ip; + uint16 src_port,dst_port; + uint32 priority; + timeval timestamp; + int16 version; + ~EQPacket(); + void DumpRawHeader(uint16 seq=0xffff, FILE *to = stdout) const; + void DumpRawHeaderNoTime(uint16 seq=0xffff, FILE *to = stdout) const; + void DumpRaw(FILE *to = stdout) const; + const char* GetOpcodeName(); + + void setVersion(int16 new_version){ version = new_version; } + void setSrcInfo(uint32 sip, uint16 sport) { src_ip=sip; src_port=sport; } + void setDstInfo(uint32 dip, uint16 dport) { dst_ip=dip; dst_port=dport; } + void setTimeInfo(uint32 ts_sec, uint32 ts_usec) { timestamp.tv_sec=ts_sec; timestamp.tv_usec=ts_usec; } + void copyInfo(const EQPacket *p) { src_ip=p->src_ip; src_port=p->src_port; dst_ip=p->dst_ip; dst_port=p->dst_port; timestamp.tv_sec=p->timestamp.tv_sec; timestamp.tv_usec=p->timestamp.tv_usec; } + uint32 Size() const { return size+2; } + +//no reason to have this method in zone or world + + uint16 GetRawOpcode() const { return(opcode); } + + + inline bool operator<(const EQPacket &rhs) { + return (timestamp.tv_sec < rhs.timestamp.tv_sec || (timestamp.tv_sec==rhs.timestamp.tv_sec && timestamp.tv_usec < rhs.timestamp.tv_usec)); + } + void SetProtocolOpcode(int16 new_opcode){ + opcode = new_opcode; + } + +protected: + uint16 opcode; + + EQPacket(const uint16 op, const unsigned char *buf, const uint32 len); + EQPacket(const EQPacket &p) { version = 0; } + EQPacket() { opcode=0; pBuffer=NULL; size=0; version = 0; setTimeInfo(0, 0); } + +}; + +class EQApplicationPacket; + +class EQProtocolPacket : public EQPacket { +public: + EQProtocolPacket(uint16 op, const unsigned char *buf, uint32 len) : EQPacket(op,buf,len) { + eq2_compressed = false; + packet_prepared = false; + packet_encrypted = false; + sequence = 0; + sent_time = 0; + attempt_count = 0; + acked = false; + } + EQProtocolPacket(const unsigned char *buf, uint32 len, int in_opcode = -1); + bool combine(const EQProtocolPacket *rhs); + uint32 serialize (unsigned char *dest, int8 offset = 0) const; + static bool ValidateCRC(const unsigned char *buffer, int length, uint32 Key); + static uint32 Decompress(const unsigned char *buffer, const uint32 length, unsigned char *newbuf, uint32 newbufsize); + static uint32 Compress(const unsigned char *buffer, const uint32 length, unsigned char *newbuf, uint32 newbufsize); + static void ChatDecode(unsigned char *buffer, int size, int DecodeKey); + static void ChatEncode(unsigned char *buffer, int size, int EncodeKey); + static bool IsProtocolPacket(const unsigned char* in_buff, uint32_t len, bool bTrimCRC); + + EQProtocolPacket *Copy() { + EQProtocolPacket* new_packet = new EQProtocolPacket(opcode,pBuffer,size); + new_packet->eq2_compressed = this->eq2_compressed; + new_packet->packet_prepared = this->packet_prepared; + new_packet->packet_encrypted = this->packet_encrypted; + return new_packet; + } + EQApplicationPacket *MakeApplicationPacket(uint8 opcode_size=0) const; + bool eq2_compressed; + bool packet_prepared; + bool packet_encrypted; + bool acked; + int32 sent_time; + int8 attempt_count; + int32 sequence; + +private: + EQProtocolPacket(const EQProtocolPacket &p) { } + //bool dont_combine; +}; +class EQ2Packet : public EQProtocolPacket { +public: + EQ2Packet(const EmuOpcode in_login_op, const unsigned char *buf, uint32 len) : EQProtocolPacket(OP_Packet,buf,len){ + login_op = in_login_op; + eq2_compressed = false; + packet_prepared = false; + packet_encrypted = false; + } + bool AppCombine(EQ2Packet* rhs); + EQ2Packet* Copy() { + EQ2Packet* new_packet = new EQ2Packet(login_op,pBuffer,size); + new_packet->eq2_compressed = this->eq2_compressed; + new_packet->packet_prepared = this->packet_prepared; + new_packet->packet_encrypted = this->packet_encrypted; + return new_packet; + } + int8 PreparePacket(int16 MaxLen); + const char* GetOpcodeName(); + EmuOpcode login_op; +}; +class EQApplicationPacket : public EQPacket { + friend class EQProtocolPacket; + friend class EQStream; +public: + EQApplicationPacket() : EQPacket(0,NULL,0) { emu_opcode = OP_Unknown; app_opcode_size=default_opcode_size; } + EQApplicationPacket(const EmuOpcode op) : EQPacket(0,NULL,0) { SetOpcode(op); app_opcode_size=default_opcode_size; } + EQApplicationPacket(const EmuOpcode op, const uint32 len) : EQPacket(0,NULL,len) { SetOpcode(op); app_opcode_size=default_opcode_size; } + EQApplicationPacket(const EmuOpcode op, const unsigned char *buf, const uint32 len) : EQPacket(0,buf,len) { SetOpcode(op); app_opcode_size=default_opcode_size; } + bool combine(const EQApplicationPacket *rhs); + uint32 serialize (unsigned char *dest) const; + uint32 Size() const { return size+app_opcode_size; } + EQApplicationPacket *Copy() const { + EQApplicationPacket *it = new EQApplicationPacket; + try { + it->pBuffer= new unsigned char[size]; + memcpy(it->pBuffer,pBuffer,size); + it->size=size; + it->opcode = opcode; + it->emu_opcode = emu_opcode; + it->version = version; + return(it); + } + catch( bad_alloc &ba ) + { + cout << ba.what() << endl; + if( NULL != it ) + delete it; + } + return NULL; + } + + void SetOpcodeSize(uint8 s) { app_opcode_size=s; } + void SetOpcode(EmuOpcode op); + const EmuOpcode GetOpcodeConst() const; + inline const EmuOpcode GetOpcode() const { return(GetOpcodeConst()); } + //caching version of get + inline const EmuOpcode GetOpcode() { EmuOpcode r = GetOpcodeConst(); emu_opcode = r; return(r); } + + static uint8 default_opcode_size; + +protected: + //this is just a cache so we dont look it up several times on Get() + EmuOpcode emu_opcode; + +private: + //this constructor should only be used by EQProtocolPacket, as it + //assumes the first two bytes of buf are the opcode. + EQApplicationPacket(const unsigned char *buf, uint32 len, uint8 opcode_size=0); + EQApplicationPacket(const EQApplicationPacket &p) { emu_opcode = OP_Unknown; app_opcode_size=default_opcode_size; } + + uint8 app_opcode_size; +}; + +void DumpPacketHex(const EQApplicationPacket* app); +void DumpPacket(const EQProtocolPacket* app); +void DumpPacketAscii(const EQApplicationPacket* app); +void DumpPacket(const EQApplicationPacket* app, bool iShowInfo = false); +void DumpPacketBin(const EQApplicationPacket* app); + +#endif diff --git a/source/common/EQStream.cpp b/source/common/EQStream.cpp new file mode 100644 index 0000000..6544496 --- /dev/null +++ b/source/common/EQStream.cpp @@ -0,0 +1,1908 @@ +/* + 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 . +*/ +#ifdef WIN32 +#include + #include +#endif +#include "debug.h" +#include +#include +#include +#include +#include +#include +#ifdef WIN32 + #include +#else + #include + #include + #include + #include + #include + #include + #include +#endif +#include "EQPacket.h" +#include "EQStream.h" +#include "EQStreamFactory.h" +#include "misc.h" +#include "Mutex.h" +#include "op_codes.h" +#include "CRC16.h" +#include "packet_dump.h" +#ifdef LOGIN + #include "../LoginServer/login_structs.h" +#endif +#include "EQ2_Common_Structs.h" +#include "Log.h" + + +//#define DEBUG_EMBEDDED_PACKETS 1 +uint16 EQStream::MaxWindowSize=2048; + +void EQStream::init(bool resetSession) { + if (resetSession) + { + streamactive = false; + sessionAttempts = 0; + } + + timeout_delays = 0; + + MInUse.lock(); + active_users = 0; + MInUse.unlock(); + + Session=0; + Key=0; + MaxLen=0; + NextInSeq=0; + NextOutSeq=0; + CombinedAppPacket=NULL; + + MAcks.lock(); + MaxAckReceived = -1; + NextAckToSend = -1; + LastAckSent = -1; + MAcks.unlock(); + + LastSeqSent=-1; + MaxSends=5; + LastPacket=Timer::GetCurrentTime2(); + oversize_buffer=NULL; + oversize_length=0; + oversize_offset=0; + Factory = NULL; + + rogue_buffer=NULL; + roguebuf_offset=0; + roguebuf_size=0; + + MRate.lock(); + RateThreshold=RATEBASE/250; + DecayRate=DECAYBASE/250; + MRate.unlock(); + + BytesWritten=0; + SequencedBase = 0; + AverageDelta = 500; + + crypto->setRC4Key(0); + + retransmittimer = Timer::GetCurrentTime2(); + retransmittimeout = 500 * RETRANSMIT_TIMEOUT_MULT; + + reconnectAttempt = 0; + if (uint16(SequencedBase + SequencedQueue.size()) != NextOutSeq) { + LogWrite(PACKET__DEBUG, 9, "Packet", "init Invalid Sequenced queue: BS %u + SQ %u != NOS %u", SequencedBase, SequencedQueue.size(), NextOutSeq); + } +} + +EQStream::EQStream(sockaddr_in addr){ + crypto = new Crypto(); + resend_que_timer = new Timer(1000); + combine_timer = new Timer(250); //250 milliseconds + combine_timer->Start(); + resend_que_timer->Start(); + init(); + remote_ip=addr.sin_addr.s_addr; + remote_port=addr.sin_port; + State=CLOSED; + StreamType=UnknownStream; + compressed=true; + encoded=false; + app_opcode_size=2; + #ifdef WIN32 + ZeroMemory(&stream, sizeof(z_stream)); + #else + bzero(&stream, sizeof(z_stream)); + #endif + stream.zalloc = (alloc_func)0; + stream.zfree = (free_func)0; + stream.opaque = (voidpf)0; + deflateInit2(&stream, 9, Z_DEFLATED, 13, 9, Z_DEFAULT_STRATEGY); + //deflateInit(&stream, 5); + compressed_offset = 0; + client_version = 0; + received_packets = 0; + sent_packets = 0; + +#ifdef WRITE_PACKETS + write_packets = 0; + char write_packets_filename[64]; + snprintf(write_packets_filename, sizeof(write_packets_filename), "PacketLog%i.log", Timer::GetCurrentTime2()); + write_packets = fopen(write_packets_filename, "w+"); +#endif +} + +EQProtocolPacket* EQStream::ProcessEncryptedData(uchar* data, int32 size, int16 opcode){ + //cout << "B4:\n"; + //DumpPacket(data, size); + /*if(size >= 2 && data[0] == 0 && data[1] == 0){ + cout << "Attempting to fix packet!\n"; + //Have to fix bad packet from client or it will screw up encryption :P + size--; + data++; + }*/ + crypto->RC4Decrypt(data,size); + int8 offset = 0; + if(data[0] == 0xFF && size > 2){ + offset = 3; + memcpy(&opcode, data+sizeof(int8), sizeof(int16)); + } + else{ + offset = 1; + memcpy(&opcode, data, sizeof(int8)); + } + //cout << "After:\n"; + //DumpPacket(data, size); + return new EQProtocolPacket(opcode, data+offset, size - offset); +} + +EQProtocolPacket* EQStream::ProcessEncryptedPacket(EQProtocolPacket *p){ + EQProtocolPacket* ret = NULL; + if(p->opcode == OP_Packet && p->size > 2) + ret = ProcessEncryptedData(p->pBuffer+2, p->size-2, p->opcode); + else + ret = ProcessEncryptedData(p->pBuffer, p->size, p->opcode); + return ret; +} + +bool EQStream::ProcessEmbeddedPacket(uchar* pBuffer, int16 length,int8 opcode) { + if(!pBuffer || !crypto->isEncrypted()) + return false; + + MCombineQueueLock.lock(); + EQProtocolPacket* newpacket = ProcessEncryptedData(pBuffer, length, opcode); + MCombineQueueLock.unlock(); + + if (newpacket) { +#ifdef DEBUG_EMBEDDED_PACKETS + printf("Opcode: %u\n", newpacket->opcode); + DumpPacket(newpacket->pBuffer, newpacket->size); +#endif + + EQApplicationPacket* ap = newpacket->MakeApplicationPacket(2); + if (ap->version == 0) + ap->version = client_version; + InboundQueuePush(ap); +#ifdef WRITE_PACKETS + WritePackets(ap->GetOpcodeName(), pBuffer, length, false); +#endif + safe_delete(newpacket); + return true; + } + + return false; +} + +bool EQStream::HandleEmbeddedPacket(EQProtocolPacket *p, int16 offset, int16 length){ + if(!p) + return false; + +#ifdef DEBUG_EMBEDDED_PACKETS + // printf works better with DumpPacket + printf( "Start Packet with offset %u, length %u, p->size %u\n", offset, length, p->size); +#endif + + if(p->size >= ((uint32)(offset+2))){ + if(p->pBuffer[offset] == 0 && p->pBuffer[offset+1] == 0x19){ + if(length == 0) + length = p->size-2-offset; + else + length-=2; +#ifdef DEBUG_EMBEDDED_PACKETS + printf( "Creating OP_AppCombined Packet with offset %u, length %u, p->size %u\n", offset, length, p->size); + DumpPacket(p->pBuffer, p->size); +#endif + + EQProtocolPacket *subp=new EQProtocolPacket(OP_AppCombined, p->pBuffer+2+offset, length); + subp->copyInfo(p); + ProcessPacket(subp, p); + safe_delete(subp); + return true; + } + else if (p->pBuffer[offset] == 0 && p->pBuffer[offset + 1] == 0) { + if (length == 0) + length = p->size - 1 - offset; + else + length--; + +#ifdef DEBUG_EMBEDDED_PACKETS + printf( "Creating Opcode 0 Packet!"); + DumpPacket(p->pBuffer + 1 + offset, length); +#endif + uchar* buffer = (p->pBuffer + 1 + offset); + bool valid = ProcessEmbeddedPacket(buffer, length); + + if(valid) + return true; + } + else if(offset+4 < p->size && ntohl(*(uint32 *)(p->pBuffer+offset)) != 0xffffffff) { +#ifdef DEBUG_EMBEDDED_PACKETS + uint16 seq = NextInSeq-1; + sint8 check = 0; + + if(offset == 2) { + seq=ntohs(*(uint16 *)(p->pBuffer)); + check=CompareSequence(NextInSeq,seq); + } + printf( "Unhandled Packet with offset %u, length %u, p->size %u, check: %i, nextinseq: %u, seq: %u\n", offset, length, p->size, check, NextInSeq, seq); + DumpPacket(p->pBuffer, p->size); +#endif + + if(length == 0) + length = p->size - offset; + + + uchar* buffer = (p->pBuffer + offset); + + bool valid = ProcessEmbeddedPacket(buffer, length); + + if(valid) + return true; + } + else if(p->pBuffer[offset] != 0xff && p->pBuffer[offset+1] == 0xff && p->size > (1+offset)) { + uint8 new_length = 0; + + memcpy(&new_length, p->pBuffer+offset, sizeof(int8)); + if((new_length+offset+2) == p->size) { + new_length -= 2; + EQProtocolPacket *subp=new EQProtocolPacket(p->pBuffer+offset+2, new_length, OP_Packet); + subp->copyInfo(p); + ProcessPacket(subp, p); + delete subp; + return true; + } + } + } + return false; +} + +void EQStream::ProcessPacket(EQProtocolPacket *p, EQProtocolPacket* lastp) +{ + uint32 processed=0,subpacket_length=0; + + if (p) { + + if (p->opcode!=OP_SessionRequest && p->opcode!=OP_SessionResponse && !Session) { +#ifdef EQN_DEBUG + LogWrite(PACKET__ERROR, 0, "Packet", "*** Session not initialized, packet ignored "); + //p->DumpRaw(); +#endif + return; + } + + //cout << "Received " << (int)p->opcode << ":\n"; + //DumpPacket(p->pBuffer, p->size); + switch (p->opcode) { + case OP_Combined: { + processed=0; + int8 offset = 0; + int count = 0; +#ifdef LE_DEBUG + printf( "OP_Combined:\n"); + DumpPacket(p); +#endif + while(processedsize) { + if ((subpacket_length=(unsigned char)*(p->pBuffer+processed))==0xff) { + subpacket_length = ntohs(*(uint16*)(p->pBuffer + processed + 1)); + //printf("OP_Combined subpacket_length %u\n",subpacket_length); + offset = 3; + } + else { + offset = 1; + } + + //printf("OP_Combined processed %u p->size %u subpacket length %u count %i\n",processed, p->size, subpacket_length, count); + count++; +#ifdef LE_DEBUG + printf( "OP_Combined Packet %i (%u) (%u):\n", count, subpacket_length, processed); +#endif + bool isSubPacket = EQProtocolPacket::IsProtocolPacket(p->pBuffer + processed + offset, subpacket_length, false); + if (isSubPacket) { + EQProtocolPacket* subp = new EQProtocolPacket(p->pBuffer + processed + offset, subpacket_length); + subp->copyInfo(p); +#ifdef LE_DEBUG + printf( "Opcode %i:\n", subp->opcode); + DumpPacket(subp); +#endif + ProcessPacket(subp, p); +#ifdef LE_DEBUG + DumpPacket(subp); +#endif + delete subp; + } + else { + offset = 1; // 0xFF in this case means it is actually 255 bytes of encrypted data after a 00 09 packet + //Garbage packet? + if(ntohs(*reinterpret_cast(p->pBuffer + processed + offset)) <= 0x1e) { + subpacket_length=(unsigned char)*(p->pBuffer+processed); + LogWrite(PACKET__ERROR, 0, "Packet", "!!!!!!!!!Garbage Packet Unknown Process as OP_Packet!!!!!!!!!!!!!\n"); + DumpPacket(p->pBuffer + processed + offset, subpacket_length); + uchar* newbuf = p->pBuffer; + newbuf += processed + offset; + EQProtocolPacket *subp=new EQProtocolPacket(newbuf,subpacket_length); + subp->copyInfo(p); + ProcessPacket(subp, p); + delete subp; + } + else { + crypto->RC4Decrypt(p->pBuffer + processed + offset, subpacket_length); + LogWrite(PACKET__ERROR, 0, "Packet", "!!!!!!!!!Garbage Packet!!!!!!!!!!!!! processed: %u, offset: %u, count: %i, subpacket_length: %u, offset_pos_1: %u, oversized_buffer_present: %u, offset size: %u, offset length: %u\n", + processed, offset, count, subpacket_length, p->pBuffer[processed + offset], oversize_buffer ? 1 : 0, oversize_offset, oversize_length); + if(p->pBuffer[processed + offset] == 0xff) + { + uchar* newbuf = p->pBuffer; + newbuf += processed + offset + 1; + + DumpPacket(p->pBuffer + processed + offset, subpacket_length); + EQProtocolPacket *subp=new EQProtocolPacket(newbuf, subpacket_length, OP_Packet); + subp->copyInfo(p); + ProcessPacket(subp, p); + delete subp; + } + else + break; // bad packet + } + } + processed+=subpacket_length+offset; + } + break; + } + case OP_AppCombined: { + processed=0; + EQProtocolPacket* newpacket = 0; + int8 offset = 0; +#ifdef DEBUG_EMBEDDED_PACKETS + printf( "OP_AppCombined: \n"); + DumpPacket(p); +#endif + int count = 0; + while(processedsize) { + count++; + if ((subpacket_length=(unsigned char)*(p->pBuffer+processed))==0xff) { + subpacket_length=ntohs(*(uint16 *)(p->pBuffer+processed+1)); + offset = 3; + } else + offset = 1; + + if(crypto->getRC4Key()==0 && p && subpacket_length > 8+offset){ + #ifdef DEBUG_EMBEDDED_PACKETS + DumpPacket(p->pBuffer, p->size); + #endif + p->pBuffer += offset; + processRSAKey(p, subpacket_length); + p->pBuffer -= offset; + } + else if(crypto->isEncrypted()){ +#ifdef DEBUG_EMBEDDED_PACKETS + printf( "OP_AppCombined Packet %i (%u) (%u): \n", count, subpacket_length, processed); + DumpPacket(p->pBuffer+processed+offset, subpacket_length); +#endif + if(!HandleEmbeddedPacket(p, processed + offset, subpacket_length)){ + uchar* buffer = (p->pBuffer + processed + offset); + if(!ProcessEmbeddedPacket(buffer, subpacket_length, OP_AppCombined)) { + LogWrite(PACKET__ERROR, 0, "Packet", "*** This is bad, ProcessEmbeddedPacket failed, report to Image!"); + } + } + } + processed+=subpacket_length+offset; + } + } + break; + case OP_Packet: { + if (!p->pBuffer || (p->Size() < 4)) + { + break; + } + + uint16 seq=ntohs(*(uint16 *)(p->pBuffer)); + sint8 check=CompareSequence(NextInSeq,seq); + if (check == SeqFuture) { +#ifdef EQN_DEBUG + LogWrite(PACKET__DEBUG, 1, "Packet", "*** Future packet: Expecting Seq=%i, but got Seq=%i", NextInSeq, seq); + LogWrite(PACKET__DEBUG, 1, "Packet", "[Start]"); + p->DumpRawHeader(seq); + LogWrite(PACKET__DEBUG, 1, "Packet", "[End]"); +#endif + OutOfOrderpackets[seq] = p->Copy(); + + // Image (2020): Removed as this is bad contributes to infinite loop + //SendOutOfOrderAck(seq); + } else if (check == SeqPast) { +#ifdef EQN_DEBUG + LogWrite(PACKET__DEBUG, 1, "Packet", "*** Duplicate packet: Expecting Seq=%i, but got Seq=%i", NextInSeq, seq); + LogWrite(PACKET__DEBUG, 1, "Packet", "[Start]"); + p->DumpRawHeader(seq); + LogWrite(PACKET__DEBUG, 1, "Packet", "[End]"); +#endif + // Image (2020): Removed as this is bad contributes to infinite loop + //OutOfOrderpackets[seq] = p->Copy(); + SendOutOfOrderAck(seq); + } else { + EQProtocolPacket* qp = RemoveQueue(seq); + if (qp) { + LogWrite(PACKET__DEBUG, 1, "Packet", "OP_Fragment: Removing older queued packet with sequence %i", seq); + delete qp; + } + + SetNextAckToSend(seq); + NextInSeq++; + + if(HandleEmbeddedPacket(p)) + break; + if(crypto->getRC4Key()==0 && p && p->size >= 69){ + #ifdef DEBUG_EMBEDDED_PACKETS + DumpPacket(p->pBuffer, p->size); + #endif + processRSAKey(p); + } + else if(crypto->isEncrypted() && p){ + MCombineQueueLock.lock(); + EQProtocolPacket* newpacket = ProcessEncryptedPacket(p); + MCombineQueueLock.unlock(); + if(newpacket){ + EQApplicationPacket* ap = newpacket->MakeApplicationPacket(2); + if (ap->version == 0) + ap->version = client_version; +#ifdef WRITE_PACKETS + WritePackets(ap->GetOpcodeName(), p->pBuffer, p->size, false); +#endif + InboundQueuePush(ap); + safe_delete(newpacket); + } + } + } + } + break; + case OP_Fragment: { + if (!p->pBuffer || (p->Size() < 4)) + { + break; + } + + uint16 seq=ntohs(*(uint16 *)(p->pBuffer)); + sint8 check=CompareSequence(NextInSeq,seq); + if (check == SeqFuture) { +#ifdef EQN_DEBUG + LogWrite(PACKET__DEBUG, 1, "Packet", "*** Future packet2: Expecting Seq=%i, but got Seq=%i", NextInSeq, seq); + LogWrite(PACKET__DEBUG, 1, "Packet", "[Start]"); + //p->DumpRawHeader(seq); + LogWrite(PACKET__DEBUG, 1, "Packet", "[End]"); +#endif + OutOfOrderpackets[seq] = p->Copy(); + //SendOutOfOrderAck(seq); + } else if (check == SeqPast) { +#ifdef EQN_DEBUG + LogWrite(PACKET__DEBUG, 1, "Packet", "*** Duplicate packet2: Expecting Seq=%i, but got Seq=%i", NextInSeq, seq); + LogWrite(PACKET__DEBUG, 1, "Packet", "[Start]"); + //p->DumpRawHeader(seq); + LogWrite(PACKET__DEBUG, 1, "Packet", "[End]"); +#endif + //OutOfOrderpackets[seq] = p->Copy(); + SendOutOfOrderAck(seq); + } else { + // In case we did queue one before as well. + EQProtocolPacket* qp = RemoveQueue(seq); + if (qp) { + LogWrite(PACKET__DEBUG, 1, "Packet", "OP_Fragment: Removing older queued packet with sequence %i", seq); + delete qp; + } + + SetNextAckToSend(seq); + NextInSeq++; + if (oversize_buffer) { + memcpy(oversize_buffer+oversize_offset,p->pBuffer+2,p->size-2); + oversize_offset+=p->size-2; + //cout << "Oversized is " << oversize_offset << "/" << oversize_length << " (" << (p->size-2) << ") Seq=" << seq << endl; + if (oversize_offset==oversize_length) { + if (*(p->pBuffer+2)==0x00 && *(p->pBuffer+3)==0x19) { + EQProtocolPacket *subp=new EQProtocolPacket(oversize_buffer,oversize_offset); + subp->copyInfo(p); + ProcessPacket(subp, p); + delete subp; + } else { + + if(crypto->isEncrypted() && p && p->size > 2){ + MCombineQueueLock.lock(); + EQProtocolPacket* p2 = ProcessEncryptedData(oversize_buffer, oversize_offset, p->opcode); + MCombineQueueLock.unlock(); + EQApplicationPacket* ap = p2->MakeApplicationPacket(2); + ap->copyInfo(p); + if (ap->version == 0) + ap->version = client_version; +#ifdef WRITE_PACKETS + WritePackets(ap->GetOpcodeName(), oversize_buffer, oversize_offset, false); +#endif + ap->copyInfo(p); + InboundQueuePush(ap); + safe_delete(p2); + } + } + delete[] oversize_buffer; + oversize_buffer=NULL; + oversize_offset=0; + } + } else if (!oversize_buffer) { + oversize_length=ntohl(*(uint32 *)(p->pBuffer+2)); + oversize_buffer=new unsigned char[oversize_length]; + memcpy(oversize_buffer,p->pBuffer+6,p->size-6); + oversize_offset=p->size-6; + //cout << "Oversized is " << oversize_offset << "/" << oversize_length << " (" << (p->size-6) << ") Seq=" << seq << endl; + } + } + } + break; + case OP_KeepAlive: { +#ifndef COLLECTOR + NonSequencedPush(new EQProtocolPacket(p->opcode,p->pBuffer,p->size)); +#endif + } + break; + case OP_Ack: { + if (!p->pBuffer || (p->Size() < 4)) + { + LogWrite(PACKET__DEBUG, 9, "Packet", "Received OP_Ack that was of malformed size"); + break; + } + uint16 seq = ntohs(*(uint16*)(p->pBuffer)); + AckPackets(seq); + retransmittimer = Timer::GetCurrentTime2(); + } + break; + case OP_SessionRequest: { + if (p->Size() < sizeof(SessionRequest)) + { + break; + } + + if (GetState() == ESTABLISHED) { + //_log(NET__ERROR, _L "Received OP_SessionRequest in ESTABLISHED state (%d) streamactive (%i) attempt (%i)" __L, GetState(), streamactive, sessionAttempts); + + // client seems to try a max of 4 times (initial +3 retries) then gives up, giving it a few more attempts just in case + // streamactive means we identified the opcode, we cannot re-establish this connection + if (streamactive || (sessionAttempts > 30)) + { + SendDisconnect(false); + SetState(CLOSED); + break; + } + } + + sessionAttempts++; + if(GetState() == WAIT_CLOSE) { + printf("WAIT_CLOSE Reconnect with streamactive %u, sessionAttempts %u\n", streamactive, sessionAttempts); + reconnectAttempt++; + } + init(GetState() != ESTABLISHED); + OutboundQueueClear(); + SessionRequest *Request=(SessionRequest *)p->pBuffer; + Session=ntohl(Request->Session); + SetMaxLen(ntohl(Request->MaxLength)); +#ifndef COLLECTOR + NextInSeq=0; + Key=0x33624702; + SendSessionResponse(); +#endif + SetState(ESTABLISHED); + } + break; + case OP_SessionResponse: { + if (p->Size() < sizeof(SessionResponse)) + { + break; + } + init(); + OutboundQueueClear(); + SetActive(true); + SessionResponse *Response=(SessionResponse *)p->pBuffer; + SetMaxLen(ntohl(Response->MaxLength)); + Key=ntohl(Response->Key); + NextInSeq=0; + SetState(ESTABLISHED); + if (!Session) + Session=ntohl(Response->Session); + compressed=(Response->Format&FLAG_COMPRESSED); + encoded=(Response->Format&FLAG_ENCODED); + + // Kinda kludgy, but trie for now + if (compressed) { + if (remote_port==9000 || (remote_port==0 && p->src_port==9000)) + SetStreamType(WorldStream); + else + SetStreamType(ZoneStream); + } else if (encoded) + SetStreamType(ChatOrMailStream); + else + SetStreamType(LoginStream); + } + break; + case OP_SessionDisconnect: { + //NextInSeq=0; + SendDisconnect(); + //SetState(CLOSED); + } + break; + case OP_OutOfOrderAck: { + if (!p->pBuffer || (p->Size() < 4)) + { + LogWrite(PACKET__DEBUG, 9, "Packet", "Received OP_OutOfOrderAck that was of malformed size"); + break; + } + uint16 seq = ntohs(*(uint16*)(p->pBuffer)); + MOutboundQueue.lock(); + + if (uint16(SequencedBase + SequencedQueue.size()) != NextOutSeq) { + LogWrite(PACKET__DEBUG, 9, "Packet", "Pre-OOA Invalid Sequenced queue: BS %u + SQ %u != NOS %u", SequencedBase, SequencedQueue.size(), NextOutSeq); + } + + //if the packet they got out of order is between our last acked packet and the last sent packet, then its valid. + if (CompareSequence(SequencedBase, seq) != SeqPast && CompareSequence(NextOutSeq, seq) == SeqPast) { + uint16 sqsize = SequencedQueue.size(); + uint16 index = seq - SequencedBase; + LogWrite(PACKET__DEBUG, 9, "Packet", "OP_OutOfOrderAck marking packet acked in queue (queue index = %u, queue size = %u)", index, sqsize); + if (index < sqsize) { + SequencedQueue[index]->acked = true; + // flag packets for a resend + uint16 count = 0; + uint32 timeout = AverageDelta * 2 + 100; + for (auto sitr = SequencedQueue.begin(); sitr != SequencedQueue.end() && count < index; ++sitr, ++count) { + if (!(*sitr)->acked && (*sitr)->sent_time > 0 && (((*sitr)->sent_time + timeout) < Timer::GetCurrentTime2())) { + (*sitr)->sent_time = 0; + LogWrite(PACKET__DEBUG, 9, "Packet", "OP_OutOfOrderAck Flagging packet %u for retransmission", SequencedBase + count); + } + } + } + + if (RETRANSMIT_TIMEOUT_MULT) { + retransmittimer = Timer::GetCurrentTime2(); + } + } + else { + LogWrite(PACKET__DEBUG, 9, "Packet", "Received OP_OutOfOrderAck for out-of-window %u. Window (%u->%u)", seq, SequencedBase, NextOutSeq); + } + + if (uint16(SequencedBase + SequencedQueue.size()) != NextOutSeq) { + LogWrite(PACKET__DEBUG, 9, "Packet", "Post-OOA Invalid Sequenced queue: BS %u + SQ %u != NOS %u", SequencedBase, SequencedQueue.size(), NextOutSeq); + } + + MOutboundQueue.unlock(); + } + break; + case OP_ServerKeyRequest:{ + if (p->Size() < sizeof(ClientSessionStats)) + { + //_log(NET__ERROR, _L "Received OP_SessionStatRequest that was of malformed size" __L); + break; + } + + ClientSessionStats* Stats = (ClientSessionStats*)p->pBuffer; + int16 request_id = Stats->RequestID; + AdjustRates(ntohl(Stats->average_delta)); + ServerSessionStats* stats=(ServerSessionStats*)p->pBuffer; + memset(stats, 0, sizeof(ServerSessionStats)); + stats->RequestID = request_id; + stats->current_time = ntohl(Timer::GetCurrentTime2()); + stats->sent_packets = ntohl(sent_packets); + stats->sent_packets2 = ntohl(sent_packets); + stats->received_packets = ntohl(received_packets); + stats->received_packets2 = ntohl(received_packets); + NonSequencedPush(new EQProtocolPacket(OP_SessionStatResponse,p->pBuffer,p->size)); + if(!crypto->isEncrypted()) + SendKeyRequest(); + else + SendSessionResponse(); + } + break; + case OP_SessionStatResponse: { + LogWrite(PACKET__INFO, 0, "Packet", "OP_SessionStatResponse"); + } + break; + case OP_OutOfSession: { + LogWrite(PACKET__INFO, 0, "Packet", "OP_OutOfSession"); + SendDisconnect(); + SetState(CLOSED); + } + break; + default: + //EQApplicationPacket *ap = p->MakeApplicationPacket(app_opcode_size); + //InboundQueuePush(ap); + + cout << "Orig Packet: " << p->opcode << endl; + DumpPacket(p->pBuffer, p->size); + if(p && p->size >= 69){ + processRSAKey(p); + } + MCombineQueueLock.lock(); + EQProtocolPacket* p2 = ProcessEncryptedData(p->pBuffer, p->size, OP_Packet); + MCombineQueueLock.unlock(); + cout << "Decrypted Packet: " << p2->opcode << endl; + DumpPacket(p2->pBuffer, p2->size); + + safe_delete(p2); + /* if(p2) + { + EQApplicationPacket* ap = p2->MakeApplicationPacket(2); + if (ap->version == 0) + ap->version = client_version; + InboundQueuePush(ap); + safe_delete(p2); + }*/ + + //EQProtocolPacket* puse = p2; + /* if (!rogue_buffer) { + roguebuf_size=puse->size; + rogue_buffer=new unsigned char[roguebuf_size]; + memcpy(rogue_buffer,puse->pBuffer,puse->size); + roguebuf_offset=puse->size; + cout << "RogueBuf is " << roguebuf_offset << "/" << roguebuf_size << " (" << (p->size-6) << ") NextInSeq=" << NextInSeq << endl; + } + else { + int32 new_size = roguebuf_size + puse->size; + uchar* tmp_buffer = new unsigned char[new_size]; + uchar* ptr = tmp_buffer; + + memcpy(ptr,rogue_buffer,roguebuf_size); + ptr += roguebuf_size; + memcpy(ptr,puse->pBuffer,puse->size); + roguebuf_offset=puse->size; + + safe_delete_array(rogue_buffer); + + rogue_buffer = tmp_buffer; + roguebuf_size = new_size; + roguebuf_offset = new_size; + cout << "RogueBuf is " << roguebuf_offset << "/" << roguebuf_size << " (" << (p->size-6) << ") NextInSeq=" << NextInSeq << endl; + }*/ +#ifdef WRITE_PACKETS + WritePackets(ap->GetOpcodeName(), p->pBuffer, p->size, false); +#endif + //InboundQueuePush(ap); + LogWrite(PACKET__INFO, 0, "Packet", "Received unknown packet type, not adding to inbound queue"); + //safe_delete(p2); + //SendDisconnect(); + break; + } + } +} + +int8 EQStream::EQ2_Compress(EQ2Packet* app, int8 offset){ + +#ifdef LE_DEBUG + printf( "Before Compress in %s, line %i:\n", __FUNCTION__, __LINE__); + DumpPacket(app); +#endif + + + uchar* pDataPtr = app->pBuffer + offset; + int xpandSize = app->size * 2; + uchar* deflate_buff = new uchar[xpandSize]; + MCompressData.lock(); + stream.next_in = pDataPtr; + stream.avail_in = app->size - offset; + stream.next_out = deflate_buff; + stream.avail_out = xpandSize; + + int ret = deflate(&stream, Z_SYNC_FLUSH); + + if (ret != Z_OK) + { + printf("ZLIB COMPRESSION RETFAIL: %i, %i (Ret: %i)\n", app->size, stream.avail_out, ret); + MCompressData.unlock(); + safe_delete_array(deflate_buff); + return 0; + } + + int32 newsize = xpandSize - stream.avail_out; + safe_delete_array(app->pBuffer); + app->size = newsize + offset; + app->pBuffer = new uchar[app->size]; + app->pBuffer[(offset - 1)] = 1; + memcpy(app->pBuffer + offset, deflate_buff, newsize); + MCompressData.unlock(); + safe_delete_array(deflate_buff); + +#ifdef LE_DEBUG + printf( "After Compress in %s, line %i:\n", __FUNCTION__, __LINE__); + DumpPacket(app); +#endif + + return offset - 1; +} + +int16 EQStream::processRSAKey(EQProtocolPacket *p, uint16 subpacket_length){ + /*int16 limit = 0; + int8 offset = 13; + int8 offset2 = 0; + if(p->pBuffer[2] == 0) + limit = p->pBuffer[9]; + else{ + limit = p->pBuffer[5]; + offset2 = 5; + offset-=1; + } + crypto->setRC4Key(Crypto::RSADecrypt(p->pBuffer + offset + (limit-8), 8)); + return (limit + offset +1) - offset2;*/ + if(subpacket_length) + crypto->setRC4Key(Crypto::RSADecrypt(p->pBuffer + subpacket_length - 8, 8)); + else + crypto->setRC4Key(Crypto::RSADecrypt(p->pBuffer + p->size - 8, 8)); + + return 0; +} + +void EQStream::SendKeyRequest(){ + int32 crypto_key_size = 60; + int16 size = sizeof(KeyGen_Struct) + sizeof(KeyGen_End_Struct) + crypto_key_size; + EQ2Packet *outapp=new EQ2Packet(OP_WSLoginRequestMsg,NULL,size); + memcpy(&outapp->pBuffer[0], &crypto_key_size, sizeof(int32)); + memset(&outapp->pBuffer[4], 0xFF, crypto_key_size); + memset(&outapp->pBuffer[size-5], 1, 1); + memset(&outapp->pBuffer[size-1], 1, 1); + EQ2QueuePacket(outapp, true); +} + +void EQStream::EncryptPacket(EQ2Packet* app, int8 compress_offset, int8 offset){ + if(app->size>2 && crypto->isEncrypted()){ + app->packet_encrypted = true; + uchar* crypt_buff = app->pBuffer; + if(app->eq2_compressed) + crypto->RC4Encrypt(crypt_buff + compress_offset, app->size - compress_offset); + else + crypto->RC4Encrypt(crypt_buff + 2 + offset, app->size - 2 - offset); + } +} + +void EQStream::EQ2QueuePacket(EQ2Packet* app, bool attempted_combine){ + if(CheckActive()){ + if(!attempted_combine){ + MCombineQueueLock.lock(); + combine_queue.push_back(app); + MCombineQueueLock.unlock(); + } + else{ + MCombineQueueLock.lock(); + PreparePacket(app); + MCombineQueueLock.unlock(); +#ifdef LE_DEBUG + printf( "After B in %s, line %i:\n", __FUNCTION__, __LINE__); + DumpPacket(app); +#endif + SendPacket(app); + } + } +} + +void EQStream::UnPreparePacket(EQ2Packet* app){ + if(app->pBuffer[2] == 0 && app->pBuffer[3] == 19){ + uchar* new_buffer = new uchar[app->size-3]; + memcpy(new_buffer+2, app->pBuffer+5, app->size-3); + delete[] app->pBuffer; + app->size-=3; + app->pBuffer = new_buffer; + } +} + +#ifdef WRITE_PACKETS +char EQStream::GetChar(uchar in) +{ + if (in < ' ' || in > '~') + return '.'; + return (char)in; +} +void EQStream::WriteToFile(char* pFormat, ...) { + va_list args; + va_start(args, pFormat); + vfprintf(write_packets, pFormat, args); + va_end(args); +} + +void EQStream::WritePackets(const char* opcodeName, uchar* data, int32 size, bool outgoing) { + MWritePackets.lock(); + struct in_addr ip_addr; + ip_addr.s_addr = remote_ip; + char timebuffer[80]; + time_t rawtime; + struct tm* timeinfo; + time(&rawtime); + timeinfo = localtime(&rawtime); + strftime(timebuffer, 80, "%m/%d/%Y %H:%M:%S", timeinfo); + if (outgoing) + WriteToFile("-- %s --\n%s\nSERVER -> %s\n", opcodeName, timebuffer, inet_ntoa(ip_addr)); + else + WriteToFile("-- %s --\n%s\n%s -> SERVER\n", opcodeName, timebuffer, inet_ntoa(ip_addr)); + int i; + int nLines = size / 16; + int nExtra = size % 16; + uchar* pPtr = data; + for (i = 0; i < nLines; i++) + { + WriteToFile("%4.4X:\t%2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c\n", i * 16, pPtr[0], pPtr[1], pPtr[2], pPtr[3], pPtr[4], pPtr[5], pPtr[6], pPtr[7], pPtr[8], pPtr[9], pPtr[10], pPtr[11], pPtr[12], pPtr[13], pPtr[14], pPtr[15], GetChar(pPtr[0]), GetChar(pPtr[1]), GetChar(pPtr[2]), GetChar(pPtr[3]), GetChar(pPtr[4]), GetChar(pPtr[5]), GetChar(pPtr[6]), GetChar(pPtr[7]), GetChar(pPtr[8]), GetChar(pPtr[9]), GetChar(pPtr[10]), GetChar(pPtr[11]), GetChar(pPtr[12]), GetChar(pPtr[13]), GetChar(pPtr[14]), GetChar(pPtr[15])); + pPtr += 16; + } + if (nExtra) + { + WriteToFile("%4.4X\t", nLines * 16); + for (i = 0; i < nExtra; i++) + { + WriteToFile("%2.2X ", pPtr[i]); + } + for (i; i < 16; i++) + WriteToFile(" "); + for (i = 0; i < nExtra; i++) + { + WriteToFile("%c", GetChar(pPtr[i])); + } + WriteToFile("\n"); + } + WriteToFile("\n\n"); + fflush(write_packets); + MWritePackets.unlock(); +} + +void EQStream::WritePackets(EQ2Packet* app, bool outgoing) { + if (app->version == 0) + app->version = client_version; + WritePackets(app->GetOpcodeName(), app->pBuffer, app->size, outgoing); +} +#endif + +void EQStream::PreparePacket(EQ2Packet* app, int8 offset){ + app->setVersion(client_version); + compressed_offset = 0; + +#ifdef LE_DEBUG + printf( "Before A in %s, line %i:\n", __FUNCTION__, __LINE__); + DumpPacket(app); +#endif + if(!app->packet_prepared){ + if(app->PreparePacket(MaxLen) == 255) //invalid version + return; + } + +#ifdef LE_DEBUG + printf( "After Prepare in %s, line %i:\n", __FUNCTION__, __LINE__); + DumpPacket(app); +#endif +#ifdef WRITE_PACKETS + if (!app->eq2_compressed && !app->packet_encrypted) + WritePackets(app, true); +#endif + + if(!app->eq2_compressed && app->size>128){ + compressed_offset = EQ2_Compress(app); + if (compressed_offset) + app->eq2_compressed = true; + } + if(!app->packet_encrypted){ + EncryptPacket(app, compressed_offset, offset); + if(app->size > 2 && app->pBuffer[2] == 0){ + uchar* new_buffer = new uchar[app->size+1]; + new_buffer[2] = 0; + memcpy(new_buffer+3, app->pBuffer+2, app->size-2); + delete[] app->pBuffer; + app->pBuffer = new_buffer; + app->size++; + } + } + +#ifdef LE_DEBUG + printf( "After A in %s, line %i:\n", __FUNCTION__, __LINE__); + DumpPacket(app); +#endif + +} + +void EQStream::SendPacket(EQProtocolPacket *p) +{ + uint32 chunksize,used; + uint32 length; + + // Convert the EQApplicationPacket to 1 or more EQProtocolPackets + if (p->size>( MaxLen-8)) { // proto-op(2), seq(2), app-op(2) ... data ... crc(2) + uchar* tmpbuff=p->pBuffer; + length=p->size - 2; + + EQProtocolPacket *out=new EQProtocolPacket(OP_Fragment,NULL,MaxLen-4); + *(uint32 *)(out->pBuffer+2)=htonl(length); + used=MaxLen-10; + memcpy(out->pBuffer+6,tmpbuff+2,used); + +#ifdef LE_DEBUG + printf("(%s, %i) New Fragment:\n ", __FUNCTION__, __LINE__); + DumpPacket(out); +#endif + + SequencedPush(out); + + while (usedpBuffer+2,tmpbuff,1); + memcpy(out->pBuffer+2,tmpbuff+used+2,chunksize); +#ifdef LE_DEBUG + printf("Chunk: \n"); + DumpPacket(out); +#endif + SequencedPush(out); + used+=chunksize; + + } + +#ifdef LE_DEBUG + printf( "ChunkDelete: \n"); + DumpPacket(out); + //cerr << "1: Deleting 0x" << hex << (uint32)(p) << dec << endl; +#endif + + delete p; + } else { + SequencedPush(p); + } +} +void EQStream::SendPacket(EQApplicationPacket *p) +{ +uint32 chunksize,used; +uint32 length; + + // Convert the EQApplicationPacket to 1 or more EQProtocolPackets + if (p->size>(MaxLen-8)) { // proto-op(2), seq(2), app-op(2) ... data ... crc(2) + //cout << "Making oversized packet for: " << endl; + //cout << p->size << endl; + //p->DumpRawHeader(); + //dump_message(p->pBuffer,p->size,timestamp()); + //cout << p->size << endl; + unsigned char *tmpbuff=new unsigned char[p->size+2]; + //cout << hex << (int)tmpbuff << dec << endl; + length=p->serialize(tmpbuff); + + EQProtocolPacket *out=new EQProtocolPacket(OP_Fragment,NULL,MaxLen-4); + *(uint32 *)(out->pBuffer+2)=htonl(p->Size()); + memcpy(out->pBuffer+6,tmpbuff,MaxLen-10); + used=MaxLen-10; + SequencedPush(out); + //cout << "Chunk #" << ++i << " size=" << used << ", length-used=" << (length-used) << endl; + while (usedpBuffer+2,tmpbuff+used,chunksize); + out->size=chunksize+2; + SequencedPush(out); + used+=chunksize; + //cout << "Chunk #"<< ++i << " size=" << chunksize << ", length-used=" << (length-used) << endl; + } + //cerr << "1: Deleting 0x" << hex << (uint32)(p) << dec << endl; + delete p; + delete[] tmpbuff; + } else { + EQProtocolPacket *out=new EQProtocolPacket(OP_Packet,NULL,p->Size()+2); + p->serialize(out->pBuffer+2); + SequencedPush(out); + //cerr << "2: Deleting 0x" << hex << (uint32)(p) << dec << endl; + delete p; + } +} + +void EQStream::SequencedPush(EQProtocolPacket *p) +{ + p->setVersion(client_version); + MOutboundQueue.lock(); + *(uint16 *)(p->pBuffer)=htons(NextOutSeq); + SequencedQueue.push_back(p); + p->sequence = NextOutSeq; + NextOutSeq++; + MOutboundQueue.unlock(); +} + +void EQStream::NonSequencedPush(EQProtocolPacket *p) +{ + p->setVersion(client_version); + MOutboundQueue.lock(); + NonSequencedQueue.push(p); + MOutboundQueue.unlock(); +} + +void EQStream::SendAck(uint16 seq) +{ + uint16 Seq=htons(seq); + SetLastAckSent(seq); + NonSequencedPush(new EQProtocolPacket(OP_Ack,(unsigned char *)&Seq,sizeof(uint16))); +} + +void EQStream::SendOutOfOrderAck(uint16 seq) +{ + uint16 Seq=htons(seq); + NonSequencedPush(new EQProtocolPacket(OP_OutOfOrderAck,(unsigned char *)&Seq,sizeof(uint16))); +} + +bool EQStream::CheckCombineQueue(){ + bool ret = true; //processed all packets + MCombineQueueLock.lock(); + if(combine_queue.size() > 0){ + EQ2Packet* first = combine_queue.front(); + combine_queue.pop_front(); + if(combine_queue.size() == 0){ //nothing to combine this with + EQ2QueuePacket(first, true); + } + else{ + PreparePacket(first); + EQ2Packet* second = 0; + bool combine_worked = false; + int16 count = 0; + while(combine_queue.size()){ + count++; + second = combine_queue.front(); + combine_queue.pop_front(); + PreparePacket(second); + /*if(first->GetRawOpcode() != OP_AppCombined && first->pBuffer[2] == 0){ + EQ2Packet* tmp = second; + second = first; + first = tmp; + }*/ + if(!first->AppCombine(second)){ + first->SetProtocolOpcode(OP_Packet); + if(combine_worked){ + SequencedPush(first); + } + else{ + EQ2QueuePacket(first, true); + } + first = second; + combine_worked = false; + } + else{ + combine_worked = true; + //DumpPacket(first); + } + if(count >= 60 || first->size > 4000){ //other clients need packets too + ret = false; + break; + } + } + if(first){ + first->SetProtocolOpcode(OP_Packet); + if(combine_worked){ + SequencedPush(first); + } + else{ + EQ2QueuePacket(first, true); + } + } + } + } + MCombineQueueLock.unlock(); + return ret; +} + +void EQStream::CheckResend(int eq_fd){ + int32 curr = Timer::GetCurrentTime2(); + EQProtocolPacket* packet = 0; + deque::iterator itr; + MResendQue.lock(); + for(itr=resend_que.begin();itr!=resend_que.end();itr++){ + packet = *itr; + if(packet->attempt_count >= 5){//tried to resend this packet 5 times, client must already have it but didnt ack it + safe_delete(packet); + itr = resend_que.erase(itr); + if(itr == resend_que.end()) + break; + } + else{ + if((curr - packet->sent_time) < 1000) + continue; + packet->sent_time -=1000; + packet->attempt_count++; + WritePacket(eq_fd, packet); + } + } + MResendQue.unlock(); +} + + + +//returns SeqFuture if `seq` is later than `expected_seq` +EQStream::SeqOrder EQStream::CompareSequence(uint16 expected_seq, uint16 seq) +{ + if (expected_seq == seq) { + // Curent + return SeqInOrder; + } + else if ((seq > expected_seq && (uint32)seq < ((uint32)expected_seq + EQStream::MaxWindowSize)) || seq < (expected_seq - EQStream::MaxWindowSize)) { + // Future + return SeqFuture; + } + else { + // Past + return SeqPast; + } +} + +void EQStream::AckPackets(uint16 seq) +{ + std::deque::iterator itr, tmp; + + MOutboundQueue.lock(); + + SeqOrder ord = CompareSequence(SequencedBase, seq); + if (ord == SeqInOrder) { + //they are not acking anything new... + LogWrite(PACKET__DEBUG, 9, "Packet", "Received an ack with no window advancement (seq %u)", seq); + } + else if (ord == SeqPast) { + //they are nacking blocks going back before our buffer, wtf? + LogWrite(PACKET__DEBUG, 9, "Packet", "Received an ack with backward window advancement (they gave %u, our window starts at %u). This is bad" , seq, SequencedBase); + } + else { + LogWrite(PACKET__DEBUG, 9, "Packet", "Received an ack up through sequence %u. Our base is %u", seq, SequencedBase); + + + //this is a good ack, we get to ack some blocks. + seq++; //we stop at the block right after their ack, counting on the wrap of both numbers. + while (SequencedBase != seq) { + if (SequencedQueue.empty()) { + LogWrite(PACKET__DEBUG, 9, "Packet", "OUT OF PACKETS acked packet with sequence %u. Next send is %u before this", (unsigned long)SequencedBase, SequencedQueue.size()); + SequencedBase = NextOutSeq; + break; + } + LogWrite(PACKET__DEBUG, 9, "Packet", "Removing acked packet with sequence %u", (unsigned long)SequencedBase); + //clean out the acked packet + delete SequencedQueue.front(); + SequencedQueue.pop_front(); + //advance the base sequence number to the seq of the block after the one we just got rid of. + SequencedBase++; + } + if (uint16(SequencedBase + SequencedQueue.size()) != NextOutSeq) { + LogWrite(PACKET__DEBUG, 9, "Packet", "Post-Ack on %u Invalid Sequenced queue: BS %u + SQ %u != NOS %u", seq, SequencedBase, SequencedQueue.size(), NextOutSeq); + } + } + + MOutboundQueue.unlock(); +} + +void EQStream::Write(int eq_fd) +{ + queue ReadyToSend; + long maxack; + + // Check our rate to make sure we can send more + MRate.lock(); + sint32 threshold=RateThreshold; + MRate.unlock(); + if (BytesWritten > threshold) { + //cout << "Over threshold: " << BytesWritten << " > " << threshold << endl; + return; + } + + MCombinedAppPacket.lock(); + EQApplicationPacket *CombPack=CombinedAppPacket; + CombinedAppPacket=NULL; + MCombinedAppPacket.unlock(); + + if (CombPack) { + SendPacket(CombPack); + } + + // If we got more packets to we need to ack, send an ack on the highest one + MAcks.lock(); + maxack=MaxAckReceived; + // Added from peaks findings + if (NextAckToSend>LastAckSent || LastAckSent == 0x0000ffff) + SendAck(NextAckToSend); + MAcks.unlock(); + + // Lock the outbound queues while we process + MOutboundQueue.lock(); + + // Adjust where we start sending in case we get a late ack + //if (maxack>LastSeqSent) + // LastSeqSent=maxack; + + // Place to hold the base packet t combine into + EQProtocolPacket *p=NULL; + std::deque::iterator sitr; + + // Find the next sequenced packet to send from the "queue" + sitr = SequencedQueue.begin(); + + uint16 count = 0; + // get to start of packets + while (sitr != SequencedQueue.end() && (*sitr)->sent_time > 0) { + ++sitr; + ++count; + } + + bool SeqEmpty = false, NonSeqEmpty = false; + // Loop until both are empty or MaxSends is reached + while (!SeqEmpty || !NonSeqEmpty) { + + // See if there are more non-sequenced packets left + if (!NonSequencedQueue.empty()) { + if (!p) { + // If we don't have a packet to try to combine into, use this one as the base + // And remove it form the queue + p = NonSequencedQueue.front(); + LogWrite(PACKET__DEBUG, 9, "Packet", "Starting combined packet with non-seq packet of len %u",p->size); + NonSequencedQueue.pop(); + } + else if (!p->combine(NonSequencedQueue.front())) { + // Trying to combine this packet with the base didn't work (too big maybe) + // So just send the base packet (we'll try this packet again later) + LogWrite(PACKET__DEBUG, 9, "Packet", "Combined packet full at len %u, next non-seq packet is len %u", p->size, (NonSequencedQueue.front())->size); + ReadyToSend.push(p); + BytesWritten += p->size; + p = nullptr; + + if (BytesWritten > threshold) { + // Sent enough this round, lets stop to be fair + LogWrite(PACKET__DEBUG, 9, "Packet", "Exceeded write threshold in nonseq (%u > %u)", BytesWritten, threshold); + break; + } + } + else { + // Combine worked, so just remove this packet and it's spot in the queue + LogWrite(PACKET__DEBUG, 9, "Packet", "Combined non-seq packet of len %u, yeilding %u combined", (NonSequencedQueue.front())->size, p->size); + delete NonSequencedQueue.front(); + NonSequencedQueue.pop(); + } + } + else { + // No more non-sequenced packets + NonSeqEmpty = true; + } + + if (sitr != SequencedQueue.end()) { + uint16 seq_send = SequencedBase + count; //just for logging... + + if (SequencedQueue.empty()) { + LogWrite(PACKET__DEBUG, 9, "Packet", "Tried to write a packet with an empty queue (%u is past next out %u)", seq_send, NextOutSeq); + SeqEmpty = true; + continue; + } + + if ((*sitr)->acked || (*sitr)->sent_time != 0) { + ++sitr; + ++count; + if (p) { + LogWrite(PACKET__DEBUG, 9, "Packet", "Final combined packet not full, len %u", p->size); + ReadyToSend.push(p); + BytesWritten += p->size; + p = nullptr; + } + LogWrite(PACKET__DEBUG, 9, "Packet", "Not retransmitting seq packet %u because already marked as acked", seq_send); + } + else if (!p) { + // If we don't have a packet to try to combine into, use this one as the base + // Copy it first as it will still live until it is acked + p = (*sitr)->Copy(); + LogWrite(PACKET__DEBUG, 9, "Packet", "Starting combined packet with seq packet %u of len %u", seq_send, p->size); + (*sitr)->sent_time = Timer::GetCurrentTime2(); + ++sitr; + ++count; + } + else if (!p->combine(*sitr)) { + // Trying to combine this packet with the base didn't work (too big maybe) + // So just send the base packet (we'll try this packet again later) + LogWrite(PACKET__DEBUG, 9, "Packet", "Combined packet full at len %u, next seq packet %u is len %u", p->size, seq_send + 1, (*sitr)->size); + ReadyToSend.push(p); + BytesWritten += p->size; + p = nullptr; + if ((*sitr)->opcode != OP_Fragment && BytesWritten > threshold) { + // Sent enough this round, lets stop to be fair + LogWrite(PACKET__DEBUG, 9, "Packet", "Exceeded write threshold in seq (%u > %u)", BytesWritten, threshold); + break; + } + } + else { + // Combine worked + LogWrite(PACKET__DEBUG, 9, "Packet", "Combined seq packet %u of len %u, yeilding %u combined", seq_send, (*sitr)->size, p->size); + (*sitr)->sent_time = Timer::GetCurrentTime2(); + ++sitr; + ++count; + } + + if (uint16(SequencedBase + SequencedQueue.size()) != NextOutSeq) { + LogWrite(PACKET__DEBUG, 9, "Packet", "Post send Invalid Sequenced queue: BS %u + SQ %u != NOS %u", SequencedBase, SequencedQueue.size(), NextOutSeq); + } + } + else { + // No more sequenced packets + SeqEmpty = true; + } + } + MOutboundQueue.unlock(); // Unlock the queue + + // We have a packet still, must have run out of both seq and non-seq, so send it + if (p) { + LogWrite(PACKET__DEBUG, 9, "Packet", "Final combined packet not full, len %u", p->size); + ReadyToSend.push(p); + BytesWritten += p->size; + } + + // Send all the packets we "made" + while (!ReadyToSend.empty()) { + p = ReadyToSend.front(); + WritePacket(eq_fd, p); + delete p; + ReadyToSend.pop(); + } + + //see if we need to send our disconnect and finish our close + if (SeqEmpty && NonSeqEmpty) { + //no more data to send + if (GetState() == CLOSING) { + MOutboundQueue.lock(); + if (SequencedQueue.size() > 0 ) { + // retransmission attempts + } + else + { + LogWrite(PACKET__DEBUG, 9, "Packet", "All outgoing data flushed, disconnecting client."); + //we are waiting for the queues to empty, now we can do our disconnect. + //this packet will not actually go out until the next call to Write(). + SendDisconnect(); + //SetState(CLOSED); + } + MOutboundQueue.unlock(); + } + } +} + +void EQStream::WritePacket(int eq_fd, EQProtocolPacket *p) +{ +uint32 length = 0; +sockaddr_in address; +unsigned char tmpbuffer[2048]; + address.sin_family = AF_INET; + address.sin_addr.s_addr=remote_ip; + address.sin_port=remote_port; +#ifdef NOWAY + uint32 ip=address.sin_addr.s_addr; + cout << "Sending to: " + << (int)*(unsigned char *)&ip + << "." << (int)*((unsigned char *)&ip+1) + << "." << (int)*((unsigned char *)&ip+2) + << "." << (int)*((unsigned char *)&ip+3) + << "," << (int)ntohs(address.sin_port) << "(" << p->size << ")" << endl; + + p->DumpRaw(); + cout << "-------------" << endl; +#endif + length=p->serialize(buffer); + if (p->opcode!=OP_SessionRequest && p->opcode!=OP_SessionResponse) { + if (compressed) { + BytesWritten -= p->size; + uint32 newlen=EQProtocolPacket::Compress(buffer,length,tmpbuffer,2048); + memcpy(buffer,tmpbuffer,newlen); + length=newlen; + BytesWritten += newlen; + } + if (encoded) { + EQProtocolPacket::ChatEncode(buffer,length,Key); + } + *(uint16 *)(buffer+length)=htons(CRC16(buffer,length,Key)); + length+=2; + } + sent_packets++; + //dump_message_column(buffer,length,"Writer: "); + //cout << "Raw Data:\n"; + //DumpPacket(buffer, length); + sendto(eq_fd,(char *)buffer,length,0,(sockaddr *)&address,sizeof(address)); +} + +EQProtocolPacket *EQStream::Read(int eq_fd, sockaddr_in *from) +{ +int socklen; +int length=0; +unsigned char buffer[2048]; +EQProtocolPacket *p=NULL; +char temp[15]; + + socklen=sizeof(sockaddr); +#ifdef WIN32 + length=recvfrom(eq_fd, (char *)buffer, 2048, 0, (struct sockaddr*)from, (int *)&socklen); +#else + length=recvfrom(eq_fd, buffer, 2048, 0, (struct sockaddr*)from, (socklen_t *)&socklen); +#endif + if (length>=2) { + DumpPacket(buffer, length); + p=new EQProtocolPacket(buffer[1],&buffer[2],length-2); + //printf("Read packet: opcode %i length %u, expected-length: %u\n",buffer[1], length, p->size); + uint32 ip=from->sin_addr.s_addr; + sprintf(temp,"%d.%d.%d.%d:%d", + *(unsigned char *)&ip, + *((unsigned char *)&ip+1), + *((unsigned char *)&ip+2), + *((unsigned char *)&ip+3), + ntohs(from->sin_port)); + //cout << timestamp() << "Data from: " << temp << " OpCode 0x" << hex << setw(2) << setfill('0') << (int)p->opcode << dec << endl; + //dump_message(p->pBuffer,p->size,timestamp()); + + } + return p; +} + +void EQStream::SendSessionResponse() +{ +EQProtocolPacket *out=new EQProtocolPacket(OP_SessionResponse,NULL,sizeof(SessionResponse)); + SessionResponse *Response=(SessionResponse *)out->pBuffer; + Response->Session=htonl(Session); + Response->MaxLength=htonl(MaxLen); + Response->UnknownA=2; + Response->Format=0; + if (compressed) + Response->Format|=FLAG_COMPRESSED; + if (encoded) + Response->Format|=FLAG_ENCODED; + Response->Key=htonl(Key); + + out->size=sizeof(SessionResponse); + + NonSequencedPush(out); +} + +void EQStream::SendSessionRequest() +{ + EQProtocolPacket *out=new EQProtocolPacket(OP_SessionRequest,NULL,sizeof(SessionRequest)); + SessionRequest *Request=(SessionRequest *)out->pBuffer; + memset(Request,0,sizeof(SessionRequest)); + Request->Session=htonl(time(NULL)); + Request->MaxLength=htonl(512); + + NonSequencedPush(out); +} + +void EQStream::SendDisconnect(bool setstate) +{ + try{ + if(GetState() != ESTABLISHED && GetState() != WAIT_CLOSE) + return; + + EQProtocolPacket *out=new EQProtocolPacket(OP_SessionDisconnect,NULL,sizeof(uint32)+sizeof(int16)); + *(uint32 *)out->pBuffer=htonl(Session); + out->pBuffer[4] = 0; + out->pBuffer[5] = 6; + NonSequencedPush(out); + if(setstate) + SetState(CLOSING); + } + catch(...){} +} + +void EQStream::InboundQueuePush(EQApplicationPacket *p) +{ + MInboundQueue.lock(); + InboundQueue.push_back(p); + MInboundQueue.unlock(); +} + +EQApplicationPacket *EQStream::PopPacket() +{ +EQApplicationPacket *p=NULL; + + MInboundQueue.lock(); + if (InboundQueue.size()) { + p=InboundQueue.front(); + InboundQueue.pop_front(); + } + MInboundQueue.unlock(); + if(p) + p->setVersion(client_version); + return p; +} + +void EQStream::InboundQueueClear() +{ + MInboundQueue.lock(); + while(InboundQueue.size()){ + delete InboundQueue.front(); + InboundQueue.pop_front(); + } + MInboundQueue.unlock(); +} +void EQStream::EncryptPacket(uchar* data, int16 size){ + if(size>6){ + + } +} +bool EQStream::HasOutgoingData() +{ +bool flag; + + //once closed, we have nothing more to say + if(CheckClosed()) + return(false); + + MOutboundQueue.lock(); + flag=(!NonSequencedQueue.empty()); + if (!flag) { + flag = (!SequencedQueue.empty()); + } + MOutboundQueue.unlock(); + + if (!flag) { + MAcks.lock(); + flag= (NextAckToSend>LastAckSent); + MAcks.unlock(); + } + + if (!flag) { + MCombinedAppPacket.lock(); + flag=(CombinedAppPacket!=NULL); + MCombinedAppPacket.unlock(); + } + + return flag; +} + +void EQStream::OutboundQueueClear() +{ + MOutboundQueue.lock(); + while(NonSequencedQueue.size()) { + delete NonSequencedQueue.front(); + NonSequencedQueue.pop(); + } + while(SequencedQueue.size()) { + delete SequencedQueue.front(); + SequencedQueue.pop_front(); + } + MOutboundQueue.unlock(); +} + +void EQStream::Process(const unsigned char *buffer, const uint32 length) +{ + received_packets++; +static unsigned char newbuffer[2048]; +uint32 newlength=0; + +#ifdef LE_DEBUG +printf("ProcessBuffer:\n"); +DumpPacket(buffer, length); +#endif + + if (EQProtocolPacket::ValidateCRC(buffer,length,Key)) { + if (compressed) { + newlength=EQProtocolPacket::Decompress(buffer,length,newbuffer,2048); +#ifdef LE_DEBUG + printf("ProcessBufferDecompress:\n"); + DumpPacket(buffer, newlength); +#endif + } else { + memcpy(newbuffer,buffer,length); + newlength=length; + if (encoded) + EQProtocolPacket::ChatDecode(newbuffer,newlength-2,Key); + } + +#ifdef LE_DEBUG + printf("ResultProcessBuffer:\n"); + DumpPacket(buffer, newlength); +#endif + uint16 opcode=ntohs(*(const uint16 *)newbuffer); + //printf("Read packet: opcode %i newlength %u, newbuffer2len: %u, newbuffer3len: %u\n",opcode, newlength, newbuffer[2], newbuffer[3]); + if(opcode > 0 && opcode <= OP_OutOfSession) + { + if (buffer[1]!=0x01 && buffer[1]!=0x02 && buffer[1]!=0x1d) + newlength-=2; + + EQProtocolPacket p(newbuffer,newlength); + ProcessPacket(&p); + } + else + { + cout << "2Orig Packet: " << opcode << endl; + DumpPacket(newbuffer, newlength); + ProcessEmbeddedPacket(newbuffer, newlength, OP_Fragment); + } + ProcessQueue(); + } else { + cout << "Incoming packet failed checksum:" <(buffer),length,"CRC failed: "); + } +} + +long EQStream::GetMaxAckReceived() +{ + MAcks.lock(); + long l=MaxAckReceived; + MAcks.unlock(); + + return l; +} + +long EQStream::GetNextAckToSend() +{ + MAcks.lock(); + long l=NextAckToSend; + MAcks.unlock(); + + return l; +} + +long EQStream::GetLastAckSent() +{ + MAcks.lock(); + long l=LastAckSent; + MAcks.unlock(); + + return l; +} + +void EQStream::SetMaxAckReceived(uint32 seq) +{ + deque::iterator itr; + + MAcks.lock(); + MaxAckReceived=seq; + MAcks.unlock(); + MOutboundQueue.lock(); + if (long(seq) > LastSeqSent) + LastSeqSent=seq; + MResendQue.lock(); + EQProtocolPacket* packet = 0; + for(itr=resend_que.begin();itr!=resend_que.end();itr++){ + packet = *itr; + if(packet && packet->sequence <= seq){ + safe_delete(packet); + itr = resend_que.erase(itr); + if(itr == resend_que.end()) + break; + } + } + MResendQue.unlock(); + MOutboundQueue.unlock(); +} + +void EQStream::SetNextAckToSend(uint32 seq) +{ + MAcks.lock(); + NextAckToSend=seq; + MAcks.unlock(); +} + +void EQStream::SetLastAckSent(uint32 seq) +{ + MAcks.lock(); + LastAckSent=seq; + MAcks.unlock(); +} + +void EQStream::SetLastSeqSent(uint32 seq) +{ + MOutboundQueue.lock(); + LastSeqSent=seq; + MOutboundQueue.unlock(); +} + +void EQStream::SetStreamType(EQStreamType type) +{ + StreamType=type; + switch (StreamType) { + case LoginStream: + app_opcode_size=1; + compressed=false; + encoded=false; + break; + case EQ2Stream: + app_opcode_size=2; + compressed=false; + encoded=false; + break; + case ChatOrMailStream: + case ChatStream: + case MailStream: + app_opcode_size=1; + compressed=false; + encoded=true; + break; + case ZoneStream: + case WorldStream: + default: + app_opcode_size=2; + compressed=true; + encoded=false; + break; + } +} + +void EQStream::ProcessQueue() +{ + if (OutOfOrderpackets.empty()) { + return; + } + + EQProtocolPacket* qp = NULL; + while ((qp = RemoveQueue(NextInSeq)) != NULL) { + //_log(NET__DEBUG, _L "Processing Queued Packet: Seq=%d" __L, NextInSeq); + ProcessPacket(qp); + delete qp; + //_log(NET__APP_TRACE, _L "OP_Packet Queue size=%d" __L, PacketQueue.size()); + } +} + +EQProtocolPacket* EQStream::RemoveQueue(uint16 seq) +{ + map::iterator itr; + EQProtocolPacket* qp = NULL; + if ((itr = OutOfOrderpackets.find(seq)) != OutOfOrderpackets.end()) { + qp = itr->second; + OutOfOrderpackets.erase(itr); + //_log(NET__APP_TRACE, _L "OP_Packet Queue size=%d" __L, PacketQueue.size()); + } + return qp; +} + +void EQStream::Decay() +{ + MRate.lock(); + uint32 rate=DecayRate; + MRate.unlock(); + if (BytesWritten>0) { + BytesWritten-=rate; + if (BytesWritten<0) + BytesWritten=0; + } + + int count = 0; + MOutboundQueue.lock(); + for (auto sitr = SequencedQueue.begin(); sitr != SequencedQueue.end(); ++sitr, count++) { + if (!(*sitr)->acked && (*sitr)->sent_time > 0 && ((*sitr)->sent_time + retransmittimeout) < Timer::GetCurrentTime2()) { + (*sitr)->sent_time = 0; + LogWrite(PACKET__DEBUG, 9, "Packet", "Timeout exceeded for seq %u. Flagging packet for retransmission", SequencedBase + count); + } + } + MOutboundQueue.unlock(); +} + +void EQStream::AdjustRates(uint32 average_delta) +{ + if (average_delta && (average_delta <= AVERAGE_DELTA_MAX)) { + MRate.lock(); + AverageDelta = average_delta; + RateThreshold = RATEBASE / average_delta; + DecayRate = DECAYBASE / average_delta; + if (BytesWritten > RateThreshold) + BytesWritten = RateThreshold + DecayRate; + MRate.unlock(); + } + else { + AverageDelta = AVERAGE_DELTA_MAX; + } +} diff --git a/source/common/EQStream.h b/source/common/EQStream.h new file mode 100644 index 0000000..3ec6028 --- /dev/null +++ b/source/common/EQStream.h @@ -0,0 +1,373 @@ +/* + 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 . +*/ +#ifndef _EQPROTOCOL_H +#define _EQPROTOCOL_H + +#include +#include +#include +#include + +#include +#include +#ifndef WIN32 +#include +#endif +#include "EQPacket.h" +#include "Mutex.h" +#include "opcodemgr.h" +#include "misc.h" +#include "Condition.h" +#include "Crypto.h" +#include "zlib.h" +#include "timer.h" +#ifdef WRITE_PACKETS +#include +#endif + +using namespace std; + +typedef enum { + ESTABLISHED, + WAIT_CLOSE, + CLOSING, + DISCONNECTING, + CLOSED +} EQStreamState; + +#define FLAG_COMPRESSED 0x01 +#define FLAG_ENCODED 0x04 + +#define RATEBASE 1048576 // 1 MB +#define DECAYBASE 78642 // RATEBASE/10 + +#ifndef RETRANSMIT_TIMEOUT_MULT +#define RETRANSMIT_TIMEOUT_MULT 3.0 +#endif + +#ifndef RETRANSMIT_TIMEOUT_MAX +#define RETRANSMIT_TIMEOUT_MAX 5000 +#endif + +#ifndef AVERAGE_DELTA_MAX +#define AVERAGE_DELTA_MAX 2500 +#endif + +#pragma pack(1) +struct SessionRequest { + uint32 UnknownA; + uint32 Session; + uint32 MaxLength; +}; + +struct SessionResponse { + uint32 Session; + uint32 Key; + uint8 UnknownA; + uint8 Format; + uint8 UnknownB; + uint32 MaxLength; + uint32 UnknownD; +}; + +//Deltas are in ms, representing round trip times +struct ClientSessionStats { +/*000*/ uint16 RequestID; +/*002*/ uint32 last_local_delta; +/*006*/ uint32 average_delta; +/*010*/ uint32 low_delta; +/*014*/ uint32 high_delta; +/*018*/ uint32 last_remote_delta; +/*022*/ uint64 packets_sent; +/*030*/ uint64 packets_recieved; +/*038*/ +}; + +struct ServerSessionStats { + uint16 RequestID; + uint32 current_time; + uint32 unknown1; + uint32 received_packets; + uint32 unknown2; + uint32 sent_packets; + uint32 unknown3; + uint32 sent_packets2; + uint32 unknown4; + uint32 received_packets2; +}; + +#pragma pack() + +class OpcodeManager; +extern OpcodeManager *EQNetworkOpcodeManager; + +class EQStreamFactory; + +typedef enum { + UnknownStream=0, + LoginStream, + WorldStream, + ZoneStream, + ChatOrMailStream, + ChatStream, + MailStream, + EQ2Stream, +} EQStreamType; + +class EQStream { + protected: + typedef enum { + SeqPast, + SeqInOrder, + SeqFuture + } SeqOrder; + + uint32 received_packets; + uint32 sent_packets; + uint32 remote_ip; + uint16 remote_port; + uint8 buffer[8192]; + unsigned char *oversize_buffer; + uint32 oversize_offset,oversize_length; + unsigned char *rogue_buffer; + uint32 roguebuf_offset,roguebuf_size; + uint8 app_opcode_size; + EQStreamType StreamType; + bool compressed,encoded; + + uint32 retransmittimer; + uint32 retransmittimeout; + //uint32 buffer_len; + + uint16 sessionAttempts; + uint16 reconnectAttempt; + bool streamactive; + + uint32 Session, Key; + uint16 NextInSeq; + uint16 NextOutSeq; + uint16 SequencedBase; //the sequence number of SequencedQueue[0] + uint32 MaxLen; + uint16 MaxSends; + int8 timeout_delays; + + uint8 active_users; //how many things are actively using this + Mutex MInUse; + +#ifdef WRITE_PACKETS + FILE* write_packets = NULL; + char GetChar(uchar in); + void WriteToFile(char* pFormat, ...); + void WritePackets(const char* opcodeName, uchar* data, int32 size, bool outgoing); + void WritePackets(EQ2Packet* app, bool outgoing); + Mutex MWritePackets; +#endif + + EQStreamState State; + Mutex MState; + + uint32 LastPacket; + Mutex MVarlock; + + EQApplicationPacket* CombinedAppPacket; + Mutex MCombinedAppPacket; + + long LastSeqSent; + Mutex MLastSeqSent; + void SetLastSeqSent(uint32); + + // Ack sequence tracking. + long MaxAckReceived,NextAckToSend,LastAckSent; + long GetMaxAckReceived(); + long GetNextAckToSend(); + long GetLastAckSent(); + void SetMaxAckReceived(uint32 seq); + void SetNextAckToSend(uint32); + void SetLastAckSent(uint32); + + Mutex MAcks; + + // Packets waiting to be sent + queue NonSequencedQueue; + deque SequencedQueue; + map OutOfOrderpackets; + Mutex MOutboundQueue; + + // Packes waiting to be processed + deque InboundQueue; + Mutex MInboundQueue; + + static uint16 MaxWindowSize; + + sint32 BytesWritten; + + Mutex MRate; + sint32 RateThreshold; + sint32 DecayRate; + uint32 AverageDelta; + + EQStreamFactory *Factory; + + public: + Mutex MCombineQueueLock; + bool CheckCombineQueue(); + deque combine_queue; + Timer* combine_timer; + + Crypto* crypto; + int8 EQ2_Compress(EQ2Packet* app, int8 offset = 3); + z_stream stream; + uchar* stream_buffer; + int32 stream_buffer_size; + bool eq2_compressed; + int8 compressed_offset; + int16 client_version; + int16 GetClientVersion(){ return client_version; } + void SetClientVersion(int16 version){ client_version = version; } + void ResetSessionAttempts() { reconnectAttempt = 0; } + bool HasSessionAttempts() { return reconnectAttempt>0; } + EQStream() { init(); remote_ip = 0; remote_port = 0; State = CLOSED; StreamType = UnknownStream; compressed = true; + encoded = false; app_opcode_size = 2;} + EQStream(sockaddr_in addr); + virtual ~EQStream() { + MOutboundQueue.lock(); + SetState(CLOSED); + MOutboundQueue.unlock(); + RemoveData(); + safe_delete(crypto); + safe_delete(combine_timer); + safe_delete(resend_que_timer); + safe_delete_array(oversize_buffer); + safe_delete_array(rogue_buffer); + deque::iterator cmb; + MCombineQueueLock.lock(); + for (cmb = combine_queue.begin(); cmb != combine_queue.end(); cmb++){ + safe_delete(*cmb); + } + MCombineQueueLock.unlock(); + deflateEnd(&stream); + map::iterator oop; + for (oop = OutOfOrderpackets.begin(); oop != OutOfOrderpackets.end(); oop++){ + safe_delete(oop->second); + } +#ifdef WRITE_PACKETS + if (write_packets) + fclose(write_packets); +#endif + } + inline void SetFactory(EQStreamFactory *f) { Factory=f; } + void init(bool resetSession = true); + void SetMaxLen(uint32 length) { MaxLen=length; } + int8 getTimeoutDelays(){ return timeout_delays; } + void addTimeoutDelay(){ timeout_delays++; } + void EQ2QueuePacket(EQ2Packet* app, bool attempted_combine = false); + void PreparePacket(EQ2Packet* app, int8 offset = 0); + void UnPreparePacket(EQ2Packet* app); + void EncryptPacket(EQ2Packet* app, int8 compress_offset, int8 offset); + void FlushCombinedPacket(); + void SendPacket(EQApplicationPacket *p); + void QueuePacket(EQProtocolPacket *p); + void SendPacket(EQProtocolPacket *p); + vector convert(EQApplicationPacket *p); + void NonSequencedPush(EQProtocolPacket *p); + void SequencedPush(EQProtocolPacket *p); + + Mutex MResendQue; + Mutex MCompressData; + dequeresend_que; + void CheckResend(int eq_fd); + + void AckPackets(uint16 seq); + void Write(int eq_fd); + + void SetActive(bool val) { streamactive = val; } + + void WritePacket(int fd,EQProtocolPacket *p); + + void EncryptPacket(uchar* data, int16 size); + uint32 GetKey() { return Key; } + void SetKey(uint32 k) { Key=k; } + void SetSession(uint32 s) { Session=s; } + void SetLastPacketTime(uint32 t) {LastPacket=t;} + + void Process(const unsigned char *data, const uint32 length); + void ProcessPacket(EQProtocolPacket *p, EQProtocolPacket* lastp=NULL); + + bool ProcessEmbeddedPacket(uchar* pBuffer, uint16 length, int8 opcode = OP_Packet); + bool HandleEmbeddedPacket(EQProtocolPacket *p, int16 offset = 2, int16 length = 0); + + EQProtocolPacket * ProcessEncryptedPacket(EQProtocolPacket *p); + EQProtocolPacket * ProcessEncryptedData(uchar* data, int32 size, int16 opcode); + + virtual void DispatchPacket(EQApplicationPacket *p) { p->DumpRaw(); } + + void SendSessionResponse(); + void SendSessionRequest(); + void SendDisconnect(bool setstate = true); + void SendAck(uint16 seq); + void SendOutOfOrderAck(uint16 seq); + + bool CheckTimeout(uint32 now, uint32 timeout=30) { return (LastPacket && (now-LastPacket) > timeout); } + bool Stale(uint32 now, uint32 timeout=30) { return (LastPacket && (now-LastPacket) > timeout); } + + void InboundQueuePush(EQApplicationPacket *p); + EQApplicationPacket *PopPacket(); // InboundQueuePop + void InboundQueueClear(); + + void OutboundQueueClear(); + bool HasOutgoingData(); + void SendKeyRequest(); + int16 processRSAKey(EQProtocolPacket *p, uint16 subpacket_length = 0); + void RemoveData() { InboundQueueClear(); OutboundQueueClear(); if (CombinedAppPacket) delete CombinedAppPacket; } + + // + inline bool IsInUse() { bool flag; MInUse.lock(); flag=(active_users>0); MInUse.unlock(); return flag; } + inline void PutInUse() { MInUse.lock(); active_users++; MInUse.unlock(); } + inline void ReleaseFromUse() { MInUse.lock(); if(active_users > 0) active_users--; MInUse.unlock(); } + + static SeqOrder CompareSequence(uint16 expected_seq, uint16 seq); + + inline EQStreamState GetState() { return State; } + inline void SetState(EQStreamState state) { MState.lock(); State = state; MState.unlock(); } + + inline uint32 GetRemoteIP() { return remote_ip; } + inline uint32 GetrIP() { return remote_ip; } + inline uint16 GetRemotePort() { return remote_port; } + inline uint16 GetrPort() { return remote_port; } + + + static EQProtocolPacket *Read(int eq_fd, sockaddr_in *from); + + void Close() { SendDisconnect(); } + bool CheckActive() { return (GetState()==ESTABLISHED); } + bool CheckClosed() { return GetState()==CLOSED; } + void SetOpcodeSize(uint8 s) { app_opcode_size = s; } + void SetStreamType(EQStreamType t); + inline const EQStreamType GetStreamType() const { return StreamType; } + + void ProcessQueue(); + EQProtocolPacket* RemoveQueue(uint16 seq); + + void Decay(); + void AdjustRates(uint32 average_delta); + Timer* resend_que_timer; +}; + +#endif diff --git a/source/common/EQStreamFactory.cpp b/source/common/EQStreamFactory.cpp new file mode 100644 index 0000000..965865e --- /dev/null +++ b/source/common/EQStreamFactory.cpp @@ -0,0 +1,444 @@ +/* + 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 . +*/ + +#include "EQStreamFactory.h" +#include "Log.h" + +#ifdef WIN32 + #include + #include + #include + #include + #include +#else + #include + #include + #include + #include + #include + #include +#endif +#include +#include +#include +#include "op_codes.h" +#include "EQStream.h" +#include "packet_dump.h" +#ifdef WORLD + #include "../WorldServer/client.h" +#endif +using namespace std; + +#ifdef WORLD + extern ClientList client_list; +#endif +ThreadReturnType EQStreamFactoryReaderLoop(void *eqfs) +{ + if(eqfs){ + EQStreamFactory *fs=(EQStreamFactory *)eqfs; + fs->ReaderLoop(); + } + THREAD_RETURN(NULL); +} + +ThreadReturnType EQStreamFactoryWriterLoop(void *eqfs) +{ + if(eqfs){ + EQStreamFactory *fs=(EQStreamFactory *)eqfs; + fs->WriterLoop(); + } + THREAD_RETURN(NULL); +} + +ThreadReturnType EQStreamFactoryCombinePacketLoop(void *eqfs) +{ + if(eqfs){ + EQStreamFactory *fs=(EQStreamFactory *)eqfs; + fs->CombinePacketLoop(); + } + THREAD_RETURN(NULL); +} + +EQStreamFactory::EQStreamFactory(EQStreamType type, int port) +{ + StreamType=type; + Port=port; + listen_ip_address = 0; +} + +void EQStreamFactory::Close() +{ + CheckTimeout(true); + Stop(); + if (sock != -1) { +#ifdef WIN32 + closesocket(sock); +#else + close(sock); +#endif + sock = -1; + } +} +bool EQStreamFactory::Open() +{ +struct sockaddr_in address; +#ifndef WIN32 + pthread_t t1, t2, t3; +#endif + /* Setup internet address information. + This is used with the bind() call */ + memset((char *) &address, 0, sizeof(address)); + address.sin_family = AF_INET; + address.sin_port = htons(Port); +#if defined(LOGIN) || defined(MINILOGIN) + if(listen_ip_address) + address.sin_addr.s_addr = inet_addr(listen_ip_address); + else + address.sin_addr.s_addr = htonl(INADDR_ANY); +#else + address.sin_addr.s_addr = htonl(INADDR_ANY); +#endif + /* Setting up UDP port for new clients */ + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) { + return false; + } + + if (::bind(sock, (struct sockaddr *) &address, sizeof(address)) < 0) { + //close(sock); + sock=-1; + return false; + } + #ifdef WIN32 + unsigned long nonblock = 1; + ioctlsocket(sock, FIONBIO, &nonblock); + #else + fcntl(sock, F_SETFL, O_NONBLOCK); + #endif + //moved these because on windows the output was delayed and causing the console window to look bad +#ifdef LOGIN + LogWrite(LOGIN__DEBUG, 0, "Login", "Starting factory Reader"); + LogWrite(LOGIN__DEBUG, 0, "Login", "Starting factory Writer"); +#elif WORLD + LogWrite(WORLD__DEBUG, 0, "World", "Starting factory Reader"); + LogWrite(WORLD__DEBUG, 0, "World", "Starting factory Writer"); +#endif + #ifdef WIN32 + _beginthread(EQStreamFactoryReaderLoop,0, this); + _beginthread(EQStreamFactoryWriterLoop,0, this); + _beginthread(EQStreamFactoryCombinePacketLoop,0, this); + #else + pthread_create(&t1,NULL,EQStreamFactoryReaderLoop,this); + pthread_create(&t2,NULL,EQStreamFactoryWriterLoop,this); + pthread_create(&t3,NULL,EQStreamFactoryCombinePacketLoop,this); + pthread_detach(t1); + pthread_detach(t2); + pthread_detach(t3); + #endif + return true; +} + +EQStream *EQStreamFactory::Pop() +{ + if (!NewStreams.size()) + return NULL; + +EQStream *s=NULL; + //cout << "Pop():Locking MNewStreams" << endl; + MNewStreams.lock(); + if (NewStreams.size()) { + s=NewStreams.front(); + NewStreams.pop(); + s->PutInUse(); + } + MNewStreams.unlock(); + //cout << "Pop(): Unlocking MNewStreams" << endl; + + return s; +} + +void EQStreamFactory::Push(EQStream *s) +{ + //cout << "Push():Locking MNewStreams" << endl; + MNewStreams.lock(); + NewStreams.push(s); + MNewStreams.unlock(); + //cout << "Push(): Unlocking MNewStreams" << endl; +} + +void EQStreamFactory::ReaderLoop() +{ +fd_set readset; +map::iterator stream_itr; +int num; +int length; +unsigned char buffer[2048]; +sockaddr_in from; +int socklen=sizeof(sockaddr_in); +timeval sleep_time; + ReaderRunning=true; + while(sock!=-1) { + MReaderRunning.lock(); + if (!ReaderRunning) + break; + MReaderRunning.unlock(); + + FD_ZERO(&readset); + FD_SET(sock,&readset); + + sleep_time.tv_sec=30; + sleep_time.tv_usec=0; + if ((num=select(sock+1,&readset,NULL,NULL,&sleep_time))<0) { + // What do we wanna do? + } else if (num==0) + continue; + + if (FD_ISSET(sock,&readset)) { +#ifdef WIN32 + if ((length=recvfrom(sock,(char*)buffer,sizeof(buffer),0,(struct sockaddr*)&from,(int *)&socklen))<2) +#else + if ((length=recvfrom(sock,buffer,2048,0,(struct sockaddr *)&from,(socklen_t *)&socklen))<2) +#endif + { + // What do we wanna do? + } else { + char temp[25]; + sprintf(temp,"%u.%d",ntohl(from.sin_addr.s_addr),ntohs(from.sin_port)); + MStreams.lock(); + if ((stream_itr=Streams.find(temp))==Streams.end() || buffer[1]==OP_SessionRequest) { + MStreams.unlock(); + if (buffer[1]==OP_SessionRequest) { + if(stream_itr != Streams.end() && stream_itr->second) + stream_itr->second->SetState(CLOSED); + EQStream *s=new EQStream(from); + s->SetFactory(this); + s->SetStreamType(StreamType); + Streams[temp]=s; + WriterWork.Signal(); + Push(s); + s->Process(buffer,length); + s->SetLastPacketTime(Timer::GetCurrentTime2()); + } + } else { + EQStream *curstream = stream_itr->second; + //dont bother processing incoming packets for closed connections + if(curstream->CheckClosed()) + curstream = NULL; + else + curstream->PutInUse(); + MStreams.unlock(); + + if(curstream) { + curstream->Process(buffer,length); + curstream->SetLastPacketTime(Timer::GetCurrentTime2()); + curstream->ReleaseFromUse(); + } + } + } + } + } +} + +void EQStreamFactory::CheckTimeout(bool remove_all) +{ + //lock streams the entire time were checking timeouts, it should be fast. + MStreams.lock(); + + unsigned long now=Timer::GetCurrentTime2(); + map::iterator stream_itr; + + for(stream_itr=Streams.begin();stream_itr!=Streams.end();) { + EQStream *s = stream_itr->second; + EQStreamState state = s->GetState(); + + if (state==CLOSING && !s->HasOutgoingData()) { + stream_itr->second->SetState(CLOSED); + state = CLOSED; + } else if (s->CheckTimeout(now, STREAM_TIMEOUT)) { + const char* stateString; + switch (state){ + case ESTABLISHED: + stateString = "Established"; + break; + case CLOSING: + stateString = "Closing"; + break; + case CLOSED: + stateString = "Closed"; + break; + case WAIT_CLOSE: + stateString = "Wait-Close"; + break; + default: + stateString = "Unknown"; + break; + } + LogWrite(WORLD__DEBUG, 0, "World", "Timeout up!, state=%s (%u)", stateString, state); + if (state==ESTABLISHED) { + s->Close(); + } + else if (state == WAIT_CLOSE) { + s->SetState(CLOSING); + state = CLOSING; + } + else if (state == CLOSING) { + //if we time out in the closing state, just give up + s->SetState(CLOSED); + state = CLOSED; + } + } + //not part of the else so we check it right away on state change + if (remove_all || state==CLOSED) { + if (!remove_all && s->getTimeoutDelays()<2) { + s->addTimeoutDelay(); + //give it a little time for everybody to finish with it + } else { + //everybody is done, we can delete it now + +#ifdef LOGIN + LogWrite(LOGIN__DEBUG, 0, "Login", "Removing connection..."); +#else + LogWrite(WORLD__DEBUG, 0, "World", "Removing connection..."); +#endif + map::iterator temp=stream_itr; + stream_itr++; + //let whoever has the stream outside delete it + #ifdef WORLD + client_list.RemoveConnection(temp->second); + #endif + EQStream* stream = temp->second; + Streams.erase(temp); + delete stream; + continue; + } + } + + stream_itr++; + } + MStreams.unlock(); +} + +void EQStreamFactory::CombinePacketLoop(){ + deque combine_que; + CombinePacketRunning = true; + bool packets_waiting = false; + while(sock!=-1) { + if (!CombinePacketRunning) + break; + MStreams.lock(); + map::iterator stream_itr; + for(stream_itr=Streams.begin();stream_itr!=Streams.end();stream_itr++) { + if(!stream_itr->second){ + continue; + } + if(stream_itr->second->combine_timer && stream_itr->second->combine_timer->Check()) + combine_que.push_back(stream_itr->second); + } + EQStream* stream = 0; + packets_waiting = false; + while(combine_que.size()){ + stream = combine_que.front(); + if(stream->CheckActive()){ + if(!stream->CheckCombineQueue()) + packets_waiting = true; + } + combine_que.pop_front(); + } + MStreams.unlock(); + if(!packets_waiting) + Sleep(25); + + Sleep(1); + } +} + +void EQStreamFactory::WriterLoop() +{ +map::iterator stream_itr; +vector wants_write; +vector::iterator cur,end; +deque resend_que; +bool decay=false; +uint32 stream_count; + +Timer DecayTimer(20); + + WriterRunning=true; + DecayTimer.Enable(); + while(sock!=-1) { + Timer::SetCurrentTime(); + //if (!havework) { + //WriterWork.Wait(); + //} + MWriterRunning.lock(); + if (!WriterRunning) + break; + MWriterRunning.unlock(); + + wants_write.clear(); + + decay=DecayTimer.Check(); + + //copy streams into a seperate list so we dont have to keep + //MStreams locked while we are writting + MStreams.lock(); + for(stream_itr=Streams.begin();stream_itr!=Streams.end();stream_itr++) { + // If it's time to decay the bytes sent, then let's do it before we try to write + if(!stream_itr->second){ + Streams.erase(stream_itr); + break; + } + if (decay) + stream_itr->second->Decay(); + + if (stream_itr->second->HasOutgoingData()) { + stream_itr->second->PutInUse(); + wants_write.push_back(stream_itr->second); + } + if(stream_itr->second->resend_que_timer->Check()) + resend_que.push_back(stream_itr->second); + } + MStreams.unlock(); + + //do the actual writes + cur = wants_write.begin(); + end = wants_write.end(); + for(; cur != end; cur++) { + (*cur)->Write(sock); + (*cur)->ReleaseFromUse(); + } + while(resend_que.size()){ + resend_que.front()->CheckResend(sock); + resend_que.pop_front(); + } + Sleep(10); + + MStreams.lock(); + stream_count=Streams.size(); + MStreams.unlock(); + if (!stream_count) { + //cout << "No streams, waiting on condition" << endl; + WriterWork.Wait(); + //cout << "Awake from condition, must have a stream now" << endl; + } + } +} + + diff --git a/source/common/EQStreamFactory.h b/source/common/EQStreamFactory.h new file mode 100644 index 0000000..9dce3c7 --- /dev/null +++ b/source/common/EQStreamFactory.h @@ -0,0 +1,86 @@ +/* + 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 . +*/ +#ifndef _EQSTREAMFACTORY_H + +#define _EQSTREAMFACTORY_H + +#include +#include +#include "../common/EQStream.h" +#include "../common/Condition.h" +#include "../common/opcodemgr.h" +#include "../common/timer.h" + +#define STREAM_TIMEOUT 45000 //in ms + +class EQStreamFactory { + private: + int sock; + int Port; + + bool ReaderRunning; + Mutex MReaderRunning; + bool WriterRunning; + Mutex MWriterRunning; + bool CombinePacketRunning; + Mutex MCombinePacketRunning; + + Condition WriterWork; + + EQStreamType StreamType; + + queue NewStreams; + Mutex MNewStreams; + + map Streams; + Mutex MStreams; + + + + Timer *DecayTimer; + + public: + char* listen_ip_address; + void CheckTimeout(bool remove_all = false); + EQStreamFactory(EQStreamType type) { ReaderRunning=false; WriterRunning=false; StreamType=type; } + EQStreamFactory(EQStreamType type, int port); + ~EQStreamFactory(){ + safe_delete_array(listen_ip_address); + } + + EQStream *Pop(); + void Push(EQStream *s); + + bool loadPublicKey(); + bool Open(); + bool Open(unsigned long port) { Port=port; return Open(); } + void Close(); + void ReaderLoop(); + void WriterLoop(); + void CombinePacketLoop(); + void Stop() { StopReader(); StopWriter(); StopCombinePacket(); } + void StopReader() { MReaderRunning.lock(); ReaderRunning=false; MReaderRunning.unlock(); } + void StopWriter() { MWriterRunning.lock(); WriterRunning=false; MWriterRunning.unlock(); WriterWork.Signal(); } + void StopCombinePacket() { MCombinePacketRunning.lock(); CombinePacketRunning=false; MCombinePacketRunning.unlock(); } + void SignalWriter() { WriterWork.Signal(); } + +}; + +#endif diff --git a/source/common/GlobalHeaders.h b/source/common/GlobalHeaders.h new file mode 100644 index 0000000..98b7458 --- /dev/null +++ b/source/common/GlobalHeaders.h @@ -0,0 +1,58 @@ +/* + 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 . +*/ + +//Character Creation Replies, put in globals so name filter can return proper responses +#define UNKNOWNERROR_REPLY 0 +#define CREATESUCCESS_REPLY 1 +#define NOSERVERSAVAIL_REPLY 2 +#define CREATEPENDING_REPLY 3 +#define MAXCHARSALLOWED_REPLY 4 +#define INVALIDRACE_REPLY 5 +#define INVALIDCITY_REPLY 6 +#define INVALIDCLASS_REPLY 7 +#define INVALIDGENDER_REPLY 8 +#define INVALIDFIRST_LVL_REPLY 9 +#define BADNAMELENGTH_REPLY 10 +#define NAMEINVALID_REPLY 11 +#define NAMEFILTER_REPLY 12 // name_filter reply (bad word or blocked words) +#define NAMETAKEN_REPLY 13 +#define OVERLOADEDSERVER_REPLY 14 +#define UNKNOWNERROR_REPLY2 15 +#define INVALIDFEATURES1_REPLY 16 +#define INVALIDFEATURES2_REPLY 17 +#define INVALIDRACE_APPEARANCE_REPLY 18 + +#define PLAY_ERROR_PROBLEM 0 +#define PLAY_ERROR_ZONE_DOWN 4 +#define PLAY_ERROR_CHAR_NOT_LOADED 5 +#define PLAY_ERROR_CHAR_NOT_FOUND 6 +#define PLAY_ERROR_ACCOUNT_IN_USE 7 +#define PLAY_ERROR_SERVER_TIMEOUT 8 +#define PLAY_ERROR_SERVER_SHUTDOWN 9 +#define PLAY_ERROR_LOADING_ERROR 10 +#define PLAY_ERROR_EXCHANGE_SERVER 11 +#define PLAY_ERROR_REGION_SERVER 12 +#define PLAY_ERROR_CLASS_INVALID 13 +#define PLAY_ERROR_TOO_MANY_CHARACTERS 14 +#define PLAY_ERROR_EOF_EXP_NOT_FOUND 15 +#define PLAY_ERROR_UNKNOWN_RESPONSE 16 +#define PLAY_ERROR_UNKNOWN 17 +#define PLAY_ERROR_ACCOUNT_BANNED 18 +#define PLAY_ERROR_PROHIBITED 19 diff --git a/source/common/JsonParser.cpp b/source/common/JsonParser.cpp new file mode 100644 index 0000000..cca065a --- /dev/null +++ b/source/common/JsonParser.cpp @@ -0,0 +1,97 @@ +#include "JsonParser.h" + +JsonParser::JsonParser(const std::string &filename) { + is_loaded = false; + try { + boost::property_tree::read_json(filename, pt); + parseTree(pt, ""); + is_loaded = true; + } catch (const boost::property_tree::json_parser_error &e) { + std::cerr << "Error reading JSON file: " << e.what() << std::endl; + } +} + +bool JsonParser::convertStringToUnsignedChar(const std::string& str, unsigned char& result) { + unsigned long ul; + try { + ul = std::stoul(str); + } catch (const std::invalid_argument&) { + return false; // Not a valid number + } catch (const std::out_of_range&) { + return false; // Number is too large for unsigned long + } + + if (ul > std::numeric_limits::max()) { + return false; // Number is too large for unsigned short + } + + result = static_cast(ul); + return true; +} + +bool JsonParser::convertStringToUnsignedShort(const std::string& str, unsigned short& result) { + unsigned long ul; + try { + ul = std::stoul(str); + } catch (const std::invalid_argument&) { + return false; // Not a valid number + } catch (const std::out_of_range&) { + return false; // Number is too large for unsigned long + } + + if (ul > std::numeric_limits::max()) { + return false; // Number is too large for unsigned short + } + + result = static_cast(ul); + return true; +} + +bool JsonParser::convertStringToUnsignedInt(const std::string& str, unsigned int& result) { + unsigned long ul; + try { + ul = std::stoul(str); + } catch (const std::invalid_argument&) { + return false; // Not a valid number + } catch (const std::out_of_range&) { + return false; // Number is too large for unsigned long + } + + if (ul > std::numeric_limits::max()) { + return false; // Number is too large for unsigned short + } + + result = static_cast(ul); + return true; +} + +bool JsonParser::convertStringToUnsignedLong(const std::string& str, unsigned long& result) { + unsigned long ul; + try { + ul = std::stoul(str); + } catch (const std::invalid_argument&) { + return false; // Not a valid number + } catch (const std::out_of_range&) { + return false; // Number is too large for unsigned long + } + + if (ul > std::numeric_limits::max()) { + return false; // Number is too large for unsigned short + } + + result = ul; + return true; +} + +void JsonParser::parseTree(const boost::property_tree::ptree &tree, const std::string &path) { + for (const auto &node : tree) { + std::string currentPath = path.empty() ? node.first : path + "." + node.first; + if (node.second.empty()) { + std::string name = currentPath; + boost::algorithm::to_lower(name); + values[name] = node.second.get_value(); + } else { + parseTree(node.second, currentPath); + } + } +} \ No newline at end of file diff --git a/source/common/JsonParser.h b/source/common/JsonParser.h new file mode 100644 index 0000000..f92b07d --- /dev/null +++ b/source/common/JsonParser.h @@ -0,0 +1,33 @@ + +#include +#include +#include +#include +#include +#include +#include + +class JsonParser { +public: + JsonParser(const std::string &filename); + + std::string getValue(const std::string &path) const { + auto it = values.find(path); + if (it != values.end()) { + return it->second; + } + return ""; + } + + static bool convertStringToUnsignedChar(const std::string& str, unsigned char& result); + static bool convertStringToUnsignedShort(const std::string& str, unsigned short& result); + static bool convertStringToUnsignedInt(const std::string& str, unsigned int& result); + static bool convertStringToUnsignedLong(const std::string& str, unsigned long& result); + bool IsLoaded() { return is_loaded; } +private: + boost::property_tree::ptree pt; + std::map values; + + void parseTree(const boost::property_tree::ptree &tree, const std::string &path); + bool is_loaded; +}; diff --git a/source/common/Log.cpp b/source/common/Log.cpp new file mode 100644 index 0000000..0c59aa9 --- /dev/null +++ b/source/common/Log.cpp @@ -0,0 +1,615 @@ +/* + 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 . +*/ +#include "Log.h" +#include "xmlParser.h" +#include "types.h" +#include +#include +#include +#include +#include +#include +#include +#include "../WorldServer/World.h" +#include "../WorldServer/client.h" +#include "../WorldServer/zoneserver.h" + +extern ZoneList zone_list; + +#ifdef _WIN32 + #include + #ifndef snprintf + #define snprintf sprintf_s + #endif +#include + #include +#else +#endif + +#define LOG_CATEGORY(category) #category, +const char *log_category_names[NUMBER_OF_LOG_CATEGORIES] = { + #include "LogTypes.h" +}; + +#define LOG_TYPE(category, type, level, color, enabled, logfile, console, client, str) { level, color, enabled, logfile, console, client, LOG_ ##category, #category "__" #type, ( strlen(str)>0 ) ? str : #category "__" #type }, +static LogTypeStatus real_log_type_info[NUMBER_OF_LOG_TYPES+1] = +{ + #include "LogTypes.h" + { 0, 0, false, false, false, false, NUMBER_OF_LOG_CATEGORIES, "BAD TYPE", "Bad Name" } /* dummy trailing record */ +}; + +LogTypeStatus *log_type_info = real_log_type_info; + +//make these rules? +#define LOG_CYCLE 100 //milliseconds between each batch of log writes +#define LOGS_PER_CYCLE 50 //amount of logs to write per cycle + +#define LOG_DIR "logs" + +#if defined LOGIN +#define EXE_NAME "login" +#elif defined WORLD +#define EXE_NAME "world" +#elif defined PARSER +#define EXE_NAME "parser" +#elif defined PATCHER +#define EXE_NAME "patcher" +#else +#define EXE_NAME "whatprogyourunning" +#endif + +#define DATE_MAX 8 +#define LOG_NAME_MAX 32 + +struct logq_t { + LogType log_type; + char date[DATE_MAX + 1]; + char name[LOG_NAME_MAX + 1]; + char *text; + struct logq_t *next; + struct logq_t *prev; +}; + +//doubly linked list of logs +static logq_t head; +static logq_t tail; +static int num_logqs = 0; +static Mutex mlogqs; + +//loop until.... +static bool looping = false; + +//because our code has LogWrite's before main(), make sure any of those do the +//call to LogStart if it hasn't been called already... +static bool start_called = false; + +static void SetConsoleColor(int color) { +#ifdef _WIN32 + HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); + + if (handle == NULL || handle == INVALID_HANDLE_VALUE) + return; +#endif + + switch (color) { + case FOREGROUND_WHITE: + case FOREGROUND_WHITE_BOLD: + case FOREGROUND_RED: + case FOREGROUND_RED_BOLD: + case FOREGROUND_GREEN: + case FOREGROUND_GREEN_BOLD: + case FOREGROUND_BLUE: + case FOREGROUND_BLUE_BOLD: + case FOREGROUND_YELLOW: + case FOREGROUND_YELLOW_BOLD: + case FOREGROUND_CYAN: + case FOREGROUND_CYAN_BOLD: + case FOREGROUND_MAGENTA: + case FOREGROUND_MAGENTA_BOLD: +#ifdef _WIN32 + SetConsoleTextAttribute(handle, color); +#else + printf("\033[%i;%i;40m", color > 100 ? 1 : 0, color > 100 ? color - 100 : color); +#endif + break; + default: +#ifdef _WIN32 + SetConsoleTextAttribute(handle, FOREGROUND_WHITE_BOLD); +#else + printf("\033[0;37;40m"); +#endif + break; + } +} + +static FILE * OpenLogFile() { + char file[FILENAME_MAX + 1]; + struct stat st; + struct tm *tm; + time_t now; + FILE *f; + + now = time(NULL); + tm = localtime(&now); + + //make sure the logs directory exists + if (stat(LOG_DIR, &st) != 0) { +#ifdef _WIN32 + if (!CreateDirectory(LOG_DIR, NULL)) { + fprintf(stderr, "Unable to create directory '%s'\n", LOG_DIR); + return stderr; + } +#else + if (mkdir(LOG_DIR, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0) { + fprintf(stderr, "Unable to create direcotry '%s': %s\n", LOG_DIR, strerror(errno)); + return stderr; + } +#endif + } + +#ifdef NO_PIDLOG + snprintf(file, FILENAME_MAX, LOG_DIR"/%04i-%02i-%02i_eq2" EXE_NAME ".log", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); +#else + snprintf(file, FILENAME_MAX, LOG_DIR"/%04i-%02i-%02i_eq2" EXE_NAME "_%04i.log", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, getpid()); +#endif + + if ((f = fopen(file, "a")) == NULL) { + fprintf(stderr, "Could not open '%s' for writing: %s\n", file, strerror(errno)); + return stderr; + } + + return f; +} + +static void WriteQueuedLogs(int count) { + logq_t pending_head, pending_tail, *logq, *tmp; + int i = 0; + FILE *f; + + pending_head.next = &pending_tail; + pending_tail.prev = &pending_head; + + //loop through our queued logs and store at most `count` logs into a temporary list + //since io functions are expensive, we'll write from a temporary list so we don't hold the + //write lock of the main list for a long period of time + mlogqs.writelock(); + logq = head.next; + + while (head.next != &tail) { + //first remove the log from the master list + logq = head.next; + logq->next->prev = &head; + head.next = logq->next; + + //now insert it into the temporary list + tmp = pending_tail.prev; + tmp->next = logq; + logq->prev = tmp; + logq->next = &pending_tail; + pending_tail.prev = logq; + --num_logqs; + + logq = logq->next; + + //if we have a limit, check it + if (count > 0 && ++i == count) + break; + } + + //if we have no logs to write, we're done + if ((logq = pending_head.next) == &pending_tail) + { + mlogqs.releasewritelock(); + return; + } + + while (logq != &pending_tail) { + if (log_type_info[logq->log_type].console) { + SetConsoleColor(FOREGROUND_WHITE_BOLD); + printf("%s ", logq->date); + SetConsoleColor(log_type_info[logq->log_type].color); + printf("%s ", log_type_info[logq->log_type].display_name); + SetConsoleColor(FOREGROUND_WHITE_BOLD); + printf("%-10s: ", logq->name); + SetConsoleColor(log_type_info[logq->log_type].color); + printf("%s\n", logq->text); + SetConsoleColor(-1); + fflush(stdout); + } + + if (log_type_info[logq->log_type].logfile) { + f = OpenLogFile(); + + if (f != stderr || (f == stderr && !log_type_info[logq->log_type].console)) { + fprintf(f, "%s %s %s: %s\n", logq->date, log_type_info[logq->log_type].display_name, logq->name, logq->text); + fflush(f); + if (f != stderr) + fclose(f); + } + } + +#if defined WORLD + if (log_type_info[logq->log_type].client) { + // eventually output logging to the client who "subscribed" to the logger + // in-game, they type: + // /logsys add WORLD__DEBUG 5 + // to watch world debug loggers of level 5 or less + } +#endif + + tmp = logq; + logq = logq->next; + + mlogqs.releasewritelock(); + + free(tmp->text); + free(tmp); + } +} + +ThreadReturnType LogLoop(void *args) { + while (looping) { + WriteQueuedLogs(LOGS_PER_CYCLE); + Sleep(LOG_CYCLE); + } + + THREAD_RETURN(NULL); +} + +void LogStart() { + if (start_called) + return; + + //initialize the doubly linked list + head.prev = NULL; + head.next = &tail; + tail.prev = &head; + tail.next = NULL; + + mlogqs.SetName("logqueue"); + looping = true; + +#ifdef _WIN32 + _beginthread(LogLoop, 0, NULL); +#else + pthread_t thread; + pthread_create(&thread, NULL, LogLoop, NULL); + pthread_detach(thread); +#endif + + start_called = true; +} + +void LogStop() { + looping = false; + WriteQueuedLogs(-1); + start_called = false; +} + +static void LogQueueAdd(LogType log_type, char *text, int len, const char *cat_text = NULL) { + logq_t *logq; + struct tm *tm; + time_t now; + + if ((logq = (logq_t *)calloc(1, sizeof(logq_t))) == NULL) { + free(text); + fprintf(stderr, "%s: %u: Unable to allocate %zu bytes\n", __FUNCTION__, __LINE__, sizeof(logq_t)); + return; + } + + if ((logq->text = (char *)calloc(len + 1, sizeof(char))) == NULL) { + free(text); + free(logq); + fprintf(stderr, "%s: %u: Unable to allocate %i bytes\n", __FUNCTION__, __LINE__, len + 1); + return; + } + + now = time(NULL); + tm = localtime(&now); + + logq->log_type = log_type; + snprintf(logq->date, DATE_MAX + 1, "%02i:%02i:%02i", tm->tm_hour, tm->tm_min, tm->tm_sec); + strncpy(logq->name, cat_text == NULL || cat_text[0] == '\0' ? log_type_info[log_type].name : cat_text, LOG_NAME_MAX); + strncpy(logq->text, text, len); + free(text); + + if (!start_called) + LogStart(); + + //insert at the end + mlogqs.writelock(); + tail.prev->next = logq; + logq->prev = tail.prev; + logq->next = &tail; + tail.prev = logq; + ++num_logqs; + mlogqs.releasewritelock(); +} + +int8 GetLoggerLevel(LogType type) +{ + return log_type_info[type].level; +} + +// JA: horrific hack for Parser, since queued logging keeps crashing between parses. +#ifndef PARSER +void LogWrite(LogType type, int8 log_level, const char *cat_text, const char *fmt, ...) { + int count, size = 64; + char *buf; + va_list ap; + + // if there is no formatting, or the logger is DISABLED + // or the log_level param exceeds the minimum allowed value, abort logwrite + if (!log_type_info[type].enabled || (log_level > 0 && log_type_info[type].level < log_level)) + return; + + while (true) { + if ((buf = (char *)malloc(size)) == NULL) { + fprintf(stderr, "%s: %i: Unable to allocate %i bytes\n", __FUNCTION__, __LINE__, size); + return; + } + + va_start(ap, fmt); + count = vsnprintf(buf, size, fmt, ap); + va_end(ap); + + if (count > -1 && count < size) + break; + + free(buf); + if (count > 1) + size = count + 1; + else + size *= 2; + } + + LogQueueAdd(type, buf, count, cat_text); +} +#else +void LogWrite(LogType type, int8 log_level, const char *cat_text, const char *format, ...) +{ + // if there is no formatting, or the logger is DISABLED + // or the log_level param exceeds the minimum allowed value, abort logwrite + if ( !format || !log_type_info[type].enabled || (log_level > 0 && log_type_info[type].level < log_level) ) + return; + + time_t clock; + struct tm *tm; + + char buffer[LOG_BUFFER_SIZE], date[32]; + va_list args; + FILE *f; + size_t cat_text_len = 0; + + memset(buffer, 0, sizeof(buffer)); + memset(date, 0, sizeof(date)); + + va_start(args, format); + vsnprintf(buffer, sizeof(buffer) - 1, format, args); + va_end(args); + + time(&clock); + tm = localtime(&clock); + snprintf(date, sizeof(date)-1, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec); + // DateString(date, sizeof(date)); + + cat_text_len = strlen(cat_text); + //if( strlen(cat_text) == 0 ) // cat_text was blank + // cat_text = (char*)log_type_info[type].name; + + /* write to the log file? */ + if (log_type_info[type].logfile) + { + char exename[200] = ""; + + #ifdef LOGIN + snprintf(exename, sizeof(exename), "login"); + #endif + #ifdef WORLD + snprintf(exename, sizeof(exename), "world"); + #endif + #ifdef PARSER + snprintf(exename, sizeof(exename), "parser"); + #endif + #ifdef PATCHER + snprintf(exename, sizeof(exename), "patcher"); + #endif + + char filename[200], log_header[200] = ""; + + #ifndef NO_PIDLOG + snprintf(filename, sizeof(filename)-1, "logs/%04d-%02d-%02d_eq2%s_%04i.log", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, exename, getpid()); + #else + snprintf(filename, sizeof(filename)-1, "logs/%04d-%02d-%02d_eq2%s.log", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, exename); + #endif + + f=fopen(filename, "r"); + if( !f ) + snprintf(log_header, sizeof(log_header), "===[ New log '%s' started ]===\n\n", filename); + else + fclose (f); + + f = fopen(filename, "a"); + if (f) { + if( strlen(log_header) > 0 ) + fprintf(f, "%s\n", log_header); + fprintf(f, "%s %s %s: %s\n", date, log_type_info[type].display_name, cat_text, buffer); + fclose(f); + } + } + + /* write to the console? */ + if (log_type_info[type].console) + { + #ifdef _WIN32 + ColorizeLog(log_type_info[type].color, date, log_type_info[type].display_name, cat_text_len == 0 ? log_type_info[type].name : cat_text, (string)buffer); + #else + printf("%s %s %s: %s\n", date, log_type_info[type].display_name, cat_text_len == 0 ? log_type_info[type].name : cat_text, buffer); + #endif + } +} + +void +ColorizeLog(int color, char *date, const char *display_name, const char *category, string buffer) +{ + #ifdef _WIN32 + HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE); + if (console == INVALID_HANDLE_VALUE) { + printf("%s %s %s: %s\n", date, display_name, category, buffer); + return; + } + printf("%s ", date); + SetConsoleTextAttribute(console, color); + printf("%s ", display_name); + SetConsoleTextAttribute(console, FOREGROUND_WHITE_BOLD); + printf("%s: ", category); + SetConsoleTextAttribute(console, color); + printf("%s\n", buffer.c_str()); + SetConsoleTextAttribute(console, FOREGROUND_WHITE); + #endif +} + +#endif + +LogTypeStatus * +GetLogTypeStatus(const char *category, const char *type) { + char combined[256]; + int i; + + memset(combined, 0, sizeof(combined)); + snprintf(combined, sizeof(combined) - 1, "%s__%s", category, type); + + for (i = 0; i < NUMBER_OF_LOG_TYPES; i++) { + if (strcasecmp(log_type_info[i].name, combined) == 0) + return &log_type_info[i]; + } + + return &log_type_info[NUMBER_OF_LOG_TYPES]; +} + +void +ProcessLogConfig(XMLNode node) { + int i; + const char *category, *type, *level, *color, *enabled, *logs; + LogTypeStatus *lfs; + XMLNode child; + + category = node.getAttribute("Category"); + if (!category) { + LogWrite(MISC__WARNING, 0, "Misc", "Error parsing log config. Config missing a Category"); + return; + } + + for (i = 0; i < node.nChildNode("ConfigType"); i++) { + child = node.getChildNode("ConfigType", i); + type = child.getAttribute("Type"); + + if (!type) { + LogWrite(MISC__WARNING, 0, "Misc", "Error parsing log config. Config missing a Type"); + continue; + } + + lfs = GetLogTypeStatus(category, type); + level = child.getAttribute("Level"); + enabled = child.getAttribute("Enabled"); + color = child.getAttribute("Color"); + logs = child.getAttribute("Logs"); + + if (!logs) { + LogWrite(MISC__WARNING, 0, "Misc", "Error parsing log config. Config missing 'Logs' attribute to specify which log(s) to write to"); + continue; + } + if (!IsNumber(logs)) { + LogWrite(MISC__WARNING, 0, "Misc", "Error parsing log config. Attribute 'Logs' must be a number. See LogTypes.h for the valid types."); + continue; + } + + if (enabled) { + if (!strcasecmp("true", enabled) || !strcasecmp("on", enabled)) + lfs->enabled = true; + else if (!strcasecmp("false", enabled) || !strcasecmp("off", enabled)) + lfs->enabled = false; + else + LogWrite(MISC__WARNING, 0, "Misc", "Error parsing log config. Log setting 'Enabled' has invalid value '%s'. 'true'/'on' or 'false'/'off' are valid values", enabled); + } + + if (IsNumber(level)) + lfs->level = atoi(level); + else + lfs->level = 0; + + if (color) { + if (IsNumber(color)) + lfs->color = atoi(color); + else if (!strcasecmp("White", color)) + lfs->color = FOREGROUND_WHITE; + else if (!strcasecmp("Green", color)) + lfs->color = FOREGROUND_GREEN; + else if (!strcasecmp("Yellow", color)) + lfs->color = FOREGROUND_YELLOW; + else if (!strcasecmp("Red", color)) + lfs->color = FOREGROUND_RED; + else if (!strcasecmp("Blue", color)) + lfs->color = FOREGROUND_BLUE; + else if (!strcasecmp("Cyan", color)) + lfs->color = FOREGROUND_CYAN; + else if (!strcasecmp("Magenta", color)) + lfs->color = FOREGROUND_MAGENTA; + else if (!strcasecmp("WhiteBold", color)) + lfs->color = FOREGROUND_WHITE_BOLD; + else if (!strcasecmp("GreenBold", color)) + lfs->color = FOREGROUND_GREEN_BOLD; + else if (!strcasecmp("YellowBold", color)) + lfs->color = FOREGROUND_YELLOW_BOLD; + else if (!strcasecmp("RedBold", color)) + lfs->color = FOREGROUND_RED_BOLD; + else if (!strcasecmp("BlueBold", color)) + lfs->color = FOREGROUND_BLUE_BOLD; + else if (!strcasecmp("CyanBold", color)) + lfs->color = FOREGROUND_CYAN_BOLD; + else if (!strcasecmp("MagentaBold", color)) + lfs->color = FOREGROUND_MAGENTA_BOLD; + else + LogWrite(MISC__WARNING, 0, "Misc", "Error parsing log config. Log setting 'Color' has invalid value '%s'", color); + } + + // JA: something was wrong here, lfs->logfile or console always was true, even if bit was off. Will ask Scatman about it someday. + lfs->logfile = (atoi(logs) & LOG_LOGFILE); + lfs->console = (atoi(logs) & LOG_CONSOLE); + lfs->client = (atoi(logs) & LOG_CLIENT); + } +} + +bool +LogParseConfigs() { + XMLNode main_node; + int i; + + main_node = XMLNode::openFileHelper("log_config.xml", "EQ2EmuLogConfigs"); + if (main_node.isEmpty()) { + LogWrite(MISC__WARNING, 0, "Misc", "Unable to parse the file 'log_config.xml' or it does not exist. Default values will be used"); + return false; + } + + for (i = 0; i < main_node.nChildNode("LogConfig"); i++) + ProcessLogConfig(main_node.getChildNode("LogConfig", i)); + + return true; +} diff --git a/source/common/Log.h b/source/common/Log.h new file mode 100644 index 0000000..8a7e6a6 --- /dev/null +++ b/source/common/Log.h @@ -0,0 +1,69 @@ +/* + 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 . +*/ +#ifndef LOG_H_ +#define LOG_H_ + +#include +#include "../WorldServer/client.h" + +#define LOG_BUFFER_SIZE 4096 + +#define LOG_CATEGORY(category) LOG_ ##category , +enum LogCategory +{ + #include "LogTypes.h" + NUMBER_OF_LOG_CATEGORIES +}; + +#define LOG_TYPE(category, type, level, color, enabled, logfile, console, client, str) category##__##type , +enum LogType +{ + #include "LogTypes.h" + NUMBER_OF_LOG_TYPES +}; + +extern const char* log_category_names[NUMBER_OF_LOG_CATEGORIES]; + +struct LogTypeStatus +{ + int8 level; + int color; + bool enabled; + bool logfile; + bool console; + bool client; + LogCategory category; + const char *name; + const char *display_name; +}; + +extern LogTypeStatus* log_type_info; + +void LogStart(); +void LogStop(); +int8 GetLoggerLevel(LogType type); +void LogWrite(LogType type, int8 log_level, const char *cat_text, const char *fmt, ...); +#ifdef PARSER + void ColorizeLog(int color, char *date, const char *display_name, const char *category, string buffer); +#endif + +bool LogParseConfigs(); + +#endif diff --git a/source/common/LogTypes.h b/source/common/LogTypes.h new file mode 100644 index 0000000..615904b --- /dev/null +++ b/source/common/LogTypes.h @@ -0,0 +1,519 @@ +/* + 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 . +*/ +#ifndef LOG_CATEGORY + #define LOG_CATEGORY(name) +#endif + +#ifndef LOG_TYPE + #define LOG_TYPE(category, type, level, color, enabled, logfile, console, client, str) +#endif + +#ifndef ENABLED + #define ENABLED true +#endif + +#ifndef DISABLED + #define DISABLED false +#endif + +#ifdef _WIN32 + #define FOREGROUND_WHITE (FOREGROUND_RED |FOREGROUND_GREEN | FOREGROUND_BLUE) + #define FOREGROUND_WHITE_BOLD (FOREGROUND_RED |FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY) + #define FOREGROUND_RED_BOLD (FOREGROUND_RED | FOREGROUND_INTENSITY) + #define FOREGROUND_GREEN_BOLD (FOREGROUND_GREEN | FOREGROUND_INTENSITY) + #define FOREGROUND_BLUE_BOLD (FOREGROUND_BLUE | FOREGROUND_INTENSITY) + #define FOREGROUND_YELLOW (FOREGROUND_RED | FOREGROUND_GREEN) + #define FOREGROUND_YELLOW_BOLD (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY) + #define FOREGROUND_CYAN (FOREGROUND_GREEN | FOREGROUND_BLUE) + #define FOREGROUND_CYAN_BOLD (FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY) + #define FOREGROUND_MAGENTA (FOREGROUND_RED | FOREGROUND_BLUE) + #define FOREGROUND_MAGENTA_BOLD (FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY) +#else + #define FOREGROUND_WHITE 37 + #define FOREGROUND_WHITE_BOLD 137 + #define FOREGROUND_RED 31 + #define FOREGROUND_RED_BOLD 131 + #define FOREGROUND_GREEN 32 + #define FOREGROUND_GREEN_BOLD 132 + #define FOREGROUND_BLUE 34 + #define FOREGROUND_BLUE_BOLD 134 + #define FOREGROUND_YELLOW 33 + #define FOREGROUND_YELLOW_BOLD 133 + #define FOREGROUND_CYAN 36 + #define FOREGROUND_CYAN_BOLD 136 + #define FOREGROUND_MAGENTA 35 + #define FOREGROUND_MAGENTA_BOLD 135 +#endif + + + +#define LOG_LOGFILE 1 +#define LOG_CONSOLE 2 +#define LOG_CLIENT 4 /* not yet using */ + +/* + Legend for str output (optional): + I : Information messages + W : Warning messages + E : Error messages + D : Debug messages + P : DumpPacket/PrintPacket messages - should NEVER go to Client channel!!! + T : Low-level debug tracing messages - should NEVER go to Client channel!!! + + Note: If str = null, output #catagory__#type to logs +*/ + + +/*** SYSTEM Loggers ******************************************************************************/ +// Logging specific to general events within the World code +LOG_CATEGORY(WORLD) +LOG_TYPE(WORLD, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") // Information messages (minimum output) +LOG_TYPE(WORLD, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") // Warning messages +LOG_TYPE(WORLD, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") // Error messages (should always be enabled) +LOG_TYPE(WORLD, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") // Debug messages (enabled during alpha dev) +LOG_TYPE(WORLD, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") // DumpPacket/PrintPacket messages +LOG_TYPE(WORLD, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") // Low-level debug tracing messages + +// LoginServer and MiniLogin events +LOG_CATEGORY(LOGIN) +LOG_TYPE(LOGIN, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(LOGIN, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(LOGIN, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(LOGIN, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(LOGIN, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(LOGIN, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// PatchServer (DB auto-patcher) events +LOG_CATEGORY(PATCHER) +LOG_TYPE(PATCHER, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(PATCHER, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(PATCHER, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(PATCHER, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(PATCHER, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(PATCHER, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// PacketParser events +LOG_CATEGORY(PARSER) +LOG_TYPE(PARSER, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(PARSER, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(PARSER, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(PARSER, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(PARSER, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(PARSER, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// World/Login/Patcher/Parser Initialization loggers +LOG_CATEGORY(INIT) +LOG_TYPE(INIT, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(INIT, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(INIT, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(INIT, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(INIT, LOGIN_INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(INIT, LOGIN_WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(INIT, LOGIN_ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(INIT, LOGIN_DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(INIT, PATCHER_INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(INIT, PATCHER_WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(INIT, PATCHER_ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(INIT, PATCHER_DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") + +// General DB logging +LOG_CATEGORY(DATABASE) +LOG_TYPE(DATABASE, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(DATABASE, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(DATABASE, QUERY, 0, FOREGROUND_CYAN, DISABLED, DISABLED, DISABLED, DISABLED, "Q") +LOG_TYPE(DATABASE, RESULT, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "R") +LOG_TYPE(DATABASE, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(DATABASE, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") + +// Logging Mutex code +LOG_CATEGORY(MUTEX) +LOG_TYPE(MUTEX, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(MUTEX, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(MUTEX, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(MUTEX, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(MUTEX, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(MUTEX, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// anything else... including a special DEV type "TODO" +LOG_CATEGORY(MISC) +LOG_TYPE(MISC, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(MISC, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(MISC, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(MISC, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(MISC, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(MISC, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") +LOG_TYPE(MISC, TODO, 0, FOREGROUND_YELLOW_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "M") + + + +/*** NETWORK Loggers *****************************************************************************/ +// Client Communications Logging +LOG_CATEGORY(CCLIENT) +LOG_TYPE(CCLIENT, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(CCLIENT, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(CCLIENT, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(CCLIENT, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(CCLIENT, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(CCLIENT, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Logging Net code +LOG_CATEGORY(NET) +LOG_TYPE(NET, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(NET, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(NET, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(NET, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(NET, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(NET, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Logging opcodes as they are encountered +LOG_CATEGORY(OPCODE) +LOG_TYPE(OPCODE, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(OPCODE, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(OPCODE, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(OPCODE, DEBUG, 0, FOREGROUND_MAGENTA_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(OPCODE, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(OPCODE, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Special category for dumping out excessive DumpPacket or Opcode debugging entries - All DISABLED by default! +LOG_CATEGORY(PACKET) +LOG_TYPE(PACKET, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(PACKET, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(PACKET, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(PACKET, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(PACKET, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(PACKET, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + + + +/*** PLAYER Loggers ******************************************************************************/ +// Events related to character progress +LOG_CATEGORY(PLAYER) +LOG_TYPE(PLAYER, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(PLAYER, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(PLAYER, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(PLAYER, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(PLAYER, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(PLAYER, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + + + +/*** SUBSYSTEM Loggers ***************************************************************************/ +// Achievements Logging +LOG_CATEGORY(ACHIEVEMENT) +LOG_TYPE(ACHIEVEMENT, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(ACHIEVEMENT, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(ACHIEVEMENT, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(ACHIEVEMENT, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(ACHIEVEMENT, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(ACHIEVEMENT, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Chat Logging +LOG_CATEGORY(CHAT) +LOG_TYPE(CHAT, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(CHAT, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(CHAT, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(CHAT, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(CHAT, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(CHAT, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Collection generated events +LOG_CATEGORY(COLLECTION) +LOG_TYPE(COLLECTION, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(COLLECTION, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(COLLECTION, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(COLLECTION, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(COLLECTION, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(COLLECTION, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Events related to combat,aggro, hate, melee, damages, etc. +LOG_CATEGORY(COMBAT) +LOG_TYPE(COMBAT, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(COMBAT, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(COMBAT, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(COMBAT, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(COMBAT, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(COMBAT, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Events related to commands (slash commands, UI commands, etc) +LOG_CATEGORY(COMMAND) +LOG_TYPE(COMMAND, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(COMMAND, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(COMMAND, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(COMMAND, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(COMMAND, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(COMMAND, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Faction-related events, adjustments, querying, etc. +LOG_CATEGORY(FACTION) +LOG_TYPE(FACTION, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(FACTION, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(FACTION, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(FACTION, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(FACTION, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(FACTION, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Guild events, members, logging, permissions, recruiting, etc. +LOG_CATEGORY(GUILD) +LOG_TYPE(GUILD, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(GUILD, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(GUILD, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(GUILD, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(GUILD, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(GUILD, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Group events, members, permissions, etc. +LOG_CATEGORY(GROUP) +LOG_TYPE(GROUP, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(GROUP, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(GROUP, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(GROUP, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(GROUP, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(GROUP, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Item events, stats, appearances, loading/reloading, etc. +LOG_CATEGORY(ITEM) +LOG_TYPE(ITEM, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(ITEM, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(ITEM, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(ITEM, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(ITEM, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(ITEM, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Loot events, loot lists, rules, smart loot +LOG_CATEGORY(LOOT) +LOG_TYPE(LOOT, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(LOOT, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(LOOT, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(LOOT, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(LOOT, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(LOOT, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Events that occur within the LUA subsystem +LOG_CATEGORY(LUA) +LOG_TYPE(LUA, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(LUA, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(LUA, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(LUA, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(LUA, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(LUA, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Merchant events, buy/sell/broker, faction merchants, etc. +LOG_CATEGORY(MERCHANT) +LOG_TYPE(MERCHANT, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(MERCHANT, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(MERCHANT, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(MERCHANT, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(MERCHANT, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(MERCHANT, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// NPC events, stats, appearances, movement, gear, abilities, etc. +LOG_CATEGORY(NPC) +LOG_TYPE(NPC, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(NPC, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(NPC, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(NPC, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(NPC, COMBAT, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(NPC, SPELLS, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(NPC, AI, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(NPC, DAMAGE, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(NPC, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(NPC, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// What is that NPC thinking?! ...etc. +LOG_CATEGORY(NPC_AI) +LOG_TYPE(NPC_AI, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(NPC_AI, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(NPC_AI, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(NPC_AI, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(NPC_AI, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(NPC_AI, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// PET events, stats, appearances, movement, gear, abilities, etc. +LOG_CATEGORY(PET) +LOG_TYPE(PET, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(PET, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(PET, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(PET, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(PET, COMBAT, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(PET, SPELLS, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(PET, AI, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(PET, DAMAGE, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(PET, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(PET, TRACE, 0, FOREGROUND_GREEN, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Quest generated events +LOG_CATEGORY(QUEST) +LOG_TYPE(QUEST, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(QUEST, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(QUEST, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(QUEST, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(QUEST, REWARD, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(QUEST, STEP, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(QUEST, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(QUEST, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Events relating to Recipes in the world +LOG_CATEGORY(RECIPE) +LOG_TYPE(RECIPE, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(RECIPE, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(RECIPE, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(RECIPE, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(RECIPE, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(RECIPE, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Events relating to Rules in the world +LOG_CATEGORY(RULESYS) +LOG_TYPE(RULESYS, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(RULESYS, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(RULESYS, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(RULESYS, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(RULESYS, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(RULESYS, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// The Skill system, books, scribing, stats, usage +LOG_CATEGORY(SKILL) +LOG_TYPE(SKILL, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(SKILL, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(SKILL, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(SKILL, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(SKILL, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(SKILL, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// The Spell system, books, scribing, stats, usage +LOG_CATEGORY(SPELL) +LOG_TYPE(SPELL, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(SPELL, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(SPELL, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(SPELL, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(SPELL, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(SPELL, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// The Crafting system, recipies, reactions, progress, etc. +LOG_CATEGORY(TRADESKILL) +LOG_TYPE(TRADESKILL, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(TRADESKILL, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(TRADESKILL, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(TRADESKILL, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(TRADESKILL, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(TRADESKILL, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// The Transportation system, teleporters, mounts, etc. +LOG_CATEGORY(TRANSPORT) +LOG_TYPE(TRANSPORT, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(TRANSPORT, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(TRANSPORT, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(TRANSPORT, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(TRANSPORT, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(TRANSPORT, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + + +/*** SPAWN Loggers *******************************************************************************/ +// General Spawn events, location, placement, grouping +LOG_CATEGORY(SPAWN) +LOG_TYPE(SPAWN, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(SPAWN, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(SPAWN, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(SPAWN, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(SPAWN, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(SPAWN, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Events relating to interactable objects in the world +LOG_CATEGORY(OBJECT) +LOG_TYPE(OBJECT, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(OBJECT, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(OBJECT, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(OBJECT, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(OBJECT, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(OBJECT, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Events relating to Signs in the world +LOG_CATEGORY(SIGN) +LOG_TYPE(SIGN, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(SIGN, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(SIGN, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(SIGN, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(SIGN, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(SIGN, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Events relating to Widgets in the world +LOG_CATEGORY(WIDGET) +LOG_TYPE(WIDGET, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(WIDGET, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(WIDGET, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(WIDGET, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(WIDGET, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(WIDGET, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Events relating to Groundspawns in the world +LOG_CATEGORY(GROUNDSPAWN) +LOG_TYPE(GROUNDSPAWN, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(GROUNDSPAWN, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(GROUNDSPAWN, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(GROUNDSPAWN, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(GROUNDSPAWN, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(GROUNDSPAWN, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + + + +/*** ZONE Loggers ********************************************************************************/ +// Zone-related events, status, messaging, access +LOG_CATEGORY(ZONE) +LOG_TYPE(ZONE, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(ZONE, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(ZONE, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(ZONE, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(ZONE, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(ZONE, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Instance loading/reloading, etc. +LOG_CATEGORY(INSTANCE) +LOG_TYPE(INSTANCE, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(INSTANCE, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(INSTANCE, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(INSTANCE, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(INSTANCE, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(INSTANCE, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + + +/*** MAP Loggers ********************************************************************************/ +// Map-related events, status, messaging, access +LOG_CATEGORY(MAP) +LOG_TYPE(MAP, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(MAP, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(MAP, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(MAP, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(MAP, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(MAP, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + + +/*** Region Map Loggers ********************************************************************************/ +// RegionMap-related events, status, messaging, access +LOG_CATEGORY(REGION) +LOG_TYPE(REGION, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(REGION, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(REGION, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(REGION, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(REGION, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(REGION, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +#undef LOG_TYPE +#undef LOG_CATEGORY +#undef ENABLED +#undef DISABLED diff --git a/source/common/MiscFunctions.cpp b/source/common/MiscFunctions.cpp new file mode 100644 index 0000000..f4d5bbf --- /dev/null +++ b/source/common/MiscFunctions.cpp @@ -0,0 +1,980 @@ +/* + 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 . +*/ +#include "../common/debug.h" +#include "../common/Log.h" +#include "MiscFunctions.h" +#include +#include +#include +#include + +#ifndef WIN32 +#include +#include +#endif +#include +#include +#ifdef WIN32 + #include +#endif +#include "../common/timer.h" +#include "../common/seperator.h" +#include "../common/packet_dump.h" +#include + +using namespace std; + +#ifndef PATCHER +extern map EQOpcodeVersions; +#endif + +#ifdef WIN32 + #include + #include + + #define snprintf _snprintf + #define vsnprintf _vsnprintf + #define strncasecmp _strnicmp + #define strcasecmp _stricmp +#else + #include + #include + #include + #include + #include +#ifdef FREEBSD //Timothy Whitman - January 7, 2003 + #include + #include + #endif + #include + #include + #include + #include +#endif + +void CoutTimestamp(bool ms) { + time_t rawtime; + struct tm* gmt_t; + time(&rawtime); + gmt_t = gmtime(&rawtime); + + struct timeval read_time; + gettimeofday(&read_time,0); + + cout << (gmt_t->tm_year + 1900) << "/" << setw(2) << setfill('0') << (gmt_t->tm_mon + 1) << "/" << setw(2) << setfill('0') << gmt_t->tm_mday << " " << setw(2) << setfill('0') << gmt_t->tm_hour << ":" << setw(2) << setfill('0') << gmt_t->tm_min << ":" << setw(2) << setfill('0') << gmt_t->tm_sec; + if (ms) + cout << "." << setw(3) << setfill('0') << (read_time.tv_usec / 1000); + cout << " GMT"; +} + +string loadInt32String(uchar* buffer, int16 buffer_size, int16* pos, EQ2_32BitString* eq_string){ + buffer += *pos; + int32 size = *(int32*)buffer; + if((size + *pos + sizeof(int16)) > buffer_size){ + cout << "Error in loadInt32String: Corrupt packet.\n"; + return string(""); + } + buffer += sizeof(int32); + string ret((char*)buffer, 0, size); + if(eq_string){ + eq_string->size = size; + eq_string->data = ret; + } + *pos += (size + sizeof(int32)); + return ret; +} +string loadInt16String(uchar* buffer, int16 buffer_size, int16* pos, EQ2_16BitString* eq_string){ + buffer += *pos; + int16 size = *(int16*)buffer; + if((size + *pos + sizeof(int16))> buffer_size){ + cout << "Error in loadInt16String: Corrupt packet.\n"; + return string(""); + } + buffer += sizeof(int16); + string ret((char*)buffer, 0, size); + if(eq_string){ + eq_string->size = size; + eq_string->data = ret; + } + *pos += (size + sizeof(int16)); + return ret; +} +string loadInt8String(uchar* buffer, int16 buffer_size, int16* pos, EQ2_8BitString* eq_string){ + buffer += *pos; + int8 size = *(int8*)buffer; + if((size + *pos + sizeof(int16)) > buffer_size){ + cout << "Error in loadInt8String: Corrupt packet.\n"; + return string(""); + } + buffer += sizeof(int8); + string ret((char*)buffer, 0, size); + if(eq_string){ + eq_string->size = size; + eq_string->data = ret; + } + *pos += (size + sizeof(int8)); + return ret; +} + +sint16 storeInt32String(uchar* buffer, int16 buffer_size, string in_str){ + sint16 string_size = in_str.length(); + if((string_size + sizeof(int32)) > buffer_size) + return -1; + memcpy(buffer, &string_size, sizeof(int32)); + buffer += sizeof(int32); + memcpy(buffer, in_str.c_str(), string_size); + buffer += string_size; + return (buffer_size - (string_size + sizeof(int32))); +} +sint16 storeInt16String(uchar* buffer, int16 buffer_size, string in_str){ + sint16 string_size = in_str.length(); + if((string_size + sizeof(int16)) > buffer_size) + return -1; + memcpy(buffer, &string_size, sizeof(int16)); + buffer += sizeof(int16); + memcpy(buffer, in_str.c_str(), string_size); + buffer += string_size; + return (buffer_size - (string_size + sizeof(int16))); +} +sint16 storeInt8String(uchar* buffer, int16 buffer_size, string in_str){ + sint16 string_size = in_str.length(); + if((string_size + sizeof(int8)) > buffer_size) + return -1; + memcpy(buffer, &string_size, sizeof(int8)); + buffer += sizeof(int8); + memcpy(buffer, in_str.c_str(), string_size); + buffer += string_size; + return (buffer_size - (string_size + sizeof(int8))); +} + + +sint32 filesize(FILE* fp) { +#ifdef WIN32 + return _filelength(_fileno(fp)); +#else + struct stat file_stat; + fstat(fileno(fp), &file_stat); + return (sint32) file_stat.st_size; +#endif +} + +int32 ResolveIP(const char* hostname, char* errbuf) { +#ifdef WIN32 + static InitWinsock ws; +#endif + if (errbuf) + errbuf[0] = 0; + if (hostname == 0) { + if (errbuf) + snprintf(errbuf, ERRBUF_SIZE, "ResolveIP(): hostname == 0"); + return 0; + } + struct sockaddr_in server_sin; +#ifdef WIN32 + PHOSTENT phostent = NULL; +#else + struct hostent *phostent = NULL; +#endif + server_sin.sin_family = AF_INET; + if ((phostent = gethostbyname(hostname)) == NULL) { +#ifdef WIN32 + if (errbuf) + snprintf(errbuf, ERRBUF_SIZE, "Unable to get the host name. Error: %i", WSAGetLastError()); +#else + if (errbuf) + snprintf(errbuf, ERRBUF_SIZE, "Unable to get the host name. Error: %s", strerror(errno)); +#endif + return 0; + } +#ifdef WIN32 + memcpy ((char FAR *)&(server_sin.sin_addr), phostent->h_addr, phostent->h_length); +#else + memcpy ((char*)&(server_sin.sin_addr), phostent->h_addr, phostent->h_length); +#endif + return server_sin.sin_addr.s_addr; +} + +#ifdef WIN32 +InitWinsock::InitWinsock() { + WORD version = MAKEWORD (1,1); + WSADATA wsadata; + WSAStartup (version, &wsadata); +} + +InitWinsock::~InitWinsock() { + WSACleanup(); +} + +#endif + +#ifndef WIN32 +const char * itoa(int value) { + static char temp[_ITOA_BUFLEN]; + memset(temp, 0, _ITOA_BUFLEN); + snprintf(temp, _ITOA_BUFLEN,"%d", value); + return temp; +} + + +char * itoa(int value, char *result, int base) { + char *ptr1, *ptr2; + char c; + int tmp_value; + + //need a valid base + if (base < 2 || base > 36) { + *result = '\0'; + return result; + } + + ptr1 = ptr2 = result; + do { + tmp_value = value; + value /= base; + + *ptr1++ = "zyxwvutsrqponmlkjihgfedcba9876543210123456789abcdefghijklmnopqrstuvwxyz" [35 + (tmp_value - value * base)]; + } + while (value > 0); + + //apply a negative sign if need be + if (tmp_value < 0) + *ptr1++ = '-'; + + *ptr1-- = '\0'; + while (ptr2 < ptr1) { + c = *ptr1; + *ptr1-- = *ptr2; + *ptr2++ = c; + } + + return result; +} +#endif + +/* + * solar: generate a random integer in the range low-high + * this should be used instead of the rand()%limit method + */ +int MakeRandomInt(int low, int high) +{ + return (int)MakeRandomFloat((double)low, (double)high + 0.999); +} +int32 hextoi(char* num) { + int len = strlen(num); + if (len < 3) + return 0; + + if (num[0] != '0' || (num[1] != 'x' && num[1] != 'X')) + return 0; + + int32 ret = 0; + int mul = 1; + for (int i=len-1; i>=2; i--) { + if (num[i] >= 'A' && num[i] <= 'F') + ret += ((num[i] - 'A') + 10) * mul; + else if (num[i] >= 'a' && num[i] <= 'f') + ret += ((num[i] - 'a') + 10) * mul; + else if (num[i] >= '0' && num[i] <= '9') + ret += (num[i] - '0') * mul; + else + return 0; + mul *= 16; + } + return ret; +} + +int64 hextoi64(char* num) { + int len = strlen(num); + if (len < 3) + return 0; + + if (num[0] != '0' || (num[1] != 'x' && num[1] != 'X')) + return 0; + + int64 ret = 0; + int mul = 1; + for (int i=len-1; i>=2; i--) { + if (num[i] >= 'A' && num[i] <= 'F') + ret += ((num[i] - 'A') + 10) * mul; + else if (num[i] >= 'a' && num[i] <= 'f') + ret += ((num[i] - 'a') + 10) * mul; + else if (num[i] >= '0' && num[i] <= '9') + ret += (num[i] - '0') * mul; + else + return 0; + mul *= 16; + } + return ret; +} + +float MakeRandomFloat(float low, float high) +{ +#ifdef _WIN32 + thread_local bool seeded = false; +#else + static bool seeded = false; +#endif + + float diff = high - low; + + if(!diff) return low; + if(diff < 0) + diff = 0 - diff; + + if(!seeded) + { + srand(time(0) * (time(0) % (int)diff)); + seeded = true; + } + + return (rand() / (float)RAND_MAX * diff + (low > high ? high : low)); +} + +int32 GenerateEQ2Color(float* r, float* g, float* b){ + int8 rgb[4] = {0}; + rgb[0] = (int8)((*r)*255); + rgb[1] = (int8)((*b)*255); + rgb[2] = (int8)((*g)*255); + int32 color = 0; + memcpy(&color, rgb, sizeof(int32)); + return color; +} +int32 GenerateEQ2Color(float* rgb[3]){ + return GenerateEQ2Color(rgb[0], rgb[1], rgb[2]); +} +int8 MakeInt8(float* input){ + float input2 = *input; + if(input2 < 0) + input2 *= -1; + return (int8)(input2*255); +} + +vector* SplitString(string str, char delim){ + vector* results = new vector; + int32 pos; + while((pos = str.find_first_of(delim))!= str.npos){ + if(pos > 0){ + results->push_back(str.substr(0,pos)); + } + if(str.length() > pos) + str = str.substr(pos+1); + else + break; + } + if(str.length() > 0) + results->push_back(str); + return results; +} + +bool Unpack(uchar* data, uchar* dst, int16 dstLen, int16 version, bool reverse){ + int32 srcLen = 0; + memcpy(&srcLen, data, sizeof(int32)); + return Unpack(srcLen, data + 4, dst, dstLen, version, reverse); +} +bool Unpack(int32 srcLen, uchar* data, uchar* dst, int16 dstLen, int16 version, bool reverse) { +// int32 srcLen = 0; +// memcpy(&srcLen, data, sizeof(int32)); +// data+=4; + if(reverse) + Reverse(data, srcLen); + int16 pos = 0; + int16 real_pos = 0; + while(srcLen && pos < dstLen) { + if(srcLen >= 0 && !srcLen--) + return false; + int8 code = data[real_pos++]; + + if(code >= 128) { + for(int8 index=0; index<7; index++) { + if(code & 1) { + if(pos >= dstLen) + return false; + if(srcLen >= 0 && !srcLen--) + return false; + dst[pos++] = data[real_pos++]; + } else { + if(pos < dstLen) dst[pos++] = 0; + } + code >>= 1; + } + } else { + if(pos + code > dstLen) + return false; + memset(dst+pos, 0, code); + pos+=code; + } + } + return srcLen <= 0; +} + +int32 Pack(uchar* data, uchar* src, int16 srcLen, int16 dstLen, int16 version, bool reverse) { + int16 real_pos = 4; + int32 pos = 0; + int32 code = 0; + int codePos = 0; + int codeLen = 0; + int8 zeroLen = 0; + memset(data,0,dstLen); + if (version > 1 && version <= 374) + reverse = false; + while(pos < srcLen) { + if(src[pos] || codeLen) { + if(!codeLen) { + /*if(zeroLen > 5) { + data[real_pos++] = zeroLen; + zeroLen = 0; + } + else if(zeroLen >= 1 && zeroLen<=5){ + for(;zeroLen>0;zeroLen--) + codeLen++; + }*/ + if (zeroLen) { + data[real_pos++] = zeroLen; + zeroLen = 0; + } + codePos = real_pos; + code = 0; + data[real_pos++] = 0; + } + if(src[pos]) { + data[real_pos++] = src[pos]; + code |= 0x80; + } + code >>= 1; + codeLen++; + + if(codeLen == 7) { + data[codePos] = int8(0x80 | code); + codeLen = 0; + } + } else { + if(zeroLen == 0x7F) { + data[real_pos++] = zeroLen; + zeroLen = 0; + } + zeroLen++; + } + pos++; + } + if(codeLen) { + code >>= (7 - codeLen); + data[codePos] = int8(0x80 | code); + } else if(zeroLen) { + data[real_pos++] = zeroLen; + } + if(reverse) + Reverse(data + 4, real_pos - 4); + int32 dataLen = real_pos - 4; + memcpy(&data[0], &dataLen, sizeof(int32)); + return dataLen + 4; +} +void Reverse(uchar* input, int32 srcLen){ + int16 real_pos = 0; + int16 orig_pos = 0; + int8 reverse_count = 0; + while(srcLen > 0 && srcLen < 0xFFFFFFFF){ // XXX it was >=0 before. but i think it was a bug + int8 code = input[real_pos++]; + srcLen--; + if(code >= 128) { + for(int8 index=0; index<7; index++) { + if(code & 1) { + if(srcLen >= 0 && !srcLen--) + return; + real_pos++; + reverse_count++; + } + code >>= 1; + } + } + if(reverse_count > 0){ + int8 tmp_data[8] = {0}; + for(int8 i=0;i 0){ + ret = atoul(input.c_str()); + } + } + catch(...){} + return ret; +} + +int64 ParseLongLongValue(string input){ + int64 ret = 0xFFFFFFFFFFFFFFFF; + try{ + if(input.length() > 0){ +#ifdef WIN32 + ret = _strtoui64(input.c_str(), NULL, 10); +#else + ret = strtoull(input.c_str(), 0, 10); +#endif + } + } + catch(...){} + return ret; +} + +map TranslateBrokerRequest(string request){ + map ret; + string key; + string value; + int32 start_pos = 0; + int32 end_pos = 0; + int32 pos = request.find("="); + bool str_val = false; + while(pos < 0xFFFFFFFF){ + str_val = false; + key = request.substr(start_pos, pos-start_pos); + if(request.find("|", pos) == pos+1){ + pos++; + end_pos = request.find("|", pos+1); + str_val = true; + } + else + end_pos = request.find(" ", pos); + if(end_pos < 0xFFFFFFFF){ + value = request.substr(pos+1, end_pos-pos-1); + start_pos = end_pos+1; + if(str_val){ + start_pos++; + ret[key] = ToLower(value); + } + else + ret[key] = value; + pos = request.find("=", start_pos); + } + else{ + value = request.substr(pos+1); + if(str_val){ + start_pos++; + ret[key] = ToLower(value); + } + else + ret[key] = value; + break; + } + } + return ret; +} + +int8 CheckOverLoadSize(int32 val){ + int8 ret = 1; + if(val >= 0xFFFF) //int32 + ret = sizeof(int16) + sizeof(int32); + else if(val >= 0xFF) + ret = sizeof(int8) + sizeof(int16); + return ret; +} + +int8 DoOverLoad(int32 val, uchar* data){ + int8 ret = 1; + if(val >= 0xFFFF){ //int32 + memset(data, 0xFF, sizeof(int16)); + memcpy(data + sizeof(int16), &val, sizeof(int32)); + ret = sizeof(int16) + sizeof(int32); + } + else if(val >= 0xFF){ //int16 + memset(data, 0xFF, sizeof(int8)); + memcpy(data + sizeof(int8), &val, sizeof(int16)); + ret = sizeof(int8) + sizeof(int16); + } + else + memcpy(data, &val, sizeof(int8)); + return ret; +} + +/* Treats contiguous spaces as one space. */ +int32 CountWordsInString(const char* text) { + int32 words = 0; + if (text && strlen(text) > 0) { + bool on_word = false; + for (int32 i = 0; i < strlen(text); i++) { + char letter = text[i]; + if (on_word && !((letter >= 48 && letter <= 57) || (letter >= 65 && letter <= 90) || (letter >= 97 && letter <= 122))) + on_word = false; + else if (!on_word && ((letter >= 48 && letter <= 57) || (letter >= 65 && letter <= 90) || (letter >= 97 && letter <= 122))){ + on_word = true; + words++; + } + } + } + return words; +} + +bool IsNumber(const char *num) { + size_t len, i; + + if (!num) + return false; + + len = strlen(num); + if (len == 0) + return false; + + for (i = 0; i < len; i++) { + if (!isdigit(num[i])) + return false; + } + + return true; +} + +void PrintSep(Seperator *sep, const char *name) { + int32 i = 0; + + LogWrite(MISC__DEBUG, 0, "Misc", "Printing sep %s", name ? name : "No Name"); + if (!sep) + LogWrite(MISC__DEBUG, 0, "Misc", "\tSep is null"); + else { + while (sep->arg[i] && strlen(sep->arg[i]) > 0) { + LogWrite(MISC__DEBUG, 0, "Misc", "\t%i => %s", i, sep->arg[i]); + i++; + } + } +} + +#define INI_IGNORE(c) (c == '\n' || c == '\r' || c == '#') + +static bool INIGoToSection(FILE *f, const char *section) { + size_t size = strlen(section) + 3; + char line[256], *buf, *tmp; + bool found = false; + + if ((buf = (char *)malloc(size)) == NULL) { + fprintf(stderr, "%s: %u: Unable to allocate %zu bytes\n", __FUNCTION__, __LINE__, size); + return false; + } + + sprintf(buf, "[%s]", section); + + while (fgets(line, sizeof(line), f) != NULL) { + if (INI_IGNORE(line[0])) + continue; + + if (line[0] == '[') { + if ((tmp = strstr(line, "\n")) != NULL) + *tmp = '\0'; + if ((tmp = strstr(line, "\r")) != NULL) + *tmp = '\0'; + + if (strcasecmp(buf, line) == 0) { + found = true; + break; + } + } + } + + free(buf); + return found; +} + +static char * INIFindValue(FILE *f, const char *section, const char *property) { + char line[256], *key, *val; + + if (section != NULL && !INIGoToSection(f, section)) + return NULL; + + while (fgets(line, sizeof(line), f) != NULL) { + if (INI_IGNORE(line[0])) + continue; + + if (section != NULL && line[0] == '[') + return NULL; + + if ((key = strtok(line, "=")) == NULL) + continue; + + if (strcasecmp(key, property) == 0) { + val = strtok(NULL, "\n\r"); + + if (val == NULL) + return NULL; + + return strdup(val); + } + } + + return NULL; +} + +bool INIReadInt(FILE *f, const char *section, const char *property, int *out) { + char *value; + + rewind(f); + + if ((value = INIFindValue(f, section, property)) == NULL) + return false; + + if (!IsNumber(value)) { + free(value); + return false; + } + + *out = atoi(value); + free(value); + + return true; +} + +bool INIReadBool(FILE *f, const char *section, const char *property, bool *out) { + char *value; + + rewind(f); + + if ((value = INIFindValue(f, section, property)) == NULL) + return false; + + *out = (strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0 || strcasecmp(value, "on") == 0 || strcasecmp(value, "yes") == 0); + free(value); + + return true; + +} +string GetDeviceName(string device) { + if (device == "chemistry_table") + device = "Chemistry Table"; + else if (device == "work_desk") + device = "Engraved Desk"; + else if (device == "forge") + device = "Forge"; + else if (device == "stove and keg") + device = "Stove & Keg"; + else if (device == "sewing_table") + device = "Sewing Table & Mannequin"; + else if (device == "woodworking_table") + device = "Woodworking Table"; + else if (device == "work_bench") + device = "Work Bench"; + else if (device == "crafting_intro_anvil") + device = "Mender's Anvil"; + return device; +} + +int32 GetDeviceID(string device) { + if (device == "Chemistry Table") + return 3; + else if (device == "Engraved Desk") + return 4; + else if (device == "Forge") + return 2; + else if (device == "Stove & Keg") + return 7; + else if (device == "Sewing Table & Mannequin") + return 1; + else if (device == "Woodworking Table") + return 6; + else if (device == "Work Bench") + return 5; + else if (device == "Mender's Anvil") + return 0xFFFFFFFF; + return 0; +} +int16 GetItemPacketType(int32 version) { + int16 item_version; + if (version >= 64707) + item_version = 0x5CFE; + else if (version >= 63119) + item_version = 0x56FE; + else if (version >= 60024) + item_version = 0x51FE; + else if (version >= 57107) + item_version = 0x4CFE; + else if (version >= 57048) + item_version = 0x48FE; + else if (version >= 1199) + item_version = 0x44FE; + else if (version >= 1195) + item_version = 0x40FE; + else if (version >= 1193) + item_version = 0x3FFE; + else if (version >= 1190) + item_version = 0x3EFE; + else if (version >= 1188) + item_version = 0x3DFE; + else if (version >= 1096) + item_version = 0x35FE; + else if (version >= 1027) + item_version = 0x31FE; + else if (version >= 1008) + item_version = 0x2CFE; + else if (version >= 927) + item_version = 0x23FE; + else if (version >= 893) + item_version = 0x22FE; + else if (version >= 860) + item_version = 0x20FE; + else if (version > 546) + item_version = 0x1CFE; + else + item_version = 0; + + return item_version; +} + +#ifndef PATCHER +int16 GetOpcodeVersion(int16 version) { + int16 ret = version; + int16 version1 = 0; + int16 version2 = 0; + map::iterator itr; + for (itr = EQOpcodeVersions.begin(); itr != EQOpcodeVersions.end(); itr++) { + version1 = itr->first; + version2 = itr->second; + if (version >= version1 && version <= version2) { + ret = version1; + break; + } + } + + return ret; +} +#endif + +void SleepMS(int32 milliseconds) { +#if defined(_WIN32) + Sleep(milliseconds); +#else + usleep(milliseconds * 1000); +#endif +} + +size_t +strlcpy(char *dst, const char *src, size_t size) { + char *d = dst; + const char *s = src; + size_t n = size; + + if (n != 0 && --n != 0) { + do { + if ((*d++ = *s++) == 0) + break; + } while (--n != 0); + } + + if (n == 0) { + if (size != 0) + *d = '\0'; + while (*s++) + ; + } + + return(s - src - 1); +} + +float short_to_float(const ushort x) { // IEEE-754 16-bit floating-point format (without infinity): 1-5-10, exp-15, +-131008.0, +-6.1035156E-5, +-5.9604645E-8, 3.311 digits + const uint32 e = (x & 0x7C00) >> 10; // exponent + const uint32 m = (x & 0x03FF) << 13; // mantissa + const uint32 v = as_uint((float)m) >> 23; // evil log2 bit hack to count leading zeros in denormalized format + return as_float((x & 0x8000) << 16 | (e != 0) * ((e + 112) << 23 | m) | ((e == 0) & (m != 0)) * ((v - 37) << 23 | ((m << (150 - v)) & 0x007FE000))); // sign : normalized : denormalized +} + +uint32 float_to_int(const float x) { // IEEE-754 16-bit floating-point format (without infinity): 1-5-10, exp-15, +-131008.0, +-6.1035156E-5, +-5.9604645E-8, 3.311 digits + const uint32 b = as_uint(x) + 0x00001000; // round-to-nearest-even: add last bit after truncated mantissa + const uint32 e = (b & 0x7F800000) >> 23; // exponent + const uint32 m = b & 0x007FFFFF; // mantissa; in line below: 0x007FF000 = 0x00800000-0x00001000 = decimal indicator flag - initial rounding + return (b & 0x80000000) >> 16 | (e > 112)* ((((e - 112) << 10) & 0x7C00) | m >> 13) | ((e < 113) & (e > 101))* ((((0x007FF000 + m) >> (125 - e)) + 1) >> 1) | (e > 143) * 0x7FFF; // sign : normalized : denormalized : saturate +} + +uint32 as_uint(const float x) { + return *(uint32*)&x; +} + +float as_float(const uint32 x) { + return *(float*)&x; +} + +// Function to get the current timestamp in milliseconds +int64 getCurrentTimestamp() { + auto now = std::chrono::steady_clock::now(); + auto duration = std::chrono::duration_cast(now.time_since_epoch()); + return duration.count(); +} + +std::tuple convertTimestampDuration(int64 total_seconds) { + std::chrono::milliseconds duration(total_seconds); + // Convert to days, hours, minutes, and seconds + auto days = std::chrono::duration_cast>>(duration); + duration -= days; + + auto hours = std::chrono::duration_cast(duration); + duration -= hours; + + auto minutes = std::chrono::duration_cast(duration); + duration -= minutes; + + auto seconds = std::chrono::duration_cast(duration); + + // Return the result as a tuple + return std::make_tuple(days.count(), hours.count(), minutes.count(), seconds.count()); +} \ No newline at end of file diff --git a/source/common/MiscFunctions.h b/source/common/MiscFunctions.h new file mode 100644 index 0000000..8a5db4f --- /dev/null +++ b/source/common/MiscFunctions.h @@ -0,0 +1,187 @@ +/* + 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 . +*/ +#ifndef MISCFUNCTIONS_H +#define MISCFUNCTIONS_H + +#include "types.h" +#include "seperator.h" +#include +#include +#include +#include +#include + +#ifndef ERRBUF_SIZE +#define ERRBUF_SIZE 1024 +#endif + +//int MakeAnyLenString(char** ret, const char* format, ...); +int32 hextoi(char* num); +int64 hextoi64(char* num); +sint32 filesize(FILE* fp); +int32 ResolveIP(const char* hostname, char* errbuf = 0); +void CoutTimestamp(bool ms = true); +//char* strn0cpy(char* dest, const char* source, int32 size); + // return value =true if entire string(source) fit, false if it was truncated +//bool strn0cpyt(char* dest, const char* source, int32 size); +string loadInt32String(uchar* buffer, int16 buffer_size, int16* pos, EQ2_32BitString* eq_string = NULL); +string loadInt16String(uchar* buffer, int16 buffer_size, int16* pos, EQ2_16BitString* eq_string = NULL); +string loadInt8String(uchar* buffer, int16 buffer_size, int16* pos, EQ2_8BitString* eq_string = NULL); +sint16 storeInt32String(uchar* buffer, int16 buffer_size, string in_str); +sint16 storeInt16String(uchar* buffer, int16 buffer_size, string in_str); +sint16 storeInt8String(uchar* buffer, int16 buffer_size, string in_str); +int MakeRandomInt(int low, int high); +float MakeRandomFloat(float low, float high); + +float TransformToFloat(sint16 data, int8 bits); +sint16 TransformFromFloat(float data, int8 bits); + +int32 GenerateEQ2Color(float r, float g, float b); +int32 GenerateEQ2Color(float* rgb[3]); +void SetColor(EQ2_Color* color, long data); +//void CreateEQ2Color(EQ2_Color* color, uchar* data, int16* size); +int8 MakeInt8(uchar* data, int16* size); +int8 MakeInt8(float* input); +bool Unpack(int32 srcLen, uchar* data, uchar* dst, int16 dstLen, int16 version = 0, bool reverse = true); +bool Unpack(uchar* data, uchar* dst, int16 dstLen, int16 version = 0, bool reverse = true); +int32 Pack(uchar* data, uchar* src, int16 srcLen, int16 dstLen, int16 version = 0, bool reverse = true); +void Reverse(uchar* input, int32 srcLen); +void Encode(uchar* dst, uchar* src, int16 len); +void Decode(uchar* dst, uchar* src, int16 len); +string ToUpper(string input); +string ToLower(string input); +int32 ParseIntValue(string input); +int64 ParseLongLongValue(string input); +map TranslateBrokerRequest(string request); +void MovementDecode(uchar* dst, uchar* newval, uchar* orig, int16 len); +vector* SplitString(string str, char delim); +int8 DoOverLoad(int32 val, uchar* data); +int8 CheckOverLoadSize(int32 val); +int32 CountWordsInString(const char* text); +bool IsNumber(const char *num); +void PrintSep(Seperator *sep, const char *name = 0); +string GetDeviceName(string device); +int32 GetDeviceID(string device); +///Gets the packet type for the given version +///The client version +int16 GetItemPacketType(int32 version); +///Gets the opcode version_range1 from the clients version +///The client version +int16 GetOpcodeVersion(int16 version); +void SleepMS(int32 milliseconds); +size_t strlcpy(char *dst, const char *src, size_t size); + +float short_to_float(const ushort x); +uint32 float_to_int(const float x); +uint32 as_uint(const float x); +float as_float(const uint32 x); + +int64 getCurrentTimestamp(); +std::tuple convertTimestampDuration(int64 total_seconds); + +bool INIReadBool(FILE *f, const char *section, const char *property, bool *out); +bool INIReadInt(FILE *f, const char *section, const char *property, int *out); + +static bool IsPrivateAddress(uint32_t ip) +{ + uint8_t b1, b2;//, b3, b4; + b1 = (uint8_t)(ip >> 24); + b2 = (uint8_t)((ip >> 16) & 0x0ff); + //b3 = (uint8_t)((ip >> 8) & 0x0ff); + //b4 = (uint8_t)(ip & 0x0ff); + + // 10.x.y.z + if (b1 == 10) + return true; + + // 172.16.0.0 - 172.31.255.255 + if ((b1 == 172) && (b2 >= 16) && (b2 <= 31)) + return true; + + // 192.168.0.0 - 192.168.255.255 + if ((b1 == 192) && (b2 == 168)) + return true; + + return false; +} + +template void AddData(Type input, string* datastring){ + if(datastring) + datastring->append((char*)&input, sizeof(input)); +} + +template void AddData(Type input, int32 array_size, string* datastring){ + if(array_size>0){ + for(int32 i=0;i class AutoDelete { +public: + AutoDelete(T** iVar, T* iSetTo = 0) { + init(iVar, iSetTo); + } + AutoDelete() {} + void init(T** iVar, T* iSetTo = 0) + { + pVar = iVar; + if (iSetTo) + *pVar = iSetTo; + } + ~AutoDelete() { + safe_delete(*pVar); + } +private: + T** pVar; +}; + +class VersionRange { +public: + VersionRange(int32 in_min_version, int32 in_max_version) + { + min_version = in_min_version; + max_version = in_max_version; + } + int32 GetMinVersion() { return min_version; } + int32 GetMaxVersion() { return max_version; } +private: + int32 min_version; + int32 max_version; +}; +#endif + + diff --git a/source/common/Mutex.cpp b/source/common/Mutex.cpp new file mode 100644 index 0000000..732e451 --- /dev/null +++ b/source/common/Mutex.cpp @@ -0,0 +1,361 @@ +/* + 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 . +*/ +#include "../common/Log.h" +#include "../common/debug.h" +#include "../common/Mutex.h" + +Mutex::Mutex() { + readers = 0; + mlocked = false; + writing = false; + name = ""; +#ifdef DEBUG + stack.clear(); +#endif + //CSLock is a pointer so we can use a different attribute type on create + CSLock = new CriticalSection(MUTEX_ATTRIBUTE_RECURSIVE); +} + +Mutex::~Mutex() { + safe_delete(CSLock); +#ifdef DEBUG + stack.clear(); +#endif +} + +void Mutex::SetName(string in_name) { +#ifdef DEBUG + name = in_name; +#endif +} + +void Mutex::lock() { +#ifdef DEBUG + int i = 0; +#endif + if (name.length() > 0) { + while (mlocked) { +#ifdef DEBUG + if (i > MUTEX_TIMEOUT_MILLISECONDS) { + LogWrite(MUTEX__ERROR, 0, "Mutex", "Possible deadlock attempt by '%s'!", name.c_str()); + return; + } + i++; +#endif + Sleep(1); + } + } + mlocked = true; + CSLock->lock(); +} + +bool Mutex::trylock() { + return CSLock->trylock(); +} + +void Mutex::unlock() { + CSLock->unlock(); + mlocked = false; +} + +void Mutex::readlock(const char* function, int32 line) { +#ifdef DEBUG + int32 i = 0; +#endif + while (true) { + //Loop until there isn't a writer, then we can read! + CSRead.lock(); + if (!writing) { + readers++; + CSRead.unlock(); +#ifdef DEBUG + CSStack.lock(); + if (function) + stack[(string)function]++; + CSStack.unlock(); +#endif + return; + } + CSRead.unlock(); +#ifdef DEBUG + if (i > MUTEX_TIMEOUT_MILLISECONDS) { + LogWrite(MUTEX__ERROR, 0, "Mutex", "The mutex %s called from %s at line %u timed out waiting for a readlock!", name.c_str(), function ? function : "name_not_provided", line); + LogWrite(MUTEX__ERROR, 0, "Mutex", "The following functions had locks:"); + map::iterator itr; + CSStack.lock(); + for (itr = stack.begin(); itr != stack.end(); itr++) { + if (itr->second > 0 && itr->first.length() > 0) + LogWrite(MUTEX__ERROR, 0, "Mutex", "%s, number of locks = %u", itr->first.c_str(), itr->second); + } + CSStack.unlock(); + i = 0; + continue; + } + i++; +#endif + Sleep(1); + } +} + +void Mutex::releasereadlock(const char* function, int32 line) { + //Wait for the readcount lock + CSRead.lock(); + //Lower the readcount by one, when readcount is 0 writers may start writing + readers--; + CSRead.unlock(); +#ifdef DEBUG + CSStack.lock(); + if (function) { + map::iterator itr = stack.find((string)function); + if (itr != stack.end()) { + if (--(itr->second) == 0) { + stack.erase(itr); + } + } + } + CSStack.unlock(); +#endif +} + +bool Mutex::tryreadlock(const char* function) { + //This returns true if able to instantly obtain a readlock, false if not + CSRead.lock(); + if (!writing) { + readers++; + CSRead.unlock(); + } + else { + CSRead.unlock(); + return false; + } + +#ifdef DEBUG + CSStack.lock(); + if (function) + stack[(string)function]++; + CSStack.unlock(); +#endif + + return true; +} + +void Mutex::writelock(const char* function, int32 line) { + //Wait until the writer lock becomes available, then we can be the only writer! +#ifdef DEBUG + int32 i = 0; +#endif + while (!CSWrite.trylock()) { +#ifdef DEBUG + if (i > MUTEX_TIMEOUT_MILLISECONDS) { + LogWrite(MUTEX__ERROR, 0, "Mutex", "The mutex %s called from %s at line %u timed out waiting on another writelock!", name.c_str(), function ? function : "name_not_provided", line); + LogWrite(MUTEX__ERROR, 0, "Mutex", "The following functions had locks:"); + map::iterator itr; + CSStack.lock(); + for (itr = stack.begin(); itr != stack.end(); itr++) { + if (itr->second > 0 && itr->first.length() > 0) + LogWrite(MUTEX__ERROR, 0, "Mutex", "%s, number of locks = %u", itr->first.c_str(), itr->second); + } + CSStack.unlock(); + i = 0; + continue; + } + i++; +#endif + Sleep(1); + } + waitReaders(function, line); +#ifdef DEBUG + CSStack.lock(); + if (function) + stack[(string)function]++; + CSStack.unlock(); +#endif +} + +void Mutex::releasewritelock(const char* function, int32 line) { + //Wait for the readcount lock + CSRead.lock(); + //Readers are aloud again + writing = false; + CSRead.unlock(); + //Allow other writers to write + CSWrite.unlock(); +#ifdef DEBUG + CSStack.lock(); + if (function) { + map::iterator itr = stack.find((string)function); + if (itr != stack.end()) { + if (--(itr->second) == 0) { + stack.erase(itr); + } + } + } + CSStack.unlock(); +#endif +} + +bool Mutex::trywritelock(const char* function) { + //This returns true if able to instantly obtain a writelock, false if not + if (CSWrite.trylock()) { + CSRead.lock(); + if (readers == 0) + writing = true; + CSRead.unlock(); + if (!writing) { + CSWrite.unlock(); + return false; + } + } + else + return false; + +#ifdef DEBUG + CSStack.lock(); + if (function) + stack[(string)function]++; + CSStack.unlock(); +#endif + + return true; +} + +void Mutex::waitReaders(const char* function, int32 line) +{ + //Wait for all current readers to stop, then we can write! +#ifdef DEBUG + int32 i = 0; +#endif + while (true) + { + CSRead.lock(); + if (readers == 0) + { + writing = true; + CSRead.unlock(); + break; + } + CSRead.unlock(); +#ifdef DEBUG + if (i > MUTEX_TIMEOUT_MILLISECONDS) { + LogWrite(MUTEX__ERROR, 0, "Mutex", "The mutex %s called from %s at line %u timed out while waiting on readers!", name.c_str(), function ? function : "name_not_provided", line); + LogWrite(MUTEX__ERROR, 0, "Mutex", "The following functions had locks:"); + map::iterator itr; + CSStack.lock(); + for (itr = stack.begin(); itr != stack.end(); itr++) { + if (itr->second > 0 && itr->first.length() > 0) + LogWrite(MUTEX__ERROR, 0, "Mutex", "%s, number of locks = %u", itr->first.c_str(), itr->second); + } + CSStack.unlock(); + i = 0; + continue; + } + i++; +#endif + Sleep(1); + } +} + +LockMutex::LockMutex(Mutex* in_mut, bool iLock) { + mut = in_mut; + locked = iLock; + if (locked) { + mut->lock(); + } +} + +LockMutex::~LockMutex() { + if (locked) { + mut->unlock(); + } +} + +void LockMutex::unlock() { + if (locked) + mut->unlock(); + locked = false; +} + +void LockMutex::lock() { + if (!locked) + mut->lock(); + locked = true; +} + +CriticalSection::CriticalSection(int attribute) { +#ifdef WIN32 + InitializeCriticalSection(&CSMutex); +#else + pthread_mutexattr_init(&type_attribute); + switch (attribute) + { + case MUTEX_ATTRIBUTE_FAST: + pthread_mutexattr_settype(&type_attribute, PTHREAD_MUTEX_FAST_NP); + break; + case MUTEX_ATTRIBUTE_RECURSIVE: + pthread_mutexattr_settype(&type_attribute, PTHREAD_MUTEX_RECURSIVE_NP); + break; + case MUTEX_ATTRIBUTE_ERRORCHK: + pthread_mutexattr_settype(&type_attribute, PTHREAD_MUTEX_ERRORCHECK_NP); + break; + default: + LogWrite(MUTEX__DEBUG, 0, "Critical Section", "Invalid mutex attribute type! Using PTHREAD_MUTEX_FAST_NP"); + pthread_mutexattr_settype(&type_attribute, PTHREAD_MUTEX_FAST_NP); + break; + } + pthread_mutex_init(&CSMutex, &type_attribute); +#endif +} + +CriticalSection::~CriticalSection() { +#ifdef WIN32 + DeleteCriticalSection(&CSMutex); +#else + pthread_mutex_destroy(&CSMutex); + pthread_mutexattr_destroy(&type_attribute); +#endif +} + +void CriticalSection::lock() { + //Waits for a lock on this critical section +#ifdef WIN32 + EnterCriticalSection(&CSMutex); +#else + pthread_mutex_lock(&CSMutex); +#endif +} + +void CriticalSection::unlock() { + //Gets rid of one of the current thread's locks on this critical section +#ifdef WIN32 + LeaveCriticalSection(&CSMutex); +#else + pthread_mutex_unlock(&CSMutex); +#endif +} + +bool CriticalSection::trylock() { + //Returns true if able to instantly get a lock on this critical section, false if not +#ifdef WIN32 + return TryEnterCriticalSection(&CSMutex); +#else + return (pthread_mutex_trylock(&CSMutex) == 0); +#endif +} + diff --git a/source/common/Mutex.h b/source/common/Mutex.h new file mode 100644 index 0000000..ae96333 --- /dev/null +++ b/source/common/Mutex.h @@ -0,0 +1,103 @@ +/* + 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 . +*/ +#ifndef MYMUTEX_H +#define MYMUTEX_H +#ifdef WIN32 + #include + #include +#else + #include + #include "../common/unix.h" +#endif +#include "../common/types.h" +#include +#include + +#define MUTEX_ATTRIBUTE_FAST 1 +#define MUTEX_ATTRIBUTE_RECURSIVE 2 +#define MUTEX_ATTRIBUTE_ERRORCHK 3 +#define MUTEX_TIMEOUT_MILLISECONDS 10000 + +class CriticalSection { +public: + CriticalSection(int attribute = MUTEX_ATTRIBUTE_FAST); + ~CriticalSection(); + void lock(); + void unlock(); + bool trylock(); +private: +#ifdef WIN32 + CRITICAL_SECTION CSMutex; +#else + pthread_mutex_t CSMutex; + pthread_mutexattr_t type_attribute; +#endif +}; + +class Mutex { +public: + Mutex(); + ~Mutex(); + + void lock(); + void unlock(); + bool trylock(); + + void readlock(const char* function = 0, int32 line = 0); + void releasereadlock(const char* function = 0, int32 line = 0); + bool tryreadlock(const char* function = 0); + + void writelock(const char* function = 0, int32 line = 0); + void releasewritelock(const char* function = 0, int32 line = 0); + bool trywritelock(const char* function = 0); + + void waitReaders(const char* function = 0, int32 line = 0); + + void SetName(string in_name); +private: + CriticalSection CSRead; + CriticalSection CSWrite; + CriticalSection* CSLock; + +#ifdef DEBUG //Used for debugging only + CriticalSection CSStack; + map stack; +#endif + + int readers; + bool writing; + volatile bool mlocked; + string name; +}; + + +class LockMutex { +public: + LockMutex(Mutex* in_mut, bool iLock = true); + ~LockMutex(); + void unlock(); + void lock(); +private: + bool locked; + Mutex* mut; +}; + + +#endif diff --git a/source/common/PacketStruct.cpp b/source/common/PacketStruct.cpp new file mode 100644 index 0000000..4de8120 --- /dev/null +++ b/source/common/PacketStruct.cpp @@ -0,0 +1,2738 @@ +/* + 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 . +*/ +#include +#include +#include "PacketStruct.h" +#include "ConfigReader.h" +#include "../common/debug.h" +#include "MiscFunctions.h" +#include "Log.h" + +#ifdef WORLD +#include "../WorldServer/Items/Items.h" +#include "../WorldServer/Player.h" +#include "../WorldServer/World.h" +#endif + +extern ConfigReader configReader; +using namespace std; + +DataStruct::DataStruct() { + item_size = 0; + type = 0; + type2 = 0; + length = 1; + if_flag_set = false; + if_flag_not_set = false; + if_set = false; + if_not_set = false; + if_not_equals = false; + if_equals = false; + is_set = false; + optional = false; + oversized = 0; + oversized_byte = 0; + add = false; + addType = 0; + maxArraySize = 0; + default_value = 0; +} +DataStruct::DataStruct(DataStruct* data_struct) { + type = data_struct->GetType(); + type2 = data_struct->GetType2(); + length = data_struct->GetLength(); + name = data_struct->GetName(); + array_size_variable = data_struct->array_size_variable; + default_value = data_struct->default_value; + add = true; + addType = type; + oversized = data_struct->GetOversized(); + oversized_byte = data_struct->GetOversizedByte(); + maxArraySize = data_struct->GetMaxArraySize(); + if_flag_set = false; + if_flag_not_set = false; + if_set = false; + if_not_set = false; + if_not_equals = false; + if_equals = false; + optional = false; + if (data_struct->GetIfSet()) + SetIfSetVariable(data_struct->GetIfSetVariable()); + if (data_struct->GetIfNotSet()) + SetIfNotSetVariable(data_struct->GetIfNotSetVariable()); + if (data_struct->GetIfNotEquals()) + SetIfNotEqualsVariable(data_struct->GetIfNotEqualsVariable()); + if (data_struct->GetIfEquals()) + SetIfEqualsVariable(data_struct->GetIfEqualsVariable()); + if (data_struct->GetIfFlagSet()) + SetIfFlagSetVariable(data_struct->GetIfFlagSetVariable()); + if (data_struct->GetIfFlagNotSet()) + SetIfFlagNotSetVariable(data_struct->GetIfFlagNotSetVariable()); + item_size = 0; + is_set = false; +} +DataStruct::DataStruct(const char* new_name, const char* new_type, int32 new_length, const char* new_type2) { + name = string(new_name); + type = 0; + type2 = 0; + SetType(new_type, &type); + if (new_type2) + SetType(new_type2, &type2); + length = new_length; + add = true; + addType = type; + if_set = false; + if_not_set = false; + is_set = false; + if_not_equals = false; + item_size = 0; +} +const char* DataStruct::GetArraySizeVariable() { + return array_size_variable.c_str(); +} +void DataStruct::SetArraySizeVariable(const char* new_name) { + array_size_variable = string(new_name); +} +void DataStruct::SetType(const char* new_type, int8* output_type) { + if (strlen(new_type) > 3 && strncasecmp("int", new_type, 3) == 0) { + if (strncasecmp("int8", new_type, 4) == 0) + *output_type = DATA_STRUCT_INT8; + else if (strncasecmp("int16", new_type, 5) == 0) + *output_type = DATA_STRUCT_INT16; + else if (strncasecmp("int32", new_type, 5) == 0) + *output_type = DATA_STRUCT_INT32; + else if (strncasecmp("int64", new_type, 5) == 0) + *output_type = DATA_STRUCT_INT64; + } + else if (strlen(new_type) > 4 && strncasecmp("sint", new_type, 4) == 0) { + if (strncasecmp("sint8", new_type, 5) == 0) + *output_type = DATA_STRUCT_SINT8; + else if (strncasecmp("sint16", new_type, 6) == 0) + *output_type = DATA_STRUCT_SINT16; + else if (strncasecmp("sint32", new_type, 6) == 0) + *output_type = DATA_STRUCT_SINT32; + else if (strncasecmp("sint64", new_type, 6) == 0) + *output_type = DATA_STRUCT_SINT64; + } + else if (strlen(new_type) == 4 && strncasecmp("char", new_type, 4) == 0) + *output_type = DATA_STRUCT_CHAR; + else if (strlen(new_type) == 5 && strncasecmp("float", new_type, 5) == 0) + *output_type = DATA_STRUCT_FLOAT; + else if (strlen(new_type) == 6 && strncasecmp("double", new_type, 6) == 0) + *output_type = DATA_STRUCT_DOUBLE; + else if (strlen(new_type) >= 5 && strncasecmp("EQ2_", new_type, 4) == 0) { + if (strncasecmp("EQ2_8", new_type, 5) == 0) + *output_type = DATA_STRUCT_EQ2_8BIT_STRING; + else if (strncasecmp("EQ2_16", new_type, 6) == 0) + *output_type = DATA_STRUCT_EQ2_16BIT_STRING; + else if (strncasecmp("EQ2_32", new_type, 6) == 0) + *output_type = DATA_STRUCT_EQ2_32BIT_STRING; + else if (strncasecmp("EQ2_E", new_type, 5) == 0) + *output_type = DATA_STRUCT_EQUIPMENT; + else if (strncasecmp("EQ2_C", new_type, 5) == 0) + *output_type = DATA_STRUCT_COLOR; + else if (strncasecmp("EQ2_I", new_type, 5) == 0) + *output_type = DATA_STRUCT_ITEM; + } + else if (strlen(new_type) >= 5) { + if (strncasecmp("Array", new_type, 5) == 0) + *output_type = DATA_STRUCT_ARRAY; + } + else + LogWrite(PACKET__ERROR, 0, "Packet", "Invalid Type: %s", new_type); +} +DataStruct::DataStruct(const char* new_name, int32 new_length) { + name = string(new_name); + length = new_length; + if_set = false; + if_not_set = false; + is_set = false; + item_size = 0; +} +DataStruct::DataStruct(const char* new_name, int8 new_type, int32 new_length, int8 new_type2) { + name = string(new_name); + type = new_type; + length = new_length; + type2 = new_type2; + addType = type; + if_set = false; + if_not_set = false; + is_set = false; + item_size = 0; +} +void DataStruct::SetType(int8 new_type) { + type = new_type; + addType = type; +} +void DataStruct::SetMaxArraySize(int8 size) { + maxArraySize = size; +} +void DataStruct::SetOversized(int8 val) { + oversized = val; +} +void DataStruct::SetDefaultValue(int8 new_val) { + default_value = new_val; +} +void DataStruct::SetName(const char* new_name) { + name = string(new_name); +} +void DataStruct::SetLength(int32 new_length) { + length = new_length; +} +void DataStruct::SetOversizedByte(int8 val) { + oversized_byte = val; +} +void DataStruct::SetItemSize(int32 val) { + item_size = val; + if(item_size) + is_set = true; + else + is_set = false; +} +void DataStruct::SetIfEqualsVariable(const char* variable) { + if (variable) { + if_equals = true; + if_equals_variable = string(variable); + } + else + if_equals = false; +} +void DataStruct::SetIfNotEqualsVariable(const char* variable) { + if (variable) { + if_not_equals = true; + if_not_equals_variable = string(variable); + } + else + if_not_equals = false; +} +void DataStruct::SetIfFlagNotSetVariable(const char* variable) { + if (variable) { + if_flag_not_set = true; + if_flag_not_set_variable = string(variable); + } + else + if_flag_not_set = false; +} +void DataStruct::SetIfFlagSetVariable(const char* variable) { + if (variable) { + if_flag_set = true; + if_flag_set_variable = string(variable); + } + else + if_flag_set = false; +} +void DataStruct::SetIfSetVariable(const char* variable) { + if (variable) { + if_set = true; + if_set_variable = string(variable); + } + else + if_set = false; +} +void DataStruct::SetIfNotSetVariable(const char* variable) { + if (variable) { + if_not_set = true; + if_not_set_variable = string(variable); + } + else + if_not_set = false; +} +void DataStruct::SetIsSet(bool val) { + is_set = val; +} +bool DataStruct::IsSet() { + return is_set; +} +void DataStruct::SetIsOptional(bool val) { + optional = val; +} +bool DataStruct::IsOptional() { + return optional; +} +int32 DataStruct::GetItemSize() { + return item_size; +} +bool DataStruct::GetIfSet() { + return if_set; +} +const char* DataStruct::GetIfSetVariable() { + if (if_set_variable.length() > 0) + return if_set_variable.c_str(); + return 0; +} +bool DataStruct::GetIfNotSet() { + return if_not_set; +} +const char* DataStruct::GetIfNotSetVariable() { + if (if_not_set_variable.length() > 0) + return if_not_set_variable.c_str(); + return 0; +} +bool DataStruct::GetIfEquals() { + return if_equals; +} +const char* DataStruct::GetIfEqualsVariable() { + if (if_equals_variable.length() > 0) + return if_equals_variable.c_str(); + return 0; +} +bool DataStruct::GetIfNotEquals() { + return if_not_equals; +} +const char* DataStruct::GetIfNotEqualsVariable() { + if (if_not_equals_variable.length() > 0) + return if_not_equals_variable.c_str(); + return 0; +} +bool DataStruct::GetIfFlagSet() { + return if_flag_set; +} +const char* DataStruct::GetIfFlagSetVariable() { + if (if_flag_set_variable.length() > 0) + return if_flag_set_variable.c_str(); + return 0; +} +bool DataStruct::GetIfFlagNotSet() { + return if_flag_not_set; +} +const char* DataStruct::GetIfFlagNotSetVariable() { + if (if_flag_not_set_variable.length() > 0) + return if_flag_not_set_variable.c_str(); + return 0; +} +int8 DataStruct::GetDefaultValue() { + return default_value; +} +int8 DataStruct::GetType() { + return type; +} +int8 DataStruct::GetType2() { + return type2; +} +const char* DataStruct::GetName() { + return name.c_str(); +} +int8 DataStruct::GetOversized() { + return oversized; +} +int8 DataStruct::GetOversizedByte() { + return oversized_byte; +} +int8 DataStruct::GetMaxArraySize() { + return maxArraySize; +} +int32 DataStruct::GetLength() { + return length; +} +string DataStruct::GetStringName() { + return name; +} +bool DataStruct::AddToStruct() { + return add; +} +void DataStruct::SetAddToStruct(bool val) { + add = val; +} +int8 DataStruct::GetAddType() { + return addType; +} +void DataStruct::SetAddType(int8 new_type) { + addType = new_type; +} +string DataStruct::AppendVariable(string orig, const char* val) { + if (!val) + return orig; + if(orig.length() == 0) + return string(val); + if (orig.find(",") < 0xFFFFFFFF) { //has more than one already + string valstr = string(val); + vector* varnames = SplitString(orig, ','); + if (varnames) { + for (int32 i = 0; i < varnames->size(); i++) { + if (valstr.compare(varnames->at(i)) == 0) { + return orig; //already in the variable, no need to append + } + } + safe_delete(varnames); + } + } + return orig.append(",").append(val); +} +int32 DataStruct::GetDataSizeInBytes() { + int32 ret = 0; + switch (type) { + case DATA_STRUCT_INT8: + ret = length * sizeof(int8); + break; + case DATA_STRUCT_INT16: + ret = length * sizeof(int16); + break; + case DATA_STRUCT_INT32: + ret = length * sizeof(int32); + break; + case DATA_STRUCT_INT64: + ret = length * sizeof(int64); + break; + case DATA_STRUCT_SINT8: + ret = length * sizeof(sint8); + break; + case DATA_STRUCT_SINT16: + ret = length * sizeof(sint16); + break; + case DATA_STRUCT_SINT32: + ret = length * sizeof(sint32); + break; + case DATA_STRUCT_SINT64: + ret = length * sizeof(sint64); + break; + case DATA_STRUCT_FLOAT: + ret = length * sizeof(float); + break; + case DATA_STRUCT_DOUBLE: + ret = length * sizeof(double); + break; + case DATA_STRUCT_ARRAY: + // Array elements won't have a size so get out now to avoid the warning. + break; + default: + LogWrite(PACKET__WARNING, 0, "DataStruct", "Tried retrieving a data size from an unsupported data struct type in GetDataSizeInBytes()"); + break; + } + return ret; +} + +PacketStruct::PacketStruct(PacketStruct* packet, int16 in_client_version) { + parent = packet->parent; + client_version = in_client_version; + vector::iterator itr2; + name = packet->name; + + for (itr2 = packet->structs.begin(); itr2 != packet->structs.end(); itr2++) { + add(new DataStruct(*itr2)); + } + vector::iterator itr; + for (itr = packet->flags.begin(); itr != packet->flags.end(); itr++) { + AddFlag((*itr).c_str()); + } + sub_packet = false; + opcode = packet->opcode; + version = packet->version; + opcode_type = packet->opcode_type; + sub_packet_size = 1; + + + addPacketArrays(packet); +} + +PacketStruct::PacketStruct() { + parent = 0; + opcode = OP_Unknown; + opcode_type = string(""); +} + +PacketStruct::PacketStruct(PacketStruct* packet, bool sub) { + vector::iterator itr2; + + for (itr2 = packet->structs.begin(); itr2 != packet->structs.end(); itr2++) { + add(new DataStruct(*itr2)); + } + vector::iterator itr; + for (itr = packet->flags.begin(); itr != packet->flags.end(); itr++) { + AddFlag((*itr).c_str()); + } + sub_packet = sub; + opcode = packet->opcode; + version = packet->version; + opcode_type = packet->opcode_type; + name = packet->name; + sub_packet_size = 0; + parent = 0; +} +PacketStruct::~PacketStruct() { + deleteDataStructs(&structs); + deleteDataStructs(&orig_structs); + deletePacketArrays(this); + struct_map.clear(); + struct_data.clear(); + flags.clear(); +} + +void PacketStruct::deleteDataStructs(vector* data_structs) { + if ( !data_structs || data_structs->size() == 0 ) + return; + + DataStruct* data = 0; + vector::iterator itr; + for (itr = data_structs->begin(); itr != data_structs->end(); itr++) { + data = *itr; + void* ptr = GetStructPointer(data); + + // stop the struct_data from growing with old data/ptr info, memory leaking and eventual buffer overflow (crash) + map::iterator datastr = struct_data.find(data); + if (datastr != struct_data.end()) + struct_data.erase(datastr); + + switch (data->GetType()) { + case DATA_STRUCT_EQ2_8BIT_STRING: { + EQ2_8BitString* real_ptr = (EQ2_8BitString*)ptr; + safe_delete(real_ptr); + break; + } + case DATA_STRUCT_EQ2_16BIT_STRING: { + EQ2_16BitString* real_ptr = (EQ2_16BitString*)ptr; + safe_delete(real_ptr); + break; + } + case DATA_STRUCT_EQ2_32BIT_STRING: { + EQ2_32BitString* real_ptr = (EQ2_32BitString*)ptr; + safe_delete(real_ptr); + break; + } + case DATA_STRUCT_EQUIPMENT: { + EQ2_EquipmentItem* real_ptr = (EQ2_EquipmentItem*)ptr; + safe_delete_array(real_ptr); + break; + } + case DATA_STRUCT_DOUBLE: { + double* real_ptr = (double*)ptr; + safe_delete_array(real_ptr); + break; + } + case DATA_STRUCT_FLOAT: { + float* real_ptr = (float*)ptr; + safe_delete_array(real_ptr); + break; + } + case DATA_STRUCT_INT8: { + int8* real_ptr = (int8*)ptr; + safe_delete_array(real_ptr); + break; + } + case DATA_STRUCT_INT16: { + int16* real_ptr = (int16*)ptr; + safe_delete_array(real_ptr); + break; + } + case DATA_STRUCT_INT32: { + int32* real_ptr = (int32*)ptr; + safe_delete_array(real_ptr); + break; + } + case DATA_STRUCT_INT64: { + int64* real_ptr = (int64*)ptr; + safe_delete_array(real_ptr); + break; + } + case DATA_STRUCT_SINT8: { + sint8* real_ptr = (sint8*)ptr; + safe_delete_array(real_ptr); + break; + } + case DATA_STRUCT_SINT16: { + sint16* real_ptr = (sint16*)ptr; + safe_delete_array(real_ptr); + break; + } + case DATA_STRUCT_SINT32: { + sint32* real_ptr = (sint32*)ptr; + safe_delete_array(real_ptr); + break; + } + case DATA_STRUCT_SINT64: { + sint64* real_ptr = (sint64*)ptr; + safe_delete_array(real_ptr); + break; + } + case DATA_STRUCT_ITEM: { + uchar* real_ptr = (uchar*)ptr; + safe_delete_array(real_ptr); + break; + } + case DATA_STRUCT_CHAR: { + char* real_ptr = (char*)ptr; + safe_delete_array(real_ptr); + break; + } + case DATA_STRUCT_COLOR: { + EQ2_Color* real_ptr = (EQ2_Color*)ptr; + safe_delete_array(real_ptr); + break; + } + } + ptr = 0; + safe_delete(data); + } +} +void PacketStruct::deletePacketArrays(PacketStruct* packet) { + if (!packet) + return; + vector::iterator itr; + + for (itr = packet->arrays.begin(); itr != packet->arrays.end(); itr++) + safe_delete(*itr); + packet->arrays.clear(); + + for (itr = packet->orig_packets.begin(); itr != packet->orig_packets.end(); itr++) + safe_delete(*itr); + packet->orig_packets.clear(); +} + +void PacketStruct::renameSubstructArray(const char* substruct, int32 index) { + vector::iterator itr; + char tmp[10] = { 0 }; + sprintf(tmp, "%i", index); + for (itr = arrays.begin(); itr != arrays.end(); itr++) { + (*itr)->SetName(string(substruct).append("_").append((*itr)->GetName()).append("_").append(tmp).c_str()); + } +} + +void PacketStruct::addPacketArrays(PacketStruct* packet) { + if (!packet) + return; + vector::iterator itr; + + for (itr = packet->arrays.begin(); itr != packet->arrays.end(); itr++) { + PacketStruct* tmp = new PacketStruct(*itr, true); + tmp->addPacketArrays(*itr); + add(tmp); + } +} + +bool PacketStruct::IsStringValueType(string in_name, int32 index) { + DataStruct* data = findStruct(in_name.c_str(), index); + switch (data->GetType()) { + case DATA_STRUCT_CHAR: + case DATA_STRUCT_EQ2_8BIT_STRING: + case DATA_STRUCT_EQ2_16BIT_STRING: + case DATA_STRUCT_EQ2_32BIT_STRING: + return true; + } + return false; +} + +bool PacketStruct::IsColorValueType(string in_name, int32 index) { + DataStruct* data = findStruct(in_name.c_str(), index); + if (data->GetType() == DATA_STRUCT_COLOR) + return true; + else + return false; +} +void PacketStruct::setColor(DataStruct* data, int8 red, int8 green, int8 blue, int32 index = 0) { + if (data && data->GetType() == DATA_STRUCT_COLOR) { + EQ2_Color* color = (EQ2_Color*)GetStructPointer(data); + color[index].blue = blue; + color[index].red = red; + color[index].green = green; + if (blue > 0 || green > 0 || red > 0) + data->SetIsSet(true); + } +} +void PacketStruct::setEquipment(DataStruct* data, int16 type, int8 c_red, int8 c_blue, int8 c_green, int8 h_red, int8 h_blue, int8 h_green, int32 index) { + if (data) { + EQ2_EquipmentItem* equipment = (EQ2_EquipmentItem*)GetStructPointer(data); + EQ2_Color* color = (EQ2_Color*)&equipment[index].color; + EQ2_Color* highlight = (EQ2_Color*)&equipment[index].highlight; + equipment[index].type = type; + color->blue = c_blue; + color->red = c_red; + color->green = c_green; + highlight->blue = h_blue; + highlight->red = h_red; + highlight->green = h_green; + if (c_red > 0 || c_blue > 0 || c_green > 0 || h_red > 0 || h_blue > 0 || h_green > 0) + data->SetIsSet(true); + } +} +void PacketStruct::add(PacketStruct* packet_struct) { + packet_struct->parent = this; + arrays.push_back(packet_struct); +} +const char* PacketStruct::GetOpcodeType() { + return opcode_type.c_str(); +} + +void PacketStruct::SetOpcodeType(const char* in_type) { + if (in_type) + opcode_type = string(in_type); + else + opcode_type = string(""); +} + +void PacketStruct::setDataType(DataStruct* data_struct, sint8 data, int32 index) { + if (data_struct) { + sint8* ptr = (sint8*)GetStructPointer(data_struct); + ptr[index] = data; + if (data != 0 && data != -1) + data_struct->SetIsSet(true); + } +} +void PacketStruct::setDataType(DataStruct* data_struct, sint16 data, int32 index) { + if (data_struct) { + sint16* ptr = (sint16*)GetStructPointer(data_struct); + ptr[index] = data; + if (data != 0 && data != -1) + data_struct->SetIsSet(true); + } +} +void PacketStruct::setDataType(DataStruct* data_struct, sint32 data, int32 index) { + if (data_struct) { + sint32* ptr = (sint32*)GetStructPointer(data_struct); + ptr[index] = data; + if (data != 0 && data != -1) + data_struct->SetIsSet(true); + } +} +void PacketStruct::setDataType(DataStruct* data_struct, sint64 data, int32 index) { + if (data_struct) { + sint64* ptr = (sint64*)GetStructPointer(data_struct); + ptr[index] = data; + if (data > 0) + data_struct->SetIsSet(true); + } +} +void PacketStruct::setDataType(DataStruct* data_struct, char data, int32 index) { + if (data_struct) { + char* ptr = (char*)GetStructPointer(data_struct); + ptr[index] = data; + if (data > 0) + data_struct->SetIsSet(true); + } +} +void PacketStruct::setDataType(DataStruct* data_struct, int8 data, int32 index) { + if (data_struct) { + int8* ptr = (int8*)GetStructPointer(data_struct); + ptr[index] = data; + if (data > 0) + data_struct->SetIsSet(true); + } +} +void PacketStruct::setDataType(DataStruct* data_struct, int16 data, int32 index) { + if (data_struct) { + int16* ptr = (int16*)GetStructPointer(data_struct); + ptr[index] = data; + if (data > 0) + data_struct->SetIsSet(true); + } +} +void PacketStruct::setDataType(DataStruct* data_struct, int32 data, int32 index) { + if (data_struct) { + int32* ptr = (int32*)GetStructPointer(data_struct); + ptr[index] = data; + if (data > 0) + data_struct->SetIsSet(true); + } +} +void PacketStruct::setDataType(DataStruct* data_struct, int64 data, int32 index) { + if (data_struct) { + int64* ptr = (int64*)GetStructPointer(data_struct); + ptr[index] = data; + if (data > 0) + data_struct->SetIsSet(true); + } +} +void PacketStruct::setDataType(DataStruct* data_struct, float data, int32 index) { + if (data_struct) { + float* ptr = (float*)GetStructPointer(data_struct); + ptr[index] = data; + if (data > 0) + data_struct->SetIsSet(true); + } +} +void PacketStruct::setDataType(DataStruct* data_struct, double data, int32 index) { + if (data_struct) { + double* ptr = (double*)GetStructPointer(data_struct); + ptr[index] = data; + if (data > 0) + data_struct->SetIsSet(true); + } +} +void PacketStruct::setData(DataStruct* data_struct, EQ2_8BitString* input_string, int32 index, bool use_second_type) { + if (data_struct) { + EQ2_8BitString* tmp = (EQ2_8BitString*)GetStructPointer(data_struct); + tmp->data = input_string->data; + tmp->size = input_string->data.length(); + if (input_string->data.length() > 0) + data_struct->SetIsSet(true); + } +} +void PacketStruct::setData(DataStruct* data_struct, EQ2_16BitString* input_string, int32 index, bool use_second_type) { + if (data_struct) { + EQ2_16BitString* tmp = (EQ2_16BitString*)GetStructPointer(data_struct); + tmp->data = input_string->data; + tmp->size = input_string->data.length(); + if (input_string->data.length() > 0) + data_struct->SetIsSet(true); + } +} +void PacketStruct::setData(DataStruct* data_struct, EQ2_32BitString* input_string, int32 index, bool use_second_type) { + if (data_struct) { + EQ2_32BitString* tmp = (EQ2_32BitString*)GetStructPointer(data_struct); + tmp->data = input_string->data; + tmp->size = input_string->data.length(); + if (input_string->data.length() > 0) + data_struct->SetIsSet(true); + } +} +void PacketStruct::add(DataStruct* data) { + structs.push_back(data); + struct_map[data->GetStringName()] = data; + switch (data->GetType()) { + case DATA_STRUCT_INT8: { + struct_data[data] = new int8[data->GetLength()]; + int8* ptr = (int8*)GetStructPointer(data); + if (data->GetLength() > 1) { + int8 default_val = data->GetDefaultValue(); + memset(ptr, default_val, data->GetLength() * sizeof(int8)); + } + else + ptr[0] = 0; + break; + } + case DATA_STRUCT_INT16: { + struct_data[data] = new int16[data->GetLength()]; + int16* ptr = (int16*)GetStructPointer(data); + if (data->GetLength() > 1) { + int8 default_val = data->GetDefaultValue(); + memset(ptr, default_val, data->GetLength() * sizeof(int16)); + } + else + ptr[0] = 0; + break; + } + case DATA_STRUCT_INT32: { + struct_data[data] = new int32[data->GetLength()]; + int32* ptr = (int32*)GetStructPointer(data); + if (data->GetLength() > 1) { + int8 default_val = data->GetDefaultValue(); + memset(ptr, default_val, data->GetLength() * sizeof(int32)); + } + else + ptr[0] = 0; + break; + } + case DATA_STRUCT_INT64: { + struct_data[data] = new int64[data->GetLength()]; + int64* ptr = (int64*)GetStructPointer(data); + if (data->GetLength() > 1) { + int8 default_val = data->GetDefaultValue(); + memset(ptr, default_val, data->GetLength() * sizeof(int64)); + } + else + ptr[0] = 0; + break; + } + case DATA_STRUCT_SINT8: { + struct_data[data] = new sint8[data->GetLength()]; + sint8* ptr = (sint8*)GetStructPointer(data); + if (data->GetLength() > 1) + memset(ptr, 0, data->GetLength() * sizeof(sint8)); + else + ptr[0] = 0; + break; + } + case DATA_STRUCT_SINT16: { + struct_data[data] = new sint16[data->GetLength()]; + sint16* ptr = (sint16*)GetStructPointer(data); + if (data->GetLength() > 1) + memset(ptr, 0, data->GetLength() * sizeof(sint16)); + else + ptr[0] = 0; + break; + } + case DATA_STRUCT_SINT32: { + struct_data[data] = new sint32[data->GetLength()]; + sint32* ptr = (sint32*)GetStructPointer(data); + if (data->GetLength() > 1) + memset(ptr, 0, data->GetLength() * sizeof(sint32)); + else + ptr[0] = 0; + break; + } + case DATA_STRUCT_SINT64: { + struct_data[data] = new sint64[data->GetLength()]; + sint64* ptr = (sint64*)GetStructPointer(data); + if (data->GetLength() > 1) + memset(ptr, 0, data->GetLength() * sizeof(sint64)); + else + ptr[0] = 0; + break; + } + case DATA_STRUCT_CHAR: { + struct_data[data] = new char[data->GetLength()]; + char* ptr = (char*)GetStructPointer(data); + if (data->GetLength() > 1) + memset(ptr, 0, data->GetLength() * sizeof(char)); + else + ptr[0] = 0; + break; + } + case DATA_STRUCT_FLOAT: { + struct_data[data] = new float[data->GetLength()]; + float* ptr = (float*)GetStructPointer(data); + if (data->GetLength() > 1) + memset(ptr, 0, data->GetLength() * sizeof(float)); + else + ptr[0] = 0; + break; + } + case DATA_STRUCT_DOUBLE: { + struct_data[data] = new double[data->GetLength()]; + double* ptr = (double*)GetStructPointer(data); + if (data->GetLength() > 1) + memset(ptr, 0, data->GetLength() * sizeof(double)); + else + ptr[0] = 0; + break; + } + case DATA_STRUCT_ARRAY: { + data->SetLength(0); + break; + } + case DATA_STRUCT_EQ2_8BIT_STRING: { + string name2 = data->GetStringName(); + for (int32 i = 1; i < data->GetLength(); i++) { + DataStruct* new_data = new DataStruct(data); + char blah[10] = { 0 }; + sprintf(blah, "%i", i); + name2.append("_").append(blah); + new_data->SetName(name2.c_str()); + new_data->SetLength(1); + EQ2_8BitString* tmp = new EQ2_8BitString; + tmp->size = 0; + struct_data[new_data] = tmp; + structs.push_back(new_data); + } + data->SetLength(1); + EQ2_8BitString* tmp = new EQ2_8BitString; + tmp->size = 0; + struct_data[data] = tmp; + break; + } + case DATA_STRUCT_EQ2_16BIT_STRING: { + string name2 = data->GetStringName(); + for (int32 i = 1; i < data->GetLength(); i++) { + DataStruct* new_data = new DataStruct(data); + char blah[10] = { 0 }; + sprintf(blah, "%i", i); + name2.append("_").append(blah); + new_data->SetName(name2.c_str()); + new_data->SetLength(1); + EQ2_16BitString* tmp = new EQ2_16BitString; + tmp->size = 0; + struct_data[new_data] = tmp; + structs.push_back(new_data); + } + data->SetLength(1); + EQ2_16BitString* tmp = new EQ2_16BitString; + tmp->size = 0; + struct_data[data] = tmp; + break; + } + case DATA_STRUCT_EQ2_32BIT_STRING: { + string name2 = data->GetStringName(); + for (int32 i = 1; i < data->GetLength(); i++) { + DataStruct* new_data = new DataStruct(data); + char blah[10] = { 0 }; + sprintf(blah, "%i", i); + name2.append("_").append(blah); + new_data->SetName(name2.c_str()); + new_data->SetLength(1); + EQ2_32BitString* tmp = new EQ2_32BitString; + tmp->size = 0; + struct_data[new_data] = tmp; + structs.push_back(new_data); + } + data->SetLength(1); + EQ2_32BitString* tmp = new EQ2_32BitString; + tmp->size = 0; + struct_data[data] = tmp; + break; + } + case DATA_STRUCT_COLOR: { + struct_data[data] = new EQ2_Color[data->GetLength()]; + EQ2_Color* ptr = (EQ2_Color*)GetStructPointer(data); + for (int16 i = 0; i < data->GetLength(); i++) { + ptr[i].red = 0; + ptr[i].blue = 0; + ptr[i].green = 0; + } + break; + } + case DATA_STRUCT_EQUIPMENT: { + struct_data[data] = new EQ2_EquipmentItem[data->GetLength()]; + EQ2_EquipmentItem* ptr = (EQ2_EquipmentItem*)GetStructPointer(data); + for (int16 i = 0; i < data->GetLength(); i++) { + memset(&ptr[i].color, 0, sizeof(ptr[i].color)); + memset(&ptr[i].highlight, 0, sizeof(ptr[i].highlight)); + ptr[i].type = 0; + } + break; + } + case DATA_STRUCT_ITEM: { + struct_data[data] = new uchar[10000]; + char* ptr = (char*)GetStructPointer(data); + memset(ptr, 0, 10000); + break; + } + } +} +void PacketStruct::remove(DataStruct* data) { + vector::iterator itr; + for (itr = structs.begin(); itr != structs.end(); itr++) { + if (data == (*itr)) { + structs.erase(itr); + safe_delete(data); + return; + } + } +} +DataStruct* PacketStruct::findStruct(const char* name, int32 index) { + return findStruct(name, index, index); +} + +DataStruct* PacketStruct::findStruct(const char* name, int32 index1, int32 index2) { + DataStruct* data = 0; + + if (struct_map.count(string(name)) > 0) { + data = struct_map[string(name)]; + if (data && index2 < data->GetLength()) + return data; + } + vector::iterator itr; + + PacketStruct* packet = 0; + vector::iterator itr2; + string name2 = string(name); + if (index1 < 0xFFFF) { + char blah[10] = { 0 }; + sprintf(blah, "_%i", index1); + name2.append(blah); + } + if (struct_map.count(name2) > 0) { + data = struct_map[name2]; + if (data && index2 < data->GetLength()) + return data; + } + for (itr2 = arrays.begin(); itr2 != arrays.end(); itr2++) { + packet = *itr2; + data = packet->findStruct(name, index1, index2); + if (data != 0) + return data; + } + return 0; +} +void PacketStruct::remove(const char* name) { + DataStruct* data = 0; + vector::iterator itr; + for (itr = structs.begin(); itr != structs.end(); itr++) { + data = *itr; + if (strcmp(name, data->GetName()) == 0) { + structs.erase(itr); + safe_delete(data); + return; + } + } +} +string* PacketStruct::serializeString() { + serializePacket(); + return getDataString(); +} +void PacketStruct::setSmallString(DataStruct* data_struct, const char* text, int32 index) { + EQ2_8BitString* string_data = new EQ2_8BitString; + string_data->data = string(text); + string_data->size = string_data->data.length(); + setData(data_struct, string_data, index); + safe_delete(string_data); +} +void PacketStruct::setMediumString(DataStruct* data_struct, const char* text, int32 index) { + EQ2_16BitString* string_data = new EQ2_16BitString; + string_data->data = string(text); + string_data->size = string_data->data.length(); + setData(data_struct, string_data, index); + safe_delete(string_data); +} +void PacketStruct::setLargeString(DataStruct* data_struct, const char* text, int32 index) { + EQ2_32BitString* string_data = new EQ2_32BitString; + string_data->data = string(text); + string_data->size = string_data->data.length(); + setData(data_struct, string_data, index); + safe_delete(string_data); +} +void PacketStruct::setSmallStringByName(const char* name, const char* text, int32 index) { + setSmallString(findStruct(name, index), text, index); +} +void PacketStruct::setMediumStringByName(const char* name, const char* text, int32 index) { + setMediumString(findStruct(name, index), text, index); +} +void PacketStruct::setLargeStringByName(const char* name, const char* text, int32 index) { + setLargeString(findStruct(name, index), text, index); +} + +bool PacketStruct::GetVariableIsSet(const char* name) { + DataStruct* ds2 = findStruct(name, 0); + if (!ds2 || !ds2->IsSet()) + return false; + return true; +} + +bool PacketStruct::GetVariableIsNotSet(const char* name) { + DataStruct* ds2 = findStruct(name, 0); + if (ds2 && ds2->IsSet()) + return false; + return true; +} + +bool PacketStruct::CheckFlagExists(const char* name) { + vector::iterator itr; + for (itr = flags.begin(); itr != flags.end(); itr++) { + if (*itr == string(name)) + return true; + } + return false; +} + +void PacketStruct::AddFlag(const char* name) { + if (flags.size() > 0) { + vector::iterator itr; + for (itr = flags.begin(); itr != flags.end(); itr++) { + if (*itr == string(name)) + return; + } + } + flags.push_back(string(name)); +} + +bool PacketStruct::LoadPacketData(uchar* data, int32 data_len, bool create_color) { + loadedSuccessfully = true; + DataStruct* data_struct = 0; + try { + InitializeLoadData(data, data_len); + vector::iterator itr; + + for (itr = structs.begin(); itr != structs.end(); itr++) { + data_struct = *itr; + if (!data_struct->AddToStruct()) + continue; + + if (data_struct->GetIfSet() && data_struct->GetIfSetVariable()) { + string varname = string(data_struct->GetIfSetVariable()); + if (varname.find(",") < 0xFFFFFFFF) { + vector* varnames = SplitString(varname, ','); + if (varnames) { + bool should_continue = true; + for (int32 i = 0; i < varnames->size(); i++) { + if (GetVariableIsSet(varnames->at(i).c_str())) { + should_continue = false; + break; + } + } + safe_delete(varnames); + if (should_continue) + continue; + } + } + else { + // Check to see if the variable contains %i, if it does assume we are in an array + // and get the current index from the end of the data struct's name + char name[250] = { 0 }; + if (varname.find("%i") < 0xFFFFFFFF) { + vector* varnames = SplitString(data_struct->GetName(), '_'); + int index = atoi(varnames->at(varnames->size() - 1).c_str()); + sprintf(name, varname.c_str(), index); + } + else + strcpy(name, varname.c_str()); + + if (!GetVariableIsSet(name)) + continue; + } + } + if (data_struct->GetIfNotSet() && data_struct->GetIfNotSetVariable()) { + string varname = string(data_struct->GetIfNotSetVariable()); + if (varname.find(",") < 0xFFFFFFFF) { + vector* varnames = SplitString(varname, ','); + if (varnames) { + bool should_continue = false; + for (int32 i = 0; i < varnames->size(); i++) { + if (!GetVariableIsNotSet(varnames->at(i).c_str())) { + should_continue = true; + break; + } + } + safe_delete(varnames); + if (should_continue) + continue; + } + } + else { + // Check to see if the variable contains %i, if it does assume we are in an array + // and get the current index from the end of the data struct's name + char name[250] = { 0 }; + if (varname.find("%i") < 0xFFFFFFFF) { + vector* varnames = SplitString(data_struct->GetName(), '_'); + int index = atoi(varnames->at(varnames->size() - 1).c_str()); + sprintf(name, varname.c_str(), index); + } + else + strcpy(name, varname.c_str()); + + if (!GetVariableIsNotSet(name)) + continue; + } + } + // Quick implementaion of IfVariableNotEquals + // probably not what it was intended for as it currently just checks to see if the given variable equals 1 + // should probably change it so you can define what the variable should or shouldn't equal + // + // ie: IfVariableNotEquals="stat_type_%i=1" + // would be a check to make sure that stat_type_%i does not equal 1 and if it does exclude this element + if (data_struct->GetIfNotEquals() && data_struct->GetIfNotEqualsVariable()) { + // Get the variable name + string varname = string(data_struct->GetIfNotEqualsVariable()); + char name[250] = { 0 }; + // Check to see if the variable has %i in the name, if it does assume we are in an array and get the current + // index and replace it + if (varname.find("%i") < 0xFFFFFFFF) { + // Get the current index by getting the number at the end of the name + vector* varnames = SplitString(data_struct->GetName(), '_'); + int index = atoi(varnames->at(varnames->size() - 1).c_str()); + + string substr = "stat_type"; + if (strncmp(varname.c_str(), substr.c_str(), strlen(substr.c_str())) == 0) { + // adorn_stat_subtype 18 chars + string temp = varname.substr(12); + char temp2[20] = { 0 }; + int index2 = atoi(temp.c_str()); + itoa(index2, temp2, 10); + varname = varname.substr(0, 12).append(temp2).append("_%i"); + } + sprintf(name, varname.c_str(), index); + safe_delete(varnames); + } + else + strcpy(name, varname.c_str()); + + // Get the data for the variable + int16 value = 0; + DataStruct* data_struct2 = findStruct(name, 0); + value = getType_int16(data_struct2); + // Hack for items as it is the only struct that currently uses IfVariableNotEquals + if (value == 1) + continue; + } + // copy and paste of the code above for IfEquals + if (data_struct->GetIfEquals() && data_struct->GetIfEqualsVariable()) { + // Get the variable name + string varname = string(data_struct->GetIfEqualsVariable()); + char name[250] = { 0 }; + // Check to see if the variable has %i in the name, if it does assume we are in an array and get the current + // index and replace it + if (varname.find("%i") < 0xFFFFFFFF) { + // Get the current index by getting the number at the end of the name + vector* varnames = SplitString(data_struct->GetName(), '_'); + int index = atoi(varnames->at(varnames->size() - 1).c_str()); + + string substr = "stat_type"; + if (strncmp(varname.c_str(), substr.c_str(), strlen(substr.c_str())) == 0) { + // adorn_stat_subtype 18 chars + string temp = varname.substr(12); + char temp2[20] = { 0 }; + int index2 = atoi(temp.c_str()); + itoa(index2, temp2, 10); + varname = varname.substr(0, 12).append(temp2).append("_%i"); + } + sprintf(name, varname.c_str(), index); + safe_delete(varnames); + } + else + strcpy(name, varname.c_str()); + + // Get the data for the variable + int16 value = 0; + DataStruct* data_struct2 = findStruct(name, 0); + value = getType_int16(data_struct2); + // Hack for items as it is the only struct that currently uses IfVariableNotEquals + if (value != 1) + continue; + } + + // The following is tailored to items as they are the only structs that use type2 + // if more type2's are needed outside of the item stats array we need to think up an element + // to determine when to use type2 over type. + // Added checks for set stats and adorn stats - Zcoretri + bool useType2 = false; + if (data_struct->GetType2() > 0) { + int16 type = 0; + char name[250] = { 0 }; + vector* varnames = SplitString(data_struct->GetName(), '_'); + string struct_name = data_struct->GetName(); + if (struct_name.find("set") < 0xFFFFFFFF) { + string tmp = "set_stat_type"; + struct_name.replace(0, 9, tmp); + sprintf(name, "%s", struct_name.c_str()); + } + else if (struct_name.find("adorn") < 0xFFFFFFFF) { + string tmp = "adorn_stat_type"; + struct_name.replace(0, 9, tmp); + sprintf(name, "%s", struct_name.c_str()); + } + else { + // set name to stat_type_# (where # is the current index of the array we are in) + sprintf(name, "%s_%s", "stat_type", varnames->at(varnames->size() - 1).c_str()); + } + // Look up the value for stat_type + DataStruct* data_struct2 = findStruct(name, 0); + type = getType_int16(data_struct2); + // If stat_type == 6 we use a float, else we use sint16 + if (type != 6 && type != 7) + useType2 = true; + safe_delete(varnames); + } + if (!StructLoadData(data_struct, GetStructPointer(data_struct), data_struct->GetLength(), useType2, create_color)) + { + loadedSuccessfully = false; + break; + } + } + } + catch (...) { + loadedSuccessfully = false; + } + return loadedSuccessfully; +} +bool PacketStruct::StructLoadData(DataStruct* data_struct, void* data, int32 len, bool useType2, bool create_color) { + int8 type = 0; + if (useType2) { + type = data_struct->GetType2(); + // Need to change the data the struct expects to type2 + data_struct->SetType(type); + LogWrite(PACKET__DEBUG, 7, "Items", "Using type2 = %i", type); + } + else + type = data_struct->GetType(); + + switch (type) { + case DATA_STRUCT_INT8: + LoadData((int8*)data, len); + data_struct->SetIsSet(*((int8*)data) > 0); + break; + case DATA_STRUCT_INT16: + if (data_struct->GetOversized() > 0) { + LoadData((int8*)data, len); + if (getType_int16(data_struct) == data_struct->GetOversizedByte()) { + LoadData((int16*)data, len); + } + } + else { + LoadData((int16*)data, len); + } + data_struct->SetIsSet(*((int16*)data) > 0); + break; + case DATA_STRUCT_INT32: + if (data_struct->GetOversized() > 0) { + LoadData((int8*)data, len); + if (getType_int32(data_struct) == data_struct->GetOversizedByte()) { + LoadData((int32*)data, len); + } + else + LoadData((int8*)data, len); + } + else { + LoadData((int32*)data, len); + } + data_struct->SetIsSet(*((int32*)data) > 0); + break; + case DATA_STRUCT_INT64: + LoadData((int64*)data, len); + data_struct->SetIsSet(*((int64*)data) > 0); + break; + case DATA_STRUCT_SINT8: + LoadData((sint8*)data, len); + data_struct->SetIsSet(*((sint8*)data) > 0); + break; + case DATA_STRUCT_SINT16: + if (data_struct->GetOversized() > 0) { + LoadData((sint8*)data, len); + sint8 val = (sint8)getType_sint16(data_struct); + if (val < 0) //necessary because when using memcpy from a smaller data type to a larger one, the sign is lost + setData(data_struct, val, 0); + if (getType_sint16(data_struct) == data_struct->GetOversizedByte()) + LoadData((sint16*)data, len); + } + else + LoadData((sint16*)data, len); + data_struct->SetIsSet(*((sint16*)data) > 0); + break; + case DATA_STRUCT_SINT32: + LoadData((sint32*)data, len); + data_struct->SetIsSet(*((sint32*)data) > 0); + break; + case DATA_STRUCT_SINT64: + LoadData((sint64*)data, len); + data_struct->SetIsSet(*((sint64*)data) > 0); + break; + case DATA_STRUCT_CHAR: + LoadData((char*)data, len); + data_struct->SetIsSet(true); + break; + case DATA_STRUCT_FLOAT: + LoadData((float*)data, len); + data_struct->SetIsSet(*((float*)data) > 0); + break; + case DATA_STRUCT_DOUBLE: + LoadData((double*)data, len); + data_struct->SetIsSet(*((double*)data) > 0); + break; + case DATA_STRUCT_EQ2_8BIT_STRING: { + LoadDataString((EQ2_8BitString*)data); + data_struct->SetIsSet(((EQ2_8BitString*)data)->data.length() > 0); + break; + } + case DATA_STRUCT_EQ2_16BIT_STRING: { + LoadDataString((EQ2_16BitString*)data); + data_struct->SetIsSet(((EQ2_16BitString*)data)->data.length() > 0); + break; + } + case DATA_STRUCT_EQ2_32BIT_STRING: { + LoadDataString((EQ2_32BitString*)data); + data_struct->SetIsSet(((EQ2_32BitString*)data)->data.length() > 0); + break; + } + case DATA_STRUCT_COLOR: { + // lets not do this again, DoF behaves differently than AoM, DoF is not compatible with CreateEQ2Color + //if (strcmp(GetName(), "CreateCharacter") == 0 || strcmp(GetName(), "WS_SubmitCharCust") == 0) + if(create_color) + CreateEQ2Color((EQ2_Color*)data); + else + LoadData((EQ2_Color*)data, len); + break; + } + case DATA_STRUCT_EQUIPMENT: { + LoadData((EQ2_EquipmentItem*)data); + break; + } + case DATA_STRUCT_ARRAY: { + int32 size = GetArraySize(data_struct, 0); + if (size > 0xFFFF || size > GetLoadLen()-GetLoadPos()) { + LogWrite(PACKET__WARNING, 1, "Packet", "Possible corrupt packet while loading struct array, orig array size: %u in struct name %s, data name %s, load_len %u, load_pos %u", size, GetName(), (data_struct && data_struct->GetName()) ? data_struct->GetName() : "??", GetLoadLen(), GetLoadPos()); + return false; + } + PacketStruct* ps = GetPacketStructByName(data_struct->GetName()); + if (ps && ps->GetSubPacketSize() != size) { + if (data_struct->GetMaxArraySize() > 0 && size > data_struct->GetMaxArraySize()) + size = data_struct->GetMaxArraySize(); + ps->reAddAll(size); + } + if (ps && size > 0) { + //for(int i=0;i 0;i++){ + if(ps->LoadPacketData(GetLoadBuffer() + GetLoadPos(), GetLoadLen() - GetLoadPos(), create_color)) { + SetLoadPos(GetLoadPos() + ps->GetLoadPos()); + } + //} + } + break; + } + default: { + data_struct->SetIsSet(false); + } + } + + return true; +} +PacketStruct* PacketStruct::GetPacketStructByName(const char* name) { + PacketStruct* ps = 0; + vector::iterator itr; + for (itr = arrays.begin(); itr != arrays.end(); itr++) { + ps = *itr; + if (strcmp(ps->GetName(), name) == 0) + return ps; + ps = ps->GetPacketStructByName(name); + if (ps) + return ps; + } + return 0; +} + +void PacketStruct::reAddAll(int32 length) { + vector::iterator itr; + DataStruct* ds = 0; + PacketStruct* ps = 0; + vector::iterator packet_itr; + if (orig_structs.size() == 0) + orig_structs = structs; + else + deleteDataStructs(&structs); + structs.clear(); + + if (orig_packets.size() == 0 && arrays.size() > 0) + orig_packets = arrays; + else { + for (packet_itr = arrays.begin(); packet_itr != arrays.end(); packet_itr++) { + ps = *packet_itr; + safe_delete(ps); + } + } + arrays.clear(); + + for (int16 i = 0; i < length; i++) { + for (packet_itr = orig_packets.begin(); packet_itr != orig_packets.end(); packet_itr++) { + ps = *packet_itr; + PacketStruct* new_packet = new PacketStruct(ps, true); + char tmp[20] = { 0 }; + sprintf(tmp, "_%i", i); + string name = string(new_packet->GetName()); + name.append(tmp); + new_packet->SetName(name.c_str()); + add(new_packet); + } + for (itr = orig_structs.begin(); itr != orig_structs.end(); itr++) { + ds = *itr; + DataStruct* new_data = new DataStruct(ds); + char tmp[20] = { 0 }; + sprintf(tmp, "_%i", i); + string name = new_data->GetStringName(); + if (IsSubPacket() && parent->IsSubPacket()) { + string parent_name = string(GetName()); + try { + if (parent_name.rfind("_") < 0xFFFFFFFF) + sprintf(tmp, "%i_%i", atoi(parent_name.substr(parent_name.rfind("_") + 1).c_str()), i); + } + catch (...) { + sprintf(tmp, "_%i", i); + } + } + name.append(tmp); + new_data->SetName(name.c_str()); + if (new_data->GetType() == DATA_STRUCT_ARRAY) { + string old_size_arr = string(new_data->GetArraySizeVariable()); + new_data->SetArraySizeVariable(old_size_arr.append(tmp).c_str()); + } + add(new_data); + } + } + sub_packet_size = length; +} +int32 PacketStruct::GetArraySize(DataStruct* data_struct, int32 index) { + if (data_struct) { + const char* name = data_struct->GetArraySizeVariable(); + return GetArraySize(name, index); + } + return 0; +} +int32 PacketStruct::GetArraySize(const char* name, int32 index) { + int32 ret = 0; + DataStruct* ds = findStruct(name, index); + if (ds) { + if (ds->GetType() == DATA_STRUCT_INT8) { + int8* tmp = (int8*)GetStructPointer(ds); + ret = *tmp; + } + else if (ds->GetType() == DATA_STRUCT_INT16) { + int16* tmp = (int16*)GetStructPointer(ds); + ret = *tmp; + } + else if (ds->GetType() == DATA_STRUCT_INT32) { + int32* tmp = (int32*)GetStructPointer(ds); + ret = *tmp; + } + else if (ds->GetType() == DATA_STRUCT_INT64) { + int64* tmp = (int64*)GetStructPointer(ds); + ret = *tmp; + } + } + return ret; +} + +void PacketStruct::UpdateArrayByArrayLength(DataStruct* data_struct, int32 index, int32 size) { + if (data_struct) { + PacketStruct* packet = 0; + DataStruct* data = 0; + vector::iterator itr; + + for (itr = structs.begin(); itr != structs.end(); itr++) { + data = *itr; + if (strcmp(data->GetArraySizeVariable(), data_struct->GetName()) == 0) { + packet = GetPacketStructByName(data->GetName()); + if (packet) + packet->reAddAll(size); + return; + } + } + vector::iterator itr2; + for (itr2 = arrays.begin(); itr2 != arrays.end(); itr2++) { + packet = *itr2; + packet->UpdateArrayByArrayLength(data_struct, index, size); + } + } +} + +void PacketStruct::UpdateArrayByArrayLengthName(const char* name, int32 index, int32 size) { + UpdateArrayByArrayLength(findStruct(name, index), index, size); +} +int32 PacketStruct::GetArraySizeByName(const char* name, int32 index) { + DataStruct* ds1 = findStruct(name, index); + return GetArraySize(ds1, index); +} + +int16 PacketStruct::GetOpcodeValue(int16 client_version) { + int16 opcode = 0xFFFF; + bool client_cmd = false; + int16 OpcodeVersion = 0; +#ifndef LOGIN + if (GetOpcode() == OP_ClientCmdMsg && strlen(GetOpcodeType()) > 0 && !IsSubPacket()) + client_cmd = true; +#endif + if (client_cmd) { + EmuOpcode sub_opcode = EQOpcodeManager[0]->NameSearch(GetOpcodeType()); + if (sub_opcode != OP_Unknown) { //numbers should be used at OpcodeTypes, define them! + OpcodeVersion = GetOpcodeVersion(client_version); + if (EQOpcodeManager.count(OpcodeVersion) > 0) { + opcode = EQOpcodeManager[OpcodeVersion]->EmuToEQ(sub_opcode); + if (opcode == 0xCDCD) { + LogWrite(PACKET__ERROR, 0, "Packet", "Could not find valid opcode for opcode: %s and client_version: %i", EQOpcodeManager[OpcodeVersion]->EmuToName(sub_opcode), client_version); + } + } + } + } + else { + OpcodeVersion = GetOpcodeVersion(client_version); + if (EQOpcodeManager.count(OpcodeVersion) > 0) { + opcode = EQOpcodeManager[OpcodeVersion]->EmuToEQ(GetOpcode()); + if (opcode == 0xCDCD) { + LogWrite(PACKET__ERROR, 0, "Packet", "Could not find valid opcode for opcode: %s and client_version: %i", EQOpcodeManager[OpcodeVersion]->EmuToName(GetOpcode()), client_version); + } + } + } +#ifndef LOGIN + if(opcode == 0) + opcode = 0xFFFF; +#endif + return opcode; +} + +void PacketStruct::serializePacket(bool clear) { + if (clear) + Clear(); + bool client_cmd = false; + string client_data; +#ifndef LOGIN + if (GetOpcode() == OP_ClientCmdMsg && strlen(GetOpcodeType()) > 0 && !IsSubPacket()) + client_cmd = true; +#endif + DataStruct* data = 0; + vector::iterator itr; + for (itr = structs.begin(); itr != structs.end(); itr++) { + data = *itr; + if (data->IsOptional())//this would be false if the datastruct WAS optional, but was set + continue; + if (data->AddToStruct()) { + if (data->GetIfFlagNotSet() && CheckFlagExists(data->GetIfFlagNotSetVariable())) + continue; + if (data->GetIfFlagSet() && !CheckFlagExists(data->GetIfFlagSetVariable())) + continue; + if (data->GetIfSet() && data->GetIfSetVariable()) { + string varname = string(data->GetIfSetVariable()); + if (varname.find(",") < 0xFFFFFFFF) { + vector* varnames = SplitString(varname, ','); + if (varnames) { + bool should_continue = true; + for (int32 i = 0; i < varnames->size(); i++) { + if (GetVariableIsSet(varnames->at(i).c_str())) { + should_continue = false; + break; + } + } + safe_delete(varnames); + if (should_continue) + continue; + } + } + else { + if (!GetVariableIsSet(varname.c_str())) + continue; + } + } + if (data->GetIfNotSet() && data->GetIfNotSetVariable()) { + string varname = string(data->GetIfNotSetVariable()); + if (varname.find(",") < 0xFFFFFFFF) { + vector* varnames = SplitString(varname, ','); + if (varnames) { + bool should_continue = false; + for (int32 i = 0; i < varnames->size(); i++) { + if (!GetVariableIsNotSet(varnames->at(i).c_str())) { + should_continue = true; + break; + } + } + safe_delete(varnames); + if (should_continue) + continue; + } + } + else { + // Check to see if the variable contains %i, if it does assume we are in an array + // and get the current index from the end of the data struct's name + char name[250] = { 0 }; + if (varname.find("%i") < 0xFFFFFFFF) { + vector* varnames = SplitString(varname, '_'); + vector* indexes = SplitString(data->GetName(), '_'); + int index = 0; + if (indexes->size() > 0) + index = atoi(indexes->at(indexes->size() - 1).c_str()); + + sprintf(name, varname.c_str(), index); + } + else + strcpy(name, varname.c_str()); + if (!GetVariableIsNotSet(name)) + continue; + } + } + // Quick implementaion of IfVariableNotEquals + // probably not what it was intended for as it currently just checks to see if the given variable equals 1 + // should probably change it so you can define what the variable should or shouldn't equal + // + // ie: IfVariableNotEquals="stat_type_%i=1" + // would be a check to make sure that stat_type_%i does not equal 1 and if it does exclude this element + if (data->GetIfNotEquals() && data->GetIfNotEqualsVariable()) { + // Get the variable name + string varname = string(data->GetIfNotEqualsVariable()); + char name[250] = { 0 }; + // Check to see if the variable has %i in the name, if it does assume we are in an array and get the current + // index and replace it + if (varname.find("%i") < 0xFFFFFFFF) { + // Get the current index by getting the number at the end of the name + vector* varnames = SplitString(data->GetName(), '_'); + int index = atoi(varnames->at(varnames->size() - 1).c_str()); + + string substr = "stat_type"; + if (strncmp(varname.c_str(), substr.c_str(), strlen(substr.c_str())) == 0) { + // adorn_stat_subtype 18 chars + string temp = varname.substr(12); + char temp2[20] = { 0 }; + int index2 = atoi(temp.c_str()); + itoa(index2, temp2, 10); + varname = varname.substr(0, 12).append(temp2).append("_%i"); + } + sprintf(name, varname.c_str(), index); + safe_delete(varnames); + } + else + strcpy(name, varname.c_str()); + + // Get the data for the variable + int16 value = 0; + DataStruct* data_struct2 = findStruct(name, 0); + value = getType_int16(data_struct2); + // Hack for items as it is the only struct that currently uses IfVariableNotEquals + if (value == 1) + continue; + } + // copy and paste of the code above for IfEquals + if (data->GetIfEquals() && data->GetIfEqualsVariable()) { + // Get the variable name + string varname = string(data->GetIfEqualsVariable()); + char name[250] = { 0 }; + // Check to see if the variable has %i in the name, if it does assume we are in an array and get the current + // index and replace it + if (varname.find("%i") < 0xFFFFFFFF) { + // Get the current index by getting the number at the end of the name + vector* varnames = SplitString(data->GetName(), '_'); + int index = 0; + if (varnames) + index = atoi(varnames->at(varnames->size() - 1).c_str()); + + string substr = "stat_type"; + if (strncmp(varname.c_str(), substr.c_str(), strlen(substr.c_str())) == 0) { + // adorn_stat_subtype 18 chars + string temp = varname.substr(12); + char temp2[20] = { 0 }; + int index2 = atoi(temp.c_str()); + itoa(index2, temp2, 10); + varname = varname.substr(0, 12).append(temp2).append("_%i"); + } + sprintf(name, varname.c_str(), index); + safe_delete(varnames); + } + else + strcpy(name, varname.c_str()); + + // Get the data for the variable + int16 value = 0; + DataStruct* data_struct2 = findStruct(name, 0); + value = getType_int16(data_struct2); + // Hack for items as it is the only struct that currently uses IfVariableNotEquals + if (value != 1) + continue; + } + if (client_cmd) + AddSerializedData(data, 0, &client_data); + else + AddSerializedData(data); + } + } +#ifndef LOGIN + if (client_cmd) { + int16 opcode_val = GetOpcodeValue(client_version); + Clear(); + int32 size = client_data.length() + 3; //gotta add the opcode and oversized + int8 oversized = 0xFF; + int16 OpcodeVersion = GetOpcodeVersion(client_version); + if (opcode_val == EQOpcodeManager[OpcodeVersion]->EmuToEQ(OP_EqExamineInfoCmd) && client_version > 561) + size += (size - 9); + if (client_version <= 374) { + if (size >= 255) { + StructAddData(oversized, sizeof(int8), 0); + StructAddData(size, sizeof(int16), 0); + } + else { + StructAddData(size, sizeof(int8), 0); + } + StructAddData(oversized, sizeof(int8), 0); + StructAddData(opcode_val, sizeof(int16), 0); + } + else { + StructAddData(size, sizeof(int32), 0); + StructAddData(oversized, sizeof(int8), 0); + StructAddData(opcode_val, sizeof(int16), 0); + } + AddData(client_data); + } +#endif +} +int32 PacketStruct::GetTotalPacketSize() { + int32 retSize = 0; + DataStruct* data = 0; + vector::iterator itr; + EQ2_8BitString* tmp1 = 0; + EQ2_16BitString* tmp2 = 0; + EQ2_32BitString* tmp3 = 0; + for (itr = structs.begin(); itr != structs.end(); itr++) { + data = *itr; + switch (data->GetType()) { + case DATA_STRUCT_INT8: + case DATA_STRUCT_SINT8: + case DATA_STRUCT_CHAR: + retSize += (1 * data->GetLength()); + break; + case DATA_STRUCT_SINT16: + case DATA_STRUCT_INT16: + retSize += (2 * data->GetLength()); + break; + case DATA_STRUCT_INT32: + case DATA_STRUCT_SINT32: + case DATA_STRUCT_FLOAT: + case DATA_STRUCT_DOUBLE: + retSize += (4 * data->GetLength()); + break; + case DATA_STRUCT_SINT64: + case DATA_STRUCT_INT64: + retSize += (8 * data->GetLength()); + break; + case DATA_STRUCT_EQ2_8BIT_STRING: + tmp1 = (EQ2_8BitString*)GetStructPointer(data); + if (tmp1) { + for (int16 i = 0; i < data->GetLength(); i++) + retSize += tmp1[i].data.length(); + } + retSize += (1 * data->GetLength()); + break; + case DATA_STRUCT_EQ2_16BIT_STRING: { + tmp2 = (EQ2_16BitString*)GetStructPointer(data); + if (tmp2) { + for (int16 i = 0; i < data->GetLength(); i++) + retSize += tmp2[i].data.length(); + } + retSize += (2 * data->GetLength()); + break; + } + case DATA_STRUCT_EQ2_32BIT_STRING: { + tmp3 = (EQ2_32BitString*)GetStructPointer(data); + if (tmp3) { + for (int16 i = 0; i < data->GetLength(); i++) + retSize += tmp3[i].data.length(); + } + retSize += (4 * data->GetLength()); + break; + } + case DATA_STRUCT_ARRAY: { + int32 size = GetArraySize(data, 0); + PacketStruct* ps = GetPacketStructByName(data->GetName()); + if (ps && ps->GetSubPacketSize() != size) { + ps->reAddAll(size); + } + if (ps && size > 0) + retSize += ps->GetTotalPacketSize(); + break; + } + case DATA_STRUCT_COLOR: { + retSize += ((sizeof(int8) * 3) * data->GetLength()); + break; + } + case DATA_STRUCT_EQUIPMENT: { + retSize += ((((sizeof(int8) * 3) * 2) + sizeof(int16)) * data->GetLength()); + break; + } + } + } + return retSize; +} + +void PacketStruct::AddSerializedData(DataStruct* data, int32 index, string* datastring) { + switch (data->GetAddType()) { + case DATA_STRUCT_INT8: + StructAddData((int8*)GetStructPointer(data), data->GetLength(), sizeof(int8), datastring); + break; + case DATA_STRUCT_INT16: + if (data->GetOversized() > 0) { + if (*((int16*)GetStructPointer(data)) >= data->GetOversized()) { + StructAddData(data->GetOversizedByte(), sizeof(int8), datastring); + StructAddData((int16*)GetStructPointer(data), data->GetLength(), sizeof(int16), datastring); + } + else + StructAddData((int8*)GetStructPointer(data), data->GetLength(), sizeof(int8), datastring); + } + else + StructAddData((int16*)GetStructPointer(data), data->GetLength(), sizeof(int16), datastring); + break; + case DATA_STRUCT_INT32: + if (data->GetOversized() > 0) { + if (*((int32*)GetStructPointer(data)) >= data->GetOversized()) { + StructAddData(data->GetOversizedByte(), sizeof(int8), datastring); + StructAddData((int32*)GetStructPointer(data), data->GetLength(), sizeof(int32), datastring); + } + else + StructAddData((int16*)GetStructPointer(data), data->GetLength(), sizeof(int16), datastring); + } + else + StructAddData((int32*)GetStructPointer(data), data->GetLength(), sizeof(int32), datastring); + break; + case DATA_STRUCT_INT64: + StructAddData((int64*)GetStructPointer(data), data->GetLength(), sizeof(int64), datastring); + break; + case DATA_STRUCT_SINT8: + StructAddData((sint8*)GetStructPointer(data), data->GetLength(), sizeof(sint8), datastring); + break; + case DATA_STRUCT_SINT16: + if (data->GetOversized() > 0) { + sint16 val = *((sint16*)GetStructPointer(data)); + if (val >= data->GetOversized() || val <= (data->GetOversized() * -1)) { + StructAddData(data->GetOversizedByte(), sizeof(sint8), datastring); + StructAddData((sint16*)GetStructPointer(data), data->GetLength(), sizeof(sint16), datastring); + } + else + StructAddData((sint8*)GetStructPointer(data), data->GetLength(), sizeof(sint8), datastring); + } + else + StructAddData((sint16*)GetStructPointer(data), data->GetLength(), sizeof(sint16), datastring); + break; + case DATA_STRUCT_SINT32: + StructAddData((sint32*)GetStructPointer(data), data->GetLength(), sizeof(sint32), datastring); + break; + case DATA_STRUCT_SINT64: + StructAddData((sint64*)GetStructPointer(data), data->GetLength(), sizeof(sint64), datastring); + break; + case DATA_STRUCT_CHAR: + StructAddData((char*)GetStructPointer(data), data->GetLength(), sizeof(char), datastring); + break; + case DATA_STRUCT_FLOAT: + StructAddData((float*)GetStructPointer(data), data->GetLength(), sizeof(float), datastring); + break; + case DATA_STRUCT_DOUBLE: + StructAddData((double*)GetStructPointer(data), data->GetLength(), sizeof(double), datastring); + break; + case DATA_STRUCT_EQ2_8BIT_STRING: { + for (int16 i = 0; i < data->GetLength(); i++) { + EQ2_8BitString* ds = (EQ2_8BitString*)GetStructPointer(data); + AddDataString(ds[i], datastring); + } + break; + } + case DATA_STRUCT_EQ2_16BIT_STRING: { + for (int16 i = 0; i < data->GetLength(); i++) { + EQ2_16BitString* ds = (EQ2_16BitString*)GetStructPointer(data); + AddDataString(ds[i], datastring); + } + break; + } + case DATA_STRUCT_EQ2_32BIT_STRING: { + for (int16 i = 0; i < data->GetLength(); i++) { + EQ2_32BitString* ds = (EQ2_32BitString*)GetStructPointer(data); + AddDataString(ds[i], datastring); + } + break; + } + case DATA_STRUCT_ARRAY: { + int32 size = GetArraySize(data, 0); + PacketStruct* ps = GetPacketStructByName(data->GetName()); + if (ps && ps->GetSubPacketSize() != size) { + ps->reAddAll(size); + } + if (ps && size > 0) { + ps->serializePacket(); + string data = *(ps->getDataString()); + AddData(data, datastring); + } + break; + } + case DATA_STRUCT_COLOR: { + StructAddData((EQ2_Color*)GetStructPointer(data), data->GetLength(), sizeof(EQ2_Color), datastring); + break; + } + case DATA_STRUCT_EQUIPMENT: { + StructAddData((EQ2_EquipmentItem*)GetStructPointer(data), data->GetLength(), sizeof(EQ2_EquipmentItem), datastring); + break; + } + case DATA_STRUCT_ITEM: { + //DumpPacket((uchar*)GetStructPointer(data), data->GetItemSize()); + AddCharArray((char*)GetStructPointer(data), data->GetItemSize(), datastring); + break; + } + } +} + +int8 PacketStruct::getType_int8_ByName(const char* name, int32 index, bool force) { + DataStruct* data_struct = findStruct(name, index); + return getType_int8(data_struct, index, force); +} + +int16 PacketStruct::getType_int16_ByName(const char* name, int32 index, bool force) { + DataStruct* data_struct = findStruct(name, index); + return getType_int16(data_struct, index, force); +} + +int32 PacketStruct::getType_int32_ByName(const char* name, int32 index, bool force) { + DataStruct* data_struct = findStruct(name, index); + return getType_int32(data_struct, index, force); +} + +int64 PacketStruct::getType_int64_ByName(const char* name, int32 index, bool force) { + DataStruct* data_struct = findStruct(name, index); + return getType_int64(data_struct, index, force); +} + +sint8 PacketStruct::getType_sint8_ByName(const char* name, int32 index, bool force) { + DataStruct* data_struct = findStruct(name, index); + return getType_sint8(data_struct, index, force); +} + +sint16 PacketStruct::getType_sint16_ByName(const char* name, int32 index, bool force) { + DataStruct* data_struct = findStruct(name, index); + return getType_sint16(data_struct, index, force); +} + +sint32 PacketStruct::getType_sint32_ByName(const char* name, int32 index, bool force) { + DataStruct* data_struct = findStruct(name, index); + return getType_sint32(data_struct, index, force); +} + +sint64 PacketStruct::getType_sint64_ByName(const char* name, int32 index, bool force) { + DataStruct* data_struct = findStruct(name, index); + return getType_sint64(data_struct, index, force); +} + +float PacketStruct::getType_float_ByName(const char* name, int32 index, bool force) { + DataStruct* data_struct = findStruct(name, index); + return getType_float(data_struct, index, force); +} + +double PacketStruct::getType_double_ByName(const char* name, int32 index, bool force) { + DataStruct* data_struct = findStruct(name, index); + return getType_double(data_struct, index, force); +} + +char PacketStruct::getType_char_ByName(const char* name, int32 index, bool force) { + DataStruct* data_struct = findStruct(name, index); + return getType_char(data_struct, index, force); +} + +EQ2_8BitString PacketStruct::getType_EQ2_8BitString_ByName(const char* name, int32 index, bool force) { + DataStruct* data_struct = findStruct(name, index); + return getType_EQ2_8BitString(data_struct, index, force); +} + +EQ2_16BitString PacketStruct::getType_EQ2_16BitString_ByName(const char* name, int32 index, bool force) { + DataStruct* data_struct = findStruct(name, index); + return getType_EQ2_16BitString(data_struct, index, force); +} + +EQ2_32BitString PacketStruct::getType_EQ2_32BitString_ByName(const char* name, int32 index, bool force) { + DataStruct* data_struct = findStruct(name, index); + return getType_EQ2_32BitString(data_struct, index, force); +} + +EQ2_Color PacketStruct::getType_EQ2_Color_ByName(const char* name, int32 index, bool force) { + DataStruct* data_struct = findStruct(name, index); + return getType_EQ2_Color(data_struct, index, force); +} + +EQ2_EquipmentItem PacketStruct::getType_EQ2_EquipmentItem_ByName(const char* name, int32 index, bool force) { + DataStruct* data_struct = findStruct(name, index); + return getType_EQ2_EquipmentItem(data_struct, index, force); +} + +int8 PacketStruct::getType_int8(DataStruct* data_struct, int32 index, bool force) { + if (data_struct && ((data_struct->GetType() == DATA_STRUCT_INT8) || force)) { + int8* ptr = (int8*)GetStructPointer(data_struct); + return ptr[index]; + } + return 0; +} +int16 PacketStruct::getType_int16(DataStruct* data_struct, int32 index, bool force) { + if (data_struct && ((data_struct->GetType() == DATA_STRUCT_INT16) || force)) { + int16* ptr = (int16*)GetStructPointer(data_struct); + return ptr[index]; + } + return 0; +} +int32 PacketStruct::getType_int32(DataStruct* data_struct, int32 index, bool force) { + if (data_struct && ((data_struct->GetType() == DATA_STRUCT_INT32) || force)) { + int32* ptr = (int32*)GetStructPointer(data_struct); + return ptr[index]; + } + return 0; +} +int64 PacketStruct::getType_int64(DataStruct* data_struct, int32 index, bool force) { + if (data_struct && ((data_struct->GetType() == DATA_STRUCT_INT64) || force)) { + int64* ptr = (int64*)GetStructPointer(data_struct); + return ptr[index]; + } + return 0; +} +sint8 PacketStruct::getType_sint8(DataStruct* data_struct, int32 index, bool force) { + if (data_struct && ((data_struct->GetType() == DATA_STRUCT_SINT8) || force)) { + sint8* ptr = (sint8*)GetStructPointer(data_struct); + return ptr[index]; + } + return 0; +} +sint16 PacketStruct::getType_sint16(DataStruct* data_struct, int32 index, bool force) { + if (data_struct && ((data_struct->GetType() == DATA_STRUCT_SINT16) || force)) { + sint16* ptr = (sint16*)GetStructPointer(data_struct); + return ptr[index]; + } + return 0; +} +sint32 PacketStruct::getType_sint32(DataStruct* data_struct, int32 index, bool force) { + if (data_struct && ((data_struct->GetType() == DATA_STRUCT_SINT32) || force)) { + sint32* ptr = (sint32*)GetStructPointer(data_struct); + return ptr[index]; + } + return 0; +} +sint64 PacketStruct::getType_sint64(DataStruct* data_struct, int32 index, bool force) { + if (data_struct && ((data_struct->GetType() == DATA_STRUCT_SINT64) || force)) { + sint64* ptr = (sint64*)GetStructPointer(data_struct); + return ptr[index]; + } + return 0; +} +float PacketStruct::getType_float(DataStruct* data_struct, int32 index, bool force) { + if (data_struct && ((data_struct->GetType() == DATA_STRUCT_FLOAT) || force)) { + float* ptr = (float*)GetStructPointer(data_struct); + return ptr[index]; + } + return 0; +} +double PacketStruct::getType_double(DataStruct* data_struct, int32 index, bool force) { + if (data_struct && ((data_struct->GetType() == DATA_STRUCT_DOUBLE) || force)) { + double* ptr = (double*)GetStructPointer(data_struct); + return ptr[index]; + } + return 0; +} +char PacketStruct::getType_char(DataStruct* data_struct, int32 index, bool force) { + if (data_struct && ((data_struct->GetType() == DATA_STRUCT_CHAR) || force)) { + char* ptr = (char*)GetStructPointer(data_struct); + return ptr[index]; + } + return 0; +} +EQ2_8BitString PacketStruct::getType_EQ2_8BitString(DataStruct* data_struct, int32 index, bool force) { + if (data_struct && ((data_struct->GetType() == DATA_STRUCT_EQ2_8BIT_STRING) || force)) { + EQ2_8BitString* ptr = (EQ2_8BitString*)GetStructPointer(data_struct); + return ptr[index]; + } + EQ2_8BitString ret; + ret.size = 0; + return ret; +} +EQ2_16BitString PacketStruct::getType_EQ2_16BitString(DataStruct* data_struct, int32 index, bool force) { + if (data_struct && ((data_struct->GetType() == DATA_STRUCT_EQ2_16BIT_STRING) || force)) { + EQ2_16BitString* ptr = (EQ2_16BitString*)GetStructPointer(data_struct); + return ptr[index]; + } + EQ2_16BitString ret; + ret.size = 0; + return ret; +} +EQ2_32BitString PacketStruct::getType_EQ2_32BitString(DataStruct* data_struct, int32 index, bool force) { + if (data_struct && ((data_struct->GetType() == DATA_STRUCT_EQ2_32BIT_STRING) || force)) { + EQ2_32BitString* ptr = (EQ2_32BitString*)GetStructPointer(data_struct); + return ptr[index]; + } + EQ2_32BitString ret; + ret.size = 0; + return ret; +} +EQ2_Color PacketStruct::getType_EQ2_Color(DataStruct* data_struct, int32 index, bool force) { + if (data_struct && ((data_struct->GetType() == DATA_STRUCT_COLOR) || force)) { + EQ2_Color* ptr = (EQ2_Color*)GetStructPointer(data_struct); + return ptr[index]; + } + EQ2_Color ret; + ret.blue = 0; + ret.red = 0; + ret.green = 0; + return ret; +} +EQ2_EquipmentItem PacketStruct::getType_EQ2_EquipmentItem(DataStruct* data_struct, int32 index, bool force) { + if (data_struct && ((data_struct->GetType() == DATA_STRUCT_EQUIPMENT) || force)) { + EQ2_EquipmentItem* ptr = (EQ2_EquipmentItem*)GetStructPointer(data_struct); + return ptr[index]; + } + EQ2_EquipmentItem ret; + ret.color.blue = 0; + ret.color.red = 0; + ret.color.green = 0; + ret.highlight.blue = 0; + ret.highlight.red = 0; + ret.highlight.green = 0; + ret.type = 0; + return ret; +} + +bool PacketStruct::SetOpcode(const char* new_opcode) { + opcode = EQOpcodeManager[0]->NameSearch(new_opcode); + if (opcode == OP_Unknown) { +#ifndef MINILOGIN + LogWrite(PACKET__ERROR, 0, "Packet", "Warning: PacketStruct '%s' uses an unknown opcode named '%s', this struct cannot be serialized directly.", GetName(), new_opcode); +#endif + return false; + } + return true; +} + +EQ2Packet* PacketStruct::serialize() { + serializePacket(); + + if (GetOpcode() != OP_Unknown) + return new EQ2Packet(GetOpcode(), getData(), getDataSize()); + else { +#ifndef MINILOGIN + LogWrite(PACKET__ERROR, 0, "Packet", "Warning: PacketStruct '%s' uses an unknown opcode and cannot be serialized directly.", GetName()); +#endif + return 0; + } +} + +EQ2Packet* PacketStruct::serializeCountPacket(int16 version, int8 offset, uchar* orig_packet, uchar* xor_packet) { + string* packet_data = serializeString(); + uchar* data = (uchar*)packet_data->c_str(); + int32 size = packet_data->size(); + uchar* packed_data = new uchar[size + 1000]; // this size + 20 is poorly defined, depending on the packet data, we could use a additional length of 1K+ + memset(packed_data, 0, size + 1000); + if (orig_packet && xor_packet) { + memcpy(xor_packet, data + 6, size - 6 - offset); + Encode(xor_packet, orig_packet, size - 6 - offset); + size = Pack(packed_data, xor_packet, size - 6 - offset, size + 1000, version); + } + else + size = Pack(packed_data, data + 6, packet_data->size() - 6 - offset, packet_data->size() + 1000, version); + uchar* combined = new uchar[size + sizeof(int16) + offset]; + memset(combined, 0, size + sizeof(int16) + offset); + uchar* ptr = combined; + memcpy(ptr, data, sizeof(int16)); + ptr += sizeof(int16); + memcpy(ptr, packed_data, size); + if (offset > 0) { + ptr += size; + uchar* ptr2 = data; + ptr2 += packet_data->size() - offset; + memcpy(ptr, ptr2, offset); + } + EQ2Packet* app = new EQ2Packet(GetOpcode(), combined, size + sizeof(int16) + offset); + safe_delete_array(packed_data); + safe_delete_array(combined); + return app; +} + +bool PacketStruct::IsSubPacket() { + return sub_packet; +} +void PacketStruct::IsSubPacket(bool new_val) { + sub_packet = new_val; +} +int32 PacketStruct::GetSubPacketSize() { + return sub_packet_size; +} +void PacketStruct::SetSubPacketSize(int32 new_size) { + sub_packet_size = new_size; +} +void* PacketStruct::GetStructPointer(DataStruct* data_struct, bool erase) { + try { + map::iterator tmpitr = struct_data.find(data_struct); + if (tmpitr != struct_data.end()) { + if (erase) + struct_data.erase(data_struct); + return tmpitr->second; + } + else { + PacketStruct* packet = 0; + vector::iterator itr2; + for (itr2 = arrays.begin(); itr2 != arrays.end(); itr2++) { + packet = *itr2; + if (packet) { + void* tmp = packet->GetStructPointer(data_struct, erase); + if (tmp != 0) + return tmp; + } + } + } + } + catch (...) { + cout << "Caught Exception...\n"; + } + return 0; +} + +vector PacketStruct::GetDataStructs() { + vector ret; + DataStruct* ds = 0; + vector::iterator itr; + for (itr = structs.begin(); itr != structs.end(); itr++) { + ds = *itr; + if (ds->GetType() == DATA_STRUCT_ARRAY) { + int32 size = GetArraySize(ds, 0); + PacketStruct* ps = GetPacketStructByName(ds->GetName()); + if (ps && ps->GetSubPacketSize() != size) { + ps->reAddAll(size); + } + if (ps) { + vector ret2 = ps->GetDataStructs(); + vector::iterator itr2; + for (itr2 = ret2.begin(); itr2 != ret2.end(); itr2++) { + ret.push_back(*itr2); + } + } + } + else if (ds->GetLength() == 0 && ds->GetType() == DATA_STRUCT_ARRAY) { + int32 size = GetArraySize(ds, 0); + PacketStruct* ps = GetPacketStructByName(ds->GetName()); + if (ps && ps->GetSubPacketSize() != size) { + ps->reAddAll(size); + } + if (ps) { + vector ret2 = ps->GetDataStructs(); + vector::iterator itr2; + for (itr2 = ret2.begin(); itr2 != ret2.end(); itr2++) { + ret.push_back(*itr2); + } + } + } + else + ret.push_back(ds); + } + return ret; +} + +void PacketStruct::PrintPacket() { + DataStruct* ds = 0; + vector::iterator itr; + for (itr = structs.begin(); itr != structs.end(); itr++) { + ds = *itr; + if (!ds->AddToStruct()) + continue; + for (int16 i = 0; i < ds->GetLength(); i++) { + cout << "Name: " << ds->GetName() << " \tIndex: " << i << " \tType: "; + switch (ds->GetType()) { + case DATA_STRUCT_INT8: + printf("int8\t\tData: %i", getType_int8_ByName(ds->GetName(), i)); + break; + case DATA_STRUCT_INT16: + printf("int16\t\tData: %i", getType_int16_ByName(ds->GetName(), i)); + break; + case DATA_STRUCT_INT32: + printf("int32\t\tData: %u", getType_int32_ByName(ds->GetName(), i)); + break; + case DATA_STRUCT_INT64: + printf("int64\t\tData: %llu", getType_int64_ByName(ds->GetName(), i)); + break; + case DATA_STRUCT_SINT8: + printf("sint8\t\tData: %i", getType_sint8_ByName(ds->GetName(), i)); + break; + case DATA_STRUCT_SINT16: + printf("sint16\t\tData: %i", getType_sint16_ByName(ds->GetName(), i)); + break; + case DATA_STRUCT_SINT32: + printf("sint32\t\tData: %i", getType_sint32_ByName(ds->GetName(), i)); + break; + case DATA_STRUCT_SINT64: + printf("sint64\t\tData: %lli", getType_sint64_ByName(ds->GetName(), i)); + break; + case DATA_STRUCT_CHAR: + printf("char\t\tData: %c", getType_char_ByName(ds->GetName(), i)); + break; + case DATA_STRUCT_FLOAT: + printf("float\t\tData: %f", getType_float_ByName(ds->GetName(), i)); + break; + case DATA_STRUCT_DOUBLE: + printf("double\t\tData: %f", getType_double_ByName(ds->GetName(), i)); + break; + case DATA_STRUCT_EQ2_8BIT_STRING: + printf("EQ2_8BitString\tData: %s", getType_EQ2_8BitString_ByName(ds->GetName(), i).data.c_str()); + break; + case DATA_STRUCT_EQ2_16BIT_STRING: + printf("EQ2_16BitString\tData: %s", getType_EQ2_16BitString_ByName(ds->GetName(), i).data.c_str()); + break; + case DATA_STRUCT_EQ2_32BIT_STRING: { + printf("EQ2_32BitString\tData: %s", getType_EQ2_32BitString_ByName(ds->GetName(), i).data.c_str()); + break; + } + case DATA_STRUCT_ITEM: { + if (ds->GetItemSize() > 0) { + DumpPacket((uchar*)GetStructPointer(ds), ds->GetItemSize()); + } + break; + } + case DATA_STRUCT_ARRAY: { + int32 size = GetArraySize(ds, 0); + PacketStruct* ps = GetPacketStructByName(ds->GetName()); + if (ps && ps->GetSubPacketSize() != size) { + ps->reAddAll(size); + } + if (ps) { + cout << "Array:\tData: \n"; + ps->PrintPacket(); + } + break; + } + case DATA_STRUCT_COLOR: { + cout.unsetf(ios_base::dec); + cout.setf(ios_base::hex); + printf("EQ2_Color\tData: "); + EQ2_Color tmp = getType_EQ2_Color_ByName(ds->GetName(), i); + printf("R: %i", tmp.red); + printf(", G: %i", tmp.green); + printf(", B: %i", tmp.blue); + break; + } + case DATA_STRUCT_EQUIPMENT: { + cout.unsetf(ios_base::dec); + cout.setf(ios_base::hex); + printf("EQ2_EquipmentItem\tData: "); + EQ2_EquipmentItem tmp = getType_EQ2_EquipmentItem_ByName(ds->GetName(), i); + printf("type: "); + printf(" ,color R: %i", tmp.color.red); + printf(" ,color G: %i", tmp.color.green); + printf(" ,color B: %i", tmp.color.blue); + printf(" ,hl R: %i", tmp.highlight.red); + printf(" ,hl G: %i", tmp.highlight.green); + printf(" ,hl B: %i", tmp.highlight.blue); + break; + } + } + cout << endl; + } + if (ds->GetLength() == 0 && ds->GetType() == DATA_STRUCT_ARRAY) { + int32 size = GetArraySize(ds, 0); + PacketStruct* ps = GetPacketStructByName(ds->GetName()); + if (ps && ps->GetSubPacketSize() != size) { + ps->reAddAll(size); + } + if (ps) { + cout << "Array:\tData: \n"; + ps->PrintPacket(); + } + } + } +} + +void PacketStruct::LoadFromPacketStruct(PacketStruct* packet, char* substruct_name) { + vector::iterator itr; + DataStruct* ds = 0; + char name[512]; + + //scatman (1/30/2012): these declarations are here to get rid of a linux compile error "taking address of temporary" + EQ2_8BitString str8; + EQ2_16BitString str16; + EQ2_32BitString str32; + EQ2_EquipmentItem equip; + + for (itr = structs.begin(); itr != structs.end(); itr++) { + ds = *itr; + for (int16 i = 0; i < ds->GetLength(); i++) { + memset(name, 0, sizeof(name)); + + if (substruct_name) + snprintf(name, sizeof(name), "%s_%s_0", substruct_name, ds->GetName()); + else + strncpy(name, ds->GetName(), sizeof(name)); + name[sizeof(name) - 1] = '\0'; + + switch (ds->GetType()) { + case DATA_STRUCT_INT8: + setData(ds, packet->getType_int8_ByName(name, i), i); + break; + case DATA_STRUCT_SINT8: + setData(ds, packet->getType_sint8_ByName(name, i), i); + break; + case DATA_STRUCT_CHAR: + setData(ds, packet->getType_char_ByName(name, i), i); + break; + case DATA_STRUCT_SINT16: + setData(ds, packet->getType_sint16_ByName(name, i), i); + break; + case DATA_STRUCT_INT16: + setData(ds, packet->getType_int16_ByName(name, i), i); + break; + case DATA_STRUCT_INT32: + setData(ds, packet->getType_int32_ByName(name, i), i); + break; + case DATA_STRUCT_INT64: + setData(ds, packet->getType_int64_ByName(name, i), i); + break; + case DATA_STRUCT_SINT32: + setData(ds, packet->getType_sint32_ByName(name, i), i); + break; + case DATA_STRUCT_SINT64: + setData(ds, packet->getType_sint64_ByName(name, i), i); + break; + case DATA_STRUCT_FLOAT: + setData(ds, packet->getType_float_ByName(name, i), i); + break; + case DATA_STRUCT_DOUBLE: + setData(ds, packet->getType_double_ByName(name, i), i); + break; + case DATA_STRUCT_EQ2_8BIT_STRING: + str8 = packet->getType_EQ2_8BitString_ByName(name, i); + setData(ds, &str8, i); + break; + case DATA_STRUCT_EQ2_16BIT_STRING: + str16 = packet->getType_EQ2_16BitString_ByName(name, i); + setData(ds, &str16, i); + break; + case DATA_STRUCT_EQ2_32BIT_STRING: + str32 = packet->getType_EQ2_32BitString_ByName(name, i); + setData(ds, &str32, i); + break; + case DATA_STRUCT_ARRAY: { + int32 size = GetArraySize(ds, 0); + PacketStruct* ps = GetPacketStructByName(ds->GetName()); + if (ps && size > 0) + ps->LoadFromPacketStruct(packet, substruct_name); + break; + } + case DATA_STRUCT_COLOR: + setColor(ds, packet->getType_EQ2_Color_ByName(name, i), i); + break; + case DATA_STRUCT_EQUIPMENT: + equip = packet->getType_EQ2_EquipmentItem_ByName(name, i); + setEquipmentByName(ds->GetName(), &equip, i); + break; + default: + break; + } + } + } +} + +#ifdef WORLD +void PacketStruct::setItem(DataStruct* ds, Item* item, Player* player, int32 index, sint8 offset, bool loot_item, bool make_empty_item_packet, bool inspect) { + if (!ds) + return; + uchar* ptr = (uchar*)GetStructPointer(ds); + + if(!item) { + if(make_empty_item_packet) { + if(client_version <= 373) { + // for player inspection this will offset the parts of the packet that have no items + uchar bogusItemBuffer[] = {0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x64,0x3C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + int sizeOfArray = sizeof(bogusItemBuffer) / sizeof(bogusItemBuffer[0]); + ds->SetItemSize(sizeOfArray); + memcpy(ptr, bogusItemBuffer, sizeOfArray); + } + else if(client_version <= 561) { + // for player inspection this will offset the parts of the packet that have no items + uchar bogusItemBuffer[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x8C,0x5A,0xF1,0xD2,0x8C,0x5A,0xF1,0xD2,0x01,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00}; + int sizeOfArray = sizeof(bogusItemBuffer) / sizeof(bogusItemBuffer[0]); + ds->SetItemSize(sizeOfArray); + memcpy(ptr, bogusItemBuffer, sizeOfArray); + } + else { + // for player inspection this will offset the parts of the packet that have no items + uchar bogusItemBuffer[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00 /*0x68 was item flags*/,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x64,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + int sizeOfArray = sizeof(bogusItemBuffer) / sizeof(bogusItemBuffer[0]); + ds->SetItemSize(sizeOfArray); + memcpy(ptr, bogusItemBuffer, sizeOfArray); + } + } + return; + } + PacketStruct* packet = item->PrepareItem(client_version, false, loot_item, inspect); + if (packet) { + int16 item_version = GetItemPacketType(packet->GetVersion()); + // newer clients can handle the item structure without the loot_item flag set to true, older clients like DoF need a smaller subpacket of item + item->serialize(packet, true, player, item_version, 0, (packet->GetVersion() <= 561) ? loot_item : false, inspect); + + string* generic_string_data = packet->serializeString(); + int32 size = generic_string_data->length(); // had to set to 81 + int32 actual_length = size; + + if(client_version <= 373 && make_empty_item_packet && inspect) { + size += 2; // end padding for the specialized item (used for player inspection) + } + else if(offset > 12) { + size += 6; // end padding for the specialized item (used for player inspection) + } + //DumpPacket((uchar*)generic_string_data->c_str(), size); + if (size <= 13) + { + ds->SetIsSet(false); + return; + } + size -= (9 + offset); + if (client_version > 561 && item->IsBag() == false && item->IsBauble() == false && item->IsFood() == false && (offset == 0 || offset == -1 || offset == 2)) + size = (size * 2) - 5; + uchar* out_data = new uchar[size + 1]; + memset(out_data, 0, size+1); + uchar* out_ptr = out_data; + memcpy(out_ptr, (uchar*)generic_string_data->c_str() + (9 + offset), actual_length - (9 + offset)); + //DumpPacket((uchar*)generic_string_data->c_str() + (9 + offset), size); + //without these it will prompt for your character name + if (offset == 0 || offset == -1 || offset == 2) { + if (client_version <= 561 && item->details.count > 0) + out_data[0] = item->details.count; + else + out_data[0] = 1; + } + // + out_ptr += generic_string_data->length() - (10 + offset); + if (client_version > 561 && item->IsBag() == false && item->IsBauble() == false && item->IsFood() == false && (offset == 0 || offset == -1 || offset == 2)) { + out_data[4] = 0x80; + memcpy(out_ptr, (uchar*)generic_string_data->c_str() + (13 + offset), generic_string_data->length() - (13 + offset)); + } + ds->SetItemSize(size); + memcpy(ptr, out_data, size); + safe_delete_array(out_data); + delete packet; + } + //DumpPacket(ptr2, ds->GetItemSize()); +} +void PacketStruct::setItemByName(const char* name, Item* item, Player* player, int32 index, sint8 offset, bool loot_item, bool make_empty_item_packet, bool inspect) { + setItem(findStruct(name, index), item, player, index, offset, loot_item, make_empty_item_packet, inspect); +} +void PacketStruct::setItemArrayDataByName(const char* name, Item* item, Player* player, int32 index1, int32 index2, sint8 offset, bool loot_item, bool make_empty_item_packet, bool inspect) { + char tmp[10] = { 0 }; + sprintf(tmp, "_%i", index1); + string name2 = string(name).append(tmp); + setItem(findStruct(name2.c_str(), index1, index2), item, player, index2, offset, loot_item, make_empty_item_packet, inspect); +} + +void PacketStruct::ResetData() { + vector::iterator itr; + for (itr = structs.begin(); itr != structs.end(); itr++) { + DataStruct* ds = *itr; + void* ptr = GetStructPointer(ds); + if (!ptr) + continue; + switch (ds->GetType()) + { + case DATA_STRUCT_EQ2_8BIT_STRING: { + EQ2_8BitString* real_ptr = (EQ2_8BitString*)ptr; + real_ptr->size = 0; + real_ptr->data.clear(); + break; + } + case DATA_STRUCT_EQ2_16BIT_STRING: { + EQ2_16BitString* real_ptr = (EQ2_16BitString*)ptr; + real_ptr->size = 0; + real_ptr->data.clear(); + break; + } + case DATA_STRUCT_EQ2_32BIT_STRING: { + EQ2_32BitString* real_ptr = (EQ2_32BitString*)ptr; + real_ptr->size = 0; + real_ptr->data.clear(); + break; + } + case DATA_STRUCT_EQUIPMENT: { + memset(ptr, 0, sizeof(EQ2_EquipmentItem) * ds->GetLength()); + break; + } + case DATA_STRUCT_DOUBLE: { + memset(ptr, 0, sizeof(double) * ds->GetLength()); + break; + } + case DATA_STRUCT_FLOAT: { + memset(ptr, 0, sizeof(float) * ds->GetLength()); + break; + } + case DATA_STRUCT_INT8: { + memset(ptr, 0, sizeof(int8) * ds->GetLength()); + break; + } + case DATA_STRUCT_INT16: { + memset(ptr, 0, sizeof(int16) * ds->GetLength()); + break; + } + case DATA_STRUCT_INT32: { + memset(ptr, 0, sizeof(int32) * ds->GetLength()); + break; + } + case DATA_STRUCT_INT64: { + memset(ptr, 0, sizeof(int64) * ds->GetLength()); + break; + } + case DATA_STRUCT_SINT8: { + memset(ptr, 0, sizeof(sint8) * ds->GetLength()); + break; + } + case DATA_STRUCT_SINT16: { + memset(ptr, 0, sizeof(sint16) * ds->GetLength()); + break; + } + case DATA_STRUCT_SINT32: { + memset(ptr, 0, sizeof(sint32) * ds->GetLength()); + break; + } + case DATA_STRUCT_SINT64: { + memset(ptr, 0, sizeof(sint64) * ds->GetLength()); + break; + } + case DATA_STRUCT_ITEM: { + memset(ptr, 0, 10000 * ds->GetLength()); + break; + } + case DATA_STRUCT_CHAR: { + memset(ptr, 0, sizeof(char) * ds->GetLength()); + break; + } + case DATA_STRUCT_COLOR: { + memset(ptr, 0, sizeof(EQ2_Color) * ds->GetLength()); + break; + } + } + } + vector::iterator itr2; + for (itr2 = arrays.begin(); itr2 != arrays.end(); itr2++) + (*itr2)->ResetData(); +} +#endif diff --git a/source/common/PacketStruct.h b/source/common/PacketStruct.h new file mode 100644 index 0000000..98bca7d --- /dev/null +++ b/source/common/PacketStruct.h @@ -0,0 +1,513 @@ +/* + 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 . +*/ +#ifndef __EQ2_PACKETSTRUCT__ +#define __EQ2_PACKETSTRUCT__ +#include "types.h" +#include "DataBuffer.h" +#include "opcodemgr.h" + +#include +#include +#ifdef WORLD +class Item; +class Player; +#endif +extern mapEQOpcodeManager; +using namespace std; + +#define DATA_STRUCT_NONE 0 +#define DATA_STRUCT_INT8 1 +#define DATA_STRUCT_INT16 2 +#define DATA_STRUCT_INT32 3 +#define DATA_STRUCT_INT64 4 +#define DATA_STRUCT_FLOAT 5 +#define DATA_STRUCT_DOUBLE 6 +#define DATA_STRUCT_COLOR 7 +#define DATA_STRUCT_SINT8 8 +#define DATA_STRUCT_SINT16 9 +#define DATA_STRUCT_SINT32 10 +#define DATA_STRUCT_CHAR 11 +#define DATA_STRUCT_EQ2_8BIT_STRING 12 +#define DATA_STRUCT_EQ2_16BIT_STRING 13 +#define DATA_STRUCT_EQ2_32BIT_STRING 14 +#define DATA_STRUCT_EQUIPMENT 15 +#define DATA_STRUCT_ARRAY 16 +#define DATA_STRUCT_ITEM 17 +#define DATA_STRUCT_SINT64 18 + +class DataStruct { +public: + DataStruct(); + DataStruct(DataStruct* data_struct); + DataStruct(const char* new_name, int8 new_type, int32 new_length = 1, int8 new_type2 = DATA_STRUCT_NONE); + DataStruct(const char* new_name, const char* new_type, int32 new_length = 1, const char* new_type2 = 0); + DataStruct(const char* new_name, int32 new_length); + void SetType(const char* new_type, int8* output_type); + void SetType(int8 new_type); + void SetName(const char* new_name); + void SetLength(int32 new_length); + void SetArraySizeVariable(const char* new_name); + void SetDefaultValue(int8 new_val); + void SetMaxArraySize(int8 size); + void SetOversized(int8 val); + void SetOversizedByte(int8 val); + void SetAddToStruct(bool val); + void SetAddType(int8 new_type); + void SetPackedIndex(int8 new_index); + void SetPackedSizeVariable(const char* new_name); + void SetPacked(const char* value); + void SetItemSize(int32 val); + void SetIfSetVariable(const char* variable); + void SetIfNotSetVariable(const char* variable); + void SetIfEqualsVariable(const char* variable); + void SetIfNotEqualsVariable(const char* variable); + void SetIfFlagSetVariable(const char* variable); + void SetIfFlagNotSetVariable(const char* variable); + void SetIsSet(bool val); + void SetIsOptional(bool val); + + int8 GetPackedIndex(); + const char* GetPackedSizeVariable(); + const char* GetArraySizeVariable(); + int8 GetDefaultValue(); + int8 GetOversized(); + int8 GetOversizedByte(); + int8 GetMaxArraySize(); + int8 GetType(); + int8 GetType2(); + const char* GetName(); + string GetStringName(); + int32 GetLength(); + bool AddToStruct(); + int8 GetAddType(); + int32 GetItemSize(); + bool GetIfSet(); + const char* GetIfSetVariable(); + bool GetIfNotSet(); + const char* GetIfNotSetVariable(); + bool GetIfEquals(); + const char* GetIfEqualsVariable(); + bool GetIfNotEquals(); + const char* GetIfNotEqualsVariable(); + bool GetIfFlagSet(); + const char* GetIfFlagSetVariable(); + bool GetIfFlagNotSet(); + const char* GetIfFlagNotSetVariable(); + bool IsSet(); + bool IsOptional(); + int32 GetDataSizeInBytes(); + string AppendVariable(string orig, const char* val); + void AddIfSetVariable(const char* val) { + if (val) { + if_set_variable = AppendVariable(if_set_variable, val); + is_set = true; + } + } + void AddIfNotSetVariable(const char* val) { + if (val) { + if_not_set_variable = AppendVariable(if_not_set_variable, val); + if_not_set = true; + } + } + +private: + bool is_set; + bool if_not_set; + bool optional; + bool if_set; + bool if_not_equals; + bool if_equals; + bool if_flag_set; + bool if_flag_not_set; + string if_flag_not_set_variable; + string if_flag_set_variable; + string if_not_equals_variable; + string if_equals_variable; + string if_not_set_variable; + string if_set_variable; + int8 oversized; + int8 oversized_byte; + bool add; + int8 addType; + int8 maxArraySize; + string array_size_variable; + string name; + int8 type; + int8 default_value; + int8 type2; + int32 length; + int32 item_size; +}; +class PacketStruct : public DataBuffer { +public: + PacketStruct(); + PacketStruct(PacketStruct* packet, bool sub); + PacketStruct(PacketStruct* packet, int16 in_client_version); + ~PacketStruct(); + void add(DataStruct* data); + void reAddAll(int32 length); + void add(PacketStruct* packet_struct); + void addPacketArrays(PacketStruct* packet); + void deletePacketArrays(PacketStruct* packet); + void deleteDataStructs(vector* data_structs); + void setSmallStringByName(const char* name, const char* text, int32 index = 0); + void setMediumStringByName(const char* name, const char* text, int32 index = 0); + void setLargeStringByName(const char* name, const char* text, int32 index = 0); + void setSmallString(DataStruct* data_struct, const char* text, int32 index = 0); + void setMediumString(DataStruct* data_struct, const char* text, int32 index = 0); + void setLargeString(DataStruct* data_struct, const char* text, int32 index = 0); + void renameSubstructArray(const char* substruct, int32 index); + template void setSubstructSubstructDataByName(const char* substruct_name1, const char* substruct_name2, const char* name, Data data, int32 substruct_index1 = 0, int32 substruct_index2 = 0, int32 index = 0) { + char tmp[15] = { 0 }; + sprintf(tmp, "_%i_%i", substruct_index1, substruct_index2); + string name2 = string(substruct_name1).append("_").append(substruct_name2).append("_").append(name).append(tmp); + setData(findStruct(name2.c_str(), index), data, index); + } + template void setSubstructDataByName(const char* substruct_name, const char* name, Data data, int32 substruct_index = 0, int32 index = 0) { + char tmp[10] = { 0 }; + sprintf(tmp, "_%i", substruct_index); + string name2 = string(substruct_name).append("_").append(name).append(tmp); + setData(findStruct(name2.c_str(), index), data, index); + } + template void setSubstructColorByName(const char* substruct_name, const char* name, Data data, int32 substruct_index = 0, int32 index = 0) { + char tmp[10] = { 0 }; + sprintf(tmp, "_%i", substruct_index); + string name2 = string(substruct_name).append("_").append(name).append(tmp); + setColor(findStruct(name2.c_str(), index), data, index); + } + template void setSubstructArrayDataByName(const char* substruct_name, const char* name, Data data, int32 index = 0, int32 substruct_index = 0) { + char tmp[10] = { 0 }; + sprintf(tmp, "_%i", substruct_index); + string name2 = string(substruct_name).append("_").append(name).append(tmp); + setData(findStruct(name2.c_str(), substruct_index, index), data, index); + } + template void setSubstructArrayColorByName(const char* substruct_name, const char* name, Data data, int32 substruct_index = 0, int32 index = 0) { + char tmp[10] = { 0 }; + sprintf(tmp, "_%i", substruct_index); + string name2 = string(substruct_name).append("_").append(name).append(tmp); + setColor(findStruct(name2.c_str(), index, substruct_index), data, index); + } + template void setDataByName(const char* name, Data data, int32 index = 0, bool use_second_type = false) { + setData(findStruct(name, index), data, index, use_second_type); + } + template void setDataByName(const char* name, Data* data, int32 index = 0, bool use_second_type = false) { + setData(findStruct(name, index), data, index, use_second_type); + } + template void setSubArrayDataByName(const char* name, Data data, int32 index1 = 0, int32 index2 = 0, int32 index3 = 0) { + char tmp[20] = { 0 }; + sprintf(tmp, "%i_%i", index1, index2); + string name2 = string(name).append(tmp); + setData(findStruct(name2.c_str(), index2, index3), data, index3); + } + template void setArrayDataByName(const char* name, Data data, int32 index1 = 0, int32 index2 = 0, bool use_second_type = false) { + char tmp[10] = { 0 }; + sprintf(tmp, "_%i", index1); + string name2 = string(name).append(tmp); + setData(findStruct(name2.c_str(), index1, index2), data, index2, use_second_type); + } + void setArrayAddToPacketByName(const char* name, bool new_val, int32 index1 = 0, int32 index2 = 0) { + char tmp[10] = { 0 }; + sprintf(tmp, "_%i", index1); + string name2 = string(name).append(tmp); + DataStruct* data = findStruct(name2.c_str(), index2); + if (data) + data->SetAddToStruct(new_val); + } + void setAddToPacketByName(const char* name, bool new_val, int32 index = 0) { + DataStruct* data = findStruct(name, index); + if (data) + data->SetAddToStruct(new_val); + } + void setAddTypePacketByName(const char* name, int8 new_val, int32 index = 0) { + DataStruct* data = findStruct(name, index); + if (data) + data->SetAddType(new_val); + } + const char* GetOpcodeType(); + bool IsSubPacket(); + void IsSubPacket(bool new_val); + int32 GetSubPacketSize(); + void SetSubPacketSize(int32 new_size); + void SetOpcodeType(const char* opcodeType); + int32 GetArraySizeByName(const char* name, int32 index); + int32 GetArraySize(DataStruct* data_struct, int32 index); + int32 GetArraySize(const char* name, int32 index); + void LoadFromPacketStruct(PacketStruct* packet, char* substruct_name = 0); + bool GetVariableIsSet(const char* name); + bool GetVariableIsNotSet(const char* name); + + int8 getType_int8_ByName(const char* name, int32 index = 0, bool force = false); + int16 getType_int16_ByName(const char* name, int32 index = 0, bool force = false); + int32 getType_int32_ByName(const char* name, int32 index = 0, bool force = false); + int64 getType_int64_ByName(const char* name, int32 index = 0, bool force = false); + sint8 getType_sint8_ByName(const char* name, int32 index = 0, bool force = false); + sint16 getType_sint16_ByName(const char* name, int32 index = 0, bool force = false); + sint32 getType_sint32_ByName(const char* name, int32 index = 0, bool force = false); + sint64 getType_sint64_ByName(const char* name, int32 index = 0, bool force = false); + float getType_float_ByName(const char* name, int32 index = 0, bool force = false); + double getType_double_ByName(const char* name, int32 index = 0, bool force = false); + char getType_char_ByName(const char* name, int32 index = 0, bool force = false); + EQ2_8BitString getType_EQ2_8BitString_ByName(const char* name, int32 index = 0, bool force = false); + EQ2_16BitString getType_EQ2_16BitString_ByName(const char* name, int32 index = 0, bool force = false); + EQ2_32BitString getType_EQ2_32BitString_ByName(const char* name, int32 index = 0, bool force = false); + EQ2_Color getType_EQ2_Color_ByName(const char* name, int32 index = 0, bool force = false); + EQ2_EquipmentItem getType_EQ2_EquipmentItem_ByName(const char* name, int32 index = 0, bool force = false); + + int8 getType_int8(DataStruct* data_struct, int32 index = 0, bool force = false); + int16 getType_int16(DataStruct* data_struct, int32 index = 0, bool force = false); + int32 getType_int32(DataStruct* data_struct, int32 index = 0, bool force = false); + int64 getType_int64(DataStruct* data_struct, int32 index = 0, bool force = false); + sint8 getType_sint8(DataStruct* data_struct, int32 index = 0, bool force = false); + sint16 getType_sint16(DataStruct* data_struct, int32 index = 0, bool force = false); + sint32 getType_sint32(DataStruct* data_struct, int32 index = 0, bool force = false); + sint64 getType_sint64(DataStruct* data_struct, int32 index = 0, bool force = false); + float getType_float(DataStruct* data_struct, int32 index = 0, bool force = false); + double getType_double(DataStruct* data_struct, int32 index = 0, bool force = false); + char getType_char(DataStruct* data_struct, int32 index = 0, bool force = false); + EQ2_8BitString getType_EQ2_8BitString(DataStruct* data_struct, int32 index = 0, bool force = false); + EQ2_16BitString getType_EQ2_16BitString(DataStruct* data_struct, int32 index = 0, bool force = false); + EQ2_32BitString getType_EQ2_32BitString(DataStruct* data_struct, int32 index = 0, bool force = false); + EQ2_Color getType_EQ2_Color(DataStruct* data_struct, int32 index = 0, bool force = false); + EQ2_EquipmentItem getType_EQ2_EquipmentItem(DataStruct* data_struct, int32 index = 0, bool force = false); + + void setDataType(DataStruct* data_struct, char data, int32 index); + void setDataType(DataStruct* data_struct, int8 data, int32 index); + void setDataType(DataStruct* data_struct, int16 data, int32 index); + void setDataType(DataStruct* data_struct, int32 data, int32 index); + void setDataType(DataStruct* data_struct, int64 data, int32 index); + void setDataType(DataStruct* data_struct, sint8 data, int32 index); + void setDataType(DataStruct* data_struct, sint16 data, int32 index); + void setDataType(DataStruct* data_struct, sint32 data, int32 index); + void setDataType(DataStruct* data_struct, sint64 data, int32 index); + void setDataType(DataStruct* data_struct, float data, int32 index); + void setDataType(DataStruct* data_struct, double data, int32 index); + void setData(DataStruct* data_struct, EQ2_8BitString* input_string, int32 index, bool use_second_type = false); + void setData(DataStruct* data_struct, EQ2_16BitString* input_string, int32 index, bool use_second_type = false); + void setData(DataStruct* data_struct, EQ2_32BitString* input_string, int32 index, bool use_second_type = false); + + template void setData(DataStruct* data_struct, Data* data, int32 index, bool use_second_type = false) { + if (!data_struct) + return; + data_struct->SetIsOptional(false); + int8 type_to_use = (use_second_type) ? data_struct->GetType2() : data_struct->GetType(); + if (type_to_use >= DATA_STRUCT_EQ2_8BIT_STRING && type_to_use <= DATA_STRUCT_EQ2_32BIT_STRING) { + if (type_to_use == DATA_STRUCT_EQ2_8BIT_STRING) { + setSmallString(data_struct, data, index); + } + else if (type_to_use == DATA_STRUCT_EQ2_16BIT_STRING) { + setMediumString(data_struct, data, index); + } + else { + setLargeString(data_struct, data, index); + } + } + else { + if (data_struct && index == 0 && data_struct->GetLength() > 1) { + if (type_to_use == DATA_STRUCT_CHAR) { + for (int32 i = 0; data && i < data_struct->GetLength() && i < strlen(data); i++) + setData(data_struct, data[i], i); + } + else { + for (int32 i = 0; i < data_struct->GetLength(); i++) + setData(data_struct, data[i], i); + } + } + else + setData(data_struct, *data, index); + } + } + template void setData(DataStruct* data_struct, Data data, int32 index, bool use_second_type = false) { + if (data_struct && index < data_struct->GetLength()) { + data_struct->SetIsOptional(false); + int8 type_to_use = (use_second_type) ? data_struct->GetType2() : data_struct->GetType(); + if (use_second_type) { + // Need to figure out why type2 always seems to be 205 + // since only items use type2 for now just hardcoded the value needed (BAD!!!) + //type_to_use = DATA_STRUCT_SINT16; // 9; + data_struct->SetType(type_to_use); + } + switch (type_to_use) { + case DATA_STRUCT_INT8: + setDataType(data_struct, (int8)data, index); + break; + case DATA_STRUCT_INT16: + setDataType(data_struct, (int16)data, index); + break; + case DATA_STRUCT_INT32: + setDataType(data_struct, (int32)data, index); + break; + case DATA_STRUCT_INT64: + setDataType(data_struct, (int64)data, index); + break; + case DATA_STRUCT_SINT8: + setDataType(data_struct, (sint8)data, index); + break; + case DATA_STRUCT_SINT16: + setDataType(data_struct, (sint16)data, index); + break; + case DATA_STRUCT_SINT32: + setDataType(data_struct, (sint32)data, index); + break; + case DATA_STRUCT_SINT64: + setDataType(data_struct, (sint64)data, index); + break; + case DATA_STRUCT_CHAR: + setDataType(data_struct, (char)data, index); + break; + case DATA_STRUCT_FLOAT: + setDataType(data_struct, (float)data, index); + break; + case DATA_STRUCT_DOUBLE: + setDataType(data_struct, (double)data, index); + break; + case DATA_STRUCT_COLOR: + setColor(data_struct, *((EQ2_Color*)&data), index); + break; + case DATA_STRUCT_EQUIPMENT: + setEquipmentByName(data_struct, *((EQ2_EquipmentItem*)&data), index); + break; + case DATA_STRUCT_ITEM: + break; + } + } + } + + template void setSubArrayLengthByName(const char* name, Data data, int32 index1 = 0, int32 index2 = 0) { + char tmp[10] = { 0 }; + sprintf(tmp, "_%i", index1); + string name2 = string(name).append(tmp); + DataStruct* data_struct = findStruct(name2.c_str(), index2); + setData(data_struct, data, index2); + UpdateArrayByArrayLength(data_struct, index2, data); + } + template void setArrayLengthByName(const char* name, Data data, int32 index = 0) { + DataStruct* data_struct = findStruct(name, index); + setData(data_struct, data, index); + UpdateArrayByArrayLength(data_struct, index, data); + } + template void setSubstructArrayLengthByName(const char* substruct_name, const char* name, Data data, int32 substruct_index = 0, int32 index = 0) { + char tmp[10] = { 0 }; + sprintf(tmp, "_%i", substruct_index); + string name2 = string(substruct_name).append("_").append(name).append(tmp); + + DataStruct* data_struct = findStruct(name2.c_str(), index); + setData(data_struct, data, index); + UpdateArrayByArrayLength(data_struct, index, data); + } + void UpdateArrayByArrayLengthName(const char* name, int32 index, int32 size); + void UpdateArrayByArrayLength(DataStruct* data_struct, int32 index, int32 size); + bool StructLoadData(DataStruct* data_struct, void* data, int32 len, bool useType2 = false, bool create_color = false); + bool LoadPacketData(uchar* data, int32 data_len, bool create_color = false); + bool CheckFlagExists(const char* name); + + void setColorByName(const char* name, EQ2_Color* data, int32 index = 0) { + if (data) + setColorByName(name, data->red, data->green, data->blue, index); + } + void setColorByName(const char* name, EQ2_Color data, int32 index = 0) { + setColorByName(name, data.red, data.green, data.blue, index); + } + void setColor(DataStruct* data_struct, EQ2_Color data, int32 index = 0) { + if (data_struct) { + EQ2_Color* ptr = (EQ2_Color*)struct_data[data_struct]; + ptr[index] = data; + } + } + void setColorByName(const char* name, int8 red, int8 green, int8 blue, int32 index = 0) { + setColor(findStruct(name, index), red, green, blue, index); + } + void setColor(DataStruct* data, int8 red, int8 green, int8 blue, int32 index); + void setEquipmentByName(DataStruct* data_struct, EQ2_EquipmentItem data, int32 index = 0) { + if (data_struct) { + EQ2_EquipmentItem* ptr = (EQ2_EquipmentItem*)struct_data[data_struct]; + ptr[index] = data; + } + } +#ifdef WORLD + void setItem(DataStruct* ds, Item* item, Player* player, int32 index, sint8 offset = 0, bool loot_item = false, bool make_empty_item_packet = false, bool inspect = false); + void setItemByName(const char* name, Item* item, Player* player, int32 index = 0, sint8 offset = 0, bool loot_item = false, bool make_empty_item_packet = false, bool inspect = false); + void setItemArrayDataByName(const char* name, Item* item, Player* player, int32 index1 = 0, int32 index2 = 0, sint8 offset = 0, bool loot_item = false, bool make_empty_item_packet = false, bool inspect = false); +#endif + void setEquipmentByName(const char* name, EQ2_EquipmentItem data, int32 index = 0) { + setEquipmentByName(findStruct(name, index), data, index); + } + void setEquipmentByName(const char* name, EQ2_EquipmentItem* data, int32 size) { + DataStruct* data_struct = findStruct(name, 0); + if (data_struct) { + for (int32 i = 0; i < size; i++) + setEquipmentByName(data_struct, data[i], i); + } + } + void setEquipmentByName(const char* name, int32 type, int8 c_red, int8 c_blue, int8 c_green, int8 h_red, int8 h_blue, int8 h_green, int32 index = 0) { + setEquipment(findStruct(name, index), type, c_red, c_blue, c_green, h_red, h_blue, h_green, index); + } + void setEquipment(DataStruct* data, int16 type, int8 c_red, int8 c_blue, int8 c_green, int8 h_red, int8 h_blue, int8 h_green, int32 index); + void remove(DataStruct* data); + vector* getStructs() { return &structs; } + DataStruct* findStruct(const char* name, int32 index); + DataStruct* findStruct(const char* name, int32 index1, int32 index2); + void remove(const char* name); + void remove(int32 position); + void serializePacket(bool clear = true); + + void AddSerializedData(DataStruct* data, int32 index = 0, string* datastring = 0); + EQ2Packet* serialize(); + EQ2Packet* serializeCountPacket(int16 version, int8 offset = 0, uchar* orig_packet = 0, uchar* xor_packet = 0); + string* serializeString(); + int32 GetVersion() { return version; } + void SetVersion(int32 in_version) { version = in_version; } + bool SetOpcode(const char* new_opcode); + EmuOpcode GetOpcode() { return opcode; } + int16 GetOpcodeValue(int16 client_version); + const char* GetName() { return name.c_str(); } + void SetName(const char* in_name) { name = string(in_name); } + bool LoadedSuccessfully() { return loadedSuccessfully; } + bool IsStringValueType(string in_name, int32 index); + bool IsColorValueType(string in_name, int32 index); + int32 GetTotalPacketSize(); + PacketStruct* GetPacketStructByName(const char* name); + void* GetStructPointer(DataStruct* data_struct, bool erase = false); + void PrintPacket(); + string GetSQLQuery(const char* table_name); + vector GetDataStructs(); + void AddPackedData(); + void ResetData(); + void AddFlag(const char* name); + +private: + PacketStruct* parent; + int32 sub_packet_size; + string opcode_type; + bool sub_packet; + bool loadedSuccessfully; + string name; + EmuOpcode opcode; + int16 version; + int16 client_version; + vector arrays; + vector flags; + map struct_data; + map packed_data; + map struct_map; + vector structs; + vector orig_structs; + vector orig_packets; +}; +#endif \ No newline at end of file diff --git a/source/common/RC4.cpp b/source/common/RC4.cpp new file mode 100644 index 0000000..15d9f78 --- /dev/null +++ b/source/common/RC4.cpp @@ -0,0 +1,93 @@ +/* + 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 . +*/ +#include "RC4.h" +#include + +static bool g_bInitStateInitialized = false; +static uchar g_byInitState[256]; + +RC4::RC4(int64 nKey) +{ + if( !g_bInitStateInitialized ) + { + for(int16 i = 0; i < 256; i++ ) + g_byInitState[i] = i; + } + Init(nKey); +} + +RC4::~RC4() +{ +} + +void RC4::Init(int64 nKey) +{ + memcpy(m_state, g_byInitState, 256); + m_x = 0; + m_y = 0; + + ulong dwKeyIndex = 0; + ulong dwStateIndex = 0; + uchar* pKey = (uchar*)&nKey; + for(int16 i = 0; i < 256; i++ ) + { + ulong dwTemp = m_state[i]; + dwStateIndex += pKey[dwKeyIndex] + dwTemp; + dwStateIndex &= 0xFF; + m_state[i] = m_state[dwStateIndex]; + m_state[dwStateIndex] = (uchar)dwTemp; + dwKeyIndex++; + dwKeyIndex &= 7; + } +} + +// A = m_state[X + 1] +// B = m_state[Y + A] +// C ^= m_state[(A + B)] + +// X = 20 +// Y = ? +// C = 0 +// m_state[(A + B)] = Cypher Byte + +void RC4::Cypher(uchar* pBuffer, int32 nLength) +{ + int32 nOffset = 0; + uchar byKey1 = m_x; + uchar byKey2 = m_y; + if( nLength > 0 ) + { + do + { + byKey1++; + uchar byKeyVal1 = m_state[byKey1]; + + byKey2 += byKeyVal1; + uchar byKeyVal2 = m_state[byKey2]; + + m_state[byKey1] = byKeyVal2; + m_state[byKey2] = byKeyVal1; + + pBuffer[nOffset++] ^= m_state[(byKeyVal1 + byKeyVal2) & 0xFF]; + } while( nOffset < nLength ); + } + m_x = byKey1; + m_y = byKey2; +} diff --git a/source/common/RC4.h b/source/common/RC4.h new file mode 100644 index 0000000..52a3ea9 --- /dev/null +++ b/source/common/RC4.h @@ -0,0 +1,38 @@ +/* + 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 . +*/ +#ifndef _EQ2_RC4_H +#define _EQ2_RC4_H +#include "../common/types.h" +class RC4 +{ +public: + RC4(int64 nKey); + ~RC4(); + + void Init(int64 nKey); + void Cypher(uchar* pData, int32 nLen); + +private: + uchar m_state[256]; + uchar m_x; + uchar m_y; +}; +#endif + diff --git a/source/common/TCPConnection.cpp b/source/common/TCPConnection.cpp new file mode 100644 index 0000000..81493b2 --- /dev/null +++ b/source/common/TCPConnection.cpp @@ -0,0 +1,1729 @@ +/* + 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 . +*/ +#include "../common/debug.h" + +#include +using namespace std; +#include +#include +#include +using namespace std; + +#include "TCPConnection.h" +#include "../common/servertalk.h" +#include "../common/timer.h" +#include "../common/packet_dump.h" +#include "Log.h" + +#ifdef FREEBSD //Timothy Whitman - January 7, 2003 +#define MSG_NOSIGNAL 0 +#endif + +#ifdef WIN32 +InitWinsock winsock; +#endif + +#define LOOP_GRANULARITY 3 //# of ms between checking our socket/queues +#define SERVER_LOOP_GRANULARITY 3 //# of ms between checking our socket/queues + +#define TCPN_DEBUG 0 +#define TCPN_DEBUG_Console 0 +#define TCPN_DEBUG_Memory 0 +#define TCPN_LOG_PACKETS 0 +#define TCPN_LOG_RAW_DATA_OUT 0 +#define TCPN_LOG_RAW_DATA_IN 0 + +TCPConnection::TCPNetPacket_Struct* TCPConnection::MakePacket(ServerPacket* pack, int32 iDestination) { + sint32 size = sizeof(TCPNetPacket_Struct) + pack->size; + if (pack->compressed) { + size += 4; + } + if (iDestination) { + size += 4; + } + TCPNetPacket_Struct* tnps = (TCPNetPacket_Struct*) new uchar[size]; + tnps->size = size; + tnps->opcode = pack->opcode; + *((int8*) &tnps->flags) = 0; + uchar* buffer = tnps->buffer; + if (pack->compressed) { + tnps->flags.compressed = 1; + *((sint32*) buffer) = pack->InflatedSize; + buffer += 4; + } + if (iDestination) { + tnps->flags.destination = 1; + *((sint32*) buffer) = iDestination; + buffer += 4; + } + memcpy(buffer, pack->pBuffer, pack->size); + return tnps; +} + +TCPConnection::TCPConnection(bool iOldFormat, TCPServer* iRelayServer, eTCPMode iMode) { + id = 0; + Server = iRelayServer; + if (Server) + RelayServer = true; + else + RelayServer = false; + RelayLink = 0; + RelayCount = 0; + RemoteID = 0; + pOldFormat = iOldFormat; + ConnectionType = Outgoing; + TCPMode = iMode; + pState = TCPS_Ready; + pFree = false; + pEcho = false; + sock = 0; + rIP = 0; + rPort = 0; + keepalive_timer = new Timer(SERVER_TIMEOUT); + timeout_timer = new Timer(SERVER_TIMEOUT * 2); + recvbuf = 0; + sendbuf = 0; + pRunLoop = false; + charAsyncConnect = 0; + pAsyncConnect = false; + connection_socket = 0; + recvbuf_size = 0; + recvbuf_used = 0; + recvbuf_echo = 0; + sendbuf_size = 0; + sendbuf_used = 0; +#if TCPN_DEBUG_Memory >= 7 + cout << "Constructor #1 on outgoing TCP# " << GetID() << endl; +#endif +} + +TCPConnection::TCPConnection(TCPServer* iServer, SOCKET in_socket, int32 irIP, int16 irPort, bool iOldFormat) { + Server = iServer; + RelayLink = 0; + RelayServer = false; + RelayCount = 0; + RemoteID = 0; + id = Server->GetNextID(); + ConnectionType = Incomming; + pOldFormat = iOldFormat; + TCPMode = modePacket; + pState = TCPS_Connected; + pFree = false; + pEcho = false; + sock = 0; + connection_socket = in_socket; + rIP = irIP; + rPort = irPort; + keepalive_timer = new Timer(SERVER_TIMEOUT); + timeout_timer = new Timer(SERVER_TIMEOUT * 2); + recvbuf = 0; + sendbuf = 0; + pRunLoop = false; + charAsyncConnect = 0; + pAsyncConnect = false; + recvbuf_size = 0; + recvbuf_used = 0; + recvbuf_echo = 0; + sendbuf_size = 0; + sendbuf_used = 0; +#if TCPN_DEBUG_Memory >= 7 + cout << "Constructor #2 on outgoing TCP# " << GetID() << endl; +#endif +} + +TCPConnection::TCPConnection(TCPServer* iServer, TCPConnection* iRelayLink, int32 iRemoteID, int32 irIP, int16 irPort) { + Server = iServer; + RelayLink = iRelayLink; + RelayServer = true; + id = Server->GetNextID(); + RelayCount = 0; + RemoteID = iRemoteID; + if (!RemoteID) + ThrowError("Error: TCPConnection: RemoteID == 0 on RelayLink constructor"); + pOldFormat = false; + ConnectionType = Incomming; + TCPMode = modePacket; + pState = TCPS_Connected; + pFree = false; + pEcho = false; + sock = 0; + connection_socket = 0; + rIP = irIP; + rPort = irPort; + keepalive_timer = 0; + timeout_timer = 0; + recvbuf = 0; + sendbuf = 0; + pRunLoop = false; + charAsyncConnect = 0; + pAsyncConnect = false; + recvbuf_size = 0; + recvbuf_used = 0; + recvbuf_echo = 0; + sendbuf_size = 0; + sendbuf_used = 0; +#if TCPN_DEBUG_Memory >= 7 + cout << "Constructor #3 on outgoing TCP# " << GetID() << endl; +#endif +} + +TCPConnection::~TCPConnection() { + Disconnect(); + ClearBuffers(); + if (ConnectionType == Outgoing) { + MRunLoop.lock(); + pRunLoop = false; + MRunLoop.unlock(); + MLoopRunning.lock(); + MLoopRunning.unlock(); +#if TCPN_DEBUG_Memory >= 6 + cout << "Deconstructor on outgoing TCP# " << GetID() << endl; +#endif + } +#if TCPN_DEBUG_Memory >= 5 + else { + cout << "Deconstructor on incomming TCP# " << GetID() << endl; + } +#endif + safe_delete(keepalive_timer); + safe_delete(timeout_timer); + safe_delete_array(recvbuf); + safe_delete_array(sendbuf); + safe_delete_array(charAsyncConnect); +} + +void TCPConnection::SetState(int8 in_state) { + MState.lock(); + pState = in_state; + MState.unlock(); +} + +int8 TCPConnection::GetState() { + int8 ret; + MState.lock(); + ret = pState; + MState.unlock(); + return ret; +} + +void TCPConnection::Free() { + if (ConnectionType == Outgoing) { + ThrowError("TCPConnection::Free() called on an Outgoing connection"); + } +#if TCPN_DEBUG_Memory >= 5 + cout << "Free on TCP# " << GetID() << endl; +#endif + Disconnect(); + pFree = true; +} + +bool TCPConnection::SendPacket(ServerPacket* pack, int32 iDestination) { + LockMutex lock(&MState); + if (!Connected()) + return false; + eTCPMode tmp = GetMode(); + if (tmp != modePacket && tmp != modeTransition) + return false; + if (RemoteID) + return RelayLink->SendPacket(pack, RemoteID); + else { + TCPNetPacket_Struct* tnps = MakePacket(pack, iDestination); + if (tmp == modeTransition) { + InModeQueuePush(tnps); + } + else { +#if TCPN_LOG_PACKETS >= 1 + if (pack && pack->opcode != 0) { + struct in_addr in; + in.s_addr = GetrIP(); + CoutTimestamp(true); + cout << ": Logging outgoing TCP packet. OPCode: 0x" << hex << setw(4) << setfill('0') << pack->opcode << dec << ", size: " << setw(5) << setfill(' ') << pack->size << " " << inet_ntoa(in) << ":" << GetrPort() << endl; +#if TCPN_LOG_PACKETS == 2 + if (pack->size >= 32) + DumpPacket(pack->pBuffer, 32); + else + DumpPacket(pack); +#endif +#if TCPN_LOG_PACKETS >= 3 + DumpPacket(pack); +#endif + } +#endif + ServerSendQueuePushEnd((uchar**) &tnps, tnps->size); + } + } + return true; +} + +bool TCPConnection::SendPacket(TCPNetPacket_Struct* tnps) { + LockMutex lock(&MState); + if (RemoteID) + return false; + if (!Connected()) + return false; + eTCPMode tmp = GetMode(); + if (tmp == modeTransition) { + TCPNetPacket_Struct* tnps2 = (TCPNetPacket_Struct*) new uchar[tnps->size]; + memcpy(tnps2, tnps, tnps->size); + InModeQueuePush(tnps2); + return true; + } + if (GetMode() != modePacket) + return false; +#if TCPN_LOG_PACKETS >= 1 + if (tnps && tnps->opcode != 0) { + struct in_addr in; + in.s_addr = GetrIP(); + CoutTimestamp(true); + cout << ": Logging outgoing TCP NetPacket. OPCode: 0x" << hex << setw(4) << setfill('0') << tnps->opcode << dec << ", size: " << setw(5) << setfill(' ') << tnps->size << " " << inet_ntoa(in) << ":" << GetrPort(); + if (pOldFormat) + cout << " (OldFormat)"; + cout << endl; +#if TCPN_LOG_PACKETS == 2 + if (tnps->size >= 32) + DumpPacket((uchar*) tnps, 32); + else + DumpPacket((uchar*) tnps, tnps->size); +#endif +#if TCPN_LOG_PACKETS >= 3 + DumpPacket((uchar*) tnps, tnps->size); +#endif + } +#endif + ServerSendQueuePushEnd((const uchar*) tnps, tnps->size); + return true; +} + +bool TCPConnection::Send(const uchar* data, sint32 size) { + if (!Connected()) + return false; + if (GetMode() != modeConsole) + return false; + if (!size) + return true; + ServerSendQueuePushEnd(data, size); + return true; +} + +void TCPConnection::InModeQueuePush(TCPNetPacket_Struct* tnps) { + MSendQueue.lock(); + InModeQueue.push(tnps); + MSendQueue.unlock(); +} + +void TCPConnection::ServerSendQueuePushEnd(const uchar* data, sint32 size) { + MSendQueue.lock(); + if (sendbuf == 0) { + sendbuf = new uchar[size]; + sendbuf_size = size; + sendbuf_used = 0; + } + else if (size > (sendbuf_size - sendbuf_used)) { + sendbuf_size += size + 1024; + uchar* tmp = new uchar[sendbuf_size]; + memcpy(tmp, sendbuf, sendbuf_used); + safe_delete_array(sendbuf); + sendbuf = tmp; + } + memcpy(&sendbuf[sendbuf_used], data, size); + sendbuf_used += size; + MSendQueue.unlock(); +} + +void TCPConnection::ServerSendQueuePushEnd(uchar** data, sint32 size) { + MSendQueue.lock(); + if (sendbuf == 0) { + sendbuf = *data; + sendbuf_size = size; + sendbuf_used = size; + MSendQueue.unlock(); + *data = 0; + return; + } + if (size > (sendbuf_size - sendbuf_used)) { + sendbuf_size += size; + uchar* tmp = new uchar[sendbuf_size]; + memcpy(tmp, sendbuf, sendbuf_used); + safe_delete_array(sendbuf); + sendbuf = tmp; + } + memcpy(&sendbuf[sendbuf_used], *data, size); + sendbuf_used += size; + MSendQueue.unlock(); + delete[] (TCPNetPacket_Struct*)*data; +} + +void TCPConnection::ServerSendQueuePushFront(uchar* data, sint32 size) { + MSendQueue.lock(); + if (sendbuf == 0) { + sendbuf = new uchar[size]; + sendbuf_size = size; + sendbuf_used = 0; + } + else if (size > (sendbuf_size - sendbuf_used)) { + sendbuf_size += size; + uchar* tmp = new uchar[sendbuf_size]; + memcpy(&tmp[size], sendbuf, sendbuf_used); + safe_delete_array(sendbuf); + sendbuf = tmp; + } + memcpy(sendbuf, data, size); + sendbuf_used += size; + MSendQueue.unlock(); +} + +bool TCPConnection::ServerSendQueuePop(uchar** data, sint32* size) { + bool ret; + if (!MSendQueue.trylock()) + return false; + if (sendbuf) { + *data = sendbuf; + *size = sendbuf_used; + sendbuf = 0; + ret = true; + } + else { + ret = false; + } + MSendQueue.unlock(); + return ret; +} + +ServerPacket* TCPConnection::PopPacket() { + ServerPacket* ret; + if (!MOutQueueLock.trylock()) + return 0; + ret = OutQueue.pop(); + MOutQueueLock.unlock(); + return ret; +} + +char* TCPConnection::PopLine() { + char* ret; + if (!MOutQueueLock.trylock()) + return 0; + ret = (char*) LineOutQueue.pop(); + MOutQueueLock.unlock(); + return ret; +} + +void TCPConnection::OutQueuePush(ServerPacket* pack) { + MOutQueueLock.lock(); + OutQueue.push(pack); + MOutQueueLock.unlock(); +} + +void TCPConnection::LineOutQueuePush(char* line) { +#if defined(GOTFRAGS) && 0 + if (strcmp(line, "**CRASHME**") == 0) { + int i = 0; + cout << (5 / i) << endl; + } +#endif + if (strcmp(line, "**PACKETMODE**") == 0) { + MSendQueue.lock(); + safe_delete_array(sendbuf); + if (TCPMode == modeConsole) + Send((const uchar*) "\0**PACKETMODE**\r", 16); + TCPMode = modePacket; + TCPNetPacket_Struct* tnps = 0; + while ((tnps = InModeQueue.pop())) { + SendPacket(tnps); + safe_delete_array(tnps); + } + MSendQueue.unlock(); + safe_delete_array(line); + return; + } + MOutQueueLock.lock(); + LineOutQueue.push(line); + MOutQueueLock.unlock(); +} + +void TCPConnection::Disconnect(bool iSendRelayDisconnect) { + if (connection_socket != INVALID_SOCKET && connection_socket != 0) { + MState.lock(); + if (pState == TCPS_Connected || pState == TCPS_Disconnecting || pState == TCPS_Disconnected) + SendData(); + pState = TCPS_Closing; + MState.unlock(); + shutdown(connection_socket, 0x01); + shutdown(connection_socket, 0x00); +#ifdef WIN32 + closesocket(connection_socket); +#else + close(connection_socket); +#endif + connection_socket = 0; + rIP = 0; + rPort = 0; + ClearBuffers(); + } + SetState(TCPS_Ready); + if (RelayLink) { + RelayLink->RemoveRelay(this, iSendRelayDisconnect); + RelayLink = 0; + } +} + +bool TCPConnection::GetAsyncConnect() { + bool ret; + MAsyncConnect.lock(); + ret = pAsyncConnect; + MAsyncConnect.unlock(); + return ret; +} + +bool TCPConnection::SetAsyncConnect(bool iValue) { + bool ret; + MAsyncConnect.lock(); + ret = pAsyncConnect; + pAsyncConnect = iValue; + MAsyncConnect.unlock(); + return ret; +} + +void TCPConnection::AsyncConnect(char* irAddress, int16 irPort) { + if (ConnectionType != Outgoing) { + // If this code runs, we got serious problems + // Crash and burn. + ThrowError("TCPConnection::AsyncConnect() call on a Incomming connection object!"); + return; + } + if (GetState() != TCPS_Ready) + return; + MAsyncConnect.lock(); + if (pAsyncConnect) { + MAsyncConnect.unlock(); + return; + } + pAsyncConnect = true; + safe_delete_array(charAsyncConnect); + charAsyncConnect = new char[strlen(irAddress) + 1]; + strcpy(charAsyncConnect, irAddress); + rPort = irPort; + MAsyncConnect.unlock(); + if (!pRunLoop) { + pRunLoop = true; +#ifdef WIN32 + _beginthread(TCPConnectionLoop, 0, this); +#else + pthread_t thread; + pthread_create(&thread, NULL, TCPConnectionLoop, this); + pthread_detach(thread); +#endif + } + return; +} + +void TCPConnection::AsyncConnect(int32 irIP, int16 irPort) { + if (ConnectionType != Outgoing) { + // If this code runs, we got serious problems + // Crash and burn. + ThrowError("TCPConnection::AsyncConnect() call on a Incomming connection object!"); + return; + } + if (GetState() != TCPS_Ready) + return; + MAsyncConnect.lock(); + if (pAsyncConnect) { + MAsyncConnect.unlock(); + return; + } + pAsyncConnect = true; + safe_delete(charAsyncConnect); + rIP = irIP; + rPort = irPort; + MAsyncConnect.unlock(); + if (!pRunLoop) { + pRunLoop = true; +#ifdef WIN32 + _beginthread(TCPConnectionLoop, 0, this); +#else + pthread_t thread; + pthread_create(&thread, NULL, TCPConnectionLoop, this); + pthread_detach(thread); +#endif + } + return; +} + +bool TCPConnection::Connect(char* irAddress, int16 irPort, char* errbuf) { + if (errbuf) + errbuf[0] = 0; + int32 tmpIP = ResolveIP(irAddress); + if (!tmpIP) { + if (errbuf) { +#ifdef WIN32 + snprintf(errbuf, TCPConnection_ErrorBufferSize, "TCPConnection::Connect(): Couldnt resolve hostname. Error: %i", WSAGetLastError()); +#else + snprintf(errbuf, TCPConnection_ErrorBufferSize, "TCPConnection::Connect(): Couldnt resolve hostname. Error #%i: %s", errno, strerror(errno)); +#endif + } + return false; + } + return Connect(tmpIP, irPort, errbuf); +} + +bool TCPConnection::Connect(int32 in_ip, int16 in_port, char* errbuf) { + if (errbuf) + errbuf[0] = 0; + if (ConnectionType != Outgoing) { + // If this code runs, we got serious problems + // Crash and burn. + ThrowError("TCPConnection::Connect() call on a Incomming connection object!"); + return false; + } + MState.lock(); + if (pState == TCPS_Ready) { + pState = TCPS_Connecting; + } + else { + MState.unlock(); + SetAsyncConnect(false); + return false; + } + MState.unlock(); + if (!pRunLoop) { + pRunLoop = true; +#ifdef WIN32 + _beginthread(TCPConnectionLoop, 0, this); +#else + pthread_t thread; + pthread_create(&thread, NULL, TCPConnectionLoop, this); + pthread_detach(thread); +#endif + } + + connection_socket = INVALID_SOCKET; + struct sockaddr_in server_sin; + // struct in_addr in; + + if ((connection_socket = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET || connection_socket == 0) { +#ifdef WIN32 + if (errbuf) + snprintf(errbuf, TCPConnection_ErrorBufferSize, "TCPConnection::Connect(): Allocating socket failed. Error: %i", WSAGetLastError()); +#else + if (errbuf) + snprintf(errbuf, TCPConnection_ErrorBufferSize, "TCPConnection::Connect(): Allocating socket failed. Error: %s", strerror(errno)); +#endif + SetState(TCPS_Ready); + SetAsyncConnect(false); + return false; + } + server_sin.sin_family = AF_INET; + server_sin.sin_addr.s_addr = in_ip; + server_sin.sin_port = htons(in_port); + + // Establish a connection to the server socket. +#ifdef WIN32 + if (connect(connection_socket, (PSOCKADDR) &server_sin, sizeof (server_sin)) == SOCKET_ERROR) { + if (errbuf) + snprintf(errbuf, TCPConnection_ErrorBufferSize, "TCPConnection::Connect(): connect() failed. Error: %i", WSAGetLastError()); + closesocket(connection_socket); + connection_socket = 0; + SetState(TCPS_Ready); + SetAsyncConnect(false); + return false; + } +#else + if (connect(connection_socket, (struct sockaddr *) &server_sin, sizeof (server_sin)) == SOCKET_ERROR) { + if (errbuf) + snprintf(errbuf, TCPConnection_ErrorBufferSize, "TCPConnection::Connect(): connect() failed. Error: %s", strerror(errno)); + close(connection_socket); + connection_socket = 0; + SetState(TCPS_Ready); + SetAsyncConnect(false); + return false; + } +#endif + int bufsize = 64 * 1024; // 64kbyte recieve buffer, up from default of 8k + setsockopt(connection_socket, SOL_SOCKET, SO_RCVBUF, (char*) &bufsize, sizeof(bufsize)); +#ifdef WIN32 + unsigned long nonblocking = 1; + ioctlsocket(connection_socket, FIONBIO, &nonblocking); +#else + fcntl(connection_socket, F_SETFL, O_NONBLOCK); +#endif + + SetEcho(false); + MSendQueue.lock(); + ClearBuffers(); + TCPMode = modePacket; + + MSendQueue.unlock(); + + rIP = in_ip; + rPort = in_port; + SetState(TCPS_Connected); + SetAsyncConnect(false); + return true; +} + +void TCPConnection::ClearBuffers() { + LockMutex lock1(&MSendQueue); + LockMutex lock2(&MOutQueueLock); + LockMutex lock3(&MRunLoop); + LockMutex lock4(&MState); + safe_delete_array(recvbuf); + safe_delete_array(sendbuf); + ServerPacket* pack = 0; + while ((pack = PopPacket())) + safe_delete(pack); + TCPNetPacket_Struct* tnps = 0; + while ((tnps = InModeQueue.pop())) + safe_delete(tnps); + char* line = 0; + while ((line = LineOutQueue.pop())) + safe_delete_array(line); + keepalive_timer->Start(); + timeout_timer->Start(); +} + +bool TCPConnection::CheckNetActive() { + MState.lock(); + if (pState == TCPS_Connected || pState == TCPS_Disconnecting) { + MState.unlock(); + return true; + } + MState.unlock(); + return false; +} + +bool TCPConnection::Process() { + char errbuf[TCPConnection_ErrorBufferSize]; + if (!CheckNetActive()) { + if (ConnectionType == Outgoing) { + if (GetAsyncConnect()) { + if (charAsyncConnect) + rIP = ResolveIP(charAsyncConnect); + Connect(rIP, rPort); + } + } + if (GetState() == TCPS_Disconnected) { + Disconnect(); + return false; + } + else if (GetState() == TCPS_Connecting) + return true; + else + return false; + } + if (!SendData(errbuf)) { + struct in_addr in; + in.s_addr = GetrIP(); + cout << inet_ntoa(in) << ":" << GetrPort() << ": " << errbuf << endl; + return false; + } + if (!Connected()) + return false; + if (!RecvData(errbuf)) { + struct in_addr in; + in.s_addr = GetrIP(); + cout << inet_ntoa(in) << ":" << GetrPort() << ": " << errbuf << endl; + return false; + } + return true; +} + +bool TCPConnection::RecvData(char* errbuf) { + if (errbuf) + errbuf[0] = 0; + if (!Connected()) { + return false; + } + + int status = 0; + if (recvbuf == 0) { + recvbuf = new uchar[5120]; + recvbuf_size = 5120; + recvbuf_used = 0; + recvbuf_echo = 0; + } + else if ((recvbuf_size - recvbuf_used) < 2048) { + uchar* tmpbuf = new uchar[recvbuf_size + 5120]; + memcpy(tmpbuf, recvbuf, recvbuf_used); + recvbuf_size += 5120; + safe_delete_array(recvbuf); + recvbuf = tmpbuf; + if (recvbuf_size >= MaxTCPReceiveBufferSize) { + if (errbuf) + snprintf(errbuf, TCPConnection_ErrorBufferSize, "TCPConnection::RecvData(): recvbuf_size >= MaxTCPReceiveBufferSize"); + return false; + } + } + + status = recv(connection_socket, (char *) &recvbuf[recvbuf_used], (recvbuf_size - recvbuf_used), 0); + if (status >= 1) { +#if TCPN_LOG_RAW_DATA_IN >= 1 + struct in_addr in; + in.s_addr = GetrIP(); + CoutTimestamp(true); + cout << ": Read " << status << " bytes from network. (recvbuf_used = " << recvbuf_used << ") " << inet_ntoa(in) << ":" << GetrPort(); + if (pOldFormat) + cout << " (OldFormat)"; + cout << endl; +#if TCPN_LOG_RAW_DATA_IN == 2 + sint32 tmp = status; + if (tmp > 32) + tmp = 32; + DumpPacket(&recvbuf[recvbuf_used], status); +#elif TCPN_LOG_RAW_DATA_IN >= 3 + DumpPacket(&recvbuf[recvbuf_used], status); +#endif +#endif + recvbuf_used += status; + timeout_timer->Start(); + if (!ProcessReceivedData(errbuf)) + return false; + } + else if (status == SOCKET_ERROR) { +#ifdef WIN32 + if (!(WSAGetLastError() == WSAEWOULDBLOCK)) { + if (errbuf) + snprintf(errbuf, TCPConnection_ErrorBufferSize, "TCPConnection::RecvData(): Error: %i", WSAGetLastError()); + return false; + } +#else + if (!(errno == EWOULDBLOCK)) { + if (errbuf) + snprintf(errbuf, TCPConnection_ErrorBufferSize, "TCPConnection::RecvData(): Error: %s", strerror(errno)); + return false; + } +#endif + } + if ((TCPMode == modePacket || TCPMode == modeTransition) && timeout_timer->Check()) { + if (errbuf) + snprintf(errbuf, TCPConnection_ErrorBufferSize, "TCPConnection::RecvData(): Connection timeout"); + return false; + } + + return true; +} + + +bool TCPConnection::GetEcho() { + bool ret; + MEcho.lock(); + ret = pEcho; + MEcho.unlock(); + return ret; +} + +void TCPConnection::SetEcho(bool iValue) { + MEcho.lock(); + pEcho = iValue; + MEcho.unlock(); +} + +bool TCPConnection::ProcessReceivedData(char* errbuf) { + if (errbuf) + errbuf[0] = 0; + if (!recvbuf) + return true; + if (TCPMode == modePacket) { + //if (pOldFormat) + // return ProcessReceivedDataAsOldPackets(errbuf); + //else + return ProcessReceivedDataAsPackets(errbuf); + } + else { +#if TCPN_DEBUG_Console >= 4 + if (recvbuf_used) { + cout << "Starting Processing: recvbuf=" << recvbuf_used << endl; + DumpPacket(recvbuf, recvbuf_used); + } +#endif + for (int i=0; i < recvbuf_used; i++) { + if (GetEcho() && i >= recvbuf_echo) { + Send(&recvbuf[i], 1); + recvbuf_echo = i + 1; + } + switch(recvbuf[i]) { + case 0: { // 0 is the code for clear buffer + if (i==0) { + recvbuf_used--; + recvbuf_echo--; + memcpy(recvbuf, &recvbuf[1], recvbuf_used); + i = -1; + } else { + if (i == recvbuf_used) { + safe_delete_array(recvbuf); + i = -1; + } + else { + uchar* tmpdel = recvbuf; + recvbuf = new uchar[recvbuf_size]; + memcpy(recvbuf, &tmpdel[i+1], recvbuf_used-i); + recvbuf_used -= i + 1; + recvbuf_echo -= i + 1; + safe_delete(tmpdel); + i = -1; + } + } +#if TCPN_DEBUG_Console >= 5 + cout << "Removed 0x00" << endl; + if (recvbuf_used) { + cout << "recvbuf left: " << recvbuf_used << endl; + DumpPacket(recvbuf, recvbuf_used); + } + else + cout << "recbuf left: None" << endl; +#endif + break; + } + case 10: + case 13: // newline marker + { + if (i==0) { // empty line + recvbuf_used--; + recvbuf_echo--; + memcpy(recvbuf, &recvbuf[1], recvbuf_used); + i = -1; + } else { + char* line = new char[i+1]; + memset(line, 0, i+1); + memcpy(line, recvbuf, i); +#if TCPN_DEBUG_Console >= 3 + cout << "Line Out: " << endl; + DumpPacket((uchar*) line, i); +#endif + //line[i] = 0; + uchar* tmpdel = recvbuf; + recvbuf = new uchar[recvbuf_size]; + recvbuf_used -= i+1; + recvbuf_echo -= i+1; + memcpy(recvbuf, &tmpdel[i+1], recvbuf_used); +#if TCPN_DEBUG_Console >= 5 + cout << "i+1=" << i+1 << endl; + if (recvbuf_used) { + cout << "recvbuf left: " << recvbuf_used << endl; + DumpPacket(recvbuf, recvbuf_used); + } + else + cout << "recbuf left: None" << endl; +#endif + safe_delete(tmpdel); + if (strlen(line) > 0) + LineOutQueuePush(line); + else + safe_delete_array(line); + if (TCPMode == modePacket) { + return ProcessReceivedDataAsPackets(errbuf); + } + i = -1; + } + break; + } + case 8: // backspace + { + if (i==0) { // nothin to backspace + recvbuf_used--; + recvbuf_echo--; + memcpy(recvbuf, &recvbuf[1], recvbuf_used); + i = -1; + } else { + uchar* tmpdel = recvbuf; + recvbuf = new uchar[recvbuf_size]; + memcpy(recvbuf, tmpdel, i-1); + memcpy(&recvbuf[i-1], &tmpdel[i+1], recvbuf_used-i); + recvbuf_used -= 2; + recvbuf_echo -= 2; + safe_delete(tmpdel); + i -= 2; + } + break; + } + } + } + if (recvbuf_used < 0) + safe_delete_array(recvbuf); + } + return true; +} + +bool TCPConnection::ProcessReceivedDataAsPackets(char* errbuf) { + if (errbuf) + errbuf[0] = 0; + sint32 base = 0; + sint32 size = 0; + uchar* buffer; + sint32 sizeReq = sizeof(TCPNetPacket_Struct); + ServerPacket* pack = 0; + while ((recvbuf_used - base) >= size) { + TCPNetPacket_Struct* tnps = (TCPNetPacket_Struct*) &recvbuf[base]; + buffer = tnps->buffer; + size = tnps->size; + + if (size < sizeReq || recvbuf_used < sizeReq || size >= MaxTCPReceiveBufferSize) { +#if TCPN_DEBUG_Memory >= 1 + cout << "TCPConnection[" << GetID() << "]::ProcessReceivedDataAsPackets(): size[" << size << "] >= MaxTCPReceiveBufferSize" << endl; +#endif + if (errbuf) + snprintf(errbuf, TCPConnection_ErrorBufferSize, "TCPConnection::ProcessReceivedDataAsPackets(): size provided %i, recvbuf_used %i, checks failed: struct_size < %i || recvbuf_used < sizeReq || size >= MaxTCPReceiveBufferSize", size, recvbuf_used, sizeReq); + return false; + } + if ((recvbuf_used - base) >= size) { + // ok, we got enough data to make this packet! + safe_delete(pack); + pack = new ServerPacket; + pack->size = size - sizeof(TCPNetPacket_Struct); + // read headers + pack->opcode = tnps->opcode; + if (tnps->flags.compressed) { + sizeReq += 4; + if(size < sizeReq || recvbuf_used < sizeReq) + { + if (errbuf) + snprintf(errbuf, TCPConnection_ErrorBufferSize, "TCPConnection::ProcessReceivedDataAsPackets(Flags.Compressed): size provided %i, recvbuf_used %i, checks failed: struct_size < %i || recvbuf_used < sizeReq", size, recvbuf_used, sizeReq); + safe_delete(pack); + return false; + } + pack->compressed = true; + pack->InflatedSize = *((sint32*)buffer); + pack->size -= 4; + buffer += 4; + } + if (tnps->flags.destination) { + sizeReq += 4; + if(size < sizeReq || recvbuf_used < sizeReq) + { + if (errbuf) + snprintf(errbuf, TCPConnection_ErrorBufferSize, "TCPConnection::ProcessReceivedDataAsPackets(Flags.Destination): size provided %i, recvbuf_used %i, checks failed: struct_size < %i || recvbuf_used < sizeReq", size, recvbuf_used, sizeReq); + safe_delete(pack); + return false; + } + pack->destination = *((sint32*)buffer); + pack->size -= 4; + buffer += 4; + } + // end read headers + if (pack->size > 0) { + if (tnps->flags.compressed) { + // Lets decompress the packet here + pack->compressed = false; + if(pack->InflatedSize < MaxTCPReceiveBufferSize) + { + pack->pBuffer = new uchar[pack->InflatedSize]; + pack->size = InflatePacket(buffer, pack->size, pack->pBuffer, pack->InflatedSize); + if(!pack->size) + { + if (errbuf) + snprintf(errbuf, TCPConnection_ErrorBufferSize, "TCPConnection::ProcessReceivedDataAsPackets(InflatePacket): size provided %i, recvbuf_used: %i, sizeReq: %i, could not inflate packet", size, recvbuf_used, sizeReq); + + safe_delete(pack); + return false; + } + } + else + { + cout << "Invalid inflated packet." << endl; + safe_delete(pack); + return false; + } + } + else { + pack->pBuffer = new uchar[pack->size]; + memcpy(pack->pBuffer, buffer, pack->size); + } + } + if (pack->opcode == 0) { + if (pack->size) { +#if TCPN_DEBUG >= 2 + cout << "Received TCP Network layer packet" << endl; +#endif + ProcessNetworkLayerPacket(pack); + } +#if TCPN_DEBUG >= 5 + else { + cout << "Received TCP keepalive packet. (opcode=0)" << endl; + } +#endif + } + else { +#if TCPN_LOG_PACKETS >= 1 + if (pack && pack->opcode != 0) { + struct in_addr in; + in.s_addr = GetrIP(); + CoutTimestamp(true); + cout << ": Logging incoming TCP packet. OPCode: 0x" << hex << setw(4) << setfill('0') << pack->opcode << dec << ", size: " << setw(5) << setfill(' ') << pack->size << " " << inet_ntoa(in) << ":" << GetrPort() << endl; +#if TCPN_LOG_PACKETS == 2 + if (pack->size >= 32) + DumpPacket(pack->pBuffer, 32); + else + DumpPacket(pack); +#endif +#if TCPN_LOG_PACKETS >= 3 + DumpPacket(pack); +#endif + } +#endif + if (RelayServer && Server && pack->destination) { + TCPConnection* con = Server->GetConnection(pack->destination); + if (!con) { +#if TCPN_DEBUG >= 1 + cout << "Error relaying packet: con = 0" << endl; +#endif + } + else{ + con->OutQueuePush(pack); + pack = 0; + } + } + else{ + OutQueuePush(pack); + pack = 0; + } + } + base += size; + size = 7; + } + } + safe_delete(pack); + if (base != 0) { + if (base >= recvbuf_used) { + safe_delete_array(recvbuf); + } + else { + uchar* tmpbuf = new uchar[recvbuf_size - base]; + memcpy(tmpbuf, &recvbuf[base], recvbuf_used - base); + safe_delete_array(recvbuf); + recvbuf = tmpbuf; + recvbuf_used -= base; + recvbuf_size -= base; + } + } + return true; +} + +bool TCPConnection::ProcessReceivedDataAsOldPackets(char* errbuf) { + sint32 base = 0; + sint32 size = 4; + uchar* buffer; + ServerPacket* pack = 0; + while ((recvbuf_used - base) >= size) { + buffer = &recvbuf[base]; + memcpy(&size, &buffer[2], 2); + if (size >= MaxTCPReceiveBufferSize) { +#if TCPN_DEBUG_Memory >= 1 + cout << "TCPConnection[" << GetID() << "]::ProcessReceivedDataAsPackets(): size[" << size << "] >= MaxTCPReceiveBufferSize" << endl; +#endif + if (errbuf) + snprintf(errbuf, TCPConnection_ErrorBufferSize, "TCPConnection::ProcessReceivedDataAsPackets(): size >= MaxTCPReceiveBufferSize"); + return false; + } + if ((recvbuf_used - base) >= size) { + // ok, we got enough data to make this packet! + pack = new ServerPacket; + memcpy(&pack->opcode, &buffer[0], 2); + pack->size = size - 4; + + LogWrite(MISC__TODO, 1, "TODO", "Checksum or size check or something similar\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__); + /* + if () { // TODO: Checksum or size check or something similar + // Datastream corruption, get the hell outta here! + delete pack; + return false; + } + */ + + if (pack->size > 0) { + pack->pBuffer = new uchar[pack->size]; + memcpy(pack->pBuffer, &buffer[4], pack->size); + } + if (pack->opcode == 0) { + // keepalive, no need to process + safe_delete(pack); + } + else { +#if TCPN_LOG_PACKETS >= 1 + if (pack && pack->opcode != 0) { + struct in_addr in; + in.s_addr = GetrIP(); + CoutTimestamp(true); + cout << ": Logging incoming TCP OldPacket. OPCode: 0x" << hex << setw(4) << setfill('0') << pack->opcode << dec << ", size: " << setw(5) << setfill(' ') << pack->size << " " << inet_ntoa(in) << ":" << GetrPort() << endl; +#if TCPN_LOG_PACKETS == 2 + if (pack->size >= 32) + DumpPacket(pack->pBuffer, 32); + else + DumpPacket(pack); +#endif +#if TCPN_LOG_PACKETS >= 3 + DumpPacket(pack); +#endif + } +#endif + OutQueuePush(pack); + } + base += size; + size = 4; + } + } + if (base != 0) { + if (base >= recvbuf_used) { + safe_delete_array(recvbuf); + } + else { + uchar* tmpbuf = new uchar[recvbuf_size - base]; + memcpy(tmpbuf, &recvbuf[base], recvbuf_used - base); + safe_delete_array(recvbuf); + recvbuf = tmpbuf; + recvbuf_used -= base; + recvbuf_size -= base; + } + } + return true; +} + +void TCPConnection::ProcessNetworkLayerPacket(ServerPacket* pack) { + int8 opcode = pack->pBuffer[0]; + + + /** disabling RELAY capabilities, this functionality is poorly implemented + even if such a feature needs to be re-used need authentication BEFORE allowing relay to take place + secondly we need to protect the LS accepting new connections as bogus data can be passed to open + fake TCP connections + opcode 0 is OK, that is Keep-Alive + **/ + + if (opcode > 0) + { + Disconnect(); + return; + } + + int8* data = &pack->pBuffer[1]; + switch (opcode) { + case 0: { + break; + } + case 1: { // Switch to RelayServer mode + if (pack->size != 1) { + SendNetErrorPacket("New RelayClient: wrong size, expected 1"); + break; + } + if (RelayServer) { + SendNetErrorPacket("Switch to RelayServer mode when already in RelayServer mode"); + break; + } + if (RemoteID) { + SendNetErrorPacket("Switch to RelayServer mode by a Relay Client"); + break; + } + if (ConnectionType != Incomming) { + SendNetErrorPacket("Switch to RelayServer mode on outgoing connection"); + break; + } +#if TCPC_DEBUG >= 3 + struct in_addr in; + in.s_addr = GetrIP(); + cout << "Switching to RelayServer mode: " << inet_ntoa(in) << ":" << GetPort() << endl; +#endif + RelayServer = true; + break; + } + case 2: { // New Relay Client + if (!RelayServer) { + SendNetErrorPacket("New RelayClient when not in RelayServer mode"); + break; + } + if (pack->size != 11) { + SendNetErrorPacket("New RelayClient: wrong size, expected 11"); + break; + } + if (ConnectionType != Incomming) { + SendNetErrorPacket("New RelayClient: illegal on outgoing connection"); + break; + } + TCPConnection* con = new TCPConnection(Server, this, *((int32*) data), *((int32*) &data[4]), *((int16*) &data[8])); + Server->AddConnection(con); + RelayCount++; + break; + } + case 3: { // Delete Relay Client + if (!RelayServer) { + SendNetErrorPacket("Delete RelayClient when not in RelayServer mode"); + break; + } + if (pack->size != 5) { + SendNetErrorPacket("Delete RelayClient: wrong size, expected 5"); + break; + } + TCPConnection* con = Server->GetConnection(*((int32*)data)); + if (con) { + if (ConnectionType == Incomming) { + if (con->GetRelayLink() != this) { + SendNetErrorPacket("Delete RelayClient: RelayLink != this"); + break; + } + } + con->Disconnect(false); + } + break; + } + case 255: { +#if TCPC_DEBUG >= 1 + struct in_addr in; + in.s_addr = GetrIP(); + cout "Received NetError: '"; + if (pack->size > 1) + cout << (char*) data; + cout << "': " << inet_ntoa(in) << ":" << GetPort() << endl; +#endif + break; + } + } +} + +void TCPConnection::SendNetErrorPacket(const char* reason) { +#if TCPC_DEBUG >= 1 + struct in_addr in; + in.s_addr = GetrIP(); + cout "NetError: '"; + if (reason) + cout << reason; + cout << "': " << inet_ntoa(in) << ":" << GetPort() << endl; +#endif + ServerPacket* pack = new ServerPacket(0); + pack->size = 1; + if (reason) + pack->size += strlen(reason) + 1; + pack->pBuffer = new uchar[pack->size]; + memset(pack->pBuffer, 0, pack->size); + pack->pBuffer[0] = 255; + strcpy((char*) &pack->pBuffer[1], reason); + SendPacket(pack); + safe_delete(pack); +} + +void TCPConnection::RemoveRelay(TCPConnection* relay, bool iSendRelayDisconnect) { + if (iSendRelayDisconnect) { + ServerPacket* pack = new ServerPacket(0, 5); + pack->pBuffer[0] = 3; + *((int32*) &pack->pBuffer[1]) = relay->GetRemoteID(); + SendPacket(pack); + safe_delete(pack); + } + RelayCount--; +} + +bool TCPConnection::SendData(char* errbuf) { + if (errbuf) + errbuf[0] = 0; + /************ Get first send packet on queue and send it! ************/ + uchar* data = 0; + sint32 size = 0; + int status = 0; + if (ServerSendQueuePop(&data, &size)) { +#ifdef WIN32 + status = send(connection_socket, (const char *) data, size, 0); +#else + status = send(connection_socket, data, size, MSG_NOSIGNAL); + if(errno==EPIPE) status = SOCKET_ERROR; +#endif + if (status >= 1) { +#if TCPN_LOG_RAW_DATA_OUT >= 1 + struct in_addr in; + in.s_addr = GetrIP(); + CoutTimestamp(true); + cout << ": Wrote " << status << " bytes to network. " << inet_ntoa(in) << ":" << GetrPort(); + if (pOldFormat) + cout << " (OldFormat)"; + cout << endl; +#if TCPN_LOG_RAW_DATA_OUT == 2 + sint32 tmp = status; + if (tmp > 32) + tmp = 32; + DumpPacket(data, status); +#elif TCPN_LOG_RAW_DATA_OUT >= 3 + DumpPacket(data, status); +#endif +#endif + keepalive_timer->Start(); + if (status < (signed)size) { +#if TCPN_LOG_RAW_DATA_OUT >= 1 + struct in_addr in; + in.s_addr = GetrIP(); + CoutTimestamp(true); + cout << ": Pushed " << (size - status) << " bytes back onto the send queue. " << inet_ntoa(in) << ":" << GetrPort(); + if (pOldFormat) + cout << " (OldFormat)"; + cout << endl; +#endif + // If there's network congestion, the number of bytes sent can be less than + // what we tried to give it... Push the extra back on the queue for later + ServerSendQueuePushFront(&data[status], size - status); + } + else if (status > (signed)size) { + ThrowError("TCPConnection::SendData(): WTF! status > size"); + return false; + } + // else if (status == size) {} + } + else { + ServerSendQueuePushFront(data, size); + } + + safe_delete_array(data); + if (status == SOCKET_ERROR) { +#ifdef WIN32 + if (WSAGetLastError() != WSAEWOULDBLOCK) +#else + if (errno != EWOULDBLOCK) +#endif + { + if (errbuf) { +#ifdef WIN32 + snprintf(errbuf, TCPConnection_ErrorBufferSize, "TCPConnection::SendData(): send(): Errorcode: %i", WSAGetLastError()); +#else + snprintf(errbuf, TCPConnection_ErrorBufferSize, "TCPConnection::SendData(): send(): Errorcode: %s", strerror(errno)); +#endif + } + return false; + } + } + } + if (TCPMode == modePacket && keepalive_timer->Check()) { + ServerPacket* pack = new ServerPacket(0, 0); + SendPacket(pack); + safe_delete(pack); +#if TCPN_DEBUG >= 5 + cout << "Sending TCP keepalive packet. (timeout=" << timeout_timer->GetRemainingTime() << " remaining)" << endl; +#endif + } + return true; +} + +ThreadReturnType TCPConnectionLoop(void* tmp) { +#ifdef WIN32 + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL); +#endif + if (tmp == 0) { + ThrowError("TCPConnectionLoop(): tmp = 0!"); + THREAD_RETURN(NULL); + } + TCPConnection* tcpc = (TCPConnection*) tmp; + tcpc->MLoopRunning.lock(); + while (tcpc->RunLoop()) { + Sleep(LOOP_GRANULARITY); + if (tcpc->GetState() != TCPS_Ready) { + if (!tcpc->Process()) { + tcpc->Disconnect(); + } + } + else if (tcpc->GetAsyncConnect()) { + if (tcpc->charAsyncConnect) + tcpc->Connect(tcpc->charAsyncConnect, tcpc->GetrPort()); + else + tcpc->Connect(tcpc->GetrIP(), tcpc->GetrPort()); + tcpc->SetAsyncConnect(false); + } + else + Sleep(10); + } + tcpc->MLoopRunning.unlock(); + + THREAD_RETURN(NULL); +} + +bool TCPConnection::RunLoop() { + bool ret; + MRunLoop.lock(); + ret = pRunLoop; + MRunLoop.unlock(); + return ret; +} + + + + + +TCPServer::TCPServer(int16 in_port, bool iOldFormat) { + NextID = 1; + pPort = in_port; + sock = 0; + pOldFormat = iOldFormat; + list = new LinkedList; + pRunLoop = true; +#ifdef WIN32 + _beginthread(TCPServerLoop, 0, this); +#else + pthread_t thread; + pthread_create(&thread, NULL, &TCPServerLoop, this); + pthread_detach(thread); +#endif +} + +TCPServer::~TCPServer() { + MRunLoop.lock(); + pRunLoop = false; + MRunLoop.unlock(); + MLoopRunning.lock(); + MLoopRunning.unlock(); + + while (NewQueue.pop()); // the objects are deleted with the list, clear this queue so it doesnt try to delete them again + safe_delete(list); +} + +bool TCPServer::RunLoop() { + bool ret; + MRunLoop.lock(); + ret = pRunLoop; + MRunLoop.unlock(); + return ret; +} + +ThreadReturnType TCPServerLoop(void* tmp) { +#ifdef WIN32 + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL); +#endif + if (tmp == 0) { + ThrowError("TCPServerLoop(): tmp = 0!"); + THREAD_RETURN(NULL); + } + TCPServer* tcps = (TCPServer*) tmp; + tcps->MLoopRunning.lock(); + while (tcps->RunLoop()) { + Sleep(SERVER_LOOP_GRANULARITY); + tcps->Process(); + } + tcps->MLoopRunning.unlock(); + + THREAD_RETURN(NULL); +} + +void TCPServer::Process() { + CheckInQueue(); + ListenNewConnections(); + LinkedListIterator iterator(*list); + + iterator.Reset(); + while(iterator.MoreElements()) { + if (iterator.GetData()->IsFree() && (!iterator.GetData()->CheckNetActive())) { +#if _DEBUG + LogWrite(NET__DEBUG, 0, "Net", "EQStream Connection deleted."); +#endif + iterator.RemoveCurrent(); + } + else { + if (!iterator.GetData()->Process()) + iterator.GetData()->Disconnect(); + iterator.Advance(); + } + } +} + +void TCPServer::ListenNewConnections() { + SOCKET tmpsock; + struct sockaddr_in from; + struct in_addr in; + unsigned int fromlen; + + TCPConnection* con; + + from.sin_family = AF_INET; + fromlen = sizeof(from); + LockMutex lock(&MSock); + if (!sock) + return; + + // Check for pending connects +#ifdef WIN32 + unsigned long nonblocking = 1; + while ((tmpsock = accept(sock, (struct sockaddr*) &from, (int *) &fromlen)) != INVALID_SOCKET) { + ioctlsocket (tmpsock, FIONBIO, &nonblocking); +#else + while ((tmpsock = accept(sock, (struct sockaddr*) &from, &fromlen)) != INVALID_SOCKET) { + fcntl(tmpsock, F_SETFL, O_NONBLOCK); +#endif + int bufsize = 64 * 1024; // 64kbyte recieve buffer, up from default of 8k + setsockopt(tmpsock, SOL_SOCKET, SO_RCVBUF, (char*) &bufsize, sizeof(bufsize)); + in.s_addr = from.sin_addr.s_addr; + + // New TCP connection + con = new TCPConnection(this, tmpsock, in.s_addr, ntohs(from.sin_port), pOldFormat); +#if TCPN_DEBUG >= 1 + cout << "New TCP connection: " << inet_ntoa(in) << ":" << con->GetrPort() << endl; +#endif + AddConnection(con); + } +} + +bool TCPServer::Open(int16 in_port, char* errbuf) { + if (errbuf) + errbuf[0] = 0; + LockMutex lock(&MSock); + if (sock != 0) { + if (errbuf) + snprintf(errbuf, TCPConnection_ErrorBufferSize, "Listening socket already open"); + return false; + } + if (in_port != 0) { + pPort = in_port; + } + +#ifdef WIN32 + SOCKADDR_IN address; + unsigned long nonblocking = 1; +#else + struct sockaddr_in address; +#endif + int reuse_addr = 1; + + // Setup internet address information. + // This is used with the bind() call + memset((char *) &address, 0, sizeof(address)); + address.sin_family = AF_INET; + address.sin_port = htons(pPort); + address.sin_addr.s_addr = htonl(INADDR_ANY); + + // Setting up TCP port for new TCP connections + sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock == INVALID_SOCKET) { + if (errbuf) + snprintf(errbuf, TCPConnection_ErrorBufferSize, "socket(): INVALID_SOCKET"); + return false; + } + + // Quag: dont think following is good stuff for TCP, good for UDP + // Mis: SO_REUSEADDR shouldn't be a problem for tcp--allows you to restart + // without waiting for conns in TIME_WAIT to die + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *) &reuse_addr, sizeof(reuse_addr)); + + + if (::bind(sock, (struct sockaddr *) &address, sizeof(address)) < 0) { +#ifdef WIN32 + closesocket(sock); +#else + close(sock); +#endif + sock = 0; + if (errbuf) + sprintf(errbuf, "bind(): <0"); + return false; + } + + int bufsize = 64 * 1024; // 64kbyte recieve buffer, up from default of 8k + setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char*) &bufsize, sizeof(bufsize)); +#ifdef WIN32 + ioctlsocket (sock, FIONBIO, &nonblocking); +#else + fcntl(sock, F_SETFL, O_NONBLOCK); +#endif + + if (listen(sock, SOMAXCONN) == SOCKET_ERROR) { +#ifdef WIN32 + closesocket(sock); + if (errbuf) + snprintf(errbuf, TCPConnection_ErrorBufferSize, "listen() failed, Error: %d", WSAGetLastError()); +#else + close(sock); + if (errbuf) + snprintf(errbuf, TCPConnection_ErrorBufferSize, "listen() failed, Error: %s", strerror(errno)); +#endif + sock = 0; + return false; + } + + return true; +} + +void TCPServer::Close() { + LockMutex lock(&MSock); + if (sock) { +#ifdef WIN32 + closesocket(sock); +#else + close(sock); +#endif + } + sock = 0; +} + +bool TCPServer::IsOpen() { + MSock.lock(); + bool ret = (bool) (sock != 0); + MSock.unlock(); + return ret; +} + +TCPConnection* TCPServer::NewQueuePop() { + TCPConnection* ret; + MNewQueue.lock(); + ret = NewQueue.pop(); + MNewQueue.unlock(); + return ret; +} + +void TCPServer::AddConnection(TCPConnection* con) { + list->Append(con); + MNewQueue.lock(); + NewQueue.push(con); + MNewQueue.unlock(); +} + +TCPConnection* TCPServer::GetConnection(int32 iID) { + LinkedListIterator iterator(*list); + + iterator.Reset(); + while(iterator.MoreElements()) { + if (iterator.GetData()->GetID() == iID) + return iterator.GetData(); + iterator.Advance(); + } + return 0; +} + +void TCPServer::SendPacket(ServerPacket* pack) { + TCPConnection::TCPNetPacket_Struct* tnps = TCPConnection::MakePacket(pack); + SendPacket(&tnps); +} + +void TCPServer::SendPacket(TCPConnection::TCPNetPacket_Struct** tnps) { + MInQueue.lock(); + InQueue.push(*tnps); + MInQueue.unlock(); + tnps = 0; +} + +void TCPServer::CheckInQueue() { + LinkedListIterator iterator(*list); + TCPConnection::TCPNetPacket_Struct* tnps = 0; + + while (( tnps = InQueuePop() )) { + iterator.Reset(); + while(iterator.MoreElements()) { + if (iterator.GetData()->GetMode() != modeConsole && iterator.GetData()->GetRemoteID() == 0) + iterator.GetData()->SendPacket(tnps); + iterator.Advance(); + } + safe_delete(tnps); + } +} + +TCPConnection::TCPNetPacket_Struct* TCPServer::InQueuePop() { + TCPConnection::TCPNetPacket_Struct* ret; + MInQueue.lock(); + ret = InQueue.pop(); + MInQueue.unlock(); + return ret; +} + + diff --git a/source/common/TCPConnection.h b/source/common/TCPConnection.h new file mode 100644 index 0000000..ae47526 --- /dev/null +++ b/source/common/TCPConnection.h @@ -0,0 +1,277 @@ +/* + 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 . +*/ +#ifndef TCP_CONNECTION_H +#define TCP_CONNECTION_H +/* + Parent classes for interserver TCP Communication. + -Quagmire +*/ + +#ifdef WIN32 + #define snprintf _snprintf + #define vsnprintf _vsnprintf + #define strncasecmp _strnicmp + #define strcasecmp _stricmp + + #include +#else + #include + #include + #include + #include + #include + #include + #include + #include + #define INVALID_SOCKET -1 + #define SOCKET_ERROR -1 + #include "unix.h" + +#endif + +#include "types.h" +#include "Mutex.h" +#include "linked_list.h" +#include "queue.h" +#include "servertalk.h" +#include "timer.h" +#include "MiscFunctions.h" + +class TCPServer; + +#define TCPConnection_ErrorBufferSize 1024 +#define MaxTCPReceiveBufferSize 524288 + +#define TCPS_Ready 0 +#define TCPS_Connecting 1 +#define TCPS_Connected 100 +#define TCPS_Disconnecting 200 +#define TCPS_Disconnected 201 +#define TCPS_Closing 250 +#define TCPS_Error 255 + +#ifndef DEF_eConnectionType +#define DEF_eConnectionType +enum eConnectionType {Incomming, Outgoing}; +#endif + +#ifdef WIN32 + void TCPServerLoop(void* tmp); + void TCPConnectionLoop(void* tmp); +#else + void* TCPServerLoop(void* tmp); + void* TCPConnectionLoop(void* tmp); +#endif + +enum eTCPMode { modeConsole, modeTransition, modePacket }; +class TCPConnection { +public: +#pragma pack(1) + struct TCPNetPacket_Struct { + int32 size; + struct { + int8 + compressed : 1, + destination : 1, + flag3 : 1, + flag4 : 1, + flag5 : 1, + flag6 : 1, + flag7 : 1, + flag8 : 1; + } flags; + int16 opcode; + uchar buffer[0]; + }; +#pragma pack() + + static TCPNetPacket_Struct* MakePacket(ServerPacket* pack, int32 iDestination = 0); + + TCPConnection(TCPServer* iServer, SOCKET iSock, int32 irIP, int16 irPort, bool iOldFormat = false); + TCPConnection(bool iOldFormat = false, TCPServer* iRelayServer = 0, eTCPMode iMode = modePacket); // for outgoing connections + TCPConnection(TCPServer* iServer, TCPConnection* iRelayLink, int32 iRemoteID, int32 irIP, int16 irPort); // for relay connections + virtual ~TCPConnection(); + + // Functions for outgoing connections + bool Connect(char* irAddress, int16 irPort, char* errbuf = 0); + bool Connect(int32 irIP, int16 irPort, char* errbuf = 0); + void AsyncConnect(char* irAddress, int16 irPort); + void AsyncConnect(int32 irIP, int16 irPort); + virtual void Disconnect(bool iSendRelayDisconnect = true); + + virtual bool SendPacket(ServerPacket* pack, int32 iDestination = 0); + virtual bool SendPacket(TCPNetPacket_Struct* tnps); + bool Send(const uchar* data, sint32 size); + + char* PopLine(); + ServerPacket* PopPacket(); // OutQueuePop() + inline int32 GetrIP() { return rIP; } + inline int16 GetrPort() { return rPort; } + virtual int8 GetState(); + eTCPMode GetMode() { return TCPMode; } + inline bool Connected() { return (GetState() == TCPS_Connected); } + inline bool ConnectReady() { return (bool) (GetState() == TCPS_Ready && ConnectionType == Outgoing); } + void Free(); // Inform TCPServer that this connection object is no longer referanced + + inline int32 GetID() { return id; } + inline bool IsRelayServer() { return RelayServer; } + inline int32 GetRemoteID() { return RemoteID; } + inline TCPConnection* GetRelayLink() { return RelayLink; } + + bool GetEcho(); + void SetEcho(bool iValue); +protected: + friend class TCPServer; + virtual bool Process(); + void SetState(int8 iState); + inline bool IsFree() { return pFree; } + bool CheckNetActive(); + +#ifdef WIN32 + friend void TCPConnectionLoop(void* tmp); +#else + friend void* TCPConnectionLoop(void* tmp); +#endif + SOCKET sock; + bool RunLoop(); + Mutex MLoopRunning; + Mutex MAsyncConnect; + bool GetAsyncConnect(); + bool SetAsyncConnect(bool iValue); + char* charAsyncConnect; + +#ifdef WIN32 + friend class TCPConnection; +#endif + void OutQueuePush(ServerPacket* pack); + void RemoveRelay(TCPConnection* relay, bool iSendRelayDisconnect); +private: + void ProcessNetworkLayerPacket(ServerPacket* pack); + void SendNetErrorPacket(const char* reason = 0); + TCPServer* Server; + TCPConnection* RelayLink; + int32 RemoteID; + sint32 RelayCount; + + bool pOldFormat; + bool SendData(char* errbuf = 0); + bool RecvData(char* errbuf = 0); + bool ProcessReceivedData(char* errbuf = 0); + bool ProcessReceivedDataAsPackets(char* errbuf = 0); + bool ProcessReceivedDataAsOldPackets(char* errbuf = 0); + void ClearBuffers(); + + bool pAsyncConnect; + + eConnectionType ConnectionType; + eTCPMode TCPMode; + bool RelayServer; + Mutex MRunLoop; + bool pRunLoop; + + SOCKET connection_socket; + int32 id; + int32 rIP; + int16 rPort; // host byte order + bool pFree; + + Mutex MState; + int8 pState; + + void LineOutQueuePush(char* line); + MyQueue LineOutQueue; + MyQueue OutQueue; + Mutex MOutQueueLock; + + Timer* keepalive_timer; + Timer* timeout_timer; + + uchar* recvbuf; + sint32 recvbuf_size; + sint32 recvbuf_used; + + sint32 recvbuf_echo; + bool pEcho; + Mutex MEcho; + + void InModeQueuePush(TCPNetPacket_Struct* tnps); + MyQueue InModeQueue; + Mutex MSendQueue; + uchar* sendbuf; + sint32 sendbuf_size; + sint32 sendbuf_used; + bool ServerSendQueuePop(uchar** data, sint32* size); + void ServerSendQueuePushEnd(const uchar* data, sint32 size); + void ServerSendQueuePushEnd(uchar** data, sint32 size); + void ServerSendQueuePushFront(uchar* data, sint32 size); +}; + +class TCPServer { +public: + TCPServer(int16 iPort = 0, bool iOldFormat = false); + virtual ~TCPServer(); + + bool Open(int16 iPort = 0, char* errbuf = 0); // opens the port + void Close(); // closes the port + bool IsOpen(); + inline int16 GetPort() { return pPort; } + + TCPConnection* NewQueuePop(); + + void SendPacket(ServerPacket* pack); + void SendPacket(TCPConnection::TCPNetPacket_Struct** tnps); +protected: +#ifdef WIN32 + friend void TCPServerLoop(void* tmp); +#else + friend void* TCPServerLoop(void* tmp); +#endif + void Process(); + bool RunLoop(); + Mutex MLoopRunning; + + friend class TCPConnection; + inline int32 GetNextID() { return NextID++; } + void AddConnection(TCPConnection* con); + TCPConnection* GetConnection(int32 iID); +private: + void ListenNewConnections(); + + int32 NextID; + bool pOldFormat; + + Mutex MRunLoop; + bool pRunLoop; + + Mutex MSock; + SOCKET sock; + int16 pPort; + + Mutex MNewQueue; + MyQueue NewQueue; + + void CheckInQueue(); + Mutex MInQueue; + TCPConnection::TCPNetPacket_Struct* InQueuePop(); + MyQueue InQueue; + + LinkedList* list; +}; +#endif diff --git a/source/common/Web/WebServer.cpp b/source/common/Web/WebServer.cpp new file mode 100644 index 0000000..861ec86 --- /dev/null +++ b/source/common/Web/WebServer.cpp @@ -0,0 +1,336 @@ +#include "WebServer.h" +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "../version.h" + +#ifdef WORLD + #include "../../WorldServer/WorldDatabase.h" + extern WorldDatabase database; +#endif +#ifdef LOGIN + #include "../../LoginServer/LoginDatabase.h" + extern LoginDatabase database; +#endif + +#ifdef WIN32 + #include + #define strncasecmp _strnicmp + #define strcasecmp _stricmp + #include +#else + #include + #include "../unix.h" +#endif + +ThreadReturnType RunWebServer (void* tmp); + +static std::string keypasswd = ""; + +void web_handle_version(const http::request& req, http::response& res) { + res.set(http::field::content_type, "application/json"); + boost::property_tree::ptree pt; + + // Add key-value pairs to the property tree + pt.put("eq2emu_process", std::string(EQ2EMU_MODULE)); + pt.put("version", std::string(CURRENT_VERSION)); + pt.put("compile_date", std::string(COMPILE_DATE)); + pt.put("compile_time", std::string(COMPILE_TIME)); + + // Create an output string stream to hold the JSON string + std::ostringstream oss; + + // Write the property tree to the output string stream as JSON + boost::property_tree::write_json(oss, pt); + + // Get the JSON string from the output string stream + std::string json = oss.str(); + res.body() = json; + res.prepare_payload(); +} + +void web_handle_root(const http::request& req, http::response& res) { + res.set(http::field::content_type, "text/html"); + res.body() = "Hello!"; + res.prepare_payload(); +} + +// this function is called to obtain password info about an encrypted key +std::string WebServer::my_password_callback( + std::size_t max_length, // the maximum length for a password + ssl::context::password_purpose purpose ) // for_reading or for_writing +{ + return keypasswd; +} + +//void handle_root(const http::request& req, http::response& res); + +WebServer::WebServer(const std::string& address, unsigned short port, const std::string& cert_file, const std::string& key_file, const std::string& key_password, const std::string& hardcode_user, const std::string& hardcode_password) + : ioc_(1), + ssl_ctx_(ssl::context::tlsv13_server), + acceptor_(ioc_, {boost_net::ip::make_address(address), port}) { + keypasswd = key_password; + // Initialize SSL context + if(cert_file.size() < 1 || key_file.size() < 1) { + is_ssl = false; + } + else { + ssl_ctx_.set_password_callback(my_password_callback); + ssl_ctx_.use_certificate_chain_file(cert_file); + ssl_ctx_.use_private_key_file(key_file, ssl::context::file_format::pem); + is_ssl = true; + } + keypasswd = ""; // reset no longer needed + // Initialize some test credentials + if(hardcode_user.size() > 0 && hardcode_password.size() > 0) + credentials_[hardcode_user] = hardcode_password; + + register_route("/", web_handle_root); + register_route("/version", web_handle_version); +} + +WebServer::~WebServer() { + ioc_.stop(); +} + +ThreadReturnType RunWebServer (void* tmp) { + if(tmp == nullptr) { + THREAD_RETURN(NULL); + } + WebServer* ws = (WebServer*)tmp; + ws->start(); + THREAD_RETURN(NULL); +} + +void WebServer::start() { + do_accept(); + ioc_.run(); +} + +void WebServer::run() { + pthread_t thread; + pthread_create(&thread, NULL, RunWebServer, this); + pthread_detach(thread); +} + + +void WebServer::register_route(const std::string& uri, std::function&, http::response&)> handler, bool auth_req) { + int32 status = database.NoAuthRoute((char*)uri.c_str()); // overrides the default hardcode settings via DB + if(status == 0) { + auth_req = false; + } + if(auth_req) { + routes_[uri] = handler; + } + else { + noauth_routes_[uri] = handler; + } + route_required_status_[uri] = status; +} + +void WebServer::do_accept() { + acceptor_.async_accept( + [this](beast::error_code ec, tcp::socket socket) { + this->on_accept(ec, std::move(socket)); + }); +} + +void WebServer::on_accept(beast::error_code ec, tcp::socket socket) { + if (!ec) { + if(is_ssl) { + std::thread(&WebServer::do_session_ssl, this, std::move(socket)).detach(); + } + else { + std::thread(&WebServer::do_session, this, std::move(socket)).detach(); + } + } + do_accept(); +} + +void WebServer::do_session_ssl(tcp::socket socket) { + try { + ssl::stream stream(std::move(socket), ssl_ctx_); + stream.handshake(ssl::stream_base::server); + + bool close = false; + beast::flat_buffer buffer; + + while (!close) { + http::request req; + + http::read(stream, buffer, req); + + // Send the response + handle_request(std::move(req), [&](auto&& response) { + if (response.need_eof()) { + close = true; + } + http::write(stream, response); + }); + + if (close) break; + } + + beast::error_code ec; + socket.shutdown(tcp::socket::shutdown_send, ec); + } + catch (const std::exception& e) { + // irrelevant spam for now really + } +} + +void WebServer::do_session(tcp::socket socket) { + try { + bool close = false; + beast::flat_buffer buffer; + + while (!close) { + http::request req; + http::read(socket, buffer, req); + + // Send the response + handle_request(std::move(req), [&](auto&& response) { + if (response.need_eof()) { + close = true; + } + http::write(socket, response); + }); + + if (close) break; + } + + beast::error_code ec; + socket.shutdown(tcp::socket::shutdown_send, ec); + } + catch (const std::exception& e) { + // irrelevant spam for now really + } +} + + +template +void WebServer::handle_request(http::request>&& req, std::function&&)> send) { + auto it = noauth_routes_.find(req.target().to_string()); + if (it != noauth_routes_.end()) { + http::response res{http::status::ok, req.version()}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + it->second(req, res); + return send(std::move(res)); + } + int32 user_status = 0; + std::string session_id = authenticate(req, &user_status); + if (session_id.size() < 1) { + http::response res{http::status::unauthorized, req.version()}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::www_authenticate, "Basic realm=\"example\""); + res.body() = "Unauthorized"; + res.prepare_payload(); + return send(std::move(res)); + } + + auto status_it = route_required_status_.find(req.target().to_string()); + if (status_it != route_required_status_.end()) { + if(status_it->second > 0 && status_it->second != 0xFFFFFFFF && status_it->second > user_status) { + http::response res{http::status::unauthorized, req.version()}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.body() = "Unauthorized status"; + res.prepare_payload(); + return send(std::move(res)); + } + } + + it = routes_.find(req.target().to_string()); + if (it != routes_.end()) { + http::response res{http::status::ok, req.version()}; + res.set(http::field::set_cookie, "session_id=" + session_id); + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + it->second(req, res); + return send(std::move(res)); + } +/* + + http::response res{http::status::not_found, req.version()}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.body() = "Not Found"; + res.prepare_payload(); + return send(std::move(res)); + */ + return send(http::response{http::status::bad_request, req.version()}); +} + + +std::string WebServer::authenticate(const http::request& req, int32* user_status) { + auto it = req.find(http::field::cookie); + if (it != req.end()) { + std::istringstream cookie_stream(it->value().to_string()); + std::string session_id; + std::getline(cookie_stream, session_id, '='); + if (session_id == "session_id") { + std::string id; + std::getline(cookie_stream, id); + if (sessions_.find(id) != sessions_.end()) { + if(sessions_status_.find(id) != sessions_status_.end()) { + *user_status = sessions_status_[id]; + } + return id; + } + } + } + + it = req.find(http::field::authorization); + if (it != req.end()) { + std::string auth_header = it->value().to_string(); + if (auth_header.substr(0, 6) == "Basic ") { + std::string encoded_credentials = auth_header.substr(6); + std::string decoded_credentials; + decoded_credentials.resize(boost::beast::detail::base64::decoded_size(encoded_credentials.size())); + auto result = boost::beast::detail::base64::decode( + &decoded_credentials[0], + encoded_credentials.data(), + encoded_credentials.size() + ); + decoded_credentials.resize(result.first); + + std::istringstream credentials_stream(decoded_credentials); + std::string username, password; + std::getline(credentials_stream, username, ':'); + std::getline(credentials_stream, password); + int32 out_status = 0; + if ((credentials_.find(username) != credentials_.end() && credentials_[username] == password) || (database.AuthenticateWebUser((char*)username.c_str(),(char*)password.c_str(), &out_status) > 0)) { + std::string session_id = generate_session_id(); + sessions_[session_id] = username; + sessions_status_[session_id] = out_status; + *user_status = out_status; + return session_id; + } + } + } + + return std::string(""); +} + +std::string WebServer::generate_session_id() { + static std::mt19937 rng{std::random_device{}()}; + static std::uniform_int_distribution<> dist(0, 15); + std::string session_id; + for (int i = 0; i < 32; ++i) { + session_id += "0123456789abcdef"[dist(rng)]; + } + return session_id; +} + +// Explicit template instantiation +template void WebServer::handle_request>( + http::request>>&&, + std::function&&)> +); \ No newline at end of file diff --git a/source/common/Web/WebServer.h b/source/common/Web/WebServer.h new file mode 100644 index 0000000..e30ae90 --- /dev/null +++ b/source/common/Web/WebServer.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../types.h" + +namespace beast = boost::beast; // from +namespace http = beast::http; // from +namespace boost_net = boost::asio; // from +namespace ssl = boost::asio::ssl; // from +using tcp = boost_net::ip::tcp; // from + +class WebServer { +public: + WebServer(const std::string& address, unsigned short port, const std::string& cert_file, const std::string& key_file, const std::string& key_password, const std::string& hardcode_user, const std::string& hardcode_password); + ~WebServer(); + + void run(); + void start(); + + void register_route(const std::string& uri, std::function&, http::response&)> handler, bool auth_required = true); +private: + bool is_ssl; + static std::string my_password_callback(std::size_t max_length, ssl::context::password_purpose purpose); + void do_accept(); + void on_accept(beast::error_code ec, tcp::socket socket); + void do_session_ssl(tcp::socket socket); + void do_session(tcp::socket socket); + + template + void handle_request(http::request>&& req, std::function&&)> send); + + std::string authenticate(const http::request& req, int32* user_status = 0); + std::string generate_session_id(); + + boost_net::io_context ioc_; + ssl::context ssl_ctx_; + tcp::acceptor acceptor_; + std::unordered_map sessions_; // session_id -> username + std::unordered_map sessions_status_; // session_id -> status + + std::unordered_map credentials_; // username -> password + std::unordered_map route_required_status_; // route -> status + std::unordered_map&, http::response&)>> routes_; + std::unordered_map&, http::response&)>> noauth_routes_; +}; diff --git a/source/common/Web/WebServer.o b/source/common/Web/WebServer.o new file mode 100644 index 0000000..39f6920 Binary files /dev/null and b/source/common/Web/WebServer.o differ diff --git a/source/common/database.cpp b/source/common/database.cpp new file mode 100644 index 0000000..9a88a5e --- /dev/null +++ b/source/common/database.cpp @@ -0,0 +1,567 @@ +/* + 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 . +*/ +#include "../common/debug.h" + +#include +using namespace std; +#include +#include +#include +#include +//#include +#include +#include +#include +#include + +// Disgrace: for windows compile +#ifdef WIN32 +#include +#include +#define snprintf _snprintf +#define strncasecmp _strnicmp +#define strcasecmp _stricmp +#else +#include "unix.h" +#include +#endif + +#include "database.h" +#include "EQStream.h" +#include "packet_functions.h" +#include "emu_opcodes.h" +#ifdef WORLD + #include "../WorldServer/WorldDatabase.h" + extern WorldDatabase database; +#endif +#ifdef LOGIN + #include "../LoginServer/LoginDatabase.h" + extern LoginDatabase database; +#endif +#ifdef PARSER + #include "../PacketParser/ParserDatabase.h" + extern ParserDatabase database; +#endif + +#ifdef PATCHER + #include "../PatchServer/PatcherDatabase.h" + extern PatcherDatabase database; +#endif +#include "../common/EQEMuError.h" +#include "../common/packet_dump.h" +#include "../common/Log.h" + +#ifdef WORLD +ThreadReturnType DBAsyncQueries(void* str) +{ + // allow some buffer for multiple queries to collect + Sleep(10); + DBStruct* data = (DBStruct*)str; + database.RunAsyncQueries(data->queryid); + delete data; + THREAD_RETURN(NULL); +} +#endif + +Database::Database() +{ + InitVars(); +} + +bool Database::Init(bool silentLoad) { + char host[200], user[200], passwd[200], database[200]; + unsigned int port=0; + bool compression = false; + bool items[6] = {false, false, false, false, false, false}; + const char* exampleIni[] = { "[Database]", "host = localhost", "user = root", "password = pass", "database = dbname", "### --- Assure each parameter is on a new line!" }; + + if(!ReadDBINI(host, user, passwd, database, &port, &compression, items)) { + //exit(1); + return false; + } + + if (!items[0] || !items[1] || !items[2] || !items[3]) + { + LogWrite(DATABASE__ERROR, 0, "DB", "Database file %s is incomplete.", DB_INI_FILE); + int i; + for (i = 0; i < 4; i++) + { + if ( !items[i] ) + LogWrite(DATABASE__ERROR, 0, "DB", "Could not find parameter %s", exampleIni[i+1]); // offset by 1 because the [Database] entry + } + LogWrite(DATABASE__ERROR, 0, "DB", "Example File:"); + int length = sizeof exampleIni / sizeof exampleIni[0]; + for(i=0;i Database::GetVersions(){ + map opcodes; + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "select distinct version_range1, version_range2 from opcodes"); + while(result && (row = mysql_fetch_row(result))){ + if(row[0] && row[1]) + opcodes[atoi(row[0])] = atoi(row[1]); + } + return opcodes; +} + +map Database::GetOpcodes(int16 version){ + map opcodes; + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "select name, opcode from opcodes where %i between version_range1 and version_range2 order by version_range1, id", version); + while(result && (row = mysql_fetch_row(result))){ + opcodes[row[0]] = atoi(row[1]); + } + return opcodes; +} + +int32 Database::AuthenticateWebUser(char* userName, char* passwd, int32* status){ + if(status) { + *status = 0; + } + Query query; + MYSQL_ROW row; + int32 id = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "select id, status from web_users where username='%s' and passwd = sha2('%s', 512)", getSafeEscapeString(userName).c_str(), getSafeEscapeString(passwd).c_str()); + if(result && (row = mysql_fetch_row(result))){ + id = atoul(row[0]); + if(status) { + *status = atoul(row[1]); + } + } + return id; +} + +int32 Database::NoAuthRoute(char* route){ + Query query; + MYSQL_ROW row; + int32 status = 0xFFFFFFFF; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "select status from web_routes where route='%s'", getSafeEscapeString(route).c_str()); + if(result && (row = mysql_fetch_row(result))){ + status = atoul(row[0]); + } + return status; +} + +void Database::HandleMysqlError(int32 errnum) { + switch(errnum) { + case 0: + break; + case 1045: // Access Denied + case 2001: { + AddEQEMuError(EQEMuError_Mysql_1405, true); + break; + } + case 2003: { // Unable to connect + AddEQEMuError(EQEMuError_Mysql_2003, true); + break; + } + case 2005: { // Unable to connect + AddEQEMuError(EQEMuError_Mysql_2005, true); + break; + } + case 2007: { // Unable to connect + AddEQEMuError(EQEMuError_Mysql_2007, true); + break; + } + } +} + +void Database::InitVars() { + +} + +Database::~Database() +{ +#ifdef WORLD + DBQueryMutex.writelock(__FUNCTION__, __LINE__); + activeQuerySessions.clear(); + DBQueryMutex.releasewritelock(__FUNCTION__, __LINE__); + + DBAsyncMutex.writelock(); + continueAsync = false; + map>::iterator itr; + for (itr = asyncQueries.begin(); itr != asyncQueries.end(); itr++) + { + asyncQueriesMutex[itr->first]->writelock(); + deque queries = itr->second; + while (queries.size() > 0) + { + Query* cur = queries.front(); + queries.pop_front(); + safe_delete(cur); + } + asyncQueriesMutex[itr->first]->releasewritelock(); + Mutex* mutex = asyncQueriesMutex[itr->first]; + asyncQueriesMutex.erase(itr->first); + safe_delete(mutex); + } + asyncQueries.clear(); + + asyncQueriesMutex.clear(); + DBAsyncMutex.releasewritelock(); + + PurgeDBInstances(); +#endif +} + +#ifdef WORLD +void Query::AddQueryAsync(int32 queryID, Database* db, QUERY_TYPE type, const char* format, ...) { + in_type = type; + va_list args; + va_start(args, format); +#ifdef WIN32 + char* buffer; + int buf_len = _vscprintf(format, args) + 1; + buffer = new char[buf_len]; + vsprintf(buffer, format, args); +#else + char* buffer; + int buf_len; + va_list argcopy; + va_copy(argcopy, args); + buf_len = vsnprintf(NULL, 0, format, argcopy) + 1; + va_end(argcopy); + + buffer = new char[buf_len]; + vsnprintf(buffer, buf_len, format, args); +#endif + va_end(args); + query = string(buffer); + + Query* asyncQuery = new Query(this, queryID); + + safe_delete_array(buffer); + + db->AddAsyncQuery(asyncQuery); +} + +void Query::RunQueryAsync(Database* db) { + db->RunQuery(query.c_str(), query.length(), errbuf, &result, affected_rows, last_insert_id, &errnum, retry); +} +#endif + +MYSQL_RES* Query::RunQuery2(QUERY_TYPE type, const char* format, ...){ + va_list args; + va_start( args, format ); + #ifdef WIN32 + char * buffer; + int buf_len = _vscprintf( format, args ) + 1; + buffer = new char[buf_len]; + vsprintf( buffer, format, args ); + #else + char* buffer; + int buf_len; + va_list argcopy; + va_copy(argcopy, args); + buf_len = vsnprintf(NULL, 0, format, argcopy) + 1; + va_end(argcopy); + + buffer = new char[buf_len]; + vsnprintf(buffer, buf_len, format, args); + #endif + va_end(args); + query = string(buffer); + + + safe_delete_array( buffer ); + + + return RunQuery2(query.c_str(), type); +} +MYSQL_RES* Query::RunQuery2(string in_query, QUERY_TYPE type){ + switch(type){ + case Q_SELECT: + break; + case Q_DBMS: + case Q_REPLACE: + case Q_DELETE: + case Q_UPDATE: + safe_delete(affected_rows); + affected_rows = new int32; + break; + case Q_INSERT: + safe_delete(last_insert_id); + last_insert_id = new int32; + } + if(result){ + if(!multiple_results) + multiple_results = new vector(); + multiple_results->push_back(result); + } + query = in_query; + +#if defined WORLD && defined _DEBUG + if (type == Q_UPDATE || type == Q_INSERT || type == Q_DELETE || type == Q_REPLACE) + { + char* filteredTables[] = { " characters", " character_", " `character_", " statistics", " variables", " char_colors", " `guild", " bugs" }; + + bool match = false; + for (int i = 0; i < sizeof(filteredTables) / sizeof(filteredTables[0]); i++) + { + if (query.find(filteredTables[i]) != std::string::npos) { + match = true; + break; + } + } + try + { + if (!match) + { + FILE* pFile; + pFile = fopen("sql_updates.sql", "a+"); + fwrite(query.c_str(), 1, query.length(), pFile); + fwrite(";", sizeof(char), 1, pFile); + fwrite("\n", sizeof(char), 1, pFile); + fclose(pFile); + } + } + catch (...) {} + } +#endif + + + database.RunQuery(query.c_str(), query.length(), errbuf, &result, affected_rows, last_insert_id, &errnum, retry); + return result; +} + +#ifdef WORLD +void Database::RunAsyncQueries(int32 queryid) +{ + Database* asyncdb = FindFreeInstance(); + DBAsyncMutex.writelock(); + map>::iterator itr = asyncQueries.find(queryid); + if (itr == asyncQueries.end()) + { + DBAsyncMutex.releasewritelock(); + return; + } + + asyncQueriesMutex[queryid]->writelock(); + deque queries; + while (itr->second.size()) + { + Query* cur = itr->second.front(); + queries.push_back(cur); + itr->second.pop_front(); + } + itr->second.clear(); + asyncQueries.erase(itr); + DBAsyncMutex.releasewritelock(); + asyncQueriesMutex[queryid]->releasewritelock(); + + int32 count = 0; + while (queries.size() > 0) + { + Query* cur = queries.front(); + cur->RunQueryAsync(asyncdb); + this->RemoveActiveQuery(cur); + queries.pop_front(); + safe_delete(cur); + } + FreeDBInstance(asyncdb); + + bool isActive = IsActiveQuery(queryid); + if (isActive) + { + continueAsync = true; + DBStruct* tmp = new DBStruct; + tmp->queryid = queryid; +#ifdef WIN32 + _beginthread(DBAsyncQueries, 0, (void*)tmp); +#else + pthread_t t1; + pthread_create(&t1, NULL, DBAsyncQueries, (void*)tmp); + pthread_detach(t1); +#endif + } +} + +void Database::AddAsyncQuery(Query* query) +{ + DBAsyncMutex.writelock(); + map::iterator mutexItr = asyncQueriesMutex.find(query->GetQueryID()); + if (mutexItr == asyncQueriesMutex.end()) + { + Mutex* queryMutex = new Mutex(); + queryMutex->SetName("AsyncQuery" + query->GetQueryID()); + asyncQueriesMutex.insert(make_pair(query->GetQueryID(), queryMutex)); + } + map>::iterator itr = asyncQueries.find(query->GetQueryID()); + asyncQueriesMutex[query->GetQueryID()]->writelock(); + + if ( itr != asyncQueries.end()) + itr->second.push_back(query); + else + { + deque queue; + queue.push_back(query); + asyncQueries.insert(make_pair(query->GetQueryID(), queue)); + } + + AddActiveQuery(query); + + asyncQueriesMutex[query->GetQueryID()]->releasewritelock(); + DBAsyncMutex.releasewritelock(); + + bool isActive = IsActiveQuery(query->GetQueryID(), query); + if (!isActive) + { + continueAsync = true; + DBStruct* tmp = new DBStruct; + tmp->queryid = query->GetQueryID(); +#ifdef WIN32 + _beginthread(DBAsyncQueries, 0, (void*)tmp); +#else + pthread_t t1; + pthread_create(&t1, NULL, DBAsyncQueries, (void*)tmp); + pthread_detach(t1); +#endif + } +} + +Database* Database::FindFreeInstance() +{ + Database* db_inst = 0; + map::iterator itr; + DBInstanceMutex.writelock(__FUNCTION__, __LINE__); + for (itr = dbInstances.begin(); itr != dbInstances.end(); itr++) { + if (!itr->second) + { + db_inst = itr->first; + itr->second = true; + break; + } + } + + if (!db_inst) + { + WorldDatabase* tmp = new WorldDatabase(); + db_inst = (Database*)tmp; + tmp->Init(); + tmp->ConnectNewDatabase(); + dbInstances.insert(make_pair(db_inst, true)); + } + DBInstanceMutex.releasewritelock(__FUNCTION__, __LINE__); + + return db_inst; +} + +void Database::PurgeDBInstances() +{ + map::iterator itr; + DBInstanceMutex.writelock(__FUNCTION__, __LINE__); + for (itr = dbInstances.begin(); itr != dbInstances.end(); itr++) { + WorldDatabase* tmpInst = (WorldDatabase*)itr->first; + safe_delete(tmpInst); + } + dbInstances.clear(); + DBInstanceMutex.releasewritelock(__FUNCTION__, __LINE__); +} + + +void Database::PingAsyncDatabase() +{ + map::iterator itr; + DBInstanceMutex.readlock(__FUNCTION__, __LINE__); + for (itr = dbInstances.begin(); itr != dbInstances.end(); itr++) { + Database* tmpInst = itr->first; + tmpInst->ping(); + } + DBInstanceMutex.releasereadlock(__FUNCTION__, __LINE__); +} + +void Database::FreeDBInstance(Database* cur) +{ + DBInstanceMutex.writelock(__FUNCTION__, __LINE__); + dbInstances[cur] = false; + DBInstanceMutex.releasewritelock(__FUNCTION__, __LINE__); +} + +void Database::RemoveActiveQuery(Query* query) +{ + DBQueryMutex.writelock(__FUNCTION__, __LINE__); + + vector::iterator itr; + for (itr = activeQuerySessions.begin(); itr != activeQuerySessions.end(); itr++) + { + Query* curQuery = *itr; + if (query == curQuery) + { + activeQuerySessions.erase(itr); + break; + } + } + DBQueryMutex.releasewritelock(__FUNCTION__, __LINE__); +} + +void Database::AddActiveQuery(Query* query) +{ + DBQueryMutex.writelock(__FUNCTION__, __LINE__); + activeQuerySessions.push_back(query); + DBQueryMutex.releasewritelock(__FUNCTION__, __LINE__); +} + +bool Database::IsActiveQuery(int32 id, Query* skip) +{ + bool isActive = false; + + DBQueryMutex.readlock(__FUNCTION__, __LINE__); + vector::iterator itr; + for (itr = activeQuerySessions.begin(); itr != activeQuerySessions.end(); itr++) + { + Query* query = *itr; + if (query == skip) + continue; + + if (query->GetQueryID() == id) + { + isActive = true; + break; + } + } + DBQueryMutex.releasereadlock(__FUNCTION__, __LINE__); + + return isActive; +} +#endif \ No newline at end of file diff --git a/source/common/database.h b/source/common/database.h new file mode 100644 index 0000000..4a5fbd2 --- /dev/null +++ b/source/common/database.h @@ -0,0 +1,183 @@ +/* + 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 . +*/ +#ifndef EQ2EMU_DATABASE_H +#define EQ2EMU_DATABASE_H + +#ifdef WIN32 + #include + #include +#endif +#include + +#include "dbcore.h" +#include "types.h" +#include "linked_list.h" +#include "EQStream.h" +#include "MiscFunctions.h" +#include "Mutex.h" +#include +#include +#include + +using namespace std; +class Query; + +class Database : public DBcore +{ +public: + Database(); + ~Database(); + bool Init(bool silentLoad=false); + bool LoadVariables(); + void HandleMysqlError(int32 errnum); + map GetOpcodes(int16 version); + int32 AuthenticateWebUser(char* userName, char* passwd,int32* status = 0); + int32 NoAuthRoute(char* route); + map GetVersions(); + +#ifdef WORLD + void AddAsyncQuery(Query* query); + void RunAsyncQueries(int32 queryid); + Database* FindFreeInstance(); + void RemoveActiveQuery(Query* query); + void AddActiveQuery(Query* query); + bool IsActiveQuery(int32 id, Query* skip=0); + void PingAsyncDatabase(); +#endif +protected: + +private: + void InitVars(); + +#ifdef WORLD + void PurgeDBInstances(); + void FreeDBInstance(Database* cur); + bool continueAsync; + map> asyncQueries; + map asyncQueriesMutex; + map dbInstances; + vector activeQuerySessions; + Mutex DBAsyncMutex; + Mutex DBInstanceMutex; + Mutex DBQueryMutex; +#endif +}; + +typedef struct { + int32 queryid; +}DBStruct; + +class Query{ +public: + Query() { + result = 0; + affected_rows = 0; + last_insert_id = 0; + errnum = 0; + row = 0; + retry = true; + escaped_name = 0; + escaped_pass = 0; + escaped_data1 = 0; + multiple_results = 0; + memset(errbuf, 0, sizeof(errbuf)); + queryID = 0; + } + Query(Query* queryPtr, int32 in_id) { + result = 0; + affected_rows = 0; + last_insert_id = 0; + errnum = 0; + row = 0; + retry = true; + escaped_name = 0; + escaped_pass = 0; + escaped_data1 = 0; + multiple_results = 0; + memset(errbuf, 0, sizeof(errbuf)); + query = string(queryPtr->GetQuery()); + in_type = queryPtr->GetQueryType(); + queryID = in_id; + } + + ~Query(){ + if(result) + mysql_free_result(result); + result = 0; + safe_delete(affected_rows); + safe_delete(last_insert_id); + safe_delete_array(escaped_name); + safe_delete_array(escaped_pass); + safe_delete_array(escaped_data1); + if(multiple_results){ + vector::iterator itr; + for(itr = multiple_results->begin(); itr != multiple_results->end(); itr++){ + mysql_free_result(*itr); + } + safe_delete(multiple_results); + } + } + int32 GetLastInsertedID() { return *last_insert_id; } + int32 GetAffectedRows() { return *affected_rows; } + MYSQL_RES* GetResult() { return result; } + MYSQL_RES* RunQuery2(string in_query, QUERY_TYPE type); + char* GetError() { return errbuf; } + int32 GetErrorNumber(){ return errnum; } + const char* GetQuery() { return query.c_str(); } + char* GetField(int8 field_num) { + if(!row && result) + *row = mysql_fetch_row(result); + if(row && result && field_num < mysql_num_fields(result)) + return *row[field_num]; + else + return NULL; + } + void NextRow(){ + if(result) + *row = mysql_fetch_row(result); + } + void AddQueryAsync(int32 queryID, Database* db, QUERY_TYPE type, const char* format, ...); + void RunQueryAsync(Database* db); + MYSQL_RES* RunQuery2(QUERY_TYPE type, const char* format, ...); + + QUERY_TYPE GetQueryType() { + return in_type; + } + + int32 GetQueryID() { return queryID; } + + char* escaped_name; + char* escaped_pass; + char* escaped_data1; +private: + string query; + char errbuf[MYSQL_ERRMSG_SIZE]; + MYSQL_RES *result; + vector* multiple_results; + int32* affected_rows; + int32* last_insert_id; + int32 errnum; + QUERY_TYPE in_type; + bool retry; + MYSQL_ROW* row; + MYSQL mysql; + int32 queryID; +}; +#endif diff --git a/source/common/dbcore.cpp b/source/common/dbcore.cpp new file mode 100644 index 0000000..e200e9d --- /dev/null +++ b/source/common/dbcore.cpp @@ -0,0 +1,368 @@ +/* + 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 . +*/ +#include "debug.h" + +#include +using namespace std; +#include +//#include +#include +#include "dbcore.h" +#include +#include +#include +#include "types.h" +#include "MiscFunctions.h" +#include "Log.h" + +#ifdef WIN32 + #define snprintf _snprintf + #define strncasecmp _strnicmp + #define strcasecmp _stricmp + #include +#else + #include "unix.h" + #include +#endif + +#ifdef _EQDEBUG + #define DEBUG_MYSQL_QUERIES 0 +#else + #define DEBUG_MYSQL_QUERIES 0 +#endif + +DBcore::DBcore() { + mysql_init(&mysql); + pHost = 0; + pPort = 0; + pUser = 0; + pPassword = 0; + pDatabase = 0; + pCompress = false; +pSSL = false; +pStatus = Closed; +} + +DBcore::~DBcore() { + pStatus = Closed; + mysql_close(&mysql); +#if MYSQL_VERSION_ID >= 50003 + mysql_library_end(); +#else + mysql_server_end(); +#endif + safe_delete_array(pHost); + safe_delete_array(pUser); + safe_delete_array(pPassword); + safe_delete_array(pDatabase); +} + + +bool DBcore::ReadDBINI(char* host, char* user, char* passwd, char* database, unsigned int* port, bool* compress, bool* items) { + char line[256], * key, * val; + bool on_database_section = false; + FILE* f; + + if ((f = fopen(DB_INI_FILE, "r")) == NULL) { + LogWrite(DATABASE__ERROR, 0, "DBCore", "Unable to open '%s' for reading", DB_INI_FILE); + return false; + } + + //read each line + while (fgets(line, sizeof(line), f) != NULL) { + + //remove any new line or carriage return + while ((key = strstr(line, "\n")) != NULL) + *key = '\0'; + while ((key = strstr(line, "\r")) != NULL) + *key = '\0'; + + //ignore blank lines and commented lines + if (strlen(line) == 0 || line[0] == '#') + continue; + + key = strtok(line, "="); + + if (key == NULL) + continue; + + //don't do anything until we find the [Database] section + if (!on_database_section && strncasecmp(key, "[Database]", 10) == 0) + on_database_section = true; + else { + val = strtok(NULL, "="); + + if (val == NULL) + { + if (strcasecmp(key, "password") == 0) { + strcpy(passwd, ""); + items[2] = true; + } + continue; + } + if (strcasecmp(key, "host") == 0) { + strcpy(host, val); + items[0] = true; + } + else if (strcasecmp(key, "user") == 0) { + strcpy(user, val); + items[1] = true; + } + else if (strcasecmp(key, "password") == 0) { + strcpy(passwd, val); + items[2] = true; + } + else if (strcasecmp(key, "database") == 0) { + strcpy(database, val); + items[3] = true; + } + else if (strcasecmp(key, "port") == 0 && port) { + *port = atoul(val); + items[4] = true; + } + else if (strcasecmp(key, "compression") == 0) { + if (strcasecmp(val, "on") == 0) { + if(compress) { + *compress = true; + items[5] = true; + LogWrite(DATABASE__INFO, 0, "DBCore", "DB Compression on."); + } + } + } + } + } + + fclose(f); + + if (!on_database_section) { + LogWrite(DATABASE__ERROR, 0, "DBCore", "[Database] section not found in '%s'", DB_INI_FILE); + return false; + } + + return true; +} + + +// Sends the MySQL server a keepalive +void DBcore::ping() { + if (!MDatabase.trylock()) { + // well, if's it's locked, someone's using it. If someone's using it, it doesnt need a keepalive + return; + } + mysql_ping(&mysql); + + int32* errnum = new int32; + *errnum = mysql_errno(&mysql); + + switch (*errnum) + { + case CR_COMMANDS_OUT_OF_SYNC: + case CR_SERVER_GONE_ERROR: + case CR_UNKNOWN_ERROR: + { + LogWrite(DATABASE__ERROR, 0, "DBCore", "[Database] We lost connection to the database., errno: %i", errno); + break; + } + } + + safe_delete(errnum); + MDatabase.unlock(); +} + +bool DBcore::RunQuery(const char* query, int32 querylen, char* errbuf, MYSQL_RES** result, int32* affected_rows, int32* last_insert_id, int32* errnum, bool retry) { + if (errnum) + *errnum = 0; + if (errbuf) + errbuf[0] = 0; + bool ret = false; + LockMutex lock(&MDatabase); + if (pStatus != Connected) + Open(); + + LogWrite(DATABASE__QUERY, 0, "DBCore", query); + if (mysql_real_query(&mysql, query, querylen)) { + if (mysql_errno(&mysql) == CR_SERVER_GONE_ERROR) + pStatus = Error; + if (mysql_errno(&mysql) == CR_SERVER_LOST || mysql_errno(&mysql) == CR_SERVER_GONE_ERROR) { + if (retry) { + LogWrite(DATABASE__ERROR, 0, "DBCore", "Lost connection, attempting to recover..."); + ret = RunQuery(query, querylen, errbuf, result, affected_rows, last_insert_id, errnum, false); + } + else { + pStatus = Error; + if (errnum) + *errnum = mysql_errno(&mysql); + if (errbuf) + snprintf(errbuf, MYSQL_ERRMSG_SIZE, "#%i: %s", mysql_errno(&mysql), mysql_error(&mysql)); + LogWrite(DATABASE__ERROR, 0, "DBCore", "#%i: %s\nQuery:\n%s", mysql_errno(&mysql), mysql_error(&mysql), query); + ret = false; + } + } + else { + if (errnum) + *errnum = mysql_errno(&mysql); + if (errbuf) + snprintf(errbuf, MYSQL_ERRMSG_SIZE, "#%i: %s", mysql_errno(&mysql), mysql_error(&mysql)); + LogWrite(DATABASE__ERROR, 0, "DBCore", "#%i: %s\nQuery:\n%s", mysql_errno(&mysql), mysql_error(&mysql), query); + ret = false; + } + } + else { + if (result && mysql_field_count(&mysql)) { + *result = mysql_store_result(&mysql); + } + else if (result) + *result = 0; + if (affected_rows) + *affected_rows = mysql_affected_rows(&mysql); + if (last_insert_id) + *last_insert_id = mysql_insert_id(&mysql); + if (result) { + if (*result) { + ret = true; + } + else { + if (errnum) + *errnum = UINT_MAX; + if (errbuf){ + if((!affected_rows || (affected_rows && *affected_rows == 0)) && (!last_insert_id || (last_insert_id && *last_insert_id == 0))) + LogWrite(DATABASE__RESULT, 1, "DBCore", "No Result."); + } + ret = false; + } + } + else { + ret = true; + } + } + + if (ret) + { + char tmp1[200] = {0}; + char tmp2[200] = {0}; + if (result && (*result)) + snprintf(tmp1, sizeof(tmp1), ", %i rows returned", (int) mysql_num_rows(*result)); + if (affected_rows) + snprintf(tmp2, sizeof(tmp2), ", %i rows affected", (*affected_rows)); + + LogWrite(DATABASE__DEBUG, 0, "DBCore", "Query Successful%s%s", tmp1, tmp2); + } + else + LogWrite(DATABASE__DEBUG, 0, "DBCore", "Query returned no results in %s!\n%s", __FUNCTION__, query); + + return ret; +} + +int32 DBcore::DoEscapeString(char* tobuf, const char* frombuf, int32 fromlen) { + LockMutex lock(&MDatabase); + return mysql_real_escape_string(&mysql, tobuf, frombuf, fromlen); +} + +bool DBcore::Open(const char* iHost, const char* iUser, const char* iPassword, const char* iDatabase,int32 iPort, int32* errnum, char* errbuf, bool iCompress, bool iSSL) { + LockMutex lock(&MDatabase); + safe_delete_array(pHost); + safe_delete_array(pUser); + safe_delete_array(pPassword); + safe_delete_array(pDatabase); + pHost = new char[strlen(iHost) + 1]; + strcpy(pHost, iHost); + pUser = new char[strlen(iUser) + 1]; + strcpy(pUser, iUser); + pPassword = new char[strlen(iPassword) + 1]; + strcpy(pPassword, iPassword); + pDatabase = new char[strlen(iDatabase) + 1]; + strcpy(pDatabase, iDatabase); + pCompress = iCompress; + pPort = iPort; + pSSL = iSSL; + return Open(errnum, errbuf); +} + +bool DBcore::Open(int32* errnum, char* errbuf) { + if (errbuf) + errbuf[0] = 0; + LockMutex lock(&MDatabase); + if (GetStatus() == Connected) + return true; + if (GetStatus() == Error) + mysql_close(&mysql); + if (!pHost) + return false; + /* + Quagmire - added CLIENT_FOUND_ROWS flag to the connect + otherwise DB update calls would say 0 rows affected when the value already equalled + what the function was tring to set it to, therefore the function would think it failed + */ + int32 flags = CLIENT_FOUND_ROWS; + if (pCompress) + flags |= CLIENT_COMPRESS; + if (pSSL) + flags |= CLIENT_SSL; + if (mysql_real_connect(&mysql, pHost, pUser, pPassword, pDatabase, pPort, 0, flags)) { + pStatus = Connected; + return true; + } + else { + if (errnum) + *errnum = mysql_errno(&mysql); + if (errbuf) + snprintf(errbuf, MYSQL_ERRMSG_SIZE, "#%i: %s", mysql_errno(&mysql), mysql_error(&mysql)); + pStatus = Error; + return false; + } +} + +char* DBcore::getEscapeString(const char* from_string){ + if(!from_string) + from_string =""; + int orig_size = strlen(from_string); + int escape_size = (orig_size * 2) + 1; + char* escaped = new char[escape_size]; + memset(escaped, 0, escape_size); + DoEscapeString(escaped, from_string, orig_size); + return escaped; +} + +string DBcore::getSafeEscapeString(const char* from_string){ + if(!from_string) + from_string =""; + int orig_size = strlen(from_string); + int escape_size = (orig_size * 2) + 1; + char* escaped = new char[escape_size]; + memset(escaped, 0, escape_size); + DoEscapeString(escaped, from_string, orig_size); + string ret = string(escaped); + safe_delete_array(escaped); + return ret; +} + +string DBcore::getSafeEscapeString(string* from_string){ + if(!from_string) + return ""; + int orig_size = from_string->length(); + int escape_size = (orig_size * 2) + 1; + char* escaped = new char[escape_size]; + memset(escaped, 0, escape_size); + DoEscapeString(escaped, from_string->c_str(), orig_size); + string ret = string(escaped); + safe_delete_array(escaped); + return ret; +} + diff --git a/source/common/dbcore.h b/source/common/dbcore.h new file mode 100644 index 0000000..b6cbfd2 --- /dev/null +++ b/source/common/dbcore.h @@ -0,0 +1,80 @@ +/* + 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 . +*/ +#ifndef DBCORE_H +#define DBCORE_H + +#ifdef WIN32 + #include + #include + //#include +#endif +#include +#include "../common/types.h" +#include "../common/Mutex.h" +#include "../common/linked_list.h" +#include "../common/queue.h" +#include "../common/timer.h" +#include "../common/Condition.h" +#ifdef LOGIN + #define DB_INI_FILE "login_db.ini" +#endif +#ifdef WORLD + #define DB_INI_FILE "world_db.ini" +#endif +#ifdef PARSER + #define DB_INI_FILE "parser_db.ini" +#endif +#ifdef PATCHER + #define DB_INI_FILE "patcher_db.ini" +#endif +class DBcore{ +public: + enum eStatus { Closed, Connected, Error }; + DBcore(); + ~DBcore(); + eStatus GetStatus() { return pStatus; } + bool RunQuery(const char* query, int32 querylen, char* errbuf = 0, MYSQL_RES** result = 0, int32* affected_rows = 0, int32* last_insert_id = 0, int32* errnum = 0, bool retry = true); + int32 DoEscapeString(char* tobuf, const char* frombuf, int32 fromlen); + void ping(); + char* getEscapeString(const char* from_string); + string getSafeEscapeString(const char* from_string); + string getSafeEscapeString(string* from_string); + +protected: + bool Open(const char* iHost, const char* iUser, const char* iPassword, const char* iDatabase, int32 iPort, int32* errnum = 0, char* errbuf = 0, bool iCompress = false, bool iSSL = false); + bool ReadDBINI(char *host, char *user, char *pass, char *db, unsigned int* port, bool* compress, bool *items); +private: + bool Open(int32* errnum = 0, char* errbuf = 0); + + MYSQL mysql; + Mutex MDatabase; + eStatus pStatus; + + char* pHost; + char* pUser; + char* pPassword; + char* pDatabase; + bool pCompress; + int32 pPort; + bool pSSL; +}; +#endif + + diff --git a/source/common/debug.cpp b/source/common/debug.cpp new file mode 100644 index 0000000..3f2f98b --- /dev/null +++ b/source/common/debug.cpp @@ -0,0 +1,336 @@ +/* + 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 . +*/ + +/* + JA: File rendered obsolete (2011-08-12) + +#include "debug.h" + +#include +using namespace std; +#include +#include +#ifdef WIN32 + #include + + #define snprintf _snprintf + #define vsnprintf _vsnprintf + #define strncasecmp _strnicmp + #define strcasecmp _stricmp +#else + #include + #include + #include +#endif +#include "../common/MiscFunctions.h" + +EQEMuLog* LogFile = new EQEMuLog; +AutoDelete adlf(&LogFile); + +static const char* FileNames[EQEMuLog::MaxLogID] = { "logs/eq2emu", "logs/eq2emu", "logs/eq2emu_error", "logs/eq2emu_debug", "logs/eq2emu_quest", "logs/eq2emu_commands" }; +static const char* LogNames[EQEMuLog::MaxLogID] = { "Status", "Normal", "Error", "Debug", "Quest", "Command" }; + +EQEMuLog::EQEMuLog() { + for (int i=0; i= 2 + pLogStatus[i] = 1 | 2; +#else + pLogStatus[i] = 0; +#endif + logCallbackFmt[i] = NULL; + logCallbackBuf[i] = NULL; + } +#if EQDEBUG < 2 + pLogStatus[Status] = 3; + pLogStatus[Error] = 3; + pLogStatus[Debug] = 3; + pLogStatus[Quest] = 2; + pLogStatus[Commands] = 2; +#endif +} + +EQEMuLog::~EQEMuLog() { + for (int i=0; i= MaxLogID) { + return false; + } + LockMutex lock(&MOpen); + if (pLogStatus[id] & 4) { + return false; + } + if (fp[id]) { + return true; + } + + char exename[200] = ""; +#if defined(WORLD) + snprintf(exename, sizeof(exename), "_world"); +#elif defined(ZONE) + snprintf(exename, sizeof(exename), "_zone"); +#endif + char filename[200]; +#ifndef NO_PIDLOG + snprintf(filename, sizeof(filename), "%s%s_%04i.log", FileNames[id], exename, getpid()); +#else + snprintf(filename, sizeof(filename), "%s%s.log", FileNames[id], exename); +#endif + fp[id] = fopen(filename, "a"); + if (!fp[id]) { + cerr << "Failed to open log file: " << filename << endl; + pLogStatus[id] |= 4; // set file state to error + return false; + } + fputs("---------------------------------------------\n",fp[id]); + return true; +} + +bool EQEMuLog::write(LogIDs id, const char *fmt, ...) { + char buffer[4096]; + + if (!this) { + return false; + } + if (id >= MaxLogID) { + return false; + } + bool dofile = false; + if (pLogStatus[id] & 1) { + dofile = open(id); + } + if (!(dofile || pLogStatus[id] & 2)) + return false; + LockMutex lock(&MLog[id]); + + time_t aclock; + struct tm *newtime; + + time( &aclock ); //Get time in seconds + newtime = localtime( &aclock ); //Convert time to struct + + if (dofile){ +#ifndef NO_PIDLOG + fprintf(fp[id], "[%04d%02d%02d %02d:%02d:%02d] ", newtime->tm_year+1900, newtime->tm_mon+1, newtime->tm_mday, newtime->tm_hour, newtime->tm_min, newtime->tm_sec); +#else + fprintf(fp[id], "%04i [%04d%02d%02d %02d:%02d:%02d] ", getpid(), newtime->tm_year+1900, newtime->tm_mon+1, newtime->tm_mday, newtime->tm_hour, newtime->tm_min, newtime->tm_sec); +#endif + } + + va_list argptr; + va_start(argptr, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, argptr); + va_end(argptr); + if (dofile) + fprintf(fp[id], "%s\n", buffer); + if(logCallbackFmt[id]) { + msgCallbackFmt p = logCallbackFmt[id]; + p(id, fmt, argptr ); + } + + if (pLogStatus[id] & 2) { + if (pLogStatus[id] & 8) { + fprintf(stderr, "[%04d%02d%02d %02d:%02d:%02d] [%s] ", newtime->tm_year+1900, newtime->tm_mon+1, newtime->tm_mday, newtime->tm_hour, newtime->tm_min, newtime->tm_sec, LogNames[id]); + fprintf(stderr, "%s\n", buffer); + } + else { + fprintf(stdout, "[%04d%02d%02d %02d:%02d:%02d] [%s] ", newtime->tm_year+1900, newtime->tm_mon+1, newtime->tm_mday, newtime->tm_hour, newtime->tm_min, newtime->tm_sec, LogNames[id]); + fprintf(stdout, "%s\n", buffer); + } + } + if (dofile) + fprintf(fp[id], "\n"); + if (pLogStatus[id] & 2) { + if (pLogStatus[id] & 8) + fprintf(stderr, "\n"); + else + fprintf(stdout, "\n"); + } + if(dofile) + fflush(fp[id]); + return true; +} + +bool EQEMuLog::writebuf(LogIDs id, const char *buf, int8 size, int32 count) { + if (!this) { + return false; + } + if (id >= MaxLogID) { + return false; + } + bool dofile = false; + if (pLogStatus[id] & 1) { + dofile = open(id); + } + if (!(dofile || pLogStatus[id] & 2)) + return false; + LockMutex lock(&MLog[id]); + + time_t aclock; + struct tm *newtime; + + time( &aclock ); // Get time in seconds + newtime = localtime( &aclock ); // Convert time to struct + + if (dofile){ +#ifndef NO_PIDLOG + fprintf(fp[id], "[%02d.%02d. - %02d:%02d:%02d] ", newtime->tm_mon+1, newtime->tm_mday, newtime->tm_hour, newtime->tm_min, newtime->tm_sec); +#else + fprintf(fp[id], "%04i [%02d.%02d. - %02d:%02d:%02d] ", getpid(), newtime->tm_mon+1, newtime->tm_mday, newtime->tm_hour, newtime->tm_min, newtime->tm_sec); +#endif + } + + if (dofile) { + fwrite(buf, size, count, fp[id]); + fprintf(fp[id], "\n"); + } + if(logCallbackBuf[id]) { + msgCallbackBuf p = logCallbackBuf[id]; + p(id, buf, size, count); + } + if (pLogStatus[id] & 2) { + if (pLogStatus[id] & 8) { + fprintf(stderr, "[%s] ", LogNames[id]); + fwrite(buf, size, count, stderr); + fprintf(stderr, "\n"); + } else { + fprintf(stdout, "[%s] ", LogNames[id]); + fwrite(buf, size, count, stdout); + fprintf(stdout, "\n"); + } + } + if(dofile) + fflush(fp[id]); + return true; +} + +bool EQEMuLog::writeNTS(LogIDs id, bool dofile, const char *fmt, ...) { + char buffer[4096]; + va_list argptr; + va_start(argptr, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, argptr); + va_end(argptr); + if (dofile) + fprintf(fp[id], "%s\n", buffer); + if (pLogStatus[id] & 2) { + if (pLogStatus[id] & 8) + fprintf(stderr, "%s\n", buffer); + else + fprintf(stdout, "%s\n", buffer); + } + return true; +}; + +bool EQEMuLog::Dump(LogIDs id, int8* data, int32 size, int32 cols, int32 skip) { + if (!this) { +#if EQDEBUG >= 10 + cerr << "Error: Dump() from null pointer"<= MaxLogID) + return false; + bool dofile = false; + if (pLogStatus[id] & 1) { + dofile = open(id); + } + if (!(dofile || pLogStatus[id] & 2)) + return false; + LockMutex lock(&MLog[id]); + write(id, "Dumping Packet: %i", size); + // Output as HEX + int j = 0; char* ascii = new char[cols+1]; memset(ascii, 0, cols+1); + int32 i; + for(i=skip; i= 32 && data[i] < 127) + ascii[j++] = data[i]; + else + ascii[j++] = '.'; + } + int32 k = ((i-skip)-1)%cols; + if (k < 8) + writeNTS(id, dofile, " "); + for (int32 h = k+1; h < cols; h++) { + writeNTS(id, dofile, " "); + } + writeNTS(id, dofile, " | %s\n", ascii); + if (dofile) + fflush(fp[id]); + safe_delete_array(ascii); + return true; +} + +void EQEMuLog::SetCallback(LogIDs id, msgCallbackFmt proc) { + if (!this) + return; + if (id >= MaxLogID) { + return; + } + logCallbackFmt[id] = proc; +} + +void EQEMuLog::SetCallback(LogIDs id, msgCallbackBuf proc) { + if (!this) + return; + if (id >= MaxLogID) { + return; + } + logCallbackBuf[id] = proc; +} + +void EQEMuLog::SetAllCallbacks(msgCallbackFmt proc) { + if (!this) + return; + int r; + for(r = Status; r < MaxLogID; r++) { + SetCallback((LogIDs)r, proc); + } +} + +void EQEMuLog::SetAllCallbacks(msgCallbackBuf proc) { + if (!this) + return; + int r; + for(r = Status; r < MaxLogID; r++) { + SetCallback((LogIDs)r, proc); + } +} +*/ \ No newline at end of file diff --git a/source/common/debug.h b/source/common/debug.h new file mode 100644 index 0000000..7422d5e --- /dev/null +++ b/source/common/debug.h @@ -0,0 +1,143 @@ +/* + 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 . +*/ +#ifndef EQDEBUG_H +#define EQDEBUG_H + +// Debug Levels +/* + 1 = Normal + 3 = Some extended debug info + 5 = Light DETAIL info + 7 = Heavy DETAIL info + 9 = DumpPacket/PrintPacket + You should use even numbers too, to define any subset of the above basic template +*/ +#ifndef EQDEBUG + #define EQDEBUG 1 +#endif + + +#if defined(DEBUG) && defined(WIN32) + //#ifndef _CRTDBG_MAP_ALLOC + #include + #include + #if (_MSC_VER < 1300) + #include + #include + #define _CRTDBG_MAP_ALLOC + #define new new(_NORMAL_BLOCK, __FILE__, __LINE__) + #define malloc(s) _malloc_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__) + #endif + //#endif +#endif + +#ifndef ThrowError + void CatchSignal(int); + #if defined(CATCH_CRASH) || defined(_EQDEBUG) + #define ThrowError(errstr) { cout << "Fatal error: " << errstr << " (" << __FILE__ << ", line " << __LINE__ << ")" << endl; LogWrite(WORLD__ERROR, 0, "Debug", "Thrown Error: %s (%s:%i)", errstr, __FILE__, __LINE__); throw errstr; } + #else + #define ThrowError(errstr) { cout << "Fatal error: " << errstr << " (" << __FILE__ << ", line " << __LINE__ << ")" << endl; LogWrite(WORLD__ERROR, 0, "Debug", "Thrown Error: %s (%s:%i)", errstr, __FILE__, __LINE__); CatchSignal(0); } + #endif +#endif + +#ifdef WIN32 + // VS6 doesn't like the length of STL generated names: disabling + #pragma warning(disable:4786) +#endif + +#ifndef WIN32 + #define DebugBreak() if(0) {} +#endif + +#ifdef WIN32 + #include + #include +#endif + +#include "../common/Mutex.h" +#include +#include + + +class EQEMuLog { +public: + EQEMuLog(); + ~EQEMuLog(); + + enum LogIDs { + Status = 0, //this must stay the first entry in this list + Normal, + Error, + Debug, + Quest, + Commands, + MaxLogID + }; + + //these are callbacks called for each + typedef void (* msgCallbackBuf)(LogIDs id, const char *buf, int8 size, int32 count); + typedef void (* msgCallbackFmt)(LogIDs id, const char *fmt, va_list ap); + + void SetAllCallbacks(msgCallbackFmt proc); + void SetAllCallbacks(msgCallbackBuf proc); + void SetCallback(LogIDs id, msgCallbackFmt proc); + void SetCallback(LogIDs id, msgCallbackBuf proc); + + bool writebuf(LogIDs id, const char *buf, int8 size, int32 count); + bool write(LogIDs id, const char *fmt, ...); + bool Dump(LogIDs id, int8* data, int32 size, int32 cols=16, int32 skip=0); +private: + bool open(LogIDs id); + bool writeNTS(LogIDs id, bool dofile, const char *fmt, ...); // no error checking, assumes is open, no locking, no timestamp, no newline + + Mutex MOpen; + Mutex MLog[MaxLogID]; + FILE* fp[MaxLogID]; +/* LogStatus: bitwise variable + 1 = output to file + 2 = output to stdout + 4 = fopen error, dont retry + 8 = use stderr instead (2 must be set) +*/ + int8 pLogStatus[MaxLogID]; + + msgCallbackFmt logCallbackFmt[MaxLogID]; + msgCallbackBuf logCallbackBuf[MaxLogID]; +}; + +//extern EQEMuLog* LogFile; + +#ifdef _EQDEBUG +class PerformanceMonitor { +public: + PerformanceMonitor(sint64* ip) { + p = ip; + QueryPerformanceCounter(&tmp); + } + ~PerformanceMonitor() { + LARGE_INTEGER tmp2; + QueryPerformanceCounter(&tmp2); + *p += tmp2.QuadPart - tmp.QuadPart; + } + LARGE_INTEGER tmp; + sint64* p; +}; +#endif +#endif diff --git a/source/common/emu_opcodes.cpp b/source/common/emu_opcodes.cpp new file mode 100644 index 0000000..a07135e --- /dev/null +++ b/source/common/emu_opcodes.cpp @@ -0,0 +1,39 @@ +/* + 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 . +*/ + +#include "emu_opcodes.h" + +const char *OpcodeNames[_maxEmuOpcode+1] = { + "OP_Unknown", + +//a preprocessor hack so we dont have to maintain two lists +#define N(x) #x +#if !defined(LOGIN) + #include "emu_oplist.h" +#endif +#ifdef LOGIN + #include "login_oplist.h" +#endif +#undef N + + "" +}; + + diff --git a/source/common/emu_opcodes.h b/source/common/emu_opcodes.h new file mode 100644 index 0000000..9011de1 --- /dev/null +++ b/source/common/emu_opcodes.h @@ -0,0 +1,56 @@ +/* + 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 . +*/ +#ifndef EMU_OPCODES_H +#define EMU_OPCODES_H + +//this is the highest opcode possibly used in the regular EQ protocol +#define MAX_EQ_OPCODE 0xFFFF +/* + + +the list of opcodes is in emu_oplist.h + +we somewhat rely on the fact that we have more than 255 opcodes, +so we know the enum type for the opcode defines must be at least +16 bits, so we can use the protocol flags on them. + +*/ + +typedef enum { //EQEmu internal opcodes list + OP_Unknown=0, + +//a preprocessor hack so we dont have to maintain two lists +#define N(x) x +#if !defined(LOGIN) + #include "emu_oplist.h" +#endif +#ifdef LOGIN + #include "login_oplist.h" +#endif +#undef N + + _maxEmuOpcode +} EmuOpcode; + +extern const char *OpcodeNames[_maxEmuOpcode+1]; + +#endif + + diff --git a/source/common/emu_oplist.h b/source/common/emu_oplist.h new file mode 100644 index 0000000..1784af6 --- /dev/null +++ b/source/common/emu_oplist.h @@ -0,0 +1,505 @@ +/* + 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 . +*/ +N(OP_LoginReplyMsg), +N(OP_LoginByNumRequestMsg), +N(OP_WSLoginRequestMsg), +N(OP_ESInitMsg), +N(OP_ESReadyForClientsMsg), +N(OP_CreateZoneInstanceMsg), +N(OP_ZoneInstanceCreateReplyMsg), +N(OP_ZoneInstanceDestroyedMsg), +N(OP_ExpectClientAsCharacterRequest), +N(OP_ExpectClientAsCharacterReplyMs), +N(OP_ZoneInfoMsg), +N(OP_CreateCharacterRequestMsg), +N(OP_DoneLoadingZoneResourcesMsg), +N(OP_DoneSendingInitialEntitiesMsg), +N(OP_DoneLoadingEntityResourcesMsg), +N(OP_DoneLoadingUIResourcesMsg), +N(OP_PredictionUpdateMsg), +N(OP_RemoteCmdMsg), +N(OP_SetRemoteCmdsMsg), +N(OP_GameWorldTimeMsg), +N(OP_MOTDMsg), +N(OP_ZoneMOTDMsg), +N(OP_GuildRecruitingMemberInfo), +N(OP_GuildRecruiting), +N(OP_GuildRecruitingDetails), +N(OP_GuildRecruitingImage), +N(OP_AvatarCreatedMsg), +N(OP_AvatarDestroyedMsg), +N(OP_RequestCampMsg), +N(OP_MapRequest), +N(OP_CampStartedMsg), +N(OP_CampAbortedMsg), +N(OP_WhoQueryRequestMsg), +N(OP_WhoQueryReplyMsg), +N(OP_MonitorReplyMsg), +N(OP_MonitorCharacterListMsg), +N(OP_MonitorCharacterListRequestMsg), +N(OP_ClientCmdMsg), +N(OP_Lottery), +N(OP_DispatchClientCmdMsg), +N(OP_DispatchESMsg), +N(OP_UpdateTargetMsg), +N(OP_UpdateOpportunityMsg), +N(OP_UpdateTargetLocMsg), +N(OP_UpdateCharacterSheetMsg), +N(OP_UpdateSpellBookMsg), +N(OP_UpdateInventoryMsg), +N(OP_UpdateRecipeBookMsg), +N(OP_RequestRecipeDetailsMsg), +N(OP_RecipeDetailsMsg), +N(OP_UpdateSkillBookMsg), +N(OP_UpdateSkillsMsg), +N(OP_ChangeZoneMsg), +N(OP_ClientTeleportRequestMsg), +N(OP_TeleportWithinZoneMsg), +N(OP_TeleportWithinZoneNoReloadMsg), +N(OP_MigrateClientToZoneRequestMsg), +N(OP_MigrateClientToZoneReplyMsg), +N(OP_ReadyToZoneMsg), +//N(OP_AddClientToGroupMsg), +//N(OP_AddGroupToGroupMsg), +N(OP_RemoveClientFromGroupMsg), +N(OP_RemoveGroupFromGroupMsg), +N(OP_MakeGroupLeaderMsg), +N(OP_GroupCreatedMsg), +N(OP_GroupDestroyedMsg), +N(OP_GroupMemberAddedMsg), +N(OP_GroupMemberRemovedMsg), +N(OP_GroupRemovedFromGroupMsg), +N(OP_GroupLeaderChangedMsg), +N(OP_GroupSettingsChangedMsg), +N(OP_SendLatestRequestMsg), +N(OP_ClearDataMsg), +N(OP_SetSocialMsg), +N(OP_ESStatusMsg), +N(OP_ESZoneInstanceStatusMsg), +N(OP_ZonesStatusRequestMsg), +N(OP_ZonesStatusMsg), +N(OP_ESWeatherRequestMsg), +N(OP_ESWeatherRequestEndMsg), +//N(OP_WSWeatherUpdateMsg), +N(OP_DialogSelectMsg), +N(OP_DialogCloseMsg), +N(OP_RemoveSpellEffectMsg), +N(OP_RemoveConcentrationMsg), +N(OP_QuestJournalOpenMsg), +N(OP_QuestJournalInspectMsg), +//N(OP_SkillSlotMapping), +N(OP_QuestJournalSetVisibleMsg), +N(OP_QuestJournalWaypointMsg), +N(OP_CreateGuildRequestMsg), +N(OP_CreateGuildReplyMsg), +N(OP_GuildsayMsg), +//N(OP_GuildKickMsg), +N(OP_GuildUpdateMsg), +N(OP_FellowshipExpMsg), +N(OP_ConsignmentCloseStoreMsg), +N(OP_ConsignItemRequestMsg), +N(OP_ConsignItemResponseMsg), +//N(OP_PurchaseConsignmentRequestMsg), +N(OP_PurchaseConsignmentLoreCheckRe), +N(OP_QuestReward), +//N(OP_PurchaseConsignmentResponseMsg), +//N(OP_ProcessScriptMsg), +//N(OP_ProcessWorkspaceMsg), +N(OP_HouseDeletedRemotelyMsg), +N(OP_UpdateHouseDataMsg), +N(OP_UpdateHouseAccessDataMsg), +N(OP_PlayerHouseBaseScreenMsg), +N(OP_PlayerHousePurchaseScreenMsg), +N(OP_PlayerHouseAccessUpdateMsg), +N(OP_PlayerHouseDisplayStatusMsg), +N(OP_PlayerHouseCloseUIMsg), +N(OP_BuyPlayerHouseMsg), +N(OP_BuyPlayerHouseTintMsg), +N(OP_CollectAllHouseItemsMsg), +N(OP_RelinquishHouseMsg), +N(OP_EnterHouseMsg), +N(OP_ExitHouseMsg), +N(OP_ExamineConsignmentRequestMsg), +N(OP_MoveableObjectPlacementCriteri), +N(OP_EnterMoveObjectModeMsg), +N(OP_PositionMoveableObject), +N(OP_CancelMoveObjectModeMsg), +N(OP_ShaderCustomizationMsg), +N(OP_ReplaceableSubMeshesMsg), +N(OP_ExamineConsignmentResponseMsg), +N(OP_HouseDefaultAccessSetMsg), +N(OP_HouseAccessSetMsg), +N(OP_HouseAccessRemoveMsg), +N(OP_PayHouseUpkeepMsg), +N(OP_TintWidgetsMsg), +N(OP_UISettingsResponseMsg), +N(OP_UIResetMsg), +N(OP_KeymapLoadMsg), +N(OP_KeymapNoneMsg), +N(OP_KeymapDataMsg), +N(OP_KeymapSaveMsg), +N(OP_DispatchSpellCmdMsg), +N(OP_HouseCustomizationScreenMsg), +N(OP_CustomizationPurchaseRequestMs), +N(OP_CustomizationSetRequestMsg), +N(OP_CustomizationReplyMsg), +N(OP_EntityVerbsRequestMsg), +N(OP_EntityVerbsReplyMsg), +N(OP_EntityVerbsVerbMsg), +N(OP_ChatRelationshipUpdateMsg), +N(OP_ChatCreateChannelMsg), +N(OP_ChatJoinChannelMsg), +N(OP_ChatWhoChannelMsg), +N(OP_ChatLeaveChannelMsg), +N(OP_ChatTellChannelMsg), +N(OP_ChatTellUserMsg), +N(OP_ChatToggleFriendMsg), +N(OP_ChatToggleIgnoreMsg), +N(OP_ChatSendFriendsMsg), +N(OP_ChatSendIgnoresMsg), +N(OP_ChatFiltersMsg), +N(OP_LootItemsRequestMsg), +N(OP_StoppedLootingMsg), +N(OP_SitMsg), +N(OP_StandMsg), +N(OP_SatMsg), +N(OP_StoodMsg), +//N(OP_QuickbarAddMsg), +N(OP_DefaultGroupOptionsRequestMsg), +N(OP_DefaultGroupOptionsMsg), +N(OP_GroupOptionsMsg), +N(OP_DisplayGroupOptionsScreenMsg), +N(OP_DisplayInnVisitScreenMsg), +N(OP_DumpSchedulerMsg), +//N(OP_LSRequestPlayerDescMsg), +N(OP_LSCheckAcctLockMsg), +N(OP_WSAcctLockStatusMsg), +N(OP_RequestHelpRepathMsg), +N(OP_UpdateMotdMsg), +N(OP_RequestTargetLocMsg), +N(OP_PerformPlayerKnockbackMsg), +N(OP_PerformCameraShakeMsg), +N(OP_PopulateSkillMapsMsg), +N(OP_CancelledFeignMsg), +N(OP_SignalMsg), +N(OP_SkillInfoRequest), +N(OP_SkillInfoResponse), +N(OP_ShowCreateFromRecipeUIMsg), +N(OP_CancelCreateFromRecipeMsg), +N(OP_BeginItemCreationMsg), +N(OP_StopItemCreationMsg), +N(OP_ShowItemCreationProcessUIMsg), +N(OP_UpdateItemCreationProcessUIMsg), +N(OP_DisplayTSEventReactionMsg), +N(OP_ShowRecipeBookMsg), +N(OP_KnowledgebaseRequestMsg), +N(OP_KnowledgebaseResponseMsg), +N(OP_CSTicketHeaderRequestMsg), +N(OP_CSTicketInfoMsg), +N(OP_CSTicketCommentRequestMsg), +N(OP_CSTicketCommentResponseMsg), +N(OP_CSTicketCreateMsg), +N(OP_CSTicketAddCommentMsg), +N(OP_CSTicketDeleteMsg), +N(OP_CSTicketChangeNotificationMsg), +N(OP_WorldDataUpdateMsg), +N(OP_WorldDataChangeMsg), +N(OP_KnownLanguagesMsg), +N(OP_LsRequestClientCrashLogMsg), +N(OP_LsClientBaselogReplyMsg), +N(OP_LsClientCrashlogReplyMsg), +N(OP_LsClientAlertlogReplyMsg), +N(OP_LsClientVerifylogReplyMsg), +N(OP_ClientTeleportToLocationMsg), +N(OP_UpdateClientPredFlagsMsg), +N(OP_ChangeServerControlFlagMsg), +N(OP_CSToolsRequestMsg), +N(OP_CSToolsResponseMsg), +N(OP_CreateBoatTransportsMsg), +N(OP_PositionBoatTransportMsg), +N(OP_MigrateBoatTransportMsg), +N(OP_MigrateBoatTransportReplyMsg), +N(OP_DisplayDebugNLLPointsMsg), +N(OP_ExamineInfoRequestMsg), +N(OP_QuickbarInitMsg), +N(OP_QuickbarUpdateMsg), +N(OP_MacroInitMsg), +N(OP_MacroUpdateMsg), +N(OP_QuestionnaireMsg), +N(OP_LevelChangedMsg), +N(OP_SpellGainedMsg), +N(OP_EncounterBrokenMsg), +N(OP_OnscreenMsgMsg), +N(OP_DisplayWarningMsg), +N(OP_ModifyGuildMsg), +N(OP_GuildEventMsg), +N(OP_GuildEventAddMsg), +N(OP_GuildEventActionMsg), +N(OP_GuildEventListMsg), +N(OP_RequestGuildEventDetailsMsg), +N(OP_GuildEventDetailsMsg), +N(OP_RequestGuildBankEventDetailsMs), +N(OP_GuildBankUpdateMsg), +N(OP_RewardPackMsg), +N(OP_RenameGuildMsg), +N(OP_ZoneToFriendRequestMsg), +N(OP_ZoneToFriendReplyMsg), +N(OP_WaypointRequestMsg), +N(OP_WaypointReplyMsg), +N(OP_WaypointSelectMsg), +N(OP_WaypointUpdateMsg), +N(OP_CharNameChangedMsg), +N(OP_ShowZoneTeleporterDestinations), +N(OP_SelectZoneTeleporterDestinatio), +N(OP_ReloadLocalizedTxtMsg), +N(OP_RequestGuildMembershipMsg), +N(OP_GuildMembershipResponseMsg), +N(OP_LeaveGuildNotifyMsg), +N(OP_JoinGuildNotifyMsg), +N(OP_RequestGuildInfoMsg), +N(OP_GuildBankEventListMsg), +N(OP_AvatarUpdateMsg), +N(OP_BioUpdateMsg), +N(OP_InspectPlayerMsg), +N(OP_WSServerLockMsg), +N(OP_WSServerHideMsg), +N(OP_LSServerLockMsg), +N(OP_CsCategoryRequestMsg), +N(OP_CsCategoryResponseMsg), +N(OP_KnowledgeWindowSlotMappingMsg), +N(OP_LFGUpdateMsg), +N(OP_AFKUpdateMsg), +N(OP_AnonUpdateMsg), +N(OP_UpdateActivePublicZonesMsg), +N(OP_UnknownNpcMsg), +N(OP_PromoFlagsDetailsMsg), +N(OP_ConsignViewCreateMsg), +N(OP_ConsignViewGetPageMsg), +N(OP_ConsignViewReleaseMsg), +N(OP_UpdateDebugRadiiMsg), +N(OP_ConsignRemoveItemsMsg), +//N(OP_SnoopMsg), +N(OP_ReportMsg), +N(OP_UpdateRaidMsg), +N(OP_ConsignViewSortMsg), +N(OP_TitleUpdateMsg), +N(OP_FlightPathsMsg), +N(OP_ClientFellMsg), +N(OP_ClientInDeathRegionMsg), +N(OP_CampClientMsg), +N(OP_GetAvatarAccessRequestForCSToo), +N(OP_CSToolAccessResponseMsg), +N(OP_DeleteGuildMsg), +N(OP_TrackingUpdateMsg), +N(OP_BeginTrackingMsg), +N(OP_StopTrackingMsg), +N(OP_AdvancementRequestMsg), +N(OP_MapFogDataInitMsg), +N(OP_MapFogDataUpdateMsg), +//N(OP_UpdateAvgFrameTimeMsg), +N(OP_CloseGroupInviteWindowMsg), +N(OP_UpdateGroupMemberDataMsg), +N(OP_WorldPingMsg), +N(OP_MoveLogUpdateMsg), +N(OP_OfferQuestMsg), +//N(OP_MailGetHeadersMsg), +N(OP_MailGetMessageMsg), +N(OP_MailSendMessageMsg), +N(OP_MailDeleteMessageMsg), +N(OP_MailGetHeadersReplyMsg), +N(OP_MailGetMessageReplyMsg), +N(OP_MailSendMessageReplyMsg), +N(OP_MailCommitSendMessageMsg), +N(OP_MailSendSystemMessageMsg), +N(OP_MailRemoveAttachFromMailMsg), +N(OP_WorldShutdownUpdateMsg), +N(OP_ClientIdleBeginMsg), +N(OP_ClientIdleEndMsg), +N(OP_DisplayMailScreenMsg), +N(OP_NotifyApprenticeStoppedMentori), +N(OP_CorruptedClientMsg), +N(OP_MailEventNotificationMsg), +N(OP_RestartZoneMsg), +N(OP_CharTransferStartRequestMsg), +N(OP_CharTransferStartReplyMsg), +N(OP_CharTransferRequestMsg), +N(OP_CharTransferReplyMsg), +N(OP_CharTransferRollbackRequestMsg), +N(OP_CharTransferCommitRequestMsg), +N(OP_CharTransferRollbackReplyMsg), +N(OP_CharTransferCommitReplyMsg), +N(OP_GetCharacterSerializedRequestM), +N(OP_GetCharacterSerializedReplyMsg), +N(OP_CreateCharFromCBBRequestMsg), +N(OP_CreateCharFromCBBReplyMsg), +N(OP_HousingDataChangedMsg), +N(OP_HousingRestoreMsg), +N(OP_AuctionItem), +N(OP_AuctionItemReply), +N(OP_AuctionCoin), +N(OP_AuctionCoinReply), +N(OP_AuctionCharacter), +N(OP_AuctionCharacterReply), +N(OP_AuctionCommitMsg), +N(OP_AuctionAbortMsg), +N(OP_CharTransferValidateRequestMsg), +N(OP_CharTransferValidateReplyMsg), +N(OP_CharacterLinkdeadMsg), +N(OP_RaceRestrictionMsg), +N(OP_SetInstanceDisplayNameMsg), +N(OP_EqHearChatCmd), +N(OP_EqDisplayTextCmd), +N(OP_EqCreateGhostCmd), +N(OP_EqCreateWidgetCmd), +N(OP_EqCreateSignWidgetCmd), +N(OP_EqDestroyGhostCmd), +N(OP_EqUpdateGhostCmd), +N(OP_EqSetControlGhostCmd), +N(OP_EqSetPOVGhostCmd), +N(OP_EqHearCombatCmd), +N(OP_EqHearSpellCastCmd), +N(OP_EqHearSpellInterruptCmd), +N(OP_EqHearSpellFizzleCmd), +N(OP_EqHearConsiderCmd), +N(OP_EqUpdateSubClassesCmd), +N(OP_EqCreateListBoxCmd), +N(OP_EqSetDebugPathPointsCmd), +N(OP_EqCannedEmoteCmd), +N(OP_EqStateCmd), +N(OP_EqPlaySoundCmd), +N(OP_EqPlaySound3DCmd), +N(OP_EqPlayVoiceCmd), +N(OP_EqHearDrowningCmd), +N(OP_EqHearDeathCmd), +N(OP_EqGroupMemberRemovedCmd), +N(OP_EqHearChainEffectCmd), +N(OP_EqReceiveOfferCmd), +N(OP_EqInspectPCResultsCmd), +N(OP_EqDrawablePathGraphCmd), +N(OP_EqDialogOpenCmd), +N(OP_EqDialogCloseCmd), +N(OP_EqCollectionUpdateCmd), +N(OP_EqCollectionFilterCmd), +N(OP_EqCollectionItemCmd), +N(OP_EqQuestJournalUpdateCmd), +N(OP_EqQuestJournalReplyCmd), +N(OP_EqQuestGroupCmd), +N(OP_EqUpdateMerchantCmd), +N(OP_EqUpdateStoreCmd), +N(OP_EqUpdatePlayerTradeCmd), +N(OP_EqHelpPathCmd), +N(OP_EqHelpPathClearCmd), +N(OP_EqUpdateBankCmd), +N(OP_EqExamineInfoCmd), +N(OP_EqCloseWindowCmd), +N(OP_EqUpdateLootCmd), +N(OP_EqJunctionListCmd), +N(OP_EqShowDeathWindowCmd), +N(OP_EqDisplaySpellFailCmd), +N(OP_EqSpellCastStartCmd), +N(OP_EqSpellCastEndCmd), +N(OP_EqResurrectedCmd), +N(OP_EqChoiceWinCmd), +N(OP_EqSetDefaultVerbCmd), +N(OP_EqInstructionWindowCmd), +N(OP_EqInstructionWindowCloseCmd), +N(OP_EqInstructionWindowGoalCmd), +N(OP_EqInstructionWindowTaskCmd), +N(OP_EqEnableGameEventCmd), +N(OP_EqShowWindowCmd), +N(OP_EqEnableWindowCmd), +N(OP_EqFlashWindowCmd), +N(OP_EqHearPlayFlavorCmd), +N(OP_EqUpdateSignWidgetCmd), +N(OP_EqDebugPVDCmd), +N(OP_EqShowBookCmd), +N(OP_EqQuestionnaireCmd), +N(OP_EqGetProbsCmd), +N(OP_EqHearHealCmd), +N(OP_EqChatChannelUpdateCmd), +N(OP_EqWhoChannelQueryReplyCmd), +N(OP_EqAvailWorldChannelsCmd), +N(OP_ArenaGameTypesMsg), +N(OP_EqUpdateTargetCmd), +N(OP_EqConsignmentItemsCmd), +N(OP_EqStartBrokerCmd), +N(OP_EqMapExplorationCmd), +N(OP_EqStoreLogCmd), +N(OP_EqSpellMoveToRangeAndRetryCmd), +N(OP_EqUpdatePlayerMailCmd), +N(OP_EqFactionUpdateCmd), +N(OP_UpdateTitleCmd), +N(OP_UpdatePositionMsg), +N(OP_AttackNotAllowed), +N(OP_AttackAllowed), +N(OP_CancelSpellCast), +N(OP_BadLanguageFilter), +N(OP_DressingRoom), +N(OP_TraitsList), +N(OP_PointOfInterest), +N(OP_AdventureList), +N(OP_CharacterAchievements), +N(OP_RecipeList), +N(OP_BagOptions), +N(OP_AchievementUpdateMsg), +N(OP_PetOptions), +N(OP_BrokerAddBag), +N(OP_CharacterPet), +N(OP_ClearForTakeOffMsg), +N(OP_CharacterCurrency), +N(OP_TradeskillList), +N(OP_RecipeBook), +N(OP_CharacterMerc), +N(OP_AfterInvSpellUpdate), +N(OP_CharacterCreatedDungeons), +N(OP_CharacterHousingList), +N(OP_HouseItemsList), +N(OP_CharacterMounts), +N(OP_LoadCalendarEvents), +N(OP_LoadWelcomeWindow), +N(OP_DungeonMakerItemRequest), +N(OP_SysClient), +N(OP_LFGGroupSearch), +N(OP_MarketPlacePrices), +N(OP_MarketFundsUpdate), +N(OP_MarketAddFundsRequest), +N(OP_ZoneBgInstanceList), +N(OP_UIEvent), +N(OP_Launchpad), +N(OP_EQHearThreatCmd), +N(OP_EqHearSpellNoLandCmd), +N(OP_Weakness), +N(OP_SavageBarInitMsg), +N(OP_PetOptionsResponse), +N(OP_CurrentPet), +N(OP_JournalQuestStoryline), +N(OP_DailyObjectives), +N(OP_RecipeListUnknown), +N(OP_EQHearDispellCmd), +N(OP_ClearForLandingMsg), +N(OP_LikeOption), +N(OP_HeritageMsg), +N(OP_OpenCharCust), +N(OP_PaperdollImage), +N(OP_ReadyForTakeOffMsg), +N(OP_EarlyLandingRequestMsg), +N(OP_SubmitCharCust), +N(OP_DietyAbilityWindow), +N(OP_EqTargetItemCmd), \ No newline at end of file diff --git a/source/common/linked_list.h b/source/common/linked_list.h new file mode 100644 index 0000000..023a9d2 --- /dev/null +++ b/source/common/linked_list.h @@ -0,0 +1,445 @@ +/* + 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 . +*/ +#ifndef LINKEDLIST_H +#define LINKEDLIST_H + +#include "types.h" + +enum direction{FORWARD,BACKWARD}; + +template class LinkedListIterator; + +template +class ListElement +{ +private: + + TYPE data; + ListElement* next; + ListElement* prev; +public: + ListElement (); + ListElement (const TYPE&); + ListElement (const ListElement&); + + ~ListElement (); + + ListElement& operator= (const ListElement&); + + ListElement* GetLast () + { + ListElement* tmp = this; + while (tmp->GetNext()) { + tmp = tmp->GetNext(); + } + return tmp; + } + ListElement* GetNext () const { return next ; } + ListElement* GetPrev () const { return prev ; } + + inline TYPE& GetData () { return data ; } + inline const TYPE& GetData () const { return data ; } + + void SetData ( const TYPE& d ) { data = d ; } // Quagmire - this may look like a mem leak, but dont change it, this behavior is expected where it's called + void SetLastNext ( ListElement* p ) + { + GetLast()->SetNext(p); + } + void SetNext (ListElement* n) { next = n ; } + void SetPrev (ListElement* p) { prev = p ; } + + void ReplaceData(const TYPE&); +}; + +template +class LinkedList +{ +private: + int32 count; + ListElement* first; + bool list_destructor_invoked; + +public: + + LinkedList(); + ~LinkedList(); + bool dont_delete; + LinkedList& operator= (const LinkedList&); + + void Append (const TYPE&); + void Insert (const TYPE&); + TYPE Pop(); + TYPE PeekTop(); + void Clear(); + void LCount() { count--; } + void ResetCount() { count=0; } + int32 Count() { return count; } + friend class LinkedListIterator; +}; + +template +class LinkedListIterator +{ +private: + LinkedList& list; + ListElement* current_element; + direction dir; + +public: + LinkedListIterator(LinkedList& l,direction d = FORWARD) : list(l), dir(d) {}; + + void Advance(); + const TYPE& GetData(); + bool IsFirst() + { + if (current_element->GetPrev() == 0) + return true; + else + return false; + } + bool IsLast() + { + if (current_element->GetNext() == 0) + return true; + else + return false; + } + bool MoreElements(); + void MoveFirst(); + void MoveLast(); + void RemoveCurrent(bool DeleteData = true); + void Replace(const TYPE& new_data); + void Reset(); + void SetDir(direction); +}; + +template +void LinkedListIterator::Advance() +{ + if (current_element == 0) + { + return; + } + if (dir == FORWARD) + { + current_element = current_element->GetNext(); + } + else + { + current_element = current_element->GetPrev(); + } + + if (list.list_destructor_invoked) + { + while(current_element && current_element->GetData() == 0) + { +// if (current_element == 0) +// { +// return; +// } + if (dir == FORWARD) + { + current_element = current_element->GetNext(); + } + else + { + current_element = current_element->GetPrev(); + } + } + } +} + +template +bool LinkedListIterator::MoreElements() +{ + if (current_element == 0) + return false; + return true; +} + +template +const TYPE& LinkedListIterator::GetData() +{ + return current_element->GetData(); +} + +template +void LinkedListIterator::MoveFirst() +{ + ListElement* prev = current_element->GetPrev(); + ListElement* next = current_element->GetNext(); + + if (prev == 0) + { + return; + } + +// if (prev != 0) +// { + prev->SetNext(next); +// } + if (next != 0) + { + next->SetPrev(prev); + } + current_element->SetPrev(0); + current_element->SetNext(list.first); + list.first->SetPrev(current_element); + list.first = current_element; +} + + +template +void LinkedListIterator::MoveLast() +{ + ListElement* prev = current_element->GetPrev(); + ListElement* next = current_element->GetNext(); + + if (next == 0) + { + return; + } + + if (prev != 0) + { + prev->SetNext(next); + } + else + { + list.first = next; + } +// if (next != 0) +// { + next->SetPrev(prev); +// } + current_element->SetNext(0); + current_element->SetPrev(next->GetLast()); + next->GetLast()->SetNext(current_element); +} + +template +void LinkedListIterator::RemoveCurrent(bool DeleteData) +{ + ListElement* save; + + if (list.first == current_element) + { + list.first = current_element->GetNext(); + } + + if (current_element->GetPrev() != 0) + { + current_element->GetPrev()->SetNext(current_element->GetNext()); + } + if (current_element->GetNext() != 0) + { + current_element->GetNext()->SetPrev(current_element->GetPrev()); + } + if (dir == FORWARD) + { + save = current_element->GetNext(); + } + else + { + save = current_element->GetPrev(); + } + current_element->SetNext(0); + current_element->SetPrev(0); + if (!DeleteData) + current_element->SetData(0); + safe_delete(current_element); + current_element = save; + list.LCount(); +} + +template +void LinkedListIterator::Replace(const TYPE& new_data) +{ + current_element->ReplaceData(new_data); +} + +template +void LinkedListIterator::Reset() +{ + if (!(&list)) + { + current_element=0; + return; + } + + if (dir == FORWARD) + { + current_element = list.first; + } + else + { + if (list.first == 0) + { + current_element = 0; + } + else + { + current_element = list.first->GetLast(); + } + } + + if (list.list_destructor_invoked) + { + while(current_element && current_element->GetData() == 0) + { +// if (current_element == 0) +// { +// return; +// } + if (dir == FORWARD) + { + current_element = current_element->GetNext(); + } + else + { + current_element = current_element->GetPrev(); + } + } + } +} + +template +void LinkedListIterator::SetDir(direction d) +{ + dir = d; +} + +template +ListElement::ListElement(const TYPE& d) +{ + data = d; + next = 0; + prev = 0; +} + +template +ListElement::~ListElement() +{ +// cout << "ListElement::~ListElement()" << endl; + + if (data != 0) + safe_delete(data); + data = 0; + if (next != 0) + { + safe_delete(next); + next = 0; + } +} + +template +void ListElement::ReplaceData(const TYPE& new_data) +{ + if (data != 0) + safe_delete(data); + data = new_data; +} + +template +LinkedList::LinkedList() +{ + list_destructor_invoked = false; + first = 0; + count = 0; + dont_delete = false; +} + +template +LinkedList::~LinkedList() +{ + list_destructor_invoked = true; + if(!dont_delete) + Clear(); +} + +template +void LinkedList::Clear() { + while (first) { + ListElement* tmp = first; + first = tmp->GetNext(); + tmp->SetNext(0); + safe_delete(tmp); + } + ResetCount(); +} + +template +void LinkedList::Append(const TYPE& data) +{ + ListElement* new_element = new ListElement(data); + + if (first == 0) + { + first = new_element; + } + else + { + new_element->SetPrev(first->GetLast()); + first->SetLastNext(new_element); + } + count++; +} + +template +void LinkedList::Insert(const TYPE& data) +{ + ListElement* new_element = new ListElement(data); + + new_element->SetNext(first); + if (first != 0) + { + first->SetPrev(new_element); + } + first = new_element; + count++; +} + +template +TYPE LinkedList::Pop() { + TYPE ret = 0; + if (first) { + ListElement* tmpdel = first; + first = tmpdel->GetNext(); + if (first) + first->SetPrev(0); + ret = tmpdel->GetData(); + tmpdel->SetData(0); + tmpdel->SetNext(0); + safe_delete(tmpdel); + count--; + } + return ret; +} + +template +TYPE LinkedList::PeekTop() { + if (first) + return first->GetData(); + return 0; +} + +#endif + + diff --git a/source/common/login_oplist.h b/source/common/login_oplist.h new file mode 100644 index 0000000..c8bd536 --- /dev/null +++ b/source/common/login_oplist.h @@ -0,0 +1,61 @@ +/* + 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 . +*/ +#if defined(LOGIN) || defined(MINILOGIN) +N(OP_LoginRequestMsg), +N(OP_LoginByNumRequestMsg), +N(OP_WSLoginRequestMsg), +N(OP_ESLoginRequestMsg), +N(OP_LoginReplyMsg), +N(OP_WorldListMsg), +N(OP_WorldStatusChangeMsg), +N(OP_AllWSDescRequestMsg), +N(OP_WSStatusReplyMsg), +N(OP_AllCharactersDescRequestMsg), +N(OP_AllCharactersDescReplyMsg), +N(OP_CreateCharacterRequestMsg), +N(OP_ReskinCharacterRequestMsg), +N(OP_CreateCharacterReplyMsg), +N(OP_WSCreateCharacterRequestMsg), +N(OP_WSCreateCharacterReplyMsg), +N(OP_DeleteCharacterRequestMsg), +N(OP_DeleteCharacterReplyMsg), +N(OP_PlayCharacterRequestMsg), +N(OP_PlayCharacterReplyMsg), +N(OP_ServerPlayCharacterRequestMsg), +N(OP_ServerPlayCharacterReplyMsg), +N(OP_KeymapLoadMsg), +N(OP_KeymapNoneMsg), +N(OP_KeymapDataMsg), +N(OP_KeymapSaveMsg), +//N(OP_LSRequestPlayerDescMsg), +N(OP_LSCheckAcctLockMsg), +N(OP_WSAcctLockStatusMsg), +N(OP_LsRequestClientCrashLogMsg), +N(OP_LsClientBaselogReplyMsg), +N(OP_LsClientCrashlogReplyMsg), +N(OP_LsClientAlertlogReplyMsg), +N(OP_LsClientVerifylogReplyMsg), +N(OP_BadLanguageFilter), +N(OP_WSServerLockMsg), +N(OP_WSServerHideMsg), +N(OP_LSServerLockMsg), +N(OP_UpdateCharacterSheetMsg), +N(OP_UpdateInventoryMsg), +#endif diff --git a/source/common/md5.cpp b/source/common/md5.cpp new file mode 100644 index 0000000..1244c8c --- /dev/null +++ b/source/common/md5.cpp @@ -0,0 +1,281 @@ +/* + 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 . +*/ +#include /* for memcpy() */ +#include "../common/md5.h" +#include "../common/MiscFunctions.h" +#include "../common/seperator.h" + +MD5::MD5() { + memset(pMD5, 0, 16); +} + +MD5::MD5(const uchar* buf, uint32 len) { + Generate(buf, len, pMD5); +} + +MD5::MD5(const char* buf, uint32 len) { + Generate((const uchar*) buf, len, pMD5); +} + +MD5::MD5(const int8 buf[16]) { + Set(buf); +} + +MD5::MD5(const char* iMD5String) { + Set(iMD5String); +} + +void MD5::Generate(const char* iString) { + Generate((const uchar*) iString, strlen(iString)); +} + +void MD5::Generate(const int8* buf, uint32 len) { + Generate(buf, len, pMD5); +} + +bool MD5::Set(const int8 buf[16]) { + memcpy(pMD5, buf, 16); + return true; +} + +bool MD5::Set(const char* iMD5String) { + char tmp[5] = { '0', 'x', 0, 0, 0 }; + for (int i=0; i<16; i++) { + tmp[2] = iMD5String[i*2]; + tmp[3] = iMD5String[(i*2) + 1]; + if (!Seperator::IsHexNumber(tmp)) + return false; + pMD5[i] = hextoi(tmp); + } + return true; +} + +MD5::operator const char* () { + snprintf(pMD5String, sizeof(pMD5String), "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", pMD5[0], pMD5[1], pMD5[2], pMD5[3], pMD5[4], pMD5[5], pMD5[6], pMD5[7], pMD5[8], pMD5[9], pMD5[10], pMD5[11], pMD5[12], pMD5[13], pMD5[14], pMD5[15]); + return pMD5String; +} + +bool MD5::operator== (const MD5& iMD5) { + if (memcmp(pMD5, iMD5.pMD5, 16) == 0) + return true; + else + return false; +} + +bool MD5::operator== (const int8* iMD5) { + if (memcmp(pMD5, iMD5, 16) == 0) + return true; + else + return false; +} + +bool MD5::operator== (const char* iMD5String) { + char tmp[5] = { '0', 'x', 0, 0, 0 }; + for (int i=0; i<16; i++) { + tmp[2] = iMD5String[i*2]; + tmp[3] = iMD5String[(i*2) + 1]; + if (pMD5[i] != hextoi(tmp)) + return false; + } + return true; +} + +MD5& MD5::operator= (const MD5& iMD5) { + memcpy(pMD5, iMD5.pMD5, 16); + return *this; +} + +MD5* MD5::operator= (const MD5* iMD5) { + memcpy(pMD5, iMD5->pMD5, 16); + return this; +} + +/* Byte-swap an array of words to little-endian. (Byte-sex independent) */ +void MD5::byteSwap(uint32 *buf, uint32 words) { + int8 *p = (int8 *)buf; + do { + *buf++ = (uint32)((uint32)p[3]<<8 | p[2]) << 16 | + ((uint32)p[1]<<8 | p[0]); + p += 4; + } while (--words); +} + +void MD5::Generate(const int8* buf, uint32 len, int8 digest[16]) { + MD5Context ctx; + Init(&ctx); + Update(&ctx, buf, len); + Final(digest, &ctx); +} + +/* Start MD5 accumulation. */ +void MD5::Init(struct MD5Context *ctx) { + ctx->hash[0] = 0x67452301; + ctx->hash[1] = 0xefcdab89; + ctx->hash[2] = 0x98badcfe; + ctx->hash[3] = 0x10325476; + ctx->bytes[1] = ctx->bytes[0] = 0; +} + +/* Update ctx to reflect the addition of another buffer full of bytes. */ +void MD5::Update(struct MD5Context *ctx, int8 const *buf, uint32 len) { + uint32 t = ctx->bytes[0]; + if ((ctx->bytes[0] = t + len) < t) /* Update 64-bit byte count */ + ctx->bytes[1]++; /* Carry from low to high */ + + + + t = 64 - (t & 0x3f); /* Bytes available in ctx->input (>= 1) */ + if (t > len) { + memcpy((int8*)ctx->input+64-t, buf, len); + return; + } + /* First chunk is an odd size */ + memcpy((int8*)ctx->input+64-t, buf, t); + byteSwap(ctx->input, 16); + Transform(ctx->hash, ctx->input); + buf += t; + len -= t; + /* Process data in 64-byte chunks */ + while (len >= 64) { + memcpy(ctx->input, buf, 64); + byteSwap(ctx->input, 16); + Transform(ctx->hash, ctx->input); + buf += 64; + len -= 64; + } + /* Buffer any remaining bytes of data */ + memcpy(ctx->input, buf, len); +} + +/* Final wrapup - pad to 64-byte boundary with the bit pattern +* 1 0* (64-bit count of bits processed, LSB-first) */ +void MD5::Final(int8 digest[16], MD5Context *ctx) { + int count = ctx->bytes[0] & 0x3F; /* Bytes mod 64 */ + int8 *p = (int8*)ctx->input + count; + /* Set the first byte of padding to 0x80. There is always room. */ + *p++ = 0x80; + /* Bytes of zero padding needed to make 56 bytes (-8..55) */ + count = 56 - 1 - count; + if (count < 0) { /* Padding forces an extra block */ + memset(p, 0, count+8); + byteSwap(ctx->input, 16); + Transform(ctx->hash, ctx->input); + p = (int8*)ctx->input; + count = 56; + } + memset(p, 0, count); + byteSwap(ctx->input, 14); + /* Append 8 bytes of length in *bits* and transform */ + ctx->input[14] = ctx->bytes[0] << 3; + + ctx->input[15] = ctx->bytes[1] << 3 | ctx->bytes[0] >> 29; + Transform(ctx->hash, ctx->input); + byteSwap(ctx->hash, 4); + memcpy(digest, ctx->hash, 16); + memset(ctx, 0, sizeof(*ctx)); /* In case it's sensitive */ +} + +/* The four core functions */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f,w,x,y,z,in,s) (w += f(x,y,z)+in, w = (w<>(32-s)) + x) + + + +/* The heart of the MD5 algorithm. */ +void MD5::Transform(uint32 hash[4], const uint32 input[16]) { + uint32 a = hash[0], b = hash[1], c = hash[2], d = hash[3]; + + MD5STEP(F1, a, b, c, d, input[ 0]+0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, input[ 1]+0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, input[ 2]+0x242070db, 17); + MD5STEP(F1, b, c, d, a, input[ 3]+0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, input[ 4]+0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, input[ 5]+0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, input[ 6]+0xa8304613, 17); + MD5STEP(F1, b, c, d, a, input[ 7]+0xfd469501, 22); + MD5STEP(F1, a, b, c, d, input[ 8]+0x698098d8, 7); + MD5STEP(F1, d, a, b, c, input[ 9]+0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, input[10]+0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, input[11]+0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, input[12]+0x6b901122, 7); + MD5STEP(F1, d, a, b, c, input[13]+0xfd987193, 12); + MD5STEP(F1, c, d, a, b, input[14]+0xa679438e, 17); + MD5STEP(F1, b, c, d, a, input[15]+0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, input[ 1]+0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, input[ 6]+0xc040b340, 9); + MD5STEP(F2, c, d, a, b, input[11]+0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, input[ 0]+0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, input[ 5]+0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, input[10]+0x02441453, 9); + MD5STEP(F2, c, d, a, b, input[15]+0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, input[ 4]+0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, input[ 9]+0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, input[14]+0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, input[ 3]+0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, input[ 8]+0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, input[13]+0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, input[ 2]+0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, input[ 7]+0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, input[12]+0x8d2a4c8a, 20); + + + + + MD5STEP(F3, a, b, c, d, input[ 5]+0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, input[ 8]+0x8771f681, 11); + MD5STEP(F3, c, d, a, b, input[11]+0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, input[14]+0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, input[ 1]+0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, input[ 4]+0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, input[ 7]+0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, input[10]+0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, input[13]+0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, input[ 0]+0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, input[ 3]+0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, input[ 6]+0x04881d05, 23); + MD5STEP(F3, a, b, c, d, input[ 9]+0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, input[12]+0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, input[15]+0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, input[ 2]+0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, input[ 0]+0xf4292244, 6); + MD5STEP(F4, d, a, b, c, input[ 7]+0x432aff97, 10); + MD5STEP(F4, c, d, a, b, input[14]+0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, input[ 5]+0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, input[12]+0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, input[ 3]+0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, input[10]+0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, input[ 1]+0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, input[ 8]+0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, input[15]+0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, input[ 6]+0xa3014314, 15); + MD5STEP(F4, b, c, d, a, input[13]+0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, input[ 4]+0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, input[11]+0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, input[ 2]+0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, input[ 9]+0xeb86d391, 21); + + hash[0] += a; hash[1] += b; hash[2] += c; hash[3] += d; +} diff --git a/source/common/md5.h b/source/common/md5.h new file mode 100644 index 0000000..6c54f94 --- /dev/null +++ b/source/common/md5.h @@ -0,0 +1,64 @@ +/* + 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 . +*/ +#ifndef MD5_H +#define MD5_H +#include "../common/types.h" + + +class MD5 { +public: + struct MD5Context { + uint32 hash[4]; + uint32 bytes[2]; + uint32 input[16]; + }; + static void Generate(const int8* buf, uint32 len, int8 digest[16]); + + static void Init(struct MD5Context *context); + static void Update(struct MD5Context *context, const int8 *buf, uint32 len); + static void Final(int8 digest[16], struct MD5Context *context); + + MD5(); + MD5(const uchar* buf, uint32 len); + MD5(const char* buf, uint32 len); + MD5(const int8 buf[16]); + MD5(const char* iMD5String); + + void Generate(const char* iString); + void Generate(const int8* buf, uint32 len); + bool Set(const int8 buf[16]); + bool Set(const char* iMD5String); + + bool operator== (const MD5& iMD5); + bool operator== (const int8 iMD5[16]); + bool operator== (const char* iMD5String); + + MD5& operator= (const MD5& iMD5); + MD5* operator= (const MD5* iMD5); + MD5* operator= (const int8* iMD5); + operator const char* (); +protected: + int8 pMD5[16]; +private: + static void byteSwap(uint32 *buf, uint32 words); + static void Transform(uint32 hash[4], const int32 input[16]); + char pMD5String[33]; +}; +#endif diff --git a/source/common/misc.cpp b/source/common/misc.cpp new file mode 100644 index 0000000..6f5daa0 --- /dev/null +++ b/source/common/misc.cpp @@ -0,0 +1,305 @@ +/* + 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 . +*/ +#ifdef WIN32 + // VS6 doesn't like the length of STL generated names: disabling + #pragma warning(disable:4786) +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include "misc.h" +#include "types.h" +using namespace std; + +#define ENC(c) (((c) & 0x3f) + ' ') +#define DEC(c) (((c) - ' ') & 0x3f) + +map DBFieldNames; + +#ifndef WIN32 +#ifdef FREEBSD +int print_stacktrace() +{ + printf("Insert stack trace here...\n"); + return(0); +} +#else //!WIN32 && !FREEBSD == linux +#include +int print_stacktrace() +{ + void *ba[20]; + int n = backtrace (ba, 20); + if (n != 0) + { + char **names = backtrace_symbols (ba, n); + if (names != NULL) + { + int i; + cerr << "called from " << (char*)names[0] << endl; + for (i = 1; i < n; ++i) + cerr << " " << (char*)names[i] << endl; + free (names); + } + } + return(0); +} +#endif //!FREEBSD +#endif //!WIN32 + +int Deflate(unsigned char* in_data, int in_length, unsigned char* out_data, int max_out_length) +{ +z_stream zstream; +int zerror; + + zstream.next_in = in_data; + zstream.avail_in = in_length; + zstream.zalloc = Z_NULL; + zstream.zfree = Z_NULL; + zstream.opaque = Z_NULL; + deflateInit(&zstream, Z_FINISH); + zstream.next_out = out_data; + zstream.avail_out = max_out_length; + zerror = deflate(&zstream, Z_FINISH); + + if (zerror == Z_STREAM_END) + { + deflateEnd(&zstream); + return zstream.total_out; + } + else + { + cout << "Error: Deflate: deflate() returned " << zerror << " '"; + if (zstream.msg) + cout << zstream.msg; + cout << "'" << endl; + zerror = deflateEnd(&zstream); + return 0; + } +} + +int Inflate(unsigned char* indata, int indatalen, unsigned char* outdata, int outdatalen, bool iQuiet) +{ +z_stream zstream; +int zerror = 0; +int i; + + zstream.next_in = indata; + zstream.avail_in = indatalen; + zstream.next_out = outdata; + zstream.avail_out = outdatalen; + zstream.zalloc = Z_NULL; + zstream.zfree = Z_NULL; + zstream.opaque = Z_NULL; + + i = inflateInit2( &zstream, 15 ); + if (i != Z_OK) { + return 0; + } + + zerror = inflate( &zstream, Z_FINISH ); + + if(zerror == Z_STREAM_END) { + inflateEnd( &zstream ); + return zstream.total_out; + } + else { + if (!iQuiet) { + cout << "Error: Inflate: inflate() returned " << zerror << " '"; + if (zstream.msg) + cout << zstream.msg; + cout << "'" << endl; + } + + if (zerror == Z_DATA_ERROR || zerror == Z_ERRNO) + return -1; + + if (zerror == Z_MEM_ERROR && zstream.msg == 0) + { + return 0; + } + + zerror = inflateEnd( &zstream ); + return 0; + } +} + +void dump_message_column(unsigned char *buffer, unsigned long length, string leader, FILE *to) +{ +unsigned long i,j; +unsigned long rows,offset=0; + rows=(length/16)+1; + for(i=0;i= 0x41 && val <=0x5A) || (val >= 0x61 && val <=0x7A)) + return true; + else + return false; +} + +unsigned int GetSpellNameCrc(const char* src) { + if (!src) + return 0; + uLong crc = crc32(0L, Z_NULL, 0); + return crc32(crc, (unsigned const char*)src, strlen(src)); +} + +int GetItemNameCrc(string item_name){ + const char *src = item_name.c_str(); + uLong crc = crc32(0L, Z_NULL, 0); + crc = crc32(crc, (unsigned const char *)src,strlen(src)) + 1; + return sint32(crc) * -1; +} + +unsigned int GetNameCrc(string name) { + const char* src = name.c_str(); + uLong crc = crc32(0L, Z_NULL, 0); + crc = crc32(crc, (unsigned const char*)src, strlen(src)) + 1; + return int32(crc)-1; +} diff --git a/source/common/misc.h b/source/common/misc.h new file mode 100644 index 0000000..4107eb9 --- /dev/null +++ b/source/common/misc.h @@ -0,0 +1,65 @@ +/* + 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 . +*/ +#ifndef _MISC_H + +#define _MISC_H +#include +#include +#include + +using namespace std; + +#define ITEMFIELDCOUNT 116 + +void Unprotect(string &s, char what); + +void Protect(string &s, char what); + +bool ItemParse(const char *data, int length, map > &items, int id_pos, int name_pos, int max_field, int level=0); + +int Tokenize(string s, map & tokens, char delim='|'); + +void LoadItemDBFieldNames(); + +void encode_length(unsigned long length, char *out); +unsigned long decode_length(char *in); +unsigned long encode(char *in, unsigned long length, char *out); +void decode(char *in, char *out); +void encode_chunk(char *in, int len, char *out); +void decode_chunk(char *in, char *out); + +int Deflate(unsigned char* in_data, int in_length, unsigned char* out_data, int max_out_length); +int Inflate(unsigned char* indata, int indatalen, unsigned char* outdata, int outdatalen, bool iQuiet=true); +#ifndef WIN32 +int print_stacktrace(); +#endif + +bool alpha_check(unsigned char val); + +void dump_message_column(unsigned char *buffer, unsigned long length, string leader="", FILE *to = stdout); +string string_from_time(string pattern, time_t now=0); +string timestamp(time_t now=0); +string long2ip(unsigned long ip); +string pop_arg(string &s, string seps, bool obey_quotes); +int EQsprintf(char *buffer, const char *pattern, const char *arg1, const char *arg2, const char *arg3, const char *arg4, const char *arg5, const char *arg6, const char *arg7, const char *arg8, const char *arg9); +unsigned int GetSpellNameCrc(const char* src); +int GetItemNameCrc(string item_name); +unsigned int GetNameCrc(string name); +#endif diff --git a/source/common/op_codes.h b/source/common/op_codes.h new file mode 100644 index 0000000..c25e9a0 --- /dev/null +++ b/source/common/op_codes.h @@ -0,0 +1,44 @@ +/* + 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 . +*/ +#ifndef _OP_CODES_H + +#define _OP_CODES_H + +static const char OP_SessionRequest = 0x01; +static const char OP_SessionResponse = 0x02; +static const char OP_Combined = 0x03; +static const char OP_SessionDisconnect = 0x05; +static const char OP_KeepAlive = 0x06; +static const char OP_ServerKeyRequest = 0x07; +static const char OP_SessionStatResponse= 0x08; +static const char OP_Packet = 0x09; +static const char OP_Fragment = 0x0d; +static const char OP_OutOfOrderAck = 0x11; +static const char OP_Ack = 0x15; +static const char OP_AppCombined = 0x19; +static const char OP_OutOfSession = 0x1d; + +#if defined(LOGIN) || defined(CHAT) + #define APP_OPCODE_SIZE 1 +#else + #define APP_OPCODE_SIZE 2 +#endif + +#endif diff --git a/source/common/opcodemgr.cpp b/source/common/opcodemgr.cpp new file mode 100644 index 0000000..6d57921 --- /dev/null +++ b/source/common/opcodemgr.cpp @@ -0,0 +1,350 @@ +/* + 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 . +*/ +#include "debug.h" +#include +#include +#include "opcodemgr.h" +//#include "debug.h" +#include "emu_opcodes.h" +#include "../common/Log.h" + +#if defined(SHARED_OPCODES) && !defined(EQ2) + #include "EMuShareMem.h" + extern LoadEMuShareMemDLL EMuShareMemDLL; +#endif + +#include +#include +using namespace std; + + +//#define DEBUG_TRANSLATE + + +OpcodeManager::OpcodeManager() { + loaded = false; +} +bool OpcodeManager::LoadOpcodesMap(map* eq, OpcodeSetStrategy *s, std::string* missingOpcodes){ + //do the mapping and store them in the shared memory array + bool ret = true; + EmuOpcode emu_op; + map::iterator res; + //stupid enum wont let me ++ on it... + + + for(emu_op = (EmuOpcode)(0); emu_op < _maxEmuOpcode; emu_op=(EmuOpcode)(emu_op+1)) { + //get the name of this emu opcode + const char *op_name = OpcodeNames[emu_op]; + if(op_name[0] == '\0') { + break; + } + + //find the opcode in the file + res = eq->find(op_name); + if(res == eq->end()) { + if(missingOpcodes) { + if(missingOpcodes->size() < 1) { + missingOpcodes->append(op_name); + } + else { + missingOpcodes->append(", " + std::string(op_name)); + } + } + else { + LogWrite(OPCODE__WARNING, 1, "Opcode", "Opcode %s is missing from the opcodes table.", op_name); + } + s->Set(emu_op, 0xFFFF); + continue; //continue to give them a list of all missing opcodes + } + + //ship the mapping off to shared mem. + s->Set(emu_op, res->second); + } + return ret; +} +bool OpcodeManager::LoadOpcodesFile(const char *filename, OpcodeSetStrategy *s) { + FILE *opf = fopen(filename, "r"); + if(opf == NULL) { + LogWrite(OPCODE__ERROR, 0, "Opcode", "Unable to open opcodes file '%s'. Thats bad.", filename); + return(false); + } + + map eq; + + //load the opcode file into eq, could swap in a nice XML parser here + char line[2048]; + int lineno = 0; + uint16 curop; + while(!feof(opf)) { + lineno++; + line[0] = '\0'; //for blank line at end of file + if(fgets(line, sizeof(line), opf) == NULL) + break; + + //ignore any line that dosent start with OP_ + if(line[0] != 'O' || line[1] != 'P' || line[2] != '_') + continue; + + char *num = line+3; //skip OP_ + //look for the = sign + while(*num != '=' && *num != '\0') { + num++; + } + //make sure we found = + if(*num != '=') { + LogWrite(OPCODE__ERROR, 0, "Opcode", "Malformed opcode line at %s:%d\n", filename, lineno); + continue; + } + *num = '\0'; //null terminate the name + num++; //num should point to the opcode + + //read the opcode + if(sscanf(num, "0x%hx", &curop) != 1) { + LogWrite(OPCODE__ERROR, 0, "Opcode", "Malformed opcode at %s:%d\n", filename, lineno); + continue; + } + + //we have a name and our opcode... stick it in the map + eq[line] = curop; + } + fclose(opf); + return LoadOpcodesMap(&eq, s); +} + +//convenience routines +const char *OpcodeManager::EmuToName(const EmuOpcode emu_op) { + if(emu_op > _maxEmuOpcode) + return "OP_Unknown"; + + return(OpcodeNames[emu_op]); +} + +const char *OpcodeManager::EQToName(const uint16 eq_op) { + //first must resolve the eq op to an emu op + EmuOpcode emu_op = EQToEmu(eq_op); + if(emu_op > _maxEmuOpcode) + return "OP_Unknown"; + + return(OpcodeNames[emu_op]); +} + +EmuOpcode OpcodeManager::NameSearch(const char *name) { + EmuOpcode emu_op; + //stupid enum wont let me ++ on it... + for(emu_op = (EmuOpcode)(0); emu_op < _maxEmuOpcode; emu_op=(EmuOpcode)(emu_op+1)) { + //get the name of this emu opcode + const char *op_name = OpcodeNames[emu_op]; + if(!strcasecmp(op_name, name)) { + return(emu_op); + } + } + return(OP_Unknown); +} + +RegularOpcodeManager::RegularOpcodeManager() +: MutableOpcodeManager() +{ + emu_to_eq = NULL; + eq_to_emu = NULL; + EQOpcodeCount = 0; + EmuOpcodeCount = 0; +} + +RegularOpcodeManager::~RegularOpcodeManager() { + safe_delete_array(emu_to_eq); + safe_delete_array(eq_to_emu); +} + +bool RegularOpcodeManager::LoadOpcodes(map* eq, std::string* missingOpcodes) { + NormalMemStrategy s; + s.it = this; + MOpcodes.lock(); + + loaded = true; + eq_to_emu = new EmuOpcode[MAX_EQ_OPCODE]; + emu_to_eq = new uint16[_maxEmuOpcode]; + EQOpcodeCount = MAX_EQ_OPCODE; + EmuOpcodeCount = _maxEmuOpcode; + + //dont need to set eq_to_emu cause every element should get a value + memset(eq_to_emu, 0, sizeof(EmuOpcode)*MAX_EQ_OPCODE); + memset(emu_to_eq, 0xCD, sizeof(uint16)*_maxEmuOpcode); + + bool ret = LoadOpcodesMap(eq, &s, missingOpcodes); + MOpcodes.unlock(); + return ret; +} + +bool RegularOpcodeManager::LoadOpcodes(const char *filename) { + NormalMemStrategy s; + s.it = this; + MOpcodes.lock(); + + loaded = true; + eq_to_emu = new EmuOpcode[MAX_EQ_OPCODE]; + emu_to_eq = new uint16[_maxEmuOpcode]; + EQOpcodeCount = MAX_EQ_OPCODE; + EmuOpcodeCount = _maxEmuOpcode; + + //dont need to set eq_to_emu cause every element should get a value + memset(eq_to_emu, 0, sizeof(EmuOpcode)*MAX_EQ_OPCODE); + memset(emu_to_eq, 0xCD, sizeof(uint16)*_maxEmuOpcode); + + bool ret = LoadOpcodesFile(filename, &s); + MOpcodes.unlock(); + return ret; +} + +bool RegularOpcodeManager::ReloadOpcodes(const char *filename) { + if(!loaded) + return(LoadOpcodes(filename)); + + NormalMemStrategy s; + s.it = this; + MOpcodes.lock(); + + memset(eq_to_emu, 0, sizeof(EmuOpcode)*MAX_EQ_OPCODE); + + bool ret = LoadOpcodesFile(filename, &s); + + MOpcodes.unlock(); + return(ret); +} + + +uint16 RegularOpcodeManager::EmuToEQ(const EmuOpcode emu_op) { + //opcode is checked for validity in GetEQOpcode + uint16 res; + MOpcodes.lock(); + + if(emu_op > _maxEmuOpcode) + res = 0; + else + res = emu_to_eq[emu_op]; + + MOpcodes.unlock(); +#ifdef _DEBUG_TRANSLATE + fprintf(stderr, "M Translate Emu %s (%d) to EQ 0x%.4x\n", OpcodeNames[emu_op], emu_op, res); +#endif + return(res); +} + +EmuOpcode RegularOpcodeManager::EQToEmu(const uint16 eq_op) { + //opcode is checked for validity in GetEmuOpcode +//Disabled since current live EQ uses the entire uint16 bitspace for opcodes +// if(eq_op > MAX_EQ_OPCODE) +// return(OP_Unknown); + EmuOpcode res; + MOpcodes.lock(); + if(eq_op >= MAX_EQ_OPCODE) + res = OP_Unknown; + else + res = eq_to_emu[eq_op]; + MOpcodes.unlock(); +#ifdef _DEBUG_TRANSLATE + fprintf(stderr, "M Translate EQ 0x%.4x to Emu %s (%d)\n", eq_op, OpcodeNames[res], res); +#endif + return(res); +} + +void RegularOpcodeManager::SetOpcode(EmuOpcode emu_op, uint16 eq_op) { + + //clear out old mapping + uint16 oldop = 0; + + if(emu_op <= _maxEmuOpcode) + oldop = emu_to_eq[emu_op]; + + if(oldop != 0 && oldop < MAX_EQ_OPCODE) + eq_to_emu[oldop] = OP_Unknown; + + //use our strategy, since we have it + NormalMemStrategy s; + s.it = this; + s.Set(emu_op, eq_op); +} + + +void RegularOpcodeManager::NormalMemStrategy::Set(EmuOpcode emu_op, uint16 eq_op) { + if(uint32(emu_op) >= it->EmuOpcodeCount || eq_op >= it->EQOpcodeCount) + return; + it->emu_to_eq[emu_op] = eq_op; + it->eq_to_emu[eq_op] = emu_op; +} + +NullOpcodeManager::NullOpcodeManager() +: MutableOpcodeManager() { +} + +bool NullOpcodeManager::LoadOpcodes(map* eq, std::string* missingOpcodes) { + return(true); +} + +bool NullOpcodeManager::LoadOpcodes(const char *filename) { + return(true); +} + +bool NullOpcodeManager::ReloadOpcodes(const char *filename) { + return(true); +} + +uint16 NullOpcodeManager::EmuToEQ(const EmuOpcode emu_op) { + return(0); +} + +EmuOpcode NullOpcodeManager::EQToEmu(const uint16 eq_op) { + return(OP_Unknown); +} + +EmptyOpcodeManager::EmptyOpcodeManager() +: MutableOpcodeManager() { +} + + +bool EmptyOpcodeManager::LoadOpcodes(const char *filename) { + return(true); +} + +bool EmptyOpcodeManager::LoadOpcodes(map* eq, std::string* missingOpcodes) { + return(true); +} + +bool EmptyOpcodeManager::ReloadOpcodes(const char *filename) { + return(true); +} + +uint16 EmptyOpcodeManager::EmuToEQ(const EmuOpcode emu_op) { + map::iterator f; + f = emu_to_eq.find(emu_op); + return(f == emu_to_eq.end()? 0 : f->second); +} + +EmuOpcode EmptyOpcodeManager::EQToEmu(const uint16 eq_op) { + map::iterator f; + f = eq_to_emu.find(eq_op); + return(f == eq_to_emu.end()?OP_Unknown:f->second); +} + +void EmptyOpcodeManager::SetOpcode(EmuOpcode emu_op, uint16 eq_op) { + emu_to_eq[emu_op] = eq_op; + eq_to_emu[eq_op] = emu_op; +} + + diff --git a/source/common/opcodemgr.h b/source/common/opcodemgr.h new file mode 100644 index 0000000..fe487f0 --- /dev/null +++ b/source/common/opcodemgr.h @@ -0,0 +1,162 @@ +/* + 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 . +*/ +#ifndef OPCODE_MANAGER_H +#define OPCODE_MANAGER_H + +#include "types.h" +#include "Mutex.h" +#include "emu_opcodes.h" + +#include +using namespace std; + +class OpcodeManager { +public: + OpcodeManager(); + virtual ~OpcodeManager() {} + + virtual bool Mutable() { return(false); } + virtual bool LoadOpcodes(const char *filename) = 0; + virtual bool LoadOpcodes(map* eq, std::string* missingOpcodes = nullptr) = 0; + virtual bool ReloadOpcodes(const char *filename) = 0; + + virtual uint16 EmuToEQ(const EmuOpcode emu_op) = 0; + virtual EmuOpcode EQToEmu(const uint16 eq_op) = 0; + + static const char *EmuToName(const EmuOpcode emu_op); + const char *EQToName(const uint16 emu_op); + EmuOpcode NameSearch(const char *name); + + //This has to be public for stupid visual studio + class OpcodeSetStrategy { + public: + virtual void Set(EmuOpcode emu_op, uint16 eq_op) = 0; + virtual ~OpcodeSetStrategy(){} + }; + +protected: + bool loaded; //true if all opcodes loaded + Mutex MOpcodes; //this only protects the local machine + //in a shared manager, this dosent protect others + + static bool LoadOpcodesFile(const char *filename, OpcodeSetStrategy *s); + static bool LoadOpcodesMap(map* eq, OpcodeSetStrategy *s, std::string* missingOpcodes = nullptr); +}; + +class MutableOpcodeManager : public OpcodeManager { +public: + MutableOpcodeManager() : OpcodeManager() {} + virtual bool Mutable() { return(true); } + virtual void SetOpcode(EmuOpcode emu_op, uint16 eq_op) = 0; +}; + +#ifdef SHARED_OPCODES //quick toggle since only world and zone should possibly use this +//keeps opcodes in shared memory +class SharedOpcodeManager : public OpcodeManager { +public: + virtual ~SharedOpcodeManager() {} + + virtual bool LoadOpcodes(const char *filename); + virtual bool LoadOpcodes(map* eq, std::string* missingOpcodes = nullptr); + virtual bool ReloadOpcodes(const char *filename); + + virtual uint16 EmuToEQ(const EmuOpcode emu_op); + virtual EmuOpcode EQToEmu(const uint16 eq_op); + +protected: + class SharedMemStrategy : public OpcodeManager::OpcodeSetStrategy { + public: + void Set(EmuOpcode emu_op, uint16 eq_op); + }; + static bool DLLLoadOpcodesCallback(const char *filename); +}; +#endif //SHARED_OPCODES + +//keeps opcodes in regular heap memory +class RegularOpcodeManager : public MutableOpcodeManager { +public: + RegularOpcodeManager(); + virtual ~RegularOpcodeManager(); + + virtual bool Editable() { return(true); } + virtual bool LoadOpcodes(const char *filename); + virtual bool LoadOpcodes(map* eq, std::string* missingOpcodes = nullptr); + virtual bool ReloadOpcodes(const char *filename); + + virtual uint16 EmuToEQ(const EmuOpcode emu_op); + virtual EmuOpcode EQToEmu(const uint16 eq_op); + + //implement our editing interface + virtual void SetOpcode(EmuOpcode emu_op, uint16 eq_op); + +protected: + class NormalMemStrategy : public OpcodeManager::OpcodeSetStrategy { + public: + RegularOpcodeManager *it; + void Set(EmuOpcode emu_op, uint16 eq_op); + }; + friend class NormalMemStrategy; + + uint16 *emu_to_eq; + EmuOpcode *eq_to_emu; + uint32 EQOpcodeCount; + uint32 EmuOpcodeCount; +}; + +//always resolves everything to 0 or OP_Unknown +class NullOpcodeManager : public MutableOpcodeManager { +public: + NullOpcodeManager(); + + virtual bool LoadOpcodes(const char *filename); + virtual bool LoadOpcodes(map* eq, std::string* missingOpcodes = nullptr); + virtual bool ReloadOpcodes(const char *filename); + + virtual uint16 EmuToEQ(const EmuOpcode emu_op); + virtual EmuOpcode EQToEmu(const uint16 eq_op); + + //fake it, just used for testing anyways + virtual void SetOpcode(EmuOpcode emu_op, uint16 eq_op) {} +}; + +//starts as NullOpcodeManager, but remembers any mappings set +//could prolly have been implemented with an extension to regular, +//by overriding its load methods to be empty. +class EmptyOpcodeManager : public MutableOpcodeManager { +public: + EmptyOpcodeManager(); + + virtual bool LoadOpcodes(const char *filename); + virtual bool LoadOpcodes(map* eq, std::string* missingOpcodes = nullptr); + virtual bool ReloadOpcodes(const char *filename); + + virtual uint16 EmuToEQ(const EmuOpcode emu_op); + virtual EmuOpcode EQToEmu(const uint16 eq_op); + + //fake it, just used for testing anyways + virtual void SetOpcode(EmuOpcode emu_op, uint16 eq_op); +protected: + map emu_to_eq; + map eq_to_emu; +}; + +#endif + + diff --git a/source/common/packet_dump.cpp b/source/common/packet_dump.cpp new file mode 100644 index 0000000..27cb0a9 --- /dev/null +++ b/source/common/packet_dump.cpp @@ -0,0 +1,195 @@ +/* + 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 . +*/ +#include "../common/debug.h" +#include +#include +#include + +using namespace std; + +#include "packet_dump.h" +#include "EQStream.h" +#include "../common/servertalk.h" + +void DumpPacketAscii(const uchar* buf, int32 size, int32 cols, int32 skip) { + // Output as ASCII + for(int32 i=skip; i 32 && buf[i] < 127) + { + cout << buf[i]; + } + else + { + cout << '.'; + } + } + cout << endl << endl; +} + +void DumpPacketHex(const uchar* buf, int32 size, int32 cols, int32 skip) { + if (size == 0 || size > 39565) + return; + // Output as HEX + char output[4]; + int j = 0; char* ascii = new char[cols+1]; memset(ascii, 0, cols+1); + int32 i; + for(i=skip; i= 32 && buf[i] < 127) { + ascii[j++] = buf[i]; + } + else { + ascii[j++] = '.'; + } +// cout << setfill(0) << setw(2) << hex << (int)buf[i] << " "; + } + int32 k = ((i-skip)-1)%cols; + if (k < 8) + cout << " "; + for (int32 h = k+1; h < cols; h++) { + cout << " "; + } + cout << " | " << ascii << endl; + safe_delete_array(ascii); +} + +void DumpPacket(const uchar* buf, int32 size) +{ + DumpPacketHex(buf, size); +// DumpPacketAscii(buf,size); +} + +void DumpPacket(const ServerPacket* pack, bool iShowInfo) { + if (iShowInfo) { + cout << "Dumping ServerPacket: 0x" << hex << setfill('0') << setw(4) << pack->opcode << dec; + cout << " size:" << pack->size << endl; + } + DumpPacketHex(pack->pBuffer, pack->size); +} + +void DumpPacketBin(const ServerPacket* pack) { + DumpPacketBin(pack->pBuffer, pack->size); +} + +void DumpPacketBin(int32 data) { + DumpPacketBin((uchar*)&data, sizeof(int32)); +} + +void DumpPacketBin(int16 data) { + DumpPacketBin((uchar*)&data, sizeof(int16)); +} + +void DumpPacketBin(int8 data) { + DumpPacketBin((uchar*)&data, sizeof(int8)); +} + + +void DumpPacketBin(const void* iData, int32 len) { + if (!len) + return; + const int8* data = (const int8*) iData; + int32 k=0; + for (k=0; k 1) + cout << " " << hex << setw(2) << setfill('0') << (int) data[k-3] << dec; + if (tmp > 2) + cout << " " << hex << setw(2) << setfill('0') << (int) data[k-2] << dec; + if (tmp > 3) + cout << " " << hex << setw(2) << setfill('0') << (int) data[k-1] << dec; + cout << endl; +} diff --git a/source/common/packet_dump.h b/source/common/packet_dump.h new file mode 100644 index 0000000..42fe2ef --- /dev/null +++ b/source/common/packet_dump.h @@ -0,0 +1,41 @@ +/* + 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 . +*/ +#ifndef PACKET_DUMP_H +#define PACKET_DUMP_H + +#include +using namespace std; + +#include "../common/types.h" +#include "EQPacket.h" + +class ServerPacket; + +void DumpPacketAscii(const uchar* buf, int32 size, int32 cols=16, int32 skip=0); +void DumpPacketHex(const uchar* buf, int32 size, int32 cols=16, int32 skip=0); +void DumpPacketBin(const void* data, int32 len); +void DumpPacket(const uchar* buf, int32 size); +void DumpPacket(const ServerPacket* pack, bool iShowInfo = false); +void DumpPacketBin(const ServerPacket* pack); +void DumpPacketBin(int32 data); +void DumpPacketBin(int16 data); +void DumpPacketBin(int8 data); + +#endif diff --git a/source/common/packet_functions.cpp b/source/common/packet_functions.cpp new file mode 100644 index 0000000..a7e6a6c --- /dev/null +++ b/source/common/packet_functions.cpp @@ -0,0 +1,537 @@ +/* + 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 . +*/ +#include "../common/debug.h" +#include +#include +#include +#include +#include "packet_dump.h" +#include "EQStream.h" +#include "packet_functions.h" + +#ifndef WIN32 + #include +#endif + +using namespace std; + +#define eqemu_alloc_func Z_NULL +#define eqemu_free_func Z_NULL + + +int DeflatePacket(unsigned char* in_data, int in_length, unsigned char* out_data, int max_out_length) { +#ifdef REUSE_ZLIB + static bool inited = false; + static z_stream zstream; + int zerror; + + if(in_data == NULL && out_data == NULL && in_length == 0 && max_out_length == 0) { + //special delete state + deflateEnd(&zstream); + return(0); + } + if(!inited) { + zstream.zalloc = eqemu_alloc_func; + zstream.zfree = eqemu_free_func; + zstream.opaque = Z_NULL; + deflateInit(&zstream, Z_FINISH); + } + + zstream.next_in = in_data; + zstream.avail_in = in_length; +/* zstream.zalloc = Z_NULL; + zstream.zfree = Z_NULL; + zstream.opaque = Z_NULL; + deflateInit(&zstream, Z_FINISH);*/ + zstream.next_out = out_data; + zstream.avail_out = max_out_length; + zerror = deflate(&zstream, Z_FINISH); + + deflateReset(&zstream); + + if (zerror == Z_STREAM_END) + { +// deflateEnd(&zstream); + return zstream.total_out; + } + else + { +// zerror = deflateEnd(&zstream); + return 0; + } +#else + if(in_data == NULL) { + return(0); + } + + z_stream zstream; + int zerror; + + zstream.next_in = in_data; + zstream.avail_in = in_length; + zstream.zalloc = eqemu_alloc_func; + zstream.zfree = eqemu_free_func; + zstream.opaque = Z_NULL; + deflateInit(&zstream, Z_FINISH); + zstream.next_out = out_data; + zstream.avail_out = max_out_length; + zerror = deflate(&zstream, Z_FINISH); + + if (zerror == Z_STREAM_END) + { + deflateEnd(&zstream); + return zstream.total_out; + } + else + { + zerror = deflateEnd(&zstream); + return 0; + } +#endif +} + +uint32 InflatePacket(uchar* indata, uint32 indatalen, uchar* outdata, uint32 outdatalen, bool iQuiet) { +#ifdef REUSE_ZLIB + static bool inited = false; + static z_stream zstream; + int zerror; + + if(indata == NULL && outdata == NULL && indatalen == 0 && outdatalen == 0) { + //special delete state + inflateEnd(&zstream); + return(0); + } + if(!inited) { + zstream.zalloc = eqemu_alloc_func; + zstream.zfree = eqemu_free_func; + zstream.opaque = Z_NULL; + inflateInit2(&zstream, 15); + } + + zstream.next_in = indata; + zstream.avail_in = indatalen; + zstream.next_out = outdata; + zstream.avail_out = outdatalen; + zstream.zalloc = eqemu_alloc_func; + zstream.zfree = eqemu_free_func; + zstream.opaque = Z_NULL; + + i = inflateInit2( &zstream, 15 ); + if (i != Z_OK) { + return 0; + } + + zerror = inflate( &zstream, Z_FINISH ); + + inflateReset(&zstream); + + if(zerror == Z_STREAM_END) { + return zstream.total_out; + } + else { + if (!iQuiet) { + cout << "Error: InflatePacket: inflate() returned " << zerror << " '"; + if (zstream.msg) + cout << zstream.msg; + cout << "'" << endl; + //DumpPacket(indata-16, indatalen+16); + } + + if (zerror == -4 && zstream.msg == 0) + { + return 0; + } + + return 0; + } +#else + if(indata == NULL) + return(0); + + z_stream zstream; + int zerror = 0; + int i; + + zstream.next_in = indata; + zstream.avail_in = indatalen; + zstream.next_out = outdata; + zstream.avail_out = outdatalen; + zstream.zalloc = eqemu_alloc_func; + zstream.zfree = eqemu_free_func; + zstream.opaque = Z_NULL; + + i = inflateInit2( &zstream, 15 ); + if (i != Z_OK) { + return 0; + } + + zerror = inflate( &zstream, Z_FINISH ); + + if(zerror == Z_STREAM_END) { + inflateEnd( &zstream ); + return zstream.total_out; + } + else { + if (!iQuiet) { + cout << "Error: InflatePacket: inflate() returned " << zerror << " '"; + if (zstream.msg) + cout << zstream.msg; + cout << "'" << endl; + //DumpPacket(indata-16, indatalen+16); + } + + if (zerror == -4 && zstream.msg == 0) + { + return 0; + } + + zerror = inflateEnd( &zstream ); + return 0; + } +#endif +} + +int32 roll(int32 in, int8 bits) { + return ((in << bits) | (in >> (32-bits))); +} + +int64 roll(int64 in, int8 bits) { + return ((in << bits) | (in >> (64-bits))); +} + +int32 rorl(int32 in, int8 bits) { + return ((in >> bits) | (in << (32-bits))); +} + +int64 rorl(int64 in, int8 bits) { + return ((in >> bits) | (in << (64-bits))); +} + +int32 CRCLookup(uchar idx) { + if (idx == 0) + return 0x00000000; + + if (idx == 1) + return 0x77073096; + + if (idx == 2) + return roll(CRCLookup(1), 1); + + if (idx == 4) + return 0x076DC419; + + for (uchar b=7; b>0; b--) { + uchar bv = 1 << b; + + if (!(idx ^ bv)) { + // bit is only one set + return ( roll(CRCLookup (4), b - 2) ); + } + + if (idx&bv) { + // bit is set + return( CRCLookup(bv) ^ CRCLookup(idx&(bv - 1)) ); + } + } + + //Failure + return false; +} + +uint32 GenerateCRC(int32 b, int32 bufsize, uchar *buf) { + int32 CRC = (b ^ 0xFFFFFFFF); + int32 bufremain = bufsize; + uchar* bufptr = buf; + + while (bufremain--) { + CRC = CRCLookup((uchar)(*(bufptr++)^ (CRC&0xFF))) ^ (CRC >> 8); + } + + return (htonl (CRC ^ 0xFFFFFFFF)); +} + +long int CRCArray[] = { + 0, +1996959894, +3993919788, +2567524794, +124634137, +1886057615, +3915621685, +2657392035, +249268274, +2044508324, +3772115230, +2547177864, +162941995, +2125561021, +3887607047, +2428444049, +498536548, +1789927666, +4089016648, +2227061214, +450548861, +1843258603, +4107580753, +2211677639, +325883990, +1684777152, +4251122042, +2321926636, +335633487, +1661365465, +4195302755, +2366115317, +997073096, +1281953886, +3579855332, +2724688242, +1006888145, +1258607687, +3524101629, +2768942443, +901097722, +1119000684, +3686517206, +2898065728, +853044451, +1172266101, +3705015759, +2882616665, +651767980, +1373503546, +3369554304, +3218104598, +565507253, +1454621731, +3485111705, +3099436303, +671266974, +1594198024, +3322730930, +2970347812, +795835527, +1483230225, +3244367275, +3060149565, +1994146192, +31158534, +2563907772, +4023717930, +1907459465, +112637215, +2680153253, +3904427059, +2013776290, +251722036, +2517215374, +3775830040, +2137656763, +141376813, +2439277719, +3865271297, +1802195444, +476864866, +2238001368, +4066508878, +1812370925, +453092731, +2181625025, +4111451223, +1706088902, +314042704, +2344532202, +4240017532, +1658658271, +366619977, +2362670323, +4224994405, +1303535960, +984961486, +2747007092, +3569037538, +1256170817, +1037604311, +2765210733, +3554079995, +1131014506, +879679996, +2909243462, +3663771856, +1141124467, +855842277, +2852801631, +3708648649, +1342533948, +654459306, +3188396048, +3373015174, +1466479909, +544179635, +3110523913, +3462522015, +1591671054, +702138776, +2966460450, +3352799412, +1504918807, +783551873, +3082640443, +3233442989, +3988292384, +2596254646, +62317068, +1957810842, +3939845945, +2647816111, +81470997, +1943803523, +3814918930, +2489596804, +225274430, +2053790376, +3826175755, +2466906013, +167816743, +2097651377, +4027552580, +2265490386, +503444072, +1762050814, +4150417245, +2154129355, +426522225, +1852507879, +4275313526, +2312317920, +282753626, +1742555852, +4189708143, +2394877945, +397917763, +1622183637, +3604390888, +2714866558, +953729732, +1340076626, +3518719985, +2797360999, +1068828381, +1219638859, +3624741850, +2936675148, +906185462, +1090812512, +3747672003, +2825379669, +829329135, +1181335161, +3412177804, +3160834842, +628085408, +1382605366, +3423369109, +3138078467, +570562233, +1426400815, +3317316542, +2998733608, +733239954, +1555261956, +3268935591, +3050360625, +752459403, +1541320221, +2607071920, +3965973030, +1969922972, +40735498, +2617837225, +3943577151, +1913087877, +83908371, +2512341634, +3803740692, +2075208622, +213261112, +2463272603, +3855990285, +2094854071, +198958881, +2262029012, +4057260610, +1759359992, +534414190, +2176718541, +4139329115, +1873836001, +414664567, +2282248934, +4279200368, +1711684554, +285281116, +2405801727, +4167216745, +1634467795, +376229701, +2685067896, +3608007406, +1308918612, +956543938, +2808555105, +3495958263, +1231636301, +1047427035, +2932959818, +3654703836, +1088359270, +936918000, +2847714899, +3736837829, +1202900863, +817233897, +3183342108, +3401237130, +1404277552, +615818150, +3134207493, +3453421203, +1423857449, +601450431, +3009837614, +3294710456, +1567103746, +711928724, +3020668471, +3272380065, +1510334235, +755167117}; + +uint32 GenerateCRCRecipe(uint32 initial, void* buf, uint32 len) +{ + uint32 c = 0xFFFFFFFF; + sint8* u = static_cast(buf); + for (size_t i = 0; i < len; ++i) + { + c = CRCArray[(c ^ u[i]) & 0xFF] ^ (c >> 8); + } + return c; +} \ No newline at end of file diff --git a/source/common/packet_functions.h b/source/common/packet_functions.h new file mode 100644 index 0000000..318ff2d --- /dev/null +++ b/source/common/packet_functions.h @@ -0,0 +1,45 @@ +/* + 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 . +*/ +#ifndef PACKET_FUNCTIONS_H +#define PACKET_FUNCTIONS_H +#include "types.h" +#include "EQPacket.h" + +int32 roll(int32 in, int8 bits); +int64 roll(int64 in, int8 bits); +int32 rorl(int32 in, int8 bits); +int64 rorl(int64 in, int8 bits); + +void EncryptProfilePacket(EQApplicationPacket* app); +void EncryptProfilePacket(uchar* pBuffer, int32 size); + +#define EncryptSpawnPacket EncryptZoneSpawnPacket +//void EncryptSpawnPacket(EQApplicationPacket* app); +//void EncryptSpawnPacket(uchar* pBuffer, int32 size); + +void EncryptZoneSpawnPacket(EQApplicationPacket* app); +void EncryptZoneSpawnPacket(uchar* pBuffer, int32 size); + +int DeflatePacket(unsigned char* in_data, int in_length, unsigned char* out_data, int max_out_length); +uint32 InflatePacket(uchar* indata, uint32 indatalen, uchar* outdata, uint32 outdatalen, bool iQuiet = false); +uint32 GenerateCRC(int32 b, int32 bufsize, uchar *buf); +uint32 GenerateCRCRecipe(uint32 b, void* buf, uint32 bufsize); + +#endif diff --git a/source/common/queue.h b/source/common/queue.h new file mode 100644 index 0000000..e29ea05 --- /dev/null +++ b/source/common/queue.h @@ -0,0 +1,128 @@ +/* + 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 . +*/ +#ifndef QUEUE_H +#define QUEUE_H + +template +class MyQueue; + +template +class MyQueueNode +{ +public: + MyQueueNode(T* data) + { + next = 0; + this->data = data; + } + + friend class MyQueue; + +private: + T* data; + MyQueueNode* next; +}; + +template +class MyQueue +{ +public: + MyQueue() + { + head = tail = 0; + } + ~MyQueue() { + clear(); + } + + void push(T* data) + { + if (head == 0) + { + tail = head = new MyQueueNode(data); + } + else + { + tail->next = new MyQueueNode(data); + tail = tail->next; + } + } + + T* pop() + { + if (head == 0) + { + return 0; + } + + T* data = head->data; + MyQueueNode* next_node = head->next; + delete head; + head = next_node; + + return data; + } + + T* top() + { + if (head == 0) + { + return 0; + } + + return head->data; + } + + bool empty() + { + if (head == 0) + { + return true; + } + + return false; + } + + void clear() + { + T* d = 0; + while((d = pop())) { + delete d; + } + return; + } + + int count() + { + int count = 0; + MyQueueNode* d = head; + while(d != 0) { + count++; + d = d->next; + } + return(count); + } + +private: + MyQueueNode* head; + MyQueueNode* tail; +}; + +#endif diff --git a/source/common/seperator.h b/source/common/seperator.h new file mode 100644 index 0000000..b033aa8 --- /dev/null +++ b/source/common/seperator.h @@ -0,0 +1,165 @@ +/* + 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 . +*/ +// This class will split up a string smartly at the div character (default is space and tab) +// Seperator.arg[i] is a copy of the string chopped at the divs +// Seperator.argplus[i] is a pointer to the original string so it doesnt end at the div + +// Written by Quagmire +#ifndef SEPERATOR_H +#define SEPERATOR_H + +#include +#include + +class Seperator +{ +public: + Seperator(const char* message, char div = ' ', int16 in_maxargnum = 10, int16 arglen = 100, bool iObeyQuotes = false, char div2 = '\t', char div3 = 0, bool iSkipEmpty = true) { + int i; + argnum = 0; + msg = strdup(message); + this->maxargnum = in_maxargnum; + argplus = new const char *[maxargnum+1]; + arg = new char *[maxargnum+1]; + for (i=0; i<=maxargnum; i++) { + argplus[i]=arg[i] = new char[arglen+1]; + memset(arg[i], 0, arglen+1); + } + + int len = strlen(message); + int s = 0, l = 0; + bool inarg = (!iSkipEmpty || !(message[0] == div || message[0] == div2 || message[0] == div3)); + bool inquote = (iObeyQuotes && (message[0] == '\"' || message[0] == '\'')); + argplus[0] = message; + if (len == 0) + return; + + for (i=0; i= arglen) + l = arglen; + if (l){ + if(l > 1 && (argplus[argnum][0] == '\'' || argplus[argnum][0] == '\"')){ + l--; + memcpy(arg[argnum], argplus[argnum]+1, l); + } + else + memcpy(arg[argnum], argplus[argnum], l); + } + arg[argnum][l] = 0; + argnum++; + if (iSkipEmpty) + inarg = false; + else { + s=i+1; + argplus[argnum] = &message[s]; + } + } + } + else if (iObeyQuotes && (message[i] == '\"' || message[i] == '\'')) { + inquote = true; + } + else { + s = i; + argplus[argnum] = &message[s]; + if (!(message[i] == div || message[i] == div2 || message[i] == div3)) { + inarg = true; + } + } + if (argnum > maxargnum) + break; + } + if (inarg && argnum <= maxargnum) { + l = i-s; + if (l >= arglen) + l = arglen; + if (l) + memcpy(arg[argnum], argplus[argnum], l); + } + } + ~Seperator() { + for (int i=0; i<=maxargnum; i++) + safe_delete_array(arg[i]); + safe_delete_array(arg); + safe_delete_array(argplus); + if (msg) + free(msg); + } + int16 argnum; + char** arg; + const char** argplus; + char * msg; + bool IsSet(int num) const { + return IsSet(arg[num]); + } + bool IsNumber(int num) const { + return IsNumber(arg[num]); + } + bool IsHexNumber(int num) const { + return IsHexNumber(arg[num]); + } + static bool IsSet(const char *check) { + return check[0] != '\0'; + } + static bool IsNumber(const char* check) { + bool SeenDec = false; + int len = strlen(check); + if (len == 0) { + return false; + } + int i; + for (i = 0; i < len; i++) { + if (check[i] < '0' || check[i] > '9') { + if (check[i] == '.' && !SeenDec) { + SeenDec = true; + } + else if (i == 0 && (check[i] == '-' || check[i] == '+') && !check[i+1] == 0) { + // this is ok, do nothin + } + else { + return false; + } + } + } + return true; + } + static bool IsHexNumber(char* check) { + int len = strlen(check); + if (len < 3) + return false; + if (check[0] != '0' || (check[1] != 'x' && check[1] != 'X')) + return false; + for (int i=2; i '9') && (check[i] < 'A' || check[i] > 'F') && (check[i] < 'a' || check[i] > 'f')) + return false; + } + return true; + } + inline int16 GetMaxArgNum() const { return maxargnum; } + inline int16 GetArgNumber() const { return argnum; } +private: + int16 maxargnum; +}; + +#endif diff --git a/source/common/servertalk.h b/source/common/servertalk.h new file mode 100644 index 0000000..fa2c1c1 --- /dev/null +++ b/source/common/servertalk.h @@ -0,0 +1,754 @@ +/* + 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 . +*/ +#ifndef EQ_SOPCODES_H +#define EQ_SOPCODES_H + +#define EQEMU_PROTOCOL_VERSION "0.5.0" + +#include "types.h" +#include "packet_functions.h" +#include + +#define SERVER_TIMEOUT 45000 // how often keepalive gets sent +#define INTERSERVER_TIMER 10000 +#define LoginServer_StatusUpdateInterval 15000 +#define LoginServer_AuthStale 60000 +#define AUTHCHANGE_TIMEOUT 900 // in seconds + +#define ServerOP_KeepAlive 0x0001 // packet to test if port is still open +#define ServerOP_ChannelMessage 0x0002 // broadcast/guildsay +#define ServerOP_SetZone 0x0003 // client -> server zoneinfo +#define ServerOP_ShutdownAll 0x0004 // exit(0); +#define ServerOP_ZoneShutdown 0x0005 // unload all data, goto sleep mode +#define ServerOP_ZoneBootup 0x0006 // come out of sleep mode and load zone specified +#define ServerOP_ZoneStatus 0x0007 // Shows status of all zones +#define ServerOP_SetConnectInfo 0x0008 // Tells server address and port # +#define ServerOP_EmoteMessage 0x0009 // Worldfarts +#define ServerOP_ClientList 0x000A // Update worldserver's client list, for #whos +#define ServerOP_Who 0x000B // #who +#define ServerOP_ZonePlayer 0x000C // #zone, or #summon +#define ServerOP_KickPlayer 0x000D // #kick +#define ServerOP_RefreshGuild 0x000E // Notice to all zoneservers to refresh their guild cache for ID# in packet +#define ServerOP_GuildKickAll 0x000F // Remove all clients from this guild +#define ServerOP_GuildInvite 0x0010 +#define ServerOP_GuildRemove 0x0011 +#define ServerOP_GuildPromote 0x0012 +#define ServerOP_GuildDemote 0x0013 +#define ServerOP_GuildLeader 0x0014 +#define ServerOP_GuildGMSet 0x0015 +#define ServerOP_GuildGMSetRank 0x0016 +#define ServerOP_FlagUpdate 0x0018 // GM Flag updated for character, refresh the memory cache +#define ServerOP_GMGoto 0x0019 +#define ServerOP_MultiLineMsg 0x001A +#define ServerOP_Lock 0x001B // For #lock/#unlock inside server +#define ServerOP_Motd 0x001C // For changing MoTD inside server. +#define ServerOP_Uptime 0x001D +#define ServerOP_Petition 0x001E +#define ServerOP_KillPlayer 0x001F +#define ServerOP_UpdateGM 0x0020 +#define ServerOP_RezzPlayer 0x0021 +#define ServerOP_ZoneReboot 0x0022 +#define ServerOP_ZoneToZoneRequest 0x0023 +#define ServerOP_AcceptWorldEntrance 0x0024 +#define ServerOP_ZAAuth 0x0025 +#define ServerOP_ZAAuthFailed 0x0026 +#define ServerOP_ZoneIncClient 0x0027 // Incomming client +#define ServerOP_ClientListKA 0x0028 +#define ServerOP_ChangeWID 0x0029 +#define ServerOP_IPLookup 0x002A +#define ServerOP_LockZone 0x002B +#define ServerOP_ItemStatus 0x002C +#define ServerOP_OOCMute 0x002D +#define ServerOP_Revoke 0x002E +#define ServerOP_GuildJoin 0x002F +#define ServerOP_GroupIDReq 0x0030 +#define ServerOP_GroupIDReply 0x0031 +#define ServerOP_GroupLeave 0x0032 // for disbanding out of zone folks +#define ServerOP_RezzPlayerAccept 0x0033 +#define ServerOP_SpawnCondition 0x0034 +#define ServerOP_SpawnEvent 0x0035 + +#define UpdateServerOP_Verified 0x5090 +#define UpdateServerOP_DisplayMsg 0x5091 +#define UpdateServerOP_Completed 0x5092 + +#define ServerOP_LSInfo 0x1000 +#define ServerOP_LSStatus 0x1001 +#define ServerOP_LSClientAuth 0x1002 +#define ServerOP_LSFatalError 0x1003 +#define ServerOP_SystemwideMessage 0x1005 +#define ServerOP_ListWorlds 0x1006 +#define ServerOP_PeerConnect 0x1007 + +#define ServerOP_LSZoneInfo 0x3001 +#define ServerOP_LSZoneStart 0x3002 +#define ServerOP_LSZoneBoot 0x3003 +#define ServerOP_LSZoneShutdown 0x3004 +#define ServerOP_LSZoneSleep 0x3005 +#define ServerOP_LSPlayerLeftWorld 0x3006 +#define ServerOP_LSPlayerJoinWorld 0x3007 +#define ServerOP_LSPlayerZoneChange 0x3008 + +#define ServerOP_UsertoWorldReq 0xAB00 +#define ServerOP_UsertoWorldResp 0xAB01 + +#define ServerOP_EncapPacket 0x2007 // Packet within a packet +#define ServerOP_WorldListUpdate 0x2008 +#define ServerOP_WorldListRemove 0x2009 +#define ServerOP_TriggerWorldListRefresh 0x200A + +#define ServerOP_WhoAll 0x0210 + +#define ServerOP_SetWorldTime 0x200B +#define ServerOP_GetWorldTime 0x200C +#define ServerOP_SyncWorldTime 0x200E + +//EQ2 Opcodes +#define ServerOP_CharTimeStamp 0x200F + +#define ServerOP_NameFilterCheck 0x2011 +#define ServerOP_BasicCharUpdate 0x2012 +#define ServerOP_CharacterCreate 0x2013 +#define ServerOP_NameCharUpdate 0x2014 +#define ServerOP_GetLatestTables 0x2015 +#define ServerOP_GetTableQuery 0x2016 +#define ServerOP_GetTableData 0x2017 +#define ServerOP_RaceUpdate 0x2018 +#define ServerOP_ZoneUpdate 0x2019 +#define ServerOP_BugReport 0x201A +#define ServerOP_ResetDatabase 0x201B +#define ServerOP_ZoneUpdates 0x201C +#define ServerOP_LoginEquipment 0x201D // updates charater select screen item appearances (gear appear) +#define ServerOP_CharacterPicture 0x201E + + +/************ PACKET RELATED STRUCT ************/ +class ServerPacket +{ +public: + ~ServerPacket() { safe_delete_array(pBuffer); } + ServerPacket(int16 in_opcode = 0, int32 in_size = 0) { + this->compressed = false; + size = in_size; + opcode = in_opcode; + if (size == 0) { + pBuffer = 0; + } + else { + pBuffer = new uchar[size]; + memset(pBuffer, 0, size); + } + destination = 0; + InflatedSize = 0; + } + ServerPacket* Copy() { + if (this == 0) { + return 0; + } + ServerPacket* ret = new ServerPacket(this->opcode, this->size); + if (this->size) + memcpy(ret->pBuffer, this->pBuffer, this->size); + ret->compressed = this->compressed; + ret->InflatedSize = this->InflatedSize; + return ret; + } + bool Deflate() { + if (compressed) + return false; + if ((!this->pBuffer) || (!this->size)) + return false; + uchar* tmp = new uchar[this->size + 128]; + int32 tmpsize = DeflatePacket(this->pBuffer, this->size, tmp, this->size + 128); + if (!tmpsize) { + safe_delete_array(tmp); + return false; + } + this->compressed = true; + this->InflatedSize = this->size; + this->size = tmpsize; + uchar* new_buffer = new uchar[this->size]; + memcpy(new_buffer, tmp, this->size); + safe_delete_array(tmp); + uchar* tmpdel = this->pBuffer; + this->pBuffer = new_buffer; + safe_delete_array(tmpdel); + return true; + } + bool Inflate() { + if (!compressed) + return false; + if ((!this->pBuffer) || (!this->size)) + return false; + uchar* tmp = new uchar[InflatedSize]; + int32 tmpsize = InflatePacket(this->pBuffer, this->size, tmp, InflatedSize); + if (!tmpsize) { + safe_delete_array(tmp); + return false; + } + compressed = false; + this->size = tmpsize; + uchar* tmpdel = this->pBuffer; + this->pBuffer = tmp; + safe_delete_array(tmpdel); + return true; + } + int32 size; + int16 opcode; + uchar* pBuffer; + bool compressed; + int32 InflatedSize; + int32 destination; +}; + +#pragma pack(1) + +struct GetLatestTables_Struct{ + float table_version; + float data_version; +}; + +struct ServerLSInfo_Struct { + char name[201]; // name the worldserver wants + char address[250]; // DNS address of the server + char account[31]; // account name for the worldserver + char password[256]; // password for the name + char protocolversion[25]; // Major protocol version number + char serverversion[64]; // minor server software version number + int8 servertype; // 0=world, 1=chat, 2=login, 3=MeshLogin, 4=World Debug + int32 dbversion; // database major+minor version from version.h (for PatchServer) +}; + +struct ServerLSStatus_Struct { + sint32 status; + sint32 num_players; + sint32 num_zones; + int8 world_max_level; +}; + +struct ServerSystemwideMessage { + int32 lsaccount_id; + char key[30]; // sessionID key for verification + int32 type; + char message[0]; +}; + +struct ServerSyncWorldList_Struct { + int32 RemoteID; + int32 ip; + sint32 status; + char name[201]; + char address[250]; + char account[31]; + int32 accountid; + int8 authlevel; + int8 servertype; // 0=world, 1=chat, 2=login + int32 adminid; + int8 showdown; + sint32 num_players; + sint32 num_zones; + bool placeholder; +}; + +struct UsertoWorldRequest_Struct { + int32 lsaccountid; + int32 char_id; + int32 worldid; + int32 FromID; + int32 ToID; + char ip_address[21]; +}; + +struct UsertoWorldResponse_Struct { + int32 lsaccountid; + int32 char_id; + int32 worldid; + int32 access_key; + int8 response; + char ip_address[80]; + int32 port; + int32 FromID; + int32 ToID; +}; + +struct ServerEncapPacket_Struct { + int32 ToID; // ID number of the LWorld on the other server + int16 opcode; + int16 size; + uchar data[0]; +}; + +struct ServerEmoteMessage_Struct { + char to[64]; + int32 guilddbid; + sint16 minstatus; + int32 type; + char message[0]; +}; + +/*struct TableVersion{ + char name[64]; + int32 version; + int32 max_table_version; + int32 max_data_version; + sint32 data_version; + int8 last; + char column_names[1000]; +};*/ + +typedef struct { + char name[256]; + unsigned int name_len; + unsigned int version; + unsigned int data_version; +} TableVersion; + +template void AddPtrData(string* buffer, Type& data){ + buffer->append((char*)&data, sizeof(Type)); +} +template void AddPtrData(string* buffer, Type* data, int16 size){ + buffer->append(data, size); +} +class LatestTableVersions { +public: + LatestTableVersions(){ + tables = 0; + current_index = 0; + total_tables = 0; + data_version = 0; + } + ~LatestTableVersions(){ + safe_delete_array(tables); + } + void SetTableSize(int16 size){ + total_tables = size; + tables = new TableVersion[total_tables]; + } + void AddTable(char* name, int32 version, int32 data_version){ + strcpy(tables[current_index].name, name); + tables[current_index].version = version; + tables[current_index].data_version = data_version; + current_index++; + } + int16 GetTotalSize(){ + return total_tables * sizeof(TableVersion) + sizeof(int16); + } + int16 GetTotalTables(){ + return total_tables; + } + TableVersion* GetTables(){ + return tables; + } + TableVersion GetTable(int16 index){ + return tables[index]; + } + string Serialize(){ + AddPtrData(&buffer, total_tables); + for(int16 i=0;i tmp_queries; +}; +class TableDataQuery{ +public: + TableDataQuery(char* table_name){ + if( strlen(table_name) >= sizeof(tablename) ) + return; + strcpy(tablename, table_name); + num_queries = 0; + columns_size = 0; + columns = 0; + version = 0; + table_size = 0; + } + TableDataQuery(){ + num_queries = 0; + columns_size = 0; + columns = 0; + version = 0; + table_size = 0; + } + ~TableDataQuery(){ + safe_delete_array(columns); + for(int32 i=0;iquery); + safe_delete(queries[i]); + } + } + int32 GetTotalQueries(){ + return num_queries; + } + string* Serialize(){ + buffer = ""; + num_queries = queries.size(); + if(GetTotalQueries() == 0) + return 0; + table_size = strlen(tablename); + AddPtrData(&buffer, table_size); + AddPtrData(&buffer, tablename, table_size + 1); + AddPtrData(&buffer, version); + if(num_queries > 200){ + int32 max_queries = 200; + AddPtrData(&buffer, max_queries); + } + else + AddPtrData(&buffer, num_queries); + AddPtrData(&buffer, columns_size); + AddPtrData(&buffer, columns, columns_size); + vector::iterator query_iterator; + int16 count = 0; + for(int i=GetTotalQueries() - 1;i >=0 && count < 200;i--){ + AddPtrData(&buffer, queries[i]->size); + AddPtrData(&buffer, queries[i]->query, queries[i]->size); + safe_delete_array(queries[i]->query); + safe_delete(queries[i]); + queries.pop_back(); + count++; + } + return &buffer; + } + void DeSerialize(uchar* data){ + uchar* ptr = data; + + memcpy(&table_size, ptr, sizeof(table_size)); + ptr+= sizeof(table_size); + memcpy(&tablename, ptr, table_size + 1); + ptr+= table_size + 1; + + memcpy(&version, ptr, sizeof(version)); + ptr+= sizeof(version); + + memcpy(&num_queries, ptr, sizeof(num_queries)); + ptr+= sizeof(num_queries); + + memcpy(&columns_size, ptr, sizeof(columns_size)); + ptr+= sizeof(columns_size); + columns = new char[columns_size + 1]; + memcpy(columns, ptr, columns_size + 1); + ptr+= columns_size; + + for(int32 i=0;isize, ptr, sizeof(new_query->size)); + ptr+= sizeof(new_query->size); + new_query->query = new char[new_query->size + 1]; + memcpy(new_query->query, ptr, new_query->size); + ptr+= new_query->size; + queries.push_back(new_query); + } + catch( bad_alloc &ba ) + { + cout << ba.what() << endl; + if( NULL != new_query ) + delete new_query; + } + } + } + string buffer; + int32 num_queries; + int32 version; + int16 table_size; + char tablename[64]; + int16 columns_size; + char* columns; + vector queries; +}; + +// Max number of equipment updates to send at once +struct EquipmentUpdateRequest_Struct +{ + int16 max_per_batch; +}; + +// Login's structure of equipment data +struct LoginEquipmentUpdate +{ + int32 world_char_id; + int16 equip_type; + int8 red; + int8 green; + int8 blue; + int8 highlight_red; + int8 highlight_green; + int8 highlight_blue; + int32 slot; +}; + +// World's structure of equipment data +struct EquipmentUpdate_Struct +{ + int32 id; // unique record identifier per world + int32 world_char_id; + int16 equip_type; + int8 red; + int8 green; + int8 blue; + int8 highlight_red; + int8 highlight_green; + int8 highlight_blue; + int32 slot; +}; + +// How many equipmment updates are there to send? +struct EquipmentUpdateList_Struct +{ + sint16 total_updates; +}; + +struct ZoneUpdateRequest_Struct{ + int16 max_per_batch; +}; + +struct LoginZoneUpdate{ + string name; + string description; +}; + +struct ZoneUpdate_Struct{ + int32 zone_id; + int8 zone_name_length; + int8 zone_desc_length; + char data[0]; +}; + +struct ZoneUpdateList_Struct{ + uint16 total_updates; + char data[0]; +}; + +//EQ2 Specific Structures Login -> World (Image) +struct CharacterTimeStamp_Struct { +int32 char_id; +int32 account_id; +int32 unix_timestamp; +}; + +//EQ2 Specific Structures World -> Login (Image) + +/**UPDATE_FIELD TYPES** +These will be stored beside the timestamp on the world server to determine what has changed on between the timestamp, when the update is sent, it will remove the flag. + +8 bits in a byte: +Example: 01001100 +0 Level Flag +1 Race Flag +0 Class Flag +0 Gender Flag +1 Zone Flag +1 Armor Flag +0 Name Flag +0 Delete Flag +**/ +#define LEVEL_UPDATE_FLAG 1 +#define RACE_UPDATE_FLAG 2 +#define CLASS_UPDATE_FLAG 4 +#define GENDER_UPDATE_FLAG 8 +#define ZONE_UPDATE_FLAG 16 +#define ARMOR_UPDATE_FLAG 32 +#define NAME_UPDATE_FLAG 64 +#define DELETE_UPDATE_FLAG 128 +//This structure used for basic changes such as level,class,gender, and deletes that are not able to be backed up +struct CharDataUpdate_Struct { +int32 account_id; +int32 char_id; +int8 update_field; +int32 update_data; +}; +struct BugReport{ +char category[64]; +char subcategory[64]; +char causes_crash[64]; +char reproducible[64]; +char summary[128]; +char description[2000]; +char version[32]; +char player[64]; +int32 account_id; +char spawn_name[64]; +int32 spawn_id; +int32 zone_id; +}; + +struct RaceUpdate_Struct { +int32 account_id; +int32 char_id; +int16 model_type; +int8 race; +}; + +//If this structure comes in with more than 74 bytes, should probably discard (leaves 65 bytes for new_name) +#define CHARNAMEUPDATESTRUCT_MAXSIZE 74 +struct CharNameUpdate_Struct { +int32 account_id; +int32 char_id; +int8 name_length; // If its longer than 64, something is wrong :-/ +char new_name[0]; +}; + +//If this structure comes in with more than 78 bytes, should probably discard (leaves 65 bytes for new_zone) +#define CHARZONESTRUCT_MAXSIZE 78 +struct CharZoneUpdate_Struct { +int32 account_id; +int32 char_id; +int32 zone_id; +int8 zone_length; // If its longer than 64, something is wrong :-/ +char new_zone[0]; +}; + +struct WorldCharCreate_Struct { +int32 account_id; +int32 char_id; +int16 model_type; +int16 char_size; +uchar character[0]; +}; + +struct WorldCharNameFilter_Struct { +int32 account_id; +int16 name_length; +uchar name[0]; +}; + +struct WorldCharNameFilterResponse_Struct { +int32 account_id; +int32 char_id; +int8 response; +}; + +#define CHARPICSTRUCT_MINSIZE 10 +// Should only be used for the headshot picture +struct CharPictureUpdate_Struct { + int32 account_id; + int32 char_id; + int16 pic_size; + char pic[0]; +}; + +#pragma pack() + +#endif diff --git a/source/common/sha512.cpp b/source/common/sha512.cpp new file mode 100644 index 0000000..10b9592 --- /dev/null +++ b/source/common/sha512.cpp @@ -0,0 +1,155 @@ +#include +#include +#include "sha512.h" + +const unsigned long long SHA512::sha512_k[80] = //ULL = uint64 + {0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, + 0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL, + 0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL, + 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL, + 0xd807aa98a3030242ULL, 0x12835b0145706fbeULL, + 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL, + 0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, + 0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL, + 0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL, + 0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL, + 0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL, + 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL, + 0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL, + 0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL, + 0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL, + 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL, + 0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL, + 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL, + 0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, + 0x81c2c92e47edaee6ULL, 0x92722c851482353bULL, + 0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL, + 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL, + 0xd192e819d6ef5218ULL, 0xd69906245565a910ULL, + 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL, + 0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, + 0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL, + 0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL, + 0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL, + 0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL, + 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL, + 0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, + 0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL, + 0xca273eceea26619cULL, 0xd186b8c721c0c207ULL, + 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL, + 0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL, + 0x113f9804bef90daeULL, 0x1b710b35131c471bULL, + 0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, + 0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL, + 0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL, + 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL}; + +void SHA512::transform(const unsigned char *message, unsigned int block_nb) +{ + uint64 w[80]; + uint64 wv[8]; + uint64 t1, t2; + const unsigned char *sub_block; + int i, j; + for (i = 0; i < (int) block_nb; i++) { + sub_block = message + (i << 7); + for (j = 0; j < 16; j++) { + SHA2_PACK64(&sub_block[j << 3], &w[j]); + } + for (j = 16; j < 80; j++) { + w[j] = SHA512_F4(w[j - 2]) + w[j - 7] + SHA512_F3(w[j - 15]) + w[j - 16]; + } + for (j = 0; j < 8; j++) { + wv[j] = m_h[j]; + } + for (j = 0; j < 80; j++) { + t1 = wv[7] + SHA512_F2(wv[4]) + SHA2_CH(wv[4], wv[5], wv[6]) + + sha512_k[j] + w[j]; + t2 = SHA512_F1(wv[0]) + SHA2_MAJ(wv[0], wv[1], wv[2]); + wv[7] = wv[6]; + wv[6] = wv[5]; + wv[5] = wv[4]; + wv[4] = wv[3] + t1; + wv[3] = wv[2]; + wv[2] = wv[1]; + wv[1] = wv[0]; + wv[0] = t1 + t2; + } + for (j = 0; j < 8; j++) { + m_h[j] += wv[j]; + } + + } +} + +void SHA512::init() +{ + m_h[0] = 0x6a09e667f3bcc908ULL; + m_h[1] = 0xbb67ae8584caa73bULL; + m_h[2] = 0x3c6ef372fe94f82bULL; + m_h[3] = 0xa54ff53a5f1d36f1ULL; + m_h[4] = 0x510e527fade682d1ULL; + m_h[5] = 0x9b05688c2b3e6c1fULL; + m_h[6] = 0x1f83d9abfb41bd6bULL; + m_h[7] = 0x5be0cd19137e2179ULL; + m_len = 0; + m_tot_len = 0; +} + +void SHA512::update(const unsigned char *message, unsigned int len) +{ + unsigned int block_nb; + unsigned int new_len, rem_len, tmp_len; + const unsigned char *shifted_message; + tmp_len = SHA384_512_BLOCK_SIZE - m_len; + rem_len = len < tmp_len ? len : tmp_len; + memcpy(&m_block[m_len], message, rem_len); + if (m_len + len < SHA384_512_BLOCK_SIZE) { + m_len += len; + return; + } + new_len = len - rem_len; + block_nb = new_len / SHA384_512_BLOCK_SIZE; + shifted_message = message + rem_len; + transform(m_block, 1); + transform(shifted_message, block_nb); + rem_len = new_len % SHA384_512_BLOCK_SIZE; + memcpy(m_block, &shifted_message[block_nb << 7], rem_len); + m_len = rem_len; + m_tot_len += (block_nb + 1) << 7; +} + +void SHA512::final(unsigned char *digest) +{ + unsigned int block_nb; + unsigned int pm_len; + unsigned int len_b; + int i; + block_nb = 1 + ((SHA384_512_BLOCK_SIZE - 17) + < (m_len % SHA384_512_BLOCK_SIZE)); + len_b = (m_tot_len + m_len) << 3; + pm_len = block_nb << 7; + memset(m_block + m_len, 0, pm_len - m_len); + m_block[m_len] = 0x80; + SHA2_UNPACK32(len_b, m_block + pm_len - 4); + transform(m_block, block_nb); + for (i = 0 ; i < 8; i++) { + SHA2_UNPACK64(m_h[i], &digest[i << 3]); + } +} + +std::string sha512(std::string input) +{ + unsigned char digest[SHA512::DIGEST_SIZE]; + memset(digest,0,SHA512::DIGEST_SIZE); + SHA512 ctx = SHA512(); + ctx.init(); + ctx.update((unsigned char*)input.c_str(), input.length()); + ctx.final(digest); + + char buf[2*SHA512::DIGEST_SIZE+1]; + buf[2*SHA512::DIGEST_SIZE] = 0; + for (int i = 0; i < SHA512::DIGEST_SIZE; i++) + sprintf(buf+i*2, "%02x", digest[i]); + return std::string(buf); +} \ No newline at end of file diff --git a/source/common/sha512.h b/source/common/sha512.h new file mode 100644 index 0000000..72ce5a8 --- /dev/null +++ b/source/common/sha512.h @@ -0,0 +1,71 @@ +#ifndef SHA512_H +#define SHA512_H +#include + +class SHA512 +{ +protected: + typedef unsigned char uint8; + typedef unsigned int uint32; + typedef unsigned long long uint64; + + const static uint64 sha512_k[]; + static const unsigned int SHA384_512_BLOCK_SIZE = (1024/8); + +public: + void init(); + void update(const unsigned char *message, unsigned int len); + void final(unsigned char *digest); + static const unsigned int DIGEST_SIZE = ( 512 / 8); + +protected: + void transform(const unsigned char *message, unsigned int block_nb); + unsigned int m_tot_len; + unsigned int m_len; + unsigned char m_block[2 * SHA384_512_BLOCK_SIZE]; + uint64 m_h[8]; +}; + + +std::string sha512(std::string input); + +#define SHA2_SHFR(x, n) (x >> n) +#define SHA2_ROTR(x, n) ((x >> n) | (x << ((sizeof(x) << 3) - n))) +#define SHA2_ROTL(x, n) ((x << n) | (x >> ((sizeof(x) << 3) - n))) +#define SHA2_CH(x, y, z) ((x & y) ^ (~x & z)) +#define SHA2_MAJ(x, y, z) ((x & y) ^ (x & z) ^ (y & z)) +#define SHA512_F1(x) (SHA2_ROTR(x, 28) ^ SHA2_ROTR(x, 34) ^ SHA2_ROTR(x, 39)) +#define SHA512_F2(x) (SHA2_ROTR(x, 14) ^ SHA2_ROTR(x, 18) ^ SHA2_ROTR(x, 41)) +#define SHA512_F3(x) (SHA2_ROTR(x, 1) ^ SHA2_ROTR(x, 8) ^ SHA2_SHFR(x, 7)) +#define SHA512_F4(x) (SHA2_ROTR(x, 19) ^ SHA2_ROTR(x, 61) ^ SHA2_SHFR(x, 6)) +#define SHA2_UNPACK32(x, str) \ +{ \ + *((str) + 3) = (uint8) ((x) ); \ + *((str) + 2) = (uint8) ((x) >> 8); \ + *((str) + 1) = (uint8) ((x) >> 16); \ + *((str) + 0) = (uint8) ((x) >> 24); \ +} +#define SHA2_UNPACK64(x, str) \ +{ \ + *((str) + 7) = (uint8) ((x) ); \ + *((str) + 6) = (uint8) ((x) >> 8); \ + *((str) + 5) = (uint8) ((x) >> 16); \ + *((str) + 4) = (uint8) ((x) >> 24); \ + *((str) + 3) = (uint8) ((x) >> 32); \ + *((str) + 2) = (uint8) ((x) >> 40); \ + *((str) + 1) = (uint8) ((x) >> 48); \ + *((str) + 0) = (uint8) ((x) >> 56); \ +} +#define SHA2_PACK64(str, x) \ +{ \ + *(x) = ((uint64) *((str) + 7) ) \ + | ((uint64) *((str) + 6) << 8) \ + | ((uint64) *((str) + 5) << 16) \ + | ((uint64) *((str) + 4) << 24) \ + | ((uint64) *((str) + 3) << 32) \ + | ((uint64) *((str) + 2) << 40) \ + | ((uint64) *((str) + 1) << 48) \ + | ((uint64) *((str) + 0) << 56); \ +} + +#endif \ No newline at end of file diff --git a/source/common/string_util.cpp b/source/common/string_util.cpp new file mode 100644 index 0000000..df3790d --- /dev/null +++ b/source/common/string_util.cpp @@ -0,0 +1,529 @@ +/* + * Copyright 2013 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "string_util.h" +#include + +#ifdef _WINDOWS + #include + + #define snprintf _snprintf + #define strncasecmp _strnicmp + #define strcasecmp _stricmp + +#else + #include + #include +#include + +#endif + +#ifndef va_copy + #define va_copy(d,s) ((d) = (s)) +#endif + +// original source: +// https://github.com/facebook/folly/blob/master/folly/String.cpp +// +const std::string vStringFormat(const char* format, va_list args) +{ + std::string output; + va_list tmpargs; + + va_copy(tmpargs,args); + int characters_used = vsnprintf(nullptr, 0, format, tmpargs); + va_end(tmpargs); + + // Looks like we have a valid format string. + if (characters_used > 0) { + output.resize(characters_used + 1); + + va_copy(tmpargs,args); + characters_used = vsnprintf(&output[0], output.capacity(), format, tmpargs); + va_end(tmpargs); + + output.resize(characters_used); + + // We shouldn't have a format error by this point, but I can't imagine what error we + // could have by this point. Still, return empty string; + if (characters_used < 0) + output.clear(); + } + return output; +} + +const std::string str_tolower(std::string s) +{ + std::transform( + s.begin(), s.end(), s.begin(), + [](unsigned char c) { return ::tolower(c); } + ); + return s; +} + +std::vector split(std::string str_to_split, char delimiter) +{ + std::stringstream ss(str_to_split); + std::string item; + std::vector exploded_values; + while (std::getline(ss, item, delimiter)) { + exploded_values.push_back(item); + } + + return exploded_values; +} + +const std::string str_toupper(std::string s) +{ + std::transform( + s.begin(), s.end(), s.begin(), + [](unsigned char c) { return ::toupper(c); } + ); + return s; +} + +const std::string ucfirst(std::string s) +{ + std::string output = s; + if (!s.empty()) + output[0] = static_cast(::toupper(s[0])); + + return output; +} + +const std::string StringFormat(const char *format, ...) +{ + va_list args; + va_start(args, format); + std::string output = vStringFormat(format, args); + va_end(args); + return output; +} + +std::vector SplitString(const std::string &str, char delim) { + std::vector ret; + std::stringstream ss(str); + std::string item; + + while(std::getline(ss, item, delim)) { + ret.push_back(item); + } + + return ret; +} + +std::string implode(std::string glue, std::vector src) +{ + if (src.empty()) { + return {}; + } + + std::ostringstream output; + std::vector::iterator src_iter; + + for (src_iter = src.begin(); src_iter != src.end(); src_iter++) { + output << *src_iter << glue; + } + + std::string final_output = output.str(); + final_output.resize (output.str().size () - glue.size()); + + return final_output; +} + +std::string EscapeString(const std::string &s) { + std::string ret; + + size_t sz = s.length(); + for(size_t i = 0; i < sz; ++i) { + char c = s[i]; + switch(c) { + case '\x00': + ret += "\\x00"; + break; + case '\n': + ret += "\\n"; + break; + case '\r': + ret += "\\r"; + break; + case '\\': + ret += "\\\\"; + break; + case '\'': + ret += "\\'"; + break; + case '\"': + ret += "\\\""; + break; + case '\x1a': + ret += "\\x1a"; + break; + default: + ret.push_back(c); + break; + } + } + + return ret; +} + +std::string EscapeString(const char *src, size_t sz) { + std::string ret; + + for(size_t i = 0; i < sz; ++i) { + char c = src[i]; + switch(c) { + case '\x00': + ret += "\\x00"; + break; + case '\n': + ret += "\\n"; + break; + case '\r': + ret += "\\r"; + break; + case '\\': + ret += "\\\\"; + break; + case '\'': + ret += "\\'"; + break; + case '\"': + ret += "\\\""; + break; + case '\x1a': + ret += "\\x1a"; + break; + default: + ret.push_back(c); + break; + } + } + + return ret; +} + +bool StringIsNumber(const std::string &s) { + try { + auto r = stod(s); + return true; + } + catch (std::exception &) { + return false; + } +} + +void ToLowerString(std::string &s) { + std::transform(s.begin(), s.end(), s.begin(), ::tolower); +} + +void ToUpperString(std::string &s) { + std::transform(s.begin(), s.end(), s.begin(), ::toupper); +} + +std::string JoinString(const std::vector& ar, const std::string &delim) { + std::string ret; + for (size_t i = 0; i < ar.size(); ++i) { + if (i != 0) { + ret += delim; + } + + ret += ar[i]; + } + + return ret; +} + +void find_replace(std::string &string_subject, const std::string &search_string, const std::string &replace_string) +{ + if (string_subject.find(search_string) == std::string::npos) { + return; + } + + size_t start_pos = 0; + while((start_pos = string_subject.find(search_string, start_pos)) != std::string::npos) { + string_subject.replace(start_pos, search_string.length(), replace_string); + start_pos += replace_string.length(); + } + +} + +void ParseAccountString(const std::string &s, std::string &account, std::string &loginserver) +{ + auto split = SplitString(s, ':'); + if (split.size() == 2) { + loginserver = split[0]; + account = split[1]; + } + else if(split.size() == 1) { + account = split[0]; + } +} + +//Const char based + +// normal strncpy doesnt put a null term on copied strings, this one does +// ref: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wcecrt/htm/_wcecrt_strncpy_wcsncpy.asp +char* strn0cpy(char* dest, const char* source, uint32 size) { + if (!dest) + return 0; + if (size == 0 || source == 0) { + dest[0] = 0; + return dest; + } + strncpy(dest, source, size); + dest[size - 1] = 0; + return dest; +} + +// String N w/null Copy Truncated? +// return value =true if entire string(source) fit, false if it was truncated +bool strn0cpyt(char* dest, const char* source, uint32 size) { + if (!dest) + return 0; + if (size == 0 || source == 0) { + dest[0] = 0; + return false; + } + strncpy(dest, source, size); + dest[size - 1] = 0; + return (bool)(source[strlen(dest)] == 0); +} + +const char *MakeLowerString(const char *source) { + static char str[128]; + if (!source) + return nullptr; + MakeLowerString(source, str); + return str; +} + +void MakeLowerString(const char *source, char *target) { + if (!source || !target) { + *target = 0; + return; + } + while (*source) + { + *target = tolower(*source); + target++; source++; + } + *target = 0; +} + +int MakeAnyLenString(char** ret, const char* format, ...) { + int buf_len = 128; + int chars = -1; + va_list argptr, tmpargptr; + va_start(argptr, format); + while (chars == -1 || chars >= buf_len) { + safe_delete_array(*ret); + if (chars == -1) + buf_len *= 2; + else + buf_len = chars + 1; + *ret = new char[buf_len]; + va_copy(tmpargptr, argptr); + chars = vsnprintf(*ret, buf_len, format, tmpargptr); + } + va_end(argptr); + return chars; +} + +uint32 AppendAnyLenString(char** ret, uint32* bufsize, uint32* strlen, const char* format, ...) { + if (*bufsize == 0) + *bufsize = 256; + if (*ret == 0) + *strlen = 0; + int chars = -1; + char* oldret = 0; + va_list argptr, tmpargptr; + va_start(argptr, format); + while (chars == -1 || chars >= (int32)(*bufsize - *strlen)) { + if (chars == -1) + *bufsize += 256; + else + *bufsize += chars + 25; + oldret = *ret; + *ret = new char[*bufsize]; + if (oldret) { + if (*strlen) + memcpy(*ret, oldret, *strlen); + safe_delete_array(oldret); + } + va_copy(tmpargptr, argptr); + chars = vsnprintf(&(*ret)[*strlen], (*bufsize - *strlen), format, tmpargptr); + } + va_end(argptr); + *strlen += chars; + return *strlen; +} + +uint32 hextoi(const char* num) { + if (num == nullptr) + return 0; + + int len = strlen(num); + if (len < 3) + return 0; + + if (num[0] != '0' || (num[1] != 'x' && num[1] != 'X')) + return 0; + + uint32 ret = 0; + int mul = 1; + for (int i = len - 1; i >= 2; i--) { + if (num[i] >= 'A' && num[i] <= 'F') + ret += ((num[i] - 'A') + 10) * mul; + else if (num[i] >= 'a' && num[i] <= 'f') + ret += ((num[i] - 'a') + 10) * mul; + else if (num[i] >= '0' && num[i] <= '9') + ret += (num[i] - '0') * mul; + else + return 0; + mul *= 16; + } + return ret; +} + +uint64 hextoi64(const char* num) { + if (num == nullptr) + return 0; + + int len = strlen(num); + if (len < 3) + return 0; + + if (num[0] != '0' || (num[1] != 'x' && num[1] != 'X')) + return 0; + + uint64 ret = 0; + int mul = 1; + for (int i = len - 1; i >= 2; i--) { + if (num[i] >= 'A' && num[i] <= 'F') + ret += ((num[i] - 'A') + 10) * mul; + else if (num[i] >= 'a' && num[i] <= 'f') + ret += ((num[i] - 'a') + 10) * mul; + else if (num[i] >= '0' && num[i] <= '9') + ret += (num[i] - '0') * mul; + else + return 0; + mul *= 16; + } + return ret; +} + +bool atobool(const char* iBool) { + + if (iBool == nullptr) + return false; + if (!strcasecmp(iBool, "true")) + return true; + if (!strcasecmp(iBool, "false")) + return false; + if (!strcasecmp(iBool, "yes")) + return true; + if (!strcasecmp(iBool, "no")) + return false; + if (!strcasecmp(iBool, "on")) + return true; + if (!strcasecmp(iBool, "off")) + return false; + if (!strcasecmp(iBool, "enable")) + return true; + if (!strcasecmp(iBool, "disable")) + return false; + if (!strcasecmp(iBool, "enabled")) + return true; + if (!strcasecmp(iBool, "disabled")) + return false; + if (!strcasecmp(iBool, "y")) + return true; + if (!strcasecmp(iBool, "n")) + return false; + if (atoi(iBool)) + return true; + return false; +} + +// removes the crap and turns the underscores into spaces. +char *CleanMobName(const char *in, char *out) +{ + unsigned i, j; + + for (i = j = 0; i < strlen(in); i++) + { + // convert _ to space.. any other conversions like this? I *think* this + // is the only non alpha char that's not stripped but converted. + if (in[i] == '_') + { + out[j++] = ' '; + } + else + { + if (isalpha(in[i]) || (in[i] == '`')) // numbers, #, or any other crap just gets skipped + out[j++] = in[i]; + } + } + out[j] = 0; // terimnate the string before returning it + return out; +} + + +void RemoveApostrophes(std::string &s) +{ + for (unsigned int i = 0; i < s.length(); ++i) + if (s[i] == '\'') + s[i] = '_'; +} + +char *RemoveApostrophes(const char *s) +{ + auto NewString = new char[strlen(s) + 1]; + + strcpy(NewString, s); + + for (unsigned int i = 0; i < strlen(NewString); ++i) + if (NewString[i] == '\'') + NewString[i] = '_'; + + return NewString; +} + +const char *ConvertArray(int input, char *returnchar) +{ + sprintf(returnchar, "%i", input); + return returnchar; +} + +const char *ConvertArrayF(float input, char *returnchar) +{ + sprintf(returnchar, "%0.2f", input); + return returnchar; +} + +bool isAlphaNumeric(const char *text) +{ + for (unsigned int charIndex = 0; charIndex 'z') && + (text[charIndex] < 'A' || text[charIndex] > 'Z') && + (text[charIndex] < '0' || text[charIndex] > '9')) + return false; + } + + return true; +} diff --git a/source/common/string_util.h b/source/common/string_util.h new file mode 100644 index 0000000..037d6a2 --- /dev/null +++ b/source/common/string_util.h @@ -0,0 +1,193 @@ +/* + * Copyright 2013 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef _STRINGUTIL_H_ +#define _STRINGUTIL_H_ + +#include +#include +#include +#include +#include + +#ifndef _WIN32 +// this doesn't appear to affect linux-based systems..need feedback for _WIN64 +#include +#endif + +#ifdef _WINDOWS +#include +#include +#include +#endif + +#include "types.h" + +//std::string based +const std::string str_tolower(std::string s); +const std::string str_toupper(std::string s); +const std::string ucfirst(std::string s); +std::vector split(std::string str_to_split, char delimiter); +const std::string StringFormat(const char* format, ...); +const std::string vStringFormat(const char* format, va_list args); +std::string implode(std::string glue, std::vector src); + +/** + * @param str + * @param chars + * @return + */ +inline std::string <rim(std::string &str, const std::string &chars = "\t\n\v\f\r ") +{ + str.erase(0, str.find_first_not_of(chars)); + return str; +} + +/** + * @param str + * @param chars + * @return + */ +inline std::string &rtrim(std::string &str, const std::string &chars = "\t\n\v\f\r ") +{ + str.erase(str.find_last_not_of(chars) + 1); + return str; +} + +/** + * @param str + * @param chars + * @return + */ +inline std::string &trim(std::string &str, const std::string &chars = "\t\n\v\f\r ") +{ + return ltrim(rtrim(str, chars), chars); +} + +template +std::string implode(const std::string &glue, const std::pair &encapsulation, const std::vector &src) +{ + if (src.empty()) { + return {}; + } + + std::ostringstream oss; + + for (const T &src_iter : src) { + oss << encapsulation.first << src_iter << encapsulation.second << glue; + } + + std::string output(oss.str()); + output.resize(output.size() - glue.size()); + + return output; +} + +// _WIN32 builds require that #include be included in whatever code file the invocation is made from (no header files) +template +std::vector join_pair(const std::string &glue, const std::pair &encapsulation, const std::vector> &src) +{ + if (src.empty()) { + return {}; + } + + std::vector output; + + for (const std::pair &src_iter : src) { + output.push_back( + + fmt::format( + "{}{}{}{}{}{}{}", + encapsulation.first, + src_iter.first, + encapsulation.second, + glue, + encapsulation.first, + src_iter.second, + encapsulation.second + ) + ); + } + + return output; +} + +// _WIN32 builds require that #include be included in whatever code file the invocation is made from (no header files) +template +std::vector join_tuple(const std::string &glue, const std::pair &encapsulation, const std::vector> &src) +{ + if (src.empty()) { + return {}; + } + + std::vector output; + + for (const std::tuple &src_iter : src) { + + output.push_back( + + fmt::format( + "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", + encapsulation.first, + std::get<0>(src_iter), + encapsulation.second, + glue, + encapsulation.first, + std::get<1>(src_iter), + encapsulation.second, + glue, + encapsulation.first, + std::get<2>(src_iter), + encapsulation.second, + glue, + encapsulation.first, + std::get<3>(src_iter), + encapsulation.second + ) + ); + } + + return output; +} + +std::vector SplitString(const std::string &s, char delim); +std::string EscapeString(const char *src, size_t sz); +std::string EscapeString(const std::string &s); +bool StringIsNumber(const std::string &s); +void ToLowerString(std::string &s); +void ToUpperString(std::string &s); +std::string JoinString(const std::vector& ar, const std::string &delim); +void find_replace(std::string& string_subject, const std::string& search_string, const std::string& replace_string); +void ParseAccountString(const std::string &s, std::string &account, std::string &loginserver); + +//const char based + +bool atobool(const char* iBool); +bool isAlphaNumeric(const char *text); +bool strn0cpyt(char* dest, const char* source, uint32 size); +char *CleanMobName(const char *in, char *out); +char *RemoveApostrophes(const char *s); +char* strn0cpy(char* dest, const char* source, uint32 size); +const char *ConvertArray(int input, char *returnchar); +const char *ConvertArrayF(float input, char *returnchar); +const char *MakeLowerString(const char *source); +int MakeAnyLenString(char** ret, const char* format, ...); +uint32 AppendAnyLenString(char** ret, uint32* bufsize, uint32* strlen, const char* format, ...); +uint32 hextoi(const char* num); +uint64 hextoi64(const char* num); +void MakeLowerString(const char *source, char *target); +void RemoveApostrophes(std::string &s); + +#endif diff --git a/source/common/timer.cpp b/source/common/timer.cpp new file mode 100644 index 0000000..e9c08ef --- /dev/null +++ b/source/common/timer.cpp @@ -0,0 +1,207 @@ +/* + 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 . +*/ +#include "../common/debug.h" +// Disgrace: for windows compile +#ifndef WIN32 + #include +#else + #include +#endif + +#include +using namespace std; + +#include "timer.h" + +int32 started_unix_timestamp = 0; +int32 current_time = 0; +int32 last_time = 0; + +Timer::Timer(){ + timer_time = 30000; //default to 30 seconds + start_time = current_time; + set_at_trigger = timer_time; + pUseAcurateTiming = false; + enabled = false; +} +Timer::Timer(int32 in_timer_time, bool iUseAcurateTiming) { + timer_time = in_timer_time; + start_time = current_time; + set_at_trigger = timer_time; + pUseAcurateTiming = iUseAcurateTiming; + if (timer_time == 0) { + enabled = false; + } + else { + enabled = true; + } +} + +Timer::Timer(int32 start, int32 timer, bool iUseAcurateTiming = false) { + timer_time = timer; + start_time = start; + set_at_trigger = timer_time; + pUseAcurateTiming = iUseAcurateTiming; + if (timer_time == 0) { + enabled = false; + } + else { + enabled = true; + } +} + +/* Reimplemented for MSVC - Bounce */ +#ifdef WIN32 +int gettimeofday (timeval *tp, ...) +{ + timeb tb; + + ftime (&tb); + + tp->tv_sec = tb.time; + tp->tv_usec = tb.millitm * 1000; + + return 0; +} +#endif + +/* This function checks if the timer triggered */ +bool Timer::Check(bool iReset) +{ + if (enabled && current_time-start_time > timer_time) { + if (iReset) { + if (pUseAcurateTiming) + start_time += timer_time; + else + start_time = current_time; // Reset timer + timer_time = set_at_trigger; + } + return true; + } + + return false; +} + +/* This function disables the timer */ +void Timer::Disable() { + enabled = false; +} + +void Timer::Enable() { + enabled = true; +} + +/* This function set the timer and restart it */ +void Timer::Start(int32 set_timer_time, bool ChangeResetTimer) { + start_time = current_time; + enabled = true; + if (set_timer_time != 0) + { + timer_time = set_timer_time; + if (ChangeResetTimer) + set_at_trigger = set_timer_time; + } +} + +/* This timer updates the timer without restarting it */ +void Timer::SetTimer(int32 set_timer_time) { + /* If we were disabled before => restart the timer */ + if (!enabled) { + start_time = current_time; + enabled = true; + } + if (set_timer_time != 0) { + timer_time = set_timer_time; + set_at_trigger = set_timer_time; + } +} + +int32 Timer::GetElapsedTime(){ + if (enabled) { + return current_time - start_time; + } + else { + return 0xFFFFFFFF; + } +} + +int32 Timer::GetRemainingTime() { + if (enabled) { + if (current_time-start_time > timer_time) + return 0; + else + return (start_time + timer_time) - current_time; + } + else { + return 0xFFFFFFFF; + } +} + +void Timer::SetAtTrigger(int32 in_set_at_trigger, bool iEnableIfDisabled) { + set_at_trigger = in_set_at_trigger; + if (!Enabled() && iEnableIfDisabled) { + Enable(); + } +} + +void Timer::Trigger() +{ + enabled = true; + + timer_time = set_at_trigger; + start_time = current_time-timer_time-1; +} + +const int32& Timer::GetCurrentTime2() +{ + return current_time; +} + +const int32& Timer::SetCurrentTime() +{ + struct timeval read_time; + int32 this_time; + + gettimeofday(&read_time,0); + if(started_unix_timestamp == 0) + started_unix_timestamp = read_time.tv_sec; + + this_time = (read_time.tv_sec - started_unix_timestamp) * 1000 + read_time.tv_usec / 1000; + + if (last_time == 0) + { + current_time = 0; + } + else + { + current_time += this_time - last_time; + } + + last_time = this_time; + +// cerr << "Current time:" << current_time << endl; + return current_time; +} + +int32 Timer::GetUnixTimeStamp(){ + struct timeval read_time; + gettimeofday(&read_time,0); + return read_time.tv_sec; +} diff --git a/source/common/timer.h b/source/common/timer.h new file mode 100644 index 0000000..a70a2d2 --- /dev/null +++ b/source/common/timer.h @@ -0,0 +1,88 @@ +/* + 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 . +*/ +#ifndef TIMER_H +#define TIMER_H + +#include "types.h" +#include + +// Disgrace: for windows compile +#ifdef WIN32 + #include + #include + int gettimeofday (timeval *tp, ...); +#endif + +class Timer +{ +public: + Timer(); + Timer(int32 timer_time, bool iUseAcurateTiming = false); + Timer(int32 start, int32 timer, bool iUseAcurateTiming); + ~Timer() { } + + bool Check(bool iReset = true); + void Enable(); + void Disable(); + void Start(int32 set_timer_time=0, bool ChangeResetTimer = true); + void SetTimer(int32 set_timer_time=0); + int32 GetRemainingTime(); + int32 GetElapsedTime(); + inline const int32& GetTimerTime() { return timer_time; } + inline const int32& GetSetAtTrigger() { return set_at_trigger; } + void Trigger(); + void SetAtTrigger(int32 set_at_trigger, bool iEnableIfDisabled = false); + + inline bool Enabled() { return enabled; } + inline int32 GetStartTime() { return(start_time); } + inline int32 GetDuration() { return(timer_time); } + + static const int32& SetCurrentTime(); + static const int32& GetCurrentTime2(); + static int32 GetUnixTimeStamp(); + +private: + int32 start_time; + int32 timer_time; + bool enabled; + int32 set_at_trigger; + + // Tells the timer to be more acurate about happening every X ms. + // Instead of Check() setting the start_time = now, + // it it sets it to start_time += timer_time + bool pUseAcurateTiming; + +// static int32 current_time; +// static int32 last_time; +}; + +struct BenchTimer +{ + typedef std::chrono::high_resolution_clock clock; + + BenchTimer() : start_time(clock::now()) {} + void reset() { start_time = clock::now(); } + // this is seconds + double elapsed() { return std::chrono::duration(clock::now() - start_time).count(); } +private: + std::chrono::time_point start_time; +}; + +#endif diff --git a/source/common/types.h b/source/common/types.h new file mode 100644 index 0000000..c65ccf1 --- /dev/null +++ b/source/common/types.h @@ -0,0 +1,191 @@ +/* + 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 . +*/ +#ifndef TYPES_H +#define TYPES_H + +#include + +using namespace std; + +//atoi is not int32 or uint32 safe!!!! +#define atoul(str) strtoul(str, NULL, 10) +#ifdef WIN32 + #define atoi64(str) _atoi64(str) +#else + #define atoi64(str) strtoll(str, NULL, 10) +#endif +typedef unsigned char int8; +typedef unsigned short int16; +typedef unsigned int int32; + +typedef unsigned char uint8; +typedef signed char sint8; +typedef unsigned short uint16; +typedef signed short sint16; +typedef unsigned int uint32; +typedef signed int sint32; + +#ifdef WIN32 + #if defined(_INTEGRAL_MAX_BITS) && _INTEGRAL_MAX_BITS >= 64 + typedef unsigned __int64 int64; + typedef unsigned __int64 uint64; + typedef signed __int64 sint64; + #else + #error __int64 not supported + #endif +#else +typedef unsigned long long int64; +typedef unsigned long long uint64; +typedef signed long long sint64; +//typedef __u64 int64; +//typedef __u64 uint64; +//typedef __s64 sint64; +#endif + +typedef unsigned long ulong; +typedef unsigned short ushort; +typedef unsigned char uchar; + +#ifdef WIN32 + #define snprintf _snprintf + #define vsnprintf _vsnprintf + #define strncasecmp _strnicmp + #define strcasecmp _stricmp + typedef void ThreadReturnType; +// #define THREAD_RETURN(x) return; + #define THREAD_RETURN(x) _endthread(); return; +#else + typedef void* ThreadReturnType; + typedef int SOCKET; + #define THREAD_RETURN(x) return(x); +#endif + +#define safe_delete(d) if(d) { delete d; d=0; } +#define safe_delete_array(d) if(d) { delete[] d; d=0; } +#define L32(i) ((int32) i) +#define H32(i) ((int32) (i >> 32)) +#define L16(i) ((int16) i) + +#ifndef WIN32 +// More WIN32 compatability + typedef unsigned long DWORD; + typedef unsigned char BYTE; + typedef char CHAR; + typedef unsigned short WORD; + typedef float FLOAT; + typedef FLOAT *PFLOAT; + typedef BYTE *PBYTE,*LPBYTE; + typedef int *PINT,*LPINT; + typedef WORD *PWORD,*LPWORD; + typedef long *LPLONG, LONG; + typedef DWORD *PDWORD,*LPDWORD; + typedef int INT; + typedef unsigned int UINT,*PUINT,*LPUINT; +#endif + + +#ifdef WIN32 +#define DLLFUNC extern "C" __declspec(dllexport) +#else +#define DLLFUNC extern "C" +#endif + + +#pragma pack(1) +struct uint16_breakdown { + union { + uint16 all; + struct { + uint8 b1; + uint8 b2; + } bytes; + }; + inline uint16& operator=(const uint16& val) { return (all=val); } + inline uint16* operator&() { return &all; } + inline operator uint16&() { return all; } + inline uint8& b1() { return bytes.b1; } + inline uint8& b2() { return bytes.b2; } +}; + +struct uint32_breakdown { + union { + uint32 all; + struct { + uint16 w1; + uint16 w2; + } words; + struct { + uint8 b1; + union { + struct { + uint8 b2; + uint8 b3; + } middle; + uint16 w2_3; // word bytes 2 to 3 + }; + uint8 b4; + } bytes; + }; + inline uint32& operator=(const uint32& val) { return (all=val); } + inline uint32* operator&() { return &all; } + inline operator uint32&() { return all; } + + inline uint16& w1() { return words.w1; } + inline uint16& w2() { return words.w2; } + inline uint16& w2_3() { return bytes.w2_3; } + inline uint8& b1() { return bytes.b1; } + inline uint8& b2() { return bytes.middle.b2; } + inline uint8& b3() { return bytes.middle.b3; } + inline uint8& b4() { return bytes.b4; } +}; + +struct EQ2_32BitString{ + int32 size; + string data; +}; +struct EQ2_16BitString{ + int16 size; + string data; +}; +struct EQ2_8BitString{ + int8 size; + string data; +}; + +struct EQ2_Color{ + int8 red; + int8 green; + int8 blue; +}; + +struct WorldTime{ + int16 year; + int month; + int day; + int hour; + int minute; +}; + +typedef enum QUERY_TYPE{ Q_SELECT, Q_UPDATE, Q_REPLACE, Q_INSERT, Q_DELETE, Q_DBMS} QUERY_TYPE; + +#pragma pack() + + +#endif diff --git a/source/common/unix.cpp b/source/common/unix.cpp new file mode 100644 index 0000000..6476c3a --- /dev/null +++ b/source/common/unix.cpp @@ -0,0 +1,45 @@ +/* + 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 . +*/ +#include "unix.h" +#include +#include + +void Sleep(unsigned int x) { + if (x > 0) + usleep(x*1000); +} + +char* strupr(char* tmp) { + int l = strlen(tmp); + for (int x = 0; x < l; x++) { + tmp[x] = toupper(tmp[x]); + } + return tmp; +} + +char* strlwr(char* tmp) { + int l = strlen(tmp); + for (int x = 0; x < l; x++) { + tmp[x] = tolower(tmp[x]); + } + return tmp; +} + + diff --git a/source/common/unix.h b/source/common/unix.h new file mode 100644 index 0000000..82560a1 --- /dev/null +++ b/source/common/unix.h @@ -0,0 +1,34 @@ +/* + 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 . +*/ +#ifndef WIN32 +#ifndef __UNIX_H__ +#define __UNIX_H__ + #ifndef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP + #define PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP {0, 0, 0, PTHREAD_MUTEX_RECURSIVE_NP, __LOCK_INITIALIZER} + #endif +#include + +typedef int SOCKET; + +void Sleep(unsigned int x); +char* strupr(char* tmp); +char* strlwr(char* tmp); +#endif +#endif diff --git a/source/common/version.h b/source/common/version.h new file mode 100644 index 0000000..db48edb --- /dev/null +++ b/source/common/version.h @@ -0,0 +1,56 @@ +/* + 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 . +*/ + +#include "LogTypes.h" + +#ifndef VERSION_H +#define VERSION_H + +#define CURRENT_DATABASE_MINORVERSION 43 +#define CURRENT_DATABASE_MAJORVERSION 730 +#if defined(LOGIN) + #define EQ2EMU_MODULE "EQ2EMu LoginServer" +#elif defined(PATCHER) + #define EQ2EMU_MODULE "EQ2EMu PatchServer" +#elif defined(CHAT) + #define EQ2EMU_MODULE "EQ2EMu ChatServer" +#elif defined(ZONE) + #define EQ2EMU_MODULE "EQ2EMu ZoneServer" +#else + #define EQ2EMU_MODULE "EQ2EMu WorldServer" +#endif + +#if defined(LOGIN) +#define CURRENT_VERSION "0.9.7-thetascorpii-DR2" +#elif defined(WORLD) +#define CURRENT_VERSION "0.9.7-thetascorpii-DR2" +#else +#define CURRENT_VERSION "0.9.7-thetascorpii-DR2" +#endif + +#define COMPILE_DATE __DATE__ +#define COMPILE_TIME __TIME__ +#ifndef WIN32 + #define LAST_MODIFIED __TIME__ +#else + #define LAST_MODIFIED __TIMESTAMP__ +#endif + +#endif \ No newline at end of file diff --git a/source/common/xmlParser.cpp b/source/common/xmlParser.cpp new file mode 100644 index 0000000..e0f7f33 --- /dev/null +++ b/source/common/xmlParser.cpp @@ -0,0 +1,2974 @@ +/** + **************************************************************************** + *

XML.c - implementation file for basic XML parser written in ANSI C++ + * for portability. It works by using recursion and a node tree for breaking + * down the elements of an XML document.

+ * + * @version V2.44 + * @author Frank Vanden Berghen + * + * NOTE: + * + * If you add "#define STRICT_PARSING", on the first line of this file + * the parser will see the following XML-stream: + * some textother text + * as an error. Otherwise, this tring will be equivalent to: + * some textother text + * + * NOTE: + * + * If you add "#define APPROXIMATE_PARSING" on the first line of this file + * the parser will see the following XML-stream: + * + * + * + * as equivalent to the following XML-stream: + * + * + * + * This can be useful for badly-formed XML-streams but prevent the use + * of the following XML-stream (problem is: tags at contiguous levels + * have the same names): + * + * + * + * + * + * + * NOTE: + * + * If you add "#define _XMLPARSER_NO_MESSAGEBOX_" on the first line of this file + * the "openFileHelper" function will always display error messages inside the + * console instead of inside a message-box-window. Message-box-windows are + * available on windows 9x/NT/2000/XP/Vista only. + * + * Copyright (c) 2002, Frank Vanden Berghen - All rights reserved. + * Commercialized by Business-Insight + * See the file "AFPL-license.txt about the licensing terms + * + **************************************************************************** + */ +#ifndef _CRT_SECURE_NO_DEPRECATE +#define _CRT_SECURE_NO_DEPRECATE +#endif +#include "xmlParser.h" +#ifdef _XMLWINDOWS +//#ifdef _DEBUG +//#define _CRTDBG_MAP_ALLOC +//#include +//#endif +#define WIN32_LEAN_AND_MEAN +#include // to have IsTextUnicode, MultiByteToWideChar, WideCharToMultiByte to handle unicode files + // to have "MessageBoxA" to display error messages for openFilHelper +#endif + +#include +#include +#include +#include +#include + +XMLCSTR XMLNode::getVersion() { return _CXML("v2.44"); } +void freeXMLString(XMLSTR t){if(t)free(t);} + +static XMLNode::XMLCharEncoding characterEncoding=XMLNode::char_encoding_UTF8; +static char guessWideCharChars=1, dropWhiteSpace=1, removeCommentsInMiddleOfText=1; + +inline int mmin( const int t1, const int t2 ) { return t1 < t2 ? t1 : t2; } + +// You can modify the initialization of the variable "XMLClearTags" below +// to change the clearTags that are currently recognized by the library. +// The number on the second columns is the length of the string inside the +// first column. +// The "") }, + { _CXML("") }, + { _CXML("") }, + { _CXML("
")    ,5,  _CXML("
") }, +// { _CXML("")}, + { NULL ,0, NULL } +}; + +// You can modify the initialization of the variable "XMLEntities" below +// to change the character entities that are currently recognized by the library. +// The number on the second columns is the length of the string inside the +// first column. Additionally, the syntaxes " " and " " are recognized. +typedef struct { XMLCSTR s; int l; XMLCHAR c;} XMLCharacterEntity; +static XMLCharacterEntity XMLEntities[] = +{ + { _CXML("&" ), 5, _CXML('&' )}, + { _CXML("<" ), 4, _CXML('<' )}, + { _CXML(">" ), 4, _CXML('>' )}, + { _CXML("""), 6, _CXML('\"')}, + { _CXML("'"), 6, _CXML('\'')}, + { NULL , 0, '\0' } +}; + +// When rendering the XMLNode to a string (using the "createXMLString" function), +// you can ask for a beautiful formatting. This formatting is using the +// following indentation character: +#define INDENTCHAR _CXML('\t') + +// The following function parses the XML errors into a user friendly string. +// You can edit this to change the output language of the library to something else. +XMLCSTR XMLNode::getError(XMLError xerror) +{ + switch (xerror) + { + case eXMLErrorNone: return _CXML("No error"); + case eXMLErrorMissingEndTag: return _CXML("Warning: Unmatched end tag"); + case eXMLErrorNoXMLTagFound: return _CXML("Warning: No XML tag found"); + case eXMLErrorEmpty: return _CXML("Error: No XML data"); + case eXMLErrorMissingTagName: return _CXML("Error: Missing start tag name"); + case eXMLErrorMissingEndTagName: return _CXML("Error: Missing end tag name"); + case eXMLErrorUnmatchedEndTag: return _CXML("Error: Unmatched end tag"); + case eXMLErrorUnmatchedEndClearTag: return _CXML("Error: Unmatched clear tag end"); + case eXMLErrorUnexpectedToken: return _CXML("Error: Unexpected token found"); + case eXMLErrorNoElements: return _CXML("Error: No elements found"); + case eXMLErrorFileNotFound: return _CXML("Error: File not found"); + case eXMLErrorFirstTagNotFound: return _CXML("Error: First Tag not found"); + case eXMLErrorUnknownCharacterEntity:return _CXML("Error: Unknown character entity"); + case eXMLErrorCharacterCodeAbove255: return _CXML("Error: Character code above 255 is forbidden in MultiByte char mode."); + case eXMLErrorCharConversionError: return _CXML("Error: unable to convert between WideChar and MultiByte chars"); + case eXMLErrorCannotOpenWriteFile: return _CXML("Error: unable to open file for writing"); + case eXMLErrorCannotWriteFile: return _CXML("Error: cannot write into file"); + + case eXMLErrorBase64DataSizeIsNotMultipleOf4: return _CXML("Warning: Base64-string length is not a multiple of 4"); + case eXMLErrorBase64DecodeTruncatedData: return _CXML("Warning: Base64-string is truncated"); + case eXMLErrorBase64DecodeIllegalCharacter: return _CXML("Error: Base64-string contains an illegal character"); + case eXMLErrorBase64DecodeBufferTooSmall: return _CXML("Error: Base64 decode output buffer is too small"); + }; + return _CXML("Unknown"); +} + +///////////////////////////////////////////////////////////////////////// +// Here start the abstraction layer to be OS-independent // +///////////////////////////////////////////////////////////////////////// + +// Here is an abstraction layer to access some common string manipulation functions. +// The abstraction layer is currently working for gcc, Microsoft Visual Studio 6.0, +// Microsoft Visual Studio .NET, CC (sun compiler) and Borland C++. +// If you plan to "port" the library to a new system/compiler, all you have to do is +// to edit the following lines. +#ifdef XML_NO_WIDE_CHAR +char myIsTextWideChar(const void *b, int len) { return FALSE; } +#else + #if defined (UNDER_CE) || !defined(_XMLWINDOWS) + char myIsTextWideChar(const void *b, int len) // inspired by the Wine API: RtlIsTextUnicode + { +#ifdef sun + // for SPARC processors: wchar_t* buffers must always be alligned, otherwise it's a char* buffer. + if ((((unsigned long)b)%sizeof(wchar_t))!=0) return FALSE; +#endif + const wchar_t *s=(const wchar_t*)b; + + // buffer too small: + if (len<(int)sizeof(wchar_t)) return FALSE; + + // odd length test + if (len&1) return FALSE; + + /* only checks the first 256 characters */ + len=mmin(256,len/sizeof(wchar_t)); + + // Check for the special byte order: + if (*((unsigned short*)s) == 0xFFFE) return TRUE; // IS_TEXT_UNICODE_REVERSE_SIGNATURE; + if (*((unsigned short*)s) == 0xFEFF) return TRUE; // IS_TEXT_UNICODE_SIGNATURE + + // checks for ASCII characters in the UNICODE stream + int i,stats=0; + for (i=0; ilen/2) return TRUE; + + // Check for UNICODE NULL chars + for (i=0; i + static inline int xstrnicmp(XMLCSTR c1, XMLCSTR c2, int l) { return wsncasecmp(c1,c2,l);} + static inline int xstrncmp(XMLCSTR c1, XMLCSTR c2, int l) { return wsncmp(c1,c2,l);} + static inline int xstricmp(XMLCSTR c1, XMLCSTR c2) { return wscasecmp(c1,c2); } + #else + static inline int xstrncmp(XMLCSTR c1, XMLCSTR c2, int l) { return wcsncmp(c1,c2,l);} + #ifdef __linux__ + // for gcc/linux + static inline int xstrnicmp(XMLCSTR c1, XMLCSTR c2, int l) { return wcsncasecmp(c1,c2,l);} + static inline int xstricmp(XMLCSTR c1, XMLCSTR c2) { return wcscasecmp(c1,c2); } + #else + #include + // for gcc/non-linux (MacOS X 10.3, FreeBSD 6.0, NetBSD 3.0, OpenBSD 3.8, AIX 4.3.2, HP-UX 11, IRIX 6.5, OSF/1 5.1, Cygwin, mingw) + static inline int xstricmp(XMLCSTR c1, XMLCSTR c2) + { + wchar_t left,right; + do + { + left=towlower(*c1++); right=towlower(*c2++); + } while (left&&(left==right)); + return (int)left-(int)right; + } + static inline int xstrnicmp(XMLCSTR c1, XMLCSTR c2, int l) + { + wchar_t left,right; + while(l--) + { + left=towlower(*c1++); right=towlower(*c2++); + if ((!left)||(left!=right)) return (int)left-(int)right; + } + return 0; + } + #endif + #endif + static inline XMLSTR xstrstr(XMLCSTR c1, XMLCSTR c2) { return (XMLSTR)wcsstr(c1,c2); } + static inline XMLSTR xstrcpy(XMLSTR c1, XMLCSTR c2) { return (XMLSTR)wcscpy(c1,c2); } + static inline FILE *xfopen(XMLCSTR filename,XMLCSTR mode) + { + char *filenameAscii=myWideCharToMultiByte(filename); + FILE *f; + if (mode[0]==_CXML('r')) f=fopen(filenameAscii,"rb"); + else f=fopen(filenameAscii,"wb"); + free(filenameAscii); + return f; + } + #else + static inline FILE *xfopen(XMLCSTR filename,XMLCSTR mode) { return fopen(filename,mode); } + static inline int xstrlen(XMLCSTR c) { return strlen(c); } + static inline int xstrnicmp(XMLCSTR c1, XMLCSTR c2, int l) { return strncasecmp(c1,c2,l);} + static inline int xstrncmp(XMLCSTR c1, XMLCSTR c2, int l) { return strncmp(c1,c2,l);} + static inline int xstricmp(XMLCSTR c1, XMLCSTR c2) { return strcasecmp(c1,c2); } + static inline XMLSTR xstrstr(XMLCSTR c1, XMLCSTR c2) { return (XMLSTR)strstr(c1,c2); } + static inline XMLSTR xstrcpy(XMLSTR c1, XMLCSTR c2) { return (XMLSTR)strcpy(c1,c2); } + #endif + static inline int _strnicmp(const char *c1,const char *c2, int l) { return strncasecmp(c1,c2,l);} +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// the "xmltoc,xmltob,xmltoi,xmltol,xmltof,xmltoa" functions // +/////////////////////////////////////////////////////////////////////////////// +// These 6 functions are not used inside the XMLparser. +// There are only here as "convenience" functions for the user. +// If you don't need them, you can delete them without any trouble. +#ifdef _XMLWIDECHAR + #ifdef _XMLWINDOWS + // for Microsoft Visual Studio 6.0 and Microsoft Visual Studio .NET and Borland C++ Builder 6.0 + char xmltob(XMLCSTR t,char v){ if (t&&(*t)) return (char)_wtoi(t); return v; } + int xmltoi(XMLCSTR t,int v){ if (t&&(*t)) return _wtoi(t); return v; } + long long xmltol(XMLCSTR t,long long v){ if (t&&(*t)) return _wtoi64(t); return v; } + double xmltof(XMLCSTR t,double v){ if (t&&(*t)) swscanf(t, L"%lf", &v); /*v=_wtof(t);*/ return v; } + #else + #ifdef sun + // for CC + #include + char xmltob(XMLCSTR t,char v){ if (t) return (char)wstol(t,NULL,10); return v; } + int xmltoi(XMLCSTR t,int v){ if (t) return (int)wstol(t,NULL,10); return v; } + long long xmltol(XMLCSTR t,long long v){ if (t) return wstol(t,NULL,10); return v; } + #else + // for gcc + char xmltob(XMLCSTR t,char v){ if (t) return (char)wcstol(t,NULL,10); return v; } + int xmltoi(XMLCSTR t,int v){ if (t) return (int)wcstol(t,NULL,10); return v; } + long long xmltol(XMLCSTR t,long long v){ if (t) return wcstol(t,NULL,10); return v; } + #endif + double xmltof(XMLCSTR t,double v){ if (t&&(*t)) swscanf(t, L"%lf", &v); /*v=_wtof(t);*/ return v; } + #endif +#else + #ifdef _XMLWINDOWS + long long xmltol(XMLCSTR t,long long v){ if (t&&(*t)) return _atoi64(t); return v; } + #else + long long xmltol(XMLCSTR t,long long v){ if (t&&(*t)) return atoll(t); return v; } + #endif + char xmltob(XMLCSTR t,char v){ if (t&&(*t)) return (char)atoi(t); return v; } + int xmltoi(XMLCSTR t,int v){ if (t&&(*t)) return atoi(t); return v; } + double xmltof(XMLCSTR t,double v){ if (t&&(*t)) return atof(t); return v; } +#endif +XMLCSTR xmltoa(XMLCSTR t, XMLCSTR v){ if (t) return t; return v; } +XMLCHAR xmltoc(XMLCSTR t,const XMLCHAR v){ if (t&&(*t)) return *t; return v; } + +///////////////////////////////////////////////////////////////////////// +// the "openFileHelper" function // +///////////////////////////////////////////////////////////////////////// + +// Since each application has its own way to report and deal with errors, you should modify & rewrite +// the following "openFileHelper" function to get an "error reporting mechanism" tailored to your needs. +XMLNode XMLNode::openFileHelper(XMLCSTR filename, XMLCSTR tag) +{ + // guess the value of the global parameter "characterEncoding" + // (the guess is based on the first 200 bytes of the file). + FILE *f=xfopen(filename,_CXML("rb")); + if (f) + { + char bb[205]; + int l=(int)fread(bb,1,200,f); + setGlobalOptions(guessCharEncoding(bb,l),guessWideCharChars,dropWhiteSpace,removeCommentsInMiddleOfText); + fclose(f); + } + + // parse the file + XMLResults pResults; + XMLNode xnode=XMLNode::parseFile(filename,tag,&pResults); + + // display error message (if any) + if (pResults.error != eXMLErrorNone) + { + // create message + char message[2000],*s1=(char*)"",*s3=(char*)""; XMLCSTR s2=_CXML(""); + if (pResults.error==eXMLErrorFirstTagNotFound) { s1=(char*)"First Tag should be '"; s2=tag; s3=(char*)"'.\n"; } +#ifdef _XMLWINDOWS + _snprintf(message,2000, +#else + snprintf(message,2000, +#endif +#ifdef _XMLWIDECHAR + "XML Parsing error inside file '%S'.\n%S\nAt line %i, column %i.\n%s%S%s" +#else + "XML Parsing error inside file '%s'.\n%s\nAt line %i, column %i.\n%s%s%s" +#endif + ,filename,XMLNode::getError(pResults.error),pResults.nLine,pResults.nColumn,s1,s2,s3); + + // display message +#if defined(_XMLWINDOWS) && !defined(UNDER_CE) && !defined(_XMLPARSER_NO_MESSAGEBOX_) + MessageBoxA(NULL,message,"XML Parsing error",MB_OK|MB_ICONERROR|MB_TOPMOST); +#else + printf("%s",message); +#endif + exit(255); + } + return xnode; +} + +///////////////////////////////////////////////////////////////////////// +// Here start the core implementation of the XMLParser library // +///////////////////////////////////////////////////////////////////////// + +// You should normally not change anything below this point. + +#ifndef _XMLWIDECHAR +// If "characterEncoding=ascii" then we assume that all characters have the same length of 1 byte. +// If "characterEncoding=UTF8" then the characters have different lengths (from 1 byte to 4 bytes). +// If "characterEncoding=ShiftJIS" then the characters have different lengths (from 1 byte to 2 bytes). +// This table is used as lookup-table to know the length of a character (in byte) based on the +// content of the first byte of the character. +// (note: if you modify this, you must always have XML_utf8ByteTable[0]=0 ). +static const char XML_utf8ByteTable[256] = +{ + // 0 1 2 3 4 5 6 7 8 9 a b c d e f + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x00 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x10 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x20 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x30 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x40 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x50 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x60 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x70 End of ASCII range + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x80 0x80 to 0xc1 invalid + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x90 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0xa0 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0xb0 + 1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xc0 0xc2 to 0xdf 2 byte + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xd0 + 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,// 0xe0 0xe0 to 0xef 3 byte + 4,4,4,4,4,1,1,1,1,1,1,1,1,1,1,1 // 0xf0 0xf0 to 0xf4 4 byte, 0xf5 and higher invalid +}; +static const char XML_legacyByteTable[256] = +{ + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 +}; +static const char XML_sjisByteTable[256] = +{ + // 0 1 2 3 4 5 6 7 8 9 a b c d e f + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x00 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x10 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x20 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x30 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x40 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x50 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x60 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x70 + 1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0x80 0x81 to 0x9F 2 bytes + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0x90 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0xa0 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0xb0 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0xc0 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0xd0 + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xe0 0xe0 to 0xef 2 bytes + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 // 0xf0 +}; +static const char XML_gb2312ByteTable[256] = +{ +// 0 1 2 3 4 5 6 7 8 9 a b c d e f + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x00 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x10 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x20 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x30 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x40 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x50 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x60 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x70 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x80 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x90 + 1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xa0 0xa1 to 0xf7 2 bytes + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xb0 + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xc0 + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xd0 + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xe0 + 2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1 // 0xf0 +}; +static const char XML_gbk_big5_ByteTable[256] = +{ + // 0 1 2 3 4 5 6 7 8 9 a b c d e f + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x00 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x10 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x20 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x30 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x40 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x50 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x60 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x70 + 1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0x80 0x81 to 0xfe 2 bytes + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0x90 + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xa0 + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xb0 + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xc0 + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xd0 + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xe0 + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1 // 0xf0 +}; +static const char *XML_ByteTable=(const char *)XML_utf8ByteTable; // the default is "characterEncoding=XMLNode::encoding_UTF8" +#endif + + +XMLNode XMLNode::emptyXMLNode; +XMLClear XMLNode::emptyXMLClear={ NULL, NULL, NULL}; +XMLAttribute XMLNode::emptyXMLAttribute={ NULL, NULL}; + +// Enumeration used to decipher what type a token is +typedef enum XMLTokenTypeTag +{ + eTokenText = 0, + eTokenQuotedText, + eTokenTagStart, /* "<" */ + eTokenTagEnd, /* "" */ + eTokenEquals, /* "=" */ + eTokenDeclaration, /* "" */ + eTokenClear, + eTokenError +} XMLTokenType; + +// Main structure used for parsing XML +typedef struct XML +{ + XMLCSTR lpXML; + XMLCSTR lpszText; + int nIndex,nIndexMissigEndTag; + enum XMLError error; + XMLCSTR lpEndTag; + int cbEndTag; + XMLCSTR lpNewElement; + int cbNewElement; + int nFirst; +} XML; + +typedef struct +{ + ALLXMLClearTag *pClr; + XMLCSTR pStr; +} NextToken; + +// Enumeration used when parsing attributes +typedef enum Attrib +{ + eAttribName = 0, + eAttribEquals, + eAttribValue +} Attrib; + +// Enumeration used when parsing elements to dictate whether we are currently +// inside a tag +typedef enum XMLStatus +{ + eInsideTag = 0, + eOutsideTag +} XMLStatus; + +XMLError XMLNode::writeToFile(XMLCSTR filename, const char *encoding, char nFormat) const +{ + if (!d) return eXMLErrorNone; + FILE *f=xfopen(filename,_CXML("wb")); + if (!f) return eXMLErrorCannotOpenWriteFile; +#ifdef _XMLWIDECHAR + unsigned char h[2]={ 0xFF, 0xFE }; + if (!fwrite(h,2,1,f)) + { + fclose(f); + return eXMLErrorCannotWriteFile; + } + if ((!isDeclaration())&&((d->lpszName)||(!getChildNode().isDeclaration()))) + { + if (!fwrite(L"\n",sizeof(wchar_t)*40,1,f)) + { + fclose(f); + return eXMLErrorCannotWriteFile; + } + } +#else + if ((!isDeclaration())&&((d->lpszName)||(!getChildNode().isDeclaration()))) + { + if (characterEncoding==char_encoding_UTF8) + { + // header so that windows recognize the file as UTF-8: + unsigned char h[3]={0xEF,0xBB,0xBF}; + if (!fwrite(h,3,1,f)) + { + fclose(f); + return eXMLErrorCannotWriteFile; + } + encoding="utf-8"; + } else if (characterEncoding==char_encoding_ShiftJIS) encoding="SHIFT-JIS"; + + if (!encoding) encoding="ISO-8859-1"; + if (fprintf(f,"\n",encoding)<0) + { + fclose(f); + return eXMLErrorCannotWriteFile; + } + } else + { + if (characterEncoding==char_encoding_UTF8) + { + unsigned char h[3]={0xEF,0xBB,0xBF}; + if (!fwrite(h,3,1,f)) + { + fclose(f); + return eXMLErrorCannotWriteFile; + } + } + } +#endif + int i; + XMLSTR t=createXMLString(nFormat,&i); + if (!fwrite(t,sizeof(XMLCHAR)*i,1,f)) + { + free(t); + fclose(f); + return eXMLErrorCannotWriteFile; + } + if (fclose(f)!=0) + { + free(t); + return eXMLErrorCannotWriteFile; + } + free(t); + return eXMLErrorNone; +} + +// Duplicate a given string. +XMLSTR stringDup(XMLCSTR lpszData, int cbData) +{ + if (lpszData==NULL) return NULL; + + XMLSTR lpszNew; + if (cbData==-1) cbData=(int)xstrlen(lpszData); + lpszNew = (XMLSTR)malloc((cbData+1) * sizeof(XMLCHAR)); + if (lpszNew) + { + memcpy(lpszNew, lpszData, (cbData) * sizeof(XMLCHAR)); + lpszNew[cbData] = (XMLCHAR)NULL; + } + return lpszNew; +} + +XMLSTR ToXMLStringTool::toXMLUnSafe(XMLSTR dest,XMLCSTR source) +{ + XMLSTR dd=dest; + XMLCHAR ch; + XMLCharacterEntity *entity; + while ((ch=*source)) + { + entity=XMLEntities; + do + { + if (ch==entity->c) {xstrcpy(dest,entity->s); dest+=entity->l; source++; goto out_of_loop1; } + entity++; + } while(entity->s); +#ifdef _XMLWIDECHAR + *(dest++)=*(source++); +#else + switch(XML_ByteTable[(unsigned char)ch]) + { + case 4: + if ((!(source[1]))||(!(source[2]))||(!(source[3]))) { *(dest++)='_'; source++; } + else + { + *dest=*source; + dest[1]=source[1]; + dest[2]=source[2]; + dest[3]=source[3]; + dest+=4; source+=4; + } + break; + case 3: + if ((!(source[1]))||(!(source[2]))) { *(dest++)='_'; source++; } + else + { + *dest=*source; + dest[1]=source[1]; + dest[2]=source[2]; + dest+=3; source+=3; + } + break; + case 2: + if (!(source[1])) { *(dest++)='_'; source++; } + else + { + *dest=*source; + dest[1]=source[1]; + dest+=2; source+=2; + } + break; + case 1: *(dest++)=*(source++); + } +#endif +out_of_loop1: + ; + } + *dest=0; + return dd; +} + +// private (used while rendering): +int ToXMLStringTool::lengthXMLString(XMLCSTR source) +{ + int r=0; + XMLCharacterEntity *entity; + XMLCHAR ch; + while ((ch=*source)) + { + entity=XMLEntities; + do + { + if (ch==entity->c) { r+=entity->l; source++; goto out_of_loop1; } + entity++; + } while(entity->s); +#ifdef _XMLWIDECHAR + r++; source++; +#else + ch=XML_ByteTable[(unsigned char)ch]; r+=ch; source+=ch; +#endif +out_of_loop1: + ; + } + return r; +} + +ToXMLStringTool::~ToXMLStringTool(){ freeBuffer(); } +void ToXMLStringTool::freeBuffer(){ if (buf) free(buf); buf=NULL; buflen=0; } +XMLSTR ToXMLStringTool::toXML(XMLCSTR source) +{ + if (!source) + { + if (buflen<1) { buflen=1; buf=(XMLSTR)malloc(sizeof(XMLCHAR)); } + *buf=0; + return buf; + } + int l=lengthXMLString(source)+1; + if (l>buflen) { freeBuffer(); buflen=l; buf=(XMLSTR)malloc(l*sizeof(XMLCHAR)); } + return toXMLUnSafe(buf,source); +} + +// private: +XMLSTR fromXMLString(XMLCSTR s, int lo, XML *pXML) +{ + // This function is the opposite of the function "toXMLString". It decodes the escape + // sequences &, ", ', <, > and replace them by the characters + // &,",',<,>. This function is used internally by the XML Parser. All the calls to + // the XML library will always gives you back "decoded" strings. + // + // in: string (s) and length (lo) of string + // out: new allocated string converted from xml + if (!s) return NULL; + + int ll=0,j; + XMLSTR d; + XMLCSTR ss=s; + XMLCharacterEntity *entity; + while ((lo>0)&&(*s)) + { + if (*s==_CXML('&')) + { + if ((lo>2)&&(s[1]==_CXML('#'))) + { + s+=2; lo-=2; + if ((*s==_CXML('X'))||(*s==_CXML('x'))) { s++; lo--; } + while ((*s)&&(*s!=_CXML(';'))&&((lo--)>0)) s++; + if (*s!=_CXML(';')) + { + pXML->error=eXMLErrorUnknownCharacterEntity; + return NULL; + } + s++; lo--; + } else + { + entity=XMLEntities; + do + { + if ((lo>=entity->l)&&(xstrnicmp(s,entity->s,entity->l)==0)) { s+=entity->l; lo-=entity->l; break; } + entity++; + } while(entity->s); + if (!entity->s) + { + pXML->error=eXMLErrorUnknownCharacterEntity; + return NULL; + } + } + } else + { +#ifdef _XMLWIDECHAR + s++; lo--; +#else + j=XML_ByteTable[(unsigned char)*s]; s+=j; lo-=j; ll+=j-1; +#endif + } + ll++; + } + + d=(XMLSTR)malloc((ll+1)*sizeof(XMLCHAR)); + s=d; + while (ll-->0) + { + if (*ss==_CXML('&')) + { + if (ss[1]==_CXML('#')) + { + ss+=2; j=0; + if ((*ss==_CXML('X'))||(*ss==_CXML('x'))) + { + ss++; + while (*ss!=_CXML(';')) + { + if ((*ss>=_CXML('0'))&&(*ss<=_CXML('9'))) j=(j<<4)+*ss-_CXML('0'); + else if ((*ss>=_CXML('A'))&&(*ss<=_CXML('F'))) j=(j<<4)+*ss-_CXML('A')+10; + else if ((*ss>=_CXML('a'))&&(*ss<=_CXML('f'))) j=(j<<4)+*ss-_CXML('a')+10; + else { free((void*)s); pXML->error=eXMLErrorUnknownCharacterEntity;return NULL;} + ss++; + } + } else + { + while (*ss!=_CXML(';')) + { + if ((*ss>=_CXML('0'))&&(*ss<=_CXML('9'))) j=(j*10)+*ss-_CXML('0'); + else { free((void*)s); pXML->error=eXMLErrorUnknownCharacterEntity;return NULL;} + ss++; + } + } +#ifndef _XMLWIDECHAR + if (j>255) { free((void*)s); pXML->error=eXMLErrorCharacterCodeAbove255;return NULL;} +#endif + (*d++)=(XMLCHAR)j; ss++; + } else + { + entity=XMLEntities; + do + { + if (xstrnicmp(ss,entity->s,entity->l)==0) { *(d++)=entity->c; ss+=entity->l; break; } + entity++; + } while(entity->s); + } + } else + { +#ifdef _XMLWIDECHAR + *(d++)=*(ss++); +#else + switch(XML_ByteTable[(unsigned char)*ss]) + { + case 4: *(d++)=*(ss++); ll--; + case 3: *(d++)=*(ss++); ll--; + case 2: *(d++)=*(ss++); ll--; + case 1: *(d++)=*(ss++); + } +#endif + } + } + *d=0; + return (XMLSTR)s; +} + +#define XML_isSPACECHAR(ch) ((ch==_CXML('\n'))||(ch==_CXML(' '))||(ch== _CXML('\t'))||(ch==_CXML('\r'))) + +// private: +char myTagCompare(XMLCSTR cclose, XMLCSTR copen) +// !!!! WARNING strange convention&: +// return 0 if equals +// return 1 if different +{ + if (!cclose) return 1; + int l=(int)xstrlen(cclose); + if (xstrnicmp(cclose, copen, l)!=0) return 1; + const XMLCHAR c=copen[l]; + if (XML_isSPACECHAR(c)|| + (c==_CXML('/' ))|| + (c==_CXML('<' ))|| + (c==_CXML('>' ))|| + (c==_CXML('=' ))) return 0; + return 1; +} + +// Obtain the next character from the string. +static inline XMLCHAR getNextChar(XML *pXML) +{ + XMLCHAR ch = pXML->lpXML[pXML->nIndex]; +#ifdef _XMLWIDECHAR + if (ch!=0) pXML->nIndex++; +#else + pXML->nIndex+=XML_ByteTable[(unsigned char)ch]; +#endif + return ch; +} + +// Find the next token in a string. +// pcbToken contains the number of characters that have been read. +static NextToken GetNextToken(XML *pXML, int *pcbToken, enum XMLTokenTypeTag *pType) +{ + NextToken result; + XMLCHAR ch; + XMLCHAR chTemp; + int indexStart,nFoundMatch,nIsText=FALSE; + result.pClr=NULL; // prevent warning + + // Find next non-white space character + do { indexStart=pXML->nIndex; ch=getNextChar(pXML); } while XML_isSPACECHAR(ch); + + if (ch) + { + // Cache the current string pointer + result.pStr = &pXML->lpXML[indexStart]; + + // check for standard tokens + switch(ch) + { + // Check for quotes + case _CXML('\''): + case _CXML('\"'): + // Type of token + *pType = eTokenQuotedText; + chTemp = ch; + + // Set the size + nFoundMatch = FALSE; + + // Search through the string to find a matching quote + while((ch = getNextChar(pXML))) + { + if (ch==chTemp) { nFoundMatch = TRUE; break; } + if (ch==_CXML('<')) break; + } + + // If we failed to find a matching quote + if (nFoundMatch == FALSE) + { + pXML->nIndex=indexStart+1; + nIsText=TRUE; + break; + } + +// 4.02.2002 +// if (FindNonWhiteSpace(pXML)) pXML->nIndex--; + + break; + + // Equals (used with attribute values) + case _CXML('='): + *pType = eTokenEquals; + break; + + // Close tag + case _CXML('>'): + *pType = eTokenCloseTag; + break; + + // Check for tag start and tag end + case _CXML('<'): + + { + // First check whether the token is in the clear tag list (meaning it + // does not need formatting). + ALLXMLClearTag *ctag=XMLClearTags; + do + { + if (!xstrncmp(ctag->lpszOpen, result.pStr, ctag->openTagLen)) + { + result.pClr=ctag; + pXML->nIndex+=ctag->openTagLen-1; + *pType=eTokenClear; + return result; + } + ctag++; + } while(ctag->lpszOpen); + + // Peek at the next character to see if we have an end tag 'lpXML[pXML->nIndex]; + + // If we have a tag end... + if (chTemp == _CXML('/')) + { + // Set the type and ensure we point at the next character + getNextChar(pXML); + *pType = eTokenTagEnd; + } + + // If we have an XML declaration tag + else if (chTemp == _CXML('?')) + { + + // Set the type and ensure we point at the next character + getNextChar(pXML); + *pType = eTokenDeclaration; + } + + // Otherwise we must have a start tag + else + { + *pType = eTokenTagStart; + } + break; + } + + // Check to see if we have a short hand type end tag ('/>'). + case _CXML('/'): + + // Peek at the next character to see if we have a short end tag '/>' + chTemp = pXML->lpXML[pXML->nIndex]; + + // If we have a short hand end tag... + if (chTemp == _CXML('>')) + { + // Set the type and ensure we point at the next character + getNextChar(pXML); + *pType = eTokenShortHandClose; + break; + } + + // If we haven't found a short hand closing tag then drop into the + // text process + + // Other characters + default: + nIsText = TRUE; + } + + // If this is a TEXT node + if (nIsText) + { + // Indicate we are dealing with text + *pType = eTokenText; + while((ch = getNextChar(pXML))) + { + if XML_isSPACECHAR(ch) + { + indexStart++; break; + + } else if (ch==_CXML('/')) + { + // If we find a slash then this maybe text or a short hand end tag + // Peek at the next character to see it we have short hand end tag + ch=pXML->lpXML[pXML->nIndex]; + // If we found a short hand end tag then we need to exit the loop + if (ch==_CXML('>')) { pXML->nIndex--; break; } + + } else if ((ch==_CXML('<'))||(ch==_CXML('>'))||(ch==_CXML('='))) + { + pXML->nIndex--; break; + } + } + } + *pcbToken = pXML->nIndex-indexStart; + } else + { + // If we failed to obtain a valid character + *pcbToken = 0; + *pType = eTokenError; + result.pStr=NULL; + } + + return result; +} + +XMLCSTR XMLNode::updateName_WOSD(XMLSTR lpszName) +{ + if (!d) { free(lpszName); return NULL; } + if (d->lpszName&&(lpszName!=d->lpszName)) free((void*)d->lpszName); + d->lpszName=lpszName; + return lpszName; +} + +// private: +XMLNode::XMLNode(struct XMLNodeDataTag *p){ d=p; (p->ref_count)++; } +XMLNode::XMLNode(XMLNodeData *pParent, XMLSTR lpszName, char isDeclaration) +{ + d=(XMLNodeData*)malloc(sizeof(XMLNodeData)); + d->ref_count=1; + + d->lpszName=NULL; + d->nChild= 0; + d->nText = 0; + d->nClear = 0; + d->nAttribute = 0; + + d->isDeclaration = isDeclaration; + + d->pParent = pParent; + d->pChild= NULL; + d->pText= NULL; + d->pClear= NULL; + d->pAttribute= NULL; + d->pOrder= NULL; + + updateName_WOSD(lpszName); +} + +XMLNode XMLNode::createXMLTopNode_WOSD(XMLSTR lpszName, char isDeclaration) { return XMLNode(NULL,lpszName,isDeclaration); } +XMLNode XMLNode::createXMLTopNode(XMLCSTR lpszName, char isDeclaration) { return XMLNode(NULL,stringDup(lpszName),isDeclaration); } + +#define MEMORYINCREASE 50 + +static inline void myFree(void *p) { if (p) free(p); } +static inline void *myRealloc(void *p, int newsize, int memInc, int sizeofElem) +{ + if (p==NULL) { if (memInc) return malloc(memInc*sizeofElem); return malloc(sizeofElem); } + if ((memInc==0)||((newsize%memInc)==0)) p=realloc(p,(newsize+memInc)*sizeofElem); +// if (!p) +// { +// printf("XMLParser Error: Not enough memory! Aborting...\n"); exit(220); +// } + return p; +} + +// private: +XMLElementPosition XMLNode::findPosition(XMLNodeData *d, int index, XMLElementType xxtype) +{ + if (index<0) return -1; + int i=0,j=(int)((index<<2)+xxtype),*o=d->pOrder; while (o[i]!=j) i++; return i; +} + +// private: +// update "order" information when deleting a content of a XMLNode +int XMLNode::removeOrderElement(XMLNodeData *d, XMLElementType t, int index) +{ + int n=d->nChild+d->nText+d->nClear, *o=d->pOrder,i=findPosition(d,index,t); + memmove(o+i, o+i+1, (n-i)*sizeof(int)); + for (;ipOrder=(int)realloc(d->pOrder,n*sizeof(int)); + // but we skip reallocation because it's too time consuming. + // Anyway, at the end, it will be free'd completely at once. + return i; +} + +void *XMLNode::addToOrder(int memoryIncrease,int *_pos, int nc, void *p, int size, XMLElementType xtype) +{ + // in: *_pos is the position inside d->pOrder ("-1" means "EndOf") + // out: *_pos is the index inside p + p=myRealloc(p,(nc+1),memoryIncrease,size); + int n=d->nChild+d->nText+d->nClear; + d->pOrder=(int*)myRealloc(d->pOrder,n+1,memoryIncrease*3,sizeof(int)); + int pos=*_pos,*o=d->pOrder; + + if ((pos<0)||(pos>=n)) { *_pos=nc; o[n]=(int)((nc<<2)+xtype); return p; } + + int i=pos; + memmove(o+i+1, o+i, (n-i)*sizeof(int)); + + while ((pos>2; + memmove(((char*)p)+(pos+1)*size,((char*)p)+pos*size,(nc-pos)*size); + + return p; +} + +// Add a child node to the given element. +XMLNode XMLNode::addChild_priv(int memoryIncrease, XMLSTR lpszName, char isDeclaration, int pos) +{ + if (!lpszName) return emptyXMLNode; + d->pChild=(XMLNode*)addToOrder(memoryIncrease,&pos,d->nChild,d->pChild,sizeof(XMLNode),eNodeChild); + d->pChild[pos].d=NULL; + d->pChild[pos]=XMLNode(d,lpszName,isDeclaration); + d->nChild++; + return d->pChild[pos]; +} + +// Add an attribute to an element. +XMLAttribute *XMLNode::addAttribute_priv(int memoryIncrease,XMLSTR lpszName, XMLSTR lpszValuev) +{ + if (!lpszName) return &emptyXMLAttribute; + if (!d) { myFree(lpszName); myFree(lpszValuev); return &emptyXMLAttribute; } + int nc=d->nAttribute; + d->pAttribute=(XMLAttribute*)myRealloc(d->pAttribute,(nc+1),memoryIncrease,sizeof(XMLAttribute)); + XMLAttribute *pAttr=d->pAttribute+nc; + pAttr->lpszName = lpszName; + pAttr->lpszValue = lpszValuev; + d->nAttribute++; + return pAttr; +} + +// Add text to the element. +XMLCSTR XMLNode::addText_priv(int memoryIncrease, XMLSTR lpszValue, int pos) +{ + if (!lpszValue) return NULL; + if (!d) { myFree(lpszValue); return NULL; } + d->pText=(XMLCSTR*)addToOrder(memoryIncrease,&pos,d->nText,d->pText,sizeof(XMLSTR),eNodeText); + d->pText[pos]=lpszValue; + d->nText++; + return lpszValue; +} + +// Add clear (unformatted) text to the element. +XMLClear *XMLNode::addClear_priv(int memoryIncrease, XMLSTR lpszValue, XMLCSTR lpszOpen, XMLCSTR lpszClose, int pos) +{ + if (!lpszValue) return &emptyXMLClear; + if (!d) { myFree(lpszValue); return &emptyXMLClear; } + d->pClear=(XMLClear *)addToOrder(memoryIncrease,&pos,d->nClear,d->pClear,sizeof(XMLClear),eNodeClear); + XMLClear *pNewClear=d->pClear+pos; + pNewClear->lpszValue = lpszValue; + if (!lpszOpen) lpszOpen=XMLClearTags->lpszOpen; + if (!lpszClose) lpszClose=XMLClearTags->lpszClose; + pNewClear->lpszOpenTag = lpszOpen; + pNewClear->lpszCloseTag = lpszClose; + d->nClear++; + return pNewClear; +} + +// private: +// Parse a clear (unformatted) type node. +char XMLNode::parseClearTag(void *px, void *_pClear) +{ + XML *pXML=(XML *)px; + ALLXMLClearTag pClear=*((ALLXMLClearTag*)_pClear); + int cbTemp=0; + XMLCSTR lpszTemp=NULL; + XMLCSTR lpXML=&pXML->lpXML[pXML->nIndex]; + static XMLCSTR docTypeEnd=_CXML("]>"); + + // Find the closing tag + // Seems the ')) { lpszTemp=pCh; break; } +#ifdef _XMLWIDECHAR + pCh++; +#else + pCh+=XML_ByteTable[(unsigned char)(*pCh)]; +#endif + } + } else lpszTemp=xstrstr(lpXML, pClear.lpszClose); + + if (lpszTemp) + { + // Cache the size and increment the index + cbTemp = (int)(lpszTemp - lpXML); + + pXML->nIndex += cbTemp+(int)xstrlen(pClear.lpszClose); + + // Add the clear node to the current element + addClear_priv(MEMORYINCREASE,cbTemp?stringDup(lpXML,cbTemp):NULL, pClear.lpszOpen, pClear.lpszClose,-1); + return 0; + } + + // If we failed to find the end tag + pXML->error = eXMLErrorUnmatchedEndClearTag; + return 1; +} + +void XMLNode::exactMemory(XMLNodeData *d) +{ + if (d->pOrder) d->pOrder=(int*)realloc(d->pOrder,(d->nChild+d->nText+d->nClear)*sizeof(int)); + if (d->pChild) d->pChild=(XMLNode*)realloc(d->pChild,d->nChild*sizeof(XMLNode)); + if (d->pAttribute) d->pAttribute=(XMLAttribute*)realloc(d->pAttribute,d->nAttribute*sizeof(XMLAttribute)); + if (d->pText) d->pText=(XMLCSTR*)realloc(d->pText,d->nText*sizeof(XMLSTR)); + if (d->pClear) d->pClear=(XMLClear *)realloc(d->pClear,d->nClear*sizeof(XMLClear)); +} + +char XMLNode::maybeAddTxT(void *pa, XMLCSTR tokenPStr) +{ + XML *pXML=(XML *)pa; + XMLCSTR lpszText=pXML->lpszText; + if (!lpszText) return 0; + if (dropWhiteSpace) while (XML_isSPACECHAR(*lpszText)&&(lpszText!=tokenPStr)) lpszText++; + int cbText = (int)(tokenPStr - lpszText); + if (!cbText) { pXML->lpszText=NULL; return 0; } + if (dropWhiteSpace) { cbText--; while ((cbText)&&XML_isSPACECHAR(lpszText[cbText])) cbText--; cbText++; } + if (!cbText) { pXML->lpszText=NULL; return 0; } + XMLSTR lpt=fromXMLString(lpszText,cbText,pXML); + if (!lpt) return 1; + pXML->lpszText=NULL; + if (removeCommentsInMiddleOfText && d->nText && d->nClear) + { + // if the previous insertion was a comment () AND + // if the previous previous insertion was a text then, delete the comment and append the text + int n=d->nChild+d->nText+d->nClear-1,*o=d->pOrder; + if (((o[n]&3)==eNodeClear)&&((o[n-1]&3)==eNodeText)) + { + int i=o[n]>>2; + if (d->pClear[i].lpszOpenTag==XMLClearTags[2].lpszOpen) + { + deleteClear(i); + i=o[n-1]>>2; + n=xstrlen(d->pText[i]); + int n2=xstrlen(lpt)+1; + d->pText[i]=(XMLSTR)realloc((void*)d->pText[i],(n+n2)*sizeof(XMLCHAR)); + if (!d->pText[i]) return 1; + memcpy((void*)(d->pText[i]+n),lpt,n2*sizeof(XMLCHAR)); + free(lpt); + return 0; + } + } + } + addText_priv(MEMORYINCREASE,lpt,-1); + return 0; +} +// private: +// Recursively parse an XML element. +int XMLNode::ParseXMLElement(void *pa) +{ + XML *pXML=(XML *)pa; + int cbToken; + enum XMLTokenTypeTag xtype; + NextToken token; + XMLCSTR lpszTemp=NULL; + int cbTemp=0; + char nDeclaration; + XMLNode pNew; + enum XMLStatus status; // inside or outside a tag + enum Attrib attrib = eAttribName; + + assert(pXML); + + // If this is the first call to the function + if (pXML->nFirst) + { + // Assume we are outside of a tag definition + pXML->nFirst = FALSE; + status = eOutsideTag; + } else + { + // If this is not the first call then we should only be called when inside a tag. + status = eInsideTag; + } + + // Iterate through the tokens in the document + for(;;) + { + // Obtain the next token + token = GetNextToken(pXML, &cbToken, &xtype); + + if (xtype != eTokenError) + { + // Check the current status + switch(status) + { + + // If we are outside of a tag definition + case eOutsideTag: + + // Check what type of token we obtained + switch(xtype) + { + // If we have found text or quoted text + case eTokenText: + case eTokenCloseTag: /* '>' */ + case eTokenShortHandClose: /* '/>' */ + case eTokenQuotedText: + case eTokenEquals: + break; + + // If we found a start tag '<' and declarations 'error = eXMLErrorMissingTagName; + return FALSE; + } + + // If we found a new element which is the same as this + // element then we need to pass this back to the caller.. + +#ifdef APPROXIMATE_PARSING + if (d->lpszName && + myTagCompare(d->lpszName, token.pStr) == 0) + { + // Indicate to the caller that it needs to create a + // new element. + pXML->lpNewElement = token.pStr; + pXML->cbNewElement = cbToken; + return TRUE; + } else +#endif + { + // If the name of the new element differs from the name of + // the current element we need to add the new element to + // the current one and recurse + pNew = addChild_priv(MEMORYINCREASE,stringDup(token.pStr,cbToken), nDeclaration,-1); + + while (!pNew.isEmpty()) + { + // Callself to process the new node. If we return + // FALSE this means we dont have any more + // processing to do... + + if (!pNew.ParseXMLElement(pXML)) return FALSE; + else + { + // If the call to recurse this function + // evented in a end tag specified in XML then + // we need to unwind the calls to this + // function until we find the appropriate node + // (the element name and end tag name must + // match) + if (pXML->cbEndTag) + { + // If we are back at the root node then we + // have an unmatched end tag + if (!d->lpszName) + { + pXML->error=eXMLErrorUnmatchedEndTag; + return FALSE; + } + + // If the end tag matches the name of this + // element then we only need to unwind + // once more... + + if (myTagCompare(d->lpszName, pXML->lpEndTag)==0) + { + pXML->cbEndTag = 0; + } + + return TRUE; + } else + if (pXML->cbNewElement) + { + // If the call indicated a new element is to + // be created on THIS element. + + // If the name of this element matches the + // name of the element we need to create + // then we need to return to the caller + // and let it process the element. + + if (myTagCompare(d->lpszName, pXML->lpNewElement)==0) + { + return TRUE; + } + + // Add the new element and recurse + pNew = addChild_priv(MEMORYINCREASE,stringDup(pXML->lpNewElement,pXML->cbNewElement),0,-1); + pXML->cbNewElement = 0; + } + else + { + // If we didn't have a new element to create + pNew = emptyXMLNode; + + } + } + } + } + break; + + // If we found an end tag + case eTokenTagEnd: + + // If we have node text then add this to the element + if (maybeAddTxT(pXML,token.pStr)) return FALSE; + + // Find the name of the end tag + token = GetNextToken(pXML, &cbTemp, &xtype); + + // The end tag should be text + if (xtype != eTokenText) + { + pXML->error = eXMLErrorMissingEndTagName; + return FALSE; + } + lpszTemp = token.pStr; + + // After the end tag we should find a closing tag + token = GetNextToken(pXML, &cbToken, &xtype); + if (xtype != eTokenCloseTag) + { + pXML->error = eXMLErrorMissingEndTagName; + return FALSE; + } + pXML->lpszText=pXML->lpXML+pXML->nIndex; + + // We need to return to the previous caller. If the name + // of the tag cannot be found we need to keep returning to + // caller until we find a match + if (myTagCompare(d->lpszName, lpszTemp) != 0) +#ifdef STRICT_PARSING + { + pXML->error=eXMLErrorUnmatchedEndTag; + pXML->nIndexMissigEndTag=pXML->nIndex; + return FALSE; + } +#else + { + pXML->error=eXMLErrorMissingEndTag; + pXML->nIndexMissigEndTag=pXML->nIndex; + pXML->lpEndTag = lpszTemp; + pXML->cbEndTag = cbTemp; + } +#endif + + // Return to the caller + exactMemory(d); + return TRUE; + + // If we found a clear (unformatted) token + case eTokenClear: + // If we have node text then add this to the element + if (maybeAddTxT(pXML,token.pStr)) return FALSE; + if (parseClearTag(pXML, token.pClr)) return FALSE; + pXML->lpszText=pXML->lpXML+pXML->nIndex; + break; + + default: + break; + } + break; + + // If we are inside a tag definition we need to search for attributes + case eInsideTag: + + // Check what part of the attribute (name, equals, value) we + // are looking for. + switch(attrib) + { + // If we are looking for a new attribute + case eAttribName: + + // Check what the current token type is + switch(xtype) + { + // If the current type is text... + // Eg. 'attribute' + case eTokenText: + // Cache the token then indicate that we are next to + // look for the equals + lpszTemp = token.pStr; + cbTemp = cbToken; + attrib = eAttribEquals; + break; + + // If we found a closing tag... + // Eg. '>' + case eTokenCloseTag: + // We are now outside the tag + status = eOutsideTag; + pXML->lpszText=pXML->lpXML+pXML->nIndex; + break; + + // If we found a short hand '/>' closing tag then we can + // return to the caller + case eTokenShortHandClose: + exactMemory(d); + pXML->lpszText=pXML->lpXML+pXML->nIndex; + return TRUE; + + // Errors... + case eTokenQuotedText: /* '"SomeText"' */ + case eTokenTagStart: /* '<' */ + case eTokenTagEnd: /* 'error = eXMLErrorUnexpectedToken; + return FALSE; + default: break; + } + break; + + // If we are looking for an equals + case eAttribEquals: + // Check what the current token type is + switch(xtype) + { + // If the current type is text... + // Eg. 'Attribute AnotherAttribute' + case eTokenText: + // Add the unvalued attribute to the list + addAttribute_priv(MEMORYINCREASE,stringDup(lpszTemp,cbTemp), NULL); + // Cache the token then indicate. We are next to + // look for the equals attribute + lpszTemp = token.pStr; + cbTemp = cbToken; + break; + + // If we found a closing tag 'Attribute >' or a short hand + // closing tag 'Attribute />' + case eTokenShortHandClose: + case eTokenCloseTag: + // If we are a declaration element 'lpszText=pXML->lpXML+pXML->nIndex; + + if (d->isDeclaration && + (lpszTemp[cbTemp-1]) == _CXML('?')) + { + cbTemp--; + if (d->pParent && d->pParent->pParent) xtype = eTokenShortHandClose; + } + + if (cbTemp) + { + // Add the unvalued attribute to the list + addAttribute_priv(MEMORYINCREASE,stringDup(lpszTemp,cbTemp), NULL); + } + + // If this is the end of the tag then return to the caller + if (xtype == eTokenShortHandClose) + { + exactMemory(d); + return TRUE; + } + + // We are now outside the tag + status = eOutsideTag; + break; + + // If we found the equals token... + // Eg. 'Attribute =' + case eTokenEquals: + // Indicate that we next need to search for the value + // for the attribute + attrib = eAttribValue; + break; + + // Errors... + case eTokenQuotedText: /* 'Attribute "InvalidAttr"'*/ + case eTokenTagStart: /* 'Attribute <' */ + case eTokenTagEnd: /* 'Attribute error = eXMLErrorUnexpectedToken; + return FALSE; + default: break; + } + break; + + // If we are looking for an attribute value + case eAttribValue: + // Check what the current token type is + switch(xtype) + { + // If the current type is text or quoted text... + // Eg. 'Attribute = "Value"' or 'Attribute = Value' or + // 'Attribute = 'Value''. + case eTokenText: + case eTokenQuotedText: + // If we are a declaration element 'isDeclaration && + (token.pStr[cbToken-1]) == _CXML('?')) + { + cbToken--; + } + + if (cbTemp) + { + // Add the valued attribute to the list + if (xtype==eTokenQuotedText) { token.pStr++; cbToken-=2; } + XMLSTR attrVal=(XMLSTR)token.pStr; + if (attrVal) + { + attrVal=fromXMLString(attrVal,cbToken,pXML); + if (!attrVal) return FALSE; + } + addAttribute_priv(MEMORYINCREASE,stringDup(lpszTemp,cbTemp),attrVal); + } + + // Indicate we are searching for a new attribute + attrib = eAttribName; + break; + + // Errors... + case eTokenTagStart: /* 'Attr = <' */ + case eTokenTagEnd: /* 'Attr = ' */ + case eTokenShortHandClose: /* "Attr = />" */ + case eTokenEquals: /* 'Attr = =' */ + case eTokenDeclaration: /* 'Attr = error = eXMLErrorUnexpectedToken; + return FALSE; + break; + default: break; + } + } + } + } + // If we failed to obtain the next token + else + { + if ((!d->isDeclaration)&&(d->pParent)) + { +#ifdef STRICT_PARSING + pXML->error=eXMLErrorUnmatchedEndTag; +#else + pXML->error=eXMLErrorMissingEndTag; +#endif + pXML->nIndexMissigEndTag=pXML->nIndex; + } + maybeAddTxT(pXML,pXML->lpXML+pXML->nIndex); + return FALSE; + } + } +} + +// Count the number of lines and columns in an XML string. +static void CountLinesAndColumns(XMLCSTR lpXML, int nUpto, XMLResults *pResults) +{ + XMLCHAR ch; + assert(lpXML); + assert(pResults); + + struct XML xml={ lpXML,lpXML, 0, 0, eXMLErrorNone, NULL, 0, NULL, 0, TRUE }; + + pResults->nLine = 1; + pResults->nColumn = 1; + while (xml.nIndexnColumn++; + else + { + pResults->nLine++; + pResults->nColumn=1; + } + } +} + +// Parse XML and return the root element. +XMLNode XMLNode::parseString(XMLCSTR lpszXML, XMLCSTR tag, XMLResults *pResults) +{ + if (!lpszXML) + { + if (pResults) + { + pResults->error=eXMLErrorNoElements; + pResults->nLine=0; + pResults->nColumn=0; + } + return emptyXMLNode; + } + + XMLNode xnode(NULL,NULL,FALSE); + struct XML xml={ lpszXML, lpszXML, 0, 0, eXMLErrorNone, NULL, 0, NULL, 0, TRUE }; + + // Create header element + xnode.ParseXMLElement(&xml); + enum XMLError error = xml.error; + if (!xnode.nChildNode()) error=eXMLErrorNoXMLTagFound; + if ((xnode.nChildNode()==1)&&(xnode.nElement()==1)) xnode=xnode.getChildNode(); // skip the empty node + + // If no error occurred + if ((error==eXMLErrorNone)||(error==eXMLErrorMissingEndTag)||(error==eXMLErrorNoXMLTagFound)) + { + XMLCSTR name=xnode.getName(); + if (tag&&(*tag)&&((!name)||(xstricmp(name,tag)))) + { + xnode=xnode.getChildNode(tag); + if (xnode.isEmpty()) + { + if (pResults) + { + pResults->error=eXMLErrorFirstTagNotFound; + pResults->nLine=0; + pResults->nColumn=0; + } + return emptyXMLNode; + } + } + } else + { + // Cleanup: this will destroy all the nodes + xnode = emptyXMLNode; + } + + + // If we have been given somewhere to place results + if (pResults) + { + pResults->error = error; + + // If we have an error + if (error!=eXMLErrorNone) + { + if (error==eXMLErrorMissingEndTag) xml.nIndex=xml.nIndexMissigEndTag; + // Find which line and column it starts on. + CountLinesAndColumns(xml.lpXML, xml.nIndex, pResults); + } + } + return xnode; +} + +XMLNode XMLNode::parseFile(XMLCSTR filename, XMLCSTR tag, XMLResults *pResults) +{ + if (pResults) { pResults->nLine=0; pResults->nColumn=0; } + FILE *f=xfopen(filename,_CXML("rb")); + if (f==NULL) { if (pResults) pResults->error=eXMLErrorFileNotFound; return emptyXMLNode; } + fseek(f,0,SEEK_END); + int l=(int)ftell(f),headerSz=0; + if (!l) { if (pResults) pResults->error=eXMLErrorEmpty; fclose(f); return emptyXMLNode; } + fseek(f,0,SEEK_SET); + unsigned char *buf=(unsigned char*)malloc(l+4); + l=(int)fread(buf,1,l,f); + fclose(f); + buf[l]=0;buf[l+1]=0;buf[l+2]=0;buf[l+3]=0; +#ifdef _XMLWIDECHAR + if (guessWideCharChars) + { + if (!myIsTextWideChar(buf,l)) + { + XMLNode::XMLCharEncoding ce=XMLNode::char_encoding_legacy; + if ((buf[0]==0xef)&&(buf[1]==0xbb)&&(buf[2]==0xbf)) { headerSz=3; ce=XMLNode::char_encoding_UTF8; } + XMLSTR b2=myMultiByteToWideChar((const char*)(buf+headerSz),ce); + if (!b2) + { + // todo: unable to convert + } + free(buf); buf=(unsigned char*)b2; headerSz=0; + } else + { + if ((buf[0]==0xef)&&(buf[1]==0xff)) headerSz=2; + if ((buf[0]==0xff)&&(buf[1]==0xfe)) headerSz=2; + } + } else + { + if ((buf[0]==0xef)&&(buf[1]==0xff)) headerSz=2; + if ((buf[0]==0xff)&&(buf[1]==0xfe)) headerSz=2; + if ((buf[0]==0xef)&&(buf[1]==0xbb)&&(buf[2]==0xbf)) headerSz=3; + } +#else + if (guessWideCharChars) + { + if (myIsTextWideChar(buf,l)) + { + if ((buf[0]==0xef)&&(buf[1]==0xff)) headerSz=2; + if ((buf[0]==0xff)&&(buf[1]==0xfe)) headerSz=2; + char *b2=myWideCharToMultiByte((const wchar_t*)(buf+headerSz)); + free(buf); buf=(unsigned char*)b2; headerSz=0; + } else + { + if ((buf[0]==0xef)&&(buf[1]==0xbb)&&(buf[2]==0xbf)) headerSz=3; + } + } else + { + if ((buf[0]==0xef)&&(buf[1]==0xff)) headerSz=2; + if ((buf[0]==0xff)&&(buf[1]==0xfe)) headerSz=2; + if ((buf[0]==0xef)&&(buf[1]==0xbb)&&(buf[2]==0xbf)) headerSz=3; + } +#endif + + if (!buf) { if (pResults) pResults->error=eXMLErrorCharConversionError; return emptyXMLNode; } + XMLNode x=parseString((XMLSTR)(buf+headerSz),tag,pResults); + free(buf); + return x; +} + +static inline void charmemset(XMLSTR dest,XMLCHAR c,int l) { while (l--) *(dest++)=c; } +// private: +// Creates an user friendly XML string from a given element with +// appropriate white space and carriage returns. +// +// This recurses through all subnodes then adds contents of the nodes to the +// string. +int XMLNode::CreateXMLStringR(XMLNodeData *pEntry, XMLSTR lpszMarker, int nFormat) +{ + int nResult = 0; + int cb=nFormat<0?0:nFormat; + int cbElement; + int nChildFormat=-1; + int nElementI=pEntry->nChild+pEntry->nText+pEntry->nClear; + int i,j; + if ((nFormat>=0)&&(nElementI==1)&&(pEntry->nText==1)&&(!pEntry->isDeclaration)) nFormat=-2; + + assert(pEntry); + +#define LENSTR(lpsz) (lpsz ? xstrlen(lpsz) : 0) + + // If the element has no name then assume this is the head node. + cbElement = (int)LENSTR(pEntry->lpszName); + + if (cbElement) + { + // "isDeclaration) lpszMarker[nResult++]=_CXML('?'); + xstrcpy(&lpszMarker[nResult], pEntry->lpszName); + nResult+=cbElement; + lpszMarker[nResult++]=_CXML(' '); + + } else + { + nResult+=cbElement+2+cb; + if (pEntry->isDeclaration) nResult++; + } + + // Enumerate attributes and add them to the string + XMLAttribute *pAttr=pEntry->pAttribute; + for (i=0; inAttribute; i++) + { + // "Attrib + cb = (int)LENSTR(pAttr->lpszName); + if (cb) + { + if (lpszMarker) xstrcpy(&lpszMarker[nResult], pAttr->lpszName); + nResult += cb; + // "Attrib=Value " + if (pAttr->lpszValue) + { + cb=(int)ToXMLStringTool::lengthXMLString(pAttr->lpszValue); + if (lpszMarker) + { + lpszMarker[nResult]=_CXML('='); + lpszMarker[nResult+1]=_CXML('"'); + if (cb) ToXMLStringTool::toXMLUnSafe(&lpszMarker[nResult+2],pAttr->lpszValue); + lpszMarker[nResult+cb+2]=_CXML('"'); + } + nResult+=cb+3; + } + if (lpszMarker) lpszMarker[nResult] = _CXML(' '); + nResult++; + } + pAttr++; + } + + if (pEntry->isDeclaration) + { + if (lpszMarker) + { + lpszMarker[nResult-1]=_CXML('?'); + lpszMarker[nResult]=_CXML('>'); + } + nResult++; + if (nFormat!=-1) + { + if (lpszMarker) lpszMarker[nResult]=_CXML('\n'); + nResult++; + } + } else + // If there are child nodes we need to terminate the start tag + if (nElementI) + { + if (lpszMarker) lpszMarker[nResult-1]=_CXML('>'); + if (nFormat>=0) + { + if (lpszMarker) lpszMarker[nResult]=_CXML('\n'); + nResult++; + } + } else nResult--; + } + + // Calculate the child format for when we recurse. This is used to + // determine the number of spaces used for prefixes. + if (nFormat!=-1) + { + if (cbElement&&(!pEntry->isDeclaration)) nChildFormat=nFormat+1; + else nChildFormat=nFormat; + } + + // Enumerate through remaining children + for (i=0; ipOrder[i]; + switch((XMLElementType)(j&3)) + { + // Text nodes + case eNodeText: + { + // "Text" + XMLCSTR pChild=pEntry->pText[j>>2]; + cb = (int)ToXMLStringTool::lengthXMLString(pChild); + if (cb) + { + if (nFormat>=0) + { + if (lpszMarker) + { + charmemset(&lpszMarker[nResult],INDENTCHAR,nFormat+1); + ToXMLStringTool::toXMLUnSafe(&lpszMarker[nResult+nFormat+1],pChild); + lpszMarker[nResult+nFormat+1+cb]=_CXML('\n'); + } + nResult+=cb+nFormat+2; + } else + { + if (lpszMarker) ToXMLStringTool::toXMLUnSafe(&lpszMarker[nResult], pChild); + nResult += cb; + } + } + break; + } + + // Clear type nodes + case eNodeClear: + { + XMLClear *pChild=pEntry->pClear+(j>>2); + // "OpenTag" + cb = (int)LENSTR(pChild->lpszOpenTag); + if (cb) + { + if (nFormat!=-1) + { + if (lpszMarker) + { + charmemset(&lpszMarker[nResult], INDENTCHAR, nFormat+1); + xstrcpy(&lpszMarker[nResult+nFormat+1], pChild->lpszOpenTag); + } + nResult+=cb+nFormat+1; + } + else + { + if (lpszMarker)xstrcpy(&lpszMarker[nResult], pChild->lpszOpenTag); + nResult += cb; + } + } + + // "OpenTag Value" + cb = (int)LENSTR(pChild->lpszValue); + if (cb) + { + if (lpszMarker) xstrcpy(&lpszMarker[nResult], pChild->lpszValue); + nResult += cb; + } + + // "OpenTag Value CloseTag" + cb = (int)LENSTR(pChild->lpszCloseTag); + if (cb) + { + if (lpszMarker) xstrcpy(&lpszMarker[nResult], pChild->lpszCloseTag); + nResult += cb; + } + + if (nFormat!=-1) + { + if (lpszMarker) lpszMarker[nResult] = _CXML('\n'); + nResult++; + } + break; + } + + // Element nodes + case eNodeChild: + { + // Recursively add child nodes + nResult += CreateXMLStringR(pEntry->pChild[j>>2].d, lpszMarker ? lpszMarker + nResult : 0, nChildFormat); + break; + } + default: break; + } + } + + if ((cbElement)&&(!pEntry->isDeclaration)) + { + // If we have child entries we need to use long XML notation for + // closing the element - "blah blah blah" + if (nElementI) + { + // "\0" + if (lpszMarker) + { + if (nFormat >=0) + { + charmemset(&lpszMarker[nResult], INDENTCHAR,nFormat); + nResult+=nFormat; + } + + lpszMarker[nResult]=_CXML('<'); lpszMarker[nResult+1]=_CXML('/'); + nResult += 2; + xstrcpy(&lpszMarker[nResult], pEntry->lpszName); + nResult += cbElement; + + lpszMarker[nResult]=_CXML('>'); + if (nFormat == -1) nResult++; + else + { + lpszMarker[nResult+1]=_CXML('\n'); + nResult+=2; + } + } else + { + if (nFormat>=0) nResult+=cbElement+4+nFormat; + else if (nFormat==-1) nResult+=cbElement+3; + else nResult+=cbElement+4; + } + } else + { + // If there are no children we can use shorthand XML notation - + // "" + // "/>\0" + if (lpszMarker) + { + lpszMarker[nResult]=_CXML('/'); lpszMarker[nResult+1]=_CXML('>'); + if (nFormat != -1) lpszMarker[nResult+2]=_CXML('\n'); + } + nResult += nFormat == -1 ? 2 : 3; + } + } + + return nResult; +} + +#undef LENSTR + +// Create an XML string +// @param int nFormat - 0 if no formatting is required +// otherwise nonzero for formatted text +// with carriage returns and indentation. +// @param int *pnSize - [out] pointer to the size of the +// returned string not including the +// NULL terminator. +// @return XMLSTR - Allocated XML string, you must free +// this with free(). +XMLSTR XMLNode::createXMLString(int nFormat, int *pnSize) const +{ + if (!d) { if (pnSize) *pnSize=0; return NULL; } + + XMLSTR lpszResult = NULL; + int cbStr; + + // Recursively Calculate the size of the XML string + if (!dropWhiteSpace) nFormat=0; + nFormat = nFormat ? 0 : -1; + cbStr = CreateXMLStringR(d, 0, nFormat); + // Alllocate memory for the XML string + the NULL terminator and + // create the recursively XML string. + lpszResult=(XMLSTR)malloc((cbStr+1)*sizeof(XMLCHAR)); + CreateXMLStringR(d, lpszResult, nFormat); + lpszResult[cbStr]=_CXML('\0'); + if (pnSize) *pnSize = cbStr; + return lpszResult; +} + +int XMLNode::detachFromParent(XMLNodeData *d) +{ + XMLNode *pa=d->pParent->pChild; + int i=0; + while (((void*)(pa[i].d))!=((void*)d)) i++; + d->pParent->nChild--; + if (d->pParent->nChild) memmove(pa+i,pa+i+1,(d->pParent->nChild-i)*sizeof(XMLNode)); + else { free(pa); d->pParent->pChild=NULL; } + return removeOrderElement(d->pParent,eNodeChild,i); +} + +XMLNode::~XMLNode() +{ + if (!d) return; + d->ref_count--; + emptyTheNode(0); +} +void XMLNode::deleteNodeContent() +{ + if (!d) return; + if (d->pParent) { detachFromParent(d); d->pParent=NULL; d->ref_count--; } + emptyTheNode(1); +} +void XMLNode::emptyTheNode(char force) +{ + XMLNodeData *dd=d; // warning: must stay this way! + if ((dd->ref_count==0)||force) + { + if (d->pParent) detachFromParent(d); + int i; + XMLNode *pc; + for(i=0; inChild; i++) + { + pc=dd->pChild+i; + pc->d->pParent=NULL; + pc->d->ref_count--; + pc->emptyTheNode(force); + } + myFree(dd->pChild); + for(i=0; inText; i++) free((void*)dd->pText[i]); + myFree(dd->pText); + for(i=0; inClear; i++) free((void*)dd->pClear[i].lpszValue); + myFree(dd->pClear); + for(i=0; inAttribute; i++) + { + free((void*)dd->pAttribute[i].lpszName); + if (dd->pAttribute[i].lpszValue) free((void*)dd->pAttribute[i].lpszValue); + } + myFree(dd->pAttribute); + myFree(dd->pOrder); + myFree((void*)dd->lpszName); + dd->nChild=0; dd->nText=0; dd->nClear=0; dd->nAttribute=0; + dd->pChild=NULL; dd->pText=NULL; dd->pClear=NULL; dd->pAttribute=NULL; + dd->pOrder=NULL; dd->lpszName=NULL; dd->pParent=NULL; + } + if (dd->ref_count==0) + { + free(dd); + d=NULL; + } +} + +XMLNode& XMLNode::operator=( const XMLNode& A ) +{ + // shallow copy + if (this != &A) + { + if (d) { d->ref_count--; emptyTheNode(0); } + d=A.d; + if (d) (d->ref_count) ++ ; + } + return *this; +} + +XMLNode::XMLNode(const XMLNode &A) +{ + // shallow copy + d=A.d; + if (d) (d->ref_count)++ ; +} + +XMLNode XMLNode::deepCopy() const +{ + if (!d) return XMLNode::emptyXMLNode; + XMLNode x(NULL,stringDup(d->lpszName),d->isDeclaration); + XMLNodeData *p=x.d; + int n=d->nAttribute; + if (n) + { + p->nAttribute=n; p->pAttribute=(XMLAttribute*)malloc(n*sizeof(XMLAttribute)); + while (n--) + { + p->pAttribute[n].lpszName=stringDup(d->pAttribute[n].lpszName); + p->pAttribute[n].lpszValue=stringDup(d->pAttribute[n].lpszValue); + } + } + if (d->pOrder) + { + n=(d->nChild+d->nText+d->nClear)*sizeof(int); p->pOrder=(int*)malloc(n); memcpy(p->pOrder,d->pOrder,n); + } + n=d->nText; + if (n) + { + p->nText=n; p->pText=(XMLCSTR*)malloc(n*sizeof(XMLCSTR)); + while(n--) p->pText[n]=stringDup(d->pText[n]); + } + n=d->nClear; + if (n) + { + p->nClear=n; p->pClear=(XMLClear*)malloc(n*sizeof(XMLClear)); + while (n--) + { + p->pClear[n].lpszCloseTag=d->pClear[n].lpszCloseTag; + p->pClear[n].lpszOpenTag=d->pClear[n].lpszOpenTag; + p->pClear[n].lpszValue=stringDup(d->pClear[n].lpszValue); + } + } + n=d->nChild; + if (n) + { + p->nChild=n; p->pChild=(XMLNode*)malloc(n*sizeof(XMLNode)); + while (n--) + { + p->pChild[n].d=NULL; + p->pChild[n]=d->pChild[n].deepCopy(); + p->pChild[n].d->pParent=p; + } + } + return x; +} + +XMLNode XMLNode::addChild(XMLNode childNode, int pos) +{ + XMLNodeData *dc=childNode.d; + if ((!dc)||(!d)) return childNode; + if (!dc->lpszName) + { + // this is a root node: todo: correct fix + int j=pos; + while (dc->nChild) + { + addChild(dc->pChild[0],j); + if (pos>=0) j++; + } + return childNode; + } + if (dc->pParent) { if ((detachFromParent(dc)<=pos)&&(dc->pParent==d)) pos--; } else dc->ref_count++; + dc->pParent=d; +// int nc=d->nChild; +// d->pChild=(XMLNode*)myRealloc(d->pChild,(nc+1),memoryIncrease,sizeof(XMLNode)); + d->pChild=(XMLNode*)addToOrder(0,&pos,d->nChild,d->pChild,sizeof(XMLNode),eNodeChild); + d->pChild[pos].d=dc; + d->nChild++; + return childNode; +} + +void XMLNode::deleteAttribute(int i) +{ + if ((!d)||(i<0)||(i>=d->nAttribute)) return; + d->nAttribute--; + XMLAttribute *p=d->pAttribute+i; + free((void*)p->lpszName); + if (p->lpszValue) free((void*)p->lpszValue); + if (d->nAttribute) memmove(p,p+1,(d->nAttribute-i)*sizeof(XMLAttribute)); else { free(p); d->pAttribute=NULL; } +} + +void XMLNode::deleteAttribute(XMLAttribute *a){ if (a) deleteAttribute(a->lpszName); } +void XMLNode::deleteAttribute(XMLCSTR lpszName) +{ + int j=0; + getAttribute(lpszName,&j); + if (j) deleteAttribute(j-1); +} + +XMLAttribute *XMLNode::updateAttribute_WOSD(XMLSTR lpszNewValue, XMLSTR lpszNewName,int i) +{ + if (!d) { if (lpszNewValue) free(lpszNewValue); if (lpszNewName) free(lpszNewName); return NULL; } + if (i>=d->nAttribute) + { + if (lpszNewName) return addAttribute_WOSD(lpszNewName,lpszNewValue); + return NULL; + } + XMLAttribute *p=d->pAttribute+i; + if (p->lpszValue&&p->lpszValue!=lpszNewValue) free((void*)p->lpszValue); + p->lpszValue=lpszNewValue; + if (lpszNewName&&p->lpszName!=lpszNewName) { free((void*)p->lpszName); p->lpszName=lpszNewName; }; + return p; +} + +XMLAttribute *XMLNode::updateAttribute_WOSD(XMLAttribute *newAttribute, XMLAttribute *oldAttribute) +{ + if (oldAttribute) return updateAttribute_WOSD((XMLSTR)newAttribute->lpszValue,(XMLSTR)newAttribute->lpszName,oldAttribute->lpszName); + return addAttribute_WOSD((XMLSTR)newAttribute->lpszName,(XMLSTR)newAttribute->lpszValue); +} + +XMLAttribute *XMLNode::updateAttribute_WOSD(XMLSTR lpszNewValue, XMLSTR lpszNewName,XMLCSTR lpszOldName) +{ + int j=0; + getAttribute(lpszOldName,&j); + if (j) return updateAttribute_WOSD(lpszNewValue,lpszNewName,j-1); + else + { + if (lpszNewName) return addAttribute_WOSD(lpszNewName,lpszNewValue); + else return addAttribute_WOSD(stringDup(lpszOldName),lpszNewValue); + } +} + +int XMLNode::indexText(XMLCSTR lpszValue) const +{ + if (!d) return -1; + int i,l=d->nText; + if (!lpszValue) { if (l) return 0; return -1; } + XMLCSTR *p=d->pText; + for (i=0; i=d->nText)) return; + d->nText--; + XMLCSTR *p=d->pText+i; + free((void*)*p); + if (d->nText) memmove(p,p+1,(d->nText-i)*sizeof(XMLCSTR)); else { free(p); d->pText=NULL; } + removeOrderElement(d,eNodeText,i); +} + +void XMLNode::deleteText(XMLCSTR lpszValue) { deleteText(indexText(lpszValue)); } + +XMLCSTR XMLNode::updateText_WOSD(XMLSTR lpszNewValue, int i) +{ + if (!d) { if (lpszNewValue) free(lpszNewValue); return NULL; } + if (i>=d->nText) return addText_WOSD(lpszNewValue); + XMLCSTR *p=d->pText+i; + if (*p!=lpszNewValue) { free((void*)*p); *p=lpszNewValue; } + return lpszNewValue; +} + +XMLCSTR XMLNode::updateText_WOSD(XMLSTR lpszNewValue, XMLCSTR lpszOldValue) +{ + if (!d) { if (lpszNewValue) free(lpszNewValue); return NULL; } + int i=indexText(lpszOldValue); + if (i>=0) return updateText_WOSD(lpszNewValue,i); + return addText_WOSD(lpszNewValue); +} + +void XMLNode::deleteClear(int i) +{ + if ((!d)||(i<0)||(i>=d->nClear)) return; + d->nClear--; + XMLClear *p=d->pClear+i; + free((void*)p->lpszValue); + if (d->nClear) memmove(p,p+1,(d->nClear-i)*sizeof(XMLClear)); else { free(p); d->pClear=NULL; } + removeOrderElement(d,eNodeClear,i); +} + +int XMLNode::indexClear(XMLCSTR lpszValue) const +{ + if (!d) return -1; + int i,l=d->nClear; + if (!lpszValue) { if (l) return 0; return -1; } + XMLClear *p=d->pClear; + for (i=0; ilpszValue); } + +XMLClear *XMLNode::updateClear_WOSD(XMLSTR lpszNewContent, int i) +{ + if (!d) { if (lpszNewContent) free(lpszNewContent); return NULL; } + if (i>=d->nClear) return addClear_WOSD(lpszNewContent); + XMLClear *p=d->pClear+i; + if (lpszNewContent!=p->lpszValue) { free((void*)p->lpszValue); p->lpszValue=lpszNewContent; } + return p; +} + +XMLClear *XMLNode::updateClear_WOSD(XMLSTR lpszNewContent, XMLCSTR lpszOldValue) +{ + if (!d) { if (lpszNewContent) free(lpszNewContent); return NULL; } + int i=indexClear(lpszOldValue); + if (i>=0) return updateClear_WOSD(lpszNewContent,i); + return addClear_WOSD(lpszNewContent); +} + +XMLClear *XMLNode::updateClear_WOSD(XMLClear *newP,XMLClear *oldP) +{ + if (oldP) return updateClear_WOSD((XMLSTR)newP->lpszValue,(XMLSTR)oldP->lpszValue); + return NULL; +} + +int XMLNode::nChildNode(XMLCSTR name) const +{ + if (!d) return 0; + int i,j=0,n=d->nChild; + XMLNode *pc=d->pChild; + for (i=0; id->lpszName, name)==0) j++; + pc++; + } + return j; +} + +XMLNode XMLNode::getChildNode(XMLCSTR name, int *j) const +{ + if (!d) return emptyXMLNode; + int i=0,n=d->nChild; + if (j) i=*j; + XMLNode *pc=d->pChild+i; + for (; id->lpszName, name)) + { + if (j) *j=i+1; + return *pc; + } + pc++; + } + return emptyXMLNode; +} + +XMLNode XMLNode::getChildNode(XMLCSTR name, int j) const +{ + if (!d) return emptyXMLNode; + if (j>=0) + { + int i=0; + while (j-->0) getChildNode(name,&i); + return getChildNode(name,&i); + } + int i=d->nChild; + while (i--) if (!xstricmp(name,d->pChild[i].d->lpszName)) break; + if (i<0) return emptyXMLNode; + return getChildNode(i); +} + +XMLNode XMLNode::getChildNodeByPath(XMLCSTR _path, char createMissing, XMLCHAR sep) +{ + XMLSTR path=stringDup(_path); + XMLNode x=getChildNodeByPathNonConst(path,createMissing,sep); + if (path) free(path); + return x; +} + +XMLNode XMLNode::getChildNodeByPathNonConst(XMLSTR path, char createIfMissing, XMLCHAR sep) +{ + if ((!path)||(!(*path))) return *this; + XMLNode xn,xbase=*this; + XMLCHAR *tend1,sepString[2]; sepString[0]=sep; sepString[1]=0; + tend1=xstrstr(path,sepString); + while(tend1) + { + *tend1=0; + xn=xbase.getChildNode(path); + if (xn.isEmpty()) + { + if (createIfMissing) xn=xbase.addChild(path); + else { *tend1=sep; return XMLNode::emptyXMLNode; } + } + *tend1=sep; + xbase=xn; + path=tend1+1; + tend1=xstrstr(path,sepString); + } + xn=xbase.getChildNode(path); + if (xn.isEmpty()&&createIfMissing) xn=xbase.addChild(path); + return xn; +} + +XMLElementPosition XMLNode::positionOfText (int i) const { if (i>=d->nText ) i=d->nText-1; return findPosition(d,i,eNodeText ); } +XMLElementPosition XMLNode::positionOfClear (int i) const { if (i>=d->nClear) i=d->nClear-1; return findPosition(d,i,eNodeClear); } +XMLElementPosition XMLNode::positionOfChildNode(int i) const { if (i>=d->nChild) i=d->nChild-1; return findPosition(d,i,eNodeChild); } +XMLElementPosition XMLNode::positionOfText (XMLCSTR lpszValue) const { return positionOfText (indexText (lpszValue)); } +XMLElementPosition XMLNode::positionOfClear(XMLCSTR lpszValue) const { return positionOfClear(indexClear(lpszValue)); } +XMLElementPosition XMLNode::positionOfClear(XMLClear *a) const { if (a) return positionOfClear(a->lpszValue); return positionOfClear(); } +XMLElementPosition XMLNode::positionOfChildNode(XMLNode x) const +{ + if ((!d)||(!x.d)) return -1; + XMLNodeData *dd=x.d; + XMLNode *pc=d->pChild; + int i=d->nChild; + while (i--) if (pc[i].d==dd) return findPosition(d,i,eNodeChild); + return -1; +} +XMLElementPosition XMLNode::positionOfChildNode(XMLCSTR name, int count) const +{ + if (!name) return positionOfChildNode(count); + int j=0; + do { getChildNode(name,&j); if (j<0) return -1; } while (count--); + return findPosition(d,j-1,eNodeChild); +} + +XMLNode XMLNode::getChildNodeWithAttribute(XMLCSTR name,XMLCSTR attributeName,XMLCSTR attributeValue, int *k) const +{ + int i=0,j; + if (k) i=*k; + XMLNode x; + XMLCSTR t; + do + { + x=getChildNode(name,&i); + if (!x.isEmpty()) + { + if (attributeValue) + { + j=0; + do + { + t=x.getAttribute(attributeName,&j); + if (t&&(xstricmp(attributeValue,t)==0)) { if (k) *k=i; return x; } + } while (t); + } else + { + if (x.isAttributeSet(attributeName)) { if (k) *k=i; return x; } + } + } + } while (!x.isEmpty()); + return emptyXMLNode; +} + +// Find an attribute on an node. +XMLCSTR XMLNode::getAttribute(XMLCSTR lpszAttrib, int *j) const +{ + if (!d) return NULL; + int i=0,n=d->nAttribute; + if (j) i=*j; + XMLAttribute *pAttr=d->pAttribute+i; + for (; ilpszName, lpszAttrib)==0) + { + if (j) *j=i+1; + return pAttr->lpszValue; + } + pAttr++; + } + return NULL; +} + +char XMLNode::isAttributeSet(XMLCSTR lpszAttrib) const +{ + if (!d) return FALSE; + int i,n=d->nAttribute; + XMLAttribute *pAttr=d->pAttribute; + for (i=0; ilpszName, lpszAttrib)==0) + { + return TRUE; + } + pAttr++; + } + return FALSE; +} + +XMLCSTR XMLNode::getAttribute(XMLCSTR name, int j) const +{ + if (!d) return NULL; + int i=0; + while (j-->0) getAttribute(name,&i); + return getAttribute(name,&i); +} + +XMLNodeContents XMLNode::enumContents(int i) const +{ + XMLNodeContents c; + if (!d) { c.etype=eNodeNULL; return c; } + if (inAttribute) + { + c.etype=eNodeAttribute; + c.attrib=d->pAttribute[i]; + return c; + } + i-=d->nAttribute; + c.etype=(XMLElementType)(d->pOrder[i]&3); + i=(d->pOrder[i])>>2; + switch (c.etype) + { + case eNodeChild: c.child = d->pChild[i]; break; + case eNodeText: c.text = d->pText[i]; break; + case eNodeClear: c.clear = d->pClear[i]; break; + default: break; + } + return c; +} + +XMLCSTR XMLNode::getName() const { if (!d) return NULL; return d->lpszName; } +int XMLNode::nText() const { if (!d) return 0; return d->nText; } +int XMLNode::nChildNode() const { if (!d) return 0; return d->nChild; } +int XMLNode::nAttribute() const { if (!d) return 0; return d->nAttribute; } +int XMLNode::nClear() const { if (!d) return 0; return d->nClear; } +int XMLNode::nElement() const { if (!d) return 0; return d->nAttribute+d->nChild+d->nText+d->nClear; } +XMLClear XMLNode::getClear (int i) const { if ((!d)||(i>=d->nClear )) return emptyXMLClear; return d->pClear[i]; } +XMLAttribute XMLNode::getAttribute (int i) const { if ((!d)||(i>=d->nAttribute)) return emptyXMLAttribute; return d->pAttribute[i]; } +XMLCSTR XMLNode::getAttributeName (int i) const { if ((!d)||(i>=d->nAttribute)) return NULL; return d->pAttribute[i].lpszName; } +XMLCSTR XMLNode::getAttributeValue(int i) const { if ((!d)||(i>=d->nAttribute)) return NULL; return d->pAttribute[i].lpszValue; } +XMLCSTR XMLNode::getText (int i) const { if ((!d)||(i>=d->nText )) return NULL; return d->pText[i]; } +XMLNode XMLNode::getChildNode (int i) const { if ((!d)||(i>=d->nChild )) return emptyXMLNode; return d->pChild[i]; } +XMLNode XMLNode::getParentNode ( ) const { if ((!d)||(!d->pParent )) return emptyXMLNode; return XMLNode(d->pParent); } +char XMLNode::isDeclaration ( ) const { if (!d) return 0; return d->isDeclaration; } +char XMLNode::isEmpty ( ) const { return (d==NULL); } +XMLNode XMLNode::emptyNode ( ) { return XMLNode::emptyXMLNode; } + +XMLNode XMLNode::addChild(XMLCSTR lpszName, char isDeclaration, XMLElementPosition pos) + { return addChild_priv(0,stringDup(lpszName),isDeclaration,pos); } +XMLNode XMLNode::addChild_WOSD(XMLSTR lpszName, char isDeclaration, XMLElementPosition pos) + { return addChild_priv(0,lpszName,isDeclaration,pos); } +XMLAttribute *XMLNode::addAttribute(XMLCSTR lpszName, XMLCSTR lpszValue) + { return addAttribute_priv(0,stringDup(lpszName),stringDup(lpszValue)); } +XMLAttribute *XMLNode::addAttribute_WOSD(XMLSTR lpszName, XMLSTR lpszValuev) + { return addAttribute_priv(0,lpszName,lpszValuev); } +XMLCSTR XMLNode::addText(XMLCSTR lpszValue, XMLElementPosition pos) + { return addText_priv(0,stringDup(lpszValue),pos); } +XMLCSTR XMLNode::addText_WOSD(XMLSTR lpszValue, XMLElementPosition pos) + { return addText_priv(0,lpszValue,pos); } +XMLClear *XMLNode::addClear(XMLCSTR lpszValue, XMLCSTR lpszOpen, XMLCSTR lpszClose, XMLElementPosition pos) + { return addClear_priv(0,stringDup(lpszValue),lpszOpen,lpszClose,pos); } +XMLClear *XMLNode::addClear_WOSD(XMLSTR lpszValue, XMLCSTR lpszOpen, XMLCSTR lpszClose, XMLElementPosition pos) + { return addClear_priv(0,lpszValue,lpszOpen,lpszClose,pos); } +XMLCSTR XMLNode::updateName(XMLCSTR lpszName) + { return updateName_WOSD(stringDup(lpszName)); } +XMLAttribute *XMLNode::updateAttribute(XMLAttribute *newAttribute, XMLAttribute *oldAttribute) + { return updateAttribute_WOSD(stringDup(newAttribute->lpszValue),stringDup(newAttribute->lpszName),oldAttribute->lpszName); } +XMLAttribute *XMLNode::updateAttribute(XMLCSTR lpszNewValue, XMLCSTR lpszNewName,int i) + { return updateAttribute_WOSD(stringDup(lpszNewValue),stringDup(lpszNewName),i); } +XMLAttribute *XMLNode::updateAttribute(XMLCSTR lpszNewValue, XMLCSTR lpszNewName,XMLCSTR lpszOldName) + { return updateAttribute_WOSD(stringDup(lpszNewValue),stringDup(lpszNewName),lpszOldName); } +XMLCSTR XMLNode::updateText(XMLCSTR lpszNewValue, int i) + { return updateText_WOSD(stringDup(lpszNewValue),i); } +XMLCSTR XMLNode::updateText(XMLCSTR lpszNewValue, XMLCSTR lpszOldValue) + { return updateText_WOSD(stringDup(lpszNewValue),lpszOldValue); } +XMLClear *XMLNode::updateClear(XMLCSTR lpszNewContent, int i) + { return updateClear_WOSD(stringDup(lpszNewContent),i); } +XMLClear *XMLNode::updateClear(XMLCSTR lpszNewValue, XMLCSTR lpszOldValue) + { return updateClear_WOSD(stringDup(lpszNewValue),lpszOldValue); } +XMLClear *XMLNode::updateClear(XMLClear *newP,XMLClear *oldP) + { return updateClear_WOSD(stringDup(newP->lpszValue),oldP->lpszValue); } + +char XMLNode::setGlobalOptions(XMLCharEncoding _characterEncoding, char _guessWideCharChars, + char _dropWhiteSpace, char _removeCommentsInMiddleOfText) +{ + guessWideCharChars=_guessWideCharChars; dropWhiteSpace=_dropWhiteSpace; removeCommentsInMiddleOfText=_removeCommentsInMiddleOfText; +#ifdef _XMLWIDECHAR + if (_characterEncoding) characterEncoding=_characterEncoding; +#else + switch(_characterEncoding) + { + case char_encoding_UTF8: characterEncoding=_characterEncoding; XML_ByteTable=XML_utf8ByteTable; break; + case char_encoding_legacy: characterEncoding=_characterEncoding; XML_ByteTable=XML_legacyByteTable; break; + case char_encoding_ShiftJIS: characterEncoding=_characterEncoding; XML_ByteTable=XML_sjisByteTable; break; + case char_encoding_GB2312: characterEncoding=_characterEncoding; XML_ByteTable=XML_gb2312ByteTable; break; + case char_encoding_Big5: + case char_encoding_GBK: characterEncoding=_characterEncoding; XML_ByteTable=XML_gbk_big5_ByteTable; break; + default: return 1; + } +#endif + return 0; +} + +XMLNode::XMLCharEncoding XMLNode::guessCharEncoding(void *buf,int l, char useXMLEncodingAttribute) +{ +#ifdef _XMLWIDECHAR + return (XMLCharEncoding)0; +#else + if (l<25) return (XMLCharEncoding)0; + if (guessWideCharChars&&(myIsTextWideChar(buf,l))) return (XMLCharEncoding)0; + unsigned char *b=(unsigned char*)buf; + if ((b[0]==0xef)&&(b[1]==0xbb)&&(b[2]==0xbf)) return char_encoding_UTF8; + + // Match utf-8 model ? + XMLCharEncoding bestGuess=char_encoding_UTF8; + int i=0; + while (i>2 ]; + *(curr++)=base64EncodeTable[(inbuf[0]<<4)&0x3F]; + *(curr++)=base64Fillchar; + *(curr++)=base64Fillchar; + } else if (eLen==2) + { + j=(inbuf[0]<<8)|inbuf[1]; + *(curr++)=base64EncodeTable[ j>>10 ]; + *(curr++)=base64EncodeTable[(j>> 4)&0x3f]; + *(curr++)=base64EncodeTable[(j<< 2)&0x3f]; + *(curr++)=base64Fillchar; + } + *(curr++)=0; + return (XMLSTR)buf; +} + +unsigned int XMLParserBase64Tool::decodeSize(XMLCSTR data,XMLError *xe) +{ + if (!data) return 0; + if (xe) *xe=eXMLErrorNone; + int size=0; + unsigned char c; + //skip any extra characters (e.g. newlines or spaces) + while (*data) + { +#ifdef _XMLWIDECHAR + if (*data>255) { if (xe) *xe=eXMLErrorBase64DecodeIllegalCharacter; return 0; } +#endif + c=base64DecodeTable[(unsigned char)(*data)]; + if (c<97) size++; + else if (c==98) { if (xe) *xe=eXMLErrorBase64DecodeIllegalCharacter; return 0; } + data++; + } + if (xe&&(size%4!=0)) *xe=eXMLErrorBase64DataSizeIsNotMultipleOf4; + if (size==0) return 0; + do { data--; size--; } while(*data==base64Fillchar); size++; + return (unsigned int)((size*3)/4); +} + +unsigned char XMLParserBase64Tool::decode(XMLCSTR data, unsigned char *buf, int len, XMLError *xe) +{ + if (!data) return 0; + if (xe) *xe=eXMLErrorNone; + int i=0,p=0; + unsigned char d,c; + for(;;) + { + +#ifdef _XMLWIDECHAR +#define BASE64DECODE_READ_NEXT_CHAR(c) \ + do { \ + if (data[i]>255){ c=98; break; } \ + c=base64DecodeTable[(unsigned char)data[i++]]; \ + }while (c==97); \ + if(c==98){ if(xe)*xe=eXMLErrorBase64DecodeIllegalCharacter; return 0; } +#else +#define BASE64DECODE_READ_NEXT_CHAR(c) \ + do { c=base64DecodeTable[(unsigned char)data[i++]]; }while (c==97); \ + if(c==98){ if(xe)*xe=eXMLErrorBase64DecodeIllegalCharacter; return 0; } +#endif + + BASE64DECODE_READ_NEXT_CHAR(c) + if (c==99) { return 2; } + if (c==96) + { + if (p==(int)len) return 2; + if (xe) *xe=eXMLErrorBase64DecodeTruncatedData; + return 1; + } + + BASE64DECODE_READ_NEXT_CHAR(d) + if ((d==99)||(d==96)) { if (xe) *xe=eXMLErrorBase64DecodeTruncatedData; return 1; } + if (p==(int)len) { if (xe) *xe=eXMLErrorBase64DecodeBufferTooSmall; return 0; } + buf[p++]=(unsigned char)((c<<2)|((d>>4)&0x3)); + + BASE64DECODE_READ_NEXT_CHAR(c) + if (c==99) { if (xe) *xe=eXMLErrorBase64DecodeTruncatedData; return 1; } + if (p==(int)len) + { + if (c==96) return 2; + if (xe) *xe=eXMLErrorBase64DecodeBufferTooSmall; + return 0; + } + if (c==96) { if (xe) *xe=eXMLErrorBase64DecodeTruncatedData; return 1; } + buf[p++]=(unsigned char)(((d<<4)&0xf0)|((c>>2)&0xf)); + + BASE64DECODE_READ_NEXT_CHAR(d) + if (d==99 ) { if (xe) *xe=eXMLErrorBase64DecodeTruncatedData; return 1; } + if (p==(int)len) + { + if (d==96) return 2; + if (xe) *xe=eXMLErrorBase64DecodeBufferTooSmall; + return 0; + } + if (d==96) { if (xe) *xe=eXMLErrorBase64DecodeTruncatedData; return 1; } + buf[p++]=(unsigned char)(((c<<6)&0xc0)|d); + } +} +#undef BASE64DECODE_READ_NEXT_CHAR + +void XMLParserBase64Tool::alloc(int newsize) +{ + if ((!buf)&&(newsize)) { buf=malloc(newsize); buflen=newsize; return; } + if (newsize>buflen) { buf=realloc(buf,newsize); buflen=newsize; } +} + +unsigned char *XMLParserBase64Tool::decode(XMLCSTR data, int *outlen, XMLError *xe) +{ + if (xe) *xe=eXMLErrorNone; + if (!data) { *outlen=0; return (unsigned char*)""; } + unsigned int len=decodeSize(data,xe); + if (outlen) *outlen=len; + if (!len) return NULL; + alloc(len+1); + if(!decode(data,(unsigned char*)buf,len,xe)){ return NULL; } + return (unsigned char*)buf; +} + diff --git a/source/common/xmlParser.h b/source/common/xmlParser.h new file mode 100644 index 0000000..1187165 --- /dev/null +++ b/source/common/xmlParser.h @@ -0,0 +1,732 @@ +/****************************************************************************/ +/*! \mainpage XMLParser library + * \section intro_sec Introduction + * + * This is a basic XML parser written in ANSI C++ for portability. + * It works by using recursion and a node tree for breaking + * down the elements of an XML document. + * + * @version V2.44 + * @author Frank Vanden Berghen + * + * Copyright (c) 2002, Frank Vanden Berghen - All rights reserved.
+ * Commercialized by Business-Insight
+ * See the file AFPL-license.txt about the licensing terms + * + * \section tutorial First Tutorial + * You can follow a simple Tutorial to know the basics... + * + * \section usage General usage: How to include the XMLParser library inside your project. + * + * The library is composed of two files: xmlParser.cpp and + * xmlParser.h. These are the ONLY 2 files that you need when + * using the library inside your own projects. + * + * All the functions of the library are documented inside the comments of the file + * xmlParser.h. These comments can be transformed in + * full-fledged HTML documentation using the DOXYGEN software: simply type: "doxygen doxy.cfg" + * + * By default, the XMLParser library uses (char*) for string representation.To use the (wchar_t*) + * version of the library, you need to define the "_UNICODE" preprocessor definition variable + * (this is usually done inside your project definition file) (This is done automatically for you + * when using Visual Studio). + * + * \section example Advanced Tutorial and Many Examples of usage. + * + * Some very small introductory examples are described inside the Tutorial file + * xmlParser.html + * + * Some additional small examples are also inside the file xmlTest.cpp + * (for the "char*" version of the library) and inside the file + * xmlTestUnicode.cpp (for the "wchar_t*" + * version of the library). If you have a question, please review these additionnal examples + * before sending an e-mail to the author. + * + * To build the examples: + * - linux/unix: type "make" + * - solaris: type "make -f makefile.solaris" + * - windows: Visual Studio: double-click on xmlParser.dsw + * (under Visual Studio .NET, the .dsp and .dsw files will be automatically converted to .vcproj and .sln files) + * + * In order to build the examples you need some additional files: + * - linux/unix: makefile + * - solaris: makefile.solaris + * - windows: Visual Studio: *.dsp, xmlParser.dsw and also xmlParser.lib and xmlParser.dll + * + * \section debugging Debugging with the XMLParser library + * + * \subsection debugwin Debugging under WINDOWS + * + * Inside Visual C++, the "debug versions" of the memory allocation functions are + * very slow: Do not forget to compile in "release mode" to get maximum speed. + * When I had to debug a software that was using the XMLParser Library, it was usually + * a nightmare because the library was sooOOOoooo slow in debug mode (because of the + * slow memory allocations in Debug mode). To solve this + * problem, during all the debugging session, I am now using a very fast DLL version of the + * XMLParser Library (the DLL is compiled in release mode). Using the DLL version of + * the XMLParser Library allows me to have lightening XML parsing speed even in debug! + * Other than that, the DLL version is useless: In the release version of my tool, + * I always use the normal, ".cpp"-based, XMLParser Library (I simply include the + * xmlParser.cpp and + * xmlParser.h files into the project). + * + * The file XMLNodeAutoexp.txt contains some + * "tweaks" that improve substancially the display of the content of the XMLNode objects + * inside the Visual Studio Debugger. Believe me, once you have seen inside the debugger + * the "smooth" display of the XMLNode objects, you cannot live without it anymore! + * + * \subsection debuglinux Debugging under LINUX/UNIX + * + * The speed of the debug version of the XMLParser library is tolerable so no extra + * work.has been done. + * + ****************************************************************************/ + +#ifndef __INCLUDE_XML_NODE__ +#define __INCLUDE_XML_NODE__ + +#include + +#if defined(UNICODE) || defined(_UNICODE) +// If you comment the next "define" line then the library will never "switch to" _UNICODE (wchar_t*) mode (16/32 bits per characters). +// This is useful when you get error messages like: +// 'XMLNode::openFileHelper' : cannot convert parameter 2 from 'const char [5]' to 'const wchar_t *' +// The _XMLWIDECHAR preprocessor variable force the XMLParser library into either utf16/32-mode (the proprocessor variable +// must be defined) or utf8-mode(the pre-processor variable must be undefined). +#define _XMLWIDECHAR +#endif + +#if defined(WIN32) || defined(UNDER_CE) || defined(_WIN32) || defined(WIN64) || defined(__BORLANDC__) +// comment the next line if you are under windows and the compiler is not Microsoft Visual Studio (6.0 or .NET) or Borland +#define _XMLWINDOWS +#endif + +#ifdef XMLDLLENTRY +#undef XMLDLLENTRY +#endif +#ifdef _USE_XMLPARSER_DLL +#ifdef _DLL_EXPORTS_ +#define XMLDLLENTRY __declspec(dllexport) +#else +#define XMLDLLENTRY __declspec(dllimport) +#endif +#else +#define XMLDLLENTRY +#endif + +// uncomment the next line if you want no support for wchar_t* (no need for the or libraries anymore to compile) +//#define XML_NO_WIDE_CHAR + +#ifdef XML_NO_WIDE_CHAR +#undef _XMLWINDOWS +#undef _XMLWIDECHAR +#endif + +#ifdef _XMLWINDOWS +#include +#else +#define XMLDLLENTRY +#ifndef XML_NO_WIDE_CHAR +#include // to have 'wcsrtombs' for ANSI version + // to have 'mbsrtowcs' for WIDECHAR version +#endif +#endif + +// Some common types for char set portable code +#ifdef _XMLWIDECHAR + #define _CXML(c) L ## c + #define XMLCSTR const wchar_t * + #define XMLSTR wchar_t * + #define XMLCHAR wchar_t +#else + #define _CXML(c) c + #define XMLCSTR const char * + #define XMLSTR char * + #define XMLCHAR char +#endif +#ifndef FALSE + #define FALSE 0 +#endif /* FALSE */ +#ifndef TRUE + #define TRUE 1 +#endif /* TRUE */ + + +/// Enumeration for XML parse errors. +typedef enum XMLError +{ + eXMLErrorNone = 0, + eXMLErrorMissingEndTag, + eXMLErrorNoXMLTagFound, + eXMLErrorEmpty, + eXMLErrorMissingTagName, + eXMLErrorMissingEndTagName, + eXMLErrorUnmatchedEndTag, + eXMLErrorUnmatchedEndClearTag, + eXMLErrorUnexpectedToken, + eXMLErrorNoElements, + eXMLErrorFileNotFound, + eXMLErrorFirstTagNotFound, + eXMLErrorUnknownCharacterEntity, + eXMLErrorCharacterCodeAbove255, + eXMLErrorCharConversionError, + eXMLErrorCannotOpenWriteFile, + eXMLErrorCannotWriteFile, + + eXMLErrorBase64DataSizeIsNotMultipleOf4, + eXMLErrorBase64DecodeIllegalCharacter, + eXMLErrorBase64DecodeTruncatedData, + eXMLErrorBase64DecodeBufferTooSmall +} XMLError; + + +/// Enumeration used to manage type of data. Use in conjunction with structure XMLNodeContents +typedef enum XMLElementType +{ + eNodeChild=0, + eNodeAttribute=1, + eNodeText=2, + eNodeClear=3, + eNodeNULL=4 +} XMLElementType; + +/// Structure used to obtain error details if the parse fails. +typedef struct XMLResults +{ + enum XMLError error; + int nLine,nColumn; +} XMLResults; + +/// Structure for XML clear (unformatted) node (usually comments) +typedef struct XMLClear { + XMLCSTR lpszValue; XMLCSTR lpszOpenTag; XMLCSTR lpszCloseTag; +} XMLClear; + +/// Structure for XML attribute. +typedef struct XMLAttribute { + XMLCSTR lpszName; XMLCSTR lpszValue; +} XMLAttribute; + +/// XMLElementPosition are not interchangeable with simple indexes +typedef int XMLElementPosition; + +struct XMLNodeContents; + +/** @defgroup XMLParserGeneral The XML parser */ + +/// Main Class representing a XML node +/** + * All operations are performed using this class. + * \note The constructors of the XMLNode class are protected, so use instead one of these four methods to get your first instance of XMLNode: + *
    + *
  • XMLNode::parseString
  • + *
  • XMLNode::parseFile
  • + *
  • XMLNode::openFileHelper
  • + *
  • XMLNode::createXMLTopNode (or XMLNode::createXMLTopNode_WOSD)
  • + *
*/ +typedef struct XMLDLLENTRY XMLNode +{ + private: + + struct XMLNodeDataTag; + + /// Constructors are protected, so use instead one of: XMLNode::parseString, XMLNode::parseFile, XMLNode::openFileHelper, XMLNode::createXMLTopNode + XMLNode(struct XMLNodeDataTag *pParent, XMLSTR lpszName, char isDeclaration); + /// Constructors are protected, so use instead one of: XMLNode::parseString, XMLNode::parseFile, XMLNode::openFileHelper, XMLNode::createXMLTopNode + XMLNode(struct XMLNodeDataTag *p); + + public: + static XMLCSTR getVersion();///< Return the XMLParser library version number + + /** @defgroup conversions Parsing XML files/strings to an XMLNode structure and Rendering XMLNode's to files/string. + * @ingroup XMLParserGeneral + * @{ */ + + /// Parse an XML string and return the root of a XMLNode tree representing the string. + static XMLNode parseString (XMLCSTR lpXMLString, XMLCSTR tag=NULL, XMLResults *pResults=NULL); + /**< The "parseString" function parse an XML string and return the root of a XMLNode tree. The "opposite" of this function is + * the function "createXMLString" that re-creates an XML string from an XMLNode tree. If the XML document is corrupted, the + * "parseString" method will initialize the "pResults" variable with some information that can be used to trace the error. + * If you still want to parse the file, you can use the APPROXIMATE_PARSING option as explained inside the note at the + * beginning of the "xmlParser.cpp" file. + * + * @param lpXMLString the XML string to parse + * @param tag the name of the first tag inside the XML file. If the tag parameter is omitted, this function returns a node that represents the head of the xml document including the declaration term (). + * @param pResults a pointer to a XMLResults variable that will contain some information that can be used to trace the XML parsing error. You can have a user-friendly explanation of the parsing error with the "getError" function. + */ + + /// Parse an XML file and return the root of a XMLNode tree representing the file. + static XMLNode parseFile (XMLCSTR filename, XMLCSTR tag=NULL, XMLResults *pResults=NULL); + /**< The "parseFile" function parse an XML file and return the root of a XMLNode tree. The "opposite" of this function is + * the function "writeToFile" that re-creates an XML file from an XMLNode tree. If the XML document is corrupted, the + * "parseFile" method will initialize the "pResults" variable with some information that can be used to trace the error. + * If you still want to parse the file, you can use the APPROXIMATE_PARSING option as explained inside the note at the + * beginning of the "xmlParser.cpp" file. + * + * @param filename the path to the XML file to parse + * @param tag the name of the first tag inside the XML file. If the tag parameter is omitted, this function returns a node that represents the head of the xml document including the declaration term (). + * @param pResults a pointer to a XMLResults variable that will contain some information that can be used to trace the XML parsing error. You can have a user-friendly explanation of the parsing error with the "getError" function. + */ + + /// Parse an XML file and return the root of a XMLNode tree representing the file. A very crude error checking is made. An attempt to guess the Char Encoding used in the file is made. + static XMLNode openFileHelper(XMLCSTR filename, XMLCSTR tag=NULL); + /**< The "openFileHelper" function reports to the screen all the warnings and errors that occurred during parsing of the XML file. + * This function also tries to guess char Encoding (UTF-8, ASCII or SHIT-JIS) based on the first 200 bytes of the file. Since each + * application has its own way to report and deal with errors, you should rather use the "parseFile" function to parse XML files + * and program yourself thereafter an "error reporting" tailored for your needs (instead of using the very crude "error reporting" + * mechanism included inside the "openFileHelper" function). + * + * If the XML document is corrupted, the "openFileHelper" method will: + * - display an error message on the console (or inside a messageBox for windows). + * - stop execution (exit). + * + * I strongly suggest that you write your own "openFileHelper" method tailored to your needs. If you still want to parse + * the file, you can use the APPROXIMATE_PARSING option as explained inside the note at the beginning of the "xmlParser.cpp" file. + * + * @param filename the path of the XML file to parse. + * @param tag the name of the first tag inside the XML file. If the tag parameter is omitted, this function returns a node that represents the head of the xml document including the declaration term (). + */ + + static XMLCSTR getError(XMLError error); ///< this gives you a user-friendly explanation of the parsing error + + /// Create an XML string starting from the current XMLNode. + XMLSTR createXMLString(int nFormat=1, int *pnSize=NULL) const; + /**< The returned string should be free'd using the "freeXMLString" function. + * + * If nFormat==0, no formatting is required otherwise this returns an user friendly XML string from a given element + * with appropriate white spaces and carriage returns. if pnSize is given it returns the size in character of the string. */ + + /// Save the content of an xmlNode inside a file + XMLError writeToFile(XMLCSTR filename, + const char *encoding=NULL, + char nFormat=1) const; + /**< If nFormat==0, no formatting is required otherwise this returns an user friendly XML string from a given element with appropriate white spaces and carriage returns. + * If the global parameter "characterEncoding==encoding_UTF8", then the "encoding" parameter is ignored and always set to "utf-8". + * If the global parameter "characterEncoding==encoding_ShiftJIS", then the "encoding" parameter is ignored and always set to "SHIFT-JIS". + * If "_XMLWIDECHAR=1", then the "encoding" parameter is ignored and always set to "utf-16". + * If no "encoding" parameter is given the "ISO-8859-1" encoding is used. */ + /** @} */ + + /** @defgroup navigate Navigate the XMLNode structure + * @ingroup XMLParserGeneral + * @{ */ + XMLCSTR getName() const; ///< name of the node + XMLCSTR getText(int i=0) const; ///< return ith text field + int nText() const; ///< nbr of text field + XMLNode getParentNode() const; ///< return the parent node + XMLNode getChildNode(int i=0) const; ///< return ith child node + XMLNode getChildNode(XMLCSTR name, int i) const; ///< return ith child node with specific name (return an empty node if failing). If i==-1, this returns the last XMLNode with the given name. + XMLNode getChildNode(XMLCSTR name, int *i=NULL) const; ///< return next child node with specific name (return an empty node if failing) + XMLNode getChildNodeWithAttribute(XMLCSTR tagName, + XMLCSTR attributeName, + XMLCSTR attributeValue=NULL, + int *i=NULL) const; ///< return child node with specific name/attribute (return an empty node if failing) + XMLNode getChildNodeByPath(XMLCSTR path, char createNodeIfMissing=0, XMLCHAR sep='/'); + ///< return the first child node with specific path + XMLNode getChildNodeByPathNonConst(XMLSTR path, char createNodeIfMissing=0, XMLCHAR sep='/'); + ///< return the first child node with specific path. + + int nChildNode(XMLCSTR name) const; ///< return the number of child node with specific name + int nChildNode() const; ///< nbr of child node + XMLAttribute getAttribute(int i=0) const; ///< return ith attribute + XMLCSTR getAttributeName(int i=0) const; ///< return ith attribute name + XMLCSTR getAttributeValue(int i=0) const; ///< return ith attribute value + char isAttributeSet(XMLCSTR name) const; ///< test if an attribute with a specific name is given + XMLCSTR getAttribute(XMLCSTR name, int i) const; ///< return ith attribute content with specific name (return a NULL if failing) + XMLCSTR getAttribute(XMLCSTR name, int *i=NULL) const; ///< return next attribute content with specific name (return a NULL if failing) + int nAttribute() const; ///< nbr of attribute + XMLClear getClear(int i=0) const; ///< return ith clear field (comments) + int nClear() const; ///< nbr of clear field + XMLNodeContents enumContents(XMLElementPosition i) const; ///< enumerate all the different contents (attribute,child,text, clear) of the current XMLNode. The order is reflecting the order of the original file/string. NOTE: 0 <= i < nElement(); + int nElement() const; ///< nbr of different contents for current node + char isEmpty() const; ///< is this node Empty? + char isDeclaration() const; ///< is this node a declaration + XMLNode deepCopy() const; ///< deep copy (duplicate/clone) a XMLNode + static XMLNode emptyNode(); ///< return XMLNode::emptyXMLNode; + /** @} */ + + ~XMLNode(); + XMLNode(const XMLNode &A); ///< to allow shallow/fast copy: + XMLNode& operator=( const XMLNode& A ); ///< to allow shallow/fast copy: + + XMLNode(): d(NULL){}; + static XMLNode emptyXMLNode; + static XMLClear emptyXMLClear; + static XMLAttribute emptyXMLAttribute; + + /** @defgroup xmlModify Create or Update the XMLNode structure + * @ingroup XMLParserGeneral + * The functions in this group allows you to create from scratch (or update) a XMLNode structure. Start by creating your top + * node with the "createXMLTopNode" function and then add new nodes with the "addChild" function. The parameter 'pos' gives + * the position where the childNode, the text or the XMLClearTag will be inserted. The default value (pos=-1) inserts at the + * end. The value (pos=0) insert at the beginning (Insertion at the beginning is slower than at the end).
+ * + * REMARK: 0 <= pos < nChild()+nText()+nClear()
+ */ + + /** @defgroup creation Creating from scratch a XMLNode structure + * @ingroup xmlModify + * @{ */ + static XMLNode createXMLTopNode(XMLCSTR lpszName, char isDeclaration=FALSE); ///< Create the top node of an XMLNode structure + XMLNode addChild(XMLCSTR lpszName, char isDeclaration=FALSE, XMLElementPosition pos=-1); ///< Add a new child node + XMLNode addChild(XMLNode nodeToAdd, XMLElementPosition pos=-1); ///< If the "nodeToAdd" has some parents, it will be detached from it's parents before being attached to the current XMLNode + XMLAttribute *addAttribute(XMLCSTR lpszName, XMLCSTR lpszValuev); ///< Add a new attribute + XMLCSTR addText(XMLCSTR lpszValue, XMLElementPosition pos=-1); ///< Add a new text content + XMLClear *addClear(XMLCSTR lpszValue, XMLCSTR lpszOpen=NULL, XMLCSTR lpszClose=NULL, XMLElementPosition pos=-1); + /**< Add a new clear tag + * @param lpszOpen default value "" + */ + /** @} */ + + /** @defgroup xmlUpdate Updating Nodes + * @ingroup xmlModify + * Some update functions: + * @{ + */ + XMLCSTR updateName(XMLCSTR lpszName); ///< change node's name + XMLAttribute *updateAttribute(XMLAttribute *newAttribute, XMLAttribute *oldAttribute); ///< if the attribute to update is missing, a new one will be added + XMLAttribute *updateAttribute(XMLCSTR lpszNewValue, XMLCSTR lpszNewName=NULL,int i=0); ///< if the attribute to update is missing, a new one will be added + XMLAttribute *updateAttribute(XMLCSTR lpszNewValue, XMLCSTR lpszNewName,XMLCSTR lpszOldName);///< set lpszNewName=NULL if you don't want to change the name of the attribute if the attribute to update is missing, a new one will be added + XMLCSTR updateText(XMLCSTR lpszNewValue, int i=0); ///< if the text to update is missing, a new one will be added + XMLCSTR updateText(XMLCSTR lpszNewValue, XMLCSTR lpszOldValue); ///< if the text to update is missing, a new one will be added + XMLClear *updateClear(XMLCSTR lpszNewContent, int i=0); ///< if the clearTag to update is missing, a new one will be added + XMLClear *updateClear(XMLClear *newP,XMLClear *oldP); ///< if the clearTag to update is missing, a new one will be added + XMLClear *updateClear(XMLCSTR lpszNewValue, XMLCSTR lpszOldValue); ///< if the clearTag to update is missing, a new one will be added + /** @} */ + + /** @defgroup xmlDelete Deleting Nodes or Attributes + * @ingroup xmlModify + * Some deletion functions: + * @{ + */ + /// The "deleteNodeContent" function forces the deletion of the content of this XMLNode and the subtree. + void deleteNodeContent(); + /**< \note The XMLNode instances that are referring to the part of the subtree that has been deleted CANNOT be used anymore!!. Unexpected results will occur if you continue using them. */ + void deleteAttribute(int i=0); ///< Delete the ith attribute of the current XMLNode + void deleteAttribute(XMLCSTR lpszName); ///< Delete the attribute with the given name (the "strcmp" function is used to find the right attribute) + void deleteAttribute(XMLAttribute *anAttribute); ///< Delete the attribute with the name "anAttribute->lpszName" (the "strcmp" function is used to find the right attribute) + void deleteText(int i=0); ///< Delete the Ith text content of the current XMLNode + void deleteText(XMLCSTR lpszValue); ///< Delete the text content "lpszValue" inside the current XMLNode (direct "pointer-to-pointer" comparison is used to find the right text) + void deleteClear(int i=0); ///< Delete the Ith clear tag inside the current XMLNode + void deleteClear(XMLCSTR lpszValue); ///< Delete the clear tag "lpszValue" inside the current XMLNode (direct "pointer-to-pointer" comparison is used to find the clear tag) + void deleteClear(XMLClear *p); ///< Delete the clear tag "p" inside the current XMLNode (direct "pointer-to-pointer" comparison on the lpszName of the clear tag is used to find the clear tag) + /** @} */ + + /** @defgroup xmlWOSD ???_WOSD functions. + * @ingroup xmlModify + * The strings given as parameters for the "add" and "update" methods that have a name with + * the postfix "_WOSD" (that means "WithOut String Duplication")(for example "addText_WOSD") + * will be free'd by the XMLNode class. For example, it means that this is incorrect: + * \code + * xNode.addText_WOSD("foo"); + * xNode.updateAttribute_WOSD("#newcolor" ,NULL,"color"); + * \endcode + * In opposition, this is correct: + * \code + * xNode.addText("foo"); + * xNode.addText_WOSD(stringDup("foo")); + * xNode.updateAttribute("#newcolor" ,NULL,"color"); + * xNode.updateAttribute_WOSD(stringDup("#newcolor"),NULL,"color"); + * \endcode + * Typically, you will never do: + * \code + * char *b=(char*)malloc(...); + * xNode.addText(b); + * free(b); + * \endcode + * ... but rather: + * \code + * char *b=(char*)malloc(...); + * xNode.addText_WOSD(b); + * \endcode + * ('free(b)' is performed by the XMLNode class) + * @{ */ + static XMLNode createXMLTopNode_WOSD(XMLSTR lpszName, char isDeclaration=FALSE); ///< Create the top node of an XMLNode structure + XMLNode addChild_WOSD(XMLSTR lpszName, char isDeclaration=FALSE, XMLElementPosition pos=-1); ///< Add a new child node + XMLAttribute *addAttribute_WOSD(XMLSTR lpszName, XMLSTR lpszValue); ///< Add a new attribute + XMLCSTR addText_WOSD(XMLSTR lpszValue, XMLElementPosition pos=-1); ///< Add a new text content + XMLClear *addClear_WOSD(XMLSTR lpszValue, XMLCSTR lpszOpen=NULL, XMLCSTR lpszClose=NULL, XMLElementPosition pos=-1); ///< Add a new clear Tag + + XMLCSTR updateName_WOSD(XMLSTR lpszName); ///< change node's name + XMLAttribute *updateAttribute_WOSD(XMLAttribute *newAttribute, XMLAttribute *oldAttribute); ///< if the attribute to update is missing, a new one will be added + XMLAttribute *updateAttribute_WOSD(XMLSTR lpszNewValue, XMLSTR lpszNewName=NULL,int i=0); ///< if the attribute to update is missing, a new one will be added + XMLAttribute *updateAttribute_WOSD(XMLSTR lpszNewValue, XMLSTR lpszNewName,XMLCSTR lpszOldName); ///< set lpszNewName=NULL if you don't want to change the name of the attribute if the attribute to update is missing, a new one will be added + XMLCSTR updateText_WOSD(XMLSTR lpszNewValue, int i=0); ///< if the text to update is missing, a new one will be added + XMLCSTR updateText_WOSD(XMLSTR lpszNewValue, XMLCSTR lpszOldValue); ///< if the text to update is missing, a new one will be added + XMLClear *updateClear_WOSD(XMLSTR lpszNewContent, int i=0); ///< if the clearTag to update is missing, a new one will be added + XMLClear *updateClear_WOSD(XMLClear *newP,XMLClear *oldP); ///< if the clearTag to update is missing, a new one will be added + XMLClear *updateClear_WOSD(XMLSTR lpszNewValue, XMLCSTR lpszOldValue); ///< if the clearTag to update is missing, a new one will be added + /** @} */ + + /** @defgroup xmlPosition Position helper functions (use in conjunction with the update&add functions + * @ingroup xmlModify + * These are some useful functions when you want to insert a childNode, a text or a XMLClearTag in the + * middle (at a specified position) of a XMLNode tree already constructed. The value returned by these + * methods is to be used as last parameter (parameter 'pos') of addChild, addText or addClear. + * @{ */ + XMLElementPosition positionOfText(int i=0) const; + XMLElementPosition positionOfText(XMLCSTR lpszValue) const; + XMLElementPosition positionOfClear(int i=0) const; + XMLElementPosition positionOfClear(XMLCSTR lpszValue) const; + XMLElementPosition positionOfClear(XMLClear *a) const; + XMLElementPosition positionOfChildNode(int i=0) const; + XMLElementPosition positionOfChildNode(XMLNode x) const; + XMLElementPosition positionOfChildNode(XMLCSTR name, int i=0) const; ///< return the position of the ith childNode with the specified name if (name==NULL) return the position of the ith childNode + /** @} */ + + /// Enumeration for XML character encoding. + typedef enum XMLCharEncoding + { + char_encoding_error=0, + char_encoding_UTF8=1, + char_encoding_legacy=2, + char_encoding_ShiftJIS=3, + char_encoding_GB2312=4, + char_encoding_Big5=5, + char_encoding_GBK=6 // this is actually the same as Big5 + } XMLCharEncoding; + + /** \addtogroup conversions + * @{ */ + + /// Sets the global options for the conversions + static char setGlobalOptions(XMLCharEncoding characterEncoding=XMLNode::char_encoding_UTF8, char guessWideCharChars=1, + char dropWhiteSpace=1, char removeCommentsInMiddleOfText=1); + /**< The "setGlobalOptions" function allows you to change four global parameters that affect string & file + * parsing. First of all, you most-probably will never have to change these 3 global parameters. + * + * @param guessWideCharChars If "guessWideCharChars"=1 and if this library is compiled in WideChar mode, then the + * XMLNode::parseFile and XMLNode::openFileHelper functions will test if the file contains ASCII + * characters. If this is the case, then the file will be loaded and converted in memory to + * WideChar before being parsed. If 0, no conversion will be performed. + * + * @param guessWideCharChars If "guessWideCharChars"=1 and if this library is compiled in ASCII/UTF8/char* mode, then the + * XMLNode::parseFile and XMLNode::openFileHelper functions will test if the file contains WideChar + * characters. If this is the case, then the file will be loaded and converted in memory to + * ASCII/UTF8/char* before being parsed. If 0, no conversion will be performed. + * + * @param characterEncoding This parameter is only meaningful when compiling in char* mode (multibyte character mode). + * In wchar_t* (wide char mode), this parameter is ignored. This parameter should be one of the + * three currently recognized encodings: XMLNode::encoding_UTF8, XMLNode::encoding_ascii, + * XMLNode::encoding_ShiftJIS. + * + * @param dropWhiteSpace In most situations, text fields containing only white spaces (and carriage returns) + * are useless. Even more, these "empty" text fields are annoying because they increase the + * complexity of the user's code for parsing. So, 99% of the time, it's better to drop + * the "empty" text fields. However The XML specification indicates that no white spaces + * should be lost when parsing the file. So to be perfectly XML-compliant, you should set + * dropWhiteSpace=0. A note of caution: if you set "dropWhiteSpace=0", the parser will be + * slower and your code will be more complex. + * + * @param removeCommentsInMiddleOfText To explain this parameter, let's consider this code: + * \code + * XMLNode x=XMLNode::parseString("foobarchu","a"); + * \endcode + * If removeCommentsInMiddleOfText=0, then we will have: + * \code + * x.getText(0) -> "foo" + * x.getText(1) -> "bar" + * x.getText(2) -> "chu" + * x.getClear(0) --> "" + * x.getClear(1) --> "" + * \endcode + * If removeCommentsInMiddleOfText=1, then we will have: + * \code + * x.getText(0) -> "foobar" + * x.getText(1) -> "chu" + * x.getClear(0) --> "" + * \endcode + * + * \return "0" when there are no errors. If you try to set an unrecognized encoding then the return value will be "1" to signal an error. + * + * \note Sometime, it's useful to set "guessWideCharChars=0" to disable any conversion + * because the test to detect the file-type (ASCII/UTF8/char* or WideChar) may fail (rarely). */ + + /// Guess the character encoding of the string (ascii, utf8 or shift-JIS) + static XMLCharEncoding guessCharEncoding(void *buffer, int bufLen, char useXMLEncodingAttribute=1); + /**< The "guessCharEncoding" function try to guess the character encoding. You most-probably will never + * have to use this function. It then returns the appropriate value of the global parameter + * "characterEncoding" described in the XMLNode::setGlobalOptions. The guess is based on the content of a buffer of length + * "bufLen" bytes that contains the first bytes (minimum 25 bytes; 200 bytes is a good value) of the + * file to be parsed. The XMLNode::openFileHelper function is using this function to automatically compute + * the value of the "characterEncoding" global parameter. There are several heuristics used to do the + * guess. One of the heuristic is based on the "encoding" attribute. The original XML specifications + * forbids to use this attribute to do the guess but you can still use it if you set + * "useXMLEncodingAttribute" to 1 (this is the default behavior and the behavior of most parsers). + * If an inconsistency in the encoding is detected, then the return value is "0". */ + /** @} */ + + private: + // these are functions and structures used internally by the XMLNode class (don't bother about them): + + typedef struct XMLNodeDataTag // to allow shallow copy and "intelligent/smart" pointers (automatic delete): + { + XMLCSTR lpszName; // Element name (=NULL if root) + int nChild, // Number of child nodes + nText, // Number of text fields + nClear, // Number of Clear fields (comments) + nAttribute; // Number of attributes + char isDeclaration; // Whether node is an XML declaration - '' + struct XMLNodeDataTag *pParent; // Pointer to parent element (=NULL if root) + XMLNode *pChild; // Array of child nodes + XMLCSTR *pText; // Array of text fields + XMLClear *pClear; // Array of clear fields + XMLAttribute *pAttribute; // Array of attributes + int *pOrder; // order of the child_nodes,text_fields,clear_fields + int ref_count; // for garbage collection (smart pointers) + } XMLNodeData; + XMLNodeData *d; + + char parseClearTag(void *px, void *pa); + char maybeAddTxT(void *pa, XMLCSTR tokenPStr); + int ParseXMLElement(void *pXML); + void *addToOrder(int memInc, int *_pos, int nc, void *p, int size, XMLElementType xtype); + int indexText(XMLCSTR lpszValue) const; + int indexClear(XMLCSTR lpszValue) const; + XMLNode addChild_priv(int,XMLSTR,char,int); + XMLAttribute *addAttribute_priv(int,XMLSTR,XMLSTR); + XMLCSTR addText_priv(int,XMLSTR,int); + XMLClear *addClear_priv(int,XMLSTR,XMLCSTR,XMLCSTR,int); + void emptyTheNode(char force); + static inline XMLElementPosition findPosition(XMLNodeData *d, int index, XMLElementType xtype); + static int CreateXMLStringR(XMLNodeData *pEntry, XMLSTR lpszMarker, int nFormat); + static int removeOrderElement(XMLNodeData *d, XMLElementType t, int index); + static void exactMemory(XMLNodeData *d); + static int detachFromParent(XMLNodeData *d); +} XMLNode; + +/// This structure is given by the function XMLNode::enumContents. +typedef struct XMLNodeContents +{ + /// This dictates what's the content of the XMLNodeContent + enum XMLElementType etype; + /**< should be an union to access the appropriate data. Compiler does not allow union of object with constructor... too bad. */ + XMLNode child; + XMLAttribute attrib; + XMLCSTR text; + XMLClear clear; + +} XMLNodeContents; + +/** @defgroup StringAlloc String Allocation/Free functions + * @ingroup xmlModify + * @{ */ +/// Duplicate (copy in a new allocated buffer) the source string. +XMLDLLENTRY XMLSTR stringDup(XMLCSTR source, int cbData=-1); +/**< This is + * a very handy function when used with all the "XMLNode::*_WOSD" functions (\link xmlWOSD \endlink). + * @param cbData If !=0 then cbData is the number of chars to duplicate. New strings allocated with + * this function should be free'd using the "freeXMLString" function. */ + +/// to free the string allocated inside the "stringDup" function or the "createXMLString" function. +XMLDLLENTRY void freeXMLString(XMLSTR t); // {free(t);} +/** @} */ + +/** @defgroup atoX ato? like functions + * @ingroup XMLParserGeneral + * The "xmlto?" functions are equivalents to the atoi, atol, atof functions. + * The only difference is: If the variable "xmlString" is NULL, than the return value + * is "defautValue". These 6 functions are only here as "convenience" functions for the + * user (they are not used inside the XMLparser). If you don't need them, you can + * delete them without any trouble. + * + * @{ */ +XMLDLLENTRY char xmltob(XMLCSTR xmlString,char defautValue=0); +XMLDLLENTRY int xmltoi(XMLCSTR xmlString,int defautValue=0); +XMLDLLENTRY long long xmltol(XMLCSTR xmlString,long long defautValue=0); +XMLDLLENTRY double xmltof(XMLCSTR xmlString,double defautValue=.0); +XMLDLLENTRY XMLCSTR xmltoa(XMLCSTR xmlString,XMLCSTR defautValue=_CXML("")); +XMLDLLENTRY XMLCHAR xmltoc(XMLCSTR xmlString,const XMLCHAR defautValue=_CXML('\0')); +/** @} */ + +/** @defgroup ToXMLStringTool Helper class to create XML files using "printf", "fprintf", "cout",... functions. + * @ingroup XMLParserGeneral + * @{ */ +/// Helper class to create XML files using "printf", "fprintf", "cout",... functions. +/** The ToXMLStringTool class helps you creating XML files using "printf", "fprintf", "cout",... functions. + * The "ToXMLStringTool" class is processing strings so that all the characters + * &,",',<,> are replaced by their XML equivalent: + * \verbatim &, ", ', <, > \endverbatim + * Using the "ToXMLStringTool class" and the "fprintf function" is THE most efficient + * way to produce VERY large XML documents VERY fast. + * \note If you are creating from scratch an XML file using the provided XMLNode class + * you must not use the "ToXMLStringTool" class (because the "XMLNode" class does the + * processing job for you during rendering).*/ +typedef struct XMLDLLENTRY ToXMLStringTool +{ +public: + ToXMLStringTool(): buf(NULL),buflen(0){} + ~ToXMLStringTool(); + void freeBuffer();///